c语言编程教程分支(大项目编程的正确姿势)
前面两节介绍的 C语言“伪类”和宏定义,在较大的项目中都是非常实用的。实际上,程序员的工作就是把一个较复杂的需求,分解成若干个较独立的模块,然后继续把每个模块分解成若干更简单的工作,并编写代码逐个实现,最后再合并完成需求。在实际开发中,每一个独立模块单独占用一个文件是合适的。本节将介绍 C 语言的多文件编程。
例如在某个项目中,text.c 负责处理文本,picture.c 负责处理图片,video.c 负责处理视频。整个项目的构成一目了然,维护很方便。还是像以前一样,我们从实例出发:建立 fun.c 文件,自定义的函数都在此文件编写。再建立 main.c 文件,main 函数编写在此文件中,如下图:
然后在 fun.c 文件里定义 add 函数和全局变量 cnt:
// fun.c int cnt = 0; int add(int a, int b) { printf("add cnt: %d\n", cnt ); return a b; }
再在 main.c 文件里的 main 函数中调用 add 函数:
// main.c #include <stdio.h> int main() { printf("3 4=%d\n", add(3, 4)); printf("3 7=%d\n", add(3, 7)); return 0; }
编译执行,输出结果如下。
虽然程序按照预期执行了,但是应该能在编译时发现有警告信息:
...\hello\main.c|5|warning: implicit declaration of function 'add' [-Wimplicit-function-declaration]|
这是因为编译器在处理 add 函数调用时没有找到 add 函数的原型,只能根据 add(3, 4) 函数调用“推测”隐式声明:
int add(int, int);
所幸编译器“推测”正确,因此程序得以正常执行。
C 语言编译器为什么需要函数原型?这是因为编译器必须知道函数的参数类型和个数,以及返回值的类型,才能知道该生成什么样的指令。那,为什么不从函数调用的隐式声明中“推测”呢?这是因为并不是所有情况编译器都能“推测”正确的,一旦编译器“推测”错误,要么程序无法编译通过,要么无法得到预期结果。
例如,add 函数的形参都是 int 型的,实际上我们也能传入 char 型实参,这时编译器就无法获得正确的参数类型了。对于 printf 这种参数可变的函数,编译器就更不能确定它的参数类型了。另外,函数的返回值类型,编译器也往往无法正确获得。既然如此,为什么编译器不自己去搜索函数的定义呢?因为编译器不知道去哪里找,例如我们在 main.c 里调用的 add 函数在 fun.c 里,编译器又怎么会知道呢?
extern 和 static 关键字extern 的字面意思是“外部的”,它也是 C 语言中的一个关键字,表示“外部符号”。我们已经知道 C 语言编译器需要知道函数的原型,所以在 main.c 中,正确的做法应该是:
#include <stdio.h> extern int add(int a, int b); // 或 int add(int a, int b); int main() { char i = 7; printf("3 4=%d\n", add(3, 4)); printf("3 7=%d\n", add(3, i)); return 0; }
这样编译器就知道 add 函数的原型了,也知道它来自于外部文件。实际上,函数声明中的 extern 也可以不写,不过这里就算不写 extern 仍然表示 add 函数是外部符号。
应该注意到 fun.c 文件里有个全局变量 cnt,我们能否在 main.c 中使用呢?答案是肯定的,使用 extern 引入定义就可以了。
extern int cnt;
引入外部变量时,extern 不能省略。extern int cnt; 不是定义变量,因此不会为 cnt 分配空间。另外,在 fun.c 中,我们可以把 cnt 初始化为 0 :
int cnt = 0;
而在 main.c 中,则不能对 cnt 做初始化,下面这种做法是非法的,编译器会报错:
extern int cnt = 1; //非法
引入全局变量 cnt 时,如果不写 extern,意思就变了,int cnt;显然表示定义了一个变量。
如果不希望外界使用本文件里定义的函数,或者变量,该怎么办呢?答案是使用 static 关键字。以前我们使用过 static 来定义静态变量,它其实还表示变量或者函数属于“内部符号”,有 static 修饰的全局变量和函数在外部文件中都是不可见的。
// fun.c static int cnt = 0; static int add(int a, int b) { printf("add cnt: %d\n", cnt ); return a b; }
这时,cnt 和 add 函数只能在 fun.c 文件中使用,在 main.c 中即使使用 extern 也是不能访问 cnt 和 add 函数的。
有了 extern 和 static 关键字,我们在不同的文件里定义不同的模块时,就能方便的控制变量或者函数的访问范围了。
可是还有问题啊,如果 add 函数可以被外部访问,还有 A 模块也需要用到 add 函数,那我们就需要再在 A 模块里 extern add 函数。如此一来,以后只要有模块需要用到 add 函数,就需要 extern 一次,这不是太啰嗦了吗?的确,所幸 C 语言中还有 头文件,限于篇幅,接下来再介绍。
欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com