Appearance
📍 05. 指针入门
指针 是 C/C++ 中最重要、也最独特的概念。如果说 Python 和 Java 把内存管理"藏"起来了,那么 C++ 则把内存的钥匙直接交到了你手中。
指针让你能直接操作计算机的内存——这是一把强大的武器,但也需要小心使用。
一、内存地址 🏠
计算机的内存就像一栋巨大的公寓楼,每个房间都有一个唯一的门牌号。
cpp
int a = 10;当你定义这个变量时,计算机会在内存中找一个"房间"(4 字节)来存放 10,这个房间的"门牌号"就是内存地址。
我们可以用 & 运算符来获取变量的地址:
cpp
#include <iostream>
using namespace std;
int main() {
int a = 10;
cout << "变量 a 的值:" << a << endl;
cout << "变量 a 的地址:" << &a << endl;
return 0;
}输出(每次运行地址可能不同):
变量 a 的值:10
变量 a 的地址:0x61fe1cNOTE
0x61fe1c 是十六进制表示的内存地址。0x 开头的数字就是十六进制。 每次运行程序时,操作系统分配的内存地址可能不一样。
二、什么是指针?📍
指针 就是一个变量,但它存储的不是普通的值,而是另一个变量的内存地址。
普通变量存的是"数据",指针变量存的是"数据存放在哪里"。
1. 指针的声明
cpp
int* p; // p 是一个指针,指向 int 类型的数据
char* c; // c 是一个指针,指向 char 类型的数据
double* d; // d 是一个指针,指向 double 类型的数据* 表示这是一个指针类型。int* 读作"指向 int 的指针"。
2. 取地址和取值
两个最重要的指针运算符:
| 运算符 | 名称 | 作用 |
|---|---|---|
& | 取地址符 | 获取变量的内存地址 |
* | 解引用符 | 通过地址访问该地址上存储的值 |
3. 基本使用
cpp
#include <iostream>
using namespace std;
int main() {
int a = 10;
int* p = &a; // p 存储了 a 的地址
cout << "a 的值:" << a << endl; // 10
cout << "p 的值(a的地址):" << p << endl; // 0x61fe1c
cout << "p 指向的值:" << *p << endl; // 10(解引用)
*p = 20; // 通过指针修改 a 的值
cout << "修改后 a 的值:" << a << endl; // 20
return 0;
}图解:
变量 a 指针 p
+-------+ +---------+
| 20 | <------| &a的地址 |
+-------+ +---------+
地址:0x100 地址:0x200
*p = 20(通过 p 找到 a 的地址,读取/修改那里的值)TIP
可以把 int* p = &a; 理解为:"p 是一个箭头,箭头指向了 a 所在的位置"。
p是箭头本身(存储地址)*p是箭头指向的地方(访问值)
三、指针的常见用途 🛠️
1. 通过函数修改外部变量
还记得吗?我们之前学的函数参数是值传递——函数内部修改形参不会影响实参。
cpp
void swapBad(int x, int y) {
int temp = x;
x = y;
y = temp;
// 这里交换的是副本,main 里的 a,b 不受影响
}
int main() {
int a = 3, b = 5;
swapBad(a, b);
cout << a << " " << b; // 还是 3 5,没交换成功!
return 0;
}用指针就能解决——把地址传进去,直接修改地址上的值:
cpp
#include <iostream>
using namespace std;
void swapGood(int* x, int* y) {
int temp = *x; // 取 x 指向的值
*x = *y; // 把 y 指向的值赋给 x 指向的位置
*y = temp; // 把 temp 赋给 y 指向的位置
}
int main() {
int a = 3, b = 5;
cout << "交换前:a = " << a << ", b = " << b << endl;
swapGood(&a, &b); // 传入 a 和 b 的地址
cout << "交换后:a = " << a << ", b = " << b << endl;
return 0;
}输出:
交换前:a = 3, b = 5
交换后:a = 5, b = 3NOTE
这是 C++ 中"指针做函数参数"的核心用途——让函数能修改外部的变量。 实际上 C++ 还有更安全的引用(Reference) 方式来实现同样的效果,后面会介绍。
2. 指针和数组
数组名本质上就是一个指针常量——它指向数组的第一个元素。
cpp
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
cout << "数组名 arr 的值:" << arr << endl; // 地址
cout << "arr[0] 的地址:" << &arr[0] << endl; // 和 arr 相同
cout << "arr[0] 的值:" << *arr << endl; // 10
cout << "arr[1] 的值:" << *(arr + 1) << endl; // 20
// 用指针遍历数组
int* p = arr;
for (int i = 0; i < 5; i++) {
cout << "arr[" << i << "] = " << *(p + i) << endl;
}
return 0;
}关键理解:
| 写法 | 含义 |
|---|---|
arr | 数组首地址(等价于 &arr[0]) |
*arr | 第一个元素的值(等价于 arr[0]) |
arr + i | 第 i 个元素的地址(等价于 &arr[i]) |
*(arr + i) | 第 i 个元素的值(等价于 arr[i]) |
3. 动态内存分配
有时候我们不知道需要多大的数组,想在程序运行时才决定大小:
cpp
#include <iostream>
using namespace std;
int main() {
int n;
cout << "请输入数组大小:";
cin >> n;
// 在堆上动态分配 n 个 int 的空间
int* arr = new int[n];
// 像普通数组一样使用
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
// 使用完毕后,必须手动释放内存!
delete[] arr;
return 0;
}WARNING
new 分配的内存必须用 delete 释放!
new配delete(单个对象)new[]配delete[](数组)
忘记释放会导致内存泄漏——程序占用的内存越来越多,最终可能耗尽系统资源。
四、引用(Reference)🔗
引用是 C++ 对指针的一种"安全封装"。可以把引用理解为变量的别名。
1. 基本使用
cpp
#include <iostream>
using namespace std;
int main() {
int a = 10;
int& ref = a; // ref 是 a 的引用(别名)
cout << "a = " << a << endl; // 10
cout << "ref = " << ref << endl; // 10
ref = 20; // 修改 ref 就是修改 a
cout << "a = " << a << endl; // 20
return 0;
}2. 用引用实现交换函数
cpp
void swapByReference(int& x, int& y) {
int temp = x;
x = y;
y = temp;
}
int main() {
int a = 3, b = 5;
swapByReference(a, b); // 直接传变量,不用 & 取地址
cout << a << " " << b; // 5 3 ✅
return 0;
}3. 指针 vs 引用
| 对比项 | 指针 | 引用 |
|---|---|---|
| 语法 | int* p = &a; *p | int& r = a; r |
| 可否为空 | 可以(nullptr) | 不能为空,必须初始化 |
| 可否改变指向 | 可以 | 不能改变,始终引用同一个变量 |
| 安全性 | 较低(空指针、野指针风险) | 较高 |
| 适用场景 | 动态内存、数组操作、可选参数 | 函数参数传递、操作符重载 |
TIP
能用引用的时候优先用引用,它比指针更安全、语法更简洁。 只有在需要"可以为空"或"动态内存管理"时才用指针。
五、空指针和 nullptr 🚫
指针如果没有任何指向,应该设置为 nullptr:
cpp
int* p = nullptr; // p 不指向任何地方
if (p != nullptr) {
cout << *p << endl; // 安全:确认 p 非空才使用
} else {
cout << "指针为空" << endl;
}DANGER
解引用空指针会导致程序崩溃!
cpp
int* p = nullptr;
cout << *p << endl; // ❌ 程序会崩溃!使用指针前务必检查是否为空。
六、小结 🎯
- 指针 是存储内存地址的变量,用
*声明和访问。 &取变量地址,*解引用(访问指针指向的值)。- 指针可以用来:修改函数外部的变量、操作数组、动态内存分配。
- 引用 是变量的别名,比指针更安全,能用引用时优先用引用。
new分配的内存必须用delete释放,否则造成内存泄漏。- 使用指针前检查是否为空(
nullptr)。
七、练习 📝
基本练习:定义一个
int变量x和一个指向它的指针p。通过p修改x的值并输出。指针交换:用指针实现一个函数
void swap(int* a, int* b),交换两个整数的值。指针遍历数组:定义一个数组
int arr[5] = {1, 2, 3, 4, 5},用指针遍历并输出所有元素。字符串长度:用指针实现一个函数
int myStrlen(const char* str),计算 C 风格字符串的长度(不使用strlen库函数)。引用实现:用引用而不是指针重写上面的交换函数。
动态数组:用
new创建一个动态数组,输入 n 个学生的成绩,计算平均分,最后用delete[]释放内存。挑战题:指针排序:用指针(而不是数组下标)实现冒泡排序。函数原型:
void bubbleSort(int* arr, int n)。