数组是一种用于存储多个相同类型元素,C语言中一种非常重要的数据结构。它是一种线性数据结构,可以按顺序访问和操作数组中的元素。
1. 数组存储原理
C 数组的存储原理可以通过以下几个方面来理解:
- 连续内存分配: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* 类型指针,对 arr 取地址得到结果自然是二级指针 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,整个数组的大小就是 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; }