这是一个非常核心且重要的概念,理解它能帮助你更好地掌握C语言的内存操作。

(图片来源网络,侵删)
核心要点
请记住一个关键点:在C语言中,数组名 arr 会“衰变”为其首元素的地址。
这意味着,当你把一个数组名赋值给一个指针时,你实际上是把数组第一个元素的内存地址赋给了这个指针。
一维数组与指针
这是最常见的情况,我们来看一个例子。
声明和初始化
int numbers[] = {10, 20, 30, 40, 50};
int *ptr; // 声明一个整型指针
// 将数组赋值给指针
// 这里的 numbers 会被解释为 &numbers[0]
ptr = numbers;
内存中的样子:

(图片来源网络,侵删)
numbers (数组名)
|
v
+-------+-------+-------+-------+-------+
| 10 | 20 | 30 | 40 | 50 |
+-------+-------+-------+-------+-------+
^ ^ ^ ^ ^
&numbers[0] &numbers[1] &numbers[2] &numbers[3] &numbers[4]
|
ptr (指针变量)
如何通过指针访问数组元素
一旦指针指向了数组,你就可以使用两种主要的方式来访问数组元素:
a) 使用指针算术
// 获取第一个元素 int first_value = *ptr; // *ptr 解引用指针,获取其指向的值 (10) // 移动指针到下一个元素 ptr++; // 指针现在指向 numbers[1] // 获取第二个元素 int second_value = *ptr; // 20 // 你也可以直接计算偏移量 int third_value = *(ptr + 1); // 指针不变,但通过 +1 计算出 numbers[2] 的地址并解引用 (30)
b) 使用数组下标(非常强大!)
C语言有一个非常方便的特性:如果指针指向一个数组,你可以对指针使用数组下标 []。

(图片来源网络,侵删)
// 让指针重新指向数组开头 ptr = numbers; // 使用下标访问 int first_value = ptr[0]; // 等同于 *(ptr + 0),即 10 int second_value = ptr[1]; // 等同于 *(ptr + 1),即 20 int third_value = ptr[2]; // 等同于 *(ptr + 2),即 30
这看起来就像是 ptr 本身就是一个数组。ptr[i] 在编译时总是被解释为 *(ptr + i)。
多维数组与指针
多维数组的情况稍微复杂一些,因为它涉及到“数组的数组”。
二维数组
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
*a) 赋值给指向整型的指针 (`int `)**
这是不推荐的,并且容易出错。
int *ptr; // ptr = matrix; // 错误!编译器会报错。 // 因为 matrix 的类型是 "int [3][4]",它会衰变为指向其第一个元素的地址。 // 而它的第一个元素是 "int [4]" (一个包含4个整数的数组)。 // matrix 的类型是 "int (*)[4]" (一个指向包含4个整数的数组的指针)。 // 不能将 int (*)[4] 赋值给 int *。
*b) 赋值给指向数组的指针 (`int ()[4]`)**
这是正确且推荐的方式。
// 声明一个指向 "包含4个整数的数组" 的指针 int (*ptr_to_row)[4]; // 将二维数组名赋值给这个指针 // matrix 衰变为指向第一行 (一个 int [4] 数组) 的指针 ptr_to_row = matrix;
内存中的样子:
matrix (数组名)
|
v
+---------------------------------------+
| (第一行) | (第二行) | (第三行) |
| +---+---+---+---+ | +---+---+---+---+ | +---+---+---+---+ |
| | 1 | 2 | 3 | 4 | | | 5 | 6 | 7 | 8 | | | 9 |10 |11 |12 | |
| +---+---+---+---+ | +---+---+---+---+ | +---+---+---+---+ |
+---------------------------------------+
^
|
ptr_to_row
如何通过指针访问元素:
// 获取第一行的第一个元素 int value1 = (*ptr_to_row)[0]; // 等同于 matrix[0][0],即 1 // 移动指针到下一行 ptr_to_row++; // 指针现在指向 matrix[1] // 获取第二行的第二个元素 int value2 = (*ptr_to_row)[1]; // 等同于 matrix[1][1],即 6 // 同样,也可以使用指针算术 int value3 = *(*(ptr_to_row + 1) + 2); // ptr_to_row + 1 指向第三行, *(...) 获取第三行数组, +2 指向第三行的第三个元素, * 解引用得到 11
字符数组与指针
字符数组(字符串)是数组赋值给指针最经典的例子。
char greeting[] = "Hello, World!"; char *str_ptr; // 声明一个字符指针 // 将字符数组赋值给指针 str_ptr = greeting;
内存中的样子:
greeting (数组名)
|
v
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| 'H' | 'e' | 'l' | 'l' | 'o' | ',' | ' ' | 'W' | 'o' | 'r' | 'l' | 'd' | '!' |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
^
|
str_ptr
如何通过指针访问和操作字符串:
// 打印整个字符串
printf("%s\n", str_ptr); // Hello, World!
// 打印单个字符
printf("First char: %c\n", *str_ptr); // H
// 移动指针
str_ptr++; // 指向 'e'
printf("Second char: %c\n", *str_ptr); // e
// 你也可以用下标
printf("Third char: %c\n", str_ptr[1]); // l (因为 str_ptr 指向 'e', str_ptr[1] 是 'l')
重要区别:数组名 vs. 指针变量
虽然数组名可以像指针一样使用,但它们有本质区别:
| 特性 | 数组名 (arr) |
指针变量 (int *p) |
|---|---|---|
| 本质 | 一个常量,代表数组首元素的地址。 | 一个变量,用于存储内存地址。 |
| 赋值 | 不能被赋值。arr = p; 是错误的。 |
可以被赋值。p = arr; 或 p = another_ptr; 都是正确的。 |
| sizeof | sizeof(arr) 返回整个数组的大小(int arr[5],sizeof(arr) 是 5 * sizeof(int))。 |
sizeof(p) 返回指针本身的大小(在64位系统上是8字节,在32位系统上是4字节),无论它指向哪里。 |
&运算符 |
&arr 得到的是整个数组的地址,其类型与 arr 的类型略有不同(int (*)[5])。 |
&p 得到的是指针变量本身的地址。 |
- 基本赋值:将数组名赋给指针,实际上是将其首元素的地址赋给了指针。
int *p = arr;。 - 一维数组:指针可以通过指针算术(
p++,*(p+i))或下标(p[i])来访问数组元素,两者是等价的。 - 多维数组:必须使用与数组维度匹配的指针。
int matrix[M][N]应该赋给int (*p)[N]。 - 字符数组:这是最常见的用法,指针可以方便地遍历和操作字符串。
- 核心区别:数组名是地址常量,指针变量是地址变量。
sizeof和赋值行为是它们最显著的区别。
理解了“数组名衰变为首元素地址”这一核心规则,你就能轻松掌握数组与指针的关系。
