C++: 指针与数组
最后更新:2026-08-26
第16课我们学过数组,但没讲数组名到底是什么。
其实,数组名就是指针——它存的是数组首元素的地址。
理解这一点,就能理解为什么数组作为参数时会"退化成指针"。
1. 数组名就是指针
(1) 1.1 基本事实
数组名就是指向首元素的指针。
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "arr = " << arr << std::endl;
std::cout << "&arr[0] = " << &arr[0] << std::endl;
// 两者输出一样!因为 arr 就是 &arr[0]
return 0;
}
输出(示例):
arr = 0x7ffd4a3b
&arr[0] = 0x7ffd4a3b
💡 重点: arr 和 &arr[0] 是同一个地址。
2. 用指针访问数组元素
(1) 2.1 基本用法
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // p 指向首元素
// 方法一:用下标(和数组一样)
std::cout << "arr[0] = " << p[0] << std::endl; // 10
std::cout << "arr[1] = " << p[1] << std::endl; // 20
// 方法二:用解引用
std::cout << "*p = " << *p << std::endl; // 10
std::cout << "*(p+1) = " << *(p+1) << std::endl; // 20
return 0;
}
💡 重点: p[i] 其实就是 *(p+i)(后面会讲指针算术)。
3. 指针算术(Pointer Arithmetic)
(1) 3.1 指针加减整数
指针加减整数时,不是简单地加减地址值,而是加减 整数 × sizeof(类型) 个字节。
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr; // 指向 arr[0]
std::cout << "p = " << p << std::endl; // 假设 0x1000
std::cout << "p + 1 = " << p + 1 << std::endl; // 0x1004(不是 0x1001!)
std::cout << "p + 2 = " << p + 2 << std::endl; // 0x1008
return 0;
}
原理:
int占 4 字节p + 1实际加1 × 4 = 4个字节- 所以
p + 1指向arr[1]
(2) 3.2 指针相减
两个指针相减,得到的是它们之间的距离(元素个数),不是字节数。
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p1 = &arr[0]; // 指向 arr[0]
int* p2 = &arr[3]; // 指向 arr[3]
std::cout << "p2 - p1 = " << p2 - p1 << std::endl; // 3(元素个数)
return 0;
}
💡 重点: 只有指向同一个数组的指针相减才有意义。
4. 用指针遍历数组(三种方法)
▶ 示例 1:遍历数组(难度⭐)
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// 方法一:用下标(最常用)
std::cout << "方法一:下标" << std::endl;
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 方法二:用指针算术
std::cout << "方法二:指针算术" << std::endl;
for (int* p = arr; p < arr + 5; p++) {
std::cout << *p << " ";
}
std::cout << std::endl;
// 方法三:用标准库函数(后面会学)
// std::cout << "方法三:std::begin/end" << std::endl;
// for (int* p = std::begin(arr); p != std::end(arr); p++) {
// std::cout << *p << " ";
// }
return 0;
}
输出:
方法一:下标
10 20 30 40 50
方法二:指针算术
10 20 30 40 50
💡 推荐: 方法一(下标)最直观,优先用。方法二(指针算术)在底层代码里常见。
5. 数组作为参数时的退化
(1) 5.1 问题:数组会退化成指针
当你把数组传给函数时,数组会退化成指针(丢失大小信息)。
#include <iostream>
void printSize(int arr) {
// arr 其实是指针,不是数组!
std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl; // 8(64位系统,指针大小)
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl; // 20(5 × 4 = 20 字节)
printSize(arr);
return 0;
}
💡 重点: printSize 函数里的 arr 其实是指针,所以 sizeof(arr) 是指针的大小(8 字节),不是数组的大小(20 字节)。
(2) 5.2 解决方法:传数组大小
#include <iostream>
// 必须额外传数组大小
void printArray(int arr, int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
printArray(arr, 5); // 传数组名(就是首元素地址)
return 0;
}
💡 提示: 后面会学 std::array 和 std::vector,它们能保留大小信息,更安全。
6. 实战:反转数组
▶ 示例 2:用指针反转数组(难度⭐⭐)
#include <iostream>
void reverseArray(int* arr, int size) {
int* left = arr; // 指向第一个元素
int* right = arr + size - 1; // 指向最后一个元素
while (left < right) {
// 交换 left 和 right 指向的值
int temp = *left;
*left = *right;
*right = temp;
left++; // 向右移动
right--; // 向左移动
}
}
int main() {
int arr[5] = {10, 20, 30, 40, 50};
std::cout << "反转前:";
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
reverseArray(arr, 5);
std::cout << "反转后:";
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
输出:
反转前:10 20 30 40 50
反转后:50 40 30 20 10
7. 常见错误
(1) 7.1 指针越界
#include <iostream>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* p = arr;
std::cout << *(p + 10) << std::endl; // ❌ 越界!p+10 指向数组外
// 输出垃圾值,或程序崩溃
return 0;
}
💡 提示: C++ 不会自动检查指针越界!这是 C++ 速度快的原因之一,但也是常见的 bug 来源。
(2) 7.2 指针相减没有意义
#include <iostream>
int main() {
int x = 5;
int y = 10;
int* p1 = &x;
int* p2 = &y;
std::cout << p2 - p1 << std::endl; // ❌ 未定义行为!p1 和 p2 指向不同数组
// 只有指向同一个数组的指针相减才有意义
return 0;
}
❓ 常见问题
arr 和 &arr 的值一样,但类型不同?arr 的类型是 int*(指向 int 的指针) > - &arr 的类型是 int(*)[5](指向"包含5个int的数组"的指针)* 和 / 吗?p + n、p - n) > - 两个指针相减(p2 - p1) > - 比较大小(p1 < p2)▶ 示例 3:指针遍历数组(难度⭐)
#include <iostream>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* p = arr;
for (int i = 0; i < 5; i++) {
std::cout << "*(p+" << i << ")=" << *(p + i) << std::endl;
}
return 0;
}
输出:
*(p+0)=10
*(p+1)=20
*(p+2)=30
*(p+3)=40
*(p+4)=50
*(p + i) 等价于 p[i] 或 arr[i]。指针加减整数会自动考虑类型大小。
- 数组名就是指针(指向首元素的地址)
- 指针加减整数时,加的是
整数 × sizeof(类型)个字节 - 两个指针相减,得到的是元素个数,不是字节数
- 数组作为参数时会退化成指针,需要额外传大小
- 用指针遍历数组有三种方法:下标、指针算术、标准库函数
📖 小节
- 数组名:首元素地址,等同于指针
- 指针遍历:
*(p + i)等价于p[i] - 数组传参:退化为指针,需额外传长度
- 多维数组:用指向数组的指针遍历
📝 作业
-
基础题 (Difficulty ⭐): 声明一个
int数组arr[5] = {1, 2, 3, 4, 5},再声明一个int*指针p指向arr。用指针输出所有元素。 -
进阶题 (Difficulty ⭐⭐): 写一个函数
int* findElement(int* arr, int size, int target),在数组中查找目标值。如果找到,返回指向该元素的指针;如果没找到,返回nullptr。 -
挑战题 (Difficulty ⭐⭐⭐): 写一个程序,用指针实现"冒泡排序":
-
遍历数组,比较相邻元素
-
如果顺序不对,交换它们
-
重复这个过程,直到数组有序
- 数组名是常量指针(首元素地址)
- 指针算术:p + n 偏移 n 个元素,不是 n 个字节
- 数组下标访问 arr[i] 等价于 *(arr + i)
- 指针遍历数组比下标灵活
- 数组作为函数参数退化为指针
8. 🚀 下一步
学会了指针与数组,接下来我们学习 指针算术进阶(第24课)—— 深入理解指针的类型、void* 指针、以及多级指针。