为什么我们不能将数组的地址赋给指针?

4
int q[10]={0};
cout << q << endl;
cout << &q << endl;
cout << &q[0] << endl;

输出结果为:

0x7fffd4d2f860 
0x7fffd4d2f860 
0x7fffd4d2f860 

现在当我这样做时->

int *r=q;    // allowed
int *r=&q[0] // allowed
int *r=&q    // not allowed

为什么第三个分配任务不被允许,尽管它实质上与其他任务相同?

5
&q将会是int ** - ALX23z
3
因为std::ostream定义了一个operator<<(void*),而void*可以接受任何类型的指针(几乎是所有类型)。你所使用的三个<<调用都解析到同一个内存地址,这就是它们都打印出相同数字的原因。 - Remy Lebeau
1
@jamesgem,您似乎混淆了“类型”和“值”。cout打印的是值。cout不会告诉您一个表达式是否是可分配的有效类型。 - Drew Dormann
2
我的编译器输出以下错误:error: cannot convert 'int (*)[10]' to 'int*' in initialization 从中很容易看出类型是错误的。好的编译器消息就像黄金一样珍贵。 - user4581301
3
“&q”不仅不是“int **”,而且它也不会退化成它。指向数组的指针不会退化。 - eerorika
显示剩余4条评论
5个回答

9
如果您声明了一个数组,例如:
T a[N];

T为某个类型说明符时,指向数组的指针将声明为如下:

T ( *p )[N] = &a;

一个通用的规则是:如果你有一个多维数组(包括一维数组),例如:

T a[N1][N2][N3];

那么你可以将这个声明重写为:
T ( a[N1] )[N2][N3];

要获取数组的第一个元素的指针,只需按以下方式替换括号中的内容。
T ( *p )[N2][N3] = a;

如果您想获得整个数组的指针,那么请重新编写数组声明,例如:
T ( a )[N1][N2][N3];

并进行替换
T ( *p )[N1][N2][N3] = &a;

与标量对象及其指针的声明相比较。
例如:
T obj;

您可以将声明重写为:
T ( obj );

现在,如果您想获取一个指向对象的指针,可以编写
T ( *p ) = &obj;

当然,在这种情况下括号是多余的,上述声明等价于:
T *p = &obj;

关于这段代码片段

int q[10]={0};
cout << q << endl;
cout << &q << endl;
cout << &q[0] << endl;

以及它的输出

0x7fffd4d2f860 
0x7fffd4d2f860 
0x7fffd4d2f860 

很少有例外情况下,用于表达式中的数组设计器会被转换为指向其第一个元素的指针。

因此,实际上在这些语句中,表达式q&q[0]是相同的。

cout << q << endl;
cout << &q[0] << endl;

是等效的。然而,数组本身的地址是其占用的内存范围的地址。在范围的开头是数组的第一个元素。所以这三个表达式都给出了同样的结果:数组占用的内存范围的地址。


1
我认为这将是一个不错的常见问题解答。 - Timo
将cout更改为类似于cout << q << " " << typeid(q).name() << endl;的内容,这将提供一些见解(我希望如此)。还需要一个#include <typeinfo> - Eljay
@Eljay typeid(something).name() 在大多数情况下不提供易于理解的输出。我在这个答案中实现了你的建议。 - Kitswas

3
为什么第三个赋值语句不允许,明明是同样的东西?
因为 C++ 语言有一个名为“类型安全”的特性。有一个类型系统可以帮助您保持程序逻辑的正确性。
其中一个规则是,任意指针类型都不能用来初始化其他不兼容类型的指针。在这种情况下,您有一个指向 int 类型的指针(即 int *),您尝试使用指向数组的指针(即 int(*)[10])类型的表达式进行初始化。一种类型无法隐式转换为另一种类型,因此程序无法通过编译。
那么,为什么cout在所有三种情况下都打印相同的内容呢?
因为所有的指针都具有相同的值。数组第一个元素的第一个字节与整个数组的第一个字节相同。
恰好流插入运算符处理所有指针类型1完全相同,因此具有相同值但不同类型的指针会产生相同的输出。 1字符类型指针是一个例外。它们被完全不同地处理。
为什么我们不能将数组的地址赋给指针?
实际上,我们可以将数组的地址赋给指针。我们只是不能将数组(或任何其他对象)的地址分配给错误类型的指针。在这种情况下,我们需要一个指向数组的指针:
int (*r)[10] = &q;

是的,我的错,我刚刚意识到了。 - Remy Lebeau

1

您无法完成第三个任务,因为&q的类型是int (*)[10],与int* r的类型不兼容。

cout << &q的输出未显示&q的类型。请查看this文档链接。


那么为什么cout在这三种情况下打印相同的内容呢? - user11910577
2
关于编辑:int*[10]int(*)[10]是不同的。一个是指针数组,另一个是数组指针。 - HolyBlackCat
您是否有关于这种符号的文档链接? - Kurt Stolle
1
请查看此处链接:https://zh.cppreference.com/w/cpp/language/array - Timo
今天学到了新东西! - Kurt Stolle

1

q是一个固定长度的数组。在表达式中仅指定q衰减成指向q第一个元素的指针。因此,q 衰减成与&q[0]返回的指针值相同。另一方面,&q返回的是q变量本身的内存地址,对于数组来说,其第一个元素占据了同样的内存地址。

std::ostream定义了一个operator<<(void*),而void*可以接受(几乎)任何类型的指针。由于你的所有三个cout调用都解析成相同的内存地址,并且有一个接受所有三种类型指针的operator<<,这就是为什么所有三个调用都打印相同数字的原因。

至于你的赋值:

  • q 是一个 int[10] 数组,它会 衰变(decay) 成一个 int* 指针,因此 int *r=q; 能够工作。

  • &q[0]q 进行解引用以访问其第一个元素,它是一个 int 类型,然后取该元素的地址,生成一个 int* 指针,因此 int *r=&q[0]; 能够工作。

  • 由于 q 是一个 int[10] 数组,&q 是一个 int(*)[10] 指针,它不会 衰变 成一个 int* 指针,因此 int *r=&q; 不能工作。你需要使用正确的类型声明 r

    int (*r)[10] = &q;


0
以下代码演示了整数数组arr&arr&arr[0]之间的区别。
#include <iostream>
#include <string_view>

// Reference: https://dev59.com/qXVD5IYBdhLWcg3wHnwX#56766138
// Type finding code start
template <typename T>
constexpr auto type_name()
{
    std::string_view name, prefix, suffix;
#ifdef __clang__
    name = __PRETTY_FUNCTION__;
    prefix = "auto type_name() [T = ";
    suffix = "]";
#elif defined(__GNUC__)
    name = __PRETTY_FUNCTION__;
    prefix = "constexpr auto type_name() [with T = ";
    suffix = "]";
#elif defined(_MSC_VER)
    name = __FUNCSIG__;
    prefix = "auto __cdecl type_name<";
    suffix = ">(void)";
#endif
    name.remove_prefix(prefix.size());
    name.remove_suffix(suffix.size());
    return name;
}
// Type finding code end

int main()
{
    int arr[5] = {1, 2, 3, 4, 5};
    std::cout << "Value: " << arr << "\tType: " << type_name<decltype(arr)>() << std::endl;
    std::cout << "Value: " << &arr << "\tType: " << type_name<decltype(&arr)>() << std::endl;
    std::cout << "Value: " << &arr[0] << "\tType: " << type_name<decltype(&arr[0])>() << std::endl;
    return 0;
}

看它如何运作。

示例输出:

Value: 0x7ffcdadd22d0   Type: int [5]
Value: 0x7ffcdadd22d0   Type: int (*)[5]
Value: 0x7ffcdadd22d0   Type: int*

虽然这三个变量都与同一内存位置相关联,但它们是不同类型的。

参考:在标准C++中打印变量类型是否可能?


还有旧版的Boost demangle,或者新版的Boost typename - Eljay

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