1. 数组基础:从零开始理解C语言数组
在C语言的世界里,数组是最基础也是最强大的数据结构之一。我第一次接触数组时,被它的简单和高效所震撼——它就像一排整齐的邮箱,每个格子都有编号,可以存放特定类型的数据。这种直观的存储方式,让数组成为处理批量数据的首选方案。
数组本质上是一块连续的内存空间,存储相同类型的元素。这种连续存储的特性带来了两个关键优势:一是可以通过索引快速访问任意元素(时间复杂度O(1)),二是对缓存友好(局部性原理)。举个例子,当我们处理学生成绩时,用数组存储比单独定义100个变量要高效得多:
c复制// 定义包含100个学生成绩的数组
float scores[100];
// 访问第5个学生的成绩
scores[4] = 92.5f; // 注意索引从0开始
数组的声明语法很简单:数据类型 数组名[元素个数]。但这里有几个新手常犯的错误:
- 数组大小必须是编译期常量(C99前)
- 索引从0开始,不是1
- 访问越界不会报错但会导致未定义行为
重要提示:数组越界是C语言中最危险的错误之一。它可能悄无声息地破坏其他内存数据,导致程序出现难以追踪的bug。良好的编程习惯是:始终检查数组索引是否有效。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 一维数组的实战应用
2.1 一维数组的初始化技巧
一维数组的初始化有多种灵活的方式,理解这些技巧能显著提升编码效率。最基本的初始化方式是在声明时用花括号列举值:
c复制int primes[5] = {2, 3, 5, 7, 11};
但C语言提供了更多便利的初始化方式:
- 不指定大小,编译器自动计算:
c复制int primes[] = {2, 3, 5, 7, 11}; // 自动确定为5个元素 - 部分初始化,其余自动补零:
c复制int arr[10] = {1, 2}; // 前两个为1和2,后面全0 - C99新增的指定初始化器:
c复制int arr[10] = {[3] = 5, [7] = 9}; // 只初始化第4和第8个元素
2.2 一维数组的典型算法
数组的真正威力体现在各种算法应用中。以下是几个必须掌握的数组算法:
1. 数组遍历
c复制for(int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) {
printf("%d ", arr[i]);
}
注意这里用sizeof计算数组长度的方法——这是获取静态数组长度的标准做法。
2. 查找元素
线性查找是最基础的方式:
c复制int linear_search(int arr[], int size, int target) {
for(int i = 0; i < size; i++) {
if(arr[i] == target) return i;
}
return -1;
}
对于有序数组,二分查找效率更高(O(log n))。
3. 数组排序
冒泡排序虽然效率不高(O(n²)),但实现简单:
c复制void bubble_sort(int arr[], int size) {
for(int i = 0; i < size-1; i++) {
for(int j = 0; j < size-i-1; j++) {
if(arr[j] > arr[j+1]) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
4. 数组去重
这是面试常见题,这里给出一个O(n²)的实现:
c复制int remove_duplicates(int arr[], int size) {
if(size <= 1) return size;
int new_size = 1;
for(int i = 1; i < size; i++) {
int j;
for(j = 0; j < new_size; j++) {
if(arr[i] == arr[j]) break;
}
if(j == new_size) arr[new_size++] = arr[i];
}
return new_size;
}
3. 二维数组:矩阵与表格处理
3.1 二维数组的内存布局
二维数组本质上是一维数组的数组。理解它的内存布局对高效使用至关重要。在C语言中,二维数组是"行优先"存储的——先连续存储第一行的所有元素,然后是第二行,依此类推。
c复制int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
在内存中的实际排列顺序是:1,2,3,4,5,6,7,8,9,10,11,12。这种布局特性对性能有重要影响——按行访问比按列访问更快,因为缓存命中率更高。
3.2 二维数组的初始化技巧
二维数组的初始化方式非常灵活:
c复制// 完全初始化
int arr1[2][3] = {{1,2,3}, {4,5,6}};
// 省略内部括号(按顺序填充)
int arr2[2][3] = {1,2,3,4,5,6};
// 部分初始化
int arr3[2][3] = {{1}, {4,5}}; // 其余元素为0
// 省略第一维大小
int arr4[][3] = {{1,2,3}, {4,5,6}}; // 自动推断为2行
3.3 二维数组的典型应用
1. 矩阵运算
c复制// 矩阵相加
void matrix_add(int a[][3], int b[][3], int result[][3], int rows) {
for(int i = 0; i < rows; i++) {
for(int j = 0; j < 3; j++) {
result[i][j] = a[i][j] + b[i][j];
}
}
}
2. 图像处理
二维数组非常适合表示像素矩阵。例如实现简单的图像模糊:
c复制void blur_image(unsigned char img[][100], int width, int height) {
unsigned char temp[height][width];
for(int i = 1; i < height-1; i++) {
for(int j = 1; j < width-1; j++) {
temp[i][j] = (img[i-1][j] + img[i+1][j] +
img[i][j-1] + img[i][j+1]) / 4;
}
}
// 将结果复制回原数组
for(int i = 1; i < height-1; i++) {
for(int j = 1; j < width-1; j++) {
img[i][j] = temp[i][j];
}
}
}
3. 游戏地图表示
二维数组可以完美表示棋盘、迷宫等游戏地图:
c复制#define ROWS 8
#define COLS 8
char chessboard[ROWS][COLS];
void init_chessboard() {
// 初始化棋盘
for(int i = 0; i < ROWS; i++) {
for(int j = 0; j < COLS; j++) {
if(i == 1) chessboard[i][j] = 'P'; // 白兵
else if(i == 6) chessboard[i][j] = 'p'; // 黑兵
else chessboard[i][j] = ' ';
}
}
// 放置其他棋子...
}
4. 高级数组技巧与性能优化
4.1 变长数组(VLA)的使用
C99引入了变长数组(Variable Length Array),允许数组长度在运行时确定。这在处理用户输入或动态问题时非常有用:
c复制void process_array(int size) {
float arr[size]; // 变长数组
for(int i = 0; i < size; i++) {
arr[i] = i * 0.5f;
}
// ...其他处理
}
但需要注意:
- VLA不能初始化
- 大尺寸VLA可能导致栈溢出
- 不是所有编译器都完全支持(尤其在嵌入式系统中)
4.2 数组与指针的关系
数组名在大多数情况下会退化为指向其首元素的指针。这种关系非常微妙但重要:
c复制int arr[5] = {1,2,3,4,5};
int *ptr = arr; // 等价于 &arr[0]
// 以下表达式等价
arr[2] == *(arr + 2) == *(ptr + 2) == ptr[2]
理解这种等价性对理解C语言的数组操作至关重要。例如,函数参数中的int arr[]实际上就是int *arr。
4.3 多维数组作为函数参数
传递多维数组给函数时,必须指定除第一维外的所有维度:
c复制// 正确的声明方式
void process_matrix(int mat[][10], int rows);
// 错误的声明方式
void process_matrix(int mat[][], int rows); // 编译错误
这是因为编译器需要知道每一行的大小,才能正确计算元素地址。
4.4 数组性能优化技巧
- 循环展开:减少循环开销
c复制// 普通循环
for(int i = 0; i < 100; i++) arr[i] *= 2;
// 展开4次的循环
for(int i = 0; i < 100; i += 4) {
arr[i] *= 2;
arr[i+1] *= 2;
arr[i+2] *= 2;
arr[i+3] *= 2;
}
- 避免缓存抖动:按内存顺序访问
c复制// 好:按行访问
for(int i = 0; i < 100; i++) {
for(int j = 0; j < 100; j++) {
arr[i][j] = 0;
}
}
// 差:按列访问
for(int j = 0; j < 100; j++) {
for(int i = 0; i < 100; i++) {
arr[i][j] = 0;
}
}
- 使用restrict关键字:告诉编译器指针不重叠
c复制void add_arrays(int *restrict a, int *restrict b, int *restrict c, int size) {
for(int i = 0; i < size; i++) {
c[i] = a[i] + b[i];
}
}
5. 常见陷阱与调试技巧
5.1 数组越界访问
这是最常见的数组相关错误。越界访问可能导致:
- 修改了其他变量
- 程序崩溃
- 安全漏洞(如缓冲区溢出攻击)
防御性编程建议:
- 始终检查数组索引
- 使用安全的库函数(如
memcpy_s代替memcpy) - 启用编译器的边界检查选项(如GCC的
-fsanitize=bounds)
5.2 数组大小计算错误
常见错误场景:
c复制int arr[] = {1,2,3,4,5};
int size = sizeof(arr); // 错误:得到的是字节数,不是元素个数
正确做法:
c复制int size = sizeof(arr) / sizeof(arr[0]);
5.3 多维数组参数传递错误
尝试传递动态大小的多维数组:
c复制void process(int **arr, int rows, int cols); // 不能这样传递静态二维数组
正确方式:
c复制// 方法1:传递一维数组并手动计算索引
void process(int *arr, int rows, int cols) {
arr[i * cols + j] = 0; // 访问第i行第j列
}
// 方法2:使用定宽数组
void process(int arr[][10], int rows);
5.4 数组与指针混淆
新手常犯的错误:
c复制char str1[] = "hello";
char *str2 = "world";
str1 = "new"; // 错误:不能给数组赋值
str2 = "new"; // 正确:可以改变指针指向
理解数组和指针的区别至关重要:
- 数组名是常量指针,不能重新赋值
- 指针变量可以指向不同内存位置
6. 实战案例:用数组实现简单数据库
让我们用一个综合案例展示数组的强大能力——实现一个简单的学生成绩管理系统:
c复制#include <stdio.h>
#include <string.h>
#define MAX_STUDENTS 100
typedef struct {
int id;
char name[50];
float score;
} Student;
Student database[MAX_STUDENTS];
int count = 0;
void add_student(int id, const char *name, float score) {
if(count >= MAX_STUDENTS) {
printf("数据库已满\n");
return;
}
database[count].id = id;
strncpy(database[count].name, name, 49);
database[count].name[49] = '\0';
database[count].score = score;
count++;
}
void print_students() {
printf("ID\tName\tScore\n");
for(int i = 0; i < count; i++) {
printf("%d\t%s\t%.1f\n", database[i].id, database[i].name, database[i].score);
}
}
void search_by_name(const char *name) {
int found = 0;
for(int i = 0; i < count; i++) {
if(strcmp(database[i].name, name) == 0) {
printf("找到学生: %d\t%s\t%.1f\n",
database[i].id, database[i].name, database[i].score);
found = 1;
}
}
if(!found) printf("未找到学生: %s\n", name);
}
void sort_by_score() {
for(int i = 0; i < count-1; i++) {
for(int j = 0; j < count-i-1; j++) {
if(database[j].score < database[j+1].score) {
Student temp = database[j];
database[j] = database[j+1];
database[j+1] = temp;
}
}
}
}
int main() {
add_student(101, "张三", 85.5);
add_student(102, "李四", 92.0);
add_student(103, "王五", 78.5);
printf("所有学生:\n");
print_students();
printf("\n按姓名搜索:\n");
search_by_name("李四");
printf("\n按成绩排序:\n");
sort_by_score();
print_students();
return 0;
}
这个案例展示了如何用数组实现基本CRUD操作、搜索和排序功能。在实际项目中,当数据量变大时,可能需要考虑更高效的数据结构,但对于小型应用,数组是完全够用的。
7. 数组与其他数据结构的结合
7.1 数组实现栈
栈是一种LIFO(后进先出)数据结构,用数组实现非常简单:
c复制#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top;
} Stack;
void init_stack(Stack *s) {
s->top = -1;
}
int is_empty(Stack *s) {
return s->top == -1;
}
int is_full(Stack *s) {
return s->top == MAX_SIZE - 1;
}
void push(Stack *s, int value) {
if(is_full(s)) {
printf("栈已满\n");
return;
}
s->data[++s->top] = value;
}
int pop(Stack *s) {
if(is_empty(s)) {
printf("栈为空\n");
return -1; // 错误值
}
return s->data[s->top--];
}
7.2 数组实现队列
队列是FIFO(先进先出)结构,数组实现稍复杂:
c复制#define QUEUE_SIZE 100
typedef struct {
int data[QUEUE_SIZE];
int front;
int rear;
int size;
} Queue;
void init_queue(Queue *q) {
q->front = 0;
q->rear = -1;
q->size = 0;
}
int is_empty(Queue *q) {
return q->size == 0;
}
int is_full(Queue *q) {
return q->size == QUEUE_SIZE;
}
void enqueue(Queue *q, int value) {
if(is_full(q)) {
printf("队列已满\n");
return;
}
q->rear = (q->rear + 1) % QUEUE_SIZE;
q->data[q->rear] = value;
q->size++;
}
int dequeue(Queue *q) {
if(is_empty(q)) {
printf("队列为空\n");
return -1;
}
int value = q->data[q->front];
q->front = (q->front + 1) % QUEUE_SIZE;
q->size--;
return value;
}
7.3 数组实现哈希表
虽然链表是解决哈希冲突的常见方法,但数组也能实现简单哈希表:
c复制#define TABLE_SIZE 100
#define EMPTY -1
#define DELETED -2
typedef struct {
int keys[TABLE_SIZE];
int values[TABLE_SIZE];
} HashTable;
void init_table(HashTable *ht) {
for(int i = 0; i < TABLE_SIZE; i++) {
ht->keys[i] = EMPTY;
}
}
int hash(int key) {
return key % TABLE_SIZE;
}
void insert(HashTable *ht, int key, int value) {
int index = hash(key);
int original = index;
while(ht->keys[index] != EMPTY && ht->keys[index] != DELETED && ht->keys[index] != key) {
index = (index + 1) % TABLE_SIZE;
if(index == original) {
printf("哈希表已满\n");
return;
}
}
ht->keys[index] = key;
ht->values[index] = value;
}
int search(HashTable *ht, int key) {
int index = hash(key);
int original = index;
while(ht->keys[index] != EMPTY) {
if(ht->keys[index] == key) {
return ht->values[index];
}
index = (index + 1) % TABLE_SIZE;
if(index == original) break;
}
return -1; // 未找到
}
8. 现代C语言中的数组新特性
8.1 复合字面量
C99引入了复合字面量,允许创建匿名数组:
c复制// 传统方式
int arr1[] = {1,2,3,4};
// 复合字面量
int *ptr = (int[]){1,2,3,4}; // 匿名数组
// 作为函数参数
print_array((int[]){1,2,3,4}, 4);
这在需要临时数组时非常方便,避免了单独定义数组变量的麻烦。
8.2 指定初始化器的增强
C99/C11对数组初始化做了更多增强:
c复制// 指定范围的初始化
int arr[100] = {[0 ... 9] = 1, [10 ... 19] = 2};
// 与常规初始化混合
int arr2[10] = {1, 2, [4] = 5, 6, [8] = 9};
8.3 静态断言检查数组大小
C11的_Static_assert可以在编译时检查数组大小:
c复制#define ARR_SIZE 10
int arr[ARR_SIZE];
_Static_assert(ARR_SIZE > 5, "数组太小");
这比运行时检查更安全高效。
8.4 泛型选择与数组
C11的泛型选择可以写出更通用的数组处理代码:
c复制#define print_array(arr, size) _Generic((arr), \
int*: print_int_array, \
float*: print_float_array \
)(arr, size)
void print_int_array(int *arr, int size) {
for(int i = 0; i < size; i++) printf("%d ", arr[i]);
}
void print_float_array(float *arr, int size) {
for(int i = 0; i < size; i++) printf("%f ", arr[i]);
}
9. 性能对比:数组与其他数据结构
9.1 数组 vs 链表
| 特性 | 数组 | 链表 |
|---|---|---|
| 内存布局 | 连续内存 | 非连续内存 |
| 随机访问 | O(1) | O(n) |
| 插入/删除 | O(n) | O(1)(已知位置) |
| 缓存友好度 | 高 | 低 |
| 内存开销 | 仅数据 | 数据+指针 |
| 大小调整 | 固定大小或昂贵调整 | 动态调整 |
适用场景:
- 数组:频繁随机访问、已知最大大小、需要缓存效率
- 链表:频繁插入删除、大小变化大、不需要随机访问
9.2 数组 vs 动态数组(C++ vector)
虽然纯C没有vector,但可以对比动态数组的实现:
| 特性 | 静态数组 | 动态数组 |
|---|---|---|
| 大小 | 编译期固定 | 运行时可变 |
| 内存管理 | 自动 | 手动 |
| 扩容成本 | 不能扩容 | 需要重新分配内存 |
| 访问速度 | 略快 | 略慢(间接访问) |
| 安全性 | 可能越界 | 可边界检查 |
9.3 多维数组 vs 数组的数组
在C语言中,真正的多维数组和指针数组的性能特性不同:
c复制// 真正的二维数组(连续内存)
int arr1[10][10];
// 指针数组(可能不连续)
int *arr2[10];
for(int i = 0; i < 10; i++) {
arr2[i] = malloc(10 * sizeof(int));
}
性能对比:
- 真正多维数组:缓存友好,访问快,但所有行必须等长
- 指针数组:每行可以不同长度,但访问稍慢,可能缓存不命中
10. 嵌入式系统中的数组特殊考量
在嵌入式开发中,数组使用有一些特殊注意事项:
10.1 内存受限环境的小技巧
- 使用位数组节省空间:
c复制#define BIT_ARRAY_SIZE 100
#define WORD_SIZE (sizeof(unsigned int) * 8)
unsigned int bit_array[(BIT_ARRAY_SIZE + WORD_SIZE - 1) / WORD_SIZE];
void set_bit(int index) {
bit_array[index / WORD_SIZE] |= 1 << (index % WORD_SIZE);
}
int get_bit(int index) {
return (bit_array[index / WORD_SIZE] >> (index % WORD_SIZE)) & 1;
}
- 使用联合体实现变体数组:
c复制typedef union {
int i;
float f;
char c;
} Variant;
Variant arr[100];
arr[0].i = 10;
arr[1].f = 3.14;
10.2 避免动态内存分配
在嵌入式系统中,通常避免使用malloc:
- 使用静态数组替代动态分配
- 如果必须动态大小,使用最大可能尺寸的静态数组
- 或者使用内存池技术
10.3 寄存器数组访问
在操作硬件寄存器时,数组非常有用:
c复制#define GPIO_BASE 0x40020000
volatile uint32_t *gpio = (uint32_t *)GPIO_BASE;
// 访问第n个GPIO寄存器
gpio[n] = value;
10.4 内存对齐问题
嵌入式系统对内存对齐要求严格:
c复制// 保证数组按4字节对齐
__attribute__((aligned(4))) uint8_t buffer[128];
11. 数组在算法竞赛中的应用
在编程竞赛中,数组技巧可以大幅提升解题效率:
11.1 前缀和数组
快速计算区间和的技巧:
c复制void build_prefix_sum(int arr[], int n, int prefix[]) {
prefix[0] = arr[0];
for(int i = 1; i < n; i++) {
prefix[i] = prefix[i-1] + arr[i];
}
}
int range_sum(int prefix[], int l, int r) {
return l == 0 ? prefix[r] : prefix[r] - prefix[l-1];
}
11.2 差分数组
高效处理区间更新:
c复制void build_diff(int arr[], int n, int diff[]) {
diff[0] = arr[0];
for(int i = 1; i < n; i++) {
diff[i] = arr[i] - arr[i-1];
}
}
void range_add(int diff[], int n, int l, int r, int val) {
diff[l] += val;
if(r + 1 < n) diff[r+1] -= val;
}
void restore(int diff[], int n, int arr[]) {
arr[0] = diff[0];
for(int i = 1; i < n; i++) {
arr[i] = arr[i-1] + diff[i];
}
}
11.3 滑动窗口技巧
解决子数组/子串问题的利器:
c复制int max_subarray(int arr[], int n, int k) {
int max_sum = 0;
int window_sum = 0;
// 计算第一个窗口
for(int i = 0; i < k; i++) {
window_sum += arr[i];
}
max_sum = window_sum;
// 滑动窗口
for(int i = k; i < n; i++) {
window_sum += arr[i] - arr[i-k];
if(window_sum > max_sum) {
max_sum = window_sum;
}
}
return max_sum;
}
12. 从数组到指针:深入理解关系
12.1 数组名的特殊性质
数组名在大多数表达式中会转换为指向其首元素的指针,但有例外:
sizeof(arr)返回整个数组的大小&arr产生指向整个数组的指针(类型是int(*)[N])- 字符串字面量初始化字符数组时
c复制int arr[5] = {1,2,3,4,5};
int *p1 = arr; // 指向首元素
int (*p2)[5] = &arr; // 指向整个数组
printf("%p %p\n", (void*)p1, (void*)p2); // 值相同但类型不同
12.2 指针算术与数组访问
指针算术是数组索引的底层实现:
c复制int arr[5] = {10,20,30,40,50};
int *p = arr;
// 以下表达式等价
arr[2] == *(arr + 2) == *(p + 2) == p[2]
理解这一点对理解C语言的数组操作至关重要。
12.3 数组指针与指针数组
这两个概念常被混淆:
- 数组指针:指向数组的指针
c复制int (*arr_ptr)[10]; // 指向含10个int的数组 - 指针数组:元素为指针的数组
c复制int *ptr_arr[10]; // 含10个int指针的数组
12.4 多维数组的指针表示
理解多维数组的指针表示是进阶关键:
c复制int arr[3][4];
// arr的类型是int[3][4],会退化为int(*)[4]
// arr[i]的类型是int[4],会退化为int*
// arr[i][j]的类型是int
13. 标准库中的数组工具函数
13.1 <string.h>中的数组函数
虽然主要用于字符串,但这些函数也适用于一般数组:
memcpy:内存拷贝c复制int src[5] = {1,2,3,4,5}; int dest[5]; memcpy(dest, src, sizeof(src));memmove:安全的内存移动(处理重叠区域)memset:内存设置c复制int arr[10]; memset(arr, 0, sizeof(arr)); // 清零memcmp:内存比较c复制if(memcmp(arr1, arr2, sizeof(arr1)) == 0) { printf("数组内容相同\n"); }
13.2 <stdlib.h>中的数组相关函数
qsort:快速排序c复制int compare(const void *a, const void *b) { return (*(int*)a - *(int*)b); } int arr[] = {5,3,9,1,7}; qsort(arr, 5, sizeof(int), compare);bsearch:二分查找c复制int key = 7; int *result = bsearch(&key, arr, 5, sizeof(int), compare); if(result) printf("找到%d\n", *result);
13.3 自定义数组工具函数
实现一些常用工具函数能提高编码效率:
数组反转
c复制void reverse_array(int arr[], int size) {
for(int i = 0; i < size/2; i++) {
int temp = arr[i];
arr[i] = arr[size-i-1];
arr[size-i-1] = temp;
}
}
数组填充
c复制void fill_array(int arr[], int size, int value) {
for(int i = 0; i < size; i++) {
arr[i] = value;
}
}
数组比较
c复制int compare_arrays(int a[], int b[], int size) {
for(int i = 0; i < size; i++) {
if(a[i] != b[i]) return 0;
}
return 1;
}
14. 数组的最佳实践与代码风格
14.1 防御性编程技巧
- 边界检查
c复制void safe_array_access(int arr[], int size, int index) {
if(index < 0 || index >= size) {
fprintf(stderr, "数组索引%d越界(大小=%d)\n", index, size);
exit(EXIT_FAILURE);
}
// 安全访问
printf("%d\n", arr[index]);
}
- 使用assert
c复制#include <assert.h>
void process_array(int arr[], int size) {
assert(size > 0 && "数组大小必须为正");
assert(arr != NULL && "数组指针不能为NULL");
// ...
}
- 初始化所有元素
c复制int arr[100] = {0}; // 全部初始化为0
14.2 可读性建议
- 避免魔数
c复制// 不好
int scores[30];
// 好
#define MAX_STUDENTS 30
int scores[MAX_STUDENTS];
- 合理使用typedef
c复制typedef float Matrix[100][100];
void multiply(Matrix a, Matrix b, Matrix result) {
// ...
}
- 注释多维数组的维度
c复制/* 参数说明:
* matrix - 一个rows×cols的矩阵
*/
void process_matrix(int rows, int cols, int matrix[rows][cols]) {
// ...
}
14.3 性能优化建议
- 循环展开
c复制// 展开4次的循环
for(int i = 0; i < size; i += 4) {
arr[i] *= 2;
arr[i+1] *= 2;
arr[i+2] *= 2;
arr[i+3] *= 2;
}
- 避免不必要的边界检查
c复制// 编译器可能优化掉这个检查
for(int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) {
arr[i] *= 2;
}
- 使用const正确性
c复制// 明确表示不会修改数组内容
int sum_array(const int arr[], int size) {
int sum = 0;
for(int i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
15. 数组在图形编程中的应用
15.1 像素缓冲区
图形编程中常用一维数组表示二维像素:
c复制#define WIDTH 800
#define HEIGHT 600
typedef struct {
unsigned char r, g, b;
} Pixel;
Pixel framebuffer[WIDTH * HEIGHT];
void set_pixel(int x, int y, Pixel p) {
if(x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) {
framebuffer[y * WIDTH + x] = p;
}
}
Pixel get_pixel(int x, int y) {
return framebuffer[y * WIDTH + x];
}
15.2 图像卷积运算
数组在图像滤波中发挥重要作用:
c复制void apply_kernel(float kernel[3][3], Pixel *src, Pixel *dst, int width, int height) {
for(int y = 1; y < height-1; y++) {
for(int x = 1; x < width-1; x++) {
float r = 0, g = 0, b = 0;
for(int ky = -1; ky <= 1; ky++) {
for(int kx = -1; kx <= 1; kx++) {
Pixel p = src[(y+ky)*width + (x+kx)];
float k = kernel[ky+1][kx+1];
r += p.r * k;
g += p.g * k;
b += p.b * k;
}
}
dst[y*width + x].r = (unsigned char)fmax(0, fmin(255, r));
dst[y*width + x].g = (unsigned char)fmax(0, fmin(255, g));
dst[y*width + x].b = (unsigned char)fmax(0, fmin(255, b));
}
}
}
15.3 3D顶点数据
3D图形中使用数组存储顶点和索引:
c复制typedef struct {
float x, y, z;
} Vertex;
typedef struct {
float r, g, b, a;
} Color;
Vertex vertices[] = {
{-1.0, -1.0, 0.0},
{1.0, -1.0, 0.0},
{0.0, 1.0, 0.0}
};
Color colors[] = {
{1.0, 0.0, 0.0, 1.0},
{0.0, 1.0, 0.0, 1.0},
{0.0, 0.0, 1.0, 1
