Skip to content

Latest commit

 

History

History
600 lines (327 loc) · 33.1 KB

c_primer_plus_note.md

File metadata and controls

600 lines (327 loc) · 33.1 KB

第1章 概览

1. 使用C语言的7个步骤

  1. 定义程序目标

  2. 设计程序

  3. 编写代码

  4. 编译

  5. 运行程序

  6. 测试和调试程序

  7. 维护和修改程序

2. 编译器和链接器的作用

  1. 编译器:把源代码转换成机器语言代码,即目标代码

  2. 链接器:把多个来源(例如,已编译的源代码、库代码和启动代码)的目标代码连接成一个单独的可执行程序

第2章 C语言概述

  1. C99标准允许一个标识符最多可以有63个字符

  2. 操作系统和C库通常使用以一个或两个下划线开始的名字

  3. 转义字符:通常用于代表难于表达的或是无法键入的字符

  4. 程序状态(program state):指在程序执行过程中给定点上所有变量值的集合

第3章 数据和C

1. 数据类型关键字

  1. 数据类型按其在计算机中的存储方式被划分为两个系列:整数类型和浮点类型

  2. 最小的存储单位称为,位是计算机存储的基本单位,字是自然的存储单位

  3. 浮点数往往只是实际值的近似

2. C数据类型

  1. C的各种整数类型的区别在于所提供数值的范围,以及数值是否可以取负值

  2. 系统通过使用一个指示正负符号的特定位来表示有符号整数

  3. 最好避免在一个声明语句中同时出现初始化和未初始化的变量,如int dogs, cats = 94;

  4. %d被称为格式说明符,必须确保格式说明符的数目同待打印值的数目相同,编译器不会发现这类错误;C通过一种函数原型机制检查函数调用是否使用了正确数目及类型的参数,但这对参数数目可变的printf()和scanf()不起作用

  5. 前缀0x或者0X表示使用十六进制,前缀0表示使用八进制;%o用于显示八进制整数,%x用于显示十六进制;可以使用说明符**%#o**、%#x和**%#X**分别生成前缀0、0x和0X

  6. C99增加了unsigned long long int(简写为unsigned long long)类型

  7. 为了适应不同机器,C仅保证short类型不会比int类型长,并且long类型不会比int类型短

  8. 在long类型大于int类型的系统中,使用long类型会减慢计算

  9. 建议使用L后缀显式标识long类型值,使用LL后缀显式标识long long类型值

  10. %ho表示以八进制显式short整数

  11. C将字符常量视为int类型而非char类型,如char grade = 'B',这里'B'作为数值存储在一个32位单元中,而赋值后的grade则把66存储在一个8位单元中,利用这种特性,可以定义一个字符常量'FATE',这将把4个独立的8位ASCII码存储在一个32位单元中,若把该字符常量赋值给一个char变量,那么变量值位'E'

  12. 在ASCII码和转义序列之间优先选择使用转义序列;当需要使用数值编码时,应使用'\032'而非032,因为'\032'这样的转义序列可以嵌入到C字符串中

  13. 浮点常量最基本的形式:包含一个小数点的一个带符号的数字序列,接着是字母e或E,然后是代表10的指数的一个有符号值

  14. 在浮点常量中不要使用空格,如1.56 E+12;可以通过f或F后缀使编译器把浮点常量当作float类型,没有后缀的浮点常量默认为double类型

  15. printf()函数使用**%f格式说明符打印十进制计数法的float和double数字,用%e打印指数计数法的数字;打印long double类型需要用%Lf**、%Le和**%La**说明符

  16. 未在原型中显式说明参数类型的函数(如printf())传递参数时,C自动将float类型的参数转换为double类型

3. 参数和易犯的错误

  1. 使用**%d显示float值不会把该float值转换为近似的int值,而是显示垃圾值;使用%f**显示int值也不会把该int值转换为浮点值,同样显示垃圾值

4. 刷新输出

  1. 标准C规定以下几种情况会将缓冲区内容传给屏幕:缓冲区满、遇到换行符、需要输入的时候;除此之外,使用fflush()函数也可以刷新输出缓冲区

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

1. 字符串简介

  1. \0表示空字符,C用它来标记字符串的结束;空字符不是数字0,是非打印字符,其ASCII码值为0

  2. scanf()读取输入时在遇到第一个空白字符空格、制表符或者换行符时停止读取

  3. strlen()函数以字符为单位计算字符串的长度,字符串结束符'\0'不计算在内

  4. 一些ANSI之前的UNIX系统使用头文件strings.h而非string.h

  5. sizeof运算符把标识字符串结束的不可见的空字符'\0'也计算在内;sizeof是否使用圆括号取决于是想获取一个类型的大小还是想获取某个具体量的大小;圆括号对于类型是必需的,而对于具体量则是可选的,但建议都使用圆括号,如:sizeof(char)sizeof(6.28)

2. printf()和scanf()

  1. printf()函数的转换说明符及作为结果的打印输出

    %a / %A:浮点数、十六进制数字和p-计数法(C99)

    %g / %G:根据数值不同自动选择**%f%e**

    %p:指针

    %%:打印一个百分号

  2. printf()和其他任何不使用显式原型的C函数的所有float参数会自动被转换成double

  3. 不匹配的浮点数转换,如下所示:

    #include <stdio.h>
    int main(void)
    {
        float n1 = 3.0;
        double n2 = 3.0;
        long n3 = 2000000000;
        long n4 = 1234567890;
    
        printf("%.1e %.1e %.1e %.1e\n", n1, n2, n3, n4);
        printf("%ld %ld\n", n3, n4);
        printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
    
        return 0;
    }
    结果如下:
    3.0e+00 3.0e+00 3.1e+46 2.2e-314
    2000000000 1234567890
    0 1074266112 0 1074266112
    

    由上可见,程序试图使用**%ld**打印一个long类型居然也失败了,因为printf()一次按格式说明符从栈顶取出相应大小的数据,详情请查看P75-76

  4. printf()函数的返回值表示打印字符的数目(统计字符数目时,针对所有的打印字符,包括空格和不可见的换行字符),如果输出有错误,那么printf()返回一个负数

  5. 当一行代码太长需要换行时,如果使用'',则下一行必须从行的最左边开始

  6. 参数:printf()使用变量名、常量和表达式;scanf()使用指向变量的指针

  7. printf()把**%f**、%e%E%g和**%G同时用于float类型和double类型,而scanf()只把它们用于float类型,而用于double类型时要求使用l**修饰

  8. scanf()读取时持续读取和保存字符直到它遇到第一个非数字的字符,scanf()把这个非数字字符放回输入缓冲

  9. 当scanf()把字符串存放在一个指定的数组中时,会添加终止符**'\0'**

  10. scanf()格式字符串中的常规字符,如:scanf("%d, %d", &n, &m);则在输入时第一个数字后必须紧跟着输入一个逗号

  11. scanf()格式字符串中的空格意味着跳过下一个输入项之前的任何空格(包括没有空格);如果在格式字符串中**%c**之前有一个空格,那么scanf()会跳到第一个非空白字符处

  12. scanf()返回成功读入的项目的个数;若没有任何读入,返回0;若检测到文件结尾,返回EOF

  13. printf()和scanf()的***修饰符:对于printf()必须使用一个参数来告诉函数该*是什么;对于scanf(),当*放在%**和说明符字母之间时,是函数跳过相应的输入项目,比如可用于读取一个文件中某个特定的列

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

1. 基本运算符

  1. 数据对象、左值、右值和操作数

    a. 数据对象:泛指数据存储区

    b. 左值:用于标识一个特定的数据对象的名字或表达式

    c. 右值:能赋值给可修改的左值的量

    d. 操作数:运算符操作的对象

  2. C90标准将一元 + 运算符加进了C中,该运算符不改变它的操作数的值或符号

  3. 在C中,整数除法结果的小数部分被丢弃,称为截尾

  4. 运算符的结合性只适用于共享同一操作数的运算符

2. 其他运算符

  1. sizeof运算符以字节为单位返回其操作数的大小;该操作数可以是一个具体的数据对象,或者一个类型;如果它是一个类型,操作数必须被括在圆括号内;通常应使用圆括号将操作数括起来

  2. 取模运算只针对整数而言,负数取模,结果的符号由第一个操作数的符号决定

  3. 在同一个位置执行循环的判断和循环的改变可以防止忘记更新循环而导致死循环,如**while (count++ < 10)**

  4. 增量运算符 ++ 通常能产生更高效的机器语言代码

  5. 避免使用前缀形式和后缀形式将导致不同效果的代码,如不要使用 b = ++i; 而应使用 ++i; b =i; 来代替

  6. 增量运算符和减量运算符只能影响一个变量,如 (x * y)++ 是无效的

  7. 在C中编译器可以选择优先计算函数里的哪个参数的值

  8. 使用自增自减运算符时应当注意:

    a. 如果一个变量出现在同一个函数的多个参数中时,不要将增量或者减量运算符用于该变量

    b. 当一个变量多次出现在一个表达式里时,不要将增量或者减量运算符运用于该变量

3. 表达式和语句

  1. 程序:一系列带有某种必需的标点的语句的集合

  2. 一个语句是一条完整的指令,但不是所有完整的指令都是语句,如 x = 6 + (y = 5); y = 5是一个完整的指令,但不是一个语句

  3. 副作用:对数据对象或文件的修改

  4. 顺序点:程序中执行的一点,所有的副作用都在进入下一步之前被计算

  5. 复合语句:使用花括号组织起来的两个或更多的语句

4. 类型转换

  1. 类型转换的基本规则:

    a. 当出现在表达式里,有符号和无符号的char和short类型都将自动被转换为int,在需要的时候,将自动转换为unsigned int(如果short与int有相同的大小,那么unsigned short比int大,这时将把unsigned short转换为unsigned int)

    b. 在包含两种数据类型的任何运算里,两个值都将被转换为两种类型里较高的级别

    c. 类型级别从高到低的顺序:long double, double, float, unsigned long long, long long, unsigned long, long, unsigned int; (注:当long和int具有相同大小时,unsigned int 比 long 的级别更高)

    d. 在赋值语句里,计算的最后结果被转换成将被赋予值的那个变量的类型

    e. 当作为函数的参数被传递时,char和short会被转换为int,float会被转换成double

  2. 指派运算符:(type),如 float f = 1.2f; int a = (int)f;

5. 带有参数的函数

  1. C99规定:对实际参数或者实际参量使用术语 参数 ; 对形式参数或者形式参量使用术语 参量

  2. 函数原型:是一个函数声明,描述了函数的返回值和参数

  3. 每个运算符的特性包括:所需操作数的数量、优先级和结合性

第6章 C控制语句:循环

1. while语句

  1. 每次循环都被称为一次迭代

  2. 若while语句块为空语句,应该将分号放在下面的一行而不是放在同一行中,如下:

    while (scanf("%d", &num) == 1)
    	;
    

2. 比较大小:使用关系运算符和表达式

  1. 在浮点数比较中只能使用**><**

  2. 如果进行比较的双方中有一个是常量,则可以把它放在比较表达式的左边

  3. 一个**_Bool变量只可以具有值1(真)或0(假),如果把一个非零的值赋给_Bool变量,则_Bool**变量被设置为1

  4. C99还提供了一个stdbool.h头文件,可以使用bool来代替_Bool,并把true和false定义为值1和0的符号常量,通过这种方式可以和C++兼容

3. 退出条件循环:do while

  1. while循环和for循环是入口条件循环,do while循环是退出条件循环

  2. do while循环本身就是一个语句,因此需要一个结束的分号

  3. C99允许使用常量值制定数组大小,而C90不允许,但#define在两种情况下都可以使用

  4. 现代编程习惯把程序的元素分为接口和实现两部分

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

1. if语句

  1. 在需要类型转换的地方,建议使用显式类型转换

  2. C99标准要求编译器至少支持127层嵌套

2. 求值的顺序

  1. 除了两个运算符共享一个操作数的情况以外,C通常不保证复杂表达式的哪个部分首先被求值

  2. 判断一个字符是否是字符时,建议使用移植性更好的方法:使用ctype.h中声明的函数

3. 多重选择:switch和break

  1. switch判断表达式应该具有整数值(包括char类型);case标签必须是整型(包括char)常量或者整数常量表达式(仅包含整数常量的表达式)

第8章 字符输入/输出和输入确认

1. 单字符I/O:getchar()和putchar()

  1. 输入回显:获取从键盘输入的字符并将其发送至屏幕

2. 缓冲区

  1. 缓冲分为两类:完全缓冲I/O和行缓冲I/O;对于完全缓冲输入来说,缓冲区满时被清空,通常出现在文件输入中,缓冲区的大小取决于系统;对行缓冲I/O来说,遇到一个换行符时将被清空缓冲区,键盘输入是标准的行缓冲

  2. 文件结束符:Ctrl+D

3. 重定向和文件

  1. 使用重定向运算符(>、>>、<等)时,输入不能来自一个以上的文件,输出也不能定向至一个以上的文件

第9章 函数

1. 函数概述

  1. 函数:用于完成特定任务的程序代码的自包含单元

  2. return; 只能用于void类型的函数中

2. ANSI C的函数原型

  1. 函数原型用来声明函数的返回值类型,参数个数以及各参数的类型;函数原型中可以省略参数变量名,函数原型中的参数变量名只是虚设的名字,不必和函数定义中使用的参数变量名想匹配;

  2. 若使用函数原型 void print_name(); ,ANSI C编译器不会进行参数检查;为了表示一个函数确实不适用参数,应当在圆括号内使用void关键字,如:void print_name(void);,这时当对该函数进行调用时,ANSI C编译器会检查函数参数以保证确实没有使用参数

3. 多源代码文件程序的编译

  1. 编译第一个文件并将其链接到第二个文件的目标代码中:gcc file1.c file2.o

  2. 在UNIX和DOS环境下,#include "hotel.h" 中的双引号表示被包含的文件位于当前工作目录下

4. 指针简介

  1. 指针变量ptr和**&pooh的区别:ptr为变量,而&pooh**为常量

  2. ***** 和指针名之间的空格是可选的,通常在声明中使用空格,而在指向变量时省略空格

  3. 函数的参数用于函数间通信,输入参数可以把调用函数中的数值传递给被调用函数,输出参数可以把被调用函数中的数值传递给调用函数

第10章 数组和指针

1. 数组

  1. 如果不初始化数组,其中存储的时无用的数值;但是如果部分初始化数组,则未初始化的元素则被设置为0;变长数组除外

  2. 指定初始化项目:C99规定,在初始化列表中使用带有方括号的元素下标可以指定某个特定的元素

    a. 如果一个指定初始化项目后跟有不止一个值,则这些数值将用来对后续的数组元素初始化

    b. 如果多次对一个元素进行初始化,则会自动覆盖前面的数值,只有最后的一次生效

  3. C不支持把数组作为一个整体来进行赋值,也不支持用花括号括起来的列表形式进行赋值(初始化赋值除外)

  4. 变长数组:VLA,变长数组中得“变”指使用已初始化的变量值来指定数组大小,(sizeof表达式是一个整数常量,而一个const值不是整数常量)C99引入变长数组主要为了更适合于做数值计算

  5. 多维数组初始化时,可以省略内部的花括号,只保留最外面的一对花括号,只要保证数值的个数正确即可

2. 函数、数组和指针

  1. 在C中,对一个指针加1的结果是对该指针增加一个存储单元;对于数组而言,地址会增加到下一个元素的地址,而不是下一个字节

  2. 在函数原型或函数定义头的场合(并且只有在这两种场合),可以用int *arr代替int arr[]

  3. 向函数传递数组信息时的方法:

    a. 使用一个指针参量确定数组的开始地址;使用另一个整数参量来指明数组的元素个数

    b. 传递两个指针,一个指针指明数组的起始地址,第二个指针指明数组的结束地址;如:while (start < end),处理的最后一个元素将是end所指向的位置之前的元素,左闭右开区间,end实际指向的位置在数组最后一个元素之后;C保证在为数组分配存储空间的时候,指向数组之后的第一个位置的指针也是合法的

  4. 指针符号(尤其在对其使用增量运算符时)更接近于机器语言,而且某些编译器在编译时能生成更高效的代码

3. 指针操作

  1. 指针求差值:差值的单位是相应类型的大小,有效指针差值运算的前提是参加运算的两个指针是指向同一个数组(或是其中之一指向数组后面的第一个地址)

  2. 不能对未初始化的指针取值

4. 保护数组内容

  1. 如果函数想修改数组,那么在声明数组参量时就不要使用const;如果函数不需要修改数组,那么在声明数组参量时最好使用const

  2. 将常量或非常量数据的地址赋给指向常量的指针(**const ***)是合法的;但只有非常量数据的地址可以赋给普通的指针

  3. 在函数参量定义中使用const,不仅可以保护数据,而且使函数可以使用声明为const的数组

5. 指针和多维数组

  1. 数组指针和指针数组:

    a. 数组指针:指向一个数组的指针,本身是一个指针,指向一个数组;如:int (* p) [2];

    b. 指针数组:本身是一个数组,数组元素为某种类型的指针;如:int * p[2];

  2. const指针赋给非const指针是错误的,因为有可能使用新指针来改变const数据出现异常;但是把非const指针赋给const指针是允许的(前提是只进行一层间接运算),如下:

    const int **p2;
    int *p1;
    const int n = 13;
    pp2 = &p1; //不允许,先假设允许
    *pp2 = &n; //合法,二者都是const,但同时会使p1指向n
    *p1 = 10; //合法,但这将改变const n的值
    
  3. 编译器会把数组符号转换成指针符号

  4. 通常声明N维数组的指针时,除了最左边的方括号可以留空之外,其他都需要填写数值;如:int sum(int arr[][12][20][30], int rows);;因为第一个方括号表示一个指针,而其他方括号描述的是所指向对象的数据类型

6. 变长数组(VLA)

  1. 数组的行可以在函数调用时传递,但是数组的列却只能被预置在函数内部,这是因为数组的维数必须是常量

  2. C99标准引入了变长数组,允许使用变量定义数组各维;但是变长数组必须是自动存储类的,因此必须在函数内部或作为函数参量声明,而且声明时不可以进行初始化;如:**int sum(const unsigned int size, arr[size] = {0});**是非法的

  3. 声明带有一个二维变长数组参数的函数:int sum(int rows, int cols, int arr[rows][cols]);

  4. C99标准规定可以省略函数原型中得名称;但是如果省略名称,则需要用星号来代替省略的维数;如:int sum(int, int, int arr[*][*]);

  5. 变长数组允许动态分配存储单元,可以在程序运行时指定数组的大小

7. 复合文字

  1. C99新增了复合文字,文字是非符号常量;对于数组来说,复合文字看起来像在数组的初始化列表前面加上用圆括号括起来的类型名;如:**(int [2]) {10, 20}**是一个复合文字

  2. 像初始化一个命名数组时可以省略数组大小一样,初始化一个复合文字也可以省略数组大小,编译器会自动计算元素的数目;如:**(int []) {50, 20, 90}**是一个有三个元素的复合文字

  3. 对于未命名的复合文字,必须在创建它们的同时通过某种方法来使用它们;通常有两种方法:

    a. 一种方法是使用指针保存其位置,如:int * pt1; pt1 = (int [2]){10, 20};;

    b. 另一种方法是作为实参被传递给带有类型与之匹配的形参的函数,如:

    int sum(int arr[], int n);
    ......
    int total3;
    total3 = sum((int []){4, 4, 4, 5, 5, 5}, 6);
    

第11章 字符串和字符串函数

1. 字符串表示和字符串I/O

  1. 如果字符串文字中间没有间隔或者间隔是空格符,ANSI C会将其串联起来;如:char str[50] = "Hello, and" "how are" "you" "today!";等同于char str[50] = "Hello, and how are you today!";

  2. 字符串常量属于静态存储,在程序运行过程中只存储一份

  3. 指定字符串数组大小时,一定要确保数组元素数比字符串长度至少多1,未被使用的元素将被自动初始化为0

  4. 增量运算符只能用在变量名前,不能用在常量前

  5. 数组初始化是从静态存储区把一个字符串复制给数组,而指针初始化只是复制字符串的地址

  6. 数组的元素是变量,但是数组名不是变量,不能对数组名进行自增自减运算

  7. 初始化一个指向字符串文字的指针时应使用const修饰符,因为编译器可能选择内存中的同一个单个的拷贝,来表示所有相同的字符串文字,如果不加const,可能会修改某个单元,这样会影响到所有对这个字符串的使用;用一个字符串文字来初始化一个非const的数组,则不会出现这种那个情况,因为数组从最初的字符串得到了一份拷贝

2. 字符串输入

  1. gets()读取换行符之前(不包括换行符)的所有字符,并在其后添加一个空字符\0gets()会读取换行符并将其丢弃;如果出错或遇到文件结尾(EOF),将返回一个空地址**(NULL)**

  2. fgets()如果第二个参数指定最大读入字符数为n,会读取最多n-1个字符或读完一个换行符为止;**fgets()读到换行符后会将其存入字符串而不像gets()一样丢弃;由于gets()**不检查目标数组是否能够容纳输入,存在不安全因素

3. 字符串输出

  1. **puts()**显示字符串时自动在其后添加一个换行符;**puts()**遇到空字符时会停止输出

  2. **fputs()**不会为输出自动添加换行符;

  3. **gets()丢掉输入里的换行符,但是puts()**为输出添加换行符;**fgets()存储输入的换行符,而fputs()**不为输出添加换行符

4. 自定义字符串输入/输出函数

  1. 用方括号的一个用意是提醒用户这个函数处理的时数组而不是字符串;如:const char *stringconst char string[]

5. 字符串函数

  1. **strcat()**函数并不检查第一个数组是否能够容纳第二个字符串;如果没有为第一个数组分配足够大得空间,多出来的字符溢出到相邻存储单元时就会出现异常;

  2. char * strncpy(char * s1, const char * s2, size_t n):如果源字符串的字符数少于n个,在目标字符串中以空字符填充;如果源字符串的字符数大于或等于n个,空字符将不会被复制到目标字符串,需要在目标字符串最后手动添加空字符(\0)

  3. **strlen()**函数测得的字符串长度不包含空字符

第12章 存储类、链接和内存管理

1. 存储类

  1. 作用域:一个C变量的作用域可以是代码块作用域函数原型作用域,或者文件作用域

    a. 代码块作用域:在代码块中定义的变量具有代码块作用域,从该变量被定义的地方到包含该定义的代码块的末尾该变量均可见;传统上,具有代码块作用域的变量都必须在代码块的开始处进行声明,但是C99放宽了这一规则,允许在一个代码块中任何位置声明变量

    b. 函数原型作用域:函数原型作用域从变量定义处一直到原型声明的末尾

    c. 文件作用域:一个在所有函数之外定义的变量具有文件作用域

  2. 链接:一个C变量可能具有外部链接内部链接,或空链接;具有代码块作用域或者函数原型作用域的变量具有空链接,一个具有外部链接的变量可以在多文件程序的任何地方使用,一个具有内部链接的变量可以在一个文件的任何地方使用;如:

    int giants = 5;    //文件作用域,外部链接
    static int dodgers = 3;    //文件作用域,内部链接
    
    int main(void)
    {
        int size = 10;    //代码块作用域,空链接
    }
    ...
    
  3. 存储时期:一个C变量可以有以下两种存储时期之一

    a. 静态存储时期:如果一个变量具有静态存储时期,它在程序执行期间将一直存在;具有文件作用域的变量具有静态存储时期

    b. 自动存储时期:具有代码块作用域的变量一般情况下具有自动存储时期

  4. 存储类:C使用作用域、链接和存储时期来定义5中存储类:自动、寄存器、具有代码块作用域的静态、具有外部链接的静态,以及具有内部链接的静态

    a. 自动变量:属于自动存储类的变量具有自动存储时期、代码块作用域和空链接;默认情况下,在代码块或函数的头部定义的任意变量都属于自动存储类;也可以显式地使用关键字auto(存储类说明符);除非显式地初始化自动变量,否则它不会被自动初始化

    b. 寄存器变量:具有代码块作用域、空链接以及自动存储时期;可以用存储类说明符register声明寄存器变量;声明一个变量为寄存器类变量仅是一个请求,编译器必须在用户请求与可用寄存器的个数或可用高速内存的数量之间做权衡,如果资源无法满足,很可能将寄存器变量当作一个普通的自动变量处理;可以使用register变量声明的类型是有限的

    c. 具有代码块作用域的静态变量:“静态”指变量的位置固定不动;具有静态存储时期、代码块作用域和空链接;静态变量只在编译时被初始化一次,如果不显式地对静态变量进行初始化,默认将被初始化为0;处于代码块中得静态变量声明定义语句,实际上并不是代码块的一部分,将该语句放置在代码块中只是为了告诉编译器只有该代码块可以看到该变量,该语句不是在运行时执行的语句,而是在编译阶段已经执行,分配空间并初始化;对函数参量不能使用static;如:int wontwork(static int flu);

    d. 具有外部链接的静态变量:具有文件作用域、外部链接和静态存储时期;可以在使用外部变量的函数中通过使用extern关键字来再次声明;如果变量是在别的文件中定义的,使用extern来声明该变量就是必须得;只可以用常量表达式来初始化文件作用域变量;不要用关键字extern来进行外部定义;只用它来引用一个已经存在的外部定义;一个外部变量只可进行一次初始化,而且一定是在变量被定义时进行

    e. 具有内部链接的静态变量:具有静态存储时期、文件作用域以及内部链接;普通的外部变量可以被程序的任一文件中所包含的函数使用,而具有内部链接的静态变量只可以被与它在同一个文件中得函数使用;可以在函数中使用存储类说明符extern来再次声明任何具有文件作用域的变量,这样的声明不改变链接

2. 存储类说明符

  1. C语言中有5个作为存储类说明符的关键字:autoregisterstaticextern以及typedef

  2. 不可以在一个声明中使用一个以上的存储类说明符

  3. autoregister只能用在具有代码块作用域的变量声明中

  4. 无法获取一个寄存器变量的地址

3. 存储类和函数

  1. 外部函数可被其他文件中的函数调用,静态函数只可以在定义它的文件中使用

  2. **rand()**是一个“伪随机数发生器”

4. 分配内存:malloc()和free()

  1. ANSI C标准使用了一个新类型:指向void的指针,被称为“通用指针”;将void指针值赋给其他类型的指针并不构成类型冲突;如果**malloc()**找不到所需的空间,它将返回空指针

  2. 在C中,类型指派是可选的,而在C++中必须有,通常选择都使用类型指派;如:ptd = (double *)malloc(max * sizeof(double));

  3. calloc():如,**long * newmem = (long *)calloc(100, sizeof(long));第一个参数是所需内存单元的数量,第二个参数是每个单元以字节计的大小;函数calloc()**会将块中全部位都置为0

5. ANSI C 的类型限定词

  1. C99授予类型限定词一个新属性:幂等性;即可以在一个声明中不止一次地使用同一限定词,多余的将被忽略;如:const const const int n = 6;//相当于: const int n = 6;

  2. 常量指针(const * ):一个位于 * 左边任意位置的const使得数据成为常量;指针常量(** * const**):一个位于** * 右边的const**使得指针自身为常量

  3. 在文件之间共享const数据时要小心,可以使用两个策略:

    a. 遵循外部变量的惯用规则,在一个文件中进行定义声明,在其他文件中使用关键字extern进行引用声明;

    /* file1.c --定义一些全局常量 */
    const double PI = 3.14159;
    const char * MONTHS[12] = 
        {"Jannuary", "February", "March", "April", "May", "June"
        "July", "August", "September", "October", "November", "December"};
        
    /* file2.c --使用在其他文件中定义的全局常量 */
    extern const double PI;
    extern const char * MONTHS[];
    

    b. 将常量放在一个include文件中,同时使用静态外部存储类;

    /* constant.h --定义一些全局变量 */
    static const double PI = 3.14159;
    static const char * MONTHS[12] = 
        {"Jannuary", "February", "March", "April", "May", "June"
        "July", "August", "September", "October", "November", "December"};
    
    /* file1.c --使用在其他文件中定义的全局变量 */
    #include  "constant.h"
    
    /* file2.c -- 使用在其他文件中定义的全局变量 */
    #include  "cosntant.h"
    

    如果不使用关键字static,在文件file1.c和file2.c中包含constant.h将导致每个文件都有同一标识符的定义声明,ANSI标准不支持这样做;通过使每个标识符成为静态外部的,实际上给了每个文件一个独立的数据拷贝,每个文件都只能看到它自己的拷贝,无法使用该数据来与另一个文件通信;使用头文件的好处是不必考虑在一个文件中进行定义声明,在下一个文件中进行引用声明,只需包含相关头文件即可,全部文件都包含同一个头文件,缺点在于复制了数据,当头文件包含较大的常量数据时可能存在问题

  4. 限定词volatile告诉编译器该变量除了可被程序改变以外还可被其他代理改变

  5. 关键字restrict通过允许编译器优化某几种代码增强了计算支持;只可用于指针,并表明指针是访问一个数据对象的惟一且初始的方式

  6. 函数memcpy()要求两个位置之间不重叠,但memmove()没有这个要求;如:void * memcpy(void * restrict s1, const void * restrict s2, size_t n);

6. 总结

  1. 用于存储程序数据的内存可用存储时期、作用域和链接来表征。

    a. 存储时期可以是静态的、自动的或者分配的;如果是静态的,内存在程序开始执行时被分配,并在程序运行时一直存在;如果是自动的,变量所用内存在程序执行到该变量定义所在代码块时开始分配,在退出代码块时释放;如果是分配的内存,内存通过malloc()(或其他相关函数)分配,通过调用**free()**释放

    b. 作用域决定了哪一部分程序可以访问某个数据。在所有函数之外定义的变量具有文件作用域,并对该变量声明之后定义的全部函数可见;在代码块内定义或者作为函数参量定义的变量具有代码块作用域,并只在该代码块及其子代码块中可见

    c. 链接描述了程序的某个单元定义的变量可被链接到其他哪些地方。具有代码块作用域的变量作为局部变量,具有空链接;具有文件作用域的变量可有内部链接或外部链接;内部链接意味着变量只可在包含变量定义的文件内部使用;外部链接意味着变量也可在其他文件中使用

  2. 类型限定词说明符有constvolatilerestrict