哪个定义更好?(涉及IT技术)

5
#include<stdio.h>
int main(int argc , char *argv[])
{
    int array[2][2] = {{1,100},{1000,10000}};

    int *pointer    = array;
    int *ppointer   = &array;
    int *pppointer  = array[0];
    int *ppppointer = &array[0];

    printf("%d\n",*pointer);
    printf("%d\n",*ppointer);
    printf("%d\n",*pppointer);
    printf("%d\n",*ppppointer);

    return 0;
}

四个指针指向数组的第一个元素。 上述定义中哪一个更好? 而且我不知道为什么数组和&array相同?


4
你为什么给这个问题打了 [tag:c] 和 [tag:c++] 标签?它们是**不同的编程语言。请移除不必要的标签(很可能是 [tag:c++])。 - ThiefMaster
一个好的实践方法,正如许多人在这里指出的那样,是通过%p打印指针地址。然后,您可以将指针增加1并查看指针移动了多少字节 :) - mmirzadeh
1
@GradGuy:在这个例子中,已声明的指针将被相同地移动,因为类型指针之间的区别在初始化的时候已经丢失了。 - AnT stands with Russia
@AndreyT:同意。我更多地是考虑像这样的东西 printf("ptr = %p\tshifted_ptr = %p\n", (void*)array, (void*)(array+1)) - mmirzadeh
7个回答

8
你所有定义都能编译通过的唯一原因是你的C编译器在指针类型转换方面太过宽容。如果你使用一些更严格的开关,它应该会立即告诉你只有第三个初始化是有效的,而其他的是错误的。
在大多数情况下(有几个例外),当使用类型为T[N]的数组在表达式中时,它会“衰变”(被隐式转换)为指针类型T *——一个指向其第一个元素的指针。换句话说,在这种情况下,对于任何数组A,A表达式等同于&A[0]。唯一不发生数组类型衰变的情况是一元&运算符、sizeof运算符和用作char数组初始化程序的字符串字面值。
在你的例子中,array是int [2][2]类型的值。当用于初始化的右侧时,它会衰变为指针类型int (*)[2]。因此,这是无效的。
int *pointer    = array;

右侧是int (*)[2],而左侧是int *。这些是不同的指针类型。您不能使用一个初始化另一个。
int *ppppointer = &array[0];

这与之前的内容完全相同:右侧生成了一个int (*)[2]类型的值,但出于同样的原因,它是无效的。

&array表达式生成了一个int (*)[2][2]类型的指针。同样,出于这个原因,它也是无效的。

int *ppointer   = &array;

无效。

你的示例中唯一有效的初始化方式是

int *pppointer  = array[0];

array[0]是一种int [2]类型的表达式,它会衰变成int *类型 - 与左侧相同的类型。

换句话说,在这里没有哪一个更好的问题。只有一种初始化是有效的,其他都是非法的。有效的初始化也可以写成

int *pppointer  = &array[0][0];

出于我上面所述的原因,现在,“更好”的右侧(array[0]还是&array[0][0])取决于您个人的喜好。


为了使您的其他初始化有效,指针应该声明如下:

int (*pointer)[2]     = array;
int (*ppointer)[2][2] = &array;
int (*ppppointer)[2]  = &array[0];

但是这样的指针与int *指针的语义不同。而且显然你特别需要int *

谢谢你的回答!但是你怎么知道右侧的类型是什么?我的意思是有没有方法可以确定类型?我真的很困惑…… - std

2
只有第三种方法是正确的。其他三种方法都是无效的C++,会导致我的C编译器发出警告。通常最好编写既是有效C也是有效C++的代码,因为在某些平台上,C++编译器也是建议用于C的编译器(如MSVC)。这也使得将C代码包含在C++项目中变得更加容易,而不需要进行重大的构建系统调整。
为什么你的编译器会对1、2和4发出警告呢?右侧表达式之一没有正确的类型,不能转换为int*。
1. 数组array的类型是int[2][2],可以转换为int(*)[2],而不是int*。 2. &array是指向int[2][2]的指针。 3. array[x]实际上具有类型int*。 4. &array[x]的类型为int**。

2
他在询问关于C语言而不是C++。 - dgund
@DevinGund C++的兼容性通常很重要,在这种情况下,C++更严格的类型系统只是指出了C勉强允许的错误。 - pmr

1
int *pointer    = array;     //Incorrect
int *ppointer   = &array;    //Incorrect
int *pppointer  = array[0];  //Correct
int *ppppointer = &array[0]; //Incorrect

这是简短的版本。 现在讲一下原因。

第一个指针是不正确的,因为你正在分配'array'(它是一个没有进一步规定的指针)...但不是int类型的指针,而是int * []类型的指针

第二个指针是不正确的,因为你将要分配指针的地址...实际上是变量的地址,该变量保存了数据的指针。

第三个是正确的,因为你得到了一个指向int数组的指针,无论大小如何。

第四个是不正确的,因为你正在复制第一个数组的地址。 这使它成为int **,而不是int *。

对于我的许多编辑,我很抱歉...我可能很累了。


2
@akappa 好吧,我忽略了它是二维的这个关键点...嘿,可能是因为我太累了。请取消踩的操作,我会删除答案。已经有一个更好的答案被发布了。 - ATaylor
@akappa 非常感谢您,还有指出这个愚蠢的错误。 - ATaylor

0

简短回答:

$ cat decls.c

int main(void)
{
    int array[2][2] = {{1,100},{1000,10000}};
    int *pointer    = array;
    int *ppointer   = &array;
    int *pppointer  = array[0];
    int *ppppointer = &array[0];
}
$ clang decls.c -Wall -o decls
这段内容与编程有关。
decls.c:4:7: warning: incompatible pointer types initializing 'int *' with an
      expression of type 'int [2][2]' [-Wincompatible-pointer-types]
        int *pointer    = array;
             ^            ~~~~~
decls.c:5:7: warning: incompatible pointer types initializing 'int *' with an
      expression of type 'int (*)[2][2]' [-Wincompatible-pointer-types]
        int *ppointer   = &array;
             ^            ~~~~~~
decls.c:7:7: warning: incompatible pointer types initializing 'int *' with an
      expression of type 'int (*)[2]' [-Wincompatible-pointer-types]
        int *ppppointer = &array[0];
             ^            ~~~~~~~~~

所以,只有第三个声明是正确的。

稍微长一点的答案:在C语言中,当你声明一个变量时,你使用一个表达式,当被求值时,它会给你左边的类型。

因此,如果你有一个char name[][],那么这意味着当你有name[2][3]时,你得到一个char。反过来也一样:假设你有A = name[3];如何从A中获取一个char?通过执行A[2],所以A是一个char *

这就是为什么只有第三个声明是正确的原因:因为左边声明的表达式和右边的表达式都具有相同的类型。


警告并不意味着代码是错误的,它只是意味着程序员应该关注并检查代码是否正好符合他想要做的事情。 - Samy Arous
这几乎从来不是情况。当你真的想搞砸类型系统时,通常会放置显式转换。 - akappa
@lcfseth:你在这个问题上的观点,充其量可以说是天真。你认为类型系统是一个无用的形式主义,因为在“裸金属”中,类型并不存在。这是对编程语言最糟糕的看法。编程语言是一个自成体系的形式主义,具有一套语法和相关的语义。它与汇编语言等无关。在C语言的美妙世界中,表达式具有特定的类型,等号左右的东西必须具有相同的类型。编译器通常不会那么严格,但这是另外一回事了。 - akappa

0

它们都不正确(第三个不会报错,但我认为它不是发帖者想要的值)。

应该是:

int ** pointer1    = array;
int ** pointer2   = &array;        //This one is wrong
int ** pointer3  = array[0];      //This one is not correct in this case
int * ppointer3  = array[0];
int ** pointer4 = &array[0];
int * pointer5 = &array[0][0];

我更喜欢第一个和最后一个。

如果是一维数组,我会选择第一个,因为它显示了数组基本上是指针的事实。如果是多维数组,我会使用最后一个(因为只需要解引用一次就可以得到值,但要注意索引:例如,如果您想获取1000,则需要使用pointer5 [2]而不是pointer5 [1] [1]


“我认为这不是海报想要的值”,并不意味着它是“不正确的”。顺便问一下,你为什么认为这不是原帖作者想要的? - akappa
@akappa 假设。假设并不好,但是当没有足够的信息时,有些假设必须被做出,就像老板从来没有明确他想要什么,但我们必须根据自己的假设来解决问题。 - SwiftMango

-1

它们都是等价的。

&array 是数组的地址,它从与其第一个元素相同的位置开始,因此 &array = &array[0]

array 是一个数组,但在某些情况下,它可以衰减为指向其第一个元素的指针,这就是为什么 array = &array = &array[0] 的原因

至于 int *pppointer = array[0];

我的第一印象是这应该是错误的。也许有人可以解释一下。

更新:我猜编译器在这里将 array 视为指针,因此给出:

int *pppointer = (&array)[0] = array[0]


它们仅在它们指向的地址方面是等效的,但在它们的类型方面并不相同。 - glglgl
没错 :) 但是类型在代码编译后就没有意义了 :). 就性能而言,它们没有区别。 - Samy Arous
1
@lcfseth:这段代码在任何严格的C编译器中都无法编译通过。代码是错误的。如果代码无法编译通过,现在谈论“性能”还为时过早。 - AnT stands with Russia
它可以编译。在回答之前,我当然已经尝试过了 :) 它只被一些编译器和大多数C++所否定,但没有C。这是正确的C代码,即使规范自我学习C以来有些变化(大约15年前:p)。 - Samy Arous
@lcfseth 但类型很重要,例如对于指针运算,包括索引访问。 如果您的类型错误,那么您的结果也将是错误的。 - glglgl

-2

所以,你有一个数组内嵌在另一个数组中。因此变量“array”实际上是指向另一个指针的指针。

如果你这样说:

int *pointer = array;

你遇到了类型不匹配的问题。*pointer是指向整型的指针,但array是指向另一个指针的指针。

当你这么说时:

int *ppointer = &array;

您仍然有类型不匹配的问题。&array 给我们指向指针的指针的地址,我们只能将其分配给指向指针的指针的指针。

当您说:

int *pppointer = array[0];

这是正确的。方括号解引用数组变量。因此,array[0]实际上是指向int的指针,与*pppointer的类型相匹配。

当你说:

int *ppppointer = &array[0];

所以,我们现在又回到了起点。array[0]是一个指向整数的指针,因此&array[0]是指向指向整数的指针的地址,只能赋值给指向指向整数的指针。

因此,最终第三个是唯一有效的。但是,我个人认为更好的实现方式是:

int *pointer = *array;

3
array并不是"指向另一个指针",它是一个二维数组。如果它衰变了,它会衰变为int (*)[2] - 一个指向一维数组的指针,而不是指向指针的指针。 - AnT stands with Russia

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