🔷 第六章 函数
📄 一句话概括

extern 用来声明"这个变量在别处有"——它不分配空间,只是告诉编译器"这个名字不是你这儿定义的,去别处找"。它是跨文件共享全局变量的手段,思路和 6.1.2 的函数声明完全一样。

📝 本节和前面几节的关系

前几节讲的都是"单个 .cpp 文件内"的变量。这一节第一次踏出单文件边界——多个源文件之间怎么共享一个变量。它也回到 6.1.2 的老套路:声明(打招呼)与定义(真身)分开。而 1.3.1 讲过的"编译 + 链接两步",正是在这里显出价值:extern 的账,要等链接器来结。

一、为什么需要 extern

一个工程里常有多个 .cpp 文件(1.3.1 讲过的多文件工程)。每个文件独立编译,编译器眼里只有自己这一个文件。

如果 b.cpp 想用 a.cpp 里定义的全局变量 g,编译 b.cpp 时编译器根本不知道 g 是什么。得先告诉它:

extern int g;      // "有个叫 g 的 int,它在别处定义过了"

编译器于是放行,把"到底在哪"的问题留给链接器去解决。

二、定义 vs extern 声明

定义 extern 声明
写法 int g = 100; extern int g;
做什么 分配空间 + 给值 只告诉编译器这个名字存在
能写几次 整个工程只能一次 可以多次
放在哪 某个 .cpp 里 需要用它的 .cpp 里(或头文件里)

记忆点:extern 声明不带初始值——一旦写成 extern int g = 100;,它就变成定义了(多处写会报"重复定义")。

三、动手 Demo:跨文件共享

这需要建一个工程(1.3.1 讲过怎么建),里面放两个 .cpp 文件。

文件一:data.cpp(定义者)

int g = 100;                    // 定义:这里真的分配了空间

int getG() {
    return g;
}

文件二:main.cpp(使用者)

#include <iostream>
using namespace std;

extern int g;                   // 声明:g 在别的文件里
int getG();                     // 函数也一样要声明(6.1.2)

int main() {
    cout << "从 data.cpp 拿到的 g = " << g << endl;
    g = 200;                    // 改它:改的就是同一个变量
    cout << "改完之后 getG() 返回 = " << getG() << endl;
    return 0;
}

看到什么算成功:

从 data.cpp 拿到的 g = 100
改完之后 getG() 返回 = 200

第二行证明"两个文件操作的是同一个变量":main 里改了 g,data.cpp 里的函数读出来也是 200。

30 秒实验(在单文件里也能做):

#include <iostream>
using namespace std;

extern int g;               // 声明
int g = 100;                // 定义

int main() {
    cout << g << endl;      // 100
    return 0;
}

把 int g = 100; 这一行注释掉再编译——报错是:

undefined reference to `g'

注意这是"链接错误"而不是"编译错误":编译器已经认下了这个名字(因为声明在),是链接器最后没找到真身才报的。undefined reference 这个报错就是 extern 的标志脸。

四、extern 的两个常见搭档

  1. 配 const 做跨文件常量:extern const double PI;(在别处定义)。不过更常见的做法是把常量直接写进头文件。
  2. extern "C":让 C++ 代码能调用用 C 语言写的函数(按 C 的规则处理函数名)。调用系统库时常见,先记个印象。

五、常见误区

  1. "extern 是定义"——不是,是声明。写成 extern int g = 100; 就变成定义,多处写会报重复定义。
  2. "extern 变量不占内存"——声明不占;但它指向的那个定义当然占(归定义所在的文件管)。
  3. "extern 在单文件里也有用"——基本没用。它的舞台是多文件工程。
  4. "extern 和 static 没关系"——其实是一对:static 让全局变量只在本文件可见,extern 让别处也能用它(回顾 6.7.2 的第三种用法)。
  5. "链接错误和编译错误差不多"——这是本章最值得记住的一个区分:
  6. 编译错误:编译器看不懂(语法、未声明、类型不匹配)
  7. 链接错误:编译器看懂了,但拼装时找不到真身(undefined reference)
  8. 回看 1.3.1 的"编译 + 链接两步"——extern 就是这条分界线的活教材。
自测三问(点开对答案) 1. extern 是声明还是定义?——声明,不分配空间。 2. 它解决什么问题?——让多个 .cpp 文件共享同一个全局变量。 3. `undefined reference` 是编译错误还是链接错误?——链接错误(编译器认了名字,链接器找不到真身)。

相关笔记