为什么我需要明确地写“ auto”关键字?

我正在从 C + + 98转向 C + + 11,并且已经熟悉了 auto关键字。我想知道为什么我们需要显式声明 auto,如果编译器能够自动推断类型。我知道 C + + 是一种强类型语言,这是一个规则,但是不显式声明变量 auto就不可能实现相同的结果吗?

10347 次浏览

放弃明确的 auto将打破这种语言:

例如:。

int main()
{
int n;
{
auto n = 0; // this shadows the outer n.
}
}

你可以看到,放弃 auto将不会 影子的外部 n

语法必须明确,并且向后兼容。

如果自动被删除,将无法区分语句和定义。

auto n = 0; // fine
n=0; // statememt, n is undefined.

auto是一个关键字,您可以在通常需要指定 类型的地方使用它。

  int x = some_function();

通过自动推导 int类型,可以使其更加通用:

  auto x = some_function();

所以它是语言的保守扩展; 它适合现有的语法。没有它,x = some_function()就变成了赋值语句,不再是声明。

你的问题有两种解释:

  • 我们为什么需要“自动”? 我们不能简单地放弃它吗?
  • 为什么我们必须使用 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...
}

我们能否达成一致,不需要任何进一步的解释?

不需要 auto 的更大的危险是,它意味着在远离函数的地方添加一个全局变量(例如在头文件中) ,可以将原本在该函数中声明局部作用域变量的内容转换为对全局变量的赋值... ... 这可能会带来灾难性的(当然也是非常混乱的)后果。

(由于其重要性,引用了 Psmears 的的评论——谢谢你的暗示)

简而言之: auto在某些情况下可能会被删除,但这会导致不一致。

首先,如前所述,C + + 中的声明语法是 <type> <varname>。显式声明需要某种类型或至少一个声明关键字来代替它。所以我们可以使用 var <varname>或者 declare <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:

难道没有明确的 使用类型占位符就不可能达到同样的结果吗?

不是 有可能吗?当然有可能。问题是这样做是否值得。

其他语言中不使用类型名称的大多数语法有两种工作方式。有一种 Go-like 方式,name := value;声明一个变量。还有一种类似 Python 的方法,如果 name以前没有声明过,那么 name = value;就声明一个新变量。

让我们假设对 C + + 应用任何一种语法都没有语法问题(尽管我已经看到在 C + + 中 identifier后面跟着 :意味着“创建一个标签”)。那么,与占位符相比,你会失去什么呢?

我不能再这么做了:

auto &name = get<0>(some_tuple);

看,auto总是意味着“价值”。如果希望获得引用,则需要显式使用 &。如果赋值表达式是一个价值,那么它将正确地无法编译。这两种基于赋值的语法都没有区分引用和值的方法。

现在,如果给定的值是一个引用,那么您可以让这样的赋值语法推断引用。但这意味着你不能这么做:

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 + + 实际上有 占位符: ABC0和 decltype(auto)。这个演绎法的基本思想是,它的工作原理与 decltype(expr) name = expr;完全一样。所以在我们的例子中,如果 some_thing()是一个对象,它将推导出一个对象。如果 some_thing()是一个引用,它将推导出一个引用。

当您使用模板代码并且不确定函数的返回值是什么时,这非常有用。这对于转发非常有用,而且是一个必不可少的工具,即使它没有被广泛使用。

现在我们需要在语法中添加更多内容。name ::= value;的意思是“做 decltype(auto)所做的事情”。我没有 Python 变体的等价物。

看看这个语法,是不是很容易意外地输入错误?不仅如此,它几乎没有自我记录。即使你从来没有见过 decltype(auto)之前,它是足够大和明显的,你至少可以很容易地告诉,有一些特殊的事情正在发生。而 ::=:=之间的视觉差异是最小的。

但这只是观点问题,还有更实质性的问题。看,所有这些都是基于使用赋值语法。那么... 不行使用赋值语法的地方呢?像这样:

for(auto &x : container)

要改成 for(&x := container)吗?因为这似乎说的东西 非常不同从范围为基础的 for。它看起来像是来自常规 for循环的初始化器语句,而不是基于范围的 for。这也将是一个不同的语法从未推导的情况下。

此外,复制初始化(使用 =)与直接初始化(使用构造函数语法)在 C + + 中并不相同。因此,name := value;可能不工作的情况下,auto name(value)会有。

当然,您可以声明 :=将使用直接初始化,但是这将与 C + + 的其余部分的行为方式完全不一致。

还有一件事: C + + 14。它给了我们一个有用的演绎特征: 返回类型演绎。但这是基于占位符的。与基于范围的 for非常相似,它基本上是基于由编译器填充的类型名,而不是基于应用于特定名称和表达式的某种语法。

看,所有这些问题都来自同一个源头: 您正在为声明变量发明全新的语法。基于占位符的声明不必发明新的 语法。它们使用与前面完全相同的语法; 它们只是使用了一个新的关键字,这个关键字的作用类似于一个类型,但具有特殊的含义。这就允许它在基于范围的 for和返回类型推断中工作。它允许它有多种形式(autodecltype(auto))。等等。

占位符之所以有效,是因为它们是问题的最简单解决方案,同时保留了使用实际类型名称的所有好处和普遍性。如果您想出了另一个像占位符那样普遍适用的替代方案,那么它不太可能像占位符那样简单。

除非只是用不同的关键字或符号拼写占位符。

除了之前的答案之外,还有一个来自老古董的附加说明: 看起来你可能认为只是开始使用一个新变量而不用以任何方式声明它是一个优势。

在可能隐式定义变量的语言中,这可能是一个大问题,特别是在较大的系统中。你打错了一个字,调试了几个小时,却发现你无意中引入了一个值为零(或者更糟)的变量—— blue vs bleulabel vs lable... ... 结果是,如果不彻底检查变量的精确名称,你就不能真正信任任何代码。

仅仅使用 auto就可以告诉编译器和维护者声明一个新变量的意图。

想想看,为了避免这种噩梦,FORTRAN 中引入了“隐式 none”语句——现在你可以在所有严肃的 FORTRAN 程序中看到它的使用。没有它只是简单的... 可怕。