1. 指针的本质与内存寻址
指针是C++中最强大也最危险的工具之一。理解指针的关键在于明白它本质上就是一个存储内存地址的变量。当我们声明一个指针变量时,系统会分配一块内存来存储另一个变量的地址,而不是直接存储数据本身。
1.1 指针的基本语法
指针声明使用星号(*)符号:
cpp复制int* ptr; // 声明一个指向int类型的指针
指针的赋值和取值操作:
cpp复制int num = 42;
int* ptr = # // &取地址运算符
cout << *ptr; // *解引用运算符,输出42
注意:声明指针时,星号(*)靠近类型还是变量名是风格问题。
int* ptr和int *ptr都合法,但前者更强调"指向int的指针"这一类型。
1.2 指针与内存布局
理解指针必须结合内存布局来看。假设有以下代码:
cpp复制int a = 10;
int b = 20;
int* p = &a;
内存中的表示可能如下:
code复制地址 内容
0x1000 [10] // 变量a
0x1004 [20] // 变量b
0x1008 [0x1000] // 指针p
指针运算的特殊性:
cpp复制p++; // 移动sizeof(int)字节,即0x1004
这是因为指针运算会根据指向类型的大小自动调整步长。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动态内存管理
C++中动态内存管理主要通过new和delete运算符实现,它们直接与操作系统交互,在堆(heap)上分配和释放内存。
2.1 基本使用模式
单个对象的动态分配:
cpp复制int* ptr = new int; // 分配一个int大小的内存
*ptr = 42; // 存储值
delete ptr; // 释放内存
数组的动态分配:
cpp复制int size = 5;
int* arr = new int[size]; // 分配数组
for(int i=0; i<size; i++) {
arr[i] = i*10;
}
delete[] arr; // 注意使用delete[]
2.2 常见内存问题
- 内存泄漏:
cpp复制void leak() {
int* ptr = new int[100];
// 忘记delete[]
} // 内存永久丢失
- 悬垂指针:
cpp复制int* ptr = new int;
delete ptr;
*ptr = 10; // 危险!ptr已成为悬垂指针
- 双重释放:
cpp复制int* ptr = new int;
delete ptr;
delete ptr; // 未定义行为
经验:在delete后立即将指针置为nullptr是个好习惯:
cpp复制delete ptr; ptr = nullptr;
3. 智能指针(C++11起)
智能指针是管理动态内存的现代方式,能自动处理资源释放。
3.1 unique_ptr
独占所有权的智能指针:
cpp复制#include <memory>
std::unique_ptr<int> uptr(new int(42));
// 自动释放内存
特性:
- 不可复制(保证唯一所有权)
- 可移动(转移所有权)
cpp复制auto uptr2 = std::move(uptr); // 所有权转移
3.2 shared_ptr
共享所有权的智能指针:
cpp复制std::shared_ptr<int> sptr1 = std::make_shared<int>(100);
auto sptr2 = sptr1; // 引用计数+1
实现原理:
- 使用引用计数
- 计数为0时自动释放资源
3.3 weak_ptr
解决shared_ptr循环引用问题:
cpp复制struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr避免循环引用
};
4. 指针高级应用
4.1 多级指针
指向指针的指针:
cpp复制int val = 10;
int* ptr = &val;
int** pptr = &ptr;
cout << **pptr; // 输出10
应用场景:
- 动态二维数组
- 函数参数中修改指针本身
4.2 函数指针
将函数作为参数传递:
cpp复制void process(int(*func)(int), int x) {
cout << func(x);
}
int square(int x) { return x*x; }
process(square, 5); // 输出25
现代C++更推荐使用std::function和lambda表达式。
4.3 指针与数组的关系
数组名在多数情况下会退化为指针:
cpp复制int arr[5] = {1,2,3,4,5};
int* ptr = arr; // 等价于 &arr[0]
但二者并非完全等同:
cpp复制sizeof(arr); // 返回数组总字节数
sizeof(ptr); // 返回指针大小
5. 实战技巧与调试
5.1 调试指针问题
- 使用地址消毒剂(AddressSanitizer):
bash复制g++ -fsanitize=address -g program.cpp
- Valgrind工具:
bash复制valgrind --leak-check=full ./a.out
5.2 性能考量
- 指针解引用比直接访问慢
- 连续内存访问(如数组)比随机访问(如链表)快
- 智能指针有少量性能开销
5.3 现代C++最佳实践
- 优先使用智能指针而非裸指针
- 使用std::vector等容器而非原始数组
- 避免new/delete,使用make_shared/make_unique
cpp复制auto ptr = std::make_unique<int>(42);
6. 常见面试问题解析
-
指针和引用的区别?
- 指针可以rebind和nullptr,引用不能
- 引用更安全但灵活性较低
-
malloc/free和new/delete的区别?
- new/delete会调用构造/析构函数
- new是类型安全的
- new失败抛出异常,malloc返回NULL
-
什么是智能指针?有哪些类型?
- unique_ptr:独占所有权
- shared_ptr:共享所有权
- weak_ptr:解决循环引用
-
如何避免内存泄漏?
- 使用RAII原则
- 优先使用智能指针
- 每个new对应一个delete
-
什么是悬垂指针?如何避免?
- 指向已释放内存的指针
- delete后置为nullptr
- 使用智能指针
指针和动态内存是C++的核心概念,也是面试中的高频考点。理解这些概念不仅需要通过理论学习,更需要大量的实践。我在实际项目中发现,90%的指针相关问题都可以通过遵循以下原则避免:
- 尽量使用容器而非原始指针
- 必须使用指针时优先选择智能指针
- 如果必须使用裸指针,确保每个new都有对应的delete
- 在团队项目中统一内存管理策略
