指针是C/C++语言中最强大也最容易让人困惑的特性之一。记得我刚开始学习指针时,光是理解什么时候该加星号(*)、什么时候不该加,就花了整整两周时间。直到有一天在调试段错误时突然开窍——原来指针的本质就是内存地址的"快递单号",而星号操作就是根据单号取包裹的过程。
在32位系统中,指针变量固定占用4字节内存空间;64位系统中则是8字节。这个空间里存储的不是实际数据,而是一个内存地址值。例如:
c复制int num = 42; // 实际存储整数的内存
int *p = # // p存储的是num的内存地址
这里&是取地址运算符,它获取变量num的内存地址;而*在变量声明时表示"这是一个指针变量"。这两个符号就像快递场景中的"填写寄件地址"和"标记为快递单"的操作。
在声明指针变量时,星号紧挨类型名书写,表示该变量将存储内存地址:
c复制char *str_ptr; // 字符指针
double *dbl_ptr; // 双精度浮点指针
此时星号是类型说明符的一部分,它告诉编译器:"这个变量要存储的是某种类型的内存地址,而不是该类型的值本身"。值得注意的是,星号与类型名或变量名之间的空格不影响语义:
c复制int* p; // 合法
int * p; // 合法但少见
int *p; // 最常见写法
当需要访问指针所指向的内存内容时,使用解引用操作:
c复制int value = *p; // 获取p指向的整数值
*p = 100; // 修改p指向的内存值
这就像根据快递单号取出包裹的过程。解引用时,星号必须直接放在指针变量名前,中间不能有空格。一个常见误区是:
c复制*p = 10; // 正确:修改指向的值
* p = 10; // 语法正确但不符合惯例
p* = 10; // 错误:编译不通过
指针运算常与数组操作结合,此时星号用法更复杂:
c复制int arr[5] = {1,2,3,4,5};
int *ptr = arr; // 等价于 &arr[0]
// 以下三种访问方式等价
arr[2] = 10;
*(arr + 2) = 10;
ptr[2] = 10;
这里*(arr + 2)的写法展示了指针算术运算和解引用的组合。中括号[]本质上就是通过指针运算实现的语法糖。
使用取地址运算符&时不需要星号:
c复制int num = 42;
int *p = # // 正确:获取num的地址
int *p = *num; // 错误:类型不匹配
当进行指针之间的赋值时,直接使用指针变量名:
c复制int *p1 = #
int *p2 = p1; // 正确:地址复制
int *p2 = *p1; // 错误:复制的是p1指向的值
在函数参数中声明指针类型时:
c复制void func(int *param) {
// 函数体内使用*param访问值
}
调用时直接传入指针变量或地址:
c复制int val = 10;
func(&val); // 传入地址
func(p); // 传入指针变量
当处理指针的指针时,星号数量对应指针层级:
c复制int num = 42;
int *p = # // 一级指针
int **pp = &p; // 二级指针
// 访问原始值
printf("%d", **pp); // 需要两个星号
多级指针常见于:
使用typedef时,星号属于类型名的一部分:
c复制typedef int* IntPtr;
IntPtr p1, p2; // p1和p2都是指针
// 对比直接声明
int *p3, p4; // 只有p3是指针
这是很多初学者容易混淆的地方。建议在团队项目中谨慎使用指针typedef,以免降低代码可读性。
星号操作符的优先级会影响表达式含义:
c复制int arr[3] = {1,2,3};
int *p = arr;
*p++; // 等价于*(p++):先取*p,然后p++
(*p)++; // 先取*p,然后对*p的值加1
*++p; // 等价于*(++p):先p++,再取*p
访问结构体指针成员时,->运算符自动处理解引用:
c复制typedef struct {
int x;
int y;
} Point;
Point pt = {10,20};
Point *ptr = &pt;
// 以下两种访问方式等价
(*ptr).x = 30; // 显式解引用
ptr->y = 40; // 使用箭头运算符
const关键字与星号的组合会产生不同效果:
c复制int num = 42;
const int *p1 = # // 指向常量的指针
int *const p2 = # // 指针本身是常量
const int *const p3 = # // 两者都是常量
记忆技巧:const在星号左侧修饰指向的值,右侧修饰指针本身。
函数指针的声明和使用有特殊语法:
c复制int add(int a, int b) { return a + b; }
// 声明函数指针
int (*func_ptr)(int, int) = &add;
// 调用
int result = (*func_ptr)(3, 4); // 传统写法
int result = func_ptr(3, 4); // C++允许的简写
对无效指针解引用会导致未定义行为:
c复制int *p = NULL; // 空指针
*p = 42; // 运行时错误:段错误
int *q; // 未初始化指针
*q = 10; // 危险:野指针
安全实践:
在C++中,智能指针自动管理内存生命周期:
cpp复制#include <memory>
std::unique_ptr<int> up(new int(42));
std::shared_ptr<double> sp = std::make_shared<double>(3.14);
// 访问方式与裸指针一致
*up = 100;
*sp = 2.718;
智能指针虽然仍使用星号解引用,但避免了手动内存管理的诸多陷阱。
指针相关的典型bug包括:
调试建议:
printf或调试器观察指针值指针操作直接影响程序性能:
优化技巧:
指针在不同平台的表现差异:
可移植代码应避免:
现代C++推荐减少裸指针使用:
当确实需要使用指针时,遵循以下原则: