条件 » 提示与风格
本文只是一个无序的技巧和窍门集合,旨在帮助您改进条件代码的编写方式。如果您一直按顺序学习本课程,这里使用的许多概念尚未涵盖。如果您不理解本文中的某些内容,请不要担心,它们将在后续课程中出现。
始终包含花括号
您可能会遇到的大多数 C++ 构造实际上都有可选的花括号。如果您省略花括号,则假定下一行代码在 if 语句的主体中,之后的所有内容都在 if 语句之外。
这有时可以使代码更短,更易于阅读。例如
if (A and B)
std::cout << "They're both true!\n";
std::cout << "Goodbye!";
然而,这对于初学者来说是一个非常常见的陷阱,因为这很快就会导致错误的行为。尤其是在编辑代码时,扩展 if 语句主体时很容易意外忘记添加大括号。请记住,C++ 不关心空白或缩进,所以如果您尝试这样做
if (A and B)
std::cout << "They're both true!\n";
std::cout << "That's cool that they are both true.\n";
std::cout << "Goodbye!";
高亮显示的行将始终运行,因为它不是 if 语句的一部分。这等同于仅在紧跟 if 语句的行之后放置花括号。由于初学者经常被这个错误所困扰,建议始终在 if 语句中添加大括号。像这样的错误甚至导致了真实世界代码库中的重大安全漏洞。
保持括号样式一致
关于花括号的话题……在在线编程社区中花任何时间都可能会让您接触到“内联”与“新行”大括号之间旷日持久的争论。这场争论的焦点是关于是否应该将开花括号 {
放在与 if 语句、函数、循环等同一行,还是应该放在新行上。下面将说明这两种样式以及它们的优缺点
- 🔷 内联大括号
- 🔶 新行大括号
int main() {
int x = 10;
if (x == 10) {
std::cout << "X is 10!";
} else {
std::cout << "X is not 10!";
}
for (int i = 0; i < 5; i++) {
std::cout << "i: " << i << "\n";
}
}
请注意,此示例用额外的空行隔开,使其与下一个示例对齐。这些额外的行与括号样式的比较无关。
最终,您选择哪种风格取决于您。这两种选择的流行度大致持平。主要的启示是,每个项目应该只使用一种风格。不要在同一代码中混合风格。
然而,不要过分执着于一种风格。一旦您找到一份专业的编程工作,您将被期望遵守该公司已有的风格。一致的代码比您自己的个人偏好更重要!
保持缩进一致
高级程序员帮助初学者时最常见的抱怨之一是他们的代码超级混乱!这使得代码难以阅读,如果您试图帮助他们找出错误,那就更难了。使代码更清晰易懂的最佳方法之一是保持缩进一致。幸运的是,这非常容易。
每次进入一个*作用域*时,您都应该使用 Tab 键将代码再缩进一个级别。每次退出一个作用域时,您都应该取消缩进一个级别。作用域基本上是一个 C++ 的“块”。函数内部是一个作用域,if 语句内部是一个作用域,for 循环内部是一个作用域,等等。下面是良好和不良缩进的示例。
- ✔️ 良好的缩进
- ❌ 不良缩进
// The parts of the functions are always unindented
// While immediately inside the function, it is indented at one level
bool check_value(int x) {
// The parts of the if statement are indented one level
// because it is immediately inside the function
if (x < 50) {
// Inside the if statement, we indent another level
return true;
} else {
return false;
} // The braces line up with what started the scope
}
int main() {
// Everything in the root scope of the function is indented one level
int value;
std::cin >> value;
// The outer parts of this while loop are indented one level
while (true) {
// Inside the while loop, we indent another level
if (check_value(value)) {
// Inside another scope, so we indent a third level
std::cout << "You have entered a good value.\n";
break;
}
// We drop one indent level after exiting the if statement scope
std::cout << "You have entered a bad value.\n";
std::cout << "Try again.\n";
} // This brace lines up with the while loop that started this scope
// We return to a single level of indentation after exiting the while loop scope
std::cout << "Goodbye!";
} // This brace lines up with the beginning of the function
bool check_value(int x) {
// This if statement should be indentented one
if (x < 50) {
return true; // This line is missing two indentations
} else {
return false; // The indentation here is obviously not consistent
}} // These braces should be on their own lines and properly indented
// The function is indented too far over
int main() {
int value;
std::cin >> value; // The indentation here is not consistent for the same scope
while (true) {
// This if statement is missing an indentation level
if (check_value(value)) {
std::cout << "You have entered a good value.\n"; break; // Don't put two things on one line
} // This brace does not line up with what created it
// These lines are not indented
std::cout << "You have entered a bad value.\n";
std::cout << "Try again.\n";
} // This brace does not line up with the while loop that created it
// This line is not consistent with the rest of the indentation in this scope
std::cout << "Goodbye!";
}
您可以看到第一个示例中的所有代码都对齐得很整齐。我们遵循一致的缩进规则,根据我们所处的范围深度进行缩进。每个嵌套层都意味着一个额外的缩进级别。此外,我们所有的大括号都与“启动”该范围的父级对齐,这使得很容易找到大括号所属的构造。
另一方面,第二个示例中的代码阅读起来明显更令人困惑和沮丧。很难找出哪些代码属于哪个范围,范围在哪里结束,以及何时应该执行。此外,它通常阅读起来非常混乱,查看起来也很痛苦。
保持缩进整洁一致似乎是额外的工作,特别是如果您正在更改现有代码。但是,请遵循这些准则,我保证您以后会感谢自己。初学者很容易被不良缩进误导,从源头解决是防止这种情况发生的最佳方法。
如果您觉得处理缩进很困难,可以考虑利用快捷键。在大多数代码编辑器中,您可以通过选择要缩进的行并按 Tab
键来一次缩进多行。反之,您可以使用 Shift+Tab
一次取消缩进多行。
避免复杂的布尔表达式
如果您试图构建一个需要验证多个布尔表达式的条件,使用过多的 and
、or
和 not
很容易导致代码混乱。防止这种情况的最佳方法是创建一个返回 bool
的函数,该函数为您计算这个复杂的表达式。这样,代码变得更具可读性和可维护性。您可能还会发现下一节中的方法很有用。
避免条件深度嵌套
使用 if 语句很容易创建复杂的逻辑,尤其是当您将它们过度嵌套时。例如,尝试弄清楚以下代码片段实际做了什么
if (A or B) {
if (not B and C) {
if (A or D) {
if (A and not B) {
std::cout << "Hello!";
} else {
std::cout << "Will this line ever be run?";
}
} else if (not D) {
std::cout << "What about this one?";
}
} else {
std::cout << "It's starting to get pretty weird now...";
}
} else if (B) {
std::cout << "I don't think this one will ever run.";
} else if (not A and not b) {
std::cout << "Surely, this one will be called eventually.";
} else {
std::cout << "Goodbye!";
}
您可能需要几分钟才能弄清楚在哪些情况下这些打印语句会激活,尤其是在大型且可能非常复杂的代码库中工作时。
有两种很好的方法来改进这一点。首先,您可以将内部嵌套条件分解为自己的函数。这样,复杂性就会隐藏在一层*抽象*之后,并且您被迫为您创建的每个函数创建一个描述性名称。
第二种方法适用于您有明确的“快乐路径”和“悲伤路径”的情况。您可以重新组织 if 语句,使得快乐路径的缩进最少,而悲伤路径则尽早退出。研究此示例以查看其可能的样子
- ✔️ 良好的 if 语句
- ⚠️ 令人困惑的 if 语句
using std::string;
string login_as_admin(string user, string pass) {
if (not does_user_exist(user)) {
return "User does not exist";
}
if (not is_password_correct(user, pass)) {
return "Password is incorrect";
}
if (not try_login(user, pass)) {
return "Failed to login";
}
if (not is_admin(user)) {
return "User is not admin";
}
return "Success";
}
using std::string;
string login_as_admin(string user, string pass) {
if (does_user_exist(user)) {
if (is_password_correct(user, pass)) {
if (try_login(user, pass)) {
if (is_admin(user)) {
return "Success";
} else {
return "User is not admin";
}
} else {
return "Failed to login";
}
} else {
return "Password is incorrect";
}
} else {
return "User does not exist";
}
}
您可以看到令人困惑的示例有一个明显的三角形形状。这是一个非常常见的指标,表明您的逻辑嵌套过深,您应该应用两种清理方法之一来修复它。在这里,我们反转了条件,首先检查悲伤路径。一旦我们通过了所有检查,快乐路径就会在底部继续。