C 初探

C vs Java

1. 类型, 运算符与表达式

1.1 基本数据类型

char, int, float, double, short int, long int   (可以省略int)

signed 和 unsigned 可以限定 char 类型或任何整形

类型转换

1.2 常量

整型常量: 默认为 int,  L 表示 long 型, U 表示 unsigned 型, UL 表示 unsigned long 型

浮点型常量: 默认为 double, F 表示 float 型, L 表示 long double 型

整型常量还可以用八进制, 十六进制表示: 前缀 0 表示八进制, 前缀 0x 表示十六进制

八进制, 十六进制也可以用 L 表示 long 型, U表示 unsigned 型 e.g: 0XFUL 

 

字符常量: 将一个字符括在单引号中, 如 'x'. '\0' 表示值为0的字符, 即 null

转移字符列表

转义字符
意义
ASCII码值(十进制)
\a
响铃(BEL)
007
\b
退格(BS) ,将当前位置移到前一列
008
\f
换页(FF),将当前位置移到下页开头
012
\n
换行(LF) ,将当前位置移到下一行开头
010
\r
回车(CR) ,将当前位置移到本行开头
013
\t
水平制表(HT) (跳到下一个TAB位置)
009
\v
垂直制表(VT)
011
\\
代表一个反斜线字符''\'
092
\'
代表一个单引号(撇号)字符
039
\"
代表一个双引号字符
034
\0
空字符(NULL)
000
\ddd
1到3位八进制数所代表的任意字符
三位八进制
\xhh
1到2位十六进制所代表的任意字符
二位十六进制

e.g: '\007', '\x7' 均表示响铃

 

字符串常量: e.g: "hello, world"

字符串常量就是字符数组, 字符串的内部使用一个空字符'\0'结尾

注意字符常量与仅含一个字符的字符串常量的区别: 'x' 与 "x"

 

Consts:
const int days_in_week = 7; /*相当于final in Java*/

const 常用在函数的 prototype 中,说明函数的功能

void foo(const struct fraction* fract);   // 表明foo函数并不会改变fract指针指向的内容

 

枚举常量: 枚举是一个常量整型值的列表 (名字与常量值之间建立联系)

e.g: enum boolean {NO, YES} ;

在没有显示说明的情况下, 第一个枚举名的值为0, 然后依次递推. 如果指定了部分枚举名的值, 则依最后一个递推.

e.g: enum months { JAN = 1, FEB, MAR, APP, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };

 

1.3 运算符

 

 

2. 函数与程序结构

C 不支持函数定义的嵌套

参数传递传递的是 value (好好理解这句话:传递普通数据,传递指针,传递指针的指针)

作用域规则:

storage classes in C

C语言不像Java, Python, 不同文件中的变量都已 文件名.变量名 访问. C 语言中不同文件不能定义相同名称的外部变量.(无法通过编译). 

如果要在外部变量的定义之前使用该变量, 或外部变量的定义与变量的使用不再同一个源文件中, 则必须在相应的变量声明中强制性地使用关键字 external.

注意区分 声明 定义. 变量声明用于说明变量的属性, 而变量定义还将引起存储器的分配. 因此外部变量的定义必须指定数组的长度, 但声明不一定.

// 定义
int sp;
double val[MAXVAL];

// 声明
extern int sp;
extern double val[];

 

静态变量(static):

用static来声明一个变量的作用有二:

(1) 外部变量用static来声明,则该变量的作用只限于本文件模块.(这种不同文件可以使用相同变量名)
(2) 对于局部变量用static声明,则是为该变量分配的空间在整个程序的执行期内都始终存在(但只初始化一次)

对于外部变量和静态变量来说, 初始化表达式必须是常量表达式, 而自动变量没有这要求.

 

宏定义 (#define)

#define 名字 替换文本

#define forever for(;;);  // 无限循环
#define square(x) (x) * (x);  

 注意可能传入的参数有副作用(比如含有自增运算符), 以及计算次序.

 

 

C 语言中 可以返回 any type except arrays and functions. 但可以返回 pointer to array or pointer to function.

 

 

3. 指针与数组

Pointer:

Pointer: A variable that contains the address of a variable
int *x;
- Tells compiler that variable x is address of an int
x = &y;
- Tells compiler to assign address of y to x
- & called the "address operator" in this context
z = *x;
- Tells compiler to assign value at address in x to z
- * called the "dereference operator" in this context

// 创建指针
int *p, x;
x = 3;
// 设定指针值
p = &x;
// 更改指针内容
*p = 5;  // p指向的地址的内容变为5 (x变为5)
int y = 5;
p = &y;  // p中的内容为5的地址 (x不变) (这种即为Java中的引用)


// 函数传递指针:
void add_one(int x)
{
    x = x + 1;
}
int y = 3;
add_one(y);  // y仍为3

void add_one(int *p)
{
    *p = *p + 1;
}
int y = 3;
add_one(&y);  // y变为4

pointers can point any kind of data, normally a pointer only points to one type
but void * is a type that can point to anything

 

x = (*p)++ ?
x  = *p; *p = *p + 1

x = *p++ ? (*p++) ? *(p)++ ? *(p++) ? x = *p; p = p + 1

x = *++p ? p = p + 1; x = *p

尽量只用 *p++ , (*p) ++

 

 通过不同类型的 cast, 可以确切控制指针的位置

// assume p is a pointer point to a int
p = p + 12;   // move 48 bytes
p = (int *)( ((char *)p) + 12);    // move 12 bytes

 

Array:

int ar[2]; 
int ar[] = {1, 2};
char string[] = "abc"; // String in C is just an array of characters 

数组表示的字符串可能并不以 '\0' 结尾

数组的大小必须是常量,比如 int ar[n] 就不行,要想使用变量的数组,要在 heap 中分配

 

Array variable is a "pointer" to the first element, so array variable almost identical to pointers 

ar[0] sames as *ar
ar[2] sames as *(ar+2) 

Q: 如果数组中的元素不是一个byte大小会怎么样? A: 因为声明指针时说明了 type, 所以 +1 就是增加相应类型的位数

 

当把数组名传递给一个函数时, 实际上传递的是该数组第一个元素的地址.  即 int strlen(char s[])  等价于 int strlen(char *s)  而且更习惯于后一种形式(因为清晰的表面了传入的是指针)

 

Array vs Pointer

 

指向指针的指针

int n = 3;
int *pn = &n;    // pointer to n
int **ppn = &pn;    // pointer to address of n

 

e.g:

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;        
}

void swap(int **a, int **b) {
    int *temp = *a;
    *a = *b;
    *b = temp;    
}

 e.g:

void inc_ptr(int *p) {
    p = p + 1;    
}

int A[3] = {50, 60, 70};
int *q = A;
inc_ptr(q);
printf("%d\n", *q);

// output: 50

因为这里传递的是值, 所以函数内改变只是改变了拷贝. 要修改q的指向, 需要传递q的指针

void inc_ptr(int **p) {
    *p = *p + 1;    
}

int A[3] = {50, 60, 70};
int *q = A;
inc_ptr(&q);
printf("%d\n", *q);

// output: 60

 

指针数组  

e.g: char *name[] = { "Illegal month", "Jan", "Feb", "Mar" };

注意区分指针数组与二维数组的区别(两者在内存上的分配不同, 指针数组更自由, 其每一行的长度可以不同)

 

函数指针

int *f();     //  f 是一个函数, 它返回一个指向 int 类型的指针
int (*f) ();  //  f 是一个指向函数的指针, 该函数返回一个 int 类型的对象

C 中函数不能穿入函数做参数,但可以传入函数指针做参数。(所以要支持Callback(回调)就传入函数指针)

ps: 函数做指针时,不必用 & 和 *,当然用也没错

int strcmp_wrapper(void * pa, void * pb) {
    return strcmp((const char *)pa, (const char *)pb); 
 }

int (*fp)(void *, void *) = strcmp_wrapper;   // 或 &strcmp_wrapper

int ret = fp(str1, str2);    // 或 (*fp)(str1, str2)

 

函数指针应用举例

以标准库中的qsort为例, qsort的signature为

void qsort(void* arr, int num, int size, int (*fp)(void* pa, void* pb))

qsort 使用举例

int arr[] = {10, 9, 8, 1, 2, 3, 5};
/* callback */
int asc(void* pa, void* pb) {
    return (*(int *)pa - *(int *)pb);        
}

/* callback */
int desc(void* pa, void* pb) {
    return (*(int *)pb - *(int *)pa);
}

qsort(arr, sizeof(arr)/sizeof(int), sizeof(int), asc);  //&asc也行
qsort(arr, sizeof(arr)/sizeof(int), sizeof(int), desc); //&desc也行

 

 

4. Structures 、uniontypedefbit fields

typedef struct {
    int x;
    int y;
} Point;

Point p1;
Point p2;
Point *paddr;

/* dot notation */
int h = p1.x;
p2.y = p1.y;

/* arrow notation */
int h = paddr -> x;
int h = (*paddr).x;

/* structure assignment*/
p2 = p1;

ps:C structure assignment is not a "deep copy". All members are copied, but not things pointed to by members.

 

Structure Member Alignment, Padding

(将structure 中的元素由 size 从大到小或从小到大排列可以减小总大小)

 

Bit Fields 用于Structure中, 目的是节省空间

/* define simple structure */
struct
{
  unsigned int widthValidated;
  unsigned int heightValidated;
} status1;

/* define a structure with bit fields */
struct
{
  unsigned int widthValidated : 1;
  unsigned int heightValidated : 1;
} status2;
 
int main( )
{
   printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
   printf( "Memory size occupied by status2 : %d\n", sizeof(status2));

   return 0;
}

 

 

5. C Memory Management

 

static, global 变量储存在 static data 区

局部变量 储存在 stack 区, 并且当函数返回时释放

 

C 内存管理是指管理 Heap

内存管理有如下几个函数

1 void *calloc(int num, int size);

This function allocates an array of num elements each of which size in bytes will be size.

2 void free(void *address);

This function release a block of memory block specified by address.

3 void *malloc(size_t num);

This function allocates an array of num bytes and leave them initialized.

4 void *realloc(void *address, int newsize);

This function re-allocates memory extending it upto newsize.

切记检查 malloc, calloc, realloc 的返回值是否为 NULL

 

e.g:  没有cast也行,但编译器会给出warnning

int *ip
ip = (int *) malloc(sizeof(int));  
typedef struct { ... } TreeNode;
TreeNode *tp = (TreeNode *) malloc(sizeof(TreeNode));
free((void *) tp);

 

ps:如果内存不够时, malloc() 返回 NULL 指针。

free 内存时,必须保证传递给 free() 是 malloc() 返回的指针 (不能做修改)

 

一个实例:Binary Tree

typedef struct node {
    int key;
    struct node *left;
    struct node *right;
} Node;

Node *root = NULL;

Node *create_node(int key, Node *left, Node *right) {
    Node *np;
    if ( (np = (Node *) malloc(sizeof(Node))) == NULL ) {
        printf("Memory exhausted!\n");
        exit(1);
    } else {
        np->key = key;
        np->left = left;
        np->right = right;
        return np;
    }
}

void insert(int key, Node **tree) {
    if ( (*tree) == NULL ) {
        (*tree) = create_node(key, NULL, NULL); return;
    }
    if (key <= (*tree)->key)
        insert(key, &((*tree)->left));
    else
        insert(key, &((*tree)->right));
}

 

Memory Leak:more mallocs than frees

int *pi;
void foo() {
    pi = malloc(8*sizeof(int));
    free(pi);
}

void main() {
    pi = malloc(4*sizeof(int));
    foo();
}

 

 

 

6. I/O

Standard I/O

// 注意 IO 函数的返回值类型是 int
int putchar(int); 
int getchar(int);

int printf(char* format, arg1, arg2,...)
int scanf(char* format, arg1. arg2,...) 
// 注意 scanf 的arg必须是地址或指针(所以传递数字时常加&, 传递字符串则没事)

 

 File I/O

FILE* fopen(char name[], char model[])
int fclose(FILE* fp);

fopen 返回一个指向文件流的指针(如果不存在则返回 NULL )

Standard I/O 其实就是 FILE* (stdin, stdout)

 

int getc(FILE* fp);    // 从文件流中读取一个字符

返回读取值或EOF(注意返回值类型为 int)

getchar() 就是 getc(stdin)

 

char[] fgets(char line[], int maxlen, FILE* fp)  // 读取一行(包括换行符) 

 返回字符串的指针或EOF

 

int putc(int c, FILE* fp);
int fputs(char line[], FILE* fp);

 

main 函数参数输入

int main(int argc, char* argv[])

args: 参数数量  argv[]:参数  

注意参数包含了程序名(第一个)  

 

 

 

2015-09-19

 

posted @ 2015-09-08 12:05  whu.yt  阅读(247)  评论(0编辑  收藏  举报