在阅读完《C和指针》后得出的重要心得,在编程遇到困难时,或许将起到醍醐灌顶的作用。也将更好的帮助理解C语言的执行过程,带来对C语言新的感悟。
1、gets()函数从输入缓冲区(键盘输入)读取一个字符串存储到指针变量str所指向的内存空间。
2、“Hello”字符串在内存中占6个字节,最后一个是“NUL”。但字符串长度并不包含“NUL”
3、“NUL”是ASCII字符集中‘\0’字符的名字。而“NULL”是值为0的指针。‘\0’位于字符串末端,表示字符串结束。“NULL”则一般用于空指针的初始化。
4、函数声明中对数组,可只指出数组类型,而长度不指定,无论调用函数传来的数组长度多少,都照收不误。(实际传来的也是数组首元素地址)
5、数组名作为实参时,传递的是指向数组起始位置的指针(地址)
6、初始化时:int ch;可未赋初值,但初值会不可预计
7、scanf(“%d %d”,&columns[num1], &columns[num2])中每个%d对应一个输入的整形,每一个&colums[ ]表示这个整形输入后存储的地址。%s对应一个字符串。
8、整形值可容纳字符。
9、EOF:end of file(在stdio.h)中定义,用于提示文件结尾或结束。EOF被定义为一个整数常量,标准C库中,通常为-1。
10、const作用于函数参数(int const columns[ ])表示该参数不能被修改。编译器也会验证是否违背该意图。
11、保证数组不越界,否则会报错。
12、声明(函数原型等)放在头文件中,只需编写一次,维护和修改更容易,同时避免了多份拷贝中写法不一致
13、程序编译过程:
(1)预处理器处理:a、#define:用实际值代替符号 b、读入#include包含的文件内容。(2)解析:判断每行语句意思(大部分编译和警告产生的地方)。
(3)生成目标代码(机器指令的初步形式)。
14、程序翻译阶段(源代码转化为可执行的机器指令):
(1)程序每个源文件编译为目标代码
(2)目标文件再由链接器捆绑,形成单一完成的可执行文件。
(3)链接器同时检测并引入程序中用到的标准C函数库中被该程序用到的函数,并搜索个人函数库,将其中使用的函数页链接到程序中。
15、可只单独编译一个C文件,和先存的可执行文件链接在一起(C编译器称为cc):
cc main.o lookup.o sort.c 节省编译的时间
16、程序执行时必须先载入内存中
17、程序由声明和函数组成:
(1)函数:要执行的工作
(2)声明:描述函数及其参数
18、标识符(变量、函数、类型等的名字),由大小写字母、数字和下划线组成,不能以数字开头。
19、关键字由系统保留,不能用作标识符。
数据
20、字面值:C语言中的固定值。如整数常量:十进制‘42’、八进制以0开头‘052’、十六进制以0x开头‘0x2A’;浮点数:“3.14”;字符:‘A’等
21、变量三个属性:作用域、链接属性、存储类型。(1)作用域:指明变量在程序中的作用范围。(2)链接属性:变量是否可在不同文件共享。(3)变量的生命周期和存储位置。如动态存储还是静态存储,存储于寄存器还是内存中等。
22、指针:变量值存储于计算机内存中,每个变量都有一个唯一确定的内存地址,该地址即指针。
23、指针变量:值为另一个变量的内存地址
24、字符串表达式值为存储地址(字符串存储的首地址,即指针),故无法赋值给字符串数组(数组中每个元素是单个字符值)。
25、说明符(一个或多个)用于描述被声明标识符的类型,包括有无符号或长度+类型
26、定义时*(间接访问符号)表示该变量类型是指针变量,并仅表示与*相近的变量。如:int * b,c,d;只有b是指向整形的指针变量,c、d为整形
27、char *message = “Hello world!”;相当于两条语句(1)char *message;(2)message = “ Hello world! ”;
28、函数若不显式声明返回值类型,则默认返回整形。(类似情况,都会默认为整形)
29、typedef可将各种数据类型定义为新名字,int可变为u8
30、const关键字可用于声明常量:int const a=15;a的值固定不能修改
31、int const *pci则为一个指向整形常量的指针,即可更改指向的常量,但无法改变指向的常量的值。
32、int const * const pci则为一个指向整形的常量指针。指针是常量,无法修改,但可修改它所指向的整形的值
33、代码块作用域:代码块中内层标识符的作用域优先。
34、文件作用域:任何代码块之外的标识符具有文件作用域,从该标识符声明处至它所在源文件结尾。
35、链接属性共三种—external(外部)、internal(内部)、none(无):
(1)external链接属性的标识符无论声明多少次、位于几个源文件都表示一个实体。
(2)internal链接属性的标识符同一源文件的所有声明都为一个实体。
(3)none即没有链接属性的标识符(正常的定义),每个声明都当作不同实体,即同名的变量,但是不同的实体。
36、extern修改标识符链接属性为外部,static修改为内部(标识符处于代码块外)
37、变量的缺省类型即对变量类型的自动推断(未定义变量类型的情况),一般默认为’int’。 缺省的存储类型则取决于变量定义的位置,若在代码块内部则为‘auto’自动变量,存储于堆栈中;代码块外定义的则为‘static’,存储于静态内存中。
38、静态、自动、寄存器变量:
(1)静态变量(static)存储于静态内存中,程序执行过程中始终存在。
(2)自动变量(auto)存储于堆栈中,程序执行到该代码块时,才被创建,执行流离开后自行销毁。
(3)寄存器变量存储于机器的硬件寄存器中,相比于内存中的变量访问效率更高。
39、堆栈实质上也是一块指定的内存,只是数据存储的方式,和存储在此内存块中的时间有专门的规定。
40、static的作用:
(1)代码块外:修改链接属性为internal,但存储类型和作用域不变。
(2)代码块内:修改为静态变量,但链接属性和作用用户不变。
41、静态变量在程序运行过程中,始终存在,对值的更改也一直为同一变量。若多次调用同一函数,动态变量(在该函数中定义的)实际上是不同的变量,不会记忆上一次该变量的值,因为在上次函数结束调用后,该变量就已被删除一次。
42、属于文件作用域(代码块外定义)的声明默认情况下为external链接属性,如int a = 5;但还是建议加上extern:extern int a = 5;
43、无符号变量和相同长度的有符号变量容量值是一样的。如-64~64和0~128
语句:
44、C语言语句的结尾以分号’ ; ’表示
45、C语言表达式都会有一个值,值“零“为假,“非零”为真。赋值表达式除了将值赋给变量外,还作为表达式的值返回。
如‘x = 10’,将‘10’赋值给x,同时返回‘10’作为表达式的值
46、else子句从属于最靠近它的不完整if语句
47、break和continue只对最内层的循环起作用。break直接结束整个循环,continue终止当前的当次循环
48、switch中case标签值一但和express表达式的匹配,则从当前语句起,到switch语句底层之间的所有语句都被执行。
49、若要划分switch语句的不同部分,需要使用break。
50、当switch表达式的值不匹配所有case标签值时,则执行default后面的语句。default语句只能出现一次,但可出现于任何位置。
操作符:
51、算术操作符(+ - * / %):除了%只能作用于整数计算,其他操作符作用于整数、浮点数都可以。
52、移位操作符(《 》):(1)左移位:值最左边的几位丢弃,右边多出来的空位则由0补齐。(2)右移位:a、逻辑移位:左边移入用0填充 b、算术移位:左边移入取决符号位(最左位)与其一致
53、无符号值都是逻辑移位
54、位操作符(AND、OR和XOR):操作数要求为整形。异或XOR:位不同“1”,位同“0”。按位进行操作。
55、赋值(=):结合性(求值的顺序)从右到左。左值需为可以存储变量值(结果值)的地址,右值为要赋值的变量值。左值意味着一个位置,右值意味着一个值。
56、单目操作符(&、*、sizeof):(1)&产生它操作数的地址。(2)*间接访问操作符,与指针一同使用时,访问指针所指向的值。(3)sizeof返回操作数的类型长度,数组名时返回数组的长度。
57、++、--得到的是变量值的拷贝,而非变量本身,不能放置在赋值符号左侧(无法对一个值赋值)。
58、++、--优先级低于*
59、关系操作符(>、>=、<、<=、!=、==):产生的结果是整形,而不是布尔值。操作符两端的操作数得出的结果(1)符合:1(2)不符合:0
60、“=”赋值!“==”比较!
61、C语言并不具备显式的布尔类型,所以用整形替代。零为假,非零值为真
62、char型变量长度只有8位,当两个字符变量执行加法运算得到的结果超过8位时,结果将被截短。
63、操作符的操作数要转换为同一类型。
64、左值表示一个位置,右值为一个值。每个左值表达式是个右值,但右值无法作为左值。因为地址也可以作为值赋给指针变量,但无法把一个值赋给一个值。
(操作符的优先级表格在最后面)
指针
65、计算机中内存由数亿万个的位(bit)组成,每个位可容纳值0或1。但单个位作用有限,通常多个位组成字节作为一个单位(8个bit)
66、内存的最小寻址单元是字节(byte),而非位,内存被组织成一个连续的字节序列,位运算符对位的操作也是基于字节的地址而非位。
67、每个字节都有一个专门的地址,包含8个位,可以存储无符号值0~255或有符号值-128~127
68、多个字节(2个或4个,取决于计算机的位数)可合为一个字存储更大的值,比如以字位单位存储整数。通常地址为它的最左侧字节的地址
69、内存中一个字的大小是4个字节时,容纳的无符号整数范围变为0~4294967295(232-1),有符号对应增加,则可见内存中5个字内容如下:
但我们通常通过变量名而非地址来访问内存位置:
70、对变量存储值的解释,取决于变量的类型的声明(一定程度上也解释了类型转换的原理),相同的存储值在不同类型的解释下,得出的值不同:
如01100111011011000110111101100010:
71、该例一个单一值可解释为5种不同类型。
72、间接访问(也称解引用指针):访问指针中地址存储的值,间接访问操作符*。
73、指针必须要初始化,否则该指针指向位置无法预知,将导致
(1)初始值为非法地址,报错。(试图改变一个未分配给内存位置的值)
(2)初始值为合法地址,改变此地址存储的值(无意修改)
74、NULL指针:特殊的指针变量,表示不指向任何东西。
75、当指针变量初值不知指向何处时,可初始化为NULL。测试一个指针变量是否为NULL,可直接和零值比较。
76、不能将一个值赋给一个值,也不能赋给一个指针变量(存储的是地址,而非数值),只能赋值给内存中可以存储值的一个变量(一个位置,该位置存储的是数值)
77、指向指针的指针,该指针的值是另一个指针的地址。如:int a = 12; int *b = &a; int **c = &b; *操作符从右向左的结合性,则相当于int *(*c),表示该变量的类型是一个指向整形的指针(int *c)的指针(外层的*),值为b指针的地址。
78、*在定义时说明该变量的类型,使用时,则访问存储地址指向的值。
79、cp为一个指针。*(cp+1)为指针+1:指针指向的内存地址增加一个单位,单位大小取决于指针指向的数据类型。相当于若为整形则地址加1*4。(一个整形4个字节)
80、*cp++:(1)++产生cp的一份拷贝 (2)++增加cp的值 (3)对cp的拷贝产生间接访问操作。
81、++优先级高于*
82、++*cp:两个操作符结合性都是从右向左,所以(1)执行*间接访问操作,(2)再执行++,使cp指向的位置的值+1 (3)该表达式结果为增值后该值的拷贝(++在前)
83、只有操作符与变量相邻才看优先级,操作符彼此相邻看结合性。
84、指向指针数组的指针:char **strings; while(**strings != ‘\0’); 第一个间接访问指针数组中的当前地址,第二个间接访问当前地址中字符串的当前字符(地址中的值)。
85、指针间的算术运算,需要两个指针指向同一数组中的元素,相减为两指针在内存中的距离(数组元素的长度,运算时会自动除以类型长度)
86、指针即是地址,地址即是指针,当使用间接访问时,可以想到这点。
函数
87、编译器调用函数时,需要向编译器提供如何调用这个函数的信息,通常由两种方式:(1)源文件前面已出现了函数定义。
(2)函数原型(函数声明的一种形式):总结函数定义起始部分的声明,包括函数名称、返回值类型、参数列表。
88、最好令函数原型处于一个单独的文件中,当其他源文件需要该函数原型时,就使用#include指令包含该文件。
89、函数只获得参数值的拷贝,参数都为“传值调用”,但若是传的是指针,则可能改变它指向地址中的值
90、函数被调用时,变量的空间创建于运行堆栈上;若在执行完前,有新函数调用,原先变量仍保留在堆栈上,但无法访问。
91、递归函数所需特征:(1)存在限制条件,符合则不再继续(2)每次递归后,越来越接近这个条件
数组
92、标量:单一的值(数组中的每个元素都是)
93、数组名是个指针常量,而非变量,类型取决于数组类型
94、复制数组,只能挨个复制每个元素。
95、数组名对应着每个数组的首元素地址,该数组名的值是常量!不能被修改
96、对数组的下标引用和间接访问完全相同
97、下标引用可以作用于任意指针,而不仅是数组名
98、数组作为函数参数时,无需写明元素数目,函数并不为数组参数分配内存空间,形参只是一个指针(数组第一个元素的地址),指向已经在其他地方分配好的内存。
99、多维数组:相当于第一维元素的每个元素中都包含了一个数组,其中有多个元素,后各维以此类推。
每个元素存储了3个元素,相当于该元素也是个数组
100、多维数组的数组名为指向数组的指针,即指向第1维第1个元素的指针(第一个元素包含了多个元素,相当于一个数组)
101、多维数组的数组名+1,相当于指向了下一个子数组
如:int matrix[3][10]; matrix+1
102、数组指针(指向数组的指针):int (*p)[10] = matrix; 可知为指向整形数组的指针
(1)(*p)指出该变量为指针变量
(2)[10]指出该指针指向个数组,该数组拥有10个元素
(3)int指出指向的数组是整形
103、多维数组可以不指定第一维长度,但必须指定第2维及以后各维的长度。(为确保内存正确分配存储空间)
104、对数组初始化时花括号作用:(1)利于显示数组结构(2)每个子列表都可以省略末尾几个初始值
105、指针数组(存放指针的数组):int *api[10];(1)下标引用[ ]优先级高于*,首先执行下标引用,创建数组。(2)取得数组元素后,执行间接访问操作,使每个数组元素为指向整形的指针。
106、数组名的例外:
(1)sizeof:返回整个数组占用的字节
(2)单目操作符&:返回一个指向数组的指针,而非首元素
107、多维数组实质是一维数组的一种特性,它的每个元素也是一个数组。多维数组名为指向第1个元素(数组)的指针
练习:
假定ints数组在内存中起始位置是100,整形值和指针长度都是4个字节
int ints[20] = {
10, 20,30,40,50,60,70,80,90,100,
110,120,130,140,150,160,170,180,190,200 };
int *ip = ints + 3
表达式 | 值 | 表达式 | 值 |
ints | 100 | ip | 112 |
ints[4] | 50 | ip[4] | 80 |
ints+4 | 116 | ip+4 | 128 |
*ints+4 | 14 | *ip+4 | 44 |
*(ints+4) | 50 | *(ip+4) | 80 |
ints[-2] | 非法 | ip[-2] | 20 |
&ints | 100 | &ip | 未知 |
116 | &ip[4] | 128 | |
&ints+4 | 116 | &ip+4 | 未知 |
&ints[-2] | 非法 | &ip[-2] | 104 |
ints[-2]:该数组并没有这个元素
&ip:指针类型自身的地址未指定
&ints+4:&优先级高于+,地址+需要乘以类型所占的字节数
&ip[-2] :[ ]优先级高于&
字符串、字符和字节
108、字符串以字符串常量的形式出现或存储于字符数组中
结构和联合
109、聚合类型共有两种:(1)数组(2)结构:无法通过下标访问,因为成员类型可能不同,长度也不同
110、结构声明:struct tag { member-list } variable-list ;
(1)struct:为结构 (2)tag(结构标签):结构名(作为类型名声明变量)(3)member-list:结构内容(成员列表)(4)variable-list使用该类型结构的变量
111、创建结构时,可用结构名创建相同类型的结构变量,省去在每次声明中重复成员的列表。
112、对变量的创建分为两个步骤:(1)声明:包括变量的名称和类型,告知编译器有个变量将被使用。int x ;(2)定义:为变量分配内存,并可选为其的分配初始值,可与声明同时完成,也可稍后进行。int x = 42。
113、点操作符(.):(1)左操作数:结构变量的名字(2)右操作数:需要访问的成员的名字(3)结合性为从左到右
114、点操作符(.)优先级高于间接访问*
115、当定义指向结构的指针时,所指向的是整个结构。
116、px是一个指向结构的指针。(1)*px+1是非法的,*p结果是个结构,C语言未定义一个结构(值)与整型值之间的假发运算。(2)*(px+1)也是非法的,结构并不像数组元素,元素的类型相同,它的字段之间的便宜是不确定的,并不能像数组一样用*(px+1)简单的访问下一个结构。
117、结构的第一个成员的地址和结构的地址是相同的,所以px同时指向整个结构和第一个成员,但*px为对整个结构的访问,而非对第1个成员的访问,对成员的访问要用到->和 . 操作符。
118、当px是指针时,通过->对结构体成员进行间接访问px->a;px当为结构名时,用(.)点操作对成员进行访问px.a。
119、将整个结构作为函数参数传递效率低,要拷贝整个结构占用空间大,往往只需要其中的一个成员,可用传递该结构的指针。
120、const可防止函数通过指针修改结构成员的值,当不希望被修改时。
121、联合:和结构类似,但它的所有成员引用的是内存中的相同位置。即不同时刻将不同的东西存放于同一位置。不同的成员引用的位相同,区别在于不同的成员类型对位的解释。(前面提到的对存储空间中值的解释)
122、联合的长度就算它最长成员的长度。
123、在联合中可用存储指向不同成员的指针,而非成员本身,可用大量节约空间(指针的长度都是相同的,而且较短)。
124、联合变量可被初始化,但初始值需要是联合第1个成员的类型,该初始值也需位于花括号内。
125、结构中的成员可以包含指向该结构自身的指针。
练习:
struct NODE {
int a;
struct NODE *b;
struct NODE *a;
}
struct NODE nodes[5]={
{ 5, nodes + 3, NULL },
{ 15, nodes + 4, nodes + 3 },
{ 22, NULL, nodes + 4 },
{ 12, nodes + 1, nodes },
{ 18, nodes + 2, nodes + 1},
};
struct NODE *np = nodes + 2
struct NODE **npp = &nodes[1].b
假定nodes数组在内存中起始位置为200,并且该机器上整数和指针长度都为4个字节。
表达式 | 值 | 表达式 | 值 |
nodes | 200 | &nodes[3].c->a | 200 |
nodes.a | 非法 | &nodes->a | 200 |
nodes[3].a | 12 | np | 224 |
nodes[3].c | 200 | np->a | 22 |
nodes[3].c->a | 5 | np->c->c->a | 15 |
*nodes | {5,node+3,NULL} | npp | 216 |
*nodes.a | 非法 | npp->a | 非法 |
(*nodes).a | 5 | *npp | 248 |
nodes->a | 5 | **npp | {18,node+2,node+1} |
nodes[3].b->b | 248 | *npp->a | 非法 |
*nodes[3].b->b | {18,nodes+12,nodes+1} | (*npp)->a | 18 |
&nodes | 200 | &np | 未知 |
&nodes[3].a | 236 | &np->a | 224 |
&nodes[3].c | 244 | &np->c->c->a | 212 |
nodes.a:nodes是个数组,而非单一结构。nodes是结构数组首元素的地址(指针),无法使用点操作符,只能使用->
*nodes:对结构的指针进行间接访问,得到结构。
*nodes.a:.操作符优先级高于间接访问*,首先得到a的值5,无法继续对值进行间接访问操作
(*nodes).a:对结构成员进行访问
*nodes[3].b->b:间接访问优先级低于点操作符.和->。
&nodes[3].c->a:&优先级低于点操作符.和->
npp:&取得是成员b的地址(该值的地址)
*npp:对b地址进行间接访问,得到nodes + 4
&np:指针变量自身的地址是未知的
数组中的值nodes,可直接视作200(地址的值)。
动态内存分配
126、malloc:申请一块连续内存,参数为要分配的内存字节数,若可以,则返回该内存块的起始位置。
127、malloc返回值是一个void *的指针,可以转化为其他任何类型的指针。
128、calloc:calloc和malloc的区别在于,会对分配的内存初始化,初始化为0。参数为要分配的元素个数和每个元素的长度
129、realloc:扩大或缩小已分配的内存
130、free:释放一块内存,需要是malloc、calloc或realloc返回的值(分配内存块的起始位置),要么是NULL。
131、输入缓存区:从标准输入设备(如键盘)读取的字符数据临时存储区域。
结构和指针
132、单链表起始位置,用一个根指针指向第1个节点。
133、链表尾部节点指向NULL指针
134、当需要在头部插入节点时,需要用一个指向指针的指针(指向根节点root的指针的指针)
135、双向链接中的节点包含两个指针,一个指向前一个节点,一个指向后一个节点。根节点的指针一个指向链表的第一个节点,一个指向最后一个。
136、root根节点并不算双链表内的节点。
137、链表是动态分配的,可增长至几百或几千个节点,可能分布于内存的各个地方
138、链表的节点就是结构,由数值和一个或多个指针组成
高级指针
139、指针也是变量,只是存储的是地址,当用变量的方法调用它时,输出的是它存储的地址(变量的值)
140、指向指针的指针:两次间接访问得到i的值(1)*访问ppi存储地址pi中的值(i的地址)(2)*访问i的值
141、函数指针(每个函数都位于内存中的某个位置):int (*f)( ); 指向函数返回值为整形,名字为f。
142、返回值为整形指针的函数指针:int *(*f)( );
143、指针数组:[ ]下标优先级高于*。int *f[ ];(1)先数组(2)数组元素为指针。
144、int f( )[ ]:非法。f是个函数,函数只能返回标量值,不能放回数组。
145、int f[ ]( ):非法。数组元素长度需相同,但不同函数往往长度不同。
146、int (*f[ ])( ):合法。(1)数组元素为某种类型的指针(2)数组元素类型为函数指针(3)所指向的函数返回值为整形
147、int *(*f[ ])( ):函数返回值为整形指针
148、在函数优先级之后的符号为函数返回值的类型,即从确定为函数起。
149、回调函数:作为另一个函数的参数,传递给另一个函数。定义的类型一般为void*型,以便能作用于任何类型的值
练习:
类型 | 表达式 | 含义 |
int | abc() | 返回值为int的函数 |
int | abc[3] | int型数组 |
int | **abc() | 返回值为“int型指针的指针”的函数 |
int | (*abc)() | 返回值为int的函数指针 |
int | (*abc)[6] | 指向“int型数组”的指针 |
int | *abc() | 返回值为“int型指针”的函数 |
int | **(*abc[6])() | 指向“返回值为int型指针的指针的函数”的指针的数组 |
int | **abc[6] | int型指针的指针数组 |
int | *(*abc)[6] | 指向“int型指针数组”的指针 |
int | *(*abc())() | 返回值为“返回值为int型指针的函数指针”的函数 |
int | (**(*abc)())() | 返回值为“返回值为int的函数指针的指针”的函数指针 |
int | (*(*abc)())[6] | 返回值为“指向int型数组的指针”的函数指针 |
int | *(*(*(*abc)())[6])() | 返回值为“指向’返回值为int型指针的函数指针’的数组的指针“的函数指针 |
**abc():()优先级高于*,先是函数,函数之后的符号都为返回值的类型。
(*abc)():先是指针,该指针类型是函数。看哪个先成立
*(*abc)[6]:先是指针,指向的是个数组,该数组类型是int型指针。
*(*abc())():函数指针即,先是指针,后接着是函数
(**(*abc)())():指针后紧接的是函数,那么则为函数指针
(*(*abc)())[6]:数组前跟指针是指向数组的指针,数组后跟指针是元素为指针的指针数组
通过观察优先级,看哪个先成立,再一步步推导就可。数组、指针或函数。
预处理器
150、源码编译前的文本性质操作—预处理:(1)删除注释(2)插入#include包含的内容(3)定义和替换#define定义的符号(4)条件编译:部分代码是否编译、
151、#define为数值命名一个符号
152、#define基本语法:#define 标识符 替换文本
153、宏由define定义:将参数替换到文本中(即#define中有变量)
154、宏定义的尾部无需加上分号(当宏定义单独出现时)
155、宏被调用时,文本被参数原样!替代:
如:#define SQUARE(x) x * x
printf(“ %d\n, SQUARE(a+1) ”);-->printf(“%d\n”,a+1*a+1)
文本为x,参数为a+1
156、为避免出现问题,宏中的变量和表达式最好都加上括号
157、宏与类型无关,即可以用于任何类型的数据
158、宏定义不用分号结尾(定义的时候),分号于调用该宏的语句中出现
159、宏名全大写
160、条件编译:选择代码的一部分时正常编译还是忽略。基本结构如下:
#if constant-expression(常量表达式,由预处理器求值)
statements
#endif
常量表达式值为真则statements部分正常编译,否则预处理删除
161、常量表达式:(1)字面值常量(2)#define定义的符号
162、条件编译还可用#elif编译时选择不同的代码部分。#else只有前面表达式都为假才编译。
163、文件包含#include:预处理器删除该指令,用包含文件的内容取代。
164、头文件被包含时,文件内所有内容都要被编译
165、两种不同类型的#include文件包含:(1)函数库文件(2)本地文件
166、#include <>:函数库头文件用<>,标准库文件要以.h后缀结尾
167、#include “filename“:本地文件包含。本地文件查找策略:现在源文件当前目录查找,再在标准位置(同查找函数库头文件一样)查找
168、#error用以报告错误
输入/输出函数
169、I/O:对程序而言,所有I/O操作只是简单的从程序移进或移出字节。该字节流便称为流。
170、绝大多数流是完全缓冲的,意味着“读取“和”写入“实际上是从一块称为缓冲区的内存区域来回复制数据。
171、用于输出流的缓冲区只有它被写满时才会被刷新(flush,物理写入)到设备或文件中,这样效率更高。输入类似,当它为空时,会从读取一块较大的输入。
172、为避免输入和输出时缓冲混淆,通常在输入的同时刷新输出。
173、printf可能不会立即显示,当用printf查找错误时,可能用于确认错误的位置有误,可以使用fflush迫使缓冲区的数据立即写入。
174、FILE结构:一个数据结构,用于访问一个流,每个流都有一个FILE与之关联。系统运行时至少提供三个流:
(1)标准输入(stdin),通常为键盘设备
(2)标准输出(stdout),通常为终端或屏幕。
(3)标准错误(stderr),错误信息写入的地方。
175、文件I/O:
(1)程序需要为每个处于活动状态的文件声明一个指针变量,其类型为FILE*。该指针指向这个FILE结构,当它处于活动状态时由流使用。
(2)打开一个流:fopen函数。关闭一个流:fclose函数
(3)标准流的I/O不需要打开或关闭
176、I/O以三种基本形式处理数据:(1)单个字符(2)文本行(3)二进制数据。每种形式都有一组特定的函数对其进行处理。
数据类型 | 输入 | 输出 | 描述 |
字符 | getchar | putchar | 读取(写入)单个字符 |
文本行 | gets scanf | puts printf | 文本行末未格式化的输入(输出) 格式化的输入(输出) |
二进制数据 | fread | fwrite | 读取(写入)二进制数据 |
这些函数只用以下任务:
(1)只用于标准输入stdin或标准输出stdout
(2)作为参数的流使用
(3)使用内存中的字符串,而不是流
需要一个流参数的函数将接收stdin或stdout作为它的参数。
177、scanf:将输入的值存储至指针参数指向的内存位置(要加上&的原因)
标准函数库
178、函数(rand、srand)产生的随机数并非真正的随机数,而是由计算得到的,也称伪随机数。
179、srand函数能够指定一个随机数产生器的种子,每次可产生相同的随机数序列
180、clock函数可用于计算程序中代码的运行时间
181、goto函数可使程序跳转到指定位置,但一般不用,不利于对程序的理解。
182、perror函数用以报告错误,打印出用于解释errno错误代码的信息。
经典抽象数据类型(ADT)
183、ADT的内存分配方式:(1)静态数组:结构固定(2)动态分配的数组:运行时才决定数组长度(3)动态分配的链式结构:数量无限制,但占内存
184、堆栈:后进先出。基本操作有push和pop;push将一个新值压入堆栈顶部。pop则是将堆栈顶部的值移除并返回这个值。有的堆栈提供top,只返回顶部元素的值,但不移除。
185、对堆栈需要两个函数来检查堆栈是否为空和堆栈是否已满。
186、堆栈的创建(1)静态数组实现,长度固定(2)动态数组实现,堆栈长度在创建堆栈的函数被调用时给出(3)链式堆栈,只需知起始位置。
187、队列:先进先出。
(1)需要两个指针,一个指向队头front,一个指向队尾rear。
(2)使用循环数组。
(3)当留有一个元素记录队列为空或满时。队列为空时rear值比front小1:(rear+1)% QUEUE_SIZE == front;队列满时:(rear+2)% QUEUE_SIZE = front
188、二叉树:每个节点至多两个孩子。每个节点的值比它左子树所有节点都要大,比右子树所有节点都要小。四种遍历方式:
(1)前序:首先检查节点的值,然后遍历左子树和右子树。
(2)中序:首先遍历左子树,然后检查当前节点的值,最后遍历右子树
(3)后序:首先遍历左右子树,再检查节点的值
(4)层次:首先处理根节点,接着是孩子,再是孙子。
189、当使用数组存储二叉树,其规则为按照下标寻找:
(1)节点N的双亲是节点N/2
(2)节点N的左孩子是节点2N
(3)节点N的右孩子是节点2N+1
若将1,2,3,4,5,6,7按此顺序插入,将存储在数组中的1,2,4,8,16,32和64的位置。
空间利用不充分,存在大量浪费。最好采用链式二叉树。
190、链式二叉树节点用一个结构来容纳值和两个指针。数组由一个指向根节点的指针代替,初值为NULL,表示此时为一个空树。
191、为防树丢失,可由函数向用户提供根指针,以防用户自行修改根指针。
运行时环境(汇编)
192、编译器会在声明前加上下划线_,以免与各个库函数使用的名字冲突。
193、函数分为三部分:
(1)函数序:执行函数启动需要的一些工作,例如为局部变量保留堆栈中的内存
(2)函数跋:在函数返回之前清理堆栈。
(3)函数体:执行有用工作的地方。
194、寄存器a0~a7:地址。d0~d7:数据。d0和d1用于函数返回值,不能存储寄存器变量
195、堆栈由高向低地址方向生长
196、函数参数以相反次序压入堆栈,函数的第1个参数始终位于堆栈顶部。(当实际传入的参数和期望参数数量不同时,可以防止错误)保证函数可访问到它想要的参数。
遇到的坑
197、C/C++工程文件的路径不要包含任何的中文,否则可能编译错误。
198、移位操作符得到的是一个值,当使用时,需要赋给左值。如i=i>>1;不能在for循环中直接for(;;i>>1),这样子得到的只是一个值。
199、当使用gets从键盘输入一个字符串时,只需要给gets一个字符串指针就行(地址),如:char *string;作为参数gets(string);
200、for ( exprssion1 ; expression2 ; expression3 )
初始化 条件部分(判断中止循环) 调整部分(循环体后,条件部分前执行)
201、当判断语句中表示范围需要两次比较时,要用 && 。不能写为if('0' < *string < '9'),当左边第一个比较完,为真时值为1,为假时值为0。始终<'9'。该表达式无意义,也不是我们想要的意思。应写为if(*string > '0' && *string < '9'),最好变量都习惯写在左边。
202、printf需要通过格式说明符%d,获知该如何解释数据,不能省略。
203、使用realloc重新分配内存后,会有新内存块的地址,就不要再使用旧内存块的地址了(旧内存块可能已经被释放)
204、free只能用于释放malloc等动态分配的内存,不能用于释放数组中元素的内存。因为数组在栈上静态分配的,它们的内存会在程序的作用域结束时自动释放。
简单点说,free无法作用于栈,只能作用于堆上动态分配的内存。
205、栈和堆是两种不同的内存分配区域,局部变量通常在栈上分配,而全局变量和通过动态内存分配函数(如malloc)分配的变量在堆上分配。
206、printf中用双引号“ ”,字符用单引号' ',字符串用双引号“ ”。
207、若要求输入行至多80个字符,则使用gets函数,缓冲区长度至少81个字节,以保存80个字符和一个结尾的NUL字节。
208、gets函数在读取失败时,会返回NULL指针。
操作符的优先级
操作符 | 描述 |
( ) | 聚组 |
( ) | 函数调用 |
[ ] | 下标引用 |
. | 访问结构成员 |
-> | 访问结构指针成员 |
++ | 后缀自增 |
-- | 后缀自减 |
! | 逻辑反 |
~ | 按位取反 |
+ | 单目,表示正值 |
- | 单目,表示负值 |
++ | 前缀自增 |
-- | 前缀自减 |
* | 间接访问 |
& | 取地址 |
sizeof | 取其长度,以字节表示 |
(类型) | 类型转换 |
* | 乘法 |
/ | 除法 |
% | 整数求余 |
+ | 加法 |
- | 减法 |
<< | 左移位 |
>> | 右移位 |
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
& | 位与 |
^ | 位异或 |
| | 位或 |
&& | 逻辑与 |
|| | 逻辑或 |
?: | 条件操作符 |
= | 赋值 |
+= | 以…加 |
-= | 以…减 |
*= | 以…乘 |
/= | 以…除 |
%= | 以…取模 |
<<= | 以…左移 |
>>= | 以…右移 |
&= | 以…与 |
^= | 以…异或 |
|= | 以…或 |
, | 逗号 |