C 初探
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 };
2. 函数与程序结构
C 不支持函数定义的嵌套
参数传递传递的是 value (好好理解这句话:传递普通数据,传递指针,传递指针的指针)
作用域规则:
C语言不像Java, Python, 不同文件中的变量都已 文件名.变量名 访问. C 语言中不同文件不能定义相同名称的外部变量.(无法通过编译).
如果要在外部变量的定义之前使用该变量, 或外部变量的定义与变量的使用不再同一个源文件中, 则必须在相应的变量声明中强制性地使用关键字 external.
注意区分 声明 与 定义. 变量声明用于说明变量的属性, 而变量定义还将引起存储器的分配. 因此外部变量的定义必须指定数组的长度, 但声明不一定.
// 定义 int sp; double val[MAXVAL]; // 声明 extern int sp; extern double val[];
用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
数组的大小必须是常量,比如 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) 而且更习惯于后一种形式(因为清晰的表面了传入的是指针)
指向指针的指针
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 、union、typedef、bit 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; }
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
// 注意 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