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

存储类别回答两个问题——变量"住哪儿"(内存的哪块地方)和"活多久"(生存期)。四个类别:auto(默认,住栈上、随函数生灭)、static(住静态区、活到程序结束)、register(建议放寄存器,现代基本被忽略)、extern(声明"这个变量在别处有")。

📝 本节和前面几节的关系

6.4 讲的是"谁能看见"(作用域),这一节讲"住哪、活多久"(生存期)——两个维度是分开的,这是全节最重要的观念。初学时很多困惑("函数里的变量怎么一出函数就没了""怎么让变量记住上次的值")都要靠这一对概念才能解开。

一、两个维度,千万别混

维度 管什么 相关小节
作用域 名字在哪些代码里看得见 6.4(全局 / 局部 / 块内)
存储类别 变量住在内存哪一块、从生到死多久 本节(auto / static / register / extern)

一个变量同时具备这两个属性。看个例子:函数里的 static 变量——作用域是局部的(外面看不见这个名字),生存期是全局的(命一直活到程序结束)。这正是下一节最反直觉、也最有用的知识点。

二、四个类别速览

类别 住哪 活多久 什么时候用
auto 栈(stack) 函数进来时生、返回时死 默认就是它——平时写变量都在用它
static 静态存储区 程序开始到结束 想让变量"记住上次的值"
register 建议放 CPU 寄存器 同 auto 现代基本不用(编译器自己会优化)
extern 别处定义的那块地方 同被声明的那个变量 跨文件共享全局变量

三、一个直观的比喻(把内存想成一栋楼)

楼层 对应变量 特点
栈 auto 临时客房——函数一进来开房,一走就退房
静态区 static / 全局 长期住户——从开门住到关门
寄存器 register 贴身口袋——存取最快,但位置极少
堆 (第 7 章) 自租仓库——自己申请、自己归还

顺带记住:栈是"自动打扫"的,堆是"自己负责"的。后者的麻烦,第 7 章才正式遇到。

四、动手 Demo:一屏看懂"生存期"

#include <iostream>
using namespace std;

int g = 100;                    // 全局变量(住在静态存储区)

void counter() {
    int a = 0;                  // auto:每次调用都重新出生
    static int s = 0;           // static:只出生一次,之后一直活着
    a++;
    s++;
    cout << "auto a = " << a << " , static s = " << s << endl;
}

int main() {
    counter();
    counter();
    counter();
    cout << "全局 g = " << g << endl;
    return 0;
}

看到什么算成功:

auto a = 1 , static s = 1
auto a = 1 , static s = 2
auto a = 1 , static s = 3
全局 g = 100

这一屏就把"生存期"讲透了:a 每次都是 1(每次调用都是新生),s 却在长大(它没死过)。

30 秒实验:把 static int s = 0; 里的 static 删掉,重新运行——三行都变成 a = 1(s 也跟着变成 1 了,虽然名字还写着 static,名不副实,顺手把名字也改掉)。然后再加回来。删掉、加回——就是"活不过函数"和"活得比函数长"的区别。

五、常见误区

  1. "作用域和生存期是一回事"——最容易混的一点。函数里的 static 变量是标准反例:名字只在函数里看得见,命却活到程序结束。
  2. "局部变量没初始化一定是乱的"——对普通局部变量成立(2.3.2 的垃圾值),但 static 变量和全局变量会被自动清零——这是能白捡的便宜。
  3. "写了 register 就跑得快"——现代编译器对寄存器的安排比人精准,写了它多半直接忽略。
  4. "extern 是定义一个变量"——它是声明("别处有这个东西",回看 6.1.2 声明 vs 定义),本身不分配空间。
自测三问(点开对答案) 1. 存储类别回答哪两个问题?——变量住哪儿(内存位置)和活多久(生存期)。 2. 作用域和生存期是一回事吗?——不是。函数里的 static 变量作用域局部、生存期全局。 3. auto 变量住在哪、活多久?——住在栈上,随函数进入而生、返回而死。

相关笔记