【问题标题】:Declaring array without specifying the row size? [duplicate]声明数组而不指定行大小? [复制]
【发布时间】:2021-07-13 18:45:15
【问题描述】:

a[][3] 声明如何正确,而 a[3][] 在 C 语言中是错误的?

【问题讨论】:

  • 简短的回答是:那是因为标准是这样写的。至于为什么标准这么说,我认为答案太长了。
  • T a[][3] 将是 T (*a)[3]。另外,你可以使用T* a[3];
  • 您能否详细说明或发送任何参考资料
  • a[3] 是一个包含三个对象的数组。当它们被标识为[] 时,它们有多大?没有办法说,除非你知道它们有多大,否则你不能制作一个对象数组。
  • 拥有a[][3],你就知道a[0]a[1]的类型……但你不知道a[??]有多少个;拥有a[3][] 你知道你拥有a[0]a[1]a[2],但你不知道这三个元素中的每一个到底是什么

标签: c multidimensional-array declaration incomplete-type


【解决方案1】:

您可以为不完整类型的对象声明标识符。例如,考虑以下声明:

extern int a[][3];

这告诉编译器,当使用a 时,它指的是由三个int 组成的数组。如果使用它,则必须在其他地方定义该数组。然而,要使用这个数组,编译器不需要知道它的大小。如果a的起始地址是x,而int是四个字节,我们可以通过以x开始计算元素a[i][j]的地址,加上大小i 三个int 的数组(每个数组3•4 个字节)和j int 的大小(每个4 个字节)。所以a[i][j]的地址是x + 3•4•i + 4•j

在定义这个数组的地方,不能不完整;编译器需要知道完整的大小才能知道要保留多少内存。

虽然您可以为不完整类型的对象声明标识符,但数组的元素必须具有完整类型。此规则在 C 2018 6.7.6.2 1 中进行了说明:“元素类型不得为不完整或函数类型。”在上面的计算中,如果不知道子数组的大小,我们就无法计算出a[i][j] 的地址。所以声明a[3][]是不允许的。

请注意,当使用初始化器定义数组时,我们可以省略第一个维度。但是,它仍然是通过初始化程序指定的;编译器检查初始化器并确定第一维必须是什么。所以,当数组被定义时,它的类型就完成了。例如,在:

int a[][3] = {
    { 0, 1, 2 },
    { 3, 4, 5 }
};

编译器会看到有两个子数组并且知道a 的类型是int [2][3]。这对于其他维度是不允许的,因为它会产生复杂性。虽然我在上面使用了结构良好的大括号,但语言允许省略它们,如下所示:

int a[][3] = { 0, 1, 2, 3, 4, 5 };

如果除了第一个维度之外的任何维度被省略,那么初始化器初始化哪些元素以及它们暗示的维度是什么都会产生歧义。

【讨论】:

    【解决方案2】:

    重要的是要记住,在初始化二维数组时,它是 有必要提及第二个(列)维度,而第一个 维度(行)是可选的 -

    因此声明,

    int arr[ 2 ][ 3 ] = { 12, 34, 23, 45, 56, 45 } ;
    int arr[ ][ 3 ] = { 12, 34, 23, 45, 56, 45 } ;
    

    完全可以接受,

    而,

    int arr[ 2 ][ ] = { 12, 34, 23, 45, 56, 45 } ;
    int arr[ ][ ] = { 12, 34, 23, 45, 56, 45 } ;
    

    永远不会工作。

    假设你是一个编译器,你被告知你的二维数组中有 3 行。您将如何根据此信息计算元素的位置? 在内部,所有这些元素都将以顺序格式存储,并且要计算该位置,您肯定需要 col。

    arr[i][j] = *( arrr + i * col + j )

    问题的详细答案为什么? Here

    【讨论】:

    • OP 似乎已经知道了,但问为什么会这样
    • @largest_prime_is_463035818 显然你的名字不正确。最大的素数不是 463....!为了您的帮助,我已经编辑了答案
    【解决方案3】:

    这样的声明没有初始化器

    T a[][3];
    

    (其中T 是某个类型说明符)声明了一个不完整类型的数组(数组的大小未知)。以这种方式声明的数组可能没有自动存储持续时间。但可以在文件范围内声明。在任何情况下,这样一个数组的元素类型(在上面的数组示例中元素类型是T[3])都应该是完整的。那就是你不能像例子那样声明一个数组

    T a[][];
    

    T a[3][];
    

    这是一个显示允许的数组声明的演示程序

    #include <stdio.h>
    
    int a[][3];
    
    int main(void) 
    {
        extern int a[][3];
            
        for ( size_t i = 0; i < 3; i++ )
        {
            a[0][i] = i;
        }
    
        for ( size_t i = 0; i < 3; i++ )
        {
            printf( "%d ", a[0][i] );
        }
        
        putchar( '\n' );
        
        return 0;
    }
    

    文件范围内的声明

    int a[][3];
    

    是暂定定义,等价于tp

    int a[1][3];
    

    编译器可以发出警告,指出声明 int a[][3] 被假定为声明为 int a[1][3]

    如果您将使用初始化器,例如

    int a[][3] =
    {
        { 1, 2, 3 },
        { 4, 5, 6 }
    }; 
    

    那么编译器将通过初始化器的数量来确定数组中元素的数量。上述数组的类型为int [2][3]。或者如果你会写例如

    int a[][3] = { [10] = { 1, 2, 3 } };
    

    那么数组将有 11 个元素。也就是说数组的类型将是int[11][3]

    您也可以使用不完整类型的数组作为函数参数,例如

    void f( int a[][3] );
    

    编译器会将参数调整为指向数组元素类型之类的指针类型

    void f( int ( *a )[3] );
    

    也就是说,这两个函数声明声明了同一个函数,并且两者可以同时出现在程序中,尽管编译器会发出一条消息,指出存在冗余声明。

    有趣的是,带有可变长度数组参数的函数声明不能​​被声明为例如

    void f( size_t, size_t, int[*][*] );
    

    您也可以声明一个指向不完整数组类型的指针,前提是您不会取消引用该指针。这是一个演示程序。

    #include <stdio.h>
    
    int main(void) 
    {
        int ( *p )[];
        
        printf( "sizeof( p ) = %zu\n", sizeof p );
        
        return 0;
    }
    

    它的输出可能看起来像

    sizeof( p ) = 8
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-13
      • 2020-04-24
      • 2012-04-11
      • 2015-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多