二维数组可表示矩阵或表格,三维数组可表示分层数据;在 C 语言中,多维数组在内存中仍按行优先顺序连续存放。一维数组的基础见教程前面章节;作为函数参数传递时与指针的关系需特别注意列数。本节说明声明、初始化、遍历及传参方式。

逻辑上 2 行 × 3 列(arr[2][3]) 1 2 3 4 5 6 arr[r][c] 内存里按行优先排成一条 1 2 3 4 5 6 第 0 行(3 个):1 2 3 第 1 行(3 个):4 5 6 不是 4 个数一行,而是每行 3 个
声明 int arr[2][3] 时,2 是行数(第一维),3 是列数(第二维),即 2 行 3 列。内存中仍按一维连续存放:先排第 0 行的 3 个元素,再排第 1 行(行优先)。

多维数组的含义

二维数组用行、列下标访问元素;三维数组再增加一维,常用于体数据或分层网格:

#include <stdio.h>

int main(void) {
    int arr[2][2] = { {10, 20}, {30, 40} };

    printf("2D Array Elements:\n");
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

输出:

2D Array Elements:
10 20
30 40

声明语法

type arrName[size1][size2]....[sizeN];
  • type:元素类型
  • arrName:数组名
  • size1…sizeN:各维长度(须为编译期常量表达式)
int arr[10][20];   /* 10 行 20 列 */

元素个数与占用字节

  • int arr[10][20]:共 10 × 20 = 200 个元素。
  • 若 int 为 4 字节,则数组占 800 字节。
int arr[10][20];
size_t total = sizeof(arr);
size_t rows = sizeof(arr) / sizeof(arr[0]);
size_t cols = sizeof(arr[0]) / sizeof(arr[0][0]);

二维数组初始化

int arr[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};

/* 或 */
int arr[3][4] = { {0, 1, 2, 3},
                  {4, 5, 6, 7},
                  {8, 9, 10, 11} };

初始化按行优先顺序填充;第一维长度可省略,列数须显式给出:

int arr[][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };

提供的初值少于元素总数时,其余元素初始化为 0。

下标访问

int arr[5] = {2, 4, 8, 12, 16};
printf("%d\n", arr[2]);

int mat[3][2] = { {0, 1}, {2, 3}, {4, 5} };
printf("%d\n", mat[1][0]);
printf("%d\n", mat[0][1]);

遍历二维数组

#include <stdio.h>

int main(void) {
    int arr[3][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 } };

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
            printf("arr[%d][%d]: %d   ", i, j, arr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

行优先存储

C 语言采用行优先布局;Fortran 等语言常用列优先。对 int mat[2][3],先把第 0 行的 3 个元素依次放进内存,再放第 1 行的 3 个元素,线性顺序为 1, 2, 3, 4, 5, 6。

int mat[2][3] = {{1, 2, 3}, {4, 5, 6}};
行下标初始化写法元素(列 0 → 列 2)在内存中的位置
mat[0][*](第 0 行){1, 2, 3}1,2,3连续的前 3 个
mat[1][*](第 1 行){4, 5, 6}4,5,6紧接其后的 3 个

因此 mat[0][0] 对应内存中第一个数 1,mat[1][2] 对应最后一个数 6;与开篇示意图中先排满一行、再排下一行的顺序一致。

作为函数参数

数组形参退化为指针,二维数组形参须指明列数(或等价写法),以便计算 arr[i][j] 的地址:

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

int main(void) {
    int mat[2][3] = { {1, 2, 3}, {4, 5, 6} };
    print2D(mat, 2);
    return 0;
}

三维数组

在二维的行、列两维之外再增加一维,可表示分层数据(例如两个时间切片、每个切片是一张 3 行 2 列的表)。写成 type arr_name[x][m][n] 时,第一维长度 x 表示层数,第二维 m 表示每层行数,第三维 n 表示每行元素个数;用 arr[i][j][k] 访问时,i 为层下标,j 为行下标,k 为行内下标。

下列把声明、带初值的定义与下标访问放在一起;带初值列表时,大括号由里到外与 n、m、x 对应:最内层括住每行的 n 个数,其外一层括住每层的 m 行,最外层括住 x 层。

/* 声明:x 层,每层 m 行,每行 n 个元素 */
type arr_name[x][m][n];
int cube[2][2][2];   /* x=m=n=2,共 8 个 int */

/* 定义并初始化:此处 x=2,m=3,n=2 */
int arr[2][3][2] = {
    { {1, 1}, {2, 3}, {4, 5} },
    { {6, 7}, {8, 9}, {10, 11} }
};
/* 等价下标:arr[0][0][0]=1, arr[0][0][1]=1, …, arr[1][2][1]=11 */

/* 有完整初值列表时,最外一维长度可省略(与二维 arr[][列数] 相同) */
int arr2[][3][2] = {
    { {1, 1}, {2, 3}, {4, 5} },
    { {6, 7}, {8, 9}, {10, 11} }
};

/* 访问 */
int v = arr[i][j][k];
方括号维长度符号含义上例 arr[2][3][2]
第一维x层数2
第二维m每层行数3
第三维n每行元素个数2

下面用上例 arr[2][3][2](即 x=2、m=3、n=2)做完整程序:三重循环中 i、j、k 分别遍历层、行与行内元素,并打印每个下标对应的值。

#include <stdio.h>

int main(void) {
    int arr[2][3][2] = {
        { {1, 1}, {2, 3}, {4, 5} },
        { {6, 7}, {8, 9}, {10, 11} }
    };

    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 3; ++j) {
            for (int k = 0; k < 2; ++k)
                printf("arr[%d][%d][%d] = %d   ", i, j, k, arr[i][j][k]);
            printf("\n");
        }
        printf("\n\n");
    }
    return 0;
}

三维数组在内存中按最外层维依次展开,每一层内部仍为行优先。作为函数参数传递时,除第一维外,其余各维大小须在形参中写明(规则与二维数组形参类似)。

典型应用

  • 矩阵运算与图像像素网格。
  • 关系型表格数据。
  • 棋盘、地图等二维格子模型。
  • 三维体素或科学计算网格。

本节说明了多维数组的存储与访问规则。下一节介绍为类型起别名的 typedef。