返回介绍

4. 数组

发布于 2024-10-12 21:58:09 字数 6697 浏览 0 评论 0 收藏 0

数组包含多个类型相同且连续存储的元素,其类型由元素类型和元素数量共同构成。

type name[ number_of_elements ];
  • 元素数量必须大于 0 的整数。(GNU 允许 0 长度数组)
  • 元素类型不能是函数类型或不完整类型,可改用指针。
typedef void (fn_t) ();

fn_t x[3];
     ^ error: declaration of 'x' as array of functions

可通过元素数量和类型长度计算数组长度,反之亦然。

int x[10];

assert(sizeof(x) == sizeof(x[0]) * 10);
assert(sizeof(x) / sizeof(x[0]) ==  10);

定长数组 (fixed-length)元素数量为编译期可确定常量(或通过初始化推断),可定义为全局变量; 变长数组 (variable-length, VLA)元素数量是运行期提供,只能用于函数内,且不能有静态声明。

static int X[] = {1, 2, 3};   // 相当于 X[3]。
static int Y[3];

size_t test (int n)
{
    int z[n];
    return sizeof(z);
}

int main (void)
{
    assert(sizeof(X) / sizeof(X[0]) == 3);
    assert(sizeof(Y) / sizeof(Y[0]) == 3);

    assert(test(10) == sizeof(int) * 10);
    
    return 0;
}

初始化

全局数组变量会自动初始化,而局部数组变量元素值未定,需显式初始化。

  • 不能初始化变长数组。(长度未定,无法确定元素数量)
  • 可省略数组长度,通过初始化列表推断。
  • 部分初始化时,其余元素为 0 值。
  • 初始化值数量不能超过限制。(GNU 警告并忽略多余值)
  • 不能以空表达式初始化。(GNU 允许)
int main (void)
{
    // int[3]: 多余逗号被忽略。
    int a[] = {1, 2, 3, };
    assert(sizeof(a) / sizeof(a[0]) == 3);

    // int[8]: 以索引指定。
    int b[] = {1, [6] = 100, 101};
    assert(sizeof(b) / sizeof(b[0]) == 8);
    assert(b[7] == 101);

    // 其余元素值为 0。
    int c[4] = { 1, 2 };
    assert(c[2] == 0);

    // 全部为 0。
    int d[4] = { 0 };  
    assert(d[1] == 0);

    return 0;
}
int main (void)
{
    typedef struct {
        int x;
        int y;
    } data_t;

    data_t x[] = {
        { .x = 1, .y = 1 },
        { .x = 1, .y = 1 },
        { .x = 1, .y = 1 },
    };

    return 0;
}
size_t test (int n)
{
    int z[n] = {1, 2, 3};
    ^~~ error: variable-sized object may not be initialized

多维数组

多维数组的元素也是数组,比如二维数据构成一个行列组成的表(矩阵)。

只有第一维长度可以省略。因为 int[][] 的元素类型是不完整类型 int[] ,编译器报错。

int main (void)
{
    int x[3][3] = { {0} };
    assert(x[2][2] == 0);

    int y[][3] = {       // row 0 : { 1,  2,  3}
        {1, 2, 3},       // row 1 : { 4,  5,  0}
        {4, 5},          // row 2 : { 0,  0,  0}
        [3] = {7, 8, 9}, // row 3 : { 7,  8,  9}
        [4][2] = 12,     // row 4 : { 0,  0, 12}
        {13, 14, 15},    // row 5 : {13, 14, 15}
    };

    assert(sizeof(y) / sizeof(int[3])  == 6);
    assert(sizeof(y) / sizeof(y[0][0]) == 18);
    assert(y[4][0] == 0);

    return 0;
}

多维数组依旧连续存储,将其展开,可看成是一维数组。

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

    // 计算数据总量,指针。
    int n = sizeof(x) / sizeof(x[0][0]);
    int *p = (int *)x;

    for (int i = 0; i < n; i++) {
        printf("%d, ", *p++);
    }

    printf("\n");
    return 0;
}

数组指针

数组名可看成隐式指向数组第一元素的指针常量(指针本身无法修改)。

  |<------------ &x, ap ------------->|
  |                                   |
  +===========+===========+===========+    int x[3];  // 数组
  |     1     |     2     |     3     |    p  = x;    // 元素指针
  +===========+===========+===========+    ap = &x;   // 数组指针
  |           |
  |<--- x --->|                            x[1]     == *(p + 1) == p[1]
  |<- &x[0] ->|                            *ap      == x
  |<--- p --->|                            (*ap)[1] == x[1]
  
int main (void)
{
    int x[] = {1, 2, 3};
    assert(x == &x[0]);

    // 元素指针(单个元素)。
    int *p = x;
    assert(p[0] == x[0]);

    p++;
    assert(*p == x[1]);

    // 数组指针(整个数组)。
    int (*ap)[] = &x;
    assert((*ap)[1] == x[1]);

    return 0;
}
int main (void)
{
    int x[] = {1, 2, 3};
    void *p = &x;
    
    // 类型转换。
    int (*ap)[];
    ap = (int(*)[])(p);

    assert((*ap)[1] == x[1]);

    return 0;
}

如果是二维数组,那么数组名就是一个 数组指针

  |<--- y[0] ---->|<--- y[1] ---->|
  |               |               |
  +=======+=======+=======+=======+
  |   1   |   2   |   3   |   4   |    int y[2][2];  // 数组
  +=======+=======+=======+=======+    p = y;        // 元素指针,元素也是数组。
  |               | 
  |<---- y ------>|                    p[1][1] == y[1][1] 
  |<--- &y[0] --->|                    *p      == y[0]
  |<---- p ------>|                    (*p)[1] == y[0][1]
  
int main (void)
{
    int y[][2] = { 
        {1, 2}, 
        {3, 4}, 
    };

    // 元素本身就是数组。
    // 必须是 int[2],因为 int[] 不确定长度,无法指针运算。
    int (*p)[2] = y;

    assert(p[0][1] == y[0][1]);
    assert((*p)[1] == y[0][1]);

    // 下一元素,也就是第二行数组。
    p++;
    assert((*p)[1] == y[1][1]);

    return 0;
}

指针数组

还有一种元素为指针类型的数组,被称作 指针数组

	array pointer  vs.  pointer array
  =============       =============

  数组指针:int (*ap)[]                // 指向整个数组的指针。
  指针数组:int* pa[]  或  int *pa[]   // 元素为指针的数组。
          ~~~~~~~~
          这个写法更易理解。
          
int main (void)
{
    int a = 1;
    int b = 2;
    int c = 3;

    int* x[] = { &a, &b, &c };
    assert(*(x[1]) == b);

    return 0;
}

既然元素是指针,而数组名又是指向第一元素的指针,那么指针数组名也可以看作指针的指针。

  char* x[] : 最易理解。
  char* *p  : 指针的指针,二级指针未必是数组。
  char **p  : 同上。
  
int main (void)
{
    char* x[] = { "aa", "bb", "cc" };
    char* *p = x;

    int n = sizeof(x) / sizeof(x[0]);
    for (int i = 0; i < n; i++) {
        assert(*p == x[i]);
        p++;
    }

    return 0;
}

数组参数

虽然数组作为参数传递时,总是隐式当作指针,但还是要尽可能声明为数组类型。

相比之下, sum (int x[])sum (int *x) 表达的意思未必相同。

int sum (int x[5])
{
    int n = 0;
    for (int i = 0; i < 5; i++) {
        n += x[i];
    }

    return n;
}

int main (void)
{
    int x[] = {1, 2, 3, 4, 5};
    assert(sum(x) == 15);
    return 0;
}

参数可以是变长数组,需额外传递长度。

// 和 int sum (int num, int x[]) 效果相同。
// 但 x[num] 更易理解,尤其是有更多参数的时候。

int sum (int num, int x[num])
{
    int n = 0;
    for (int i = 0; i < num; i++) {
        n += x[i];
    }

    return n;
}
// 同样可写成 int sum (int nrow, int ncol, int x[][ncol])
// 参数 int x[][ncol] 里的 ncol 不能省略,因为不能是不完整类型。
// 还是建议写完整。

int sum (int nrow, int ncol, int x[nrow][ncol])
{
    int n = 0;

    for (int r = 0; r < nrow; r++) {
        for (int c = 0; c < ncol; c++) {
            n += x[r][c];
        }
    }

    return n;
}

除了传递长度参数外,也可以特定标志结束,比如 NULL 等。

void test (char* ss[])
{
    while (ss && *ss) {
        puts(*ss++);
    }
}

int main (void)
{
    char* ss[] = { "aa", "bb", "cc", NULL };
    test(ss);
    return 0;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文