Google C++每周贴士 #165: 带初始化器的`if`和`switch`语句

(原文链接:https://abseil.io/tips/165 译者:clangpp@gmail.com)

每周贴士 #165: 带初始化器的ifswitch语句

如果你用不到条件控制流程,那可以到此为止了。

一个新语法

C++17 允许 ifswitch语句包含初始化器:

if (init; cond) { /* ... */ }
switch (init; cond) { /* ... */ }

这种语法可以让你的变量作用域尽量地小:

if (auto it = m.find("key"); it != m.end()) {
  return it->second;
} else {
  return absl::NotFoundError("Entry not found");
}

这里初始化器的语义和for语句里的完全一样;下面会详细说明。

什么时候用

管理复杂度最重要的方式之一,就是把复杂系统拆解成为互不影响的,本地化的组件,每个组件都可以被单独理解,并且作为整体被忽略。在C++中,变量的存在增加了复杂度,而 作用域 允许我们限制由此带来的复杂度:变量所在的作用域越小,读者需要记住的变量就越少。

当需要读者关注的时候,将变量作用域限制在其被使用的地方就变得重要了。这个新语法为此提供了新工具。对比这个新语法和C++17以前的代码:要么我们保持作用域尽量小,为此需要多写一对大括号:

{
  auto it = m.find("key");
  if (it != m.end()) {
    return it->second;
  } else {
    return absl::NotFoundError("Entry not found");
  }
}

要么,正如看起来更典型的方案,我们 并不 保持作用域尽量小,就“泄露”变量:

auto it = m.find("key");
if (it != m.end()) {
  return it->second;
} else {
  return absl::NotFoundError("Entry not found");
}

这种复杂的考量催生了常见的程序员黑话:变量名长度应该与作用域大小相匹配;也就是说,作用域越大,其中的变量名就该越长(为了让读者走出二里地还能记着它)。反之,作用域越小,其中的变量名就可以越短。当变量名“泄露”的时候(如上所示),我们经常看见坑爹的模式涌现出来,例如:多个变量it1it2,…变得必要以防止命名冲突;变量被重新赋值(auto it = m1.find(/* ... */); it = m2.find(/* ... */));或变量有个侵入式的长名字(auto database_index_iter = m.find(/* ... */))。

细节,作用域,声明区

这个新的,可选的ifswitch语句中的初始化器,其工作方式与for语句里的初始化器完全一致。(后者基本是一个带初始化器的while语句。)也就是说,这个含有初始化器的语法基本就是如下写法的语法糖:

语法糖格式(译者注:自动)改写成为
if (init; cond) BODY{ init; if (cond) BODY }
switch (init; cond) BODY{ init; switch (cond) BODY }
for (init; cond; incr) BODY{ init; while (cond) { BODY; incr; }

重点提一句,在初始化器中声明的变量名,在if语句可能存在的else分支中仍然有效。

当然,还是有一点不同:在语法糖格式里,初始化器跟条件(condition)或主体(body)(既包含if分支,又包含else分支)在同一作用域里,而不是在另一个,更大的作用域。这意味着变量名在这一堆组件里必须保持唯一,当然它们可以遮蔽掉更早的声明。下面的例子阐释了各种禁止的重定义和允许的遮蔽声明:

int w;

if (int x, y, z; int y = g()) {   // 错误:y重定义了,初始化器中定义过了
  int x;                          // 错误:x重定义了,初始化器中定义过了
  int w;                          // 可以,遮蔽掉外层变量
  {
    int x, y;                     // 可以,在嵌套作用域中遮蔽变量是允许的
  }
} else {
  int z;                          // 错误:z重定义了,初始化器中定义过了
}

if (int w; int q = g()) {         // w的声明可以,遮蔽掉外层变量
  int q;                          // 错误:q重定义了,在条件语句中定义过了
  int w;                          // 错误:w重定义了,在初始化器中定义过了
}

与结构化绑定(structured bindings)的交互

C++17也引入了 结构化绑定,这种机制可以用来为“可解构的”值(例如元组、数组或简单的结构体)其中的元素命名:auto [iter, ins] = m.insert(/* ... */);

这个特性和if语句里的初始化器相得益彰:

if (auto [iter, ins] = m.try_emplace(key, data); ins) {
  use(iter->second);
} else {
  std::cerr << "Key '" << key << "' already exists.";
}

另一个例子来自C++17新引入的 节点句柄node handles),可以用来在map和set之间移动元素(而不是拷贝)。这个特性定义了一个可解构的 插入返回值insert-return-type),作为插入一个节点句柄的返回值。

if (auto [iter, ins, node] = m2.insert(m1.extract(k)); ins) {
  std::cout << "Element with key '" << k << "' transferred successfully";
} else if (!node) {
  std::cerr << "Key '" << k << "' does not exist in first map.";
} else {
  std::cerr << "Key '" << k << "' already in m2; m2 unchanged; m1 changed.";
}

结论

在以下情况下使用新的if (init; cond)switch (init; cond)语法:当你需要一个新变量,在ifswitch语句内部用到,且在此之外不被用到的时候。这可以简化周围的代码。另外,变量的作用域小了,变量名也可以起得更短。

帮我写c++代码内容如下 Description 表达式有三种表示方法,分别为: 前缀表示式:把运算符写在操作数之前,即运算符+操作数1+操作数2 中缀表示式:把运算符写在操作数之间,即操作数1+运算符+操作数2 后缀表示式:把运算符写在操作数之后,即操作数1+操作数2+运算符 例如:a+b*(c-d)-e/f 前缀表示式:-+a*b-cd/ef 中缀表示式:a+b*(c-d)-e/f 后缀表示式:abcd-*+ef/- 前缀表达式后缀表达式与相应的中缀表达式的操作次序相同,但是没有括号,计算顺序非常明确,不需要考虑运算符的优先级,是计算机最容易处理的表达式。 根据教材给出的算法思路参考代码,将输入的中缀表达式转换为后缀表达式。 主要参考代码见教材第92至95页,Stack类继承了Vector类,且为了实现栈的不同功能,添加或修改了部分函数。Vector类可直接使用实验二的预习代码,新添加的栈相关的函数见教材第72至74页。 OJ平台不支持多文件代码,请将Vector类、Stack类的实现代码与主程序写在同一个文件中。 Input 只有一行,中缀表达式,以&#39;#&#39;开始,以&#39;#&#39;结束。 Output 第一行,输出转换的后缀表达式; 第二行,输出表达式的计算结果; 具体格式见样例。 Sample Input #1*2-4/(5-2*1.5)# Output 转换的后缀表达式为:1 2 * 4 5 2 1.5 * - / - 计算结果为:0 Hint 避坑小贴士: 1、不要打印提示用户输入的信息,避免平台错判;所有输出的冒号为中文冒号。 2、继承的Vector类需包含remove函数(教材第28页),但该函数的序号从1开始,在本题中使用应修改为: public: // 删除指定位置的元素 ElemType remove(int i) { ElemType e = elem[i-1]; for(int j = i-1; j < length; j++) elem[j] = elem[j+1]; --length; return e; } 3、本实验平台的编译器不支持gets()方法,请用cin.get(str,n)代替,其中n为str数组的长度。 4、G++Visual C++处理流插入操作符<<的顺序不同,在G++中,它从左到右处理,所以下面的代码它先输出计算结果为:,再输出evaluate(str)中的内容,而在Visual C++中,它先调用evaluate(str),然后再输出计算结果为:。 cout << "\n计算结果为:" << evaluate(str) << endl; 我们的实验平台使用的是G++编译器,同学们如果使用Visual Studio则使用的是Visual C++,书上的代码会输出不同的结果,为了避免这个问题,同学们可以将最后的输出语句修改为下面的代码: float ans = evaluate(str); cout << "\n计算结果为:" << ans << endl;
10-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值