函数的返回类型可以写成指针:int *func(...)——把"地址"当结果交回来。但有一条铁的纪律:绝不能返回局部变量的地址。因为局部变量在函数结束时就销毁了(6.7.1),交回来的地址指向一片已经不存在的地方,这叫"悬空指针",用它就是灾难。能安全返回的有三样:静态变量、全局变量、调用方传进来的指针。
7.6.1(地址进函数)、7.6.2(函数地址当数据)之后,第三件事是"地址从函数里出来"。它踩的雷全在前面讲过:6.7.1 的"auto 变量随函数生灭"是悬空指针的根源,6.7.2 的 static 正是解药之一。所以这一节也是第六章"变量存储类别"那一段的实用落地。
一、格式:把 * 写在函数名前
int *findMax(int a[], int n) { // 返回类型是 int*(一个地址)
int *p = &a[0];
for (int i = 1; i < n; i++)
if (a[i] > *p) p = &a[i]; // 记住"最大值的地址"
return p; // 把地址交回去
}
和普通函数只差两处:返回类型多一个 *;return 语句交回的是地址而不是值。
读法:int *findMax(...) = "findMax 交回一个地址,那个地址上住着一个 int"。
注意区分两种 *(7.4.2 的三重身份又来了一次):
int *fp(int); // 这是"函数声明":fp 是函数,返回 int*
int (*fp)(int); // 这是"函数指针":fp 是指针,指向函数(7.6.2)
看 * 和名字的先后关系——* 紧贴名字的是"返回指针的函数"。
二、铁的纪律:不能返回局部变量的地址
int *bad() {
int local = 5;
return &local; // ❌ 灾难!
}
为什么是灾难?回忆 6.7.1:局部变量(auto 生存类别)住在栈上,函数一返回,它那块空间就被回收了。此时交回去的地址指向一片已经不属于你的内存——这就是"悬空指针"。
后果不可预测:可能读到"看起来还对"的旧值(最坑),可能读到垃圾,也可能被下一次函数调用覆盖。编译器通常只给警告(function returns address of local variable),不报错。
函数运行中 函数返回后
┌──────────┐ ┌──────────┐
│ local = 5 │ → │ 已回收 │ ← 地址还在,但"房子"没了
└──────────┘ └──────────┘
↑ 返回这个地址没意义:主人已经搬走了
三、能安全返回的三样东西
| 能返回什么 | 为什么安全 | 说明 |
|---|---|---|
static 局部变量 |
生存期到程序结束(6.7.2) | 不会被回收,但要小心"每次调用是同一块" |
| 全局变量 | 全程存活 | 同上 |
| 调用方传进来的指针/数组 | 内存属于调用方,函数管不着它什么时候回收 | 最常用、最推荐 |
| 动态申请的内存 | 由程序员自己管生死 | 后续章节的主角 |
最推荐的写法是第三种:函数不自己造内存,只在"调用方给的那块内存里"挑一个地址返回。本节开头的 findMax(int a[], int n) 正是这个套路——它返回的是 a 数组内部的地址,数组是调用方的,函数只是指路。
四、实例13:指针做返回值
#include <iostream>
using namespace std;
// ① 安全:返回"调用方数组"里最大元素的地址
int *findMax(int a[], int n) {
int *p = &a[0];
for (int i = 1; i < n; i++)
if (a[i] > *p) p = &a[i];
return p;
}
// ② 危险:返回局部变量的地址(演示用,别真写)
int *badDemo() {
int local = 999;
return &local; // 悬空指针
}
// ③ 安全但要注意:返回 static 变量的地址
int *counterPtr() {
static int count = 0; // 6.7.2:初始化只一次,活到程序结束
count++;
return &count;
}
int main() {
int arr[6] = {12, 45, 7, 33, 88, 20};
int *pmax = findMax(arr, 6);
cout << "最大值 = " << *pmax << endl; // 88
cout << "它的下标 = " << (pmax - arr) << endl; // 4(指针相减,7.4.3)
*pmax = 100; // 能改!因为指向 arr 内部
cout << "改后 arr[4] = " << arr[4] << endl; // 100
cout << "计数 = " << *counterPtr() << endl; // 1
cout << "计数 = " << *counterPtr() << endl; // 2
cout << "计数 = " << *counterPtr() << endl; // 3
return 0;
}
看到什么算成功:最大值 = 88、它的下标 = 4、改后 arr[4] = 100、计数 = 1、计数 = 2、计数 = 3。
三个收获:
| 输出 | 说明 |
|---|---|
最大值 = 88 |
返回的是地址,*pmax 才拿到值 |
它的下标 = 4 |
用"指针相减"就能算出下标(7.4.3)——地址含着位置信息 |
改后 arr[4] = 100 |
通过返回的指针能改调用方的数组——因为它指向的是 arr 内部 |
计数 = 1/2/3 |
static 变量活到程序结束,返回它的地址是安全的 |
注意 badDemo() 我故意没在 main 里用——它可以编译(多半只有警告),但结果不可靠。"能编译"不等于"能运行",这是本章反复出现的主旋律。
30 秒实验:加一句 cout << *badDemo() << endl; 调用那个危险函数,重跑几次——有时打印 999(假象),有时打印垃圾。这个"时对时错"正是悬空指针最难查的地方。看到"有时对",千万别以为它是对的。
五、常见误区
- "返回指针就是把值复制一份出来"——不是。交回的是地址;地址指向的内容是不是还有效,得靠你自己保证。
- "局部变量返回地址只是警告,应该没事"——有事,而且是最难查的那种(时对时错)。看到这个警告必须改。
- "返回 static 变量地址最省事,那就都用它"——省事但有陷阱:所有调用共用同一块内存(
counterPtr()的三次调用打印同一个地址)。如果调用方把两次返回的指针都留着,会互相覆盖。 - "
int *f(int)和int (*f)(int)一样"——完全不同。前者是"返回指针的函数"(本节);后者是"函数指针"(7.6.2)。 - "返回数组名就等于返回数组"——只是返回首地址,数组本体没被复制(数组从来不能"整体复制",7.1.3)。这也意味着:返回局部数组的地址和返回局部变量的地址一样危险。
自测三问(点开对答案)
1. 为什么不能返回局部变量的地址?——局部变量(`auto`)在函数返回时就被回收(6.7.1),交回的地址指向已失效的内存,成为**悬空指针**。 2. 哪三种地址可以安全返回?——`static` 变量地址、全局变量地址、**调用方传进来的指针/数组**(另外还有动态申请的内存)。 3. `int *f(int)` 和 `int (*f)(int)` 分别是什么?——前者是"返回 `int*` 的函数";后者是"指向函数的指针"。- ☐ 7.7 指针数组(下一节)
相关笔记
- auto变量 —— 局部变量为什么会被回收
- static变量 —— 能安全返回的解药
- 指向函数的指针 —— 一字之差的写法
- 指针与一维数组 —— 指针相减算下标
- 🔷 C++基础 · 索引 —— 全课路线图