1. C++指针的本质与基础操作
指针是C++语言中最强大也最危险的工具之一。理解指针的本质需要从计算机底层内存管理机制说起。每个运行中的程序都拥有自己的内存空间,这个空间被划分为连续的字节单元,每个字节都有唯一的地址标识。
指针变量本质上是一个存储内存地址的变量。与普通变量存储具体数值不同,指针存储的是其他变量在内存中的位置信息。这种间接访问机制赋予了C++直接操作内存的能力,这也是它区别于Java等高级语言的重要特征。
1.1 指针的声明与初始化
指针声明遵循特定语法规则:
cpp复制数据类型 *指针变量名;
这里的数据类型决定了指针的"步长"——即指针算术运算时的基本单位。例如:
cpp复制int *ip; // 整型指针
double *dp; // 双精度浮点指针
char *cp; // 字符指针
指针初始化必须明确指向合法的内存地址。常见初始化方式包括:
cpp复制int var = 42;
int *ptr = &var; // 取地址初始化
int *ptr2 = new int(100); // 动态内存分配
警告:未初始化的指针称为"野指针",指向随机内存地址,操作这类指针会导致不可预知的行为,是程序崩溃的常见原因。
1.2 指针的基本操作
指针有两个核心运算符:
&取址运算符:获取变量的内存地址*解引用运算符:访问指针指向的内存内容
典型操作示例:
cpp复制int main() {
int value = 10;
int *p = &value; // p存储value的地址
cout << "变量地址: " << p << endl;
cout << "变量值: " << *p << endl; // 通过指针访问值
*p = 20; // 通过指针修改值
cout << "修改后的值: " << value << endl;
return 0;
}
输出结果可能类似:
code复制变量地址: 0x7ffee3a5a8dc
变量值: 10
修改后的值: 20
1.3 指针与const限定符
const与指针结合会产生三种不同语义:
cpp复制const int *p1; // 指向常量的指针(指针可变,内容不可变)
int const *p2; // 同上,语法等价
int * const p3; // 常量指针(指针不可变,内容可变)
const int * const p4;// 指向常量的常量指针(两者都不可变)
理解这些区别对编写安全的指针代码至关重要。例如:
cpp复制int a = 1, b = 2;
const int *p = &a;
// *p = 3; // 错误:不能通过p修改a
p = &b; // 正确:可以改变p的指向
int * const q = &a;
*q = 3; // 正确:可以修改a的值
// q = &b; // 错误:不能改变q的指向
2. 指针的高级应用场景
2.1 指针与数组的关系
数组名在大多数情况下会退化为指向首元素的指针。这种特性使得指针可以像数组一样使用下标访问元素:
cpp复制int arr[5] = {1,2,3,4,5};
int *p = arr; // 等价于 int *p = &arr[0]
cout << p[2] << endl; // 输出3
cout << *(p+2) << endl; // 等价写法
指针算术运算遵循特定规则:
p + n:前进n个元素大小的距离p - n:后退n个元素大小的距离p++:移动到下一个元素p--:移动到上一个元素
示例展示指针遍历数组:
cpp复制for(int *iter = arr; iter != arr+5; ++iter) {
cout << *iter << " ";
}
2.2 动态内存管理
C++中使用new和delete运算符进行动态内存分配:
cpp复制int *p = new int; // 分配单个int
*p = 10;
delete p; // 释放内存
int *arr = new int[10]; // 分配数组
delete[] arr; // 释放数组
重要提示:忘记释放动态分配的内存会导致内存泄漏。现代C++推荐使用智能指针替代原始指针管理动态内存。
2.3 多级指针与指针数组
指针可以指向其他指针,形成多级间接访问:
cpp复制int val = 100;
int *p = &val;
int **pp = &p; // 二级指针
cout << **pp << endl; // 输出100
指针数组是存储指针的数组,常用于字符串处理:
cpp复制const char *names[] = {"Alice", "Bob", "Charlie"};
for(int i=0; i<3; i++) {
cout << names[i] << endl;
}
3. 函数指针与回调机制
3.1 函数指针基础
函数指针允许将函数作为参数传递,是实现回调机制的基础。声明语法:
cpp复制返回值类型 (*指针名)(参数列表);
示例:
cpp复制int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main() {
int (*funcPtr)(int, int); // 声明函数指针
funcPtr = add;
cout << funcPtr(3,5) << endl; // 输出8
funcPtr = sub;
cout << funcPtr(5,3) << endl; // 输出2
return 0;
}
3.2 回调函数实践
函数指针常用于实现策略模式:
cpp复制void processArray(int *arr, int size, int (*processor)(int)) {
for(int i=0; i<size; i++) {
arr[i] = processor(arr[i]);
}
}
int square(int x) { return x*x; }
int increment(int x) { return x+1; }
int main() {
int data[] = {1,2,3,4,5};
processArray(data, 5, square);
// data变为 [1,4,9,16,25]
processArray(data, 5, increment);
// data变为 [2,5,10,17,26]
return 0;
}
3.3 现代C++中的替代方案
虽然函数指针功能强大,但现代C++提供了更安全的替代方案:
std::function:类型安全的函数包装器- Lambda表达式:匿名函数对象
- 函数对象(Functor):重载了
operator()的类
例如使用Lambda表达式:
cpp复制#include <functional>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums {1,2,3,4,5};
std::function<int(int)> processor = [](int x) {
return x * x;
};
std::transform(nums.begin(), nums.end(), nums.begin(), processor);
return 0;
}
4. 智能指针:现代C++的内存管理方案
4.1 原始指针的问题
原始指针的主要风险包括:
- 内存泄漏:忘记释放分配的内存
- 悬垂指针:访问已释放的内存
- 双重释放:多次释放同一块内存
- 所有权不明确:难以确定谁负责释放内存
4.2 标准智能指针类型
C++11引入了三种智能指针:
unique_ptr:独占所有权,不可复制shared_ptr:共享所有权,引用计数weak_ptr:不增加引用计数的观察者
4.2.1 unique_ptr示例
cpp复制#include <memory>
void process() {
std::unique_ptr<int> p(new int(10));
// 自动释放内存
// 不能复制,只能移动
std::unique_ptr<int> p2 = std::move(p);
}
4.2.2 shared_ptr示例
cpp复制void sharedExample() {
std::shared_ptr<int> p1(new int(20));
{
std::shared_ptr<int> p2 = p1; // 引用计数+1
*p2 = 30;
} // p2析构,引用计数-1
// p1仍然有效
}
4.2.3 weak_ptr示例
cpp复制void observe() {
std::shared_ptr<int> shared = std::make_shared<int>(100);
std::weak_ptr<int> weak = shared;
if(
