C Primer Plus (6th Edition) 读书笔记(更新中)

7 minutes read

Published:

第一章 初识 C 语言

第二章 C 语言概述

第三章 数据和 C

  1. 八进制与十六进制:0x0X前缀表示十六进制值,0前缀表示八进制值。以八进制显示数字使用%o,以十六进制显示数字用%x。若要显示各进制数的前缀00xoX,必须分别使用%#o%#x%#X
  2. 打印 short、long 和 unsigned 类型:打印 unsigned int 类型的值,使用%u转换说明;%ld%lo%lx分别表示以十进制、八进制、十六进制格式打印 long 类型整数;对于 short 类型,可以使用h前缀,如%hd%ho等。
  3. 一些常用的转义序列:\a:警报;\b:退格;\n:换行;\r:回车;\t:水平制表符;\\:反斜杠(\);\':单引号;\":双引号;\?:问号。应用:

     #include <stdio.h>
     int main() {
         float salary;
         printf("\aEnter your desired monthly salary:");
         printf("$_______\b\b\b\b\b\b\b");   // 7个退格字符使得光标左移7个位置,紧跟在美元字符后面
         scanf("%f", &salary);
         printf("\n\t$%.2f a month is $%.2f a year.", salary, salary * 12.0);    // 水平制表符一般使光标移动9列
         printf("\rGee!\n");     // 以\r开始,使光标回到当行的起始处
         return 0;
     }
    
  4. 在浮点数后面加上fF后缀可覆盖默认设置,编译器将浮点型常量看作 float 类型;使用lL后缀使得数字成为 long double 类型;没有后缀的浮点型常量是 double 类型。
  5. %e 打印指数计数法的浮点数。
  6. 上溢与下溢:当计算导致数字过大,超过当前类型能表达的范围时,就会发生上溢,输出显示为 inf;当对一个很小的数做除法时,计算过程中损失了原末尾有效位上的数字,这种情况叫做下溢。
  7. NaN:一个特殊的浮点值,表示该值不是数字。NaN 的特点:NaN 不等于它本身,也不等于任何数。
  8. 复数类型:float _Complexdouble _Complexlong double _Complex。如果包含complex.h头文件,可以用complex代替_ComplexI表示虚数单位。

     #include <stdio.h>
     #include <complex.h>
     int main() {
         double complex a = 1.0 + 2.0 * I, b = 3.0 + 4.0 * I; // a = 1 + 2i, b = 3 + 4i
         double complex s_1 = a + b;	// 加
         double complex s_2 = a - b;	// 减
         double complex s_3 = a * b;	// 乘
         double complex s_4 = a / b;	// 除
         printf("s_1的实部为%f, 虚部为%f, 模为%f\n", creal(s_1), cimag(s_1), cabs(s_1));
         printf("s_2的实部为%f, 虚部为%f, 模为%f\n", creal(s_2), cimag(s_2), cabs(s_2));
         printf("s_3的实部为%f, 虚部为%f, 模为%f\n", creal(s_3), cimag(s_3), cabs(s_3));
         printf("s_4的实部为%f, 虚部为%f, 模为%f\n", creal(s_4), cimag(s_4), cabs(s_4));
         // creal():求实部函数,cimag():求虚部函数,cabs():求模函数,包含在complex.h库中
     } 
    
  9. sizeof()的返回类型是size_t,转换说明为%zd

第四章 字符串和格式化输入/输出

  1. 用数组存储字符串,数组末尾留给空字符\0,标记字符串的结束,占用一个字符的存储单元。
  2. 根据%s转换说明,scanf() 只会读取字符串中的第一个单词,它在遇到第一个空白(空格、制表符和换行符)时就不再读取输入。无法利用字段宽度让只有一个%sscanf()读取多个单词。当scanf()把字符串放进指定数组中时,它会在字符序列的末尾加上\0,让数组的内容成为一个 C 字符串。使用 scanf() 打印字符串时,不能使用 &
  3. 用大写表示宏定义常量是 C 语言一贯的传统。
  4. const可限定一个变量为只读,如const int a;constdefine的区别:
    • const定义的常数带类型,define不带类型,
    • const 在编译、运行的时候起作用,而define只在编译的预处理阶段起作用。
    • define只是简单的字符串替换,没有类型检查,可能导致边界效应,而const有对应的数据类型,需要进行判断,可避免一些低级错误。
    • define预处理后占用代码空间,而const占用数据段空间;
    • const不能重定义,而define可以通过undef取消某个符号的定义,再重新定义。
  5. %g:根据值的不同,自动选择%f%e%e格式用于指数小于 -4 或者大于等于精度时)。
  6. printf()打印百分号%时使用%%即可。
  7. printf()的转换说明修饰符:
    • 负号:待打印项左对齐,如%-20s
    • 数字:最小字段宽度,若该字段不足以容纳输出值,系统自动扩充为更宽字段,如%4d
    • 小数点 + 数字:精度,如%5.2f。对于%e%f转换,表示小数点右边数字的位数;对于%g转换,表示有效数字最大位数;对于%s转换,表示待打印字符的最大数量;对于整型转换,表示待打印字符的最小位数;只使用.表示其后跟随一个0,相当于.0
  8. 当printf()使用%c打印一个超出char类型范围的值(大于 255)时,会将该值以 256 取模,根据取模后的值打印字符。
  9. scanf()函数使用空白(换行符、制表符和空格)把输入分为多个字段。除了%c,其他转换说明都会自动跳过待输入值前面所有的空白。譬如,对于scanf("%d %f");,只要在每个输入项之间输入至少一个换行符、空格或制表符即可,可以在一行或多行输入。输入是缓冲的,只有当用户键入Enter键后输入项才会被发送给程序。
  10. printf()scanf()getchar()putchar() 的返回值(返回值都为** int 整型**!):
    • printf()函数返回打印字符的个数(包括空格和换行符等),输入错误时返回一个负值;
    • scanf()函数返回成功读取的项数,若未读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf()返回 0。
    • getchar()的返回值是用户输入的第一个字符的 ASCII 码;
    • putchar()的返回值返回的是原字符,但如果输入一连串字符,则只会返回第一个字符。

第五章 运算符、表达式和语句

  1. 负数的求模:求模结果的正负与%号左边数值的正负相同。实际上,无论何种情况,对于整数aba%b等于a-(a/b)*b
  2. 递增运算符++与递减运算符--后缀:使用 a 的值之后,递增 a;前缀:使用 b 的值之前,递增 b++--具有很高的优先级,仅次于()。另外,递增和递减运算符只能影响一个可修改左值。活用:while(++a<10)
  3. 如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符;如果一个变量出现在一个表达式中,不要对该变量使用递增或递减运算符。
  4. 副作用:对数据对象或文件的修改。例如对一个变量赋值的表达式,对该表达式求值才是主要目的,修改变量值是副作用;printf()函数产生的信息也是副作用。  序列点:程序执行的点,在该点上所有的副作用都在进入下一步之前发生。例如语句的分号、一个完整表达式的结束都是序列点。
  5. 自动类型转换:在混合类型的运算中,较小类型会被升级成较大类型;当把较大类型降级成较小类型时,可能会丢失数据。

第六章 C 控制语句:循环

  1. while循环的一个例子:根据用户输入的整数求和(非常方便):

    #include<stdio.h>
    int main() {
        int num, sum = 0;
        printf("Please enter an integer to be summed ");
        printf("(q to quit): ");
        while (scanf("%ld", &num) == 1) {   // 利用了scanf()的双重特性,可见第四章第10条笔记
            sum = sum + num;
            printf("Please enter next integer (q to quit): ");
        }
        printf("Those integers sum to %ld.\n", sum);
        return 0;
    }
    
  2. fabs() 函数:比较浮点数时,尽量只使用<>而非<=等关系运算符,因为浮点数的舍入误差会导致在逻辑上应该相等的两数却不相等。使用fabs()函数(math.h)可以方便地比较浮点数,该函数返回一个浮点值的绝对值。例如:fabs(a-b)<1e7;
  3. _Bool类型变量只能存储 1(真)和 0(假)。头文件 stdbool.hbool 定义为_Bool的别名,将 truefalse 分别定义为 1 和 0 的符号常量。
  4. 优先级:() > ! > 算术运算符 > 关系运算符 > && > || > 赋值运算符。
  5. for循环的灵活性:
    • 可以用字符代替数字计数:for (char ch = 'a'; ch <= 'z'; ch++) printf("The ASCII value for %c is %d.\n", ch, ch);
    • 第 3 个表达式可以使用任意合法的表达式:for (int x = 1; int y <= 75; y = (++x * 5) + 50) printf("%10d %10d\n", x, y);
    • 可以省略一个或多个表达式,只要包含能结束循环的语句即可:int ans = 2; for (int n = 3; ans <= 25;) ans = ans * n;
    • 第 1 个表达式也可以使用printf(),在执行其他表达式之前只对第 1 个表达式执行 1 次:for (printf("Keep entering numbers!\n"); int num != 6;) scanf("%d", &num);
  6. 逗号运算符:逗号运算符把多个表达式连接成一个表达式,并保证最左边的表达式最先求值。逗号运算符通常在for循环头的表达式中用于包含更多的信息。整个逗号表达式的值是逗号右侧表达式的值。例如:for (ounces = 1,cost = 46; ounces <= 16; ounces++, cost += 20) printf("%5d  $%4.2f", ounces, cost / 100.0);
  7. for (; test;)while (test) 等效。一般而言,当循环涉及初始化和更新变量时,用for循环比较合适,而在其他情况下用while循环更合适。

第七章 C 控制语句:分支和跳转

  1. 把两个行为合并成一个表达式是 C 特有的编程风格。对比while (scanf("%d", &n) == 1)while ((ch = getchar()) != '\n')
  2. ctype.h 系列的字符函数:如果字符参数属于某特殊类别,返回真;否则返回假。
    • isalnum(): 字母或数字
    • isalpha(): 字母
    • isdigit(): 数字
    • islower(): 小写字母
    • isupper(): 大写字母
    • isspace(): 空白字符
    • tolower(): 若参数是大写字符,返回小写字符;否则返回原始参数
    • toupper(): 若参数是小写字符,返回大写字符;否则返回原始参数
  3. 如果没有花括号,else与离它最近的if匹配,除非最近的if被花括号括起来。
  4. C 保证逻辑表达式的求值顺序是从左往右,&&||都是序列点,程序在从一个运算对象执行到下一个运算对象之前,所有的副作用都会生效。
  5. switch的测试表达式和标签都必须是整数值(包括char类型),标签必须是常量或完全由常量组成的表达式。若使用浮点类型的变量选择无法用switch
  6. goto的一种可接受用法:从一组嵌套循环中跳出(一条break语句只能跳出当前循环)。

第八章 字符输入/输出和输入验证

  1. 缓冲输入:用户输入的字符被收集并存储在一个被称为缓冲区的临时存储区,按下 Enter 键后,程序才可使用用户输入的字符。缓冲分为两类:
    • 完全缓冲 I/O:当缓冲区被填满时才刷新缓冲区(通常出现在文件输入中);
    • 行缓冲 I/O:出现换行符时刷新缓冲区(如键盘输入)。
  2. 流:一个实际输入或输出映射的理想化数据流。stdin 流表示键盘输入,stdout 流表示屏幕输出。
  3. getchar()scanf()读取文件检测到文件结尾时返回 EOF(定义为-1),PC 键盘输入EOF时,要在一行开始处按下Ctrl+Z
  4. 重定向:把 stdin 流或 stdout 流重新赋给文件,是一个命令行概念,是除 C 文件函数之外另一种使用文件方式。重定向运算符(在 Windows 命令提示符中使用):
    • 重定向输入:< 符号。它把文件中的内容导入程序。如:E:\program.exe < E:\file.txt
    • 重定向输出:> 符号。它创建一个新文件,把程序的输出重定向至该文件中。如:E:\program.exe > E:\new_file.txt
    • 组合重定向:两个重定向运算符可以一起使用,如:E:\program.exe < E:\file.txt > E:\new_file.txt。命令与重定向运算符的顺序无关;在一条命令中,输入文件名与输出文件名不能相同;重定向运算符不能读取多个文件的输入,也不能把输出定向至多个文件。 (补充:cmd 中的type命令能检查文件中的内容,将之打印在屏幕上。)
  5. 有时,输入时按下 Enter 键产生的换行符可能会干扰getchar()的判断使程序产生错误。可以采用while (getchar() != '\n') continue;语句丢弃输入行剩余的字符。一个可以消除这种影响的自定义函数get_first():

    char get_first(void) {
        int ch;
        ch = getchar();
        while (getchar() != '\n')
            continue;
        return ch;
    }
    

第九章 函数

  1. 如果函数返回值的类型与函数声明的类型不匹配,实际得到的返回值相当于把函数中指定的返回值赋给与函数类型相同的变量所得到的值。
  2. 递归的几个注意点:
    • 每级函数调用都有自己单独的变量;
    • 每次函数调用都会返回 1 次;
    • 递归函数中位于递归调用之前的语句,均按被调函数的顺序执行;
    • 递归函数位于递归调用之后的语句,均按被调函数相反的顺序执行;
    • 递归函数必须包含能让递归调用终止的语句。
    • 递归的缺点是消耗内存多、效率不高、费时。
  3. 尾递归:把递归调用置于函数的末尾,即return语句之前。尾递归是最简单的递归形式。
  4. 用递归实现将十进制整数转化为二进制的算法:

    void to_binary(unsigned n) {
        int r;
        r = n % 2;
        if (n >= 2)
            to_binary(n / 2);
        putchar(r == 0 ? '0' : '1');
        return;
    }
    
  5. 求斐波那契数Fibonacci(n)的循环算法和递归算法:

    unsigned Fibonacci_loop(unsigned n) {
        unsigned i, x = 1, y = 1, z;
        if (n > 2) {
            for (i = 3; i <= n; i++) {
                z = x + y;
                x = y;
                y = z;
            }
            return z;
        }
        else return 1;
    }
    
    unsigned Fibonacci_recursion(unsigned n) {
        if (n > 2)
            return Fibonacci_recursion(n - 1) + Fibonacci_recursion(n - 2);
        else return 1;
    }
    
  6. 对于多源代码文件的程序,把函数原型和已定义的字符常量放在头文件中是良好的编程习惯。
  7. 调用自定义头文件时,#include指令用双引号。
  8. %p是输出变量地址的转换说明,通常以十六进制显示指针的值。
  9. 指针:一个值为内存地址的变量。int *p表明p是一个指针,p指向的对象*p是 int 型,不能认为指针是 int 型,它的类型是“指向 int 类型的指针”。 普通变量把值作为基本量,把地址作为通过&运算符获得的派生量;指针变量把地址作为基本量,把值作为通过*运算符获得的派生量。
  10. 两种函数调用:
    • 形如function1(x);的函数调用传递的是x的值,如果要计算或处理值,采取此种形式的函数调用;
    • 形如function2(&x);的函数调用传递的是x的地址,如果要在被调函数中改变主调函数的变量,采取此种形式的函数调用。

第十章 数组和指针

  1. 使用const声明和初始化数组:在数组声明的数据类型前加上const关键字,可以把数组设置为只读。
  2. 指定初始化器:该特性可以初始化指定的数组元素:在初始化列表中使用带方括号的下标指明代初始化的元素。几个要点:
    • 对与一般的初始化,在初始化一个元素后,未初始化的元素都会被设置为 0;
    • 如果指定初始化器后面有更多的值,这些值将被用于初始化指定元素后面的元素;
    • 如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化;
    • 如果未指定元素大小,编译器会把数组的大小设置为足够装得下初始化的值。 例如:int arr[6] = {[5] = 212}; int days[12] = {31, 28, [4] = 31, 30, 31, [1] = 29}; int staff[]={1, [6] = 4, 9, 10};
  3. 数组名是数组首元素的地址:a == &a[0],指针加 1 指的是增加一个存储单元,指针的值递增它所指向类型的大小:a+2 == &a[2]; *(a+2) == a[2];
  4. 数组形参:int sum(int *ar, int n)int sum(int ar[],int n)等价。在声明函数时,还可以表示成int sum(int *,int)int sum(int [],int)
  5. 指针运算中的优先级:一元运算符*++的优先级相同,但结合律是从右往左,因此 *p++ 相当于 *(p++)。对比:*p++表示先使用指针指向位置上的值,再递增指针;*++p表示先递增指针,再使用指针指向位置上的值;(*p)++表示先使用p指向的值,再递增该值,而不是递增指针。(注意:当p是指针变量时才能使用p++这样的形式,若p是数组名则不可以)
  6. 指针操作:
    • 赋值:把地址赋给指针;
    • 解引用:*运算符给出指针指向地址上存储的值,不能解引用未初始化的指针;
    • 取址:&运算符给出指针本身的地址;
    • 指针与整数相加减:初始地址加(或减)指针指向类型的大小与整数之积,得到一个指针;
    • 递增/递减指针:递增(递减)指向数组元素的指针可以让该指针移动至数组的下一个元素;
    • 指针求差:计算两指针指向元素之间的距离,差值的单位与数组类型的单位相同(打印地址差值的转换说明是%td);
    • 比较:使用关系运算符可以比较指向相同类型对象的两个指针的值。
  7. 如果函数不是要修改数组中的数据,声明函数形参时应使用关键字const。这里使用const并非要求原数组是常量,而是该函数在处理数组时将其视为常量。如果编写的函数不用修改数组,声明形参最好使用const。如:int sum(const int ar[], int n);int sum(const int *ar, int n);
  8. 形如const double *p的指针称为指向const的指针,它不能修改指向地址上的值。const数据和非const数据的地址都可以赋给指向const的指针,但只能把非const数据的地址赋给普通指针。 把const指针赋给非const指针不安全;把非const指针赋给const指针有效,前提是只进行一级解引用。 另外,double *const p表示该指针不能更改它所指向的地址,const double *const p表示该指针既不能更改它所指向的地址,也不能修改指向地址上的值。
  9. **a==*(a[0])==a[0][0]*(*(a+i)+j)==a[i][j]
  10. 指向二维数组的指针:int (*px)[2],表示px指向一个内含两个 int 型值的数组。(注意括号,int *pz[2]表示一个内含两个指向 int 的指针的数组)
  11. 不能在指向不同类型的指针之间赋值,包括指向内含不同数目的不同类型值的数组的指针。
  12. 处理二维数组的函数的声明:void function(int (*ar)[4]);void function(int ar[][4]);
  13. 变长数组(VLA):允许使用变量表示数组的维度。比如:int rows = 4, cols = 5; int ar[rows][cols]; 声明一个带二维变长数组参数的函数时,形参列表中必须在声明数组名前声明两个维度的形参,如:int sum(int rows, int cols, int ar[rows][cols]); 其中变长数组名实际上是一个指针。使用 VLA 函数可以处理任意大小的二维数组,摆脱将数组列数内置在函数体内的束缚。
  14. 复合字面量:字面量是除符号常量外的常量,数组的复合字面量类似数组初始化列表,前面是用括号括起来的类型名,如:(int [2]){10,20}(int []){50,20,90}(数组大小可省略)、(int [2][4]){{1,2,3,-9},{4,5,6,-8}}等,相当于创建一个匿名数组,必须在创建的时候使用它。

第十一章 字符串和字符串函数

  1. 字符串常量被视为const数据,属于静态存储类别,它是指向该字符串存储位置的指针,该字符串所指向地址上存储的值是字符串的首字符。
  2. 字符串的数组表示法和指针表示法:数组表示法:如char m1[] = "Data"; 指定数组大小时确保数组元素个数至少比字符串多 1(为了容纳\0),所有未被使用的元素都被自动初始化为\0,或者直接省略大小。指针表示法:const char *pt1 = "Structure"; 建议把指针初始化为字符串字面量时使用const,若打算修改字符串就不要用指针指向字符串字面量。二者的区别:初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针直接把字符串的地址赋给指针。
  3. 字符串数组:如果要用数组表示一系列待显示的字符串,最好使用指针数组,因为它比二维字符数组的效率高。指向字符串的指针数组:const char *fruit1[3] = {"Apple", "Pear", "Orange"};char类型数组的数组:char fruit2[3][7] = {"Apple", "Pear", "Orange"}; 
  4. 字符串输入:
    • gets():读取整行输入直至遇到换行符,丢弃换行符并在末尾添加\0。如:gets(words); 但是get()无法检查数组是否能容下输入行,有可能导致缓冲区溢出,一般摒弃该函数。
    • fgets():一般用于处理文件输入,一般格式:fgets(字符串地址, 读入字符最大数量, 读入文件);fgets()读入最大读入数 - 1个字符,或者读到遇到的第一个换行符为止,并将换行符存入字符串,在末尾加\0。如果读取从键盘输入的数据,第三个参数为 stdin。如fgets(words, STLEN, stdin);
      • fgets()函数返回指向char的指针。若一切顺利,返回的地址与传入的第一个参数相同。如果函数读到文件结尾或读入数据出现错误将返回空指针 NULLNULL保证不会指向有效的数据。
      • fgets()读入的换行符处理掉的方法:将其替换为空字符:while (words[i] != '\n') i++; words[i] = '\0';
      • 丢弃输入行中超出目标数组容量的部分:while(getchar() != '\n') continue;
      • 当输入太长超过数组容量时,fgets()最容易使用,而且可以选择不同的处理方式:
        // 如果想让程序继续使用输入行中超出的字符时的处理方法
        while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n')  
                fputs(words, stdout);
      
        // 如果想丢弃输入行的超出字符时的处理方法
        while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') { 
                i = 0;
                while (words[i] != '\n' && words[i] != '\0')
                    i++;
                if (words[i] == '\n')
                    words[i] = '\0';    // 如果先遇到换行符,将其替换为空字符
                else    // 如果先遇到空字符
                    while (getchar() != '\n')
                        continue;   // 丢弃输入行中超出目标数组容量的部分
                puts(words);        
            }
      
        // 一种代替上述第二种情况的自定义输入函数,一定程度上可以替换gets()
        char *s_gets(char *st, int n){
            char *ret_val;  // 返回值
            int i = 0;
            ret_val = fgets(st, n, stdin);
            if (ret_val){
                while (st[i] != '\n' && st[i] != '\0')
                    i++;
                if (st[i] == '\n')
                    st[i] = '\0';
                else 
                    while (getchar() != '\n')
                        continue;
            }
            return ret_val;
        }
      
    • gets_s():形如gets_s(words, STLEN); 只从标准输入中读取数据,读到换行符时丢弃之,但读到最大字符数时仍未读到换行符的情况很麻烦(如果不希望程序中止需要编写特殊的处理函数,且继续运行时gets_s()会丢弃输入行的剩余字符),只要输入行未超过最大字符数,gets_s()gets()几乎一样。
    • scanf():读取“单词”,以空白字符作为字符串的结束。若指定字段宽度(如%10s),满足读取 10 个字符或读到第 1 个空白字符之一时停止。
  5. 字符串输出
    • puts():参数是字符串的地址,在遇到\0时停止输出,显示字符串时自动在末尾添加一个\n
    • fputs():第二个参数指明要写入数据的文件,打印在显示器上时为 stdout(如fputs(line, stdout)),fputs()不会在输出的末尾添加\n。 (gets()丢弃输入中的\nputs()在输出中添加\nfgets()保留输入中的\nfputs()不在输出中添加\n
    • printf()printf("%s\n", string);puts(string);等效。
    • 自定义字符串输出函数:例如一个打印字符串时不添加\n的函数:
      void put1(const char *string){
          while (*string) // 等价于while(*string != '\0')
              putchar(*string++);
      }
      
  6. 字符串函数(除了 sprintf()stdio.h,其余几个函数均在 string.h 头文件中)
    • strlen()size_t strlen(const char *s);,统计字符串长度
    • strcat()char *strcat(char *restrict str1, const char *restrict str2);,拼接两个字符串,返回str1。把str2指向的字符串拷贝至str1指向的字符串末尾,str2不变,str2的第一个字符将覆盖str1末尾的\0。另外,strcat()无法检查str1是否能容纳str2,有可能导致缓冲区溢出。
    • strncat()char *strncat(char *restrict str1, const char *restrict str2, size_t n);,把str2字符串中的n个字符拷贝至str1指向的字符串末尾,在加到第n个字符或遇到\0时停止。
    • strcmp()int strcmp(const char *str1, const char *str2);,如果str1str2相等,返回 0;否则返回非零值:字母表中str1str2前面时返回正值,str1str2后面时返回负值。(返回的具体值取决于实现,例如可以返回 ASCII 码之差)
    • strncmp()int strncmp(const char *str1, const char *str2, size_t n);,逐个比较str1str2的前n个字符,比较n个字符或遇到第 1 个\0时停止比较。返回值与strcmp()类似。
    • strcpy()char *strcpy(char *restrict str1, const char *restrict str2);,把str2指向的字符串(包括空字符)被拷贝至str1指向的数组,返回值是str1str2称为源字符串,str1称为目标字符串。可以把指向源字符串的指针声明为指针、数组名或字符串常量,但是指向目标字符串的指针应指向一个数据对象(如数组)且该对象有足够的空间存储源字符串的副本(strcpy()也不能检查目标空间是否能容纳源字符串的副本)。另外,strcpy()的第 1 个参数不必指向数组的开始,例如strcpy(str1 + 7, str2);
    • strncpy()char *strncpy(char *restrict str1, const char *restrict str2, size_t n);,可拷贝的最大字符数是n。 把str2中的n个字符或空字符前的字符(满足其一)拷贝至str1中,拷贝副本中不一定有空字符。有些情况下(source很长),为确保target仍为字符串,可以令n比目标数组大小少 1,把数组末尾元素设置为\0
    • sprintf():第 1 个参数是目标字符串的地址,其余参数与printf()相同。sprintf()把数据写入字符串,可以把多个元素合成一个字符串,存储在目标数组中。如sprintf(array, "%s,%-19s:%6.2f\n", str1, str2, number);
    • strchr()char *strchr(const char *str, char ch);,如果str中包含字符ch,返回指向str中首次出现的ch字符的指针(末尾的\0也在查找范围内),如果未找到返回NULL
  7. 命令行参数:命令行是命令行环境中用户为运行程序输入命令的行,命令行参数是同一行的附加量。int main(int argc, char *argv[])(或char **argv),argc代表命令行中的字符串数量(以空格间隔),argv是一个指向指针数组的指针,指针数组中存储每个命令行字符串的地址,argv[0]指向程序名称,argv[1]指向第一个命令行参数,以此类推。也可以使用双引号把多个单词括起来形成一个参数,如.\test.exe python c++ "c primer plus"
  8. 把字符串转换为数字(以下函数均在stdlib.h头文件中):
    • atoi():把字符串转换成 int 型;atof():把字符串转换成 double 型;atol():把字符串转换成 long 型。
    • strtol()函数:long strtol(const char *restrict nptr,char **restrict endptr,int base);,把字符串转换成 long 类型的值。nptr是指向待转换字符串的指针,endptr是一个指针的地址,该指针被设置为标识输入数字结束字符的地址,base代表以什么进制写入数字。例如:char *end; value1 = strtol("10atom", &end, 10); value2 = strtol("10atom", &end, 16);,则value1值为 10,value2值为 266(十进制下)= 10a(十六进制下)。strtoul()strtol()用法类似,它把字符串转换为 unsigned long 型;strtod()只有前两个参数,把字符串转换为 double 型。

第十二章 存储类别、链接和内存管理

  1. 作用域:程序中可访问标识符的区域。
    • 块作用域:定义在中的变量具有块作用域。块是用一对花括号括起来的代码区域(循环和 if 语句所控制的代码也是块,即使它们没有被花括号括起来),块作用域变量的可见范围是从定义处到包含该定义的块的末尾。声明在内层块中的变量,其作用域仅限于该块。
    • 文件作用域:定义在函数外面的变量具有文件作用域,称为全局变量。全局变量对翻译单元(源代码文件和它包含的头文件)中位于其声明后面的所有函数均可见。
    • 此外还有函数作用域(goto标签)、函数原型作用域(函数形参名)。
  2. 链接:文件作用域变量可以有外部链接(可以在多文件程序中使用)或内部链接(以static关键字声明,只能在一个翻译单元使用),其他三种作用域变量都是无链接变量,属于其作用域私有。
  3. 存储期:对象在内存中的生存期。
    • 静态存储期:对象在程序执行过程中一直存在。所有的文件作用域变量都具有静态存储期,以static声明在块中的变量也有静态存储期。
    • 自动存储期:块作用域变量通常具有自动存储期。程序进入块时为变量分配内存,退出块时释放内存。
    • 此外还有线程存储期(并发程序设计)、动态分配存储期。
  4. 五种存储类别
    • 自动变量:自动存储期、块作用域、无链接。可以使用存储类别说明符auto显式声明自动变量。如果内层块中声明的变量与外层块中的变量同名,内层块会隐藏外层块的定义。
    • 寄存器变量:自动存储期、块作用域、无链接。存储在 CPU 的寄存器中,无法获取其地址。使用存储类别说明符register可声明寄存器变量。
    • 块作用域的静态变量:静态存储期、块作用域、无链接。在块中以存储类别说明符static声明。不能在函数形参中使用static
    • 外部链接的静态变量:静态存储期、文件作用域、外部链接。又称外部变量。把变量的定义性声明放在所有函数之外便创建了外部变量,如果外部变量定义在另一个源代码文件中,必须用关键字extern在该文件中声明该变量(若要说明某函数使用了外部变量,可以在函数内部使用extern重复声明,可有可无)。外部变量只能使用常量表达式初始化且只能初始化一次,如果未初始化外部变量,它将被自动初始化为 0。
    • 内部链接的静态变量:静态存储期、文件作用域、内部链接。在所有函数外部用static定义的变量属于此种存储类别,也可以使用extern在函数中重复声明,不改变其链接属性。