C++ 指针
1. 什么是指针
指针是一个变量,它存储的是内存地址,而不是普通的数据值。通过地址,我们可以间接访问另一块内存中存储的数据。
类比
- 普通变量:好比一间房子,里面直接住着人(数据)。
- 指针变量:好比一张纸条,上面写着房子的地址(内存地址),通过地址可以找到那间房子。
为什么需要指针?
- 实现函数内修改外部变量(引用传递)。
- 动态分配内存(程序运行时申请内存)。
- 高效处理数组和字符串。
- 构建链表、树等复杂数据结构。
2. 指针的声明与初始化
声明语法
数据类型* 指针变量名;
数据类型 表示指针指向的变量的类型,* 表示这是一个指针。
int* p; // 指向 int 的指针
double* q; // 指向 double 的指针
char* r; // 指向 char 的指针
注意:
int* p和int *p都是合法的,但建议将*靠近类型名(强调类型的一部分),或者靠近变量名(强调变量是指针)。个人风格保持一致即可。
初始化
指针必须指向一个有效的内存地址才能安全使用。
int a = 10;
int* p = &a; // p 指向 a 的地址,& 是取地址运算符
int* q = nullptr; // C++11 引入的空指针常量,推荐使用
// int* r = NULL; // C 风格的空指针,不推荐在 C++ 中使用
&a:取变量a的内存地址。nullptr:表示指针不指向任何有效对象。
未初始化的指针(野指针)是非常危险的,它们指向不确定的地址,解引用会导致未定义行为。
3. 解引用:访问指针指向的数据
使用 * 运算符(解引用运算符)来访问指针所指向的内存中的值。
#include <iostream>
int main() {
int a = 42;
int* p = &a; // p 指向 a
std::cout << *p << std::endl; // 输出 42
*p = 100; // 通过指针修改 a 的值
std::cout << a << std::endl; // 输出 100
return 0;
}
图解:
a (地址 0x1000) -> [42]
p (地址 0x2000) -> [0x1000]
*p 就是找到地址 0x1000 中的值 42
4. 指针的运算
指针支持几种算术运算,但仅限于数组或连续内存区域中。
指针加减整数
当指针加 n 时,实际地址增加 n * sizeof(类型) 字节。
int arr[3] = {10, 20, 30};
int* p = arr; // 指向 arr[0]
std::cout << *p << std::endl; // 10
p++; // 现在指向 arr[1]
std::cout << *p << std::endl; // 20
p += 1; // 指向 arr[2]
std::cout << *p << std::endl; // 30
指针相减
两个同类型指针相减的结果是它们之间元素的个数(偏移量),不是字节数。
int* p1 = &arr[0];
int* p2 = &arr[2];
std::cout << p2 - p1 << std::endl; // 输出 2
指针比较
可以使用关系运算符(==, !=, <, > 等)比较同一数组内的指针。
if (p1 < p2) { /* ... */ }
5. 指针与数组
数组名在很多情况下会隐式转换为指向其第一个元素的指针。
int arr[5] = {1,2,3,4,5};
int* p = arr; // 等价于 int* p = &arr[0];
使用指针遍历数组
for (int* p = arr; p != arr + 5; ++p) {
std::cout << *p << " ";
}
数组名与指针的区别
sizeof(arr)返回整个数组的字节数,sizeof(p)返回指针本身的字节数(通常 8 或 4)。arr是常量地址,不能修改(如arr = someOtherPtr;非法),而指针可以重新赋值。
指针数组与数组指针
- 指针数组:数组的每个元素都是指针。
int a=1, b=2, c=3; int* arrPtr[3] = {&a, &b, &c}; - 数组指针:指向整个数组的指针。
int arr[3] = {1,2,3}; int (*pArr)[3] = &arr; // pArr 指向具有 3 个 int 的数组
6. 指针与函数
6.1 指针作为函数参数
使用指针可以在函数内部修改外部变量的值(模拟引用传递)。
void swap(int* x, int* y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 5, b = 10;
swap(&a, &b);
// a=10, b=5
}
6.2 函数指针
函数指针指向函数代码的入口地址,用于回调、策略模式等。
#include <iostream>
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int compute(int x, int y, int (*op)(int, int)) {
return op(x, y);
}
int main() {
std::cout << compute(3, 4, add) << std::endl; // 7
std::cout << compute(3, 4, multiply) << std::endl; // 12
}
6.3 指针作为返回值
函数可以返回指针,但不能返回局部变量的地址(局部变量在函数返回后销毁)。
int* badReturn() {
int local = 10;
return &local; // 危险!悬垂指针
}
int* goodReturn() {
static int staticVar = 20;
return &staticVar; // 合法,static 变量生命周期贯穿程序
}
int* dynamicReturn() {
int* p = new int(30);
return p; // 合法,但调用者需要负责 delete
}
7. 动态内存分配
C++ 中使用 new 和 delete 在堆(heap)上动态分配和释放内存。
分配单个对象
int* p = new int; // 分配一个未初始化的 int
*p = 42;
delete p; // 释放内存,避免内存泄漏
int* q = new int(100); // 分配并初始化为 100
delete q;
分配数组
int* arr = new int[10]; // 分配 10 个 int 的数组
for (int i = 0; i < 10; ++i) arr[i] = i;
delete[] arr; // 注意使用 delete[]
注意事项
- 每次
new都要配对delete,否则造成内存泄漏。 - 对
nullptr使用delete是安全的(什么都不做)。 - 已释放的指针应置为
nullptr,避免悬垂指针。
delete p;
p = nullptr;
8. 指针与 const
const 可以修饰指针指向的内容,也可以修饰指针本身,或者两者都修饰。
8.1 指向常量的指针(底层 const)
const int* p; // 或者 int const* p;
int a = 10, b = 20;
p = &a; // 合法
// *p = 30; // 错误:不能通过 p 修改指向的值
a = 30; // 合法:a 本身不是常量
p = &b; // 合法:p 可以指向其他变量
8.2 常量指针(顶层 const)
int* const p = &a; // p 本身是常量,必须初始化
*p = 50; // 合法:可以修改指向的值
// p = &b; // 错误:p 不能指向其他变量
8.3 指向常量的常量指针
const int* const p = &a;
// 既不能修改 *p,也不能修改 p
应用场景
- 函数参数中使用
const int*表示函数不会修改传入的值。 - 使用
int* const表示指针地址固定(常用于嵌入式或硬件映射)。
9. 多级指针
指向指针的指针,用于间接多级访问。
int a = 10;
int* p = &a; // 一级指针
int** pp = &p; // 二级指针
std::cout << **pp << std::endl; // 输出 10
常见用途:
- 函数中需要修改指针本身(如动态分配二维数组)。
- 命令行参数
char** argv。
void allocateMemory(int** ptr, int size) {
*ptr = new int[size];
}
int main() {
int* arr = nullptr;
allocateMemory(&arr, 100);
// 使用 arr...
delete[] arr;
}
10. 智能指针简介(C++11起)
为避免手动 new/delete 带来的内存泄漏和悬垂指针,C++11 引入了智能指针(定义在 <memory> 中)。
std::unique_ptr
独占所有权的指针,不能拷贝,只能移动。
#include <memory>
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1; // 错误:不能拷贝
std::unique_ptr<int> p2 = std::move(p1); // 转移所有权
// p1 现在为空
std::shared_ptr
共享所有权,使用引用计数。当最后一个 shared_ptr 销毁时释放内存。
std::shared_ptr<int> sp1 = std::make_shared<int>(100);
std::shared_ptr<int> sp2 = sp1; // 引用计数变为 2
// 自动管理内存,无需 delete
std::weak_ptr
配合 shared_ptr 使用,解决循环引用问题。
建议:现代 C++ 中尽量避免裸指针管理动态内存,优先使用智能指针。
11. 常见陷阱与最佳实践
陷阱
- 未初始化指针(野指针):解引用未初始化的指针导致崩溃。
- 内存泄漏:忘记
delete或delete[]。 - 悬垂指针:释放内存后继续使用原指针。
- 数组越界:指针运算超出合法范围。
- 返回局部变量地址。
最佳实践
- 总是初始化指针,没有合法地址就用
nullptr。 - 使用
delete后立即将指针设为nullptr。 - 优先使用引用代替指针,如果不需要为空或重定向。
- 动态数组优先使用
std::vector而非原始指针。 - 单一所有权用
unique_ptr,共享所有权用shared_ptr。 - 避免不必要的指针,能用对象就用对象。
12. 总结
| 概念 | 说明 |
|---|---|
| 指针的本质 | 存储内存地址的变量 |
& 运算符 |
取地址 |
* 运算符 |
解引用,访问指向的值 |
| 指针与数组 | 数组名退化为指向首元素的指针 |
new / delete |
动态内存管理 |
const 指针 |
控制指针本身或指向内容的常量性 |
| 智能指针 | 自动管理内存,unique_ptr, shared_ptr 等 |
指针是 C++ 强大的工具,也是最容易出错的领域。掌握指针需要大量练习,理解内存模型是关键。现代 C++ 鼓励使用智能指针和容器来减少显式指针操作,但在底层编程、性能优化和与 C 代码交互时,指针依然不可或缺。
(页面内容为AI总结,如有疏漏请指正)

浙公网安备 33010602011771号