c++ 我的值初始化的尝试被解释为函数声明,为什么A不是a(());解决它?

yftpprvb  于 2023-07-01  发布在  其他
关注(0)|答案(5)|浏览(123)

在Stack Overflow教会我的许多事情中,有一件事被称为“最令人烦恼的解析”,这是一行经典的演示,如

A a(B()); //declares a function

虽然对于大多数人来说,直观上看起来像是A类型的对象a的声明,将临时B对象作为构造函数参数,但它实际上是返回A的函数a的声明,将指针指向返回B的函数,并且本身不带参数。同样,线

A a(); //declares a function

也福尔斯同一类别,因为它声明的不是对象,而是函数。现在,对于第一种情况,通常的解决方法是在B()周围添加一组额外的方括号/圆括号,因为编译器会将其解释为对象的声明

A a((B())); //declares an object

但是,在第二种情况下,执行相同的操作会导致编译错误

A a(()); //compile error

我的问题是为什么是的,我很清楚正确的“变通方法”是将其更改为A a;,但我很好奇地想知道第一个示例中额外的()对编译器有什么作用,然后在第二个示例中重新应用它时就不起作用了。A a((B()));解决方案是否是标准中写入的特定例外?

vpfxa7rd

vpfxa7rd1#

没有开明的答案,这只是因为它没有被 C++ 定义为有效的语法。从语言的定义来看,就是这样。
如果你有一个表达式,那么它是有效的。例如:

((0));//compiles

更简单的说法是:因为(x)是有效的C++表达式,而()不是。
要了解更多关于语言是如何定义的,以及编译器是如何工作的,您应该了解Formal language theory,或者更具体地说,Context Free Grammars (CFG)和相关材料,如有限状态机。如果你对此感兴趣,尽管维基百科上的页面还不够,你还是得买本书。

xj3cbfub

xj3cbfub2#

这个问题的最终解决方案是如果可能的话,迁移到C+11统一初始化语法。

A a{};

http://www.stroustrup.com/C++11FAQ.html#uniform-init

vm0i2vca

vm0i2vca3#

C函数声明符

首先,有C在C中,A a()是函数声明。例如,putchar具有以下声明。通常,这样的声明存储在头文件中,但是如果你知道函数的声明是什么样子的,没有什么能阻止你手工编写它们。参数名称在声明中是可选的,所以我在这个例子中省略了它。

int putchar(int);

这允许你像这样写代码。

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C语言还允许你定义以函数为参数的函数,它的语法看起来像一个函数调用(只要你不返回一个指向函数的指针,它就是可读的)。

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

正如我提到的,C允许在头文件中省略参数名,因此output_result在头文件中看起来像这样。

int output_result(int());

构造函数中的一个参数

你不认识这个吗?好吧,让我提醒你。

A a(B());

是的,这是完全相同的函数声明。Aintaoutput_resultBint
你可以很容易地注意到C与C新特性的冲突。确切地说,构造函数是类名和括号,并且声明语法是()而不是=。通过设计,C试图与C代码兼容,因此它必须处理这种情况-即使实际上没有人关心。因此,旧的C特性优先于新的C特性。声明的语法尝试将名称匹配为函数,如果失败,则恢复为新语法()
如果这些特性中的一个不存在,或者有不同的语法(比如C
11中的{}),那么对于只有一个参数的语法来说,这个问题就永远不会发生。
现在你可能会问为什么A a((B()))工作。好吧,让我们用无用的括号声明output_result

int output_result((int()));

没用的语法要求变量不在括号中。

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

但是,C在这里需要标准表达式。在C中,您可以编写以下代码。

int value = int();

和以下代码。

int value = ((((int()))));

C++期望括号内的表达式是…表达式,而不是C期望的类型。括号在这里没有任何意义。然而,通过插入无用的括号,C函数声明不匹配,新语法可以正确匹配(只需要一个表达式,如2 + 2)。

构造函数中的更多参数

一个论点当然很好,但两个呢?这并不是说构造函数只能有一个参数。std::string是接受两个参数的内置类之一

std::string hundred_dots(100, '.');

这一切都很好(从技术上讲,如果它被写为std::string wat(int(), char()),它将有最令人烦恼的解析,但让我们诚实地说-谁会写这样的呢?但是让我们假设这段代码有一个令人烦恼的问题。你会认为你必须把所有的东西都放在括号里。

std::string hundred_dots((100, '.'));

不完全是这样。

<stdin>:2:36: error: invalid conversion from ‘char’ to ‘const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of ‘std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

我不知道为什么g++试图将char转换为const char *。无论哪种方式,构造函数都只使用一个char类型的值调用。没有重载只有一个char类型的参数,因此编译器会感到困惑。你可能会问-为什么参数是char类型?

(100, '.')

是的,这里的,是一个逗号运算符。逗号运算符接受两个参数,并给出右侧参数。这不是真的有用,但它是我的解释知道的东西。
相反,要解决最令人烦恼的解析,需要以下代码。

std::string hundred_dots((100), ('.'));

参数在括号中,而不是整个表达式。事实上,只要有一个表达式需要放在括号中,因为使用C++功能就足以稍微打破C语法。事情把我们带到了零争论的地步。

构造函数参数清零

您可能已经注意到我的解释中的eighty_four函数。

int eighty_four();

是的,这也受到最令人烦恼的解析的影响。这是一个有效的定义,如果您创建了头文件(您应该),您很可能已经看到了它。添加括号并不能解决这个问题。

int eighty_four(());

为什么会这样呢?()不是表达式。在C中,必须将表达式放在括号之间。你不能在C中写auto value = (),因为()没有任何意义(即使有,像空元组(见Python),它也是一个参数,而不是零)。实际上,这意味着你不能在不使用C++11的{}语法的情况下使用速记语法,因为没有表达式可以放在括号中,并且函数声明的C语法将始终适用。

4dc9hkyq

4dc9hkyq4#

你可以代替

A a(());

使用

A a=A();
guz6ccqo

guz6ccqo5#

在你的例子中,最里面的括号是一个表达式,在C中,语法将expression定义为assignment-expression或另一个expression,后跟一个逗号和另一个assignment-expression(附录A.4 -语法摘要/表达式)。
语法进一步将assignment-expression定义为其他几种类型的表达式之一,其中没有一种可以是nothing(或只有空格)。
所以不能使用A a(())的原因很简单,因为语法不允许这样做。然而,我不能回答为什么创造C
的人不允许这种特殊情况下使用空括号-我猜如果有合理的替代方案,他们宁愿不把这种特殊情况。

相关问题