到目前为止,本书所需要介绍的C语言知识已经全部讲完了,本章涉及了指针和递归
头文件,副作用及其他
读者应该已经知道main函数也是一个普通的函数(甚至可以递归调用),其返回值将告知操作系统,在算法竞赛中应当总是等于0,唯一的谜团就是#include<stdio.h>
笔者尝试的代码如下

点击查看代码
#include<stdio.h>
int j = 0;
int main(int i)
{
	if(j == 5)
		return 0;
	main(j++);
	printf("hello world!\n");
	return 0;
}
include<stdio.h>是头文件,实践者的理解方式就是--不加这一行时会出现社么错误,反过来就说明了这一行的作用,不加这一行的编译警告就是:

warning: incompatible implicit declaration of built-in function 'printf' [enabled by default]
也就是说,printf的隐式定义出了问题,这个头文件和printf有关。还记得第一次介绍math.h是怎么讲的吗?如果要使用数学相关的函数,需要包含这个头文件。换句话说,头文件的作用就是:包含了一些函数,供主程序使用。头文件里面并没有printf的源代码,而只有他的声明,printf属于libc的一部分,有兴趣的读者可以自行查阅相关资料。

printf/scanf及其兄弟 格式化输入输出
fopen,freopen,fclose 文件的打开与关闭
getchar,fgets等 字符/字符串输入输出
以上函数的头文件都是stdio.h
sin/cos/pow等(sqrt) 各种数学函数
以上函数的头文件都是math.h
strlen,strcat 字符串函数
memste,memcpy 内存清0与赋值
以上函数的头文件都是string.h
isalpha,isdigit,toupper等 字符分类与转换
以上函数的头文件都是ctype.h
clock
计时函数
以上函数的头文件都是time.h

在编写实用软件的时候,往往需要编写自己的头文件,但在大部分算法竞赛中,只是编写单个程序文件。在本书中,所有题目都由单个程序文件来求解。
下面是一个比较有意思的问题:
是否可以编写一个函数f(),使得依次执行int a = f()和int b = f()的值不同,使用全局变量,这个问题就不会很困难
程序的实现如下:

点击查看代码
#include<stdio.h>

int g = 0;
int f()
{
	g++;
	return g;
}

int main()
{
	int a = f();
	int b = f();
	printf("%d %d\n", a, b);
	return 0;
}

不难写出一个更有意思的程序,写3个函数f(),g(),h(),使得"int a = (f() + g()) + h()"和"int b = f() + (g() + h())"后,a和b的值不同
加法明明满足结合律,居然有可能"(f()+g())+h()"不等于"f()+(g()+h())",这个例子说明:C语言的函数并不都像数学函数那样规矩。或者说得学术一点:C语言的函数可以有副作用,而不像数学函数那样纯,本书无疑深入介绍函数式编程,但时刻警醒最小化副作用是一个良好的编程习惯,正因为如此,前面多次强调,全局变量要少用,因为有时候就会出现上述的C语言中的函数得副作用

再来看一个小问题;函数可以返回指针吗?

点击查看代码
#include<stdio.h>

int* get_pointer()
{
	int a = 3;
	return &a;
}

int main()
{
	return 0;
}
这个程序可以编译通过,不过有一个警告: warning: function returns address of local variable [enabled by default] 意思是函数返回了一个局部变量的地址。前面说过,局部变量是在栈中,函数执行完毕后,局部变量就失效了。严格的讲,指针里保存的地址仍然存在,但不再属于那个局部变量。这是如果修改那个指针指向的内容,程序有可能会崩溃,也可能悄悄地修改了另外一个变量的值,使得程序输出一个莫名其妙的结果。 推荐的写法:如果只是想得到一个指向内容为3的指针,可以把这个指针作为参数,然后在函数里修改他;如果坚持返回一个新的指针,可以使用malloc函数进行动态内存分配。

关于浮点数误差的程序如下

点击查看代码
#include<stdio.h>

int main()
{
	double f;
	for(f = 2; f > 1; f -= 1e-6);
	printf("%.7f\n", f);
	printf("%.7f\n", f / 4);
	printf("%.lf\n", f / 4);
	return 0;
}

输出结果为:
0.9999990
0.2499998
0.2
换句话说,在不断减1e-6的过程中出现了误差,使得循环终止时f并不等于1,而是比1小一点,同时除以4保留一位小数的时候应该四舍五入为0.3的,但是却是0.2
作为竞赛选手来说,有一种方法可以缓解这种情况:加上一个EPS以后再输出。这里的EPS通常取一个比最低精度还要小几个数量级的小实数,例如,要求保留3位小鼠的时候EPS为1e-6.这只是权宜之计,甚至有可能起到反作用(如正确的答案真的是0.499999),但在实践中很好用(毕竟正确答案是0.499999的情况要比0.5要少很多)
也就是说上述的方法也是提供一个尽量贴尽答案的方法,但是并不是万能的,仍然有时候会有出现缺漏的时候

在C语言中的标准输入函数scanf中不可以通过%d来读入字符,因为程序会认为此时已经到了整数的末尾并且不会再读入而是将字符放入缓存区并且下次赋值的时候也会将字符拿出来,程序如下:

点击查看代码
#include<stdio.h>

int main()
{
	int a1, a2, a3, a4;
//	scanf("%d%d%d%d", &a1, &a2, &a3, &a4);
//	printf("%d %c %d %d\n", a1, a2, a3, a4);
	scanf("%d%c%d%c%d", &a1, &a2, &a3, &a2,&a4);
	printf("%d .%c. %d %d\n", a1, a2, a3, a4);
	return 0;
}

input:1a2a3
output:1 .a. 2 3