我正在从C ++ 98迈向C ++ 11,并且已经熟悉了auto
关键字。我想知道为什么我们需要显式声明auto
编译器是否能够自动推断类型。我知道C ++是一种强类型语言,这是一条规则,但是如果不显式声明一个变量,auto
是否有可能实现相同的结果?
我正在从C ++ 98迈向C ++ 11,并且已经熟悉了auto
关键字。我想知道为什么我们需要显式声明auto
编译器是否能够自动推断类型。我知道C ++是一种强类型语言,这是一条规则,但是如果不显式声明一个变量,auto
是否有可能实现相同的结果?
Answers:
删除显式auto
将破坏语言:
例如
int main()
{
int n;
{
auto n = 0; // this shadows the outer n.
}
}
在那里你可以看到下降auto
不会遮住外部n
。
n := 0
来引入新变量。为什么auto
使用基于意见的问题。
:=
)。(d)它已经符合语法要求。我认为这里的意见空间很小。
x = f()
声明新变量(如果尚不存在),则不需要新标记,从而获得f的返回值类型...需要auto来显式声明变量,但是,这降低了声明新变量的风险偶然地(例如由于打字错误...)。
您的问题有两种解释:
Bathsheba很好地回答了第一种解释,对于第二种解释,请考虑以下内容(假设到目前为止没有其他声明;假设是有效的C ++):
int f();
double g();
n = f(); // declares a new variable, type is int;
d = g(); // another new variable, type is double
if(n == d)
{
n = 7; // reassigns n
auto d = 2.0; // new d, shadowing the outer one
}
这将是可能的,其他语言就完事很好用(当然,除了可能遮蔽问题)......,它在C并非如此++,不过,现在的问题(在第二个解释的意义上)是:为什么?
这次,答案不像第一种解释那样明显。显而易见,有一件事是:关键字的明确要求使语言更安全(我不知道这是否是促使语言委员会做出决定的原因,但仍然是重点):
grummel = f();
// ...
if(true)
{
brummel = f();
//^ uh, oh, a typo...
}
我们可以同意这一点,而无需任何进一步的解释吗?
但是,不需要自动的更大危险是,这意味着在远离函数的位置(例如,在头文件中)添加全局变量可能会使原本打算在本地声明-该函数中的作用域变量转换为全局变量的赋值……可能会带来灾难性(当然也很令人困惑)的后果。
(由于其重要性,引用了psmears的评论-感谢您的暗示)
auto
我认为,不要求这样做的更大危险是,这意味着在远离函数的位置(例如,在头文件中)添加全局变量可能会使原本打算在本地范围内声明的内容变成事实。该函数中的变量转换为对全局变量的赋值……可能会带来灾难性(当然也很令人困惑)的后果。
global <variable>
声明就可以从全局变量中读取内容。)当然,这将需要对C ++语言进行更多修改,因此可能不可行。
MOV R6 R5
SUB #nnn R6
PDP-11上的IIRC假定R5被用作帧指针,R6是堆栈指针。nnn是所需的存储字节数。
如果不显式声明变量,
auto
是否不可能达到相同的结果?
我将以某种方式稍微改一下您的问题,以帮助您了解为什么需要auto
:
如果不显式使用类型占位符,是否不可能达到相同的结果?
是不是没有可能?当然,这是“可能的”。问题是这样做是否值得付出努力。
其他没有类型名称的语言中的大多数语法都以两种方式之一工作。有一种类似Go的方式,其中name := value;
声明了一个变量。还有一种类似于Python的方式,name = value;
如果name
以前没有声明过,则在其中声明一个新变量。
让我们假设将任何一种语法应用于C ++都没有语法问题(即使我已经看到C ++中identifier
紧随其后的:
含义是“制作标签”)。那么,与占位符相比,您失去了什么?
好吧,我不能再这样做了:
auto &name = get<0>(some_tuple);
看,auto
总是意味着“价值”。如果要获取参考,则需要显式使用&
。如果赋值表达式为prvalue,则将无法正确编译。两种基于赋值的语法都没有办法区分引用和值。
现在,如果给定值是引用,则可以使这种赋值语法推导引用。但这意味着您无法执行以下操作:
auto name = get<0>(some_tuple);
这会从元组复制,创建一个独立于的对象some_tuple
。有时候,这正是您想要的。如果您想从中移出元组,这将更加有用auto name = get<0>(std::move(some_tuple));
。
好的,因此也许我们可以对这些语法进行一些扩展以解决这种区别。也许&name := value;
或&name = value;
意味着推论出像auto&
。
好的。那这个呢:
decltype(auto) name = some_thing();
哦,对了;C ++实际上有两个占位符:auto
和decltype(auto)
。这种推论的基本思想是,它就像您已经完成的一样工作decltype(expr) name = expr;
。因此,在我们的例子中,如果some_thing()
是一个对象,它将推论出一个对象。如果some_thing()
是参考,它将推断出参考。
当您使用模板代码并且不确定确切函数的返回值时,这非常有用。这对于转发非常有用,即使它没有被广泛使用,它也是必不可少的工具。
因此,现在我们需要在语法中添加更多内容。name ::= value;
意思是“做什么decltype(auto)
”。我没有等效的Pythonic版本。
看看这种语法,不是很容易偶然输入错误吗?不仅如此,它几乎没有自我记录。即使您从未见过decltype(auto)
,它也足够大而且很明显,您至少可以轻松地说出发生了什么特别的事情。而::=
和之间的视觉差异:=
很小。
但这就是意见。还有更多实质性问题。看,所有这些都是基于使用赋值语法的。好吧...那您不能使用赋值语法的地方呢?像这样:
for(auto &x : container)
我们将其更改为for(&x := container)
吗?因为这似乎表明与基于范围的区别很大for
。看起来像是来自常规for
循环的初始化语句,而不是基于范围的for
。它与非推导情况的语法也将不同。
另外,=
在C ++中,复制初始化(使用)和直接初始化(使用构造函数语法)不是同一回事。因此,在name := value;
可能的情况下可能无法正常工作auto name(value)
。
当然,您可以声明:=
它将使用直接初始化,但这与其余C ++的行为方式完全不一致。
另外,还有一件事:C ++ 14。它给了我们一个有用的推论功能:返回类型推论。但这是基于占位符的。就像基于范围的一样for
,它从根本上基于由编译器填充的类型名,而不是基于应用于特定名称和表达式的某种语法。
瞧,所有这些问题都来自同一个来源:您正在发明全新的语法来声明变量。基于占位符的声明不必发明新的语法。他们使用的语法与以前完全相同。他们只是采用了一种新关键字,该关键字的作用类似于类型,但具有特殊含义。这就是它可以在基于范围for
和归纳类型推断中工作的原因。它是什么允许它具有多种形式(auto
vs. decltype(auto)
)。依此类推。
占位符之所以起作用,是因为它们是解决问题的最简单方法,同时保留了使用实际类型名称的所有好处和通用性。如果您提出了另一个与占位符一样普遍使用的替代方法,那么它几乎不可能像占位符一样简单。
除非只是用不同的关键字或符号拼写占位符...
auto
声明/返回值推导不同。
auto
在某些情况下可能会被丢弃,但这会导致不一致。首先,正如所指出的,C ++中的声明语法为<type> <varname>
。显式声明在其位置需要某种类型或至少一个声明关键字。因此,我们可以使用var <varname>
ordeclare <varname>
或诸如此类的东西,但是auto
它在C ++中是一个长期存在的关键字,并且是自动类型推断关键字的很好的候选者。
是否可以通过赋值隐式声明变量而不破坏所有内容?
有时是的。您无法在函数外部执行赋值,因此可以在其中使用赋值语法进行声明。但是这种方法会给语言带来不一致,可能导致人为错误。
a = 0; // Error. Could be parsed as auto declaration instead.
int main() {
return 0;
}
当涉及任何类型的局部变量时,显式声明是它们控制变量范围的方法。
a = 1; // use a variable declared before or outside
auto b = 2; // declare a variable here
如果允许歧义语法,则声明全局变量可能会突然将局部隐式声明转换为赋值。找到这些转换将需要检查所有内容。为了避免冲突,您将需要为所有全局变量使用唯一的名称,这会破坏整个范围的概念。所以真的很糟糕。
语法必须明确,并且必须向后兼容。
如果删除了auto,将无法在语句和定义之间进行区分。
auto n = 0; // fine
n=0; // statememt, n is undefined.
auto
它已经是一个关键字(但具有过时的含义),因此它不会破坏使用它作为名称的代码。这是没有选择更好的关键字(例如var
或)的原因let
。
auto
实际上是一个非常出色的关键字:它准确表达了它的意思,即用“自动类型”替换类型名称。因此,使用诸如var
orlet
的关键字,即使明确指定了类型(即var int n = 0
或之类的),也应要求使用关键字var n:Int = 0
。基本上,这就是在Rust中完成的方式。
auto
在现有语法的上下文中绝对是出色的,但我想说的是,如果考虑到历史内容之外,像这样var int x = 42
的基本变量定义(带有var x = 42
和int x = 42
作为速记)将比当前语法更有意义。但这主要是口味问题。但是,您是对的,我应该在原始评论中写“原因之一”而不是“原因” :)
auto
都有一个自动类型(一个不同的类型,具体取决于表达式)。
除了以前的答案外,还有一个旧屁的注释:看起来您可能会发现,可以直接使用新变量而无需以任何方式声明它是一种优势。
在可能隐式定义变量的语言中,这可能是个大问题,尤其是在较大的系统中。您打了一个错字,调试了好几个小时,才发现您无意中引入了一个值为零(或更糟)的变量blue
-vs bleu
,label
vs lable
...结果是,如果不仔细检查精确度,您将无法真正信任任何代码变量名。
仅使用即可auto
告诉编译器和维护者您打算声明一个新变量。
考虑一下,为了避免这种噩梦,FORTRAN中引入了“隐式无”语句-并且您会发现它已用于当今的所有认真的FORTRAN程序中。没有它简直是……令人恐惧。