数组是C语言中最基础也是最强大的数据结构之一。从内存角度看,数组实际上是一块连续的内存空间,用于存储相同类型的元素集合。这种连续存储特性带来两个关键优势:
典型的数组内存布局示例(以int arr[5]为例):
code复制地址: 0x1000 0x1004 0x1008 0x100C 0x1010
值: arr[0] arr[1] arr[2] arr[3] arr[4]
注意:C语言不检查数组越界,这是许多安全漏洞的根源。实际编程中必须自行确保下标有效性。
数组声明的基本语法看似简单,但实际使用中有多个变体需要注意:
c复制// 基础声明
int arr1[5]; // 未初始化,值随机
// 完全初始化
int arr2[5] = {1, 2, 3, 4, 5};
// 部分初始化(剩余元素自动补0)
int arr3[5] = {1, 2}; // => [1, 2, 0, 0, 0]
// 自动推导长度
int arr4[] = {1, 2, 3}; // 编译器自动计算长度为3
// C99新增指定初始化器
int arr5[5] = {[2] = 5, [4] = 9}; // => [0, 0, 5, 0, 9]
实际工程中推荐使用完全初始化或指定初始化器,避免未初始化导致的不可预测行为。
基础的for循环遍历是最常见的方式,但实际开发中根据场景不同有多种优化写法:
c复制// 常规写法
for(int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) {
printf("%d ", arr[i]);
}
// 指针遍历(更接近底层实现)
for(int *p = arr; p < arr + sizeof(arr)/sizeof(arr[0]); p++) {
printf("%d ", *p);
}
// 倒序遍历
for(int i = sizeof(arr)/sizeof(arr[0]) - 1; i >= 0; i--) {
printf("%d ", arr[i]);
}
实测技巧:在x86平台,指针遍历通常比下标遍历快约5-10%,但在现代编译器优化下差异已不明显。
数组作为函数参数传递时,实际上传递的是数组首元素的指针。这意味着:
典型用法示例:
c复制void printArray(int arr[], int size) {
for(int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
// 调用方式
int main() {
int nums[] = {1, 2, 3, 4, 5};
printArray(nums, sizeof(nums)/sizeof(nums[0]));
}
二维数组本质上是"数组的数组",在内存中仍然按行优先顺序连续存储。例如:
c复制int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
内存布局:
code复制[1,2,3,4,5,6,7,8,9,10,11,12]
这种布局特性导致:
当维度在运行时才能确定时,需要使用指针数组模拟二维数组:
c复制int rows = 3, cols = 4;
int **matrix = (int **)malloc(rows * sizeof(int *));
for(int i = 0; i < rows; i++) {
matrix[i] = (int *)malloc(cols * sizeof(int));
}
// 使用方式与普通二维数组相同
matrix[1][2] = 5;
// 释放内存
for(int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
原始示例中的冒泡排序可以进一步优化:
c复制void bubbleSort(int arr[], int n) {
for(int i = 0; i < n-1; i++) {
int swapped = 0;
for(int j = 0; j < n-i-1; j++) {
if(arr[j] > arr[j+1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
swapped = 1;
}
}
if(!swapped) break; // 提前终止
}
}
优化点:
针对已排序数组的高效搜索:
c复制int binarySearch(int arr[], int size, int target) {
int left = 0, right = size - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if(arr[mid] == target) {
return mid;
} else if(arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
C语言不检查数组越界,可能导致严重问题。防护措施包括:
c复制#define ARR_LEN 5
int arr[ARR_LEN];
for(int i = 0; i < ARR_LEN; i++) {...}
c复制int safeGet(int arr[], int size, int index) {
if(index < 0 || index >= size) {
fprintf(stderr, "Index %d out of bounds\n", index);
exit(EXIT_FAILURE);
}
return arr[index];
}
不同场景下的初始化选择:
c复制// 大数组高效初始化
int bigArr[10000];
memset(bigArr, 0, sizeof(bigArr)); // 全部初始化为0
数组名在大多数情况下会退化为指针,但有两个例外:
c复制int arr[5];
printf("%p %p\n", arr, &arr); // 值相同但类型不同
printf("%zu %zu\n", sizeof(arr), sizeof(&arr)); // 20 vs 8(64位系统)
C99引入的变长数组特性:
c复制void processArray(int size) {
int vla[size]; // 长度在运行时确定
// ...使用数组
}
注意事项:
C99允许直接创建匿名数组:
c复制// 传统方式
int *ptr = (int[]){1, 2, 3};
// 函数参数
printArray((int[]){1,2,3,4}, 4);
这种写法在需要临时数组时非常方便。
对于性能关键代码,可以手动展开循环:
c复制// 常规循环
for(int i = 0; i < 100; i++) {
arr[i] = i * 2;
}
// 展开4次
for(int i = 0; i < 100; i += 4) {
arr[i] = i * 2;
arr[i+1] = (i+1) * 2;
arr[i+2] = (i+2) * 2;
arr[i+3] = (i+3) * 2;
}
实测数据:在-O2优化下,展开4次可获得约15%性能提升,但代码可读性下降。
现代CPU对对齐访问有优化:
c复制// 保证16字节对齐
int aligned_arr[100] __attribute__((aligned(16)));
对齐访问可避免CPU进行多次内存读取,提升性能。
在嵌入式系统开发中,数组使用有几个特别注意事项:
c复制// 将数组放在特定内存区域
__attribute__((section(".fast_mem"))) int critical_arr[100];
在图像处理等高性能计算领域,数组使用要注意: