Day 1
记录
-
gcc -Wall a.c 记录错误和报警信息
-
/* */ 类型注释不能嵌套
// 也是C支持的注释形式,不能跨行进行 -
gcc math.c -lm
使用math.h中声明的库函数有一些特殊,gcc命令行必须加上-lm选项,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里去找。大部分库函数(例如printf)位于libc.so库文件中,使用libc.so中的库函数在编译时不需要加-lc选项,加了也不错,因为这个选项是gcc的默认选项。 -
echo (? )?是shell中的一个特殊变量,表示上一条命令的退出状态。
-
main()函数的定义
main()函数其实可以看作一个自建函数,C标准中规定了两种写法
int main(int argc, char *argv[])
int main(void)
除了这两种形式之外,定义main函数的其他写法都是错误的,或者不可以移植的。 -
任何表达式都有值和类型两个基本属性。
语法上规定,没有返回值的函数调用表达式是void类型的。 -
函数的声明、定义、调用
void threelines(void); 这种写法叫函数的声明而非定义。
只有分配存储空间的变量声明才叫变量定义,函数也是,编译器只有见到函数定义才会生成指令。 -
最少例外原则(Rule of Least Surprise)
-
形参与实参
形参相当于函数中定义的变量,调用函数传递参数的过程相当于定义形参变量并且用实参的值来初始化 -
全局变量和局部变量
局部变量可以用类型相符的任意表达式来初始化,而全局变量只能用常量表达式(Constan Expression)初始化。
程序开始运行时要用适当的值来初始化全局变量,所以初始值必须保存在编译生成的可执行文件中,因此初始值在编译时就要计算出来,然而非常量表达式的值必须在程序运行时才会计算,所以不能用来初始化全局变量。局部变量最好先赋值再使用
非定义的函数声明也可以写在局部作用域中,但是这样声明的标识符只具有局部作用域
写非定义的函数声明时参数可以只写类型而不起名,如:void print_time(int, int)
虽然在一个函数体中可以声明另一个函数,但不能定义另一个函数,C语言不允许嵌套定义函数。
-
if 语句
C99规定,如果a和b是整形,b不等于0,则表达式(a/b)*b+a%b的值总是等于a
%运算符的结果总是与被除数同号
else总是与它上面最近的一个if配对
- switch语句
- case后的表达式必须是常量表达式,这个值和全局变量的初始值一样,必须在编译时计算出来
- C语言规定case后面必须是整形常量表达式
-
深入理解函数
log(x),sin(x)返回都是浮点数,%freturn
函数返回值的理解:函数返回一个值相当于定义一个和返回值类型相同的临时变量,并用return后面的表达式来初始化
函数的返回值不是左值,或者说函数调用表达式不能作左值C语言的传参规则“Call by Value”,按值传递,return返回值也是按值传递
正负号是单目运算符,加减号是双目运算符,正负号优先级和逻辑非运算符相同,比加减的优先级要高
math.h中有个fabs函数是求绝对值的,不需要自建。
可以调用math.h的库函数ceil和floor实现四舍五入的功能。
-
增量式编程
将大问题简化为小问题,逐步调试小模块以最后完成大功能。
尽可能复用(reuse)之前写的代码,避免重复编写代码 -
递归
如果定义一个概念需要用到这个概念本身,我们称它的定义是递归(Recursive)的。
leap of faith, 首先相信一些结论,然后再用他们去证明另一些结论。数学归纳法(Mathematical Induction)只需证明两点:BaseCase正确和递推关系正确。
递归和循环是等价的,LISP实现就只有递归而无循环。
计算机指令能做的所有事情就是数据存取、运算、测试和分支、循环(或递归) -
while、do-while、for、循环与递归
-
a+++++b怎么理解
编译过程分为词法解析和语法解析两个阶段,再词法解析阶段,编译器总是从前到后找到最长的合法Token。
词法分析:a++ ++ +b
语义分析:a++的值只能做右值,不能继续++,所以编译器最终会报错 -
break、continue、嵌套循环
-
goto语句、标号(Label)
goto error;
error:
滥用goto语句会使程序的控制流程非常复杂,可读性很差。
通常goto语句只用于这种场合,一个函数中任何地方出现了错误条件都可以立即跳转到函数末尾进行出错处理(例如释放先前分配的资源、恢复先前改动过的全局变量等),处理完之后函数返回。除此之外,在任何场合都不要轻易考虑使用goto语句。
一些语言(如C++)中有异常(Exception)处理语法,可以代替goto和setjmp/longjmp的这种用法。
第7章 结构体
用结构体来表示复数,以及构建复数运算
基本类型,组合规则,数据抽象,过程抽象
结构体的定义1:
struct complex_struct{
double x,y;
};
结构体的定义2:
struct complex_struct{
double x,y;
}z1, z2;
结构体的定义3:
struct {
double x,y;
}z1, z2;
其中定义1和2,可以复用,例如可以这样定义另外两个变量
struct complex_struct z3, z4;
结构体变量也可以在定义时初始化
struct complex_struct z = {3.0, 4.0};
参数个数超过所需时会报错,不够时补零。
结构体变量之间允许使用赋值运算符,即允许用一个结构体变量初始化另一个结构体变量
数据类型标志
enum关键字的作用和struct关键字类似,把标识符定义为一个Tag,struct表示一个结构体类型,而enum表示一个枚举(Enumeration)类型。枚举类型的成员是常量,且它们的值由编译器自动分配。
enum coordinate_type {RECTANGULAR, POLAR }; 这样定义之后,RECTANGULAR表示常量0,POLAR表示常量1
也可以不从0开始分配
enum coordinate_type {RECTANGULAR = 1, POLAR };
枚举常量也是一种整形,其值在编译时确定,因此也可以出现在常量表达式之中,用于初始化全局变量或者作为case分支的判断条件。
有一点需要注意,结构体的成员名和变量名不在同一命名空间中,但枚举的成员名却和变量名在同一命名空间中,所以会出现命名冲突。
嵌套结构体
结构体本身是一种数据类型,其内部也可以定义结构体