C语言数组详解:从一维到多维,从静态到动态

C语言数组详解:从一维到多维,从静态到动态

数组是C语言中最重要的数据结构之一,它允许我们将相同类型的多个数据项组织在一起。理解数组的工作原理对于掌握C语言的内存管理和指针概念至关重要。本篇博客将深入探讨C语言数组的各个方面,包括一维数组、多维数组、变长数组、数组与指针的关系,以及数组在函数中的使用。

一、数组简介:数据的集合

数组是一组相同类型数据的集合,这些数据在内存中连续存储。通过索引可以访问数组中的每个元素。

基本语法

C
1
2
3
4
5
6
7
8
// 声明一个包含5个整数的数组
int numbers[5];

// 声明并初始化数组
int numbers[5] = {1, 2, 3, 4, 5};

// 可以省略数组大小,编译器会自动计算
int numbers[] = {1, 2, 3, 4, 5};

访问数组元素

C
1
2
numbers[0] = 10;    // 设置第一个元素为10
int x = numbers[2]; // 获取第三个元素

重要特性

  • 数组索引从0开始
  • 数组元素在内存中连续存储
  • 数组名代表数组首元素的地址

二、数组长度:sizeof的妙用

在C语言中,数组没有内置的长度属性,但我们可以使用sizeof运算符来计算数组的长度。

C
1
2
3
int numbers[] = {1, 2, 3, 4, 5};
int length = sizeof(numbers) / sizeof(numbers[0]);
printf("数组长度:%d\n", length); // 输出:数组长度:5

工作原理

  • sizeof(numbers)返回整个数组的字节大小
  • sizeof(numbers[0])返回单个元素的字节大小
  • 两者相除得到元素个数

注意事项

  • 这种方法只适用于在声明它的作用域内的数组
  • 当数组作为函数参数传递时,会退化为指针,此时sizeof无法正确工作

三、多维数组:表格状的数据结构

多维数组可以看作是数组的数组,最常见的二维数组就像一个表格。

二维数组声明和初始化

C
1
2
3
4
5
6
7
8
9
10
11
12
// 声明一个3x4的二维数组
int matrix[3][4];

// 声明并初始化
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

// 访问元素
matrix[1][2] = 99; // 设置第二行第三列的元素为99

三维数组示例

C
1
2
3
4
5
6
7
8
9
10
11
12
int cube[2][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};

四、变长数组:运行时确定大小

C99标准引入了变长数组(VLA),允许数组的长度在运行时确定。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main() {
int n;
printf("请输入数组大小:");
scanf("%d", &n);

// 变长数组声明
int arr[n];

for (int i = 0; i < n; i++) {
arr[i] = i * i;
printf("arr[%d] = %d\n", i, arr[i]);
}

return 0;
}

变长数组的限制

  • 不能有静态存储期(不能是全局变量或static变量)
  • 不能初始化(声明时不能使用初始化列表)
  • 某些编译器可能不支持

五、数组的地址:数组名就是指针

在C语言中,数组名代表数组首元素的地址,这是一个非常重要的概念。

C
1
2
3
4
5
6
7
8
9
int numbers[5] = {1, 2, 3, 4, 5};

printf("数组首地址:%p\n", numbers); // 数组名就是首地址
printf("第一个元素地址:%p\n", &numbers[0]); // 与上面相同
printf("第二个元素地址:%p\n", &numbers[1]); // 地址增加sizeof(int)

// 数组名和指针的关系
int *ptr = numbers; // 数组名可以赋值给指针
printf("通过指针访问:%d\n", *(ptr + 2)); // 等价于numbers[2]

六、数组指针的加减法:指针算术

由于数组元素在内存中连续存储,我们可以对数组指针进行算术运算。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
int numbers[5] = {10, 20, 30, 40, 50};
int *ptr = numbers;

printf("当前值:%d\n", *ptr); // 输出:10
ptr++; // 指针移动到下一个元素
printf("移动后值:%d\n", *ptr); // 输出:20

// 指针可以像数组一样使用下标
printf("ptr[2] = %d\n", ptr[2]); // 输出:40

// 指针减法得到元素间隔
int *ptr2 = &numbers[4];
printf("元素间隔:%ld\n", ptr2 - ptr); // 输出:4

七、数组的复制:避免浅拷贝陷阱

在C语言中,数组名不能直接赋值给另一个数组,因为数组名是常量指针。

错误做法

C
1
2
3
4
int a[5] = {1, 2, 3, 4, 5};
int b[5];

b = a; // 错误!数组名不能赋值

正确复制方法1:循环复制

C
1
2
3
4
5
6
int a[5] = {1, 2, 3, 4, 5};
int b[5];

for (int i = 0; i < 5; i++) {
b[i] = a[i];
}

正确复制方法2:使用memcpy函数

C
1
2
3
4
5
6
#include <string.h>

int a[5] = {1, 2, 3, 4, 5};
int b[5];

memcpy(b, a, sizeof(a)); // 复制整个数组

memcpy()通常比循环复制更快,因为它直接操作内存块。

八、数组作为函数的参数

1. 一维数组作为参数

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

// 函数接收数组参数
int sum_array(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}

int main() {
int numbers[] = {1, 2, 3, 4, 5};
int total = sum_array(numbers, 5);
printf("数组总和:%d\n", total); // 输出:15
return 0;
}

2. 多维数组作为参数

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 二维数组作为参数,必须指定第二维大小
void print_matrix(int matrix[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}

int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
print_matrix(matrix, 3);
return 0;
}

3. 变长数组作为参数(C99)

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 变长数组参数,size参数必须在数组参数之前
int process_vla(int size, int arr[size]) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}

// 多维变长数组
int process_2d_vla(int rows, int cols, int matrix[rows][cols]) {
int sum = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
sum += matrix[i][j];
}
}
return sum;
}

4. 数组字面量作为参数

C
1
2
// 可以直接传递数组字面量
int sum = sum_array((int[]){1, 2, 3, 4, 5}, 5);

九、实际应用示例

示例1:查找数组中的最大值

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int find_max(int arr[], int size) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}

int main() {
int numbers[] = {23, 45, 12, 67, 89, 34};
int max_value = find_max(numbers, 6);
printf("最大值:%d\n", max_value);
return 0;
}

示例2:矩阵转置

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>

void transpose_matrix(int rows, int cols, int src[rows][cols], int dest[cols][rows]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
dest[j][i] = src[i][j];
}
}
}

void print_matrix(int rows, int cols, int matrix[rows][cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}

int main() {
int original[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int transposed[3][2];

transpose_matrix(2, 3, original, transposed);

printf("原矩阵:\n");
print_matrix(2, 3, original);

printf("转置矩阵:\n");
print_matrix(3, 2, transposed);

return 0;
}

十、总结与最佳实践

关键要点

  1. 数组是连续的内存块:理解这一点对于高效使用数组至关重要
  2. 数组名是指针常量:不能对数组名进行赋值操作
  3. 边界检查很重要:访问越界会导致未定义行为
  4. sizeof只在声明作用域有效:函数参数中的数组会退化为指针

最佳实践

  • 总是对数组访问进行边界检查
  • 使用memcpy()进行数组复制以提高效率
  • 对于多维数组函数参数,明确指定除第一维外的所有维度大小
  • 考虑使用变长数组来提高代码的灵活性
  • 在需要动态大小的情况下,考虑使用动态内存分配(malloc/free)

数组是C语言编程的基础,深入理解数组的工作原理将为你学习更复杂的数据结构和算法打下坚实的基础。


C语言数组详解:从一维到多维,从静态到动态
https://www.edenzeng.online/2015/10/21/0.技术栈/01.开发语言/01.C语言/08-数组详解/
作者
Edenzeng
发布于
2015年10月21日
许可协议