【问题标题】:Why can't i assign the name of a 2d array to a 2d pointer?为什么我不能将二维数组的名称分配给二维指针?
【发布时间】:2019-08-18 09:54:59
【问题描述】:

练了一些简单的数组和指针基础,遇到了看不懂的东西:

在我能找到的所有书面资料中,他们说二维数组的名称实际上是一个二维指针, 意思是如果我写:

int a[3][4];

同时声明一个指针:

int **d2;

它们都属于同一类型,我可以放心地分配:

d2 = a;

使指针指向第一行的开头。

但最令人惊讶的是,这不起作用,我的问题是——为什么?

我会为您复制我收到的代码和警告:

#include <stdio.h>

int main() {
    int **d2;
    int a[5][2] = { {1, 2}, {3, 4}, {5, 7}, {7, 8}, {9, 11} };

    d2 = a;
    while (d2 < a + 2) {
        printf("%d", **d2);
        d2++;
    }
    return 0;
} 

我得到了这个诊断结果:

warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]
  d=a;
   ^

ptr.c:11:9: warning: comparison of distinct pointer types lacks a cast
  while(d<a+2)

【问题讨论】:

标签: c arrays pointers


【解决方案1】:

它们具有完全不同的内部结构,因此它们不能互换。

二维数组

二维数组是这样的:

p[3][3]
|
v 
+---------+---------+---------+---------+---------+---------+---------+
| p[0][0] | p[0][1] | p[0][2] | p[1][0] | p[1][1] | p[1][2] |   ...   |
+---------+---------+---------+---------+---------+---------+---------+

一个连续的空格。

指针

指针是这样的:

**p
|
v 
+------+   +---------+---------+---------+---------+---------+
| p[0] |-->| p[0][0] | p[0][1] | p[0][2] | p[0][3] |   ...   |
+------+   +---------+---------+---------+---------+---------+
| p[1] |-->| p[1][0] | p[1][1] | p[1][2] | p[1][3] |   ...   |
+------+   +---------+---------+---------+---------+---------+
| p[2] |-->| p[2][0] | p[2][1] | p[2][2] | p[2][3] |   ...   |
+------+   +---------+---------+---------+---------+---------+
| .... |
+------+

每个p[i] 可能指向不同大小的不同空间。

【讨论】:

    【解决方案2】:

    int **d2 定义了一个指向 int 的指针。您也可以将其视为指向数组的指针,每个数组元素都包含一个指向 int 的指针。臧敏杰的回答很好地说明了这一点。

    数组int a[3][4]; 是一个连续 内存块,因此等价于int a[3*4];。如您所见,它不是指向行的指针数组。

    请注意,编译器知道您何时使用连续数组和指针数组,并使用正确的索引方法。例如:

    void f(int **d2)
    {
        printf("%d", d2[3][4]);
    }
    void g(void)
    {
        int a[5][2] = { {1, 2}, {3, 4}, {5, 7}, {7, 8}, {9, 11} };
    
        printf("%d", a[3][1]);
    }
    

    如您所见,编写索引表达式的方式是相同的,即使底层表示完全不同。我可以理解这令人困惑。请记住“编译器知道”。

    【讨论】:

    • 也许展示 OP 如何使用指针访问二维数组中的元素会很有用?
    • 我试图理解你们所有人在说什么......但是,如果“a”是一个二维数组,&(a+1) 将给出第二个开始的地址排。它使它看起来像在二维数组中有某种指针数组......
    • 正如其他人所指出的,没有“第二行”。二维数组是一个连续的线性数据块——只有“1 行”。二维数组中的元素在内存中的布局方式与一维数组完全相同,不同之处在于访问这些元素的方式。
    • @dedecos,部分正确。声明为 2d 允许更轻松的索引表达式。 (实际上是 n d)。
    • @davidku,您可以将其写为void f(int *a, int rowsize, int colsize) 并将其索引为a[i*rowsize+j],其中 i 您要处理的行和 j 要处理的列。较新版本的 C 允许 void f(int rowsize, int colsize, int a[rowsize][colsize] 和索引为 a[i][j]
    【解决方案3】:

    这是因为int **d2 是一个指向指针的指针。没有二维指针之类的东西。

    例如,让我们考虑一维数组:

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

    现在,一个整数指针将指向数组的第一个元素,如果分配如下:

    int *d = a;
    

    说到指针的指针,一个指针指向的内存位置保存着其他指针的地址!

    例如,

    // This will declare an integer pointer
    int *a;
    

    上面的指针将存储一些其他变量的地址。

    例如,您可以按如下方式为其赋值:

    int x = 5;
    int *a = &x;
    

    现在,指针的作用如下:

    指向指针的指针保存其他指针的内存地址。

    这意味着,在上面的例子中,我们可以声明一个指向指针的指针,该指针保存指针a的地址:

    int x = 5;
    int *a = &x;
    
    // A pointer to pointer
    int **b = &a;
    
    // To print value of x using b
    printf("%d", **b);
    

    上面的代码可以这样理解:

    1. a = &x;所以 *a = *(&x) = 5
    2. b = &a;所以 *b = *(b 中的地址) = *(a 的地址) = x 的地址
    3. 但是 **b = *(*b) = *(x 的地址) = 5

    希望这能消除您对指针指针的困惑。

    现在,针对您的用例,如果您想要一个指向二维整数数组的指针,您可以只使用一个指针并将其分配给数组的第一个元素的地址。这可以按如下方式完成:

    int a[2][2] = {{1, 2}, {3, 4}};
    
    // Declare a pointer that points to first element of array
    int *b = &a[0][0];
    

    现在如果你想使用指针访问这个数组的元素a[i][j],你可以使用b + 2*i + j来访问它。

    一般来说,如果数组的维度是p*q,则可以使用b + q * i + j访问元素a[i][j]

    例如,要使用 2 个 for 循环打印数组,

    int rows = 2;
    int cols = 3;
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int *b = &a[0][0];
    
    for(int i=0; i<rows; i++) {
        for(int j=0; j<cols; j++) {
            // Print a[i][j] using pointer b
            printf("%d ", *(b + cols * i + j));
        }
    }
    
    // 1 2 3 4 5 6
    

    你也可以使用单循环,

    int rows = 2;
    int cols = 3;
    int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int *b = &a[0][0];
    
    for(int i=0; i<rows*cols; i++) {
        printf("%d ", *(b + i));
    }
    
    // 1 2 3 4 5 6
    

    【讨论】:

    • 谢谢!这清除了它......以确保我明白:在你的最后一个例子中,真正的“a”的含义是什么,矩阵的名称?它是指向第一个元素的指针吗?
    • @davidku 请查看**b的解释。这是一个错字。我更正了。
    • @davidku 如果您将最适合您的答案标记为已接受,这将有助于其他面临同样问题的人。 :)
    【解决方案4】:

    int **d2; 不是二维指针,它是指向int 的指针,有时也称为双指针。

    int a[3][4]; 定义了一个由 4 个int 组成的 3 个数组组成的数组,也称为 2D 数组。

    这两个对象的类型不兼容:这里没有int指针变量,其地址可以存储到d2中。

    我们打个比方:

    • int 就像一个有 32 个房间的房子;
    • 一条街道就是一排房子;
    • 街区是一组相邻的街道;
    • 指针是地址簿中带有房屋地址的条目;
    • 双指针是一张纸,您可以在其中写下存储地址簿的位置。

    二维数组中的行在内存中是连续的,因此您可以这样枚举数组中的条目:

    #include <stdio.h>
    
    int main() {
        int *p;
        int a[5][2] = { {1, 2}, {3, 4}, {5, 7}, {7, 8}, {9, 11} };
    
        printf("iterating as a 2D array:\n");
        p = &a[0][0];
        for (int row = 0; row < 5; row++) {
            for (int col = 0; col < 2; col++) {
                printf("%d ", *p);
                p++;
            }
            printf("\n");
        }
        printf("\n");
    
        printf("iterating as a single array:\n");
        p = &a[0][0];
        while (p < &a[5][0]) {
            printf("%d ", *p);
            p++;
        }
        printf("\n");
        return 0;
    }
    

    输出:

    作为二维数组迭代: 1 2 3 4 5 7 7 8 9 11 迭代为单个数组: 1 2 3 4 5 7 7 8 9 11

    【讨论】:

    • 谢谢,但我还是不明白:指向指针的指针不是二维指针吗?那么什么是二维指针?
    • 建议使用“4 rows of 3 ints”。
    猜你喜欢
    • 2021-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-19
    • 2020-08-03
    • 1970-01-01
    • 1970-01-01
    • 2021-07-23
    相关资源
    最近更新 更多