关于指针和多维数组的混淆

发布于 2024-09-26 21:27:34 字数 562 浏览 0 评论 0原文

如果以下情况可行:

MyFunction(int *array, int size)
{
    for(int i=0 ; i<size ; i++)
    {
        printf(“%d”, array[i]);
    }
}

main()
{
    int array[6] = {0, 1, 2, 3, 4, 5};
    MyFunction(array, 6);
}

为什么以下情况不可行?

MyFunction(int **array, int row, int col)
{
    for(int i=0 ; i<row ; i++)
    {
        for(int j=0 ; j<col ; j++)
        {
            printf(“%d”, array[i][j]);
        }
    }
}

main()
{
    int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
    MyFunction(array, 3, 3);
}

If the following is possible:

MyFunction(int *array, int size)
{
    for(int i=0 ; i<size ; i++)
    {
        printf(“%d”, array[i]);
    }
}

main()
{
    int array[6] = {0, 1, 2, 3, 4, 5};
    MyFunction(array, 6);
}

Why the following is not?

MyFunction(int **array, int row, int col)
{
    for(int i=0 ; i<row ; i++)
    {
        for(int j=0 ; j<col ; j++)
        {
            printf(“%d”, array[i][j]);
        }
    }
}

main()
{
    int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
    MyFunction(array, 3, 3);
}

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

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

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

不再让梦枯萎 2024-10-03 21:27:34

首先,一些标准语言:

6.3.2.1 左值、数组和函数指示符
...
3 除非它是 sizeof 运算符或一元 & 的操作数。运算符,或者是用于初始化数组的字符串文字,类型为“类型数组”的表达式将转换为类型为“指向类型的指针”的表达式,该表达式指向数组对象的初始元素并且不是左值。如果数组对象具有寄存器存储类,则行为未定义。

给定声明,

int myarray[3][3];

myarray类型是“int 的 3 元素数组的 3 元素数组”。按照上面的规则,当您编写

MyFunction(myarray, 3, 3);

表达式时,myarray的类型会从“3元素数组的3元素数组”隐式转换(“衰减”) >int”到“指向 int 的 3 元素数组的指针”,或 int (*)[3]

因此,您的函数原型需要

int MyFunction(int (*array)[3], int row, int col)

注意,int **arrayint (*array)[3]相同;指针算术将会不同,因此您的下标最终不会指向正确的位置。请记住,数组索引是根据指针算术定义的:a[i] == *(a+i), a [i][j] == *(*(a + i) + j)a+i 将产生不同的值,具体取决于 aint ** 还是 int (*)[N]

这个特定的示例假设您始终传递一个 Nx3 元素的 int 数组;如果您想处理任何 NxM 大小的数组,则不太灵活。解决这个问题的一种方法是显式传递数组中第一个元素的地址,因此您只需传递一个简单的指针,然后手动计算正确的偏移量:

void MyFunction(int *arr, int row, int col)
{
  int i, j;
  for (i = 0; i < row; i++)
     for (j = 0; j < col; j++)
       printf("%d", a[i*col+j]);
}

int main(void)
{
  int myarray[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
  ...
  MyFunction(&myarray[0][0], 3, 3);

因为我们传递了一个指向int的简单指针,我们不能在MyFunc中使用双下标; arr[i] 的结果是一个整数,而不是指针,因此我们必须在一个下标操作中计算数组的完整偏移量。请注意,此技巧仅适用于真正的多维数组。

现在,** 可以表示以二维结构组织的值,但是以不同的方式构建的。例如:

void AnotherFunc(int **arr, int row, int col)
{
  int i, j;
  for (i = 0; i < row; i++)
    for (j = 0; j < col; j++)
      printf("%d", arr[i][j]);
}

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

  int *a[3] = {d0, d1, d2};

  AnotherFunc(a, 3, 3);
  ...
}

按照上面的规则,当表达式 d0d1d2 出现在 a 的初始化器中时code>,它们的类型都从“int的三元素数组”转换为“指向int的指针”。类似地,当表达式 a 出现在对 AnotherFunc 的调用中时,其类型从“指向 int 的指针的三元素数组”转换为“指向 int 指针的指针”。

请注意,在 AnotherFunc 中,我们为两个维度添加下标,而不是像在 MyFunc 中那样计算偏移量。这是因为 a 是一个指针值数组。表达式arr[i]获取距离arr位置的第i个指针值偏移量;然后我们找到距离该指针值的第 j 个整数值偏移量。

下表可能会有所帮助 - 它显示了各种数组表达式的类型以及它们根据其声明衰减为什么(T (*)[N] 是指针类型,而不是数组类型,因此它不衰减):

Declaration            Expression            Type            Implicitly Converted (Decays) to
-----------            ----------            ----            --------------------------------
     T a[N]                     a            T [N]           T *
                               &a            T (*)[N]
                               *a            T
                             a[i]            T

  T a[M][N]                     a            T [M][N]        T (*)[N]
                               &a            T (*)[M][N] 
                               *a            T [N]           T *
                             a[i]            T [N]           T *
                            &a[i]            T (*)[N] 
                            *a[i]            T
                          a[i][j]            T

T a[L][M][N]                    a            T [L][M][N]     T (*)[M][N]
                               &a            T (*)[L][M][N]
                               *a            T [M][N]        T (*)[N]
                             a[i]            T [M][N]        T (*)[N]
                            &a[i]            T (*)[M][N]
                            *a[i]            T [N]           T *
                          a[i][j]            T [N]           T *
                         &a[i][j]            T (*)[N]
                         *a[i][j]            T 
                       a[i][j][k]            T

高维数组的模式应该是清晰的。

First, some standard language:

6.3.2.1 Lvalues, arrays, and function designators
...
3 Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type "array of type" is converted to an expression with type "pointer to type" that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

Given the declaration

int myarray[3][3];

the type of myarray is "3-element array of 3-element array of int". Going by the rule above, when you write

MyFunction(myarray, 3, 3);

the expression myarray has its type implicitly converted ("decay") from "3-element array of 3-element array of int" to "pointer to 3-element array of int", or int (*)[3].

Thus, your function prototype would need to be

int MyFunction(int (*array)[3], int row, int col)

Note that int **array is not the same as int (*array)[3]; the pointer arithmetic will be different, so your subscripts won't wind up pointing to the right places. Remember that array indexing is defined in terms of pointer arithmetic: a[i] == *(a+i), a[i][j] == *(*(a + i) + j). a+i will yield a different value depending on whether a is an int ** or an int (*)[N].

This particular example assumes you're always passing an Nx3-element array of int; not terribly flexible if you want to deal with any NxM-sized array. One way to get around this would be to explicitly pass the address of the first element in the array, so you're just passing a simple pointer, and then compute the proper offset manually:

void MyFunction(int *arr, int row, int col)
{
  int i, j;
  for (i = 0; i < row; i++)
     for (j = 0; j < col; j++)
       printf("%d", a[i*col+j]);
}

int main(void)
{
  int myarray[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
  ...
  MyFunction(&myarray[0][0], 3, 3);

Since we pass a simple pointer to int, we can't use a double subscript in MyFunc; the result of arr[i] is an integer, not a pointer, so we have to compute the full offset into the array in the one subscript operation. Note that this trick will only work for truly multidimensional arrays.

Now, a ** can indicate values that are organized in a 2-D structure, but one that was built a different way. For example:

void AnotherFunc(int **arr, int row, int col)
{
  int i, j;
  for (i = 0; i < row; i++)
    for (j = 0; j < col; j++)
      printf("%d", arr[i][j]);
}

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

  int *a[3] = {d0, d1, d2};

  AnotherFunc(a, 3, 3);
  ...
}

Going by the rule above, when the expressions d0, d1, and d2 appear in the initializer for a, their types are all converted from "3-element array of int" to "pointer to int". Similarly, when the expression a appears in the call to AnotherFunc, its type is converted from "3-element array of pointer to int" to "pointer to pointer to int".

Note that in AnotherFunc we subscript both dimensions instead of computing the offset like we did in MyFunc. That's because a is an array of pointer values. The expression arr[i] gets us the i'th pointer value offset from the location arr; we then find the j'th integer value offset from that pointer value.

The following table might help - it shows the types of various array expressions and what they decay to based on their declarations (T (*)[N] is a pointer type, not an array type, so it doesn't decay):

Declaration            Expression            Type            Implicitly Converted (Decays) to
-----------            ----------            ----            --------------------------------
     T a[N]                     a            T [N]           T *
                               &a            T (*)[N]
                               *a            T
                             a[i]            T

  T a[M][N]                     a            T [M][N]        T (*)[N]
                               &a            T (*)[M][N] 
                               *a            T [N]           T *
                             a[i]            T [N]           T *
                            &a[i]            T (*)[N] 
                            *a[i]            T
                          a[i][j]            T

T a[L][M][N]                    a            T [L][M][N]     T (*)[M][N]
                               &a            T (*)[L][M][N]
                               *a            T [M][N]        T (*)[N]
                             a[i]            T [M][N]        T (*)[N]
                            &a[i]            T (*)[M][N]
                            *a[i]            T [N]           T *
                          a[i][j]            T [N]           T *
                         &a[i][j]            T (*)[N]
                         *a[i][j]            T 
                       a[i][j][k]            T

The pattern for higher-dimensional arrays should be clear.

屋檐 2024-10-03 21:27:34

编辑:这是我根据要求并根据您的新示例代码尝试给出的更中肯的答案:

无论数组维度如何,您传递的是“指向数组的指针” - 它是尽管指针的类型可能有所不同,但只有一个单个指针。

在第一个示例中,int array[6] 是一个包含 6 个 int 元素的数组。传递array会传递一个指向第一个元素的指针,它是一个int,因此参数类型是int *,可以等效地写为int[]

在第二个示例中,int array[3][3] 是一个包含 3 行(元素)的数组,每行包含 3 个 int。传递array会传递一个指向第一个元素的指针,它是一个由3个int组成的数组。因此,类型为 int (*)[3] - 指向 3 个元素的数组的指针,可以等效地写为 int [][3]

我希望你现在就能看到其中的区别。当您传递 int ** 时,它实际上是一个指向 int * 数组的指针,而不是一个指向 2D 数组的指针。

实际 int ** 的示例如下:

int a[3] = { 1, 2, 3 };
int b[3] = { 4, 5, 6 };
int c[3] = { 7, 8, 9 };
int *array[3] = { a, b, c };

这里 array 是一个由 3 个 int * 组成的数组,并将其传递为参数将产生 int **


原始答案:

您的第一个示例并不是真正的二维数组,尽管它的使用方式类似。在那里,您将创建 ROWSchar * 指针,每个指针都指向不同的 COLS 字符数组。这里有两个间接级别。

第二个和第三个示例实际上是二维数组,其中整个 ROWS * COLS 字符的内存是连续的。这里只有一层间接。指向二维数组的指针不是 char **,而是 char (*)[COLS],因此您可以执行以下操作:

char (*p)[SIZE] = arr;
// use p like arr, eg. p[1][2]

Edit: Here's my attempt at a more to-the-point answer as requested and based on your new example code:

Regardless of the array dimensions, what you pass is a "pointer to an array" - it's only a single pointer, though the type of the pointer can vary.

In your first example, int array[6] is an array of 6 int elements. Passing array passes a pointer to the first element, which is an int, hence the parameter type is int *, which can be equivalently written as int [].

In your second example, int array[3][3] is an array of 3 rows (elements), each containing 3 ints. Passing array passes a pointer to the first element, which is an array of 3 ints. Hence the type is int (*)[3] - a pointer to an array of 3 elements, which can be equivalently written as int [][3].

I hope you see the difference now. When you pass an int **, it is actually a pointer to an array of int *s and NOT a pointer to a 2D array.

An example for an actual int ** would be something like this:

int a[3] = { 1, 2, 3 };
int b[3] = { 4, 5, 6 };
int c[3] = { 7, 8, 9 };
int *array[3] = { a, b, c };

Here array is an array of 3 int *s, and passing this as an argument would result in an int **.


Original answer:

Your first example isn't really a 2D array, although it is used in a similar way. There, you're creating ROWS number of char * pointers, each of which points to a different array of COLS characters. There are two levels of indirection here.

The second and third examples are actually 2D arrays, where the memory for the entire ROWS * COLS characters is contiguous. There is only one level of indirection here. A pointer to a 2D array is not char **, but char (*)[COLS], so you can do:

char (*p)[SIZE] = arr;
// use p like arr, eg. p[1][2]
药祭#氼 2024-10-03 21:27:34

其他人已经总结得差不多了。
int **A 表示 A 是指向数组的指针,而不是对二维数组的引用。
然而,这并不意味着它不可用。由于 C 中的数据按行优先顺序存储,因此一旦知道行长度,检索数据就应该很容易

The others have pretty much summed it up.
int **A means that A is a pointer to an array and not a reference to a 2-D array.
However, that does not mean it is not usable. Since the data in C is stored in row-major order, once you know the row length, retrieving the data should be easy

渡你暖光 2024-10-03 21:27:34

因为指针与数组指针的类型不同。有关详细信息,请参阅指向指针和指针数组的指针

另外,这还有一些很好的信息:http://c-faq.com/aryptr/index。 html

Because a pointer pointer isn't the same type as a pointer to an array. See pointers to pointers and pointer arrays for details.

Also, this has some good info: http://c-faq.com/aryptr/index.html

心舞飞扬 2024-10-03 21:27:34

第一个示例是可能的,因为当作为函数参数传递时,数组会退化为指针。

第二个示例不起作用,因为 int[3][3] 退化为 int (*)[3]不是 双指针 <代码>int **。出现这种情况是因为二维数组在内存中是连续的,如果没有此信息,编译器将不知道如何访问第一行之后的元素。考虑一个简单的数字网格:

1  2  6 
0  7  9

如果我们将这些数字存储在数组 int nums[6] 中,我们将如何索引到数组中以访问元素 7?当然,通过1 * 3 + 1,或者更一般地说,row * num-columns + column。为了访问第一行之后的任何元素,您需要知道网格有多少列。

当您将数字存储为 nums[2][3] 时,编译器将使用与手动处理一维数组完全相同的 row * num-columns + column 算术,它只是对程序员隐藏。因此,在传递 2D 数组时必须传递列数,以便编译器能够执行此算术。

在许多其他语言中,数组携带有关其大小的信息,从而无需在将多维数组传递给函数时手动指定维度。

The first example is possible because arrays degenerate to pointers when passed as function parameters.

The second example does not work because int[3][3] degenerates to int (*)[3], not a double pointer int **. This is the case because 2D arrays are contiguous in memory, and without this information the compiler would not know how to access elements past the first row. Consider a simple grid of numbers:

1  2  6 
0  7  9

If we were storing these numbers in an array int nums[6], how would we index into the array to access the element 7? By 1 * 3 + 1, of course, or more generally, row * num-columns + column. In order to access any element past the first row, you need to know how many columns the grid has.

When you store the numbers as nums[2][3], the compiler uses the exact same row * num-columns + column arithmetic as you do manually with a 1D array, it's just hidden from the programmer. Therefore, you must pass the number of columns when passing a 2D array in order for the compiler to be able to carry out this arithmetic.

In many other languages, arrays carry information about their size, eliminating the need to manually specify dimensions when passing multi-dimensional arrays to functions.

So要识趣 2024-10-03 21:27:34

如果你想得到更中肯的答案,也许我们可以期待一个更“中肯”的问题。你的想法有两个问题:

  1. 使用时的二维数组 int A[3][3]
    在表达式中衰减为
    其第一个元素的地址因此为
    int (*)[3] 类型的指针。为了能够传递数组,您必须使用 &A[0][0] 来获取指向第一个“内部”成员的指针。
  2. 在你的函数内部进行操作
    A[i][j] 无法执行,因为
    你的编译器没有以下信息
    行长,在那里。

Perhaps we can expect a more "to the point" question, if you want to have a more to the point answer. Your idea has two problems:

  1. a 2D array int A[3][3] when used
    in an expression decays to the
    address of its first element thus to
    a pointer of type int (*)[3]. To be able to pass the array through you'd have to use &A[0][0] to get a pointer to the first "inner" member.
  2. inside your function the operation
    A[i][j] can't be performed since
    your compiler has no information of
    the row length, there.
尽揽少女心 2024-10-03 21:27:34

这段代码有两个主要问题。

MyFunction(int **array, int row, int col);

首先是 int **array 是错误使用的类型。这是一个指向指针的指针,而这

int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};

是一个多维数组。组成这个多维数组的内存都是一个块,并且从该数组的开头到该数组的任何元素的偏移量是根据该数组中的大小的知识来计算的。

int *A[99];

这是一个指向整数的指针数组。指向的整数可能是内存中几个整数中的第一个,这意味着它们实际上指向整数数组。

在许多情况下,当您在程序中使用数组名称时,它的计算结果为指向数组开头的指针。如果你说:

int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
printf("%p %p %p\n", array, array[0], &(array[0][0]) );

你应该得到相同的地址打印3次,因为它们都引用相同的地址,但它们的类型不一样。最后两个的数据类型在许多用途上相似且兼容,因为 array[0] 会被视为指向 array 第一行第一个元素的指针这一行本身就是一个数组。

如果你说:

int **A;

你是说有一个指向 int 的指针。虽然 A[2][4] 是一个有效的表达式,但这不是一个多维数组,其方式如下:

int B[3][3];

如果您说 A[1] ,则其计算结果为int *B[1] 类似,只不过您可以说 A[1] = (int *)0x4444;,但如果你说 B[1] = (int *)0x4444; 你会得到一个编译器错误,因为 B[1] 实际上是一个计算值,而不是一个变量。对于 B 来说,没有 int * 变量数组——只是一些基于行大小和数组第一个成员地址的计算。

此代码应该执行与您想要的类似的操作(一些输出格式更改以提高可读性)。请注意 print 语句中的索引值是如何更改的。

MyFunction(int *array, int row, int col)
{
    int x = 0;
    for(int i=0 ; i<row ; i++ )
    {
        for(int j=0 ; j<col ; j++)
        {
            printf(“%d ”, array[x++]);
        }
        printf("\n");
    }
}

main()
{
    int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
    MyFunction(array, 3, 3);
}

There are two main issues with this code.

MyFunction(int **array, int row, int col);

The first is that int **array is the wrong type to use. This is a pointer to a pointer, while

int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};

is a multidimensional array. The memory that makes up this multidimensional array is all one chunk, and the offset from the beginning of this to any element of this array is calculated based on knowledge of the size of a row in this array.

int *A[99];

This is an array of pointers to integers. The integers that are pointed to could be the first of several integers in memory, which means that these actually point to arrays of integers.

Under many circumstances when you use the name of an array in a program it evaluates to a pointer to the beginning of the array. If you say:

int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
printf("%p %p %p\n", array, array[0], &(array[0][0]) );

You should get the same address printed 3 times because they all refer to the same address, but their types are not the same. The data type of the last two are similar and compatible for many purposes, since array[0] would be treated as a pointer to the first element of the first row of array and this row is an array all by itself.

If you say:

int **A;

You are saying that there is a pointer to a pointer to an int. While A[2][4] is a valid expression, this is not a multidimensional array in the same way as:

int B[3][3];

If you say A[1] this evaluates to an int * similar to B[1] would, except that you can say A[1] = (int *)0x4444;, but if you said B[1] = (int *)0x4444; you would get a compiler error because B[1] is actually a calculated value, not a variable. With B there is no array of int * variables -- just some calculations based on the row size and the address of the very first member of the array.

This code should do something similar to what you wanted (some output formatting changes for readability). Note how the index value in the print statement is changed.

MyFunction(int *array, int row, int col)
{
    int x = 0;
    for(int i=0 ; i<row ; i++ )
    {
        for(int j=0 ; j<col ; j++)
        {
            printf(“%d ”, array[x++]);
        }
        printf("\n");
    }
}

main()
{
    int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
    MyFunction(array, 3, 3);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文