diff options
Diffstat (limited to 'zh-cn/c-cn.html.markdown')
| -rw-r--r--[-rwxr-xr-x] | zh-cn/c-cn.html.markdown | 394 | 
1 files changed, 311 insertions, 83 deletions
| diff --git a/zh-cn/c-cn.html.markdown b/zh-cn/c-cn.html.markdown index b4bff8fc..223f6e35 100755..100644 --- a/zh-cn/c-cn.html.markdown +++ b/zh-cn/c-cn.html.markdown @@ -5,37 +5,52 @@ contributors:      - ["Adam Bard", "http://adambard.com/"]  translators:      - ["Chenbo Li", "http://binarythink.net/"] +    - ["Jakukyo Friel", "http://weakish.github.io"]  lang: zh-cn  ---  C语言在今天仍然是高性能计算的主要选择。 -C大概是大多数程序员用到的最接近底层的语言了,但是C语言本身不仅可以用来提升程序运行的速度 -注意看看C语言的文档,你就会知道C语言在内存管理方面的强大也是其他语言无法比拟的。 +C大概是大多数程序员用到的最接近底层的语言了,C语言原生的速度就很高了,但是别忘了C的手动内存管理,它会让你将性能发挥到极致。  ```c -// 用“//”来实现单行注释 +// 单行注释以//开始。(仅适用于C99或更新的版本。)  /* -多行注释是这个样子的 +多行注释是这个样子的。(C89也适用。)  */ +// 常数: #define 关键词 +#define DAYS_IN_YEAR 365 + +// 以枚举的方式定义常数 +enum days {SUN = 1, MON, TUE, WED, THU, FRI, SAT}; +// MON自动被定义为2,TUE被定义为3,以此类推。 +  // 用#include来导入头文件  #include <stdlib.h>  #include <stdio.h>  #include <string.h> -// 函数的标签(signature)应该放在.h文件中,并且引入到程序顶部 -// 也可以直接放到你的.c文件的最上面 -void function_1(); -void function_2(); +// <尖括号>间的文件名是C标准库的头文件。 +// 标准库以外的头文件,使用双引号代替尖括号。 +#include "my_header.h" + +// 函数的签名可以事先在.h文件中定义, +// 也可以直接在.c文件的头部定义。 +void function_1(char c); +void function_2(void); -// c程序的入口是一个返回值为int型的函数,名字叫做main +// 如果函数出现在main()之后,那么必须在main()之前 +// 先声明一个函数原型 +int add_two_ints(int x1, int x2); // 函数原型 + +// 你的程序的入口是一个返回值为整型的main函数  int main() { -// 用printf来实现标准输出,这种输出也可以用格式来控制 -// %d 代表一个整数, \n 代表一个新行 -printf("%d\n", 0); // => 输出 0 +// 用printf打印到标准输出,可以设定格式, +// %d 代表整数, \n 代表换行 +printf("%d\n", 0); // => 打印 0  // 所有的语句都要以分号结束  /////////////////////////////////////// @@ -69,18 +84,29 @@ double x_double = 0.0;  // 整数类型也可以有无符号的类型表示。这样这些变量就无法表示负数  // 但是无符号整数所能表示的范围就可以比原来的整数大一些 -unsigned char ux_char;  unsigned short ux_short;  unsigned int ux_int;  unsigned long long ux_long_long; +// 单引号内的字符是机器的字符集中的整数。 +'0' // => 在ASCII字符集中是48 +'A' // => 在ASCII字符集中是65 +  // char类型一定会占用1个字节,但是其他的类型却会因具体机器的不同而各异  // sizeof(T) 可以返回T类型在运行的机器上占用多少个字节   // 这样你的代码就可以在各处正确运行了 -// 比如 -printf("%lu\n", sizeof(int)); // => 4 (字长为4的机器上) - -// 数组必须要在开始被初始化为特定的长度 +// sizeof(obj)返回表达式(变量、字面量等)的尺寸 +printf("%zu\n", sizeof(int)); // => 4 (大多数的机器字长为4) + +// 如果`sizeof`的参数是一个表达式,那么这个参数不会被演算(VLA例外,见下) +// 它产生的值是编译期的常数 +int a = 1; +// size_t是一个无符号整型,表示对象的尺寸,至少2个字节 +size_t size = sizeof(a++); // a++ 不会被演算 +printf("sizeof(a++) = %zu where a = %d\n", size, a); +// 打印 "sizeof(a++) = 4 where a = 1" (在32位架构上) + +// 数组必须要被初始化为具体的长度  char my_char_array[20]; // 这个数组占据 1 * 20 = 20 个字节  int my_int_array[20]; // 这个数组占据 4 * 20 = 80 个字节                        // (这里我们假设字长为4) @@ -89,48 +115,83 @@ int my_int_array[20]; // 这个数组占据 4 * 20 = 80 个字节  // 可以用下面的方法把数组初始化为0:  char my_array[20] = {0}; -// 对数组任意存取就像其他语言的方式 -- 其实是其他的语言像C +// 索引数组和其他语言类似 -- 好吧,其实是其他的语言像C  my_array[0]; // => 0  // 数组是可变的,其实就是内存的映射!  my_array[1] = 2;  printf("%d\n", my_array[1]); // => 2 +// 在C99 (C11中是可选特性),变长数组(VLA)也可以声明长度。 +// 其长度不用是编译期常量。 +printf("Enter the array size: "); // 询问用户数组长度 +char buf[0x100]; +fgets(buf, sizeof buf, stdin); + +// stroul 将字符串解析为无符号整数 +size_t size = strtoul(buf, NULL, 10); +int var_length_array[size]; // 声明VLA +printf("sizeof array = %zu\n", sizeof var_length_array); + +// 上述程序可能的输出为: +// > Enter the array size: 10 +// > sizeof array = 40 +  // 字符串就是以 NUL (0x00) 这个字符结尾的字符数组, -// 这个字符可以用'\0'来表示. -// (在字符串字面值中我们不必输入这个字符,编译器会自动添加的) +// NUL可以用'\0'来表示. +// (在字符串字面量中我们不必输入这个字符,编译器会自动添加的)  char a_string[20] = "This is a string";  printf("%s\n", a_string); // %s 可以对字符串进行格式化 -  /*  也许你会注意到 a_string 实际上只有16个字节长.  第17个字节是一个空字符(NUL)  -而第18, 19 和 20 个字符的值是不确定的。 +而第18, 19 和 20 个字符的值是未定义。  */  printf("%d\n", a_string[16]); // => 0 +//  byte #17值为0(18,19,20同样为0) + +// 单引号间的字符是字符字面量 +// 它的类型是`int`,而 *不是* `char` +// (由于历史原因) +int cha = 'a'; // 合法 +char chb = 'a'; // 同样合法 (隐式类型转换 + +// 多维数组 +int multi_array[2][5] = { +        {1, 2, 3, 4, 5}, +        {6, 7, 8, 9, 0} +    } +// 获取元素 +int array_int = multi_array[0][2]; // => 3  ///////////////////////////////////////  // 操作符  /////////////////////////////////////// -int i1 = 1, i2 = 2; // 多个变量声明的简写 +// 多个变量声明的简写 +int i1 = 1, i2 = 2;  float f1 = 1.0, f2 = 2.0; -// 算数运算 +int a, b, c; +a = b = c = 0; + +// 算数运算直截了当  i1 + i2; // => 3  i2 - i1; // => 1  i2 * i1; // => 2 -i1 / i2; // => 0 (0.5 会被化整为 0) +i1 / i2; // => 0 (0.5,但会被化整为 0)  f1 / f2; // => 0.5, 也许会有很小的误差 +// 浮点数和浮点数运算都是近似值  // 取余运算  11 % 3; // => 2 -// 比较操作符我们也很熟悉, 但是有一点,C中没有布尔类型 +// 你多半会觉得比较操作符很熟悉, 不过C中没有布尔类型  // 而是用整形替代 -// 0 就是 false, 其他的就是 true. (比较操作符的返回值则仅有0和1) +// (C99中有_Bool或bool。) +// 0为假, 其他均为真. (比较操作符的返回值总是返回0或1)  3 == 2; // => 0 (false)  3 != 2; // => 1 (true)  3 > 2; // => 1 @@ -138,7 +199,14 @@ f1 / f2; // => 0.5, 也许会有很小的误差  2 <= 2; // => 1  2 >= 2; // => 1 -// 逻辑运算符需要作用于整数 +// C不是Python —— 连续比较不合法 +int a = 1; +// 错误 +int between_0_and_2 = 0 < a < 2; +// 正确 +int between_0_and_2 = 0 < a && a < 2; + +// 逻辑运算符适用于整数  !3; // => 0 (非)  !0; // => 1  1 && 1; // => 1 (且) @@ -146,6 +214,20 @@ f1 / f2; // => 0.5, 也许会有很小的误差  0 || 1; // => 1 (或)  0 || 0; // => 0 +// 条件表达式 ( ? : ) +int a = 5; +int b = 10; +int z; +z = (a > b) ? a : b; //  10 “若a > b返回a,否则返回b。” + +// 增、减 +char *s = "iLoveC" +int j = 0; +s[j++]; // "i" 返回s的第j项,然后增加j的值。 +j = 0; +s[++j]; // => "L"  增加j的值,然后返回s的第j项。 +// j-- 和 --j 同理 +  // 位运算  ~0x0F; // => 0xF0 (取反)  0x0F & 0xF0; // => 0x00 (和) @@ -154,6 +236,13 @@ f1 / f2; // => 0.5, 也许会有很小的误差  0x01 << 1; // => 0x02 (左移1位)  0x02 >> 1; // => 0x01 (右移1位) +// 对有符号整数进行移位操作要小心 —— 以下未定义: +// 有符号整数位移至符号位 int a = 1 << 32 +// 左移位一个负数 int a = -1 << 2 +// 移位超过或等于该类型数值的长度 +// int a = 1 << 32; // 假定int32位 + +  ///////////////////////////////////////  // 控制结构  /////////////////////////////////////// @@ -168,17 +257,17 @@ if (0) {  // While循环  int ii = 0; -while (ii < 10) { +while (ii < 10) { // 任何非0的值均为真      printf("%d, ", ii++); // ii++ 在取值过后自增 -} // => 输出 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " +} // =>  打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "  printf("\n");  int kk = 0;  do {      printf("%d, ", kk); -} while (++kk < 10); // ++kk 先自增,在被取值 -// => 输出 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " +} while (++kk < 10); // ++kk 先自增,再被取值 +// => 打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "  printf("\n"); @@ -186,29 +275,55 @@ printf("\n");  int jj;  for (jj=0; jj < 10; jj++) {      printf("%d, ", jj); -} // => 输出 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, " +} // => 打印 "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "  printf("\n"); +// *****注意*****: +// 循环和函数必须有主体部分,如果不需要主体部分: +int i; +    for (i = 0; i <= 5; i++) { +    ; // 使用分号表达主体(null语句) +} + +// 多重分支:switch() +switch (some_integral_expression) { +case 0: // 标签必须是整数常量表达式 +    do_stuff(); +    break; // 如果不使用break,控制结构会继续执行下面的标签 +case 1: +    do_something_else(); +    break; +default: +    // 假设 `some_integral_expression` 不匹配任何标签 +    fputs("error!\n", stderr); +    exit(-1); +    break; +    } +  ///////////////////////////////////////  // 类型转换  ///////////////////////////////////////  // 在C中每个变量都有类型,你可以将变量的类型进行转换 +// (有一定限制) -int x_hex = 0x01; // 可以用16进制赋值 +int x_hex = 0x01; // 可以用16进制字面量赋值  // 在类型转换时,数字本身的值会被保留下来 -printf("%d\n", x_hex); // => 输出 1 -printf("%d\n", (short) x_hex); // => 输出 1 -printf("%d\n", (char) x_hex); // => 输出 1 +printf("%d\n", x_hex); // => 打印 1 +printf("%d\n", (short) x_hex); // => 打印 1 +printf("%d\n", (char) x_hex); // => 打印 1  // 类型转换时可能会造成溢出,而且不会抛出警告 -printf("%d\n", (char) 257); // => 1 (char的最大值为255) +printf("%d\n", (char) 257); // => 1 (char的最大值为255,假定char为8位长) + +// 使用<limits.h>提供的CHAR_MAX、SCHAR_MAX和UCHAR_MAX宏可以确定`char`、`signed_char`和`unisigned char`的最大值。 +  // 整数型和浮点型可以互相转换 -printf("%f\n", (float)100); // %f 表示单精度浮点 -printf("%lf\n", (double)100); // %lf 表示双精度浮点 +printf("%f\n", (float)100); // %f 格式化单精度浮点 +printf("%lf\n", (double)100); // %lf 格式化双精度浮点  printf("%d\n", (char)100.0);  /////////////////////////////////////// @@ -216,67 +331,89 @@ printf("%d\n", (char)100.0);  ///////////////////////////////////////  // 指针变量是用来储存内存地址的变量 -// 指针变量的定义也会告诉你指向的地址的变量的类型  -// 你也可以得到某个变量的地址,并对它们进行操作 +// 指针变量的声明也会告诉它所指向的数据的类型 +// 你可以使用得到你的变量的地址,并把它们搞乱,;-)  int x = 0;  printf("%p\n", &x); // 用 & 来获取变量的地址 -// (%p 表示一个指针) -// => 输出某个内存地址 +// (%p 格式化一个类型为 void *的指针) +// => 打印某个内存地址 -// 指针类型在定义是需要以*结束 -int* px; // px是一个指向int型的指针 +// 指针类型在声明中以*开头 +int* px, not_a_pointer; // px是一个指向int型的指针  px = &x; // 把x的地址保存到px中 -printf("%p\n", px); // => 输出内存中的某个地址 +printf("%p\n", (void *)px); // => 输出内存中的某个地址 +printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer)); +// => 在64位系统上打印“8, 4”。 -// 要得到某个指针指向的内容的值,可以在指针前加一个*来取得(去引用) +// 要得到某个指针指向的内容的值,可以在指针前加一个*来取得(取消引用) +// 注意: 是的,这可能让人困惑,'*'在用来声明一个指针的同时取消引用它。  printf("%d\n", *px); // => 输出 0, 即x的值  // 你也可以改变指针所指向的值 -// 此时你需要在*运算符后添加一个括号,因为++比*的优先级更高 -(*px)++; // 把px所指向的值增加2 +// 此时你需要取消引用上添加括号,因为++比*的优先级更高 +(*px)++; // 把px所指向的值增加1  printf("%d\n", *px); // => 输出 1  printf("%d\n", x); // => 输出 1 -int x_array[20]; // 数组是分配一系列连续空间的常用方式 +// 数组是分配一系列连续空间的常用方式 +int x_array[20];  int xx;  for (xx=0; xx<20; xx++) {      x_array[xx] = 20 - xx;  } // 初始化 x_array 为 20, 19, 18,... 2, 1 -// 生命一个变量为指向整型的指针类型,并初始化为指向x_array +// 声明一个整型的指针,并初始化为指向x_array  int* x_ptr = x_array;  // x_ptr现在指向了数组的第一个元素(即整数20).  - -// 事实上数组本身就是指向它的第一个元素的指针 -printf("%d\n", *(x_ptr)); // => 输出 20 -printf("%d\n", x_array[0]); // => 输出 20 +// 这是因为数组通常衰减为指向它们的第一个元素的指针。 +// 例如,当一个数组被传递给一个函数或者绑定到一个指针时, +//它衰减为(隐式转化为)一个指针。 +// 例外: 当数组是`&`操作符的参数: +int arr[10]; +int (*ptr_to_arr)[10] = &arr; // &arr的类型不是`int *`! +                              // 它的类型是指向数组的指针(数组由10个int组成) +// 或者当数组是字符串字面量(初始化字符数组) +char arr[] = "foobarbazquirk"; +// 或者当它是`sizeof`或`alignof`操作符的参数时: +int arr[10]; +int *ptr = arr; // 等价于 int *ptr = &arr[0]; +printf("%zu, %zu\n", sizeof arr, sizeof ptr); // 应该会输出"40, 4"或"40, 8"  // 指针的增减多少是依据它本身的类型而定的 -printf("%d\n", *(x_ptr + 1)); // => 输出 19 -printf("%d\n", x_array[1]); // => 输出 19 +// (这被称为指针算术) +printf("%d\n", *(x_ptr + 1)); // => 打印 19 +printf("%d\n", x_array[1]); // => 打印 19  // 你也可以通过标准库函数malloc来实现动态分配 -// 这个函数接受一个代表容量的参数  -// 系统会从堆区分配指定容量字节大小的空间 -int* my_ptr = (int*) malloc(sizeof(int) * 20); +// 这个函数接受一个代表容量的参数,参数类型为`size_t` +// 系统一般会从堆区分配指定容量字节大小的空间 +// (在一些系统,例如嵌入式系统中这点不一定成立 +// C标准对此未置一词。) +int *my_ptr = malloc(sizeof(*my_ptr) * 20);  for (xx=0; xx<20; xx++) { -    *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx 也可以 -} // 初始化内存为 20, 19, 18, 17... 2, 1 (as ints) +    *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx +} // 初始化内存为 20, 19, 18, 17... 2, 1 (类型为int) -// 如果对一些未分配的内存取值则会得到未知的结果 +// 对未分配的内存进行取消引用会产生未定义的结果  printf("%d\n", *(my_ptr + 21)); // => 谁知道会输出什么 -// 当你通过malloc得到一块区域后,你需要释放它 +// malloc分配的区域需要手动释放  // 否则没人能够再次使用这块内存,直到程序结束为止  free(my_ptr);  // 字符串通常是字符数组,但是经常用字符指针表示 -// 指针: -char* my_str = "This is my very own string"; - +// (它是指向数组的第一个元素的指针) +// 一个优良的实践是使用`const char *`来引用一个字符串字面量, +// 因为字符串字面量不应当被修改(即"foo"[0] = 'a'犯了大忌) +const char* my_str = "This is my very own string";  printf("%c\n", *my_str); // => 'T' +// 如果字符串是数组,(多半是用字符串字面量初始化的) +// 情况就不一样了,字符串位于可写的内存中 +char foo[] = "foo"; +foo[0] = 'a'; // 这是合法的,foo现在包含"aoo" +  function_1();  } // main函数结束 @@ -292,16 +429,21 @@ int add_two_ints(int x1, int x2){  }  /* -函数是按值传递的, 但是你可以通过传递参数来传递引用,这样函数就可以更改值 +函数是按值传递的。当调用一个函数的时候,传递给函数的参数 +是原有值的拷贝(数组除外)。你在函数内对参数所进行的操作 +不会改变该参数原有的值。 + +但是你可以通过指针来传递引用,这样函数就可以更改值  例子:字符串本身翻转  */  // 类型为void的函数没有返回值 -void str_reverse(char* str_in){ +void str_reverse(char *str_in){      char tmp; -    int ii=0, len = strlen(str_in); // Strlen 是C标准库函数 -    for(ii=0; ii<len/2; ii++){ +    int ii = 0; +    size_t len = strlen(str_in); // `strlen()`` 是C标准库函数 +    for(ii = 0; ii < len / 2; ii++){          tmp = str_in[ii];          str_in[ii] = str_in[len - ii - 1]; // 从倒数第ii个开始          str_in[len - ii - 1] = tmp; @@ -314,6 +456,20 @@ str_reverse(c);  printf("%s\n", c); // => ".tset a si sihT"  */ +// 如果引用函数之外的变量,必须使用extern关键字 +int i = 0; +void testFunc() { +    extern int i; // 使用外部变量 i +} + +// 使用static确保external变量为源文件私有 +static int i = 0; // 其他使用 testFunc()的文件无法访问变量i +void testFunc() { +    extern int i; +} +//**你同样可以声明函数为static** + +  ///////////////////////////////////////  // 用户自定义类型和结构  /////////////////////////////////////// @@ -322,12 +478,16 @@ printf("%s\n", c); // => ".tset a si sihT"  typedef int my_type;  my_type my_type_var = 0; -// 结构是一系列数据的集合 +// struct是数据的集合,成员依序分配,按照 +// 编写的顺序  struct rectangle {      int width;      int height;  }; +// 一般而言,以下断言不成立: +// sizeof(struct rectangle) == sizeof(int) + sizeof(int) +//这是因为structure成员之间可能存在潜在的间隙(为了对齐)[1]  void function_1(){ @@ -338,12 +498,12 @@ void function_1(){      my_rec.height = 20;      // 你也可以声明指向结构体的指针 -    struct rectangle* my_rec_ptr = &my_rec; +    struct rectangle *my_rec_ptr = &my_rec; -    // 通过取值来改变结构体的成员... +    // 通过取消引用来改变结构体的成员...      (*my_rec_ptr).width = 30; -    // ... 或者用 -> 操作符作为简写 +    // ... 或者用 -> 操作符作为简写提高可读性      my_rec_ptr->height = 10; // Same as (*my_rec_ptr).height = 10;  } @@ -354,18 +514,25 @@ int area(rect r){      return r.width * r.height;  } +// 如果struct较大,你可以通过指针传递,避免 +// 复制整个struct。 +int area(const rect *r) +{ +    return r->width * r->height; +} +  ///////////////////////////////////////  // 函数指针  ///////////////////////////////////////  /*  在运行时,函数本身也被存放到某块内存区域当中 -函数指针就像其他指针一样, 但却可以被用来直接调用函数, -并且可以被四处传递(就像回调函数那样) -但是,定义的语法有可能在一开始会有些误解 +函数指针就像其他指针一样(不过是存储一个内存地址) 但却可以被用来直接调用函数, +并且可以四处传递回调函数 +但是,定义的语法初看令人有些迷惑  例子:通过指针调用str_reverse  */ -void str_reverse_through_pointer(char * str_in) { +void str_reverse_through_pointer(char *str_in) {      // 定义一个函数指针 f.       void (*f)(char *); // 签名一定要与目标函数相同      f = &str_reverse; // 将函数的地址在运行时赋给指针 @@ -374,7 +541,7 @@ void str_reverse_through_pointer(char * str_in) {  }  /* -只要函数签名是正确的,任何时候都能将正确的函数赋给某个函数指针 +只要函数签名是正确的,任何时候都能将任何函数赋给某个函数指针  为了可读性和简洁性,函数指针经常和typedef搭配使用:  */ @@ -384,12 +551,73 @@ typedef void (*my_fnp_type)(char *);  // ...  // my_fnp_type f;  +// 特殊字符 +'\a' // bell +'\n' // 换行 +'\t' // tab +'\v' // vertical tab +'\f' // formfeed +'\r' // 回车 +'\b' // 退格 +'\0' // null,通常置于字符串的最后。 +     //   hello\n\0. 按照惯例,\0用于标记字符串的末尾。 +'\\' // 反斜杠 +'\?' // 问号 +'\'' // 单引号 +'\"' // 双引号 +'\xhh' // 十六进制数字. 例子: '\xb' = vertical tab +'\ooo' // 十进制数字. 例子: '\013' = vertical tab + +// 打印格式: +"%d"    // 整数 +"%3d"   // 3位以上整数 (右对齐文本) +"%s"    // 字符串 +"%f"    // float +"%ld"   // long +"%3.2f" // 左3位以上、右2位以上十进制浮 +"%7.4s" // (字符串同样适用) +"%c"    // 字母 +"%p"    // 指针 +"%x"    // 十六进制 +"%o"    // 十进制 +"%%"    // 打印 % + +/////////////////////////////////////// +// 演算优先级 +/////////////////////////////////////// +//---------------------------------------------------// +//        操作符                     | 组合          // +//---------------------------------------------------// +// () [] -> .                        | 从左到右      // +// ! ~ ++ -- + = *(type)sizeof       | 从右到左      // +// * / %                             | 从左到右      // +// + -                               | 从左到右      // +// << >>                             | 从左到右      // +// < <= > >=                         | 从左到右      // +// == !=                             | 从左到右      // +// &                                 | 从左到右      // +// ^                                 | 从左到右      // +// |                                 | 从左到右      // +// &&                                | 从左到右      // +// ||                                | 从左到右      // +// ?:                                | 从右到左      // +// = += -= *= /= %= &= ^= |= <<= >>= | 从右到左      // +// ,                                 | 从左到右      // +//---------------------------------------------------// +  ```  ## 更多阅读 -最好找一本 [K&R, aka "The C Programming Language", “C程序设计语言”](https://en.wikipedia.org/wiki/The_C_Programming_Language) +最好找一本 [K&R, aka "The C Programming Language", “C程序设计语言”](https://en.wikipedia.org/wiki/The_C_Programming_Language)。它是关于C最重要的一本书,由C的创作者撰写。不过需要留意的是它比较古老了,因此有些不准确的地方。 + -其他一些比较好的资源 [Learn C the hard way](http://c.learncodethehardway.org/book/) +另一个比较好的资源是 [Learn C the hard way](http://c.learncodethehardway.org/book/) +如果你有问题,请阅读[compl.lang.c Frequently Asked Questions](http://c-faq.com/)。 + +使用合适的空格、缩进,保持一致的代码风格非常重要。可读性强的代码比聪明的代码、高速的代码更重要。可以参考下[Linux内核编码风格](https://www.kernel.org/doc/Documentation/CodingStyle) +。  除了这些,多多Google吧 + +[1] http://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member | 
