前面的课咱们把函数的基本用法都讲透了。但还有一个事儿一直没聊——这个函数是不是谁都能调?我写的函数只能在当前文件里用,还是别的文件也能用?
这就涉及到今天的话题:内部函数和外部函数。
这两个概念说起来特简单,就一句话的事:内部函数只能在当前文件里用,外部函数可以被别的文件调用。
先看外部函数。
实际上,你之前写的所有函数,默认都是外部函数。啥意思?就是别的文件如果想调用你的函数,是可以的,只要人家知道你的函数长啥样。
你想啊,printf、scanf这些函数,它们不在你写的代码里,但你照样能调用它们。为啥?因为它们就是外部函数——它们定义在C标准库里,通过#include <stdio.h>把头文件引进来,你就能用了。
你自定义的函数,默认也是外部函数:
// file1.c int add(int a, int b) { // 默认就是外部函数 return a + b; }
// file2.c #include <stdio.h> int add(int a, int b); // 声明一下:这个函数在别处 int main() { int z = add(3, 5); // 能调用到file1.c里的add printf("%d\n", z); return 0; }
你只要在file2.c里声明一下add函数的存在,编译器在编译file2.c的时候知道“哦有这号人,具体的链接的时候再说”。链接的时候把两个文件拼在一起,就找到了file1.c里的add。
外部函数的关键词是extern,不过你写不写都一样,因为函数默认就是extern的。你也可以明确写出来:
extern int add(int a, int b) { // 跟不写extern效果一样 return a + b; }
那什么是内部函数?
内部函数就是只允许当前文件使用的函数。别的文件看不到它,也调不了它。想实现这个效果,就在函数定义前面加个static。
// file1.c static int helper(int x) { // 内部函数,只能在本文件用 return x * 2; } int public_add(int a, int b) { return helper(a) + helper(b); // 可以调用,因为在一个文件里 }
// file2.c #include <stdio.h> int helper(int x); // 声明也没用,链接的时候找不到 int main() { int z = helper(5); // 链接错误!找不到helper return 0; }
你看,file2.c里明明声明了helper,但链接的时候找不到它的定义,因为file1.c里的helper被static修饰了,它锁在了file1.c里面,外面看不见。
这就是内部函数的作用:把函数封装在文件内部,防止被别的文件乱调。
你可能要问:我啥时候需要把函数声明成内部函数呢?
前面咱们学存储类别的时候讲过static修饰全局变量,是为了“封装”,不让别的文件乱动。static修饰函数也是同样的道理。
在实际开发中,你通常会这么组织代码:你写了一个模块(比如一个数学工具包),对外只暴露几个核心函数让别人用,但内部有一些辅助函数(比如一些计算的子步骤)就不想让人知道,也不想让人乱调。这时候那些辅助函数你就加上static,把它们“藏”起来。
这样有几个好处:
第一,接口清晰。别人看你这个文件,只看那些没加static的函数就知道“哦,这些是能用的”,那些static的就不用管。
第二,防止命名冲突。如果你的内部函数名字跟别的文件里的函数重名了,因为它加了static,只在当前文件可见,所以不会跟别的文件冲突。
第三,封装性好。你内部怎么实现的,外面不用管,外面只调用你暴露的接口就行。
你在实际项目里看一些开源代码,里面很多static函数。这可不是为了“神秘”,而是为了“干净”。
那“内部”和“外部”到底对应咱们之前学过的啥?其实就是看有没有static:
-
外部函数:没有
static修饰。默认就是外部。作用域是整个程序,别的文件通过extern声明就能调用。 -
内部函数:有
static修饰。作用域只限当前文件,别的文件调不了。
你看下面这段代码,猜猜编译会不会报错?
// a.c #include <stdio.h> static void say_hello() { printf("Hello from a.c\n"); } void call_hello() { say_hello(); // 可以,同一个文件里 }
// b.c #include <stdio.h> void say_hello(); // 声明 int main() { say_hello(); // 编译能过,但链接会报错 return 0; }
结果:编译两个文件的时候,a.c和b.c各自编译成目标文件都没问题。但链接的时候,b.c里调用了say_hello,链接器在全局符号表里找不到它,因为a.c里的say_hello加了static,它只存在于a.c内部,不暴露给链接器。所以链接报错。
这就是内部函数的效果。
你还可以把函数的声明也加上static,这样声明和定义都限制在当前文件:
// file1.c static int helper(int x); // 声明成内部函数 static int helper(int x) { // 定义也是内部 return x * 2; } int public_add(int a, int b) { return helper(a) + helper(b); }
这样写更一致,让人一看就明白“这是个内部函数,别想着外面调”。
外部函数还有个情况你可能碰到:你在file1.c里定义了一个函数void print_msg(),在file2.c里想用。你通常会在file2.c里写extern void print_msg();来声明它。但其实你写不写extern都行,函数声明默认就是extern的。
// file2.c void print_msg(); // 不加extern,也是外部声明,因为函数默认外部 int main() { print_msg(); return 0; }
不过为了更清晰地表达“这个函数定义在别的文件里”,我建议你还是写上extern,这样看代码的人一目了然。
最后总结一下这节课的核心:
-
外部函数:没加
static,默认。整个程序可见,别的文件可以调用。 -
内部函数:加
static,只在当前文件可见,别的文件调不了。 -
内部函数的好处:封装内部实现、防止命名冲突、让接口更清晰。
-
函数默认是外部的,你想让它变成内部的才要加
static。
实际开发中,建议你把那些不打算让外界调用的辅助函数全都加上static,这算是个好习惯。
好了,同学们回去之后自己建两个.c文件,写一个内部函数和一个外部函数,分别试试能不能跨文件调用。