一、引言:为什么指针是 C++ 的 "灵魂"
在前面四篇文章中,我们已经掌握了 C++ 的基础语法、数据类型、流程控制与函数模块化。我们能够写出结构清晰、功能完整的程序,但这一切都停留在变量层面------ 我们只知道 "变量可以存数据",却不知道数据在内存中究竟如何存放、如何访问、如何传递。
而指针,正是打开计算机底层世界的钥匙。
指针是 C/C++ 最强大、最核心、最具魅力,也最让初学者头疼的概念。可以说: 不懂指针,就不算真正掌握 C++。 不会指针,就无法理解内存、数组、函数、对象、STL、动态内存......
指针的作用极其强大:
- 直接访问内存地址,操控硬件底层;
- 高效传递数据,避免拷贝,提高程序性能;
- 实现动态内存管理(new/delete);
- 构建复杂数据结构:链表、树、图;
- 在函数中修改变量本身(而非副本);
- 操作数组、字符串、二进制数据;
- 实现高级特性:多态、回调、泛型。
本篇文章将从零开始,由浅入深,彻底讲透 C++ 指针与引用:
- 内存地址与指针的本质;
- 指针的定义、赋值、解引用;
- 空指针、野指针、迷途指针;
- 指针与数组、指针与字符串;
- 指针与函数:地址传递、指针参数;
- 引用的本质、用法、与指针的区别;
- 指针的 const 用法;
- 指针常见错误、安全规范、实战案例。
本篇内容偏底层、偏难,但极其重要。请静下心阅读、动手练习,一旦掌握指针,你的 C++ 水平将直接跃升到新的高度。
二、内存地址:一切指针的基础
计算机的内存可以看作由无数个连续小房间组成的巨大仓库 ,每个小房间是 1 字节(8 位),每个房间都有一个唯一编号,这个编号就是内存地址。
在 C++ 中:
- 变量存储在内存中;
- 每个变量都有值 和地址;
- 地址用十六进制表示,如:0x7ffeefb2c8ac。
我们可以用 & 符号获取变量地址:
cpp
运行
int a = 10;
cout << &a << endl; // 输出 a 的地址
指针 = 专门存放内存地址的变量。 指针变量存储的不是普通数值,而是另一个变量的地址。
三、指针的定义与基本使用
(一)指针定义格式
cpp
运行
类型 *指针变量名;
含义:
- 类型:指针指向的变量的数据类型;
- *:表示这是一个指针变量;
- 指针变量名:存储地址。
示例:
cpp
运行
int a = 10;
int *p = &a; // p 指向 a 的地址
(二)解引用:*p 访问指针指向的值
*p 表示:取指针 p 所指向地址里的数据。
cpp
运行
int a = 10;
int *p = &a;
cout << p; // 输出 a 的地址
cout << *p; // 输出 a 的值:10
(三)通过指针修改变量值
cpp
运行
*p = 20; // 相当于 a = 20;
指针让我们通过地址直接修改变量本身。
四、指针的四个核心概念
1. 指针本身是变量,占内存空间
32 位系统:指针占 4 字节 64 位系统:指针占 8 字节
cpp
运行
cout << sizeof(p); // 输出 8(64位)
2. 指针必须指向有效地址
未初始化的指针非常危险!
3. 空指针 nullptr(C++11 推荐)
表示指针 "不指向任何地方"。
cpp
运行
int *p = nullptr;
访问空指针会直接崩溃!
4. 野指针(最危险)
指向未知内存地址的指针。 产生原因:
- 指针未初始化;
- 指针指向的变量已销毁;
- 指针被错误赋值。
野指针是程序崩溃的第一大元凶!
五、指针与数组
数组名本质上是指向数组首元素的常量指针。
cpp
运行
int arr[5] = {10,20,30,40,50};
int *p = arr; // p 指向 arr[0]
数组访问两种方式
- 下标法:
arr[i] - 指针法:
*(p+i)
cpp
运行
cout << *(arr+2); // 输出 arr[2] = 30
这就是为什么数组下标从 0 开始:因为偏移量从 0 计算。
六、指针与字符串
C 风格字符串本质是char 指针。
cpp
运行
const char *str = "Hello";
cout << str; // 输出 Hello
cout << *str; // 输出 H
字符串以 \0 结尾。
七、指针与函数:地址传递(重点)
C++ 默认函数参数是值传递:拷贝一份,不影响原变量。
如果希望在函数内部修改外部变量 ,必须使用指针传递(地址传递)。
示例:交换两个数(经典)
cpp
运行
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swap(&x, &y); // 传地址
cout << x << " " << y; // 20 10
return 0;
}
只有传地址,才能修改原变量! 这是指针最常用、最重要的功能之一。
八、引用:C++ 对指针的安全封装
(一)什么是引用
引用是变量的别名。 一旦绑定,不能再改变指向。
定义格式:
cpp
运行
类型 &别名 = 原变量;
示例:
cpp
运行
int a = 10;
int &b = a; // b 是 a 的别名
b = 20; // a 也变成 20
(二)引用的特点
- 必须初始化;
- 不能为空;
- 不能重新绑定到其他变量;
- 底层是指针,使用更安全、更简单。
(三)引用做函数参数(推荐)
引用可以实现地址传递的效果,但语法更简洁。
cpp
运行
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x=10,y=20;
swap(x,y);
}
无需传地址,代码更清晰! 现代 C++ 优先使用引用,少用指针。
九、指针 vs 引用:最重要对比
表格
| 特性 | 指针 | 引用 |
|---|---|---|
| 能否为空 | 能(nullptr) | 不能 |
| 能否重新指向 | 能 | 不能 |
| 语法 | 需要 * | 直接使用 |
| 底层实现 | 地址变量 | 编译器优化的指针 |
| 安全性 | 较低(易崩溃) | 高 |
| 效率 | 高 | 同样高 |
结论:能用引用,就不用指针。 需要动态分配、空值、复杂操作时,才用指针。
十、const 与指针(三种情况)
1. 指向常量的指针
cpp
运行
const int *p = &a;
// *p 不能改,p 可以改
2. 常量指针
cpp
运行
int *const p = &a;
// p 不能改,*p 可以改
3. 指向常量的常量指针
cpp
运行
const int *const p = &a;
// 都不能改
口诀: const 右边的东西不能改。
十一、多级指针(指针的指针)
指针可以存储另一个指针的地址。
cpp
运行
int a = 10;
int *p = &a;
int **pp = &p; // 二级指针
cout << **pp; // 输出 10
多级指针常用于:
- 二维数组
- 链表、树
- 函数参数修改指针
十二、指针常见错误(崩溃重灾区)
1. 操作空指针
cpp
运行
int *p = nullptr;
*p = 10; // 崩溃!
2. 野指针
cpp
运行
int *p; // 未初始化
*p = 10; // 崩溃!
3. 指针越界
cpp
运行
int arr[5];
int *p = arr+10;
*p = 10; // 越界!
4. 重复释放、释放栈内存
cpp
运行
int *p = new int(10);
delete p;
delete p; // 崩溃!
5. 混淆 * 和 &
cpp
运行
int a=10;
int *p = a; // 错误!应该是 &a
十三、指针安全使用规范
- 指针初始化优先使用 nullptr;
- 每次使用指针前判断是否为空;
- 避免野指针;
- 动态内存必须配对:new/delete;
- 尽量用引用代替指针;
- 少用多级指针;
- 数组不要越界;
- 现代 C++ 尽量使用智能指针(后续讲解)。
十四、经典实战案例
案例 1:指针实现数组求和
cpp
运行
int sum(int *arr, int len) {
int s = 0;
for (int i=0; i<len; i++) {
s += *(arr+i);
}
return s;
}
案例 2:引用实现成绩修改
cpp
运行
void setScore(int &score) {
score = 90;
}
案例 3:指针实现字符串长度
cpp
运行
int strLen(const char *s) {
int len = 0;
while (*s != '\0') {
len++;
s++;
}
return len;
}
十五、本章总结
本篇文章彻底、系统、深入讲解了 C++指针与引用,这是 C++ 最核心、最底层、最关键的知识点:
- 内存地址是指针的基础;
- 指针是存储地址的变量;
- *p 解引用访问地址中的值;
- 空指针、野指针是崩溃主要原因;
- 指针与数组、字符串本质相同;
- 指针传递可修改外部变量;
- 引用是安全别名,现代 C++ 优先使用;
- const 指针、多级指针、安全规范。
掌握指针,你就真正理解了 C++ 的内存模型。 掌握指针,你才能真正进入高级 C++ 开发。
指针是 C++ 从 "入门" 走向 "精通" 的必经之路。 学完本篇,你已经具备学习面向对象、动态内存、STL、数据结构的基础。
下一篇预告
第 6 篇《C++ 面向对象:类与对象、封装、构造与析构》(4000 字以上) 我们将正式进入 C++ 最核心的编程范式:面向对象编程(OOP)。