2 指针与数组

正在加载视频播放器…

GESP C++ 四级:指针与数组精讲

核心目标:理解数组名的本质,掌握用指针访问数组元素、指针运算、指针与数组作为函数参数、字符指针与字符串,能熟练判断指针相关程序的输出。
数组名就是首地址;arr[i] 等价于 *(arr + i);指针运算以元素大小为单位;数组传参传的是首地址,函数内修改会影响原数组。

一、考纲定位

GESP 四级考纲对指针与数组的要求:

  • 理解 C++ 指针类型的概念,掌握指针类型变量的定义、赋值、解引用。
  • 掌握指针与数组的关系,能用指针访问数组元素。
  • 掌握函数参数的传递方式:值传递、引用传递、指针传递。
  • 掌握字符型指针变量与字符串的关系。
  • 了解二维数组与指针的关系(四级要求掌握二维数组基本概念)。

四级不考:动态内存分配(new/delete)、函数指针、复杂指针数组。

二、数组名的本质

1. 数组名就是首地址

数组名代表数组第一个元素的地址,是一个常量指针。

int arr[5] = {10, 20, 30, 40, 50};
cout << arr << endl;        // 输出首地址,如 0x7ffee4065820
cout << &arr[0] << endl;    // 与 arr 相同

2. 数组名不能重新赋值

int arr[5];
int *p = arr;   // ✅ 正确
arr = p;        // ❌ 错误:数组名是常量,不能赋值

3. arr 与 &arr 的区别(了解)

表达式 类型 含义 +1 移动的字节数
arr int* 指向首元素 sizeof(int) = 4
&arr int(*)[5] 指向整个数组 sizeof(arr) = 20
int arr[5];
cout << (arr + 1) - arr << endl;      // 1(移动一个元素)
cout << (&arr + 1) - &arr << endl;    // 1(移动整个数组)

三、指针访问数组

1. 指针指向数组

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;          // p 指向 arr[0]

2. 三种访问方式等价

数组下标 指针形式 说明
arr[i] *(arr + i) 数组名 + 偏移
p[i] *(p + i) 指针 + 偏移
*(p + i) p[i] 完全等价
for (int i = 0; i < 5; i++) {
    cout << arr[i] << " ";      // 方式一
    cout << *(arr + i) << " ";  // 方式二
    cout << *(p + i) << " ";    // 方式三
    cout << p[i] << " ";        // 方式四
}

3. 用指针遍历数组

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;

// 方式一:指针 + 下标
for (int i = 0; i < 5; i++) {
    cout << *(p + i) << " ";
}

// 方式二:指针自增
for (int *q = arr; q < arr + 5; q++) {
    cout << *q << " ";
}

四、指针运算

1. 指针加减整数

指针加减的单位是所指向类型的大小。

int arr[3] = {10, 20, 30};
int *p = arr;       // 假设地址为 1000
p++;                // p 变为 1004(假设 int 占 4 字节)
cout << *p << endl; // 20

2. 指针相减

两个同类型指针相减,得到它们之间相差的元素个数。

int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[1];
int *p2 = &arr[4];
cout << p2 - p1 << endl;   // 3

3. 指针比较

同类型指针可以用 > < >= <= == != 比较地址大小。

int *p = arr;
while (p < arr + 5) {
    cout << *p << " ";
    p++;
}

五、指针与数组作为函数参数

1. 数组参数退化为指针

当数组作为函数参数时,实际传递的是数组首地址,函数内部修改数组元素会影响原数组。

void modify(int arr[], int n) {
    arr[0] = 100;
}

int main() {
    int a[3] = {1, 2, 3};
    modify(a, 3);
    cout << a[0];   // 100
}

以下三种函数声明完全等价:

void f(int arr[]);
void f(int arr[10]);
void f(int *arr);

2. 函数内求数组长度是错的

因为数组参数退化为指针,sizeof(arr) 得到的是指针大小,不是数组总字节数。

void f(int arr[]) {
    cout << sizeof(arr) << endl;   // 输出 8(64位系统指针大小)
}

int main() {
    int a[10];
    cout << sizeof(a) << endl;     // 输出 40
    f(a);                          // 输出 8
}

结论:数组作为参数时,必须额外传递长度 n。

3. 指针传递修改实参

void change(int *p) {
    *p = 100;
}

int main() {
    int n = 7;
    change(&n);
    cout << n;   // 100
}

4. 交换两个变量(指针版)

void xchg(int *a, int *b) {
    int t = *a;
    *a = *b;
    *b = t;
}

int main() {
    int x = 10, y = 20;
    xchg(&x, &y);
    cout << x << " " << y;   // 20 10
}

六、字符指针与字符串

1. 字符指针指向字符串常量

char *s = "Hello";
cout << s << endl;        // 输出 Hello
cout << *s << endl;       // 输出 H
cout << *(s + 1) << endl; // 输出 e

注意:s 指向字符串常量的首地址,字符串常量存储在只读区,不能修改。

s[0] = 'h';   // ❌ 错误:修改字符串常量,程序崩溃

2. 字符数组与字符指针的区别

对比项 char s[] = "Hello"; char *s = "Hello";
存储位置 栈区,可修改 只读区,不可修改
大小 sizeof(s) = 6 sizeof(s) = 8(指针大小)
可修改 ✅ ❌
赋值 需用 strcpy 可直接指向其他字符串

3. 字符指针遍历字符串

char *s = "Hello";
while (*s != '\0') {
    cout << *s;
    s++;
}
// 输出 Hello

七、指针数组与数组指针(了解)

1. 指针数组

数组的每个元素都是指针。

int a = 1, b = 2, c = 3;
int *arr[3] = {&a, &b, &c};   // 指针数组
cout << *arr[0] << endl;       // 1

2. 数组指针

指向整个数组的指针。

int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5] = &arr;   // 数组指针,指向整个数组
cout << (*p)[0] << endl;   // 1

3. 快速区分

写法 名称 本质
int *p[5] 指针数组 数组,元素是指针
int (*p)[5] 数组指针 指针,指向数组

记忆:[] 优先级高于 *,*p[5] 先结合 [],是指针数组;加括号 (*p)[5] 先结合 *,是数组指针。

八、GESP 四级真题示例

1. 指针与数组地址计算

假设整数数组 arr[4] = {0, 1, 2, 3}; 的第一个元素在内存中的地址为 0x7ffee4065820,经过 int* p = arr; p += 1; 后,指针 p 的值是( )。
答案:0x7ffee4065824(假设 int 占 4 字节)

2. 指针传参判断

执行下面程序后,输出为( )。

void fun(int a, int &b, int *c){
 a += 1;
 b += 2;
 *c += 3;
}
int main(){
 int x = 1, y = 1, z = 1;
 fun(x, y, &z);
 cout << x << " " << y << " " << z;
}

答案:1 3 4

3. 指针与数组输出

以下代码的输出是( )。

int a[3] = {1, 2, 3};
int *p = a;
cout << *(p + 2);

答案:3

4. 数组名与指针区别

以下说法正确的是( )。
A) 数组名可以重新赋值
B) arr[i] 等价于 *(arr + i)
C) 数组名是一个指针变量
D) sizeof(arr) 在函数参数中仍能得到数组总大小
答案:B

九、易错点汇总

  1. 数组名不能赋值:arr = p; 错误。
  2. 数组参数退化为指针:函数内 sizeof(arr) 得到指针大小,不是数组大小。
  3. 指针运算单位错误:p + 1 跳过 sizeof(类型) 字节,不是1字节。
  4. 字符指针指向字符串常量不可修改:char *s = "Hello"; s[0] = 'h'; 错误。
  5. 混淆指针数组与数组指针:int *p[5] 是指针数组,int (*p)[5] 是数组指针。
  6. 解引用空指针或野指针:使用前必须初始化并判空。
  7. *(p + i) 与 *p + i 混淆:前者是 p[i],后者是 p[0] + i。
  8. 指针相减得到元素个数,不是字节数:p2 - p1 返回的是元素个数。
  9. 函数内修改指针本身 vs 修改指针指向的值:p = &b 只改局部指针;*p = 20 才改外面。
  10. 字符串常量与字符数组混淆:char s[] = "Hello" 可修改,char *s = "Hello" 不可修改。

十、速查表

需求 写法 说明
数组首地址 arr 或 &arr[0] 类型 int*
指针指向数组 int *p = arr; p 指向 arr[0]
访问元素 *(p + i) 或 p[i] 等价于 arr[i]
指针自增 p++ 移动一个元素
指针相减 p2 - p1 返回元素个数
数组作参数 void f(int arr[], int n) 等价于 int *arr
函数内修改数组 arr[0] = 100; 影响原数组
指针传参修改实参 void f(int *p) { *p = 100; } 调用 f(&x)
字符指针 char *s = "Hello"; 不可修改内容
字符数组 char s[] = "Hello"; 可修改
指针数组 int *p[5]; 数组元素是指针
数组指针 int (*p)[5]; 指向整个数组

十一、总结

指针与数组是GESP四级的核心考点。

  1. 数组名是首地址,是常量,不能赋值。
  2. arr[i] 等价于 *(arr + i),也等价于 p[i]。
  3. 指针运算以所指向类型的大小为单位。
  4. 两个同类型指针相减,得到元素个数。
  5. 数组作函数参数时退化为指针,必须额外传长度。
  6. 函数内通过指针可以修改外面的变量。
  7. 字符指针指向字符串常量时,内容不可修改。
  8. int *p[5] 是指针数组,int (*p)[5] 是数组指针。
  9. *(p + i) 是取第 i 个元素,*p + i 是首元素值加 i。
  10. 多练“地址计算”“指针传参判断”“指针与数组结合输出”三类题。

复习建议:先理解数组名与指针的关系,再练指针遍历数组、指针运算、指针传参,最后区分字符指针与字符数组。考试中先判断指针指向哪里,再看是值传递、引用传递还是指针传递,最后逐行跟踪变量变化。