指针进阶
一、课上练习
编程练习
二、知识总结
✨ 数组指针
指向静态一维数组
在 C++ 中,数组名本质上是一个指向数组第一个元素的指针。因此,可以通过指针来访问数组中的元素。
1#include <iostream>
2using namespace std;
3
4int main() {
5 int arr[5] = {1, 2, 3, 4, 5}; // 定义一个包含5个整数的数组
6 int *p = arr; // 数组名是指向数组第一个元素的指针
7
8 // 通过指针访问元素
9 for (int i = 0; i < 5; ++i) {
10 cout << "*(p + " << i << ") = " << *(p + i) << endl;
11 }
12
13 return 0;
14}关键要点:
- 数组名
arr是指向数组第一个元素的指针,arr等价于&arr[0] - 通过指针可以像数组一样访问数组元素,
*(arr + i)等价于arr[i]
指向动态分配一维数组
可以使用 new 运算符动态分配一维数组,并使用 delete[] 运算符释放动态分配的内存。
1#include <iostream>
2using namespace std;
3
4int main() {
5 int n;
6 cout << "Enter the size of the array: ";
7 cin >> n;
8
9 // 动态分配一维数组
10 int* arr = new int[n];
11
12 // 初始化数组
13 cout << "Enter " << n << " elements:" << endl;
14 for (int i = 0; i < n; ++i) {
15 cin >> arr[i];
16 }
17
18 // 输出数组
19 cout << "The array is:" << endl;
20 for (int i = 0; i < n; ++i) {
21 cout << arr[i] << " ";
22 }
23 cout << endl;
24
25 // 释放动态分配的内存
26 delete[] arr;
27
28 return 0;
29}✨ 常数指针
常数指针和指向常量的指针是指针的两种不同使用方式,常用于保护数据不被修改。
指向常量的指针(Pointer to Constant)
指向常量的指针是一种指针,它指向一个常量数据,意味着不能通过这个指针修改指向的数据,但可以改变指针本身指向的地址。
const int *p;这里 p 是一个指向 const int 类型数据的指针。通过 p 不能修改指向的整数值,但是 p 本身可以修改指向其他地址。
1#include <iostream>
2using namespace std;
3
4int main() {
5 int a = 10;
6 int b = 20;
7 const int *p = &a; // p 指向 a,但不能通过 p 修改 a 的值
8
9 cout << "Value pointed to by p: " << *p << endl;
10
11 // *p = 15; // 错误:不能通过 p 修改 a 的值
12 p = &b; // 合法:可以改变 p 的指向
13 cout << "Value pointed to by p: " << *p << endl;
14
15 return 0;
16}常量指针(Constant Pointer)
常量指针是一种指针,它本身是一个常量,意味着指针的指向(地址)不能改变,但可以通过指针修改它所指向的数据。
int *const p;这表示 p 是一个常量指针,指向 int 类型数据。通过 p 可以修改指向的整数值,但不能改变 p 本身的指向。
1#include <iostream>
2using namespace std;
3
4int main() {
5 int a = 10;
6 int *const p = &a; // p 是一个常量指针,指向 a
7
8 cout << "Value pointed to by p: " << *p << endl;
9
10 *p = 15; // 合法:可以通过 p 修改 a 的值
11 cout << "Value pointed to by p: " << *p << endl;
12
13 // int b = 20;
14 // p = &b; // 错误:不能改变 p 的指向
15
16 return 0;
17}指向常量的常量指针(Constant Pointer to Constant)
这种指针既是指向常量的数据,指针本身也是常量。即:不能通过指针修改数据,且指针的指向也不能改变。
const int *const p;这表示 p 是一个常量指针,指向 const int 类型数据。通过 p 既不能修改指向的整数值,也不能改变 p 本身的指向。
1#include <iostream>
2using namespace std;
3
4int main() {
5 int a = 10;
6 const int *const p = &a; // p 是一个常量指针,指向 const int
7
8 cout << "Value pointed to by p: " << *p << endl;
9
10 // *p = 15; // 错误:不能通过 p 修改 a 的值
11 // int b = 20;
12 // p = &b; // 错误:不能改变 p 的指向
13
14 return 0;
15}const 总结
以下是四种指针类型的对比:
| 指针类型 | 名称 | 可修改指向地址 | 可修改指向地址的数据 |
|---|---|---|---|
int *pointer | 普通指针 | 是 | 是 |
const int *pointer | 指向常量的指针 | 是 | 否 |
int *const pointer | 常量指针 | 否 | 是 |
const int *const pointer | 指向常量的常量指针 | 否 | 否 |
✨ 结构体指针
可以使用指针指向一个结构体变量,并通过指针访问和修改结构体的成员。访问结构体指针的成员有两种方式:使用箭头运算符 -> 或使用 *(ptr).member** 的形式。
1#include <iostream>
2using namespace std;
3
4// 定义一个结构体,表示一个点
5struct Point {
6 int x; // x坐标
7 int y; // y坐标
8};
9
10int main() {
11 Point p1;
12 p1.x = 10;
13 p1.y = 20;
14
15 // 定义一个指向结构体的指针
16 Point *p_ptr;
17 p_ptr = &p1; // 将结构体变量p1的地址赋值给指针p_ptr
18
19 // 通过指针访问结构体成员
20 cout << "Point p1: (" << p_ptr->x << ", " << p_ptr->y << ")" << endl;
21 cout << "Point p1: (" << (*p_ptr).x << ", " << (*p_ptr).y << ")" << endl;
22
23 // 修改结构体成员的值
24 p_ptr->x = 30;
25 p_ptr->y = 40;
26 cout << "Modified Point p1: (" << p1.x << ", " << p1.y << ")" << endl;
27
28 return 0;
29}可以使用指针动态分配结构体的内存。在 C++ 中,使用 new 关键字动态分配内存,并使用 delete 关键字释放内存。
1#include <iostream>
2using namespace std;
3
4// 定义一个结构体,表示一个点
5struct Point {
6 int x; // x坐标
7 int y; // y坐标
8};
9
10int main() {
11 // 动态分配结构体的内存
12 Point *p_ptr = new Point;
13
14 // 设置结构体成员的值
15 p_ptr->x = 10;
16 p_ptr->y = 20;
17
18 // 通过指针访问结构体成员
19 cout << "Point: (" << p_ptr->x << ", " << p_ptr->y << ")" << endl;
20 cout << "Point p1: (" << (*p_ptr).x << ", " << (*p_ptr).y << ")" << endl;
21
22 // 释放动态分配的内存
23 delete p_ptr;
24
25 return 0;
26}✨ 指针的执行示例
下面通过内存示意图展示数组指针、常数指针和结构体指针的工作方式。
例1:数组名与指针的关系
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;1内存布局:
2地址(示意) 0x1000 0x1004 0x1008 0x100C 0x1010
3 +-------+-------+-------+-------+-------+
4arr: | 10 | 20 | 30 | 40 | 50 |
5 +-------+-------+-------+-------+-------+
6 arr[0] arr[1] arr[2] arr[3] arr[4]
7
8p = arr = &arr[0] = 0x1000指针算术运算的执行过程:
p → 地址 0x1000,*p = 10 等价于 arr[0]
p + 1 → 地址 0x1004,*(p+1) = 20 等价于 arr[1]
p + 2 → 地址 0x1008,*(p+2) = 30 等价于 arr[2]
p + 3 → 地址 0x100C,*(p+3) = 40 等价于 arr[3]
p + 4 → 地址 0x1010,*(p+4) = 50 等价于 arr[4]p + i不是简单地给地址加 i,而是加i * sizeof(int)个字节。因为int占4字节,所以p + 1实际上地址增加了4。
例2:动态数组的分配与释放
1int n = 5;
2int *arr = new int[n]; // 动态分配5个int的数组
3
4for (int i = 0; i < n; ++i) {
5 arr[i] = (i + 1) * 10;
6}
7// arr: [10, 20, 30, 40, 50]
8
9delete[] arr; // 释放整个数组(注意用 delete[],不是 delete)
10arr = nullptr;1new int[5] 后:
2堆内存:[ 10 | 20 | 30 | 40 | 50 ]
3 arr指向此处
4
5delete[] arr 后:
6堆内存:[ ? | ? | ? | ? | ? ] ← 已释放例3:const 指针的行为对比
1int a = 10, b = 20;
2
3// 普通指针:地址可改,数据可改
4int *p1 = &a;
5*p1 = 15; // ✓ 可以修改数据
6p1 = &b; // ✓ 可以改变指向
7
8// 指向常量的指针:地址可改,数据不可改
9const int *p2 = &a;
10// *p2 = 15; // ✗ 不能通过p2修改数据
11p2 = &b; // ✓ 可以改变指向
12
13// 常量指针:地址不可改,数据可改
14int *const p3 = &a;
15*p3 = 15; // ✓ 可以修改数据
16// p3 = &b; // ✗ 不能改变指向
17
18// 指向常量的常量指针:都不可改
19const int *const p4 = &a;
20// *p4 = 15; // ✗ 不能修改数据
21// p4 = &b; // ✗ 不能改变指向记忆技巧:const在左边 → 数据不可改;const在右边 → 指向不可改。
例4:结构体指针的两种访问方式
struct Point { int x; int y; };
Point p1 = {10, 20};
Point *ptr = &p1;1内存布局:
2p1 [0x1000]: x=10, y=20
3ptr[0x2000]: 0x1000(指向p1)
4
5访问成员的两种等价写法:
6ptr->x 等价于 (*ptr).x 结果都是 10
7ptr->y 等价于 (*ptr).y 结果都是 20使用->比(*ptr).x更简洁,是结构体指针访问成员的推荐写法。
✨ 指针的解题步骤
使用指针进阶知识解决问题的一般思路:
- 数组与指针互通:当函数参数要求传入指针时,可以直接传入数组名;当需要遍历数组时,可以用指针代替下标
- 选择合适的 const 修饰:如果函数只需要读取数据不修改,参数声明为
const int *p可以防止意外修改 - 动态数组的使用流程:
new分配 → 使用 →delete[]释放 → 置nullptr - 结构体指针操作:使用
->运算符访问成员,动态结构体用new创建、delete释放
✨ 指针的常见错误
delete和delete[]混用:用new int[n]分配的数组必须用delete[] arr释放,用delete arr释放会导致内存泄漏或程序崩溃- 指针越界访问:
*(p + 5)访问了数组范围外的内存(如果数组只有5个元素),这是未定义行为 - const 修饰位置搞混:
const int p(数据不可改)和int const p(指向不可改)含义完全不同,写反了编译器不一定报错但逻辑会出错 - *结构体指针忘记用
->或(ptr).**:直接写ptr.x是错误的,因为ptr是指针不是结构体变量 - 动态分配后忘记释放:每个
new都必须对应一个delete,否则会造成内存泄漏,在循环中尤其要注意
三、课后练习
基础知识练习
- 指针进阶 - 选择题## 编程练习