书上第一页还写着2018.6.15。第二遍看这本书,把阅读中画的重点记录下来,主要是一些C语言的细节问题。
书中的程序、附录没有详细看,留到下一遍。

1 导言

  • intfloat类型的取值范围取决于具体的机器。
  • 由于5和9都是整数,5/9相除后经截取所得的结果为0。
  • 如果某个算数运算符有一个浮点型操作数和一个整形操作数,则在开始运算之前整形操作数将会被转换为浮点型。
  • 即使浮点常量取的是整型值,在书写时最好还是为它加上一个显式的小数点。
  • #define LOWER 0
  • EOF(end of file)定义在头文件<stdio.h>中,是一个整形数。
  • !=的优先级比=高。
  • 对于floatdouble类型,printf函数都使用%f进行说明。
  • 区分字符常量和字符串常量。
  • 赋值结合次序是由右至左。nl = (nw = (nc = 0))等价于没有括号。
  • 运算符&&||保证在求值过程中只要能够判断最终的结果为真或假,求值就立即终止。
  • 函数原型(参数名可选)和函数声明。
  • 定义表示创建变量或分配存储单元,而声明指的是说明变量的性质,但并不分配存储单元。
  • extern是用来在另一个文件中声明一个全局变量或函数。extern修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候。
  • 所有外部变量的定义都放在源文件的开始处,这样就可以省略extern声明。

2 类型、运算符与表达式

  • 所有整形都包括signed(带符号)unsigned(无符号)两种形式。
  • 变量名是由字目和数字组成的序列,但其第一个字符必须为字母,下划线_被看做是字母。
  • 大写字母和小写字母是有区别的。
  • 一个字符常量是一个整数。
  • 位模式:\ooo其中ooo代表1-3个八进制数字。\xhh其中hh是一个或多个十六进制数字。
  • 常量表达式是仅仅只包含常量的表达式。这种表达式在编译时求值,而不在运行时求值。
  • 字符串常量也叫字符串字面值。
  • "hello," "world"等价于"hello,world"
  • 枚举为建立常量值与名字之间的关联提供了一种便利的方式。

    // 枚举是一种类型,通过它可以定义枚举变量:
    enum week a, b, c;
    // 也可以在定义枚举类型的同时定义变量:
    enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a, b, c;
    // 有了枚举变量,就可以把列表中的值赋给它:
    enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
    enum week a = Mon, b = Wed, c = Sat;
    // 或者:纯文本复制
    enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a = Mon, b = Wed, c = Sat;
  • 默认情况下,外部变量与静态变量将被初始化为0.未经显式初始化的自动变量的值为未定义值(即无效值)。
  • 对数组而言,const限定符指定数组所有元素的值都不能被修改。
  • 取模运算符%不能应用于floatdouble类型。
  • 运算符&&的优先级比||的优先级高。
  • 表达式中的float类型的操作数不会自动转换为double类型,这一点与最初的定义有所不同。一般来说,数学函数(如标准头文件<math.h>中定义的函数)使用双精度类型的变量。
  • 库函数sqrt的参数为double类型。声明将对参数进行自动强制转换。
  • 表达式++n先将n的值递增1,然后再使用变量n的值。而表达式n++则是先使用变量n的值,然后再将n的值递增1.也就是说,对于使用变量n的值的上下文来说,++nn++的效果时不同的。
  • 自增与自减运算符只能作用于变量,类似于表达式(i+j)++是非法的。
  • 移位运算符空出的位用0填补。
  • C语言没有指定函数各参数的求值顺序。因为最佳的求值顺序同机器结构有很大关系。函数调用、嵌套赋值语句、自增与自减运算符都有可能产生“副作用”——在对表达式求值的同时,修改了某些变量的值。

3 控制流

  • 每个else与最近的前一个没有else配对的if进行匹配。
  • 逗号表达式,也是C语言优先级最低的运算符,在for语句中经常会用到它。被逗号分隔的一对表达式将按照从左到右的顺序进行求值,求表达式右边的操作数的类型和值即为其结果的类型和值。

4 函数与程序结构

  • 一个程序可以保存在一个或者多个源文件中。各个文件可以单独编译,并可以与库中已编译过的函数一起加载。
  • 如果函数定义中省略了返回值类型,则默认int类型。
  • 函数之间的通信可以通过参数、函数返回至以及外部变量进行。
  • return语句的后面没有表达式时,函数将不向调用者返回值。当被调用函数执行到最后的右花括号而结束时,控制同样也返回给调用者(不返回值)。
  • C语言不允许在一个函数中定义其他函数。
  • 变量声明用于说明变量的属性(主要是变量的类型),而变量定义除此以外还将引起存储器的分配。
  • static的作用:

    • 隐藏:当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
    • 保持变量内容的持久。
    • 默认初始化为0。
  • rigister声明只适用于自动变量以及函数的形式参数。
  • 无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。
  • 当省略数组的长度时,编译器将把花括号中初始化表达式的个数作为数组的长度。
  • 如果初始化表达式的个数比数组元素数少,则对外部变量、静态变量和自动变量来说,没有初始化表达式的元素将被初始化为0。
  • #include中如果文件名用引号,会首先在源文件所在位置查找该文件。
  • #define中###

    • #的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号.
    • ##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。
  • #ifdef#ifndef用来测试某个名字是否已经定义。

5 指针与数组

  • 地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式、常量或register类型的变量。
  • C语言是以传值的方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调函数中变量的值。
  • “指针加1”就意味着,pa+1指向pa所指向的对象的下一个对象。
  • 数组名所代表的的就是该数组最开始的一个元素的地址。
  • 对于int a[10]; int *pa;有以下:

    • pa = &a[0]等价于pa = a
    • a[i]等价于*(a+i)
    • 指针是一个变量,数组名不是变量。pa=apa++合法,a=paa++非法。
  • 在函数定义中形式参数char s[]char *s等价。
  • C语言保证,0永远不是有效的数据地址。
  • 指针与整数之间不能相互转换,但0是唯一的例外:常量0可以赋值给指针,指针也可以和常量0进行比较。程序中经常用符号常量NULL代替常量0。
  • 指向不同数组的元素的指针之间的算数或比较运算没有定义。
  • 指针的算数运算中可使用数组最后一个元素的下一个元素的地址。
  • 有效的指针运算符包括:

    • 相同类型指针之间的赋值运算。
    • 指针同整数之间的加法或减法运算。
    • 指向相同数组中元素的两个指针间的减法或比较运算。
    • 将指针赋值为0或指针与0之间的比较运算。
  • 下述代码,数组中的单个字符可以修改,但修改字符串的内容,结果是没有定义的。

    char amessage[] = "hello";
    char *pmessage = "hello";
  • 指针数组的一个重要优点在于,数组的每一行长度可以不同。
  • argv[0]的值是启动该程序的程序名,因此argc的值至少为1。
  • ANSI标准要求argv[argc]的值必须为一空指针。

6 结构

  • 结构可以嵌套。
  • 联合只能用其第一个成员类型的值进行初始化。

7 输入与输出

  • printf%后的符号用于指定被转换的参数按照左对齐的形式输出。
  • 在转换说明中。宽度和精度可以用星号*表示,这时,宽度或精度的值通过转换下一个参数(必须为int类型)来计算。

    printf("%.*s", max, s);
  • 如果printf(s)中的s含有字符%,输出将出错。
  • 变长参数表。省略号表示参数表中参数的数量和类型是可变的,只能出现在参数表的尾部。使用<stdarg.h>处理。

    int printf(char *fmt, ...)
  • sscanf用于从一个字符串(而不是标准输入)中读取字符序列。
  • scanf函数忽略格式串中的空格和制表符。
  • 文件访问

    • fopen的第一个参数是一个字符串,它包含文件名。第二个参数是访问模式,包括:读r、写w、追加a
    • 当以写方式打开一个已存在的文件时,该文件原来的内容将被覆盖。
    • 如果发生错误,fopen将返回NULL
  • 启动一个C语言程序时,操作系统环境负责打开3个文件,并将这3个文件的指针提供给该程序。这3个文件分别是标准输入、标准输入和标准错误,相应的文件指针分别是stdinstdoutstderr。它们在<stdio.h>中声明,在大多数环境中,stdin指向键盘,而stdoutstderr指向显示器。同时stdinstdout可以被重定向到文件或管道。
  • exit()被调用时,将终止调用程序的执行。返回值0表示一切正常,非0通常表示出现了异常情况。
  • malloc返回的指针指向n字节长度未初始化的存储空间。calloc申请的存储空间被初始化为0。

8 UNIX系统接口

  • 在UNIX操作系统中,所有的外围设备(包括键盘和显示器)都被看作是文件系统中的文件。
  • 通常情况下,在读或写文件之前,必须先将这个意图通知系统,该过程称为打开文件
  • 如果一切正常(存在,权限符合),操作系统将向程序返回一个小的非负整数,该整数称为文件描述符。任何时候对文件的输入/输出都是通过文件描述符标识文件,而不是通过文件名标识文件。(文件描述符类似于标准库中的文件指针或MS-DOS中的文件句柄。)系统负责维护已打开文件的所有信息,用户程序只能通过文件描述符引用文件。
  • 标准库中的文件不是通过文件描述符描述的,而是使用文件指针描述的。文件指针是一个指向包含文件各种信息的结构的指针,该结构包含下列内容:

    • 一个指向缓冲区的指针,通过它可以一次读入文件的一大块内容。
    • 一个记录缓冲区中剩余的字符数的计数器。
    • 一个指向缓冲区中下一个字符的指针。
    • 文件描述符。
    • 描述读/写模式的标志。
    • 描述错误状态的标志。
    • 等...
  • malloc管理的空间不一定是连续的。
  • 当有申请请求时,malloc将扫描空闲块链表,直到找到一个足够大的块为止。该算法成为“首次适应”(first fit);与之相对的算法是“最佳适应”(best fit),它寻找满足条件的最小块。

    • 如果该块恰好与请求的大小相符合,则将它从链表中移走并返回给用户。
    • 如果该块太大,则将它分成两部分:大小合适的块返回给用户,剩下的部分留在空闲块链表中。
    • 如果找不到一个足够大的块,则向操作系统申请一个大块并加入到空闲块链表中。
  • 空闲块包含一个指向链表中下一个块的指针、一个块大小的记录和一个指向空闲空间本身的指针。位于块开始处的控制信息称为“头部”。为了简化块的对齐,所有块的大小都必须是头部大小的整数倍,且头部已正确地对齐。
  • 实际分配的块将多包含一个单元,用于头部本身。实际分配的块的大小将被记录在头部的size字段中。malloc函数返回的指针将指向空闲空间,而不是块的头部。
  • typedefunion的使用解决了地址的对齐问题。

参考文献:




扫一扫在手机打开当前页