第一部分、概览核心部分
- printf不是C的一部分,C本身并没有定义输入/输出功能。
- 文件复制
int c;
while ( (c = getchar()) != EOF) {
putchar(c);
}
- 单词计数——可以使用状态机
int state = OUT;
int nw = 0;
while ( (c = getchar()) != EOF) {
if ( c == ' ' || c == '\n' || c == '\t') {
state = OUT;
} else if ( state == OUT) {
state = IN;
nw++;
}
}
- main 函数需要向其执行环境返回状态:0表示正常终止,非0表示出现异常或出错结束条件。
- 函数原型必须与函数的定义和用法一致,但是参数名不要求相同。
- C语言中所有函数参数参数都是传值调用:传递给被调用函数的参数值存放在临时变量中,而不是存放在原来的变量中。被调用函数不能直接修改主调函数中变量的值,而只能修改其私有的临时副本的值。
如果必须修改,需要提供需要修改变量的地址;
当把数组名用作参数时,传递给函数的值是数组其实元素的地址。 - 写程序的一般性套路:
step1.分解问题,将问题的基本框架搭建出来
step2.将基本框架中的每一个处理转换成一个函数 - 自动变量——局部变量
外部变量——extern,在源文件中,如果外部变量的定义出现在使用它的函数之前,那么在那个函数中就没有必要使用extern。如果某个变量在file1文件中定义,在file2和file3中使用,那么在文件file2与file3中就需要extern声明来建立该变量与其定义之间的联系。 - 定义——表示创建变量或分配存储单元,声明是指说明变量的性质,但并不分配存储单元。
第二部分、各重要特性
2.1 基本的数据类型、运算符和表达式
变量、常量
声明语句:说明变量的名字及类型,也可以指定变量的初值
运算符
表达式
对象的类型:决定该对象可取值的集合以及可以对该对象执行的操作long类型的常量以l或L结尾,ul/UL表明是unsigned long
没有后缀的浮点数常量为double类型,f/F表示float类型,l/L表示long double类型
0八进制,0x/0X十六进制strlen(s)返回字符串参数s的长度,但不包括末尾的'\0'。'x'是一个整数,而"x"是一个字符串数组,包含'\0'
外部变量初始化表达式必须为常量表达式,默认情况下,外部变量和静态变量都将被初始化为0。未经初始化的自动变量的值为未定义值,其初始化表达式可以是任何表达式。
-
运算符的优先级以及结合性
函数调用、嵌套赋值语句、自增和自减运算符都有可能参数副作用——在对表达式求值的同时,修改了某些变量的值。在有副作用影响的表达式中,其执行结果同表达式中变量被修改的顺序之间存在着微妙的依赖关系。
2.2 控制流
- 语句与程序块
if-else else-if
switch
while
for
do-while
break
continue
goto语句改成不带goto语句(使用标识符)
2.3 函数和程序结构
- 作用域规则:
外部变量或函数的作用域从声明它的地方开始,到其所在的文件末尾结束。
对外部变量的声明与定义严格区分开来很重要。
在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern声明来访问它。外部变量的定义中必须指定数组的长度,但extern声明则不一定要指定数组的长度。
外部变量的初始化只能出现在其定义中。 - 静态变量static:
用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。通过static限定外部对象,可以达到隐藏外部对象的目的。
static修饰函数类似;
static修饰内部变量,使得变量生命期变长,一直占据存储空间,不想自动变量那样随着函数而生成和消失。 - register
告诉编译器,该变量在程序中使用频率较高。尽量放到寄存器中,但是编译器可以忽略此选项。无论就餐器变量实际上是不是放在寄存器中,它的地址都是不能访问的。 - 在不进行现实初始化的情况下,外部变量和静态变量都将被初始化为0,而自动变量和寄存器变量的初值则没有定义(即初值为无用的信息)。
对于外部变量与静态变量来说,初始化表达式必须常量表达式,且只初始化一次。
对于自动变量与寄存器变量来说,初始化表达式可以不是常量表达式。
如果初始化表达式的个数比数组较少,没有初始化的元素将被初始化为0;如果较多,则错误;字符数组,用字符串初始化时,最后一个元素是'\0'。 - 宏替换
#define dprint(expr) printf(#expr " = %g\n", expr)
#define paste(front, back) front ## back
- 条件包含
#ifndef HDR
#define HDR
...
#endif
2.4 指针和地址运算
- ANSI C使用void *代替char *作为通用指针的类型
- 地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式、常量或register类型的变量。
- ++ 与 * 优先级相同:*ip += 1与++*ip和(*ip)++相同,这两个运算符都是右结合
- a[i] 与*(a+i)相同,&a[i]与a+i含义相同。指针加1:pa+1指向pa所指向的对象的下一个对象。
- 数组名和指针之间有一个不同之处,指针是一个变量,数组名不是。当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。
- 如果确信相应的元素存在,也可以通过下标访问数组第一个元素之前的元素:p[-1] 和 p[-2]。
- 所有指针运算都会自动考虑它所指向的对象的长度,有向的指针运算:相同类型指针之间的赋值运算;指针同整数之间的加减法;指向相同数组中元素的两个指针之间的减法或比较;指针赋值为NULL或指针与NULL之间的比较。
- 下面两种定义有很大区别:
char amessage[] = "now is the time"; /* 定义一个数组,可以修改*/
char *pmessage = "now is the time"; /*定义一个指针,字符串的内容不可修改*/
- 二维数组作为参数:
在函数的参数声明中必须指出数组的列数。数组的行数没有太大关系,跟一维数组作为参数一样。
f(int arr[])
f(int *arr)
函数调用时传递的是一个指针,它指向由行向量构成的一维数组,其中每个行向量是具有13个整型元素的一维数组。
f(int daytab[2][13])
f(int daytab[][13])
f(int (*daytab)[13]) 这种声明形式表明参数时一个指针,它指向具有13个整型元素的一维数组。
如果去掉括号,因为[]优先级高于*:
int *daytab[13] 声明了一个数组,该数组有13个元素,每个元素都是一个指向整型对象的指针。
- 任何指针都可转换为void *类型,并且在将它转换回原来的类型时不会丢失信息。
- 函数指针:
函数名是函数的地址。
int *f(); //f是一个函数,它返回一个指向int类型的指针
int (*pf)(); // pf是一个指向函数的指针,该函数返回一个int类型的对象
- 复杂的声明怎么看,可以从外到内进行逐层分析:这个本质上跟语法分析树的递归层次是对应的,也跟语法产生式对应
2.5 结构和联合
- 三种传递结构的方法:
1)分别传递各个结构成员
2)传递整个结构
3)传递指向结构的指针 - typedef的三个优势:
1)表达式更简洁
2)使程序参数化,以提高程序的可移植性。如果typedef声明的数据类型同机器有关,那么,当程序移植到其他机器上,只需改变typedef类型定义就可以了。
3)为程序提供更好的说明性(与1)类似) - 联合union
联合就是一个结构,它的所有成员相对于基地址的偏移量都为0,此结构空间要大到足够容纳最“宽”的成员,并且其对齐方式要适合联合中所有类型的成员。
程序员负责跟踪当前保存在联合中的类型。
struct {
char *name;
int flags;
int utype;
union{
int ival;
float fval;
char *sval;
} u;
} symtab[NSYM];
第三部分、标准库
- 输入输出不是C语言本身的组成部分。
- 几个常用的库
1)输入输出,包括文件
2)字符串操作
3)字符类别测试和转换函数
4)ungetc
5)命令执行函数
6)存储管理函数
7)数学函数
8)随机数发生器函数
第四部分、C程序和UNIX操作系统之间的接口
- UNIX操作系统通过一系列的系统调用提供服务,这些系统调用实际上是操作系统内的函数,它们可以被用户程序调用。
- 标准库的输入、输出接口对任何操作系统都是一样的,在任何特定的系统中,标准库函数的实现必须通过宿主系统提供的功能来实现。
- 使用UNIX系统调用接口read write open creat unlink lseek实现fopen getc
- ls的实现
- 使用UNIX系统调用接口sbrk实现malloc和free存储分配程序


