为什么你不能在声明中有多个decl-specifier-seq?

时间:2014-12-11 21:15:59

标签: c++

以下是无效代码:

int i = 0, double j = 2.0;

标准草案说明原因:

  

[N4140 / 7.1.6]

     

2作为一般规则,最多允许一个类型说明符   完成声明的 decl-specifier-seq 或在    type-specifier-seq trailing-type-specifier-seq 。此规则的唯一例外如下:

     
    

- const可以与除自身之外的任何类型说明符组合使用。

         

- volatile可以与除自身之外的任何类型说明符组合使用。

         

- signedunsigned可与charlongshort合并,     或int

         

- shortlong可与int结合使用。

         

- long可与double结合使用。

         

- long可与long结合使用。

  

是的,它可以防止像int int这样愚蠢的内容,但我发现上面发布的无效代码没有任何问题。引用[N4140/7]简单声明 decl-specifier-seq opt init-declarator-list opt 组成;

[N4140/8]然后显示 init-declarator-list 包含 init-declarator-list init-declarator

init-declarator 声明符初始值设定项 opt

由于我们只关注int i = 0形式的语法,因此我们关心的声明符 ptr-declarator ,这是 noptr-declarator ,它是 declarator-id属性说明符-seq opt ,最后是 declarator-id 仅由... opt id-expression 组成。

为了完整性,[N4140/5.1.1]表示 id-expression 可以是 unqualified-id ,或者只是标识符。< / p>

如果到目前为止我没有绊倒,这就是语法所反映的。

int decl-specifier-seq

i unqualified-id

= 0 初始化程序

int i = 0 init-declarator

由于简单声明具有 decl-specifier-seq ,因此只有一个 decl-specifier-seq 适用于整个 > INIT-说明符列表

有趣的是,这意味着你不能做这样的事情:

int i, const j;

然而:

int i, * j;

完全合法,因为该星是 ptr-operator 的一部分。但你不能这样做:

int i, const * j; // pointer to const int

这意味着在以下代码中i成为指向const int的指针。惊喜!

int h = 25;
int const * j, * i = &h;
*i = 50; // error: assignment of read-only location '* i'

[N4140/8]中的意图很清楚:

  

3声明中的每个 init-declarator 分别进行分析   如果它本身就是一个声明。 99

     

99)包含多个声明符的声明通常等同于   相应的声明序列,每个声明都有一个   声明符。那是

T D1, D2, ... Dn;
     

通常相当于

T D1; T D2; ... T Dn;

问题是为什么会这样?


如果它是合法的,你可以在for循环中进行,这有点用处。

3 个答案:

答案 0 :(得分:3)

简短回答:一个声明只允许声明一种类型的声明&#39;但是这个声明可以声明多个标识符&#39;。 const / volatile也是类型限定符或指针限定符,因此它们需要一个类型或指针来绑定。

答案很长:

我还没有阅读过这个标准但是我在这里......

&#34;它可以防止像int int这样愚蠢的东西,但我发现上面发布的无效代码没有任何问题。&#34;

  • 我看到的方式是一个语句只允许一个声明但是一个声明可以允许你声明多个相同类型的标识符。
  • 因此int i = 0,double j = 2.0的问题;是你有两种类型,int和double。这违反了[N4140 / 7.1.6]。
  • 这是语言允许的内容,因此上述说法不正确。

但你继续深入挖掘,我相信你的困惑从此开始了#34;因为我们只关注形式的语法......&#34;。声明不会分解如下吗?

int i = 0 ::= simple-declaration
Where in...
int ::= type-specifier
i ::= identifier
= 0 ::= init-declarator

更多

你提到过......

Not Allowed: int i, const j;
Allowed: int i, * j;
Not Allowed: int i, const * j; // pointer to const int
Allowed: int const * j, * i = &h;

我的回答:

Not Allowed: int i, const j; - because const is a type modifier, syntactically there is no type specified to bind to.
Allowed: int i, * j; - because * grabs the type int and j has a complete type.
Not Allowed: int i, const * j; - because const is not associated to a type here. It is the same problem as in the first case. Read it as j is a pointer to <unexpected word in between> and thus screws up the grammar.
Allowed: int const * j, * i = &h; - because syntactically const has a type to bind to.

&#34;问题是为什么会这样?&#34;

  • 当我学习CI时,最初混淆了在类型名称之前/之后使用const并清除混淆我尝试了一些测试代码并找出了语言允许的内容,以下是我提出的内容用。这是我的旧笔记。它绝对看起来像是由新程序员制作的。但是,它清除了大多数疑虑。

    [storage class] [sign qualifier] [size qualifier] [type qualifiers]&lt; [* [type qualifiers]] [symbol name] [[] | ([参数])]&GT;

    存储类:auto,register,static,extern,typedef

    签署限定符:已签名,未签名

    尺寸限定符:短,长,长

    基本类型:char,int,float,double,void

    类型限定符:const,volatile

    符号名称可以是变量,常量,类型(def)名称和函数

    前缀为符号的* *使其成为指针。 *可以出现N次,使其指向指针等等。

    符号后面加一个[]使它成为一个数组。 []可以出现N次,使其成为一个多维数组。

    符号后面的A()使它成为一个函数。 ()可以出现N次,但由于函数不能返回函数,当函数返回函数指针时,()会再次出现。

上面帮助我在声明变量时直接思考。

从我以前的笔记修改类型说明符语法:

[storage class] [sign qualifier] [size qualifier] <type> [type qualifiers] [* [pointer qualifiers]] [symbol name] [[] | ([parameters])]

这是const和volatile是类型限定符或指针限定符,它们需要一个类型或指针来绑定它们。

考虑&#34;一个语句只允许一个声明,但一个声明可以允许你声明多个相同类型的标识符。&#34;这意味着类型说明符语法可以按如下方式进行细分:

type ::= [storage class] [sign qualifier] [size qualifier] <type> [type qualifiers]

symbol ::= [* [pointer qualifiers]] [symbol name] [[] | ([parameters])]

声明的简化语法是:

输入符号[,符号...]

显然,

int i,const j; - 不同意语法。

int const i,j; - 同意语法。

我确信一个符合标准的人可以使用标准,并使用正确的术语和参考提供答案。但是,请记住,经验不足的程序员可能会发现一个易于理解的技术性较差的答案。

如果形式&#34; int i,const j&#34;被允许然后可以写&#34; int const i,const j&#34;这意味着j是一个双常数。这没有任何意义。

答案 1 :(得分:2)

  

问题是为什么会这样?

不幸的是,我无法肯定地回答这个问题。我的猜测是,这是一个简单的方法来节省C或其前身的击键。我会说*运算符,恕我直言,确实会改变你声明的变量的类型,所以我不知道为什么const不允许这样做。

我还想添加一个您没有包含哪些是合法的的示例:

int i = 0, * const j = &i;

这是合法的,因为在这种情况下const适用于*而不是int。至少根据ideone

这可能只是需要提出的遗产。

你从标准中摘录了一些关于这个主题的内容:

99) A declaration with several declarators is usually equivalent to the corresponding sequence of declarations each with a single declarator. That is

T D1, D2, ... Dn;
is usually equivalent to

T D1; T D2; ... T Dn;

意图似乎是用逗号分隔的Dn一起声明的是同一类型。因为如果你改变类型,你也可以使用分号,因为这样就不会给你节省任何击键,例如。

int i, j = 0; double k, l = 7.3;

通过在正确的地方使用逗号,您可以自行输入intdouble两次。

我可以使用constvolatile等类型修饰符看到您的观点。为什么不允许我们随意合并?我的意思是这与*有什么不同?我不认为这是不同的,我希望比我更聪明的人来解释原因。我会说禁止*或允许我们使用其他修饰符。

总之,,我不知道,但是这里有一个很酷的另外一个例子:

int i = 0, * const j = &i;

答案 2 :(得分:1)

  

问题是为什么会这样?

因为这就是在黎明时间(实际上是1973年)在C中的情况see here

  

如果它是合法的,你可以在for循环中进行,这有点用处。

这是合法的,只是不太漂亮:

for(std::tuple<int, double> t = std::make_tuple(0, 10.0) ; 
    std::get<0>(t) < 10 ; 
    ++std::get<0>(t), std::get<1>(t) *= 1.1)
{
    cout << std::get<0>(t) << ", " << std::get<1>(t) << std::endl;
}