指针
一、什么是指针?
指针是一个变量,它存储的是另一个变量的内存地址。
int a = 10; // 普通变量,存储值10
int *p = &a; // 指针变量,存储a的地址
// & 取地址运算符
// * 间接访问运算符(解引用)
内存示意图:
变量a: [ 10 ] 地址:0x0012FF44
↑
指针p: [0x0012FF44] 地址:0x0012FF48
什么是指针的解引用?
解引用(Dereferencing) 就是通过指针访问它所指向的变量的值。
int a = 10;
int *p = &a; // p存储a的地址
// *p 就是解引用操作
cout << *p; // 输出10,通过p找到了a的值
比喻理解: - 指针就像一张藏宝图,上面写着宝藏的位置 - 解引用就是拿着藏宝图去那个位置取出宝藏
指针解引用的本质
int a = 10;
int *p = &a;
// 以下三种写法等价
a = 20; // 直接修改
*p = 20; // 通过解引用修改
*(&a) = 20; // 取地址再解引用
// 图解:
// p = 0x1000 (存地址)
// *p = 10 (取地址0x1000里的值)
内存视角:
地址0x1000: [ 10 ] ← 变量a
↑
指针p: [0x1000] ← 存着a的地址
解引用*p → 取出10
二、指针的基本操作
int main() {
int a = 10;
int *p; // 声明指针(未初始化,野指针!)
p = &a; // 指针p指向a
cout << "a的值: " << a << endl; // 10
cout << "a的地址: " << &a << endl; // 0x0012FF44
cout << "p的值: " << p << endl; // 0x0012FF44(就是a的地址)
cout << "p指向的值: " << *p << endl; // 10(解引用)
*p = 20; // 通过指针修改a的值
cout << "修改后a: " << a << endl; // 20
return 0;
}
三、指针与一维数组
3.1 数组名的本质
数组名就是指向数组第一个元素的指针常量!
int arr[5] = {10, 20, 30, 40, 50};
// arr 等价于 &arr[0]
// arr + i 等价于 &arr[i]
// *(arr + i) 等价于 arr[i]
内存布局:
arr[0] arr[1] arr[2] arr[3] arr[4]
[ 10 ] [ 20 ] [ 30 ] [ 40 ] [ 50 ]
0x1000 0x1004 0x1008 0x100C 0x1010
↑
arr
3.2 数组元素的访问方式
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 或 int *p = &arr[0];
// 四种访问数组元素的方式
cout << arr[2]; // 1. 下标法:30
cout << *(arr + 2); // 2. 指针法(数组名):30
cout << p[2]; // 3. 下标法(指针):30
cout << *(p + 2); // 4. 指针法(指针变量):30
// 指针移动
p++; // 指向下一个元素(arr[1])
cout << *p; // 20
p += 2; // 指向arr[3]
cout << *p; // 40
3.3 指针加减的字节数
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
cout << "p: " << p << endl; // 0x1000
p++; // 不是加1个字节,而是加sizeof(int)个字节
cout << "p+1: " << p << endl; // 0x1004(int占4字节)
// 指针加减的规律:p + n = p + n * sizeof(类型)
四、指针与二维数组
4.1 二维数组的内存布局
二维数组在内存中是按行连续存储的!
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
内存布局(线性):
行0: [1] [2] [3] [4]
行1: [5] [6] [7] [8]
行2: [9] [10] [11] [12]
内存地址连续:1,2,3,4,5,6,7,8,9,10,11,12
4.2 二维数组的地址表示
arr // 二维数组名,指向第0行(类型:int (*)[4])
arr + i // 指向第i行
*(arr + i) // 第i行的首地址,等价于arr[i](类型:int*)
*(arr + i) + j // 第i行第j列的地址,等价于&arr[i][j]
*(*(arr + i) + j) // 第i行第j列的值,等价于arr[i][j]
图解:
arr → 行0: [1] [2] [3] [4]
↑
arr[0] (行首地址)
arr+1 → 行1: [5] [6] [7] [8]
↑
arr[1] (行首地址)
*(arr+1)+2 → 行1列2的地址
*(*(arr+1)+2) = 7
4.3 二维数组的访问方式
int arr[3][4] = {...};
// 1. 下标法(最直观)
cout << arr[1][2]; // 第1行第2列:7
// 2. 指针法(用数组名)
cout << *(*(arr + 1) + 2); // 7
// 3. 用行指针
int (*p)[4] = arr; // p是行指针,指向包含4个int的一维数组
cout << p[1][2]; // 7
cout << *(*(p + 1) + 2); // 7
// 4. 用普通指针(将二维数组看作一维)
int *q = &arr[0][0]; // 指向第一个元素
cout << *(q + 1*4 + 2); // 7(行*列数 + 列)
4.4 行指针(数组指针)
int arr[3][4] = {...};
// 声明一个指向"包含4个int的一维数组"的指针
int (*p)[4] = arr; // 注意括号不能少!
// p+1 跳过一行(4个int,16字节)
cout << "arr[0]地址: " << arr << endl; // 0x1000
cout << "arr[1]地址: " << arr + 1 << endl; // 0x1010(+16字节)
cout << "p+1地址: " << p + 1 << endl; // 0x1010(+16字节)
// 通过行指针访问
cout << p[0][0]; // 1
cout << p[1][2]; // 7
五、指针与地址的对应关系表
| 表达式 | 含义 | 类型 |
|---|---|---|
arr |
二维数组名,第0行地址 | int (*)[4] |
&arr[0][0] |
第0行第0列地址 | int* |
arr[0] |
第0行首地址 | int* |
*arr |
第0行首地址 | int* |
arr[0] + 2 |
第0行第2列地址 | int* |
*(arr + 1) |
第1行首地址 | int* |
&arr[1][2] |
第1行第2列地址 | int* |
*(arr + 1) + 2 |
第1行第2列地址 | int* |
*(*(arr + 1) + 2) |
第1行第2列的值 | int |
六、指针运算总结
int a[5] = {10, 20, 30, 40, 50};
int b[3][4] = {...};
// 指针 + 整数 = 指针
p + 1; // 指向下一个元素(加sizeof(类型)字节)
// 指针 - 指针 = 整数(元素个数)
p2 - p1; // 两个指针相差的元素个数
// 比较运算
p1 < p2; // 比较地址大小
// 指针可以自增自减
p++; p--; ++p; --p;
// 指针可以加下标
p[2]; // 等价于 *(p+2)
七、常见错误
// 错误1:未初始化指针
int *p;
*p = 10; // 野指针,危险!
// 错误2:类型不匹配
int arr[3][4];
int *p = arr; // 错误!arr是int (*)[4]类型
// 正确写法
int (*p)[4] = arr; // 行指针
int *q = &arr[0][0]; // 普通指针指向第一个元素
// 错误3:混淆指针和数组
int *p;
p[2] = 5; // p未指向有效内存
引用和指针的对比
指针 vs 引用 代码对比
int a = 10, b = 20;
// ===== 指针版本 =====
int *p = &a; // 初始化
*p = 30; // 修改a
p = &b; // 改指b(可以改指向)
*p = 40; // 修改b
if(p != nullptr) // 要检查空指针
cout << *p;
// ===== 引用版本 =====
int &r = a; // 必须初始化
r = 30; // 修改a
// r = b; // ❌ 这不是改指向!这是把b的值赋给a
// 不能改指向,r永远是a的别名
r = 40; // 继续修改a
// 不需要检查空,引用永远有效
引用常见用处
// 1. 修改实参
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int x = 10, y = 20;
swap(x, y); // x=20, y=10
核心区别图解
指针p: 引用r:
[0x1000] (别名)
↓ ↓
[ 10 ] [ 10 ]
变量a 变量a
指针可以改:p = &b → 指向b
引用不能改:r永远是a的别名
—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com