为什么2D数组的行为类似于1D指针数组而不是1D整数数组?

时间:2017-10-25 18:28:33

标签: c arrays pointers multidimensional-array

我一直在研究和测试我在C中的知识(我是一名新的计算机工程专业的学生),并遇到了一个我无法弄清楚的问题。

当尝试将2D数组传递给函数时,我了解到动态分配的数组不能这样做,因为编译器需要知道array [] [columns]。但是,我了解到2D数组存储在一维数组中,每个新行的元素都跟在前一行的元素之后。当我将一个数组名称作为指向数组的指针传递给函数时,这似乎是这种情况,我的代码工作正常。但是,在声明2D数组的函数中,它表现为指针数组。

#include <stdio.h>

void printArray(int *A, int* dimA) {
    for(int i = 0; i < dimA[0]; ++i) {
        for(int j = 0; j < dimA[1]; ++j) {
            printf("%3d", A[i*dimA[1] + j]);//This would work if the elements of A[] are the rows of a 2D array mapped into a 1D array
        }
    printf("\n\n");
    }
return;
}

int main(){
    int A[2][2] = {{1,2},{3,4}};
    int dimA[2] = {2,2};//dimensions of the array
    int i, j; 

    for(i = 0; i < dimA[0]; ++i) {
        for(j = 0; j < dimA[1]; ++j) {
            printf("%3d", *(A[i] + j)); //This would work if the elements of A[] are pointers
        }
        printf("\n\n");
    }

    for(i = 0; i < dimA[0]; ++i) {  //Same code as printArray function
        for(j = 0; j < dimA[1]; ++j) {
            printf("%3d", A[i*dimA[1] + j]);//This would work if the elements of A[] are the rows of a 2D array mapped into a 1D array
        }
        printf("\n\n");
    }

    printArray(A, dimA);
    return 0;
}

当数组被视为指针数组时,以下代码在main()中正确输出数组,但在被视为1D整数数组时则不能。但是,当我将相同的数组作为指针传递给printArray函数时,我可以将其视为一维整数数组并且它可以工作。任何帮助将不胜感激(我已经明白我可以使用指针数组,但我真的想了解问题是什么)。谢谢!

3 个答案:

答案 0 :(得分:4)

根据C标准(6.3.2.1 Lvalues,数组和函数指示符)

  

3除非它是sizeof运算符或一元&amp;的操作数。   operator,或者是用于初始化数组的字符串文字, an   具有类型''数组类型''的表达式将转换为   带有''指向类型'的指针的表达式,指向初始值   数组对象的元素,而不是左值。如果是数组   对象具有寄存器存储类,行为未定义。

因此在第一个for循环中

for(i = 0; i < dimA[0]; ++i) {
    for(j = 0; j < dimA[1]; ++j) {
        printf("%3d", *(A[i] + j)); //This would work if the elements of A[] are pointers
    }
    printf("\n\n");
}

表达式A[i]的类型为int[2]。转换为指针时,它的类型为int *。因此,对于每个i,表达式A[i]指向数组A的每个“行”的第一个元素。

表达式A[i] + j指向每行的第j个元素。因此,取消引用指针,您将获得数组第i行的第j个元素。

在第二个循环中

for(i = 0; i < dimA[0]; ++i) {  //Same code as printArray function
    for(j = 0; j < dimA[1]; ++j) {
        printf("%3d", A[i*dimA[1] + j]);//This would work if the elements of A[] are the rows of a 2D array mapped into a 1D array
    }
    printf("\n\n");
}

表达式A[i*dimA[1] + j]的类型为int *,并指向数组之外的i *dimA[1] + j“row”,它指向数组之外。所以循环没有意义。

该函数声明为

void printArray(int *A, int* dimA);

被称为

printArray(A, dimA);

具有类型int[2]的第二个参数确实已转换为指向数组的第一个元素的类型int *的指针。

对于第一个参数,它也被转换为指向其第一个元素的指针。那个数组的元素是什么?该二维数组的元素是int[2]类型的一维数组。因此,指向此类型对象的指针将具有类型int ( * )[2]

指针int *int ( * )[2]不兼容,因此编译器应发出诊断消息。

函数的正确声明应该看起来像

void printArray(int ( *A )[2], int *dimA);

答案 1 :(得分:1)

编译代码会发出一个警告,告诉我们发生了什么:

main.c:27:27: warning: format specifies type 'int' but the argument has type 'int *' [-Wformat]
            printf("%3d", A[i*dimA[1] + j]);//This would work if the elements of A[] are the rows of a 2D array mapped into a 1D array
                    ~~~   ^~~~~~~~~~~~~~~~
main.c:32:16: warning: incompatible pointer types passing 'int [2][2]' to parameter of type 'int *' [-Wincompatible-pointer-types]
    printArray(A, dimA);
               ^
main.c:3:22: note: passing argument to parameter 'A' here
void printArray(int *A, int* dimA) {

声明数组时:

int A[2][2] = {{1,2},{3,4}};
如你所说,

存储为一个连续的内存块。在内存中,这相当于:

int A[4] = {1,2,3,4};

但是,无论何时进行查找/取消引用值,根据类型,编译器都会隐式为您做一些簿记。对于第二种情况:

int A[4] = {1,2,3,4};

A[0] = *(&A + 0) = 1
A[1] = *(&A + 1) = 2
...

相当简单,索引只是基址的偏移量。但是,对于第一种情况:

      y  x
int A[2][2] = {{1,2},{3,4}};
                     y   x
A[0][0] = *(&A + 2 * 0 + 0) = *(&A + 0) = 1
A[1][0] = *(&A + 2 * 1 + 0) = *(&A + 2) = 3
...
事情开始变得有些混乱。

首先要注意的是,由于类型被声明为int[2][2],因此必须取消引用它两次。这就是第一个警告抱怨的内容。因为它只被解除引用一次,所以int **变为int *,与int不同。

要注意的第二件事是因为类型被声明为多维数组,编译器会为你做一些簿记。由于阵列在第一个维度上被取消引用,因此已经考虑了第二个维度的大小以跨越正确的位置,因此实际上不是col * j + i而是col * (col * j + i) + i,而不是A你想要什么!

要获得所需效果,您可以:

  1. int *投射到printArray。这就是您调用A[i*dimA[1] + j]函数时发生的情况,以及它的工作原理。

  2. 从最低维度访问数组。不要说A[0][i*dimA[1] + j],而是int。这将正确地取消引用 <table class='table table-striped table-bordered table-hover' id='dataTables-example'> <thead> <tr> <th>1</th> <th>2</th> <th>3</th> <th>4</th> <th>5</th> </tr> </thead> <tbody> <form name='form' method='post' action='test.php'> <tr> <td>TEXT</td> <td><input id='timepicker' type='text' name='timepicker' value='17:00' /></td> <td><input id='timepicker1' type='text' name='timepicker1' value='23:00' /></td> <td><input type='text' name='adresatas' value='123456'/></td> <input type='hidden' name='data' value='2017-10-15'/> <td><input type='submit' name='submit' value='Išsaugoti'></td> </tr> </form> </tbody> </table> 并且还有效地绕过簿记。

答案 2 :(得分:1)

  

当尝试将2D数组传递给函数时,我了解到使用动态分配的数组不能这样做,因为编译器需要知道array [] [columns]。

这是事实,因为您无法将任何数组传递给函数。你甚至无法在C中表达这样的概念,尽管你可以随意编写看起来像这样的代码。几乎在每个上下文中都会出现一个计算数组的表达式 - 包括函数调用表达式 - 数组值被一个指向第一个数组元素的指针替换。

从某种意义上说,2D数组是一个数组数组,而(数组)元素类型的维度是整个数组类型的一部分,是每个元素类型的一部分,以及指向第一个元素的指针类型的一部分。因此,该维度必须是要向其传递的任何函数参数类型(指向第一个元素的指针)的一部分。

最精确地表征为假,但是,即使对于2D阵列,其尺寸都是在运行时确定的。自1999年以来,C一直支持可变长度数组(虽然在C11中它是可选的),并且这些数据在动态分配的多维数组和指向不同维度的数组的指针上运行得非常好:

// Dynamically allocating a 2D array of runtime-determined dimensions:
unsigned rows = calculate_number_of_rows();
unsigned columns = calculate_number_of_columns();
int (*matrix)[columns] = malloc(rows * sizeof(*matrix));

它们也适用于接受此类指针的函数:

void do_something(unsigned rows, unsigned columns, int matrix[rows][columns]);

......或等同于......

void do_something(unsigned rows, unsigned columns, int matrix[][columns]);

......或......

void do_something(unsigned rows, unsigned columns, int (*matrix)[columns]);

这三种形式完全相同。

  

但是,我了解到2D数组存储在一维数组中,每个新行的元素都跟在前一行的元素之后。

2D数组是一维数组的数组。任何数组的元素都连续存储在内存中而没有填充,因此2D维数组( r c )的布局无法与1D的布局区分开来维度 r * c 的数组,但我建议不要在术语中考虑它 你用过。

  

当我将数组名称作为指向数组的指针传递给函数时,情况似乎就是这样,我的代码运行正常。

不要那样做。在实践中,它很可能完全像你说的那样工作,但你应该注意编译器发出的警告 - 它肯定应该发出警告。

  

但是,在声明2D数组的函数中,它表现为指针数组。

您尚未提供符合您描述的功能示例。当然可以传递指针数组,但很可能将指针传递给数组。请参阅上面的示例。