5

c语言入门27,多文件编程,extern和static关键字

 3 years ago
source link: https://blog.popkx.com/c-language-entry-27-multi-file-programming-extern-and-static-keywords/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

c语言入门27,多文件编程,extern和static关键字

发表于 2018-11-27 20:11:06   |   已被 访问: 398 次   |   分类于:   C语言   |   暂无评论

前面两节介绍的 C语言“伪类”和宏定义,在较大的项目中都是非常实用的。实际上,程序员的工作就是把一个较复杂的需求,分解成若干个较独立的模块,然后继续把每个模块分解成若干更简单的工作,并编写代码逐个实现,最后再合并完成需求。在实际开发中,每一个独立模块单独占用一个文件是合适的。本节将介绍 C 语言的多文件编程

ea3f82d6756071c35d35d9c26aa206b2.png

例如在某个项目中,text.c 负责处理文本,picture.c 负责处理图片,video.c 负责处理视频。整个项目的构成一目了然,维护很方便。

还是像以前一样,我们从实例出发,建立 fun.c 文件,我们自定义的函数都在此文件编写。再建立 main.c 文件,main 函数编写在此文件中,请看下图:
ad2ddf29ba4572cd795c77b2b3c73d59.png
然后在 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;
}

编译执行,输出结果如下。

ae63d220fe9bbd16a22e66659acf2847.png

虽然程序按照预期执行了,但是应该能在编译时发现有警告信息:

...\hello\main.c|5|warning: implicit declaration of function 'add' [-Wimplicit-function-declaration]|
a5447f8507188949deb3bf106b532ec5.png

这是因为编译器在处理 add 函数调用时没有找到 add 函数的原型,只能根据 add(3, 4) 函数调用“推测”隐式声明
int add(int, int);

所幸编译器“推测”正确,因此程序得以正常执行。

C 语言编译器为什么需要函数原型?

这是因为编译器必须知道函数的参数类型和个数,以及返回值的类型,才能知道该生成什么样的指令。那,为什么不从函数调用的隐式声明中“推测”呢?这是因为并不是所有情况编译器都能“推测”正确的,一旦编译器“推测”错误,要么程序无法编译通过,要么无法得到预期结果。

8ecc94f0c93712f28a0de735a4a9fa85.png

例如,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 函数是外部符号。

721f8a1e9987ec6ea71e0948293ca151.png

应该注意到 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;     //非法

如果不写 extern,意思就变了,int cnt;显然表示定义了一个变量。

3dcdb3f8f5f0b900ac7be4991d65fe19.png

如果不希望外界使用本文件里定义的函数,或者变量,该怎么办呢?答案是使用 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 函数的。

24273bf9464035ebb630c0fe796554af.png

有了 extern 和 static 关键字,我们在不同的文件里定义不同的模块时,就能方便的控制变量或者函数的访问范围了。

可是还有问题啊,如果 add 函数可以被外部访问,还有 A 模块也需要用到 add 函数,那我们就需要再在 A 模块里 extern add 函数。如此一来,以后只要有模块需要用到 add 函数,就需要 extern 一次,这不是太啰嗦了吗?的确,所幸 C 语言中还有 头文件,限于篇幅,接下来再介绍。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK