X分钟速成Y 其中 Y=C (中英对照版)

原文链接:
https://www.zybuluo.com/zhongdao/note/1223304

根据原文翻译。

中英对照版

Ah, C. Still the language of modern high-performance computing.
C语言在今天仍然是高性能计算的主要选择。

C is the lowest-level language most programmers will ever use, but it more than makes up for it with raw speed. Just be aware of its manual memory management and C will take you as far as you need to go.
C大概是大多数程序员用到的最接近底层的语言了,C语言原生的速度就很高了,但是别忘了C的手动内存管理,它会让你将性能发挥到极致。

About compiler flags
关于编译器参数

By default, gcc and clang are pretty quiet about compilation warnings and errors, which can be very useful information. Explicitly using stricter compiler flags is recommended. Here are some recommended defaults:
缺省情况下,gcc 和 clang是没有编译的警告和错误信息,而这些含有很多信息。 所以推荐使用更严格的编译器参数。

-Wall -Wextra -Werror -O2 -std=c99 -pedantic

For information on what these flags do as well as other flags, consult the man page for your C compiler (e.g. man 1 gcc) or just search online.
为了了解参数的信息,可以查看man信息,或者在线搜索。

// Single-line comments start with // - only available in C99 and later.
// 单行注释以//开始。(仅适用于C99或更新的版本。)

/*
Multi-line comments look like this. They work in C89 as well.
多行注释是这个样子的。(C89也适用。)
*/

/*
Multi-line comments don't nest /* Be careful */  // comment ends on this line...
多行的注释不支持嵌套,所以注释在上一行结束。
*/ // ...not this one!   这一行不是结束。

// Constants: #define <keyword>
// 常量: #define 关键词
// Constants are written in all-caps out of convention, not requirement
// 常量约定全部是大写,但不是必须。
#define DAYS_IN_YEAR 365

// Enumeration constants are also ways to declare constants.
// All statements must end with a semicolon
// 以枚举的形式来定义常量,所有的声明以分号结束。
enum days {SUN = 1, MON, TUE, WED, THU, FRI, SAT};
// MON gets 2 automatically, TUE gets 3, etc.
// MON 的值自动是2,TUE值是3,等等

// Import headers with #include
// 用#include来导入头文件
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

// (File names between <angle brackets> are headers from the C standard library.)
// For your own headers, use double quotes instead of angle brackets:
// (尖括号中的文件名是C标准库中的头文件)
//  自己的头文件,采用双引号来代替尖括号。
//#include "my_header.h"


// Declare function signatures in advance in a .h file, or at the top of
// your .c file.
// 在.h文件中提前声明函数(或称为签名),或者在.c文件的头部。
void function_1();
int function_2(void);

// Must declare a 'function prototype' before main() when functions occur after
// your main() function.
// 如果函数出现在main()之后,那么必须在main()之前先声明一个函数原型
int add_two_ints(int x1, int x2); // function prototype
// although `int add_two_ints(int, int);` is also valid (no need to name the args),
// it is recommended to name arguments in the prototype as well for easier inspection
// 虽然不带参数名称的函数声明是合法的,但是推荐在原型中命名参数,方便检查。

// Your program's entry point is a function called
// main with an integer return type.
// 程序的入口是一个返回值为整型的main函数
int main(void) {
  // your program
}

// The command line arguments used to run your program are also passed to main
// argc being the number of arguments - your program's name counts as 1
// argv is an array of character arrays - containing the arguments themselves
// argv[0] = name of your program, argv[1] = first argument, etc.
// 命令行参数被传给main,  argc是参数个数,程序本身名字计数为1。 argv是字符数组的数组, 
// argv[0] 是程序本身名字, argv[1] 是第一个参数。

int main (int argc, char** argv)
{
  // print output using printf, for "print formatted"
  // %d is an integer, \n is a newline
  // 用printf打印到标准输出,可以设定格式,%d 代表整数, \n 代表换行
  printf("%d\n", 0); // => Prints 0

  ///////////////////////////////////////
  // Types 类型
  ///////////////////////////////////////

  // All variables MUST be declared at the top of the current block scope 
  // 在使用变量之前我们必须先声明它们。 
  // we declare them dynamically along the code for the sake of the tutorial
  // (however, C99-compliant compilers allow declarations near the point where the value is used)
  // 为了本教程的目的,我们沿着代码动态声明。
  // (但是,符合C99的编译器允许在值使用的地方声明)
  // ints are usually 4 bytes 整数通常是4字节
  int x_int = 0;

  // shorts are usually 2 bytes 短整通常2字节
  short x_short = 0;

  // chars are guaranteed to be 1 byte 字符保证1字节
  char x_char = 0;
  char y_char = 'y'; // Char literals are quoted with ''  字符变量需单引号包住

  // longs are often 4 to 8 bytes; long longs are guaranteed to be at least 8 bytes
  // 长整型一般需要4个字节到8个字节;而long long型则需要至少8个字节
  long x_long = 0;
  long long x_long_long = 0;

  // floats are usually 32-bit floating point numbers 一般用32位表示浮点数字
  float x_float = 0.0f; // 'f' suffix here denotes floating point literal。后缀'f'表示浮点数

  // doubles are usually 64-bit floating-point numbers 一般使用64位表示的浮点数字
  double x_double = 0.0; // real numbers without any suffix are doubles

  // integer types may be unsigned (greater than or equal to zero) 整数类型也可以无符号表示。(大于或等于零)
  unsigned short ux_short;
  unsigned int ux_int;
  unsigned long long ux_long_long;

  // chars inside single quotes are integers in machine's character set. 单引号中的char类型是机器的字符集中的整数。
  '0'; // => 48 in the ASCII character set.
  'A'; // => 65 in the ASCII character set.

  // sizeof(T) gives you the size of a variable with type T in bytes  提供类型为T的变量的大小
  // sizeof(obj) yields the size of the expression (variable, literal, etc.). 生成表达式的大小(变量,文字等)
  printf("%zu\n", sizeof(int)); // => 4 (on most machines with 4-byte words)(在大多数4字节字的机器上)

  // If the argument of the `sizeof` operator is an expression, then its argument is not evaluated (except VLAs (see below)).
  //如果`sizeof`运算符的参数是一个表达式,那么它的参数不会被计算(除了VLA(见下文))。
  // The value it yields in this case is a compile-time constant.
  // 在这种情况下它产生的值是编译时常量。
  int a = 1;
  // size_t is an unsigned integer type of at least 2 bytes used to represent the size of an object. 
  //  size_t是一个至少2个字节的无符号整数类型,用于表示对象的大小。
  size_t size = sizeof(a++); // a++ is not evaluated
  printf("sizeof(a++) = %zu where a = %d\n", size, a);
  // prints "sizeof(a++) = 4 where a = 1" (on a 32-bit architecture)

  // Arrays must be initialized with a concrete size. 
  // 必须使用具体大小初始化数组
  char my_char_array[20]; // This array occupies 1 * 20 = 20 bytes 这个数组占20字节
  int my_int_array[20]; // This array occupies 4 * 20 = 80 bytes 这个数组占80字节
  // (assuming 4-byte words) 假设4字节字

  // You can initialize an array to 0 thusly:
  // 你可以将数组初始化为0:
  char my_array[20] = {0};

  // Indexing an array is like other languages -- or,rather, other languages are like C
  //  索引数组和其他语言类似 -- 好吧,其实是其他的语言像C
  my_array[0]; // => 0

  // Arrays are mutable; it's just memory!
  // 数组是可变的,其实就是内存的映射!
  my_array[1] = 2;
  printf("%d\n", my_array[1]); // => 2

  // In C99 (and as an optional feature in C11), variable-length arrays (VLAs) can be declared as well. The size of such an array need not be a compile time constant:
  // 在C99 (C11中是可选特性),变长数组(VLA)也可以声明长度。其长度不用是编译期常量。
  printf("Enter the array size: "); // ask the user for an array size 询问用户数组长度
  int array_size;
  fscanf(stdin, "%d", &array_size); 
  int var_length_array[array_size]; // declare the VLA 声明VLA
  printf("sizeof array = %zu\n", sizeof var_length_array);

  // Example:
  // > Enter the array size: 10
  // > sizeof array = 40

  // Strings are just arrays of chars terminated by a NULL (0x00) byte, represented in strings as the special character '\0'.
  // 字符串就是以 NUL (0x00) 这个字符结尾的字符数组,  NUL可以用'\0'来表示. 
  // (We don't have to include the NULL byte in string literals; the compiler inserts it at the end of the array for us.)
  // (在字符串字面量中我们不必输入这个字符,编译器会自动添加的) 

  char a_string[20] = "This is a string";
  printf("%s\n", a_string); // %s formats a string

  printf("%d\n", a_string[16]); // => 0
  // i.e., byte #17 is 0 (as are 18, 19, and 20)
  // 例如,17字节是0,NUL, 而18,19,20也一样。

  // If we have characters between single quotes, that's a character literal.
  // It's of type `int`, and *not* `char` (for historical reasons).
  // 单引号间的字符是字符字面量
  // 它的类型是'int', 而不是'char'(由于历史原因)
  int cha = 'a'; // fine  合法
  char chb = 'a'; // fine too (implicit conversion from int to char) 同样合法 (隐式类型转换

  // Multi-dimensional arrays: 多维数组
  int multi_array[2][5] = {
    {1, 2, 3, 4, 5},
    {6, 7, 8, 9, 0}
  };
  // access elements: 获取元素
  int array_int = multi_array[0][2]; // => 3

  ///////////////////////////////////////
  // Operators 操作符
  ///////////////////////////////////////

  // Shorthands for multiple declarations: 多个变量声明的简写
  int i1 = 1, i2 = 2;
  float f1 = 1.0, f2 = 2.0;

  int b, c;
  b = c = 0;

  // Arithmetic is straightforward 算数运算直截了当
  i1 + i2; // => 3
  i2 - i1; // => 1
  i2 * i1; // => 2
  i1 / i2; // => 0 (0.5, but truncated towards 0) (0.5,但会被化整为 0)

  // You need to cast at least one integer to float to get a floating-point result
  // 你需要把整型转换成浮点来得到一个浮点结果。
  (float)i1 / i2; // => 0.5f  
  i1 / (double)i2; // => 0.5 // Same with double
  f1 / f2; // => 0.5, plus or minus epsilon  也许会有很小的误差
  // Floating-point numbers and calculations are not exact
  // 浮点数和浮点数运算都是近似值

  // Modulo is there as well  取模运算
  11 % 3; // => 2

  // Comparison operators are probably familiar, but there is no Boolean type in C. We use ints instead.
  // 比较操作符可能很熟悉,但是C中没有布尔类型,用整型代替。
  // (Or _Bool or bool in C99.) 0 is false, anything else is true. (The comparison operators always yield 0 or 1.)
  // ( C99中有布尔) 0为假,其他均为真(比较操作符的返回值总是返回0或1)
 
  3 == 2; // => 0 (false)
  3 != 2; // => 1 (true)
  3 > 2; // => 1
  3 < 2; // => 0
  2 <= 2; // => 1
  2 >= 2; // => 1

  // C is not Python - comparisons don't chain.
  // C不是python, 连续比较不合法。 

  // Warning: The line below will compile, but it means `(0 < a) < 2`.  
  // 警告:下面这行可以编译,但是含义是`(0<a) < 2`
  int between_0_and_2 = 0 < a < 2;
  // Instead use:
  // This expression is always true, because (0 < a) could be either 1 or 0.
  // In this case it's 1, because (0 < 1).
  // 这个表达式经常为真,因为(0<a)可以是1或者0. 在这个例子里是1,因为(0<1)。
  int between_0_and_2 = 0 < a && a < 2;

  // Logic works on ints 逻辑运算符适用于整数。
  !3; // => 0 (Logical not) 非
  !0; // => 1
  1 && 1; // => 1 (Logical and)  与
  0 && 1; // => 0
  0 || 1; // => 1 (Logical or)  或
  0 || 0; // => 0

  // Conditional ternary expression ( ? : ) 条件表达式
  int e = 5;
  int f = 10;
  int z;
  z = (e > f) ? e : f; // => 10 "if e > f return e, else return f." 
  // 10 “若e>f 返回 e, 否则返回 f".

  // Increment and decrement operators: 增 和  减
  int j = 0;
  int s = j++; // Return j THEN increase j. (s = 0, j = 1)  返回j 然后增加j的值
  s = ++j; // Increase j THEN return j. (s = 2, j = 2) 增加j的值然后返回j.
  // same with j-- and --j 同理

  // Bitwise operators!  位运算
  ~0x0F; // => 0xFFFFFFF0 (bitwise negation, "1's complement", example result for 32-bit int)  取反
  0x0F & 0xF0; // => 0x00 (bitwise AND) 与
  0x0F | 0xF0; // => 0xFF (bitwise OR)  或
  0x04 ^ 0x0F; // => 0x0B (bitwise XOR) 异或
  0x01 << 1; // => 0x02 (bitwise left shift (by 1))  左移
  0x02 >> 1; // => 0x01 (bitwise right shift (by 1)) 右移

  // Be careful when shifting signed integers - the following are undefined:
  // - shifting into the sign bit of a signed integer (int a = 1 << 31)
  // - left-shifting a negative number (int a = -1 << 2)
  // - shifting by an offset which is >= the width of the type of the LHS:
  //   int a = 1 << 32; // UB if int is 32 bits wide
  // 对有符号整数移位要小心--以下未定义:
  // 有符号整数位移至符号位 int a = 1 << 32
  // 左移位一个负数 ( int a = -1 << 2)
  // 移位超过或等于该类型数值的长度。
  // int a = 1 << 32; 假定int32位
  ///////////////////////////////////////
  // Control Structures 控制结构
  ///////////////////////////////////////

  if (0) {
    printf("I am never run\n");
  } else if (0) {
    printf("I am also never run\n");
  } else {
    printf("I print\n");
  }

  // While loops exist 
  // while循环
  int ii = 0;
  while (ii < 10) { //ANY value less than ten is true.  任何小于10的值均为真
    printf("%d, ", ii++); // ii++ increments ii AFTER using its current value. 
                          // ii++在取值后自增。
  } // => prints "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

  printf("\n");

  int kk = 0;
  do {
    printf("%d, ", kk);
  } while (++kk < 10); // ++kk increments kk BEFORE using its current value. 
   // ++kk 先自增,再被取值。
  // => prints "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

  printf("\n");

  // For loops too  For 循环。
  int jj;
  for (jj=0; jj < 10; jj++) {
    printf("%d, ", jj);
  } // => prints "0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "

  printf("\n");

  // *****NOTES*****:   注意
  // Loops and Functions MUST have a body. If no body is needed:
  // 循环和函数必须有主体部分,如果不需要主体,使用分号代表主体(null语句)
  int i;
  for (i = 0; i <= 5; i++) {
    ; // use semicolon to act as the body (null statement)
  }
  // Or
  for (i = 0; i <= 5; i++);

  // branching with multiple choices: switch() 
  // 多重分支:switch() 
  switch (a) { 
  case 0: // labels need to be integral *constant* expressions (such as enums)
          // 标签必须是整数常量表达式
    printf("Hey, 'a' equals 0!\n");
    break; // if you don't break, control flow falls over labels
           // 如果不使用break, 控制结构会继续执行下面的标签
  case 1:
    printf("Huh, 'a' equals 1!\n");
    break;
    // Be careful - without a "break", execution continues until the next "break" is reached.
    // 小心,没有"break", 会执行到下一个"break"达到
    
  case 3:
  case 4:
    printf("Look at that.. 'a' is either 3, or 4\n");
    break;
  default:
    // if `some_integral_expression` didn't match any of the labels 
    // 如果没有匹配任何标签。
    fputs("Error!\n", stderr);
    exit(-1);
    break;
  }
  /*
 using "goto" in C  在C语言中使用"goto"
 */
  typedef enum { false, true } bool;
  // for C don't have bool as data type before C99 :(  
  // 在C99之前没有bool类型
  bool disaster = false;
  int i, j;
  for(i=0;i<100;++i)
  for(j=0;j<100;++j)
  {
    if((i + j) >= 150)
        disaster = true;
    if(disaster)
        goto error;
  }
  error :
  printf("Error occurred at i = %d & j = %d.\n", i, j);
  /*
 https://ideone.com/GuPhd6
 this will print out "Error occurred at i = 51 & j = 99."
 */

  ///////////////////////////////////////
  // Typecasting  类型转化
  ///////////////////////////////////////

  // Every value in C has a type, but you can cast one value into another type
  // if you want (with some constraints).
  // 在C中每个变量值都有类型,你可以把一个值转换成另外一个类型,如果你想(有一定限制)

  int x_hex = 0x01; // You can assign vars with hex literals 可以用16进制字面值复制。

  // Casting between types will attempt to preserve their numeric values
  //类型转换时,数字本身的值会被保留下来。
  printf("%d\n", x_hex); // => Prints 1
  printf("%d\n", (short) x_hex); // => Prints 1
  printf("%d\n", (char) x_hex); // => Prints 1

  // Types will overflow without warning
  // 类型转换时可能会溢出,而且没有警告。
  printf("%d\n", (unsigned char) 257); // => 1 (Max char = 255 if char is 8 bits long)  
  // char的最大值是255,假定char为8位长。

  // For determining the max value of a `char`, a `signed char` and an `unsigned char`,
  // respectively, use the CHAR_MAX, SCHAR_MAX and UCHAR_MAX macros from <limits.h>
  // 使用<limits.h>提供的CHAR_MAX, SCHAR_MAX和 UCHAR_MAX 宏确定'char','signed char'和'unsigned char'的最大值。

  // Integral types can be cast to floating-point types, and vice-versa.
  // 整数类型可以转换成浮点类型,反过来也一样。
  printf("%f\n", (float)100); // %f formats a float 单精度浮点
  printf("%lf\n", (double)100); // %lf formats a double 格式化双精度浮点
  printf("%d\n", (char)100.0);

  ///////////////////////////////////////
  // Pointers 指针
  ///////////////////////////////////////

  // A pointer is a variable declared to store a memory address. Its declaration will
  // also tell you the type of data it points to. You can retrieve the memory address
  // of your variables, then mess with them.
  // 指针变量是用来存储内存地址的变量,指针变量的声明也会告诉指向数据的类型,你可以使用得到你的变量的地址,并把它们搞乱。

  int x = 0;
  printf("%p\n", (void *)&x); // Use & to retrieve the address of a variable 使用&来得到变量地址
  // (%p formats an object pointer of type void *)
  // => Prints some address in memory; 
  // (%p 格式化一个类型为 void *的指针)
  // => 打印某个内存地址

  // Pointers start with * in their declaration
  // 指针类型在声明中以*开头
  int *px, not_a_pointer; // px is a pointer to an int , px 是一个指向int型的指针
  px = &x; // Stores the address of x in px , 把x的地址保存到px中。
  printf("%p\n", (void *)px); // => Prints some address in memory, 打印内存中的某个地址
  printf("%zu, %zu\n", sizeof(px), sizeof(not_a_pointer));
  // => Prints "8, 4" on a typical 64-bit system
  // 在64位系统上打印为“8,4”
  
  // To retrieve the value at the address a pointer is pointing to,
  // put * in front to dereference it.
  // Note: yes, it may be confusing that '*' is used for _both_ declaring a
  // pointer and dereferencing it.
  // 要得到某个指针指向的内容的值,可以在指针前加一个*来取得(取消引用) 注意:是的,这可能让人困惑,'*'在用来声明一个指针的同事取消引用它。
  printf("%d\n", *px); // => Prints 0, the value of x。 输出0,即x的值


  // You can also change the value the pointer is pointing to.
  // We'll have to wrap the dereference in parenthesis because
  // ++ has a higher precedence than *.
  // 你也可以改变指针所指向的值,此时你需要取消引用上添加括号,因为++比*的优先级更高。
  (*px)++; // Increment the value px is pointing to by 1
  //把px所指向的值增加1
  printf("%d\n", *px); // => Prints 1
  printf("%d\n", x); // => Prints 1

  // Arrays are a good way to allocate a contiguous block of memory
  //数组是分配一系列连续空间的常用方式
  int x_array[20]; //declares array of size 20 (cannot change size) 声明20个整数元素的数组
  
  int xx;
  for (xx = 0; xx < 20; xx++) {
    x_array[xx] = 20 - xx;
  } // Initialize x_array to 20, 19, 18,... 2, 1

  // Declare a pointer of type int and initialize it to point to x_array
  // 声明一个整型的指针,并初始化为指向 x_array.
  int* x_ptr = x_array;
  // x_ptr now points to the first element in the array (the integer 20).
  // This works because arrays often decay into pointers to their first element.
  // For example, when an array is passed to a function or is assigned to a pointer,
  // it decays into (implicitly converted to) a pointer.
  // Exceptions: when the array is the argument of the `&` (address-of) operator:
  // x_ptr 现在指向了数组的第一个元素(即整数20).
  // 这是因为数组通常衰减为指向它们的第一个元素的指针。
  // 例如,当一个数组被传递给函数或绑定一个指针时,它衰减为(隐形转化为)一个指针。
  //例外:当数组是'&'操作符的参数
  
  int arr[10];
  int (*ptr_to_arr)[10] = &arr; // &arr is NOT of type `int *`! 
  // It's of type "pointer to array" (of ten `int`s).
  // or when the array is a string literal used for initializing a char array:
  // &arr的类型不是'int *'! 它的类型是指向数组的指针(数组由10个int组成,或者当数组是字符串字面量(初始化字符数组)
  char otherarr[] = "foobarbazquirk";
  // or when it's the argument of the `sizeof` or `alignof` operator:
  // 或者当它是'sizeof'或'alignof'的参数时:
  int arraythethird[10];
  int *ptr = arraythethird; // equivalent with int *ptr = &arr[0]; 等价于 int *ptr = &arr[0];
  printf("%zu, %zu\n", sizeof arraythethird, sizeof ptr);
  // probably prints "40, 4" or "40, 8"
  // 应该会输出“40,4" 或 ”40,8“

  // Pointers are incremented and decremented based on their type
  // (this is called pointer arithmetic)
  //指针的增减多少是根据它的类型而定,成为指针运算。
  printf("%d\n", *(x_ptr + 1)); // => Prints 19
  printf("%d\n", x_array[1]); // => Prints 19

  // You can also dynamically allocate contiguous blocks of memory with the
  // standard library function malloc, which takes one argument of type size_t
  // representing the number of bytes to allocate (usually from the heap, although this
  // may not be true on e.g. embedded systems - the C standard says nothing about it).
  // 你也可以通过标准库函数malloc来实现动态分配,这个函数接收一个代表容量的参数,参数类型为'size_t',系统一般会从堆区heap分配指定容量字节大小的空间(在一些系统,例如嵌入系统中这一点不一定成立,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
  } // Initialize memory to 20, 19, 18, 17... 2, 1 (as ints) 初始化内存为20,19,18,17...2,1(类型为int)

  // Be careful passing user-provided values to malloc! If you want
  // to be safe, you can use calloc instead (which, unlike malloc, also zeros out the memory)
  //小心传给malloc的值,为了安全,可以用calloc代替(不像malloc)会初始化内存为0.
  int* my_other_ptr = calloc(20, sizeof(int));

  // Note that there is no standard way to get the length of a
  // dynamically allocated array in C. Because of this, if your arrays are
  // going to be passed around your program a lot, you need another variable
  // to keep track of the number of elements (size) of an array. See the
  // functions section for more info.
  //注意,没有标准方法来得到C中动态分配的数组长度,如果你打算传递到程序中更多信息,需要另外一个变量来保存数组的元素个数,看functions一节来得到更多描述。
  size_t size = 10;
  int *my_arr = calloc(size, sizeof(int));
  // Add an element to the array
  size++;
  my_arr = realloc(my_arr, sizeof(int) * size);
  if (my_arr == NULL) {
    //Remember to check for realloc failure!
    return
  }
  my_arr[10] = 5;

  // Dereferencing memory that you haven't allocated gives
  // "unpredictable results" - the program is said to invoke "undefined behavior"
  // 反引用尚未分配的内存引发不可预测的结果,程序可能引发未知行为。
  printf("%d\n", *(my_ptr + 21)); // => Prints who-knows-what? It may even crash. 不知道会打印什么,可能崩溃。

  // When you're done with a malloc'd block of memory, you need to free it,
  // or else no one else can use it until your program terminates
  // (this is called a "memory leak"):
  // 如果你用malloc分配了内存,你需要释放。或者等程序结束(这叫内存泄露)
  free(my_ptr);

  // Strings are arrays of char, but they are usually represented as a
  // pointer-to-char (which is a pointer to the first element of the array).
  // It's good practice to use `const char *' when referring to a string literal,
  // since string literals shall not be modified (i.e. "foo"[0] = 'a' is ILLEGAL.)
  // 字符串通常是字符数组,但是经常用字符指针表示(它是指向数组的第一个元素的指针); 一个优良的实践是使用'const char *’来引用一个字符串字面量,因为字符串字面量不应当被修改(即“foo"[0]='a'非法)
  const char *my_str = "This is my very own string literal";
  printf("%c\n", *my_str); // => 'T'

  // This is not the case if the string is an array (potentially initialized with a string literal) that resides in writable memory, as in:
  // 如果字符串是位于可写的内存数组中,(多半是用字符串字面量初始化的),则不是这种情况,如下:
  char foo[] = "foo";
  foo[0] = 'a'; // this is legal, foo now contains "aoo"
  // 这是合法的,现在foo为"aoo"
  
  function_1();
} // end main function

///////////////////////////////////////
// Functions 函数
///////////////////////////////////////

// Function declaration syntax:
// <return type> <function name>(<args>)
// 函数声明语法:<返回值类型><函数名称>(<参数>)

int add_two_ints(int x1, int x2)
{
  return x1 + x2; // Use return to return a value
                  // 使用return来返回一个值
}

/*
Functions are call by value. When a function is called, the arguments passed to
the function are copies of the original arguments (except arrays). Anything you
do to the arguments in the function do not change the value of the original
argument where the function was called.

Use pointers if you need to edit the original argument values.
函数是按值传递的。当 调用一个函数,参数是原有值的拷贝(数组除外)。你在函数内对参数所进行的操作,不会改变该参数原有的值。
当你需要修改原始参数值是可以使用指针。

Example: in-place string reversal
例子:字符串本身翻转
*/

// A void function returns no value
// void类型函数无返回值
void str_reverse(char *str_in)
{
  char tmp;
  int ii = 0;
  size_t len = strlen(str_in); // `strlen()` is part of the c standard library  C标准库函数
  for (ii = 0; ii < len / 2; ii++) {
    tmp = str_in[ii];
    str_in[ii] = str_in[len - ii - 1]; // ii-th char from end
    str_in[len - ii - 1] = tmp;
  }
}
//NOTE: string.h header file needs to be included to use strlen()
//注意:string.h头文件需要包括来使用strlen()

/*
char c[] = "This is a test.";
str_reverse(c);
printf("%s\n", c); // => ".tset a si sihT"
*/
/*
as we can return only one variable
to change values of more than one variables we use call by reference
因为我们只能返回一个变量。要改变多个变量时使用引用
*/
void swapTwoNumbers(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
/*
int first = 10;
int second = 20;
printf("first: %d\nsecond: %d\n", first, second);
swapTwoNumbers(&first, &second);
printf("first: %d\nsecond: %d\n", first, second);
// values will be swapped  值交换了。
*/

/*
With regards to arrays, they will always be passed to functions as pointers. Even if you statically allocate an array like `arr[10]`,
it still gets passed as a pointer to the first element in any function calls.
Again, there is no standard way to get the size of a dynamically allocated array in C.
对于数组,经常是作为指针传递给函数,即使你静态分配一个数组像'arr[10]',在任何函数中它仍然是通过指向第一个元素的指针传递的。
再一次,没有标准方法来得到C中分配的动态数组的大小。
*/
// Size must be passed! 大小必须传递
// Otherwise, this function has no way of knowing how big the array is.
//否则,函数不知道数组多大。
void printIntArray(int *arr, size_t size) {
    int i;
    for (i = 0; i < size; i++) {
        printf("arr[%d] is: %d\n", i, arr[i]);
    }
}
/*
int my_arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int size = 10;
printIntArray(my_arr, size);
// will print 会打印"arr[0] is: 1" etc  
*/

// if referring to external variables outside function, you should use the extern keyword.
// 如果引用函数function外部的变量,必须使用extern关键字。
int i = 0;
void testFunc() {
  extern int i; //i here is now using external variable i
}

// make external variables private to source file with static:
// 使用static确保external变量为源文件私有
static int j = 0; //other files using testFunc2() cannot access variable j
// 其他使用testFunc2()的文件无法访问变量j.
void testFunc2() { 
  extern int j;
}
//**You may also declare functions as static to make them private**
// 你同样可以声明函数为static**

///////////////////////////////////////
// User-defined types and structs  用户自定义类型和结构
///////////////////////////////////////

// Typedefs can be used to create type aliases
// Typedefs 可以创建类型别名
typedef int my_type;
my_type my_type_var = 0;

// Structs are just collections of data, the members are allocated sequentially,in the order they are written:
// struct是数据的集合,成员按照编写的顺序依序分配。
struct rectangle {
  int width;
  int height;
};

// It's not generally true that sizeof(struct rectangle) == sizeof(int) + sizeof(int)
// due to potential padding between the structure 
members (this is for alignment reasons). [1]
// 一般而言,以下断言不成立: sizeof(struct rectangle) == sizeof(int) + sizeof(int) 这是因为structure成员之间可能存在潜在的间隙(为了对齐)[1]

void function_1()
{
  struct rectangle my_rec;

  // Access struct members with . 
  // 通过 . 来访问结构中的数据
  my_rec.width = 10;
  my_rec.height = 20;

  // You can declare pointers to structs
  // 你也可以声明指向结构体的指针
  struct rectangle *my_rec_ptr = &my_rec;

  // Use dereferencing to set struct pointer members...
  // 通过取消引用来改变结构体的成员...
  (*my_rec_ptr).width = 30;

  // ... or even better: prefer the -> shorthand for the sake of readability
  // ... 或者用 -> 操作符作为简写提高可读性
  my_rec_ptr->height = 10; // Same as 等同 (*my_rec_ptr).height = 10;
}

// You can apply a typedef to a struct for convenience
// 你也可以用typedef来给一个结构体起一个别名
typedef struct rectangle rect;

int area(rect r)
{
  return r.width * r.height;
}

// if you have large structs, you can pass them "by pointer" to avoid copying the whole struct:
// 如果sturct较大,你可以通过指针传递,避免复制整个struct.
int areaptr(const rect *r)
{
  return r->width * r->height;
}

///////////////////////////////////////
// Function pointers  函数指针
///////////////////////////////////////
/*
At run time, functions are located at known memory addresses. Function pointers are
much like any other pointer (they just store a memory address), but can be used
to invoke functions directly, and to pass handlers (or callback functions) around.
However, definition syntax may be initially confusing.
在运行时,函数本身也被存放到某块内存区域当中,函数指针就像其他指针一样(不过是存储一个内存地址)但是却可以用来直接调用,并且可以四处传递回调函数,然而,初看定义的语法令人有些迷惑。

Example: use str_reverse from a pointer
例子:通过指针调用 str_reverse
*/
void str_reverse_through_pointer(char *str_in) {
  // Define a function pointer variable, named f.
  // 定义一个函数指针 f
  void (*f)(char *); // Signature should exactly match the target function. 签名一定要与目标函数相同
  f = &str_reverse; // Assign the address for the actual function (determined at run time)
  // 将函数的地址在运行时赋予指针。
  // f = str_reverse; would work as well - functions decay into pointers, similar to arrays
  // f = str_reverse; 也可以,类似数组,函数衰减decay成指针
  (*f)(str_in); // Just calling the function through the pointer 通过指针调用
  // f(str_in); // That's an alternative but equally valid syntax for calling it.  等价于这种调用方式
}

/*
As long as function signatures match, you can assign any function to the same pointer.
Function pointers are usually typedef'd for simplicity and readability, as follows:
只要函数签名是正确的,任何时候都能将任何函数赋予给某个函数指针,为了可读性和简洁性,函数指针经常和typedef搭配使用:
*/

typedef void (*my_fnp_type)(char *);

// Then used when declaring the actual pointer variable:
// 实际声明函数指针会这么用:
// ...
// my_fnp_type f;

//Special characters: 
// 特殊字符
/*
'\a'; // alert (bell) character
'\n'; // newline character  换行
'\t'; // tab character (left justifies text)
'\v'; // vertical tab
'\f'; // new page (form feed)
'\r'; // carriage return  回车
'\b'; // backspace character 退格
'\0'; // NULL character. Usually put at end of strings in C.  通常置于字符串最后。 按照惯例 \0用于标记字符串的末尾
//   hello\n\0. \0 used by convention to mark end of string.
'\\'; // backslash  反斜杠
'\?'; // question mark 问号
'\''; // single quote 单引号
'\"'; // double quote 双引号
'\xhh'; // hexadecimal number. Example: '\xb' = vertical tab character  十六进制数字。 
'\0oo'; // octal number. Example: '\013' = vertical tab character  // 十进制数字 

//print formatting: 打印格式:
"%d";    // integer  整数
"%3d";   // integer with minimum of length 3 digits (right justifies text)  3位以上整数(右对齐文本)
"%s";    // string 字符串
"%f";    // float
"%ld";   // long
"%3.2f"; // minimum 3 digits left and 2 digits right decimal float  左3位以上,右2位以上十进制浮点
"%7.4s"; // (can do with strings too)  字符串同样适用
"%c";    // char  字符
"%p";    // pointer  指针
"%x";    // hexadecimal  十六进制
"%o";    // octal  八进制
"%%";    // prints %   打印 %
*/

///////////////////////////////////////
// Order of Evaluation  演算优先级
///////////////////////////////////////

//---------------------------------------------------//
//        Operators                  | Associativity //
//---------------------------------------------------//
// () [] -> .                        | left to right //
// ! ~ ++ -- + = *(type)sizeof       | right to left //
// * / %                             | left to right //
// + -                               | left to right //
// << >>                             | left to right //
// < <= > >=                         | left to right //
// == !=                             | left to right //
// &                                 | left to right //
// ^                                 | left to right //
// |                                 | left to right //
// &&                                | left to right //
// ||                                | left to right //
// ?:                                | right to left //
// = += -= *= /= %= &= ^= |= <<= >>= | right to left //
// ,                                 | left to right //
//---------------------------------------------------//

/******************************* Header Files **********************************

Header files are an important part of C as they allow for the connection of C
source files and can simplify code and definitions by separating them into
separate files.

Header files are syntactically similar to C source files but reside in ".h"
files. They can be included in your C source file by using the precompiler
command #include "example.h", given that example.h exists in the same directory
as the C file.
头文件是C的重要一部分,可以链接源文件,并且简化代码和定义,通过将他们分开。

头文件在语法上与C源文件类似,但位于".h"文件。他们可以通过使用预编译命令#include "example.h" 来包含在你的C源代码文件里,假设 example.h 和 C文件一样存在于同样目录中

*/

/* A safe guard to prevent the header from being defined too many times. This happens in the case of circle dependency, the contents of the header is already defined.                                  
当循环依赖时,头文件的内容已经定义,为了避免头文件被定义多次,有个安全措施
*/

#ifndef EXAMPLE_H /* if EXAMPLE_H is not yet defined. */
#define EXAMPLE_H /* Define the macro EXAMPLE_H. */
/* 如果 EXAMPLE_H 没有定义,定义宏 EXAMPLE_H */

/* Other headers can be included in headers and therefore transitively included into files that include this header.                       
其他头文件可以被包含在 头文件里,因此可以传递性地包含在这个头文件里。
*/
#include <string.h>

/* Like c source files macros can be defined in headers and used in files that include this header file.         C源代码文件里的宏可以在头文件里定义,并且在文件中使用头文件里定义的宏                 
*/
#define EXAMPLE_NAME "Dennis Ritchie"

/* Function macros can also be defined.  */
// 函数宏可以被定义
#define ADD(a, b) ((a) + (b))

/* Notice the parenthesis surrounding the arguments -- this is important to ensure that a and b don't get expanded in an unexpected way (e.g. consider  MUL(x, y) (x * y); MUL(1 + 2, 3) would expand to (1 + 2 * 3), yielding an incorrect result)
注意参数周围的括号,这对于确保a和b不会以意外的方式被扩展很重要(例如考虑 MUL(x,y)(x *y); MUL(1+2,3)可能会扩展到(1+2*3)产生错误的结果)
*/

/* Structs and typedefs can be used for consistency between files. 
结构体和typedefs可以用来确保文件间的一致性
*/
typedef struct Node
{
    int val;
    struct Node *next;
} Node;

/* So can enumerations. 枚举也是如此 */
enum traffic_light_state {GREEN, YELLOW, RED};

/* Function prototypes can also be defined here for use in multiple files, but it is bad practice to define the function in the header. Definitions should instead be put in a C file.                    
函数原型可 被定义用于多个文件中,但是定义在头文件中不好,应该在C文件中
*/
Node createLinkedList(int *vals, int len);

/* Beyond the above elements, other definitions should be left to a C source file. Excessive includes or definitions should, also not be contained in a header file but instead put into separate headers or a C file. 
除上述元素外,其他定义应留给C源文件。 过多的包含或定义应该也不包含在头文件中,而是放入单独的头文件或C文件中。
*/

#endif /* End of the if precompiler directive. 指令if预编译结束 */

Further Reading 更多阅读

Best to find yourself a copy of K&R, aka “The C Programming Language” It is the book about C, written by Dennis Ritchie, the creator of C, and Brian Kernighan. Be careful, though - it’s ancient and it contains some inaccuracies (well, ideas that are not considered good anymore) or now-changed practices.

Another good resource is Learn C The Hard Way.

If you have a question, read the compl.lang.c Frequently Asked Questions.

It’s very important to use proper spacing, indentation and to be consistent with your coding style in general. Readable code is better than clever code and fast code. For a good, sane coding style to adopt, see the Linux kernel coding style.

Other than that, Google is your friend.

最好找一本 K&R, aka “The C Programming Language”, “C程序设计语言”。它是关于C最重要的一本书,由C的创作者撰写。不过需要留意的是它比较古老了,因此有些不准确的地方。

另一个比较好的资源是 Learn C the hard way

如果你有问题,请阅读compl.lang.c Frequently Asked Questions

使用合适的空格、缩进,保持一致的代码风格非常重要。可读性强的代码比聪明的代码、快速的代码更重要。可以参考下Linux内核编码风格 。 除了这些,多多Google吧

[1]Why isn’t sizeof for a struct equal to the sum of sizeof of each member?

参考资料

Y分钟学习C语言
https://www.kancloud.cn/kancloud/learnxinyminutes/58928

learn c in y minutes
https://learnxinyminutes.com/docs/c/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容