E1.2 C语言程序设计
把今天认真折叠好,明天自然会有展开的光
常量
常量是程序中最基本的元素,有字符常量,整数常量,浮点数常量和枚举常量。字符常量要用单引号括起来,例如上面的'}',单引号只能括一个字符而不能像双引号那样括一串字符。在格式化字符串中%号(Percent Sign)后面加上字母c、d、f分别表示字符型、整型和浮点型的转换说明(Conversion Specification).
1、总结前面介绍的转义序列的规律,想想在printf的格式化字符串中怎么表示一个%字符?写个小程序试验一下。
其它的他多数符号,都是用反斜杠\进行转义,但是对于%而言,是用其本身进行转义,即为%%
1 |
|

变量
变量(Variable)是编程语言最重要的概念之一,变量是计算机存储器中的一块命名的空间,可以在里面存储一个值(Value),存储的值是可以随时变的,比如这次存个字符'a'下次存个字符'b',正因为变量的值可以随时变所以才叫变量。
就是需要内存记住的,而且能够修改的数
给变量起名有一定的限制,C语言规定必须以字母或下划线_(Underscore)开头,后面可以跟若干个字母、数字、下划线,但不能有其它字符。例如这些是合法的变量名:Abc、__abc__、_123。但这些是不合法的变量名:3abc、ab$。其实这个规则不仅适用于变量名,也适用于所有可以由程序员起名的语法元素,例如以后要讲的函数名、宏定义、结构体成员名等,在C语言中这些统称为标识符(Identifier)。
关键字/保留字:
比如说char const这样的字符串
形参和实参
习题:
1、定义一个函数increment,它的作用是把传进来的参数加1。例如:
1 | void increment(int x) |
我们在main函数中调用increment增加变量i和j的值,这样能奏效吗?为什么?
不能,因为这个函数只有传入值,没有传出值,不能影响主函数的实际幅值
2、如果在一个程序中调用了printf函数却不包含头文件,例如int main(void) { printf("\n"); },编译时会报警告:warning: incompatible implicit declaration of built-in function ‘printf’。请分析错误原因。
没有读取头文件,编译器根本不知道这个printf的函数名字是什么
if语句
现在有一个问题,类似if (A) if (B) C; else D;形式的语句怎么理解呢?可以理解成与A配对,也可以理解为与B配对。
在C语言中,没有对应的缩进与对齐的要求,所以这是一个很有趣的问题。C语言规定,else总是和它上面最近的一个if配对,因此应该理解成else和if (B)配对,也就是按第二种方式理解。如果你写成上面第一种缩进的格式就很危险了:你看到的是这样,而编译器理解的却是那样。如果你希望编译器按第一种方式理解,应该明确加上{}:
浮点数不能做精确的比较,也就是说不能使用==这个符号来判断一个浮点数是否等于。
习题
1、写两个表达式,分别取整型变量x的个位和十位。
2、写一个函数,参数是整型变量x,功能是打印x的个位和十位。
习题


1 | //第一题,这第一题啥啊,给我难不会了。上面这个if else,可恶 |
深入理解函数
习题
1、编写一个布尔函数int is_leap_year(int year),判断参数year是不是闰年。如果某年份能被4整除,但不能被100整除,那么这一年就是闰年,此外,能被400整除的年份也是闰年。
2、编写一个函数double myround(double x),输入一个小数,将它四舍五入。例如myround(-3.51)的值是-4.0,myround(4.49)的值是4.0。可以调用math.h中的库函数ceil和floor实现这个函数。
这一段话写得有点精彩:
这里是讲到递归的严谨性。
这么说好像有点儿玄,我们从数学上严格证明一下factorial函数的正确性。刚才说了,factorial(n)的正确性依赖于factorial(n-1)的正确性,只要后者正确,在后者的结果上乘个n返回这一步显然也没有疑问,那么我们的函数实现就是正确的。因此要证明factorial(n)的正确性就是要证明factorial(n-1)的正确性,同理,要证明factorial(n-1)的正确性就是要证明factorial(n-2)的正确性,依此类推下去,最后是:要证明factorial(1)的正确性就是要证明factorial(0)的正确性。而factorial(0)的正确性不依赖于别的函数调用,它就是程序中的一个小的分支return 1;,这个1是我们根据阶乘的定义写的,肯定是正确的,因此factorial(1)的实现是正确的,因此factorial(2)也正确,依此类推,最后factorial(n)也是正确的。其实这就是在中学时学的数学归纳法(Mathematical Induction),用数学归纳法来证明只需要证明两点:Base Case正确,递推关系正确。写递归函数时一定要记得写Base Case,否则即使递推关系正确,整个函数也不正确。
习题
1、编写递归函数求两个正整数a和b的最大公约数(GCD,Greatest Common Divisor),使用Euclid算法:
如果
a除以b能整除,则最大公约数是b。否则,最大公约数等于
b和a%b的最大公约数。
Euclid算法是很容易证明的,请读者自己证明一下为什么这么算就能算出最大公约数。最后,修改你的程序使之适用于所有整数,而不仅仅是正整数。
2、编写递归函数求Fibonacci数列的第n项,这个数列是这样定义的:
fib(0)=1
fib(1)=1
fib(n)=fib(n-1)+fib(n-2)
上面两个看似毫不相干的问题之间却有一个有意思的联系:
Lamé定理
如果Euclid算法需要k步来计算两个数的GCD,那么这两个数之中较小的一个必然大于等于Fibonacci数列的第k项。
做作业
Euclid算法,这个证明实际上非常简单,假设有一个最大的公约数存在,那么b和a都公约一个数c,那么a%b也比较会是这个数c的倍数,所以C始终存在,但是两个参与运算的数字却一步步的在进行收敛,或者说。C一直在那里。
lame定理
下面我们想办法验证和证明lame定理,这是一个很有趣的问题:
如果Euclid算法需要k步来计算两个数的GCD,那么这两个数之中较小的一个必然大于等于Fibonacci数列的第k项。
我们尝试加标记来完成这个事情,这回我们尝试使用全局变量。
我想这个lame定理很好进行理解,因为对于feibo来说,最终完成的是前两项的”加法“,而对于euclid来说,假设a>b>0的前提,就会有a=a%b+kb,而a%b是某一次的最小项,经过一次重塑以后这个值会不断地交替进行变大,因为是kb,而feibo是加上b,所以大是显然的,而且很可能会大好几个数量级,因为k可以是无穷大。
C语言还是很有意思的,给我打开了一扇新的窗户。






