数组是一种用于存储多个相同类型元素,C语言中一种非常重要的数据结构。它是一种线性数据结构,可以按顺序访问和操作数组中的元素。
- 数组存储原理 {#title-0} ====================
C 数组的存储原理可以通过以下几个方面来理解:
- 连续内存分配:C 数组的元素在内存中是连续存储的。这使得通过数组索引来访问元素非常高效,因为可以通过简单的指针算术运算来确定元素的内存地址;
- 内存布局:数组的内存布局通常是从低地址到高地址的顺序。即:第一个元素存储在数组的开始地址,最后一个元素存储在数组的结束地址;
- 元素大小:数组的元素类型确定了每个元素占据的内存空间大小。例如,如果数组元素类型为
int
,则每个元素通常占据 4 个字节; - 数组大小:声明数组需要指定数组的大小,这样系统才能为数组分配足够的内存空间;
- 数组没有边界检查机制。如果超出数组边界访问元素,可能会导致内存越界访问,这是一种未定义行为,可能会导致程序崩溃或产生意料之外的结果。
注意: 多维数组在内存中也是使用线性的连续内存存储元素。
- 数组下标和指针 {#title-1} =====================
在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;
}
- 数组指针类型 {#title-2} ====================
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
- 数组作为函数参数 {#title-3} ======================
在 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;
}