static和extern关键字
static
和extern
是C和C++中的两个关键字,常常会被用到,又容易在不经意间用错,总结一下使用方法和注意事项。
声明和定义#
要想搞清楚这两个关键字,需要先搞懂声明和定义(或者说是实现)两个概念。
例如:
void foo(); // 声明
void foo() { // 定义
std::cout << "hello world\n";
}
int a; // 声明和定义
int b = 42; // 声明和定义
class A; // 声明
class A { // 定义
public:
int num{ 42 };
};
有两个名为foo
的函数,第一个我们只知道有这么个函数,并不知道更多的细节;第二个我们知道了这个函数具体是在做什么的。第一个就称为函数的声明,第二个就称为函数的定义(或者叫实现)。对于编译器来说也是这样,当编译器看到第一个函数时,相当于是代码告诉了编译器,程序中有这么个函数叫做foo
,然后当编译器再找到第二个时,它知道了这个foo
函数具体是干什么的,它就能把这两者联系起来了。
按照上面的说法,自然能得到关于声明和定义的一个规则:声明可以有多次,但是定义只能有一次。很好理解,就好像说,代码不停地对编译器说,“有个叫foo
的函数,有个叫foo
的函数...”,这是没问题的,但是万一编译器找到了两个foo
函数的定义,那编译器就蒙圈了,它不知道到底哪个是真正要执行的foo
函数。
对于变量而言,如果不加下面提到的关键字的话,变量的声明和定义是同时完成的(假设没有编译器优化),也就是说,上面代码中的变量a
和b
其实都是已经确定了位置的,只是一个初始化过了,有明确的值,另一个没有初始化过,则未必有明确的值(也有可能被自动给个零值)。而对于类或者结构体来说,和函数基本上是一致的。
static关键字#
static
的用法主要有两点:
- 用
static
声明的变量会被存储在静态存储区,无论是在哪里声明的,这个变量都会在main
函数执行前就被构造出来了,且只会初始化一次,直到程序完全退出时才会释放掉。 - 在cpp文件中使用
static
定义的变量不会被其他文件引用,是自身独有的。obj是c/c++的最小编译单元,也就是说每个cpp文件在编译阶段都是独立编译的。而在cpp文件中使用static
变量声明的变量就会有自己的地址,即使在其他地方有重名的变量,在当前cpp文件中也只会使用自己本身定义出来的static
变量。
// header.h
#include <iostream>
using namespace std;
void fun();
// file1.cpp
#include "header.h"
static int num = 42;
void fun() {
cout << "call fun(), num = " << num << endl;
cout << "num address: "<< &num << endl;
}
// main.cpp
#include "header.h"
static int num = 24;
int main() {
cout << "main(), num = " << num << endl;
cout << "num address: " << &num << endl;
fun();
return 0;
}
例如有一个头文件和两个cpp文件,cpp文件都引用了头文件,并且两个cpp文件中都定义了一个使用static
修饰的变量num
,执行结果如下。
main(), num = 24
num address: 00007FF686BAD004
call fun(), num = 42
num address: 00007FF686BAD000
fun
函数是在file.cpp这个文件中实现的,所以会输出24和其地址,main
函数的前两行则是输出main.cpp中num
的值42和地址,明显能看到,两个同名的变量的地址是不用的,也验证了上面说的,用static
修饰的变量只会被自身文件看到和使用。
值得一提的是,static
关键字也可以用于修饰c++的类中的成员变量和成员函数,被修饰的成员变量称为静态成员变量,被修饰的成员函数称为静态成员函数,静态成员函数只能够访问静态成员变量。
// header.h
#include <iostream>
using namespace std;
class Base {
public:
static int num;
static void fun();
};
class Drived : public Base {
public:
static int num;
static void fun2();
};
// file.cpp
#include "header.h"
int Base::num = 42;
void Base::fun()
{
cout << "Base::num = " << num << endl;
cout << "&Base::num = " << &num << endl;
}
// main.cpp
#include "header.h"
int Drived::num = 24;
void Drived::fun2()
{
cout << "Drived::num = " << num << endl;
cout << "&Drived::num = " << &num << endl;
}
int main()
{
Base b;
b.fun();
Drived d;
d.fun2();
return 0;
}
执行结果:
Base::num = 42
&Base::num = 00007FF6AF55E000
Drived::num = 24
&Drived::num = 00007FF6AF55E004
有时还会看到在头文件中使用static修饰变量的情况,这与直接在cpp中使用并没有什么区别,因为我们知道#include
只是一个宏,功能就是将后面的文件名中的内容复制粘贴到当前文件下,所以在头文件中用static
修饰的变量实际上就是在每一个引用了这个头文件的cpp文件中都定义了一个相同的静态变量,仅此而已。硬要评价的话,我觉得这不是一个好的选择,当项目越来越庞大时,你可能已经没有精力甚至没有办法去确定到底哪些文件引用了某个头文件,当你认为在头文件中定义的变量都是同一个,而头文件中又有static
时,可能就会有悲剧发生了。
extern关键字#
我们知道,在头文件中不加任何修饰的变量是全局变量,且#include
只是执行了复制粘贴功能,于是当有多个cpp文件都引用了同个头文件时,那么就会造成头文件中的变量被多次的定义了。这时候就需要extern
关键字登场了。使用extern
修饰的变量就等于告诉编译器,这个变量在别的地方有定义了,直接去找不用再定义一遍了。使用如下:
// header.h
extern int num;
void fun();
// file.cpp
#include "header.h"
int num = 42;
void fun()
{
cout << num << endl;
}
// main.cpp
#include "header.h"
int main()
{
cout << num << endl;
fun();
return 0;
}
只在file.cpp文件中定义了一次变量,其他的同名变量都是通过extern
声明。
另外,在c++中可以通过extern "C" { }
的方式引用和定义c风格的函数接口。在这个大括号中的内容将被解析成c风格的代码。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗