数组是 C 语言里一种很重要的数据结构,用来存放多个相同类型的元素。它是一种线性结构,可以按顺序访问和操作里面的元素。
1. 数组存储原理
C 数组的存储原理,可以从下面这几个角度来理解:

- 连续内存分配:数组的元素在内存里是挨着放的。这样用下标访问元素就很快,因为靠简单的指针算术就能算出元素地址。
- 内存布局:数组通常按从低地址到高地址排列,即第一个元素放在数组起始地址,最后一个元素放在结束地址。
- 元素大小:元素类型决定了每个元素占多大内存。比如元素类型是
int,每个元素一般占 4 个字节。 - 数组大小:声明数组时要指定大小,系统才好一次性给它分配够内存。
- 数组没有边界检查。越界访问元素可能导致内存越界,这是一种未定义行为,轻则数据错乱,重则程序崩溃。
注意: 多维数组在内存里同样是用线性连续内存来存元素的。
2. 下标与指针
在 C 语言里,数组和指针关系非常紧密。
- 数组名当指针用:大多数情况下,数组名会被隐式转成指向首元素的指针。比如数组
int arr[5];,arr就可以看成指向arr[0]的指针。 - 指针加偏移量:用指针算术在数组里移动,对指针做加减就能指向不同位置。比如
arr + 2返回的就是指向arr[2]的指针。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
int arr[] = {10, 20, 30, 50, 50};
// 打印 arr 存储的地址,数组首元素地址
printf("arr 值: %ld,arr[0] 地址: %ld\n", arr, &arr[0]);
// 下标和指针元素访问
printf("arr[2] = %d,*(arr + 2) = %d\n", arr[2], *(arr + 2));
return 0;
}
程序运行结果:
arr 值: 140732676908768,arr[0] 地址: 140732676908768 arr[2] = 30,*(arr + 2) = 30
注意: 数组名本身的指向是不允许修改的。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
int arr[] = {10, 20, 30, 50, 50};
// 下面代码报错
// arr = NULL;
// 可以将 arr 赋值给 p 指针,修改 p 指针指向
int* p = arr;
p += 1;
*p = 200;
printf("arr[1] = %d\n", arr[1]);
return 0;
}
3. 数组指针类型
int arr[] = {10, 20, 30, 50, 50};
我们知道数组名 arr 表示指向首元素 arr[0] 的指针。那么对数组名再取一次地址,得到的又是什么呢?
顺着直觉想,arr 是 int* 类型,那对它取地址自然就是二级指针 int** 类型。可真的是这样吗?
int* 的步长是 4 字节,int** 的步长也是 4 字节。我们直接打印出来看看是不是这样:
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 50, 50};
printf("%ld\n", &arr);
printf("%ld\n", &arr + 1);
return 0;
}
程序运行结果:
140732795418336 140732795418356
可以看到,步长是 20,正好等于整个数组的大小。这说明对数组名取地址并不是二级指针,而是指向整个数组的指针,也就是数组指针类型。数组指针的定义语法如下:
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 50, 50};
// 1. 方式一
int(*p1)[5] = &arr;
printf("%ld %ld\n", (long)p1, (long)(p1+1));
// 2. 方式二
typedef int(ARRAY_TYPE)[5];
ARRAY_TYPE* p2 = &arr;
printf("%ld %ld\n", (long)p1, (long)(p2+1));
// 3. 方式三
typedef int(*ARRAY_POINTER)[5];
ARRAY_POINTER p3 = &arr;
printf("%ld %ld\n", (long)p1, (long)(p3+1));
return 0;
}
程序运行结果:
140732691384032 140732691384052 140732691384032 140732691384052 140732691384032 140732691384052
4. 数组传参
在 C 语言里,数组可以作为函数参数传进去。把数组当参数传时,实际上传的是数组的指针。这种传址方式避免在函数内部再拷贝一份数组,省了内存和时间,处理大数组或频繁操作数组时尤其有用。要在函数里用数组参数,我们得指定数组的类型和大小。
示例代码:
#include <stdio.h>
// 1. 一维数组作为函数参数
void test01(int arr[5], int len)
{
for (unsigned int i = 0; i < len; ++i)
printf("%d ", arr[i]);
printf("\n");
}
void test02(int arr[], int len)
{
for (unsigned int i = 0; i < len; ++i)
printf("%d ", arr[i]);
printf("\n");
}
void test03(int* arr, int len)
{
for (unsigned int i = 0; i < len; ++i)
printf("%d ", arr[i]);
printf("\n");
}
// 2. 二维数组作为函数参数
void test04(int arr[3][3], int len_row, int len_col)
{
for (size_t i = 0; i < len_row; ++i)
{
for (size_t j = 0; j < len_col; ++j)
printf("%d ", arr[i][j]);
}
printf("\n");
}
void test05(int arr[][3], int len_row, int len_col)
{
for (size_t i = 0; i < len_row; ++i)
{
for (size_t j = 0; j < len_col; ++j)
printf("%d ", arr[i][j]);
}
printf("\n");
}
void test06(int(*arr)[3], int len_row, int len_col)
{
for (size_t i = 0; i < len_row; ++i)
{
for (size_t j = 0; j < len_col; ++j)
printf("%d ", arr[i][j]);
}
printf("\n");
}
int main() {
// 1. 一维数组作为函数参数
int arr1[] = {10, 20, 30, 50, 50};
test01(arr1, sizeof(arr1) / sizeof(int));
test02(arr1, sizeof(arr1) / sizeof(int));
test03(arr1, sizeof(arr1) / sizeof(int));
// 2. 二维数组作为函数参数
int arr2[][3] = {{10, 20, 30}, {40, 50, 60}, {70, 80, 90}};
test04(arr2, sizeof(arr2)/sizeof(arr2[0]), sizeof(arr2[0])/sizeof(arr2[0][0]));
test05(arr2, sizeof(arr2)/sizeof(arr2[0]), sizeof(arr2[0])/sizeof(arr2[0][0]));
test06(arr2, sizeof(arr2)/sizeof(arr2[0]), sizeof(arr2[0])/sizeof(arr2[0][0]));
return 0;
}




冀公网安备13050302001966号