
在嵌入式 C 语言中,二维数组是一种非常有用的数据结构,用于存储和处理按行和列组织的数据。
在C语言中,二维数组的定义遵循以下语法结构:
数据类型 数组名[行数][列数];int array[3][4]; // 声明一个3行4列的二维数组定义了一个整数类型的二维数组array,包含3行4列,总共能存储12个整数。这些整数在内存中是线性排列的,即先填满第一行,再接着填充第二行,以此类推。
二维数组在内存中的布局是按一维线性方式排列的,但逻辑上它呈现二维结构,即有行和列的概念。
array[rows][cols]和一个元素array[i][j],其内存地址可以通过以下方式计算(假设数组的起始地址为base_address,且每个元素的大小为element_size):
address_of_element = base_address + (i * cols + j) * element_size以int array[3][4]为例,假设每个整数元素占4个字节,且数组的起始地址为2000(仅为示例,实际地址由操作系统分配)。
array[0][0]的地址为2000。array[0][1]的地址为2004(因为每个元素占4个字节)。array[0][2]的地址为2008。array[0][3]的地址为2012。array[1][0]的地址为2016(跳过了第一行的4个元素)。array[2][3]的地址为2044。地址 元素
2000 array[0][0]
2004 array[0][1]
2008 array[0][2]
2012 array[0][3]
2016 array[1][0]
2020 array[1][1]
2024 array[1][2]
2028 array[1][3]
2032 array[2][0]
2036 array[2][1]
2040 array[2][2]
2044 array[2][3]用代码来验证这种布局,可以通过指针运算:
#include <stdio.h>
int main() {
int arr[3][4];
int *p = (int *)arr;
for (int i = 0; i < 3 * 4; i++) {
p[i] = i;
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("arr[%d][%d] = %d, address: %p\n", i, j, arr[i][j], &arr[i][j]);
}
}
return 0;
}
先把二维数组当成一维数组,用指针给所有元素赋值,之后再用二维数组的常规访问方式输出每个元素及其地址。可以清晰看到,随着循环变量的推进,元素地址是连续递增的,充分证明了按行优先的存储特性。
这种内存布局的优势在于,访问同一行元素时,由于内存连续性,缓存命中率更高,能够加快数据访问速度。而且在进行一些底层算法,例如矩阵遍历、图像像素点扫描时,了解这种布局可以简化指针运算,高效处理数组元素 。
二维数组的初始化不会影响其内存布局方式,但会决定数组元素的初始值。
int array[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};int array[3][4] = {
{1, 2},
{3}
};
// 相当于:
// int array[3][4] = {
// {1, 2, 0, 0},
// {3, 0, 0, 0},
// {0, 0, 0, 0}
// };使用下标访问二维数组元素是最直接且常见的方法:
array[i][j]; // i表示行索引,j表示列索引索引都是从 0 开始计数。例如,对于前面定义的 array数组,要访问第二行第三列的元素,就用 matrix[1][2],它的值是 6。
代码示例:
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 访问第二行第三列的元素
int element = matrix[1][2];
printf("Element at row 1, column 2: %d\n", element); // 输出: 7
return 0;
}
在嵌入式系统中,这种精准访问常用于读取传感器矩阵数据、操控显示屏像素点等场景,每个元素对应一个物理量或者像素信息。
当我们有一个二维数组时,如int array[3][4];,可以定义一个指向其首元素的指针,即指向第一行第一个元素的指针:
int (*ptr)[4] = array; // ptr 是一个指向包含4个int元素的数组的指针此时,ptr 可以被用来访问二维数组的元素。例如,ptr[i][j] 相当于 array[i][j]。这里,ptr[i] 是一个指向第 i 行第一个元素的指针(类型也是 int (*)[4]),然后 [j] 用于访问该行的第 j 个元素。
例如:
#include <stdio.h>
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*q)[4];
q = arr;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
// 通过指向行的指针访问元素并打印
printf("%d ", (*(q + i))[j]);
}
printf("\n");
}
return 0;
}
另一种方法是定义一个指向 int 的指针,并让它指向二维数组的第一个元素。这种方法需要更复杂的索引计算来访问特定的元素:
int *ptr = &array[0][0]; // ptr 是一个指向 int 的指针要访问 array[i][j],我们需要计算正确的偏移量:
int value = *(ptr + i * 4 + j); // 假设每行有4个元素这里的 i * 4 + j 计算了从数组开始到 array[i][j] 的元素偏移量(假设数组是按行存储的,且每行有4个元素)。
例如:
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int *p = &matrix[0][0]; // 指向二维数组的第一个元素
// 访问第二行第三列的元素
int element = *(p + 1*4 + 2); // 1*4 + 2 是根据二维数组的内存布局计算出的偏移量
printf("Element at row 1, column 2 (via pointer): %d\n", element); // 输出: 7
return 0;
}
指针数组的构建:可以创建一个指针数组,其中每个指针指向二维数组的每一行。例如,对于int arr[3][4];,可以定义
int *row_pointers[3];然后通过循环初始化每个指针,如
for (int i = 0; i < 3; i++) {row_pointers[i] = arr[i];}这样,row_pointers数组中的每个元素就分别指向了arr的每一行。
元素访问方式:要访问二维数组中的元素arr[i][j],可以使用
*(row_pointers[i] + j)这里row_pointers[i]获取到指向第i行的指针,然后+ j进行列偏移,最后通过解引用*获取元素的值。
#include <stdio.h>
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int *row_pointers[3];
for (int i = 0; i < 3; i++) {
row_pointers[i] = arr[i];
}
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
// 通过指针数组访问元素并打印
printf("%d ", *(row_pointers[i] + j));
}
printf("\n");
}
return 0;
}
如果二维数组的大小是动态的,我们通常使用动态内存分配(如 malloc)来创建它,并使用指针来访问它。例如:
int rows = 3, cols = 4;
int **array = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
array[i] = malloc(cols * sizeof(int));
}
// 访问元素
array[i][j] = some_value;
// 释放内存
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);在这种情况下,array 是一个指向指针的指针(即 int **),每个 array[i] 都是一个指向第 i 行第一个元素的指针(即 int *)。
使用指针访问的优势: 在嵌入式系统中,尤其是在资源有限的情况下,通过指针访问二维数组可以更灵活地操作内存。例如,在一些实时数据处理场景中,通过指针可以快速地遍历数组元素,减少不必要的下标计算开销。同时,在与硬件寄存器或者内存映射 I/O 设备交互时,指针访问方式可以更好地适应底层硬件的地址访问要求。
为了方便和安全地访问二维数组元素,可以使用宏定义来封装访问逻辑。
宏定义与代码示例:
#include <stdio.h>
#include <assert.h>
// 辅助函数,用于访问二维数组的元素,并进行边界检查
static inline int safe_access_element(int *array, int row, int column, int width, int height) {
assert(row >= 0 && row < height && column >= 0 && column < width);
return array[row * width + column];
}
// 宏,用于方便地从二维数组名获取数组指针(因为数组名在大多数情况下会退化为指针)
#define ACCESS_ARRAY(array) (&(array)[0][0])
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int rows = 3;
int cols = 4;
// 使用函数安全地访问第二行第三列的元素
int element = safe_access_element(ACCESS_ARRAY(matrix), 1, 2, cols, rows);
printf("Element at row 1, column 2 (via function): %d\n", element); // 输出: 7
// 尝试访问一个无效的索引(这将触发断言失败)
// int invalidElement = safe_access_element(ACCESS_ARRAY(matrix), 3, 2, cols, rows); // 这行会触发断言错误
return 0;
}
使用宏定义ACCESS_ELEMENT可以简化二维数组元素的访问,同时提高代码的可读性和可维护性。在需要频繁访问二维数组元素的嵌入式应用程序中,这种方法特别有用。
在嵌入式C语言中,二维数组作为一种重要的数据结构,具有广泛的应用场景。
unsigned char image[height][width];来存储,其中height表示图像的高度(行数),width表示图像的宽度(列数),而unsigned char类型的元素可以存储每个像素的灰度值(通常范围是 0 - 255)。void mean_filter(unsigned char image[height][width]) {
unsigned char filtered_image[height][width];
for (int i = 1; i < height - 1; i++) {
for (int j = 1; j < width - 1; j++) {
int sum = 0;
for (int m = -1; m <= 1; m++) {
for (int n = -1; n <= 1; n++) {
sum += image[i + m][j + n];
}
}
filtered_image[i][j] = sum / 9;
}
}
// 将滤波后的图像数据复制回原始数组或进行其他操作
for (int i = 1; i < height - 1; i++) {
for (int j = 1; j < width - 1; j++) {
image[i][j] = filtered_image[i][j];
}
}
}float joint1_matrix[4][4];
float joint2_matrix[4][4];
// 初始化关节矩阵
//...
float end_effector_matrix[4][4];
matrix_multiply(end_effector_matrix, joint1_matrix, joint2_matrix);其中matrix_multiply函数用于实现矩阵乘法运算,通过嵌套循环来访问二维数组中的元素进行计算。
int temperature_array[num_rows][num_cols];
// 读取每个传感器的数据并存储到二维数组中
for (int i = 0; i < num_rows; i++) {
for (int j = 0; j < num_cols; j++) {
temperature_array[i][j] = read_temperature_sensor(i, j);
}
}之后,可以对这些数据进行分析,如查找温度异常点、计算平均温度等操作。
char maze[height][width];来存储,其中maze[i][j]的值可以表示该位置是墙壁(例如用'#'表示)、通道(用'.'表示)还是其他特殊元素(如宝藏、怪物等)。在游戏中,角色在迷宫中的移动可以通过遍历二维数组来实现,根据当前位置和移动方向来更新角色的位置,并检查是否遇到墙壁或其他特殊元素。int tic - tac - toe_board[3][3];,数组元素的值可以表示该位置是空白(0)、玩家 1 的棋子(1)还是玩家 2 的棋子(2)。在游戏过程中,通过更新二维数组中的元素来记录游戏状态,并根据游戏规则检查是否有玩家获胜或游戏是否平局。在嵌入式C语言编程中,二维数组是一种强大的数据结构,但使用时需要注意以下几点,以确保程序的稳定性和安全性。
综上所述,二维数组在内存中的布局是按一维线性方式排列的,但逻辑上呈现二维结构。在C语言中,二维数组通常是按行存储的。了解二维数组的内存布局有助于更好地管理内存和优化程序性能。