掘金 阅读 ( ) • 2024-04-03 16:49

extern的含义和作用

在C++中, extern 关键字用于声明一个全局变量或函数,表示该变量或函数是在其他文件中定义的。 当我们在一个cpp文件中使用 extern 关键字声明一个全局变量时,我们可以在其他cpp文件中定义该全局变量。这样,多个cpp文件就可以共享同一个全局变量。

extern关键字的使用方式一

在一个cpp文件中定义全局变量

// file1.cpp
int globalVariable = 10;

在另一个cpp文件中使用全局变量

// file2.cpp
#include <iostream>

extern int globalVariable; // 声明全局变量
int main() {
    std::cout << "Global variable value: " << globalVariable << std::endl;
    return 0;
}

通过这种方式,我们可以在file2.cpp中使用和访问file1.cpp中定义的全局变量。 需要注意的是, extern 关键字只是用于声明全局变量或函数,而不是定义它们。全局变量或函数的定义应该在另一个cpp文件中进行。

c++中的链接属性

链接属性一定程度范围决定着符号的作用域。这里先重点讲下外部链接。 external,外部链接属性。非常量全局变量和自由函数(除成员函数以外的函数)均默认为外部链接的,它们具有全局可见性,在全局范围不允许重名。 比如上面举的例子就是非常量全局变量的外部链接。

extern关键字的使用方式二

1.在一个.h头文件中申明全局变量或者函数。

// global.h
#ifndef GLOBAL_H
#define GLOBAL_H

extern int globalVariable; // 声明全局变量

#endif

2.在一个.cpp文件中定义全局变量或者函数

// global.cpp 
 #include "global.h" 

int globalVariable = 10; // 定义全局变量

3.在一个cpp文件(例如main.cpp)中使用全局变量:

// main.cpp
#include "global.h"
#include <iostream>

int main() {
    std::cout << "Global variable value: " << globalVariable << std::endl;
    return 0;
}

在上面的示例中,我们将全局变量放在了头文件global.h中声明,然后在使用的源文件中通过包含头文件的方式就能使用全局变量了。

头文件中非常量全局变量的extern关键字必须保留

在上面的例子中,如果把global.h头文件中的extern关键字去掉,然后在2个及以上的.cpp文件中包含该头文件,就会编译报错。

// global.h
#ifndef GLOBAL_H
#define GLOBAL_H

int globalVariable;

#endif

因为cpp源文件被编译成.o文件之后,会把多个.o文件链接,然后就发现这个变量被重复定义了。如果加上了extern就变成声明了,声明是可以有多个的,这种情况,定义或者实现只有一个。

为什么方式二include 也可以达到一样的效果

因为#include 只是简单的把包含的文件复制粘贴过去,所以使用头文件和自己一个一个用extern写声明的作用完全相同。

头文件中不能有定义,但是有几个例外:

  1. const 修饰的变量,是可以有定义的。
  2. static修饰的变量,是可以有定义的。
  3. 内联函数是可以有定义的。
  4. 类是可以有定义的。

小结:

将全局变量的定义和声明分离也可以避免多重定义的问题。如果我们将全局变量的定义放在头文件中,然后在多个cpp文件中包含该头文件,就会导致多个cpp文件中都有该全局变量的定义,从而引发重定义错误。而使用 extern 关键字声明全局变量可以避免这个问题,因为它只是告诉编译器该变量的定义在其他地方,不需要在当前文件中重新定义。

Java程序员对extern关键字的不理解

前面已经讲了,extern关键字可以解决多个.cpp文件使用共享的全局变量或者函数。但是Java程序员可能会有这样的疑问,对于这种全局变量或者函数,在java中是通过在一个类中通过static关键字来定义全局变量或者函数的啊?但是,同学,你可能还没太搞清楚一点,Java是一门完全面向对象的语言,也就是说,我们所有的变量或者函数都是要在类中定义的;而C++不仅是支持面向对象的,还支持其他范式编程,也就是说c++的头文件和源文件除了申明/定义类之外,还能定义其他的内容,也就是说c++对变量函数的操作不一定局限于类中。 比如说,在不使用类的情况下,直接申明/定义全局变量或者函数。

extern的一些实际业务场景

在实际的C++业务场景中,使用 extern 关键字来声明全局变量或函数的情况有很多。以下是一些常见的实际业务场景:

  1. 多个源文件共享全局变量:当一个项目包含多个源文件时,有时候我们需要在多个源文件中共享同一个全局变量。使用 extern 关键字可以在每个源文件中声明该全局变量,而将其定义放在一个源文件中。这样,不同的源文件就可以共享同一个全局变量,方便数据的共享和传递。
  2. 引用外部库的全局变量或函数:当我们使用外部库或者第三方库时,有时候需要在自己的代码中引用该库中定义的全局变量或函数。使用 extern 关键字可以在自己的代码中声明外部库的全局变量或函数,以便正确地使用和访问它们。
  3. 在多个模块之间共享数据:在大型项目中,通常会将代码划分为多个模块或组件。有时候,这些模块之间需要共享一些数据,以便彼此通信和交互。使用 extern 关键字可以在不同的模块中声明全局变量,以实现数据的共享和传递。

总之, extern 关键字在C++中的实际业务场景中有很多用途,特别是在需要共享全局变量或函数的情况下。它可以帮助我们实现模块化、可维护和可扩展的代码结构。

c++为什么要引入extern,解决了什么问题?

在C++中引入 extern 关键字的原因是为了解决多文件编译和链接的问题。如果不使用 extern 关键字,可能会导致以下问题:

  1. 重复定义:如果在多个cpp文件中都定义了相同的全局变量或函数,编译器会报重复定义的错误。这是因为每个cpp文件都会生成一个.o目标文件,然后这些目标文件会被链接在一起形成最终的可执行文件。如果多个cpp文件都定义了相同的全局变量或函数,链接过程中就会发生冲突。
  2. 链接错误:如果在一个cpp文件中使用了另一个cpp文件中定义的全局变量或函数,但没有使用 extern 关键字进行声明,编译器会报找不到符号的错误。这是因为编译器在编译时只看到当前cpp文件中的代码,不知道其他cpp文件中的定义。使用 extern 关键字声明全局变量或函数可以告诉编译器这些符号的定义在其他地方。
  3. 作用域问题:如果在一个cpp文件中定义了一个全局变量,其他cpp文件中无法访问该全局变量。这是因为全局变量的作用域仅限于定义它的cpp文件。使用 extern 关键字可以在其他cpp文件中声明该全局变量,使其在其他文件中可见。

因此,引入 extern 关键字可以解决多文件编译和链接时的重复定义、链接错误和作用域问题,确保全局变量和函数在不同的cpp文件中能够正确地共享和访问。

extern "C"的含义和作用

首先我们得知道C++是在C语言上做了一些拓展,C和C++是能兼容和混编的。一个c++项目中也可能有部分功能通过c语言实现的,然后在cpp中调用该功能。

extern "C" 的作用是为了能够正确在 C++ 代码中调用 C 语言代码。 加上 extern "C"是告诉编译器将指定的函数或变量按照 C 语言的方式进行编译和链接,而而不是按照 C++ 的方式。

先来看一个c和c++混编的demo

//file.h
#ifndef CPPSTUDY_FILE4_H
#define CPPSTUDY_FILE4_H

//申明一个全局函数
int add(int x,int y);

#endif //CPPSTUDY_FILE4_H

c语言实现这个函数,注意是.c文件,不是.cpp文件

// file.c
#include "file.h"

int add(int x, int y) {
    return x + y;
}

main.cpp中调用这个c语言实现的函数

#include "extern/file.h"

int main() {
    int result = add(1,1);
    cout << result << endl;
    return 0;
}

这里会报错,错误信息如下:

Undefined symbols for architecture x86_64: "add(int, int)", referenced from: _main in main.cpp.o (found _add in CMakeFiles/CppStudy.dir/extern/file4.c.o, declaration possibly missing extern "C")

报错的意思是找不到符号的定义。这是因为在 C++ 中,函数和变量的命名规则、参数传递方式以及编译和链接规则与 C 语言有所不同。 所以需要使用extern "C", 可以使得 C++ 代码能够与 C 代码进行兼容,确保它们能够正确地进行编译和链接。

解决办法

extern "C"{
   int add(int x, int y);
}

按理说,直接加上 extern "C"按照告诉编译器C的方式编译链接就可以了。但是又出现了个新问题, extern "C"是C++的语法啊,如果在C语言调用是不需要用这个的啊。所以有了新的改进方案,同时支持c和c++

#ifndef CPPSTUDY_FILE_H
#define CPPSTUDY_FILE_H

#ifdef __cplusplus
extern "C"
{
#endif

int add(int x, int y);

#ifdef __cplusplus
}
#endif

#endif //CPPSTUDY_FILE_H

#ifdef是C/C++预处理指令,#ifdef 是一个条件编译指令,用于检查一个标识符是否已经被定义。如果该标识符已经被定义,则编译器会编译指令后面的代码,否则会忽略指令后面的代码。

__cplusplus 是一个预定义宏,用于检查当前编译器是否支持 C++ 标准。如果编译器支持 C++ 标准,则该宏会被定义,否则该宏未定义。

所以上面的代码就可以解释了。只有当编译器支持 C++ 标准时,才会使用 extern "C",否则只做函数的声明。

统一的标准格式

#ifdef __cplusplus
extern "C" {
#endif
// 表示在这里声明需要按照 C 语言方式进行编译和链接的函数或变量

//正常的函数声明

#ifdef __cplusplus
}
#endif