C++中的二维数组指针操作

4
int main(){
    int a[10][10];
    int **ptr =(int **)a;
    cout<<a<<endl<<ptr<<endl;
    cout<<*a<<endl<<*ptr<<endl;
    return 0;
}

这段代码在我的电脑上的输出结果是

0021FC20
0021FC20
0021FC20
CCCCCCCC

为什么"a"等于"*a"? 为什么"*a"不等于"*ptr"?

7
int **ptr = (int **)a; 是一种非法的类型转换。嵌套数组和嵌套指针之间不存在转换关系。请参考我们的相关常见问题解答 - fredoverflow
7
不要使用(cast)语法。如果您使用正确的static_cast<int**>语法,编译器会立即告诉您转换无效。 - R. Martinho Fernandes
5个回答

4
为什么a等于*a
当在需要指针的上下文中使用时,数组将转换为指向其第一个元素的指针。a是一个数组的数组;因此它会衰减为指向第一个数组的指针。*a是第一个数组,并且会衰减为指向该数组中第一个整数的指针。这两个存在于同一位置,因此这两个指针将具有相等的值。
为什么*a不等于*ptr
因为从数组的数组到指向指针的指针的转换是无效的。您已经使用强制转换进行了转换-这种危险的C风格转换,在这种情况下类似于reinterpret_cast - 因此*ptr将读取数组的前几个字节,并将其解释为指针(可能-这里的行为是未定义的,因此原则上任何事情都可能发生)。在ptr指向的内存中没有指针,因此*ptr肯定不会给您有效的指针。

2
由于数组无法打印,因此将int[10][10]的a隐式转换为int(*)[10]。 因此,实际上打印的是a的第一行的指针。
*a是数组的第一行,然后将其转换为指向第一个元素的指针。
由于数组与其第一个元素具有相同的地址,因此您会两次获得相同的值。

1
一个二维 C 数组并不是指向指针的指针。它实际上是一个具有行数乘以列数个元素的指针。
int main(){
    int a[10][10];
    int *ptr =(int *)a;
    cout<<a<<endl<<ptr<<endl;
    cout<<*a<<endl<<*ptr<<endl;
    return 0;
}

以上代码将会给你想要的结果。

但是当你解引用 a 或 ptr 时,你会发现得到了一个“未定义的值”。

如果你将 a[4][4] 设置为 5

那么你会发现存储在 ptr[(row * 10) + column] 中的值将返回 row = 4 和 column = 4 的值。


1
数组不是指针。从数组到指针有一种转换,但这并不意味着它们是相同的。 - fredoverflow
1
int *ptr =(int *)a; 这种写法是不合法的。你可以选择写成 int *ptr = a[0]; 或者 int (*ptr)[10] = a; - fredoverflow
@Goz 为什么 "a" 和 "*a" 的值是相同的?"*a" 应该给我 a[0][0] 的内容。 - Ganesh

1
2D数组的机制实际上比它们看起来的要简单,但通常教学不够好。尽管如此,需要很多经验才能快速解密事物,即使有经验的开发人员有时也不得不停下来思考一会儿(或两个)。基本上,当您看到像int a[5][10]这样的2D数组时(我将您的数组大小更改为5以简化问题),您需要停止将其视为2D。相反,将其视为1D数组。第一个下标指定了该1D数组中的元素数,就像任何其他1D数组一样。当您索引数组时,例如a [3],因此访问索引3处的对象。这与任何其他数组没有区别。假设"a"是这样定义的:
// Note: The size of the array, 5, can be omitted
int a[5] = {5, 10, 15, 20, 25};

下标操作将产生以下结果:

a[0] = 5
a[1] = 10
a[2] = 15
a[3] = 20
a[4] = 25

这很容易,大多数人都能理解。但是如果你现在这样做:

// Note: Like above, the size of the array, 5, can be omitted
int a[5][10] = {{  5,  10,  15,  20,  25,  30,  35,  40,  45,  50},
                { 55,  60,  65,  70,  75,  80,  85,  90,  95, 100},
                {105, 110, 115, 120, 125, 130, 135, 140, 145, 150},
                {155, 160, 165, 170, 175, 180, 185, 190, 195, 200},
                {205, 210, 220, 225, 230, 235, 240, 245, 250, 255}};
          

你仍然有一个由5个元素组成的一维数组,你可以像之前的例子一样对其进行下标操作,得到以下结果(不是真正的语法,只是你应该这样考虑):

a[0] =   5  10  15  20  25  30  35  40  45  50
a[1] =  55  60  65  70  75  80  85  90  95 100
a[2] = 105 110 115 120 125 130 135 140 145 150
a[3] = 155 160 165 170 175 180 185 190 195 200
a[4] = 205 210 220 225 230 235 240 245 250 255

这里唯一的区别是,每个元素不像前面的例子那样是一个“int”,而是一个数组(由10个整数组成)。因此,当您像所示那样进行下标操作时,每个索引(0到4)都会返回该行的10个整数的数组。例如,a [3] 返回包含值155到200的10个整数的数组。元素本身是“int [10]”,因此您可以将其视为这样的方式。换句话说,您得到的相当于:

int b[10] = {155, 160, 165, 170, 175, 180, 185, 190, 195, 200};

例如,同样地,如果b[7] = 190,那么a[3][7]也等于190,因为a[3]有效地返回了与b相同的东西(一个包含10个整数的数组),然后[7]下标就像b[7]一样获取该数组中索引为7的元素。此外,正如b是指向数组第一个元素的指针(因为所有数组名称都会衰减为指向数组第一个元素的指针),在这种情况下是155(即“b”衰减为指向155的int *),a[3]返回(衰减为)相同的东西。为什么?因为它返回了上述描述的与b相同的东西,并且就像b一样,它衰减为指向其第一个元素的指针。换句话说,就像这是真的:
int *p = b; // "b" decays into a pointer to its first element (an "int")
int val = *p; // Equals 155

这也是正确的:

int *p = a[3]; // a[3] returns the equivalent of "b", an int[10], and this decays into a pointer to its first element, just like "b"
int val = *p; // Equals 155

最后,就像FredOverflow提到的那样,这也是正确的:
int (*p)[10] = a;

语法需要适应,但它是有意义的。由于每个数组名都会衰减为指向其第一个元素的指针,因此"a"衰减为指向其第一个元素的指针。那么这个元素是什么呢?嗯,"a"的每个元素都是一个包含10个整数的数组,即"int[10]",因此"a"必须衰减为指向这些"int[10]"元素中的第一个的指针。上面的语法就是声明此指针的方法。它将"p"定义为指向包含10个整数的数组的指针。圆括号是必需的,因为C++的优先级规则。如果您删除它们,您将得到以下内容:
int *p[10] = a; // Compiler error!

这个声明将“p”声明为一个包含10个元素的数组,其中每个元素都是指向int类型的指针。它不同于IOW,因此需要使用括号来改变优先级(从而改变此声明的含义)。使用先前显示的正确语法,“p”将指向第一个元素(包含元素5到50的“int [10]”数组),p + 1将指向第二个元素(包含元素55到100的“int [10]”数组),以此类推。额外加分:因此以下代码会做什么?

(*(p + 3))[5]

它返回180,因为(p + 3)返回指向位于"a[3]"的10个整数数组的指针,当使用*运算符进行解引用时,您会得到实际位于此位置的"int [10]"数组。然后,[5]下标在此数组中产生值180。

毫无疑问,仍需要大量练习才能理解这一点,但我希望这有所帮助。


1
我建议避免静态堆栈分配大矩阵,而选择动态分配(在堆上)...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接