一、基本概念
指针是C++中极具代表性的特性,也是掌握C++高效内存操作的关键。简单来说,指针是一种存储变量内存地址的变量。我们平时使用的普通变量存储的是数据本身,而指针存储的是数据在内存中的“位置”(即地址),通过这个地址就能找到并操作对应的数据
二、指针的定义与初始化
指针的定义格式为“数据类型* 指针变量名;”,其中“*”是指针的标志,表明该变量是指针类型。需要注意的是,指针的“数据类型”必须与它指向的变量的数据类型一致,因为不同类型的变量在内存中占用的字节数不同,指针需要知道如何解析地址对应的内存。
指针初始化有两种常见方式:一是将已定义变量的地址赋值给指针(用“&”取地址运算符);二是使用“null ptr” 初始化空指针,避免野指针(指向不确定内存的指针,会导致程序崩溃)
// 指针定义与初始化示例
#include <iostream>
using namespace std;
int main() {
int num = 100; // 普通整型变量,存储数据100
int* p_num = # // 定义int型指针p_num,指向num的地址(&取地址)
char* p_char = nullptr;// 定义char型空指针,初始化为nullptr
// 输出变量地址和指针存储的地址(两者相同)
cout << "num的地址:" << &num << endl;
cout << "p_num存储的地址:" << p_num << endl;
return 0;
}
三、指针的核心操作:解引用
定义指针后,若要通过指针操作它指向的变量(比如读取或修改变量的值),就需要使用“解引用运算符*”。对指针使用“*”,就相当于直接操作指针指向的原变量。
// 指针解引用操作示例
#include <iostream>
using namespace std;
int main() {
int a = 10;
int* p_a = &a; // 指针p_a指向a
cout << "原变量a的值:" << a << endl; // 输出10
cout << "通过指针获取a的值:" << *p_a << endl; // 解引用,输出10
// 通过指针修改原变量的值
*p_a = 20;
cout << "修改后a的值:" << a << endl; // 输出20,原变量被修改
return 0;
}
“*p_a”表示“指针p_a指向的变量”,因此“*p_a = 20;”等价于“a = 20;”,直接修改了原变量a的值。这就是指针的核心作用——通过地址间接操作变量。
四、指针的常见使用场景:函数传参
C++中函数参数默认是“值传递”,即函数内部的参数是原变量的副本,修改副本不会影响原变量。而使用指针作为函数参数,可以实现“地址传递”,让函数直接操作主函数中的变量,这是指针最常用的场景之一。
// 指针作为函数参数示例:交换两个变量的值
#include <iostream>
using namespace std;
// 指针作为参数,接收两个int变量的地址
void swap(int* p1, int* p2) {
int temp = *p1; // 取p1指向的值,存入temp
*p1 = *p2; // 把p2指向的值赋给p1指向的变量
*p2 = temp; // 把temp的值赋给p2指向的变量
}
int main() {
int x = 5, y = 10;
cout << "交换前:x=" << x << ", y=" << y << endl; // 输出x=5, y=10
// 传入x和y的地址
swap(&x, &y);
cout << "交换后:x=" << x << ", y=" << y << endl; // 输出x=10, y=5
return 0;
}
若不使用指针,普通的“值传递”交换函数只能交换函数内部副本的值,主函数中的x和y不会变化。而这里传入x和y的地址,函数内部通过解引用操作直接修改了主函数中x和y的内存数据,从而实现真正的交换。
五、指针使用的注意事项
1.避免野指针:初始化是关键
野指针指的是指向不确定内存地址的指针(未初始化或指向的内存已被释放)。访问野指针会导致不可预测的后果(例如修改系统关键内存、程序崩溃)。
如何避免?
定义时立即初始化:要么指向一个已存在的变量(如 ),要么初始化为
int* p = #(C++11 引入的空指针常量,替代旧版的
nullptr)。指针指向的内存被释放后,及时置空:例如动态内存释放后,需将指针设为
NULL,避免后续误用。
nullptr
int* p; // 危险!未初始化的野指针,指向随机地址
*p = 10; // 操作野指针,程序可能崩溃
// 正确做法
int* p1 = nullptr; // 初始化为空指针
int num = 0;
int* p2 = # // 明确指向一个变量
2. 禁止对空指针解引用
表示指针不指向任何有效内存,对其解引用(
nullptr)会直接导致程序崩溃。因此使用指针前,务必检查其是否为
*nullptr。
nullptr
int* p = nullptr;
if (p != nullptr) { // 使用前先判断
*p = 10; // 只有指针有效时才操作
} else {
cout << "指针为空,无法操作" << endl;
}
3. 避免指针越界访问
指针可以通过加减运算移动(称为 “指针偏移”),但需严格控制范围,禁止访问超出数组或动态内存的边界,否则会破坏其他内存的数据。
int arr[3] = {1, 2, 3};
int* p = arr; // 指针指向数组首元素
// 合法访问:指向数组内的元素
cout << *(p + 0) << endl; // 1(arr[0])
cout << *(p + 1) << endl; // 2(arr[1])
// 越界访问:超出数组范围,危险!
cout << *(p + 3) << endl; // 访问未知内存,结果随机
4. 动态内存管理:配对使用
new 与
delete
new
delete
指针常用来管理动态内存(程序运行时手动分配的内存),使用 分配内存后,必须用
new 释放(数组用
delete),否则会导致内存泄漏(内存被占用却无法回收,长期运行会耗尽内存)。
delete[]
// 动态分配单个int变量
int* p = new int; // 分配内存,p指向该内存
*p = 100;
delete p; // 释放内存
p = nullptr; // 释放后及时置空,避免野指针
// 动态分配数组
int* arr = new int[5]; // 分配5个int的数组
arr[0] = 1;
delete[] arr; // 释放数组内存(必须用delete[])
arr = nullptr;
注意:
不可对同一块内存多次 (会导致内存损坏)。避免 “悬垂指针”:内存被
delete 后,指针仍指向原地址,需及时置为
delete。
nullptr
5. 指针类型必须与指向的数据类型匹配
指针的类型决定了它如何解析内存中的数据(例如 每次访问 4 字节,
int* 每次访问 1 字节)。若类型不匹配,会导致数据解析错误。
char*
int num = 0x12345678; // 假设int占4字节
char* p = (char*)# // 强制类型转换(不推荐)
// char* 每次访问1字节,解析结果与int*不同
cout << hex << (int)*p << endl; // 输出78(仅低1字节)
6.
6. 警惕指针的生命周期
指针的生命周期需与指向的变量 / 内存一致。例如,函数内的指针若指向局部变量,函数结束后局部变量被销毁,指针会变成野指针。
int* getPtr() {
int x = 10;
return &x; // 危险!返回局部变量的地址
}
int main() {
int* p = getPtr(); // p指向已销毁的内存(野指针)
*p = 20; // 操作无效内存,程序可能崩溃
return 0;
}
六、总结
指针是 C++ 中 “双刃剑”—— 它赋予程序直接操作内存的能力,提升效率的同时也带来了风险。掌握指针的核心是理解 “地址” 与 “解引用” 的逻辑,而安全使用的关键在于:初始化指针、检查空指针、控制访问范围、正确管理动态内存。只有严格遵循这些规则,才能充分发挥指针的优势,避免内存错误。


