C++指针传递
较难29指针传递:用地址“遥控”修改数据
想象一下,你有一盒乐高积木放在家里,朋友想帮你整理。如果你直接把整盒积木复印一份(值传递),朋友只能玩复印件,原件纹丝不动。如果你把家门钥匙(指针)给朋友,他就能直接进去,随意调整积木位置。在C++里,指针传递就是“给函数一个地址,函数顺着地址找到原变量,直接修改它”。
指针是一个变量,里面存的是另一个变量的内存地址(就像门牌号)。写函数时,参数写成 int *a,表示“a是一个指针,指向一个整数”。在函数内部,要通过 *a 才能访问那个整数本身(这叫“解引用”)。看起来有点绕,但用习惯后非常灵活。
1. 指针传递的基本写法
- 定义指针:
int *ptr;—— 声明一个能存整数地址的指针。 - 取地址:
&x—— 拿到变量x的地址。 - 解引用:
*ptr—— 通过地址得到那个变量的值。
看一个简单例子——用指针修改一个人的成绩:
#include <iostream>
using namespace std;
// 通过指针把考试成绩+10分
void addBonus(int *score) {
*score = *score + 10; // 解引用,直接修改原变量
cout << "加完分后分数是:" << *score << endl;
}
int main() {
int myScore = 75; // 我的考试成绩75分
cout << "原始分数:" << myScore << endl;
addBonus(&myScore); // 传入myScore的地址
cout << "主函数里现在分数:" << myScore << endl; // 85分,真的变了
return 0;
}
运行结果:
原始分数:75
加完分后分数:85
主函数里现在分数:85
你会发现,myScore 在函数外部也被修改了,因为函数通过地址找到了原件。
2. 用指针交换两个变量(经典案例)
值传递做不到交换,因为交换的是副本。指针传递可以。下面这个例子就像你和小明交换零食:你手里的薯片盒(变量)和小明手里的饼干盒,你们互相交换地址,就能直接换货。
#include <iostream>
using namespace std;
// 指针传递:通过地址交换两个变量的值
void swap(int *a, int *b) {
int temp = *a; // *a 就是 a 地址里存的值,即第一个数的原件
*a = *b; // 把第二个数的原件放到第一个位置
*b = temp; // 把原来第一个数的原件放到第二个位置
cout << "函数内部:*a = " << *a << ", *b = " << *b << endl;
}
int main() {
int x = 5, y = 10; // x有5块零花钱,y有10块
cout << "调用前:x = " << x << ", y = " << y << endl;
swap(&x, &y); // 把x和y的地址传给函数,就像告诉函数“x在房间101,y在房间102”
cout << "调用后:x = " << x << ", y = " << y << endl; // 确实交换了
return 0;
}
运行结果:
调用前:x = 5, y = 10
函数内部:*a = 10, *b = 5
调用后:x = 10, y = 5
注意:调用时要写成 swap(&x, &y),函数参数声明为 int *a, int *b,函数体内用 *a 和 *b 访问原值。
3. 指针传递 vs 引用传递 vs 值传递
| 传递方式 | 比喻 | 能否改原变量 | 代码写法 |
|---|---|---|---|
| 值传递 | 复印一份文件,你改的是复印件 | 不能 | void func(int a),调用 func(x) |
| 引用传递 | 给文件贴上“共用标签”,你直接改原件 | 能 | void func(int &a),调用 func(x) |
| 指针传递 | 给你文件柜的钥匙,你用钥匙打开柜子改原件 | 能 | void func(int *a),调用 func(&x) |
引用传递更直观,但指针更灵活:指针可以指向“空”(用 nullptr 表示不指向任何东西),可以指向数组,还可以通过指针运算访问连续内存(比如数组遍历)。比如你想让一个函数既可能修改原变量,也可能什么都不做(传空指针),引用就做不到,而指针可以:
void maybeModify(int *p) {
if (p != nullptr) {
*p = 100; // 只有在指针有效时才修改
}
}
4. 新手容易犯的错误
① 忘记解引用
void swap(int *a, int *b) {
int temp = a; // 错误!a是指针本身(地址),不是值
a = b; // 这只会交换指针的副本,原变量纹丝不动
b = temp;
}
正确写法必须用 *a 拿到值。
② 使用未初始化的指针(野指针)
int *p; // p没有指向任何有效地址,里面是垃圾值
*p = 10; // 危险!可能写坏其他内存,导致程序崩溃
正确做法:要么让它指向一个变量,要么初始化为 nullptr。
③ 访问空指针(解引用 nullptr)
int *p = nullptr;
*p = 5; // 错误!不能对空指针解引用,程序会崩溃
调用函数前一定要检查指针是否为空,例如 if (p != nullptr) { ... }。
④ 传入错误的地址类型
double pi = 3.14;
int *p = π // 错误!int* 不能指向 double 变量
指针类型必须匹配所指向变量的类型。
5. 完整可运行的示例:用指针计算数组元素和
指针另一个大用途是操作数组。数组名本身就是一个指针(指向第一个元素)。下面例子展示了如何用指针遍历数组求和,就像你有一排抽屉,钥匙(指针)从第一个抽屉开始,每开一个抽屉就累加里面的硬币。
#include <iostream>
using namespace std;
// 计算整型数组中所有元素的和,参数:数组指针 和 元素个数
int arraySum(int *arr, int size) {
int total = 0; // 总和初始0
for (int i = 0; i < size; i++) {
total += *(arr + i); // arr+i 是第i个元素的地址,*解引用得到值
}
return total;
}
int main() {
int scores[5] = {85, 92, 78, 88, 95}; // 5个同学的考试分数
int len = 5; // 数组长度
int sum = arraySum(scores, len); // 数组名scores就是首地址
cout << "这组分数的总和是:" << sum << endl;
cout << "平均分是:" << sum / len << endl;
return 0;
}
运行结果:
这组分数的总和是:438
平均分是:87
注意:scores 作为参数传给 arraySum 时,会自动退化为指向第一个元素的指针,所以 int *arr 接收它。这里 *(arr + i) 等价于 arr[i],两种写法都行。
6. 总结与相关指引
- 指针传递 就像给了函数一把“万能钥匙”,函数能直接钻进内存改数据,非常强大。
- 使用时牢记三点:声明
int *p、取地址&x、解引用*p。 - 小心空指针和野指针,使用前一定要检查是否为
nullptr。 - 指针也常用于动态内存分配(
new/delete)、函数指针、链表等高级话题。
想进一步学习,可以看看:
- 指针与数组:如何用指针遍历数组,指针的加减运算。
- 动态内存分配:用
new在堆上申请内存,用delete释放。 - 引用传递:另一种修改原变量的方式,写法更简洁,但不如指针灵活。
- const 指针:防止指针意外修改数据,例如
const int *p或int * const p。
掌握了指针,你就拿到了C++内存世界的钥匙,可以玩出更多花样!
例题精讲
下列哪个选项正确描述了C++中指针传递的作用?
在C++中,使用指针作为函数参数时,函数内部对指针本身进行修改(如改变指针指向的地址)不会影响实参指针的值。
有如下函数定义:void func(int *p) { *p = 20; p = nullptr; } 执行以下代码后,a的值是多少?int a = 10; int *ptr = &a; func(ptr); cout << a;
请补全以下函数,使用指针传递交换两个整数的值。
void swap(int *a, int *b) {
___
}在C++中,如果将指针作为参数传递给函数,并在函数内部使用new为该指针分配动态内存,则函数返回后,实参指针将指向新分配的内存。