static和extern关键字

staticextern是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函数。
对于变量而言,如果不加下面提到的关键字的话,变量的声明和定义是同时完成的(假设没有编译器优化),也就是说,上面代码中的变量ab其实都是已经确定了位置的,只是一个初始化过了,有明确的值,另一个没有初始化过,则未必有明确的值(也有可能被自动给个零值)。而对于类或者结构体来说,和函数基本上是一致的。

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风格的代码。

作者:cwtxx

出处:https://www.cnblogs.com/cwtxx/p/18718185

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   cwtxx  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示