第七章 基本类型
1、整数类型(7.1)
有符号整数如果为正数或零,那么最左边的位(符号位)为0;如果是负数,则符号位为1.
默认情况下,C语言中的整型变量都是有符号的。
十进制常量包含0-9的数字,但不能以零开头;八进制常量只包含0-7,必须要以零开头;十六进制常量包含0-9数字和a-f字母,并且总以0x开头。
为了强制编译器把常量作为长整数来处理,需在后面加字母L(或l),例如:15L,0x7fffL.
为了指明是无符号常量,可以在常量后面加字母U(或u),例如:15U,0x7fffU.
有符号整数溢出,程序行为是未定义的;无符号整数溢出,结果是有定义的:一般是0。
2、浮点类型(7.2)
C语言提供三种浮点类型:float(单精度浮点数),double(双精度浮点数),long double(扩展精度浮点数)。float和double对于大部分程序都够用。
float精度为6个数字,double精度为15个数字。浮点常量必须包含小数点或指数,例如:57.0,5.7e+1。
3、字符类型(7.3)
在C中,字符和整数之间的关联非常强,字符常量事实上是int类型而不是char类型。
有符号(signed)字符取值范围:-128-127;无符号(unsigned)字符取值范围:0-255。
可移植性技巧:不要假设char类型默认为signed或unsigned。如果有区别,用signed char 或unsigned char代替char.。
转义序列共有两种:字符转义序列和数字转义序列。字符转义序列,例如:\a,\n,\t等。数字转义序列,例如:\x1b,\033等。
字符处理函数:小写字符转换成大写字母,ch = toupper(ch),需要包含#include
用scanf和printf函数也能读/写字符:
do { scanf("%c",&ch); } while (ch != '\n')
getchar()返回的是一个int类型值,而不是char类型值。getchar()的惯用法。第一个用于检测是不是换行符,第二个用于检测空格。
while (getchar() != '\n') ; while ((ch = getchar()) == ' ') ;
4、类型转换(7.4)
当算术表达式或逻辑表达式中操作数的类型不相同时;当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时;当函数调用中的实参类型与其对应的形参类型不匹配时;当return语句中表达式的类型和函数返回值的类型不匹配时。
任一操作数的类型是浮点类型,需要将float提升为double;两个操作数类型都不是浮点类型,需要将int提升为unsigned int或long int。
C语言会把赋值运算右边的表达式转换成左边变量的类型。
强制类型转换:(float)dividend / divisior;将变量dividend强制转换为float,编译器也会将divisior转换为float类型。
5、类型定义(7.5)
使用typedef对类型进行定义,例如,typedef int Bool;
类型定义更加易于理解,类型定义可以更好的编写可移植的程序。
可以移植性的技巧:为了更大可移植性,可以考虑使用typedef定义新的整数类型名。
6、sizeof运算符(7.6)
sizeof (类型名),返回的值是一个无符号整数,代表存储类型名的值所需的字节数。
sizeof也可以用于常量,变量和表达式,比如sizeof(i+j)
第八章 数组
1、一维数组(8.1)
数组是含有多个数据值的数据结构,并且每个数据值具有相同的数据类型。
数组常见操作
for (i = 0; i < N; i++) a[i] = 0; //初始化数组 for (i = 0; i < N; i++) scanf("%d", &a[i]); //将数据读入数组 for (i = 0; i < N; i++) sum += a[i]; //对数组中元素求和
数组初始化:int a[10] ={0};初始化完全为空是非法的,所以要在大括号内放一个0,其他值会默认初始化为0。
C99中指定初始化:int a[15] = { [2] = 29, [9] = 7, [14] =48}
可以用sizeof计算数组元素的大小,比如数组是a[10],用数组的大小除以数组元素的大小:sizeof(a) / sizeof(a[0])
数组赋值除了用下面的方法,也可以用memcpy函数进行赋值,需要将头文件string.h包含,一般memcpy比较高效。
for (i = 0; i < N; i++) a[i] = b[i];
2、多维数组(8.2)
C语言中的数组是按照行主序存储的,也就是从第0行开始,接着第1行,以此类推。
多维数组不建议省略掉内层的花括号。
常量数组(声明前加const)好处:首先,表明程序不会改变数组(方便别人阅读程序),然后,有助于编译器发现错误(告诉编译器不会修改)。
变长数组优点:程序员不必再构造数组时给定一个长度,程序可以在执行时计算所需元素个数。
变长数组限制:没有静态存储限制,没有初始化。
第九章 函数
1、函数的定义和调用(9.1)
函数不能返回数组,但关于返回类型没有其他限制;返回类型是void类型说明函数没有返回值;省略返回类型C89默认为int类型,C99中是不合法的。
如果返回类型很冗长,可以把返回类型单独放在一行,这很有用。
C89中变量声明必须出现在语句之前;C99中,变量声明和语句可以混在一起,只要变量在第一次使用之前进行声明就行。
没有时间完成函数,预留下空间,以待后续完成编写函数体。
printf函数返回显示的字符个数。
一个函数中可以声明与另一个函数中的变量同名的变量。因为这两个同名的变量在内存中地址不同,所以给其中一个变量赋新值不会影响另一个变量。
2、函数调用(9.2-9.3)
在调用前声明每个函数。
在C语言中,实际参数是通过值传递额:调用函数时,计算出每个实际参数的值并且把它赋值给相应的形式参数。
在函数执行过程中,对形式参数的改变不会影响实际参数的值,因为形式参数中包含的是实际参数值的副本(copy).
一维数组型实际参数
//sum_array函数原型 int sum_array(int a[], int n); //sum_array函数定义 int sum_array(int a[], int n) { int i, sum = 0; for (i = 0; i < n; i++) sum += a[i]; return sum; }
如果return语句中表达式的类型和函数的返回类型不匹配,系统将会把表达式的类型隐式转换成返回类型。
3、程序终止(9.5)
return语句和exit函数之间差异:不管哪个函数调用exit函数都会导致程序终止,return语句仅当由main函数调用时才会导致程序终止。
如果函数调用它本身,那么此函数就是递归的。
第十章 程序结构
1、局部变量(10.1)
函数体内声明的变量成为该函数的局部变量。局部变量有自动存储期限和块作用域两种性质。
局部变量的块作用域不能延伸到其他函数,只能在所属函数内部。
静态局部变量有永久的存储单元(静态存储期限),在整个程序执行期间都会保留变量的值。
形式参数拥有和局部变量一样的性质,即自动存储期限和块作用域。
2、外部变量(10.2)
外部变量是声明在任何函数体外的。外部变量有静态存储期限和文件作用域(从变量被声明的点开始一直到所在文件的末尾)。
使用外部变量的缺点:1.容易造成程序的耦合;2.外部变量赋值错误,很难查找;3.不方便函数的复用。
使用外部变量时,要确保它们都拥有有意义的名字。
3、程序块和作用域(10.3-10.4)
程序块包括:{多条声明 多条语句}。声明在程序块中的变量存储期限是自动的。
当程序块内的声明命名一个标识符时,如果此标识符已经是可见的,新的升明明临时“隐藏”了旧的声明,标识符获得了新的含义。
4、构建C程序(10.5)
- 单个文件程序的构建:构建程序的编排顺序。
- #include指令;
- #define指令;
- 类型定义(typedef int Bool;);
- 外部变量的声明;
- 除main函数之外的函数的原型;
- main函数的定义;
- 其他函数的定义。
建议:在每个函数定义前需要进行注释,已给出函数名、描述函数的目的、讨论每个形式参数的含义、返回值和副作用等信息。
第十一章 指针
1、指针变量(11.1)
可执行程序由代码和数据两部分组成。程序中每个变量占有一个或多个字节内存,把第一个字节的地址称为是变量的地址。
指针就是地址,而指针变量就是存储地址的变量。
C语言要求每个指针变量只能指向一种特定类型的对象。
2、取地址运算符和间接寻址运算符(11.2)
使用&(取地址)运算符,可以找到变量的地址。使用*(间接寻址)运算符,可以获得指针所指向对象的内容。
通过使用&运算符把某个变量的地址付给指针变量
int i, *p; p = &i;
一旦指针变量指向了对象,可以使用*(间接寻址)运算符访问存储在对象中的内容。
只要p指向i,*p就是i的别名。*p不仅拥有和i相同的值,而且对*p的改变也会改变i的值。
3、指针赋值(11.3-11.5)
假设如下声明,对指针初始化后,p=&i。可以进行q=p,这是指针赋值,p和q都指向i。而*q = *p,是赋值语句,将p指向的值复制到q指向的对象中。
int i, j, *p, *q;
指针可以作为参数,传递地址给实际参数,实际参数和形参通过指针建立联系,改变形参内容即改变实参内容。
函数可以返回指针,同时函数也可以返回指向外部变量或指向声明为static的局部变量的指针。
永远不要返回指向局部变量的指针,返回后局部变量就不存在了。
第十二章 指针和数组
1、指针的算术运算(12.1)
当指针指向数组元素时,可以通过3中算术运算访问数组内的其他元素:指针加上整数,指针减去整数,两个指针相减。
指针加上整数:p原先指向的元素向后j个位置。如果p指向元素a[i],则p+j指向a[i+j]。
指针减去整数:p原先指向的元素向前j个位置。如果p指向数组元素a[i],则p-j指向a[i-j]。
两个指针相减:指针之间距离。如果p指向a[i],q指向a[j],那么p-q就等于i-j。只有两个指针指向同一个数组时,相减才有意义。
2、指针用于数组处理(12.2)
*运算符和++运算符的组合:
a[i++] = j;先将值存入一个数组元素中,然后前进到下一个元素。
*p++ = j;类似于a[i++] = j。因为++优先级高于*,也可以看成*(p++) = j;因为p++的值是p,所以p只有在表达式计算出来后才可以自增。因此*(p++)的值是*p,即p指向当前对象的值。
(*p)++:这个表达式返回p指向的对象值,然后对对象进行自增(p本身是不变化的)。
表达式 | 含义 |
*p++或*(p++) | |
(*p)++ | |
*++p或*(++p) | |
++*p或++(*p) |
最频繁用到的是*p++。
3、用数组名作为指针(12.3)
可以用数组的名字作为指向数组第一个元素的指针。
a[i]等价于*(a+i),可以把数组的取下标操作看成是指针算术运算的一种形式。
for (p = &a[0]; p < &a[N]; p++) sum += *p; // 简化循环后,用a替换&a[0],同时用a+N替换&a[N] for (p = a; p < a + N; p++) sum += *p;
许多程序员认为把形式参数声明为*a更准确,因为它会提醒我们传递的仅仅是指针而不是数组的副本。实践中,*a比a[ ]更常用。
4、指针和多维数组
C语言按行主序存储二维数组。即,先是0行的元素,接着是1行的,以此类推。
处理多维数组的行,处理多维数组的列,用多维数组名作为指针。