【C】 Primer Plus 5th Edition 阅读笔记

发布时间 2023-06-11 18:49:41作者: 徐大树
 
 
一. Getting Ready
 
1. ANSI C(C89) and ISO C(C90) are essentially the same standard.
2. #include<>,  预处理指令, include 等同于在当前位置复制和粘贴代码。它的存在是为了方便的分享公共代码。
3. stdio.h, The  stdio.h  file  is  supplied  as  part  of  all  C  compiler  packages. 
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
二. Introduce C 
 
1. In fact, rather than trying to correct all the reported errors at once, you should correct just the first one or two and then recompile; some of the other errors may go away. Continue in this way until the program works. 
2. syntax error 语法错误。semantic error 语义错误。语义错误可能包含但不限于运行时错误。
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
三. Data and C
 
1. 按照C标准,无符号整数溢出了,会从0开始。有符号数溢出了则没有定义现象。
2. 系统对待数字,一般以 int,unsigned int,long,unsigned long,long long, unsigned long long 的顺序来尽量表示。如果想在代码中指定。可以用 L, LL, UL, ULL来表示。
3. C语言将三个连续的字符串合并成一个引号的字符串,例如:"The hexavalue is %" PRId16 "\n" 第二个是宏,会和前后两个字符串自动合并成一个字符串处理。
4. C编译器通常默认浮点数为double.
5. sizeof is a built-in operator in C. C 把 char 当作 1 byte,意味着一些平台,char不是一个字节,那么会导致 sizeof 的结果翻倍。
   使用sizeof一定要注意,尤其是对数组操作。如果是字符串数组,size会多1,因为结尾的不可见的 null character '\n'.而普通的数组则不会有这样的问题,会正确返回字节数。sizeof 对数组操作,返回的是字节数 in bytes,而不是数组长度。
6. printf 里面的分类器,不会进行类型转换。比如,%f你给他10, 他不会转换成 10.0, 而是按照内存的真实样子,当作float显示。
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
四.
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
五.
 
1. For ++ 和 -- 操作符, prefix,postfix的区别就是,先对该变量加1在用该变量的值,还是先用该变量的值在对该变量加1.
1.1 Don't use increment or decrement operators on a variable that is part of more than one
argument of a function.
1.2 Don't use increment or decrement operators on a variable that appears more than once in an expression.
 
2. limits.h 提供了一些类型那个的最大最小值,通过宏定义的。比如:INT_MAX, INT_MIN
3. In any case, the standard says, in effect, that if a and b are integer values, you can calculate a%b by subtracting (a/b)*b from a.
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
六. C Control Statements: Looping
 
1. The  comma  operator is not  restricted to  for  loops,  but that's  where it  is  most  often used.  The
operator  has  two  further  properties.  First,  it  guarantees  that  the  expressions  it  separates  are
evaluated  in  a  left-to-right  order.  (In  other  words,  the  comma  is  a  sequence  point,  so  all  side
effects  to  the  left  of  the  comma  take  placee  before  the  program  moves  to  the  right  of  the
comma.)
 
2. comma can be an operator:
 
houseprice = 249,500;  
等同于:
houseprice = 249;
500;
 
这说明,comma 操作符首先计算左边的表达式,然后计算右边的表达式,但是他最终的值是右边的表达式。也就是整个表达式的值实际上是 500
 
The comma also is used as a separator, so the commas in
char ch, date;
and
printf("%d %d\n", chimps, chumps);
are separators, not comma operators.
 
上面的情况 comma 被认为是分隔符,不算是操作符。
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
七.
 
1. If else 嵌套
  Speaking  of  compiler  limits,  the  C99  standard  requires  that  a  compiler  support  a  minimum  of
127 levels of nesting.
 
2. 逻辑运算符的特殊性 p213 ~ 214
C guarantees that logical expressions are e valuated from left to right. The  &&
and  ||  operators  are  sequence  points,  so  all  side  effects  take  place  before  a  program  moves
from  one  operand to the  next.  Furthermore,  it guarantees  that  as  soon  as  an  element is  found
that  invalidates  the  expression  as  a  whole,  the  evaluation  stops. 
 
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
八.
 
1. simply note that C treats input and output devices the same as it treats regular files on storage devices.
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
九. function
 
1. We've placed the advance function declarations outside the function using them. They can also be placed inside the function. For example, you can rewrite the beginning of lesser.c as follows: #include <stdio.h> int main(void) {     int imin(int, int); /* imin() declaration */
    // Call imin function
 
2. Recursive solutions tend to be more elegant and less efficient than loop solutions.
3. In the simplest form of recursion, the recursive call is at the end of the function, just before the return statement. This is called tail recursion, or end recursion, because the recursive call comes at the end. Tail recursion is the simplest form because it acts like a loop.
 
4. actual arguments, formal parameters
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
十. Arrays
 
1. Note that the code uses a loop to assign values element by element. C doesn't let you assign one array to another as a unit. Nor can you use the list-in-braces form except when initializing.
 
2. C99 has added a new capability—designated initializers. P312
3  In short, in Listing 10.10, marbles is an array, ar is a pointer to the first element of marbles, and the C connection between arrays and pointers lets you use array notation with the pointer ar. P325
 
4  当 ar + 1 的时候,编译器需要知道需要偏移多少。ar[][4] 是二维指针,指向的是 [4]数组,ar + 1 应该偏移[4],而 ar[][]会导致编译器不知道偏移多少。
int sum2(int ar[][], int rows); // faulty declaration
int sum2(int ar[][4], int rows); // valid declaration
 
5 int (*a) [2] 和 int *a[2] 的区别应该在于,前者是一个指针,指向的类型是 int[2] 数组。而后者是一个数组,数组的每个元素是一个指针。P339
 
6  int **a 和 int[2][2]不兼容,类型不同。前者是只想一个int * 指针的指针,后者是指向 int[2],编译器认为类型不同。即使 int(*a)[3]和 int(*a)[2]( p348 ) 也是不兼容的,指向的数组大小不同。 P340
 
7  对于一维指针,普通指针可以赋值给 const 指针。对于二维指针,则不可以,因为 C 认为这样可以间接的修改常量指针指向的数据 P341
 
8  从整章来看,类似于 int[][] 的数组是永远不应该出现的,无论是在函数里还是在声明中。
 
9  VLAs have some restrictions. They need to have the automatic storage class, which means they are declared either in a function or as function parameters.  p345
 
10  数组的名字是一个指针,而且他是 const 类型的指针。
 
11  当考虑指针类型的时候,考虑 +1 这种情况往往有助于理解其类型的含义。
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
十一. Chapter 11. Character Strings and String Functions
 
1  “abc” 在代码里只要出现这种形式,编译器就认为是字符串,会自动在末尾加上 null character ‘\0'
2   Array Versus Pointer  数组存储字符串比指针存储字符串多一次拷贝。In short, initializing the array copies a string from static storage to the array, whereas initializing the pointer merely copies the address of the string. p359
 
3  char * p1 = "Klingon”; 的形式最好不要用, 要用 const char * p1 = “Klingon”; 因为 “Klingon” 通常作为一个常量存储在数据段中,That is, the compiler can replace each instance of "Klingon" with the same address. If the compiler uses this single-copy representation,那么一旦通过 p1 修改了字符串,可能会导致其余使用该字符串的地方都被修改了。这个取决于编译器,也可能没有问题。而且,数组形式是没有问题的,因为数组是copy了一份字符串,而不是直接使用的字符串地址。p362 ~ p363
 
4   By the way, don't confuse the null pointer with the null character. The null pointer is an address, and the null character is a type char data object with the value zero. Numerically, both can be represented by 0, but they differ conceptually from each other: NULL is a pointer, \0 is a type char constant.
Bin(二进制)
Oct(八进制)
Dec(十进制)
Hex(十六进制)
缩写/字符
解释
0000 0000
0
0
00
NUL(null)
空字符
 
理解来说,就是 NULL(null pointer) 代表指针地址的值是 0. 而 ‘\0’ (null character)代表字符的码值是 0. 所以,二值都可以说是 0, 类型意义不一样。
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
十二.  Storage Classes, Linkage, and Memory Management
 
 
1  Function scope 和 Block scope 是有区别的。比如 goto label 是 function scope, 不管这个 goto label 在哪里出现的,在整个函数的任意位置都可以看到它。而普通局部变量,只有 Block scope,则必须在声明后,才能被找到。P406
 
2  在 gcc 使用 c99 标准: gcc –std=c99 forc99.c
 
3  
Automatic variables are not initialized unless you do so explicitly.
 
Because a register variable may be in a register rather than in memory, you can't take the address of a register variable.
 
The calloc() function throws in one more feature: It sets all the bits in the block to zero. (Note, however, that on some hardware systems, a floating-point value of 0 is not represented by all bits set to 0.)
 
5 malloc 申请二维数组 P433
p3 = (int (*)[m]) malloc(n * m * sizeof(int)); // n * m array
 
6      P435
In short, a const anywhere to the left of the * makes the data constant, and a const to the right of the * makes the pointer itself constant.
 
7 Volatile P436
 
8  restrict 关键字只能用于指针。编译器一般无法知道你是否符合 restrict 的要求,但是你既然加了这个关键字,他就有可能去优化,就有可能出错。这需要你自己负责 p437~p438
 
9 奇怪的语法,请查看 p438
void ofmouth(int a1[const], int a2[restrict], int n); // allowed by C99
double stick(double ar[static 20]);
 
10     p439
In particular, be aware that the amount of static memory used is determined at compile time, and that static data is loaded into memory when the program is loaded into memory. Automatic variables are allocated and freed as the program runs, so the amount of memory used by automatic variables changes while a program executes. You can think of automatic memory as a rewriteable workspace. Allocated memory also grows and shrinks, but, in this case, the process is controlled by function calls rather than happening automatically.
 
11  static 限定的变量,并非整个文件,只是定义到文件尾的范围。
      static 限定的函数,则是整个文件,可以通过 extern 的声明引用。
 
#include <stdio.h>
 
static void test(int a[]);     // 编译没问题
static extern int ass;          // 编译出错了
 
int main(void) {
    int b[1] = {1};
    test(b);
    printf("b = %d, ass = %d. ", b[0], ass);
}
static void test(int a[2]) {
   a[0] = 9;
}
static int ass = 10;
 
12  register 修饰的变量,不能获取其地址
 
13  一些关键字放在了奇怪的地方:P438
void ofmouth(int a1[const], int a2[restrict], int n); // allowed by C99
double stick(double ar[static 20]);
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
Chapter 13. File Input/Output
1  When data is stored in a file using the same representation that the program uses, we say that the data is stored in binary form. P462
 
2  p455 
The fgets() function reads input through the first newline character, until one fewer than the upper limit of characters is read, or until the end-of-file is found; fgets() then adds a terminating null character to form a string. Therefore, the upper limit represents the maximum number of characters plus the null character. If fgets() reads in a whole line before running into the character limit, it adds the newline character, marking the end of the line into the string, just before the null character. Here it differs from gets(), which reads the newline but discards it. 
 
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
Chapter 14. Structures and Other Data Forms
 
1 Compound Literals  (struct book) {"The Idiot", "Fyodor Dostoyevsky", 6.99}  p497
 
2  函数指针的调用有两种方式:(*fun)(),      fun()               p518
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
Chapter 15. Bit Fiddling
 
1 For unsigned types, the places vacated at the left end are replaced by 0s. For signed types, the result is machine dependent. The vacated places may be filled with 0s, or they may be filled with copies of the sign (leftmost) bit. p539
 
2  Bit Fields: A bit field is set up with a structure declaration that labels each field and determines its width. p544
struct {
     unsigned int field1 : 1;
     unsigned int        : 2;
     unsigned int field2 : 1;
     unsigned int        : 0;
     unsigned int field3 : 1;
} stuff;
 
在 Mac OS 上,整个位域的大小单位,以其中占空间最大的类型为主。然后,用最少的单位数,满足位域的要求。像上面的例子,也可以认为的指定空区域。当然,如果不指定,当空间不够的时候,也会自动出现空区域。因为,一个域,不会跨越两个单位。
 
 
3  One important machine dependency is the order in which fields are placed into an int. On some machines, the order is left to right; on others, it is right to left. Also, machines differ in the location of boundaries between fields. For these reasons, bit fields tend not to be very portable. Typically, however, they are used for nonportable purposes, such as putting data in the exact form used by a particular hardware device.  p544
 
p554  These bit tools help C programs deal with hardware matters, so they most often appear in implementation-dependent contexts.
 
说白了,位操作,可移植性较差。
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
Chapter 16. The C Preprocessor and the C Library
 
1  Note that a macro definition can include other macros. (Some compilers do not support this nesting feature.)     p561
#define TWO 2
#define FOUR TWO*TWO
 
2  Be careful of ++ in macro
#define SQUARE(X) X*X
SQUARE(++x)
p567  The simplest remedy for this problem is to avoid using ++x as a macro argument. In general, don't use increment or decrement operators with macros. Note that ++x would work as a function argument because it would be evaluated to 5, and then the value 5 would be sent to the function.
 
3 p562  The one exception to replacement is a macro found within double quotation marks. Therefore,
在双引号里面的字符串是不会被宏替换的,但是有办法做到这一点:
Suppose you do want to include the macro argument in a string. ANSI C enables you to do that. Within the replacement part of a function-like macro, the # symbol becomes a preprocessing operator that converts tokens into strings. For example, say that x is a macro parameter, and then #x is that parameter name converted to the string "x". This process is called stringizing.  p567
注意,#x 是真的转换成了字符串,所以如果 int y = 10, x 被替换成了一个变量名 “y”,   y 是不会进一步被替换成 10 的。这和普通的宏参数是不同的。
 
4 #,## 都是用在 function-like macro 的,Note how the PRINT_XN() macro uses the # operator to combine strings and the ## operator to combine tokens into a new identifier. p569, ##拼接完之后,仍然是 identifier,所以可以被变量值进一步计算的。
 
5 #define PR(X, ...) printf("Message " #X ": " _ _VA_ARGS_ _) p570  Don't forget, the ellipses have to be the last macro argument
 
6  Remember that there are no spaces(空格) in the macro name, but that spaces can appear in the replacement string p571, a few tips there
 
7  通常来说使用宏提升了效率,使用函数减少了占用空间。p570
 
8  A few predefined macros, such as _ _DATE_ _ and _ _FILE_ _ (discussed later this chapter), are always considered defined and cannot be undefined. p576
 
9  inline function:
p583, For the compiler to make inline optimizations, it has to know the contents of the function definition. This means the inline function definition has to be in the same file as the function call. For this reason, an inline function ordinarily has internal linkage. Therefore, if you have a multifile program, you need an inline definition in each file that calls the function. The simplest way to accomplish this is to put the inline function definition in a header file and then include the header file in those files that use the function. An inline function is an exception to the rule of not placing executable code in a header file. Because the inline function has internal linkage, defining one in several files doesn't cause problems.
但是别的文件也可以 extern 这个方法,只是,extern 之后,使用的就不是 inline 的函数了。
 
10  如果 inline 函数被取地址或者传递函数指针了,那么就不再会 inline 了。
 
11  #if defined (VAX) p580
 
12  #define NDEBUG   p597  使 asset() 无效果
 
13  stdarg.h   void f1(int num, …)    p599
 
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
Chapter 17. 
 
1 What constitutes a type? A type specifies two kinds of information: a set of properties and a set of operations.  p616
 
2 A data type is characterized by how the data is structured and stored and also by what operations are possible. 
 
 
Referenc
 
1 p735, Standard ANSI C Library
 
assert.h
complex.h(C99)
ctype.h
errno.h
fenv.h          (C99)
inttypes.h    (C99)
locale.h
math.h
setjmp.h
signal.h
stdarg.h
stdbool.h    (C99)
stddef.h
stdint.h
stdio.h
stdlib.h
string.h
tgmath.h    (C99)
time.h
wchar.h      (C99)
wctype.h    (C99)
 
2 p782 Wide Characters
 
Differenc Between C and C++
 
1 p791 In C and in C++, you can declare one structure inside another: In C, you can use either structure later, but C++ requires a special notation for the nested structure.
 
文 | 20160829
 
 
 
 
疑问:
 
1  如何避免 C 函数重名,导致加载(调用)函数错误的问题?