指针与数组
较难3指针与数组:像翻书一样操作数组里的数据
你有没有想过,数组名其实就是一个“书签”,标记着数组的第一个元素的位置?指针就是可以移动的书签,帮你翻到数组的任意一页。用指针操作数组,比用下标更灵活,也更接近计算机底层的工作方式。
数组名就是第一个元素的地址
在C++中,数组名本质上是一个常量指针,它存储的是数组第一个元素的内存地址。你可以把它想象成书的目录,写着“第1页在位置X”,而 arr[0] 就是那个页面的内容。
下面这段代码验证了这一点:
#include <iostream>
using namespace std;
int main() {
int arr[5] = {10, 20, 30, 40, 50}; // 定义一个包含5个整数的数组
// 数组名 arr 就是 &arr[0]
cout << "arr 的值:" << arr << endl; // 输出地址,比如 0x61fdf0
cout << "&arr[0] 的值:" << &arr[0] << endl; // 输出同样的地址
cout << "arr[0] 的值:" << *arr << endl; // 用 * 解引用得到第一个元素 10
// 用指针遍历数组
int* p = arr; // 指针p指向数组开头,就像把书签夹在第1页
for (int i = 0; i < 5; i++) {
cout << "arr[" << i << "] = " << *(p + i) << endl; // 移动书签翻到第i页
// 也可以写成 p[i] ,和数组下标一样
}
// 也可以移动指针(像翻书一样一页一页翻)
p = arr; // 重新把书签夹回开头
for (int i = 0; i < 5; i++) {
cout << *p << " "; // 读当前页的内容
p++; // 书签翻到下一页
}
cout << endl;
return 0;
}
指针加减:翻页而不是翻字节
当你对指针做加法时,比如 p + 1,它并不是在地址上简单地加1个字节,而是跳到下一个数组元素。为什么?因为指针的类型告诉计算机每个元素占多少字节。
- 如果
p是int*,那么p + 1实际地址增加sizeof(int)= 4字节。 - 如果
p是double*,那么p + 1实际地址增加sizeof(double)= 8字节。 - 如果
p是char*,那么p + 1实际地址增加sizeof(char)= 1字节。
想象你有一本漫画书,每页一张图。用手指着第1页,然后移动手指到第2页,你跳过了整页,而不是只移动1毫米。指针加减元素也是同样的道理,它总是跳到下一个完整元素。
生活例子:排队买零食,每个人站一个格子。队伍的首位同学就是 arr[0],指针 p 指向他。你数“第1个、第2个……”时,每次移动一个格子(一个人),而不是移动1厘米。p + 2 就是指向第3个同学(从0开始计数)。
用指针遍历数组的两种方式
- 固定指针 + 偏移量:指针本身不动,用
*(p + i)或p[i]访问第i个元素。 - 移动指针:让指针变量
p不断自增p++,每次指向下一个元素。
两种方式都行,看你的习惯。第一种像拿着书签不动,只翻页数;第二种像每次翻一页,书签跟着走。
小提示:p[i] 和 *(p + i) 在C++里完全等价,编译器会做相同的处理。所以你可以把指针当成数组名来用。
新手容易掉进的坑
-
越界访问:指针可以指向数组之外的内存,但那里存着未知数据。比如:
int arr[3] = {1, 2, 3}; int* p = arr; cout << *(p + 100); // 危险!跳到数组外面,可能读到垃圾值甚至让程序崩溃就像你翻书翻到了封面外面,那里什么都没有,甚至可能撞到墙。
-
数组名不能修改:数组名是常量指针,你不能写
arr++或arr = p。但你可以用另一个指针变量来移动:int arr[3] = {1, 2, 3}; // arr++; // 错误!数组名不是变量 int* p = arr; p++; // 正确,p是指针变量 -
把指针和整数混淆:
p + 1和(int)p + 1完全不同。前者是元素地址加一个元素大小,后者是地址的整数值加1字节。不要弄混。 -
忘记解引用:想输出元素内容时,要用
*p而不是p。p输出的是地址,不是数值。
完整示例:用指针统计考试成绩
假设你班有5个同学,考试成绩分别是85, 92, 78, 90, 88。用指针来求总分和平均分。
#include <iostream>
using namespace std;
int main() {
int scores[5] = {85, 92, 78, 90, 88}; // 五名同学的成绩
int* ptr = scores; // 指针指向数组开头
int total = 0; // 总分初始化0
// 方法1:用指针移动累加
for (int i = 0; i < 5; i++) {
total += *ptr; // 加上当前指针指向的成绩
ptr++; // 指针移到下一个同学
}
cout << "总分:" << total << endl;
cout << "平均分:" << total / 5.0 << endl;
// 方法2:用指针下标(不移动指针)
ptr = scores; // 重新指回开头
total = 0;
for (int i = 0; i < 5; i++) {
total += ptr[i]; // 等效于 scores[i]
}
cout << "另一种算法总分:" << total << endl;
return 0;
}
相关知识点指引
- 指针与一维数组:这是最基础的应用,掌握后可以推广到二维数组(把二维数组看作一维数组的数组,用指针数组或行指针)。
- 动态数组:用
new在堆上分配内存,返回的也是指针,可以像操作数组一样用下标或指针移动。 - 指针与字符串:字符串常量和字符数组本质上也是指针,
char*可以指向字符串。 - 多级指针:比如
int**指向一个指针数组,常用在动态二维数组或命令行参数中。 - 函数指针:把函数的地址存入指针,可以灵活调用不同函数(比如排序时传比较函数)。
学完指针与数组,你就能理解为什么C++程序能又快又灵活地处理数据——因为指针给了你直接操作内存的能力,就像给了你一把万能钥匙!
例题精讲
关于数组名和指针,以下说法正确的是? A. 数组名是一个指针变量,可以指向其他地址 B. 数组名是一个常量指针,其值不能被修改 C. 数组名和指向数组首元素的指针在使用上完全没有区别 D. 数组名实际上是一个数组类型的变量,具有数组大小信息
若有定义:int a[5] = {1,2,3,4,5}, *p = a; 则表达式 *(p+3) 与 a[3] 的值相同。
以下代码通过指针遍历数组并输出所有元素,请将空缺的表达式补充完整。
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
for (int i = 0; i < 5; ++i)
cout << ___ << " ";对于二维数组 int a[3][4]; 以下哪个表达式表示 a[1][2] 的地址? A. *(a+1)+2 B. a[1]+2 C. a[1][2] D. *(*(a+1)+2)
以下函数通过指针参数交换两个整数,请补充指针的解引用操作。
void swap(int *x, int *y) {
int temp;
temp = *x;
___ = *y;
*y = temp;
}