if 语句与语句块:C++ 条件控制详解

我们将首先讨论控制流语句中的第一类——条件语句。条件语句用于指定在何种情况下应执行与之关联的一条或多条语句。

C++ 支持两种基本的条件语句:if 语句(《if 语句简介》中介绍,本节将继续展开)和 switch 语句(将在后续两节讲解)。

if 语句快速回顾

C++ 中最基本的条件语句是 if 语句,其语法形式如下:

if (condition)
    true_statement;

或带可选的 else 分支:

if (condition)
    true_statement;
else
    false_statement;

若条件求值为真,则执行 true_statement;若条件求值为假且存在 else 分支,则执行 false_statement

以下示例程序展示了带 elseif 语句:

#include <iostream>

int main()
{
    std::cout << "Enter a number: ";
    int x{};
    std::cin >> x;

    if (x > 10)
        std::cout << x << " is greater than 10\n";
    else
        std::cout << x << " is not greater than 10\n";

    return 0;
}

程序运行结果符合预期:

Enter a number: 15
15 is greater than 10
Enter a number: 4
4 is not greater than 10

if 或 else 后需多条语句时

初学者常写出如下代码:

#include <iostream>

namespace constants
{
    constexpr int minRideHeightCM { 140 };
}

int main()
{
    std::cout << "Enter your height (in cm): ";
    int x{};
    std::cin >> x;

    if (x >= constants::minRideHeightCM)
        std::cout << "You are tall enough to ride.\n";
    else
        std::cout << "You are not tall enough to ride.\n";
        std::cout << "Too bad!\n"; // 关注此行
    return 0;
}

然而,程序的一次运行结果如下:

Enter your height (in cm): 180
You are tall enough to ride.
Too bad!

结果不符合预期,原因在于 true_statementfalse_statement 均只能为单条语句。缩进在此处产生误导——上述代码实际等价于:

#include <iostream>

namespace constants
{
    constexpr int minRideHeightCM { 140 };
}

int main()
{
    std::cout << "Enter your height (in cm): ";
    int x{};
    std::cin >> x;

    if (x >= constants::minRideHeightCM)
        std::cout << "You are tall enough to ride.\n";
    else
        std::cout << "You are not tall enough to ride.\n";

    std::cout << "Too bad!\n"; // 始终执行

    return 0;
}

由此可见,“Too bad!” 总会被执行。

若需根据条件执行多条语句,应使用复合语句(即语句块):

#include <iostream>

namespace constants
{
    constexpr int minRideHeightCM { 140 };
}

int main()
{
    std::cout << "Enter your height (in cm): ";
    int x{};
    std::cin >> x;

    if (x >= constants::minRideHeightCM)
        std::cout << "You are tall enough to ride.\n";
    else
    { // 添加语句块
        std::cout << "You are not tall enough to ride.\n";
        std::cout << "Too bad!\n";
    }

    return 0;
}

由于语句块被视为单条语句,程序现可正确运行:

Enter your height (in cm): 180
You are tall enough to ride.
Enter your height (in cm): 130
You are not tall enough to ride.
Too bad!

隐式语句块

若程序员未在 ifelse 后的语句部分显式声明语句块,编译器将隐式创建。因此:

if (condition)
    true_statement;
else
    false_statement;

实际等价于:

if (condition)
{
    true_statement;
}
else
{
    false_statement;
}

多数情况下,此差异无关紧要。然而,初学者有时会试图在隐式块中定义变量,例如:

#include <iostream>

int main()
{
    if (true)
        int x{ 5 };
    else
        int x{ 6 };

    std::cout << x << '\n';

    return 0;
}

该程序无法通过编译,编译器报错标识符 x 未定义。原因在于上述代码等价于:

#include <iostream>

int main()
{
    if (true)
    {
        int x{ 5 };
    } // x 在此处销毁
    else
    {
        int x{ 6 };
    } // x 在此处销毁

    std::cout << x << '\n'; // x 已离开作用域

    return 0;
}

在此情形下,x 具有语句块作用域,并在块结束时被销毁;到达 std::cout 语句时,x 已不存在。

if 或 else 后单条语句是否加块的取舍

程序员社区对单条语句是否显式加块存在争议。

支持始终加块的理由:

  • 不加块易在无意中添加看似受条件控制、实则不受的语句。例如:

    if (age >= minDrinkingAge)
        purchaseBeer();
    

    匆忙中追加功能:

    if (age >= minDrinkingAge)
        purchaseBeer();
        gamble(); // 始终执行
    

    结果未成年人也能赌博,后果严重!

  • 不加块可能增加调试难度:

    if (age >= minDrinkingAge)
        addBeerToCart(); // 条件执行
    
    checkout(); // 始终执行
    

    若怀疑 addBeerToCart() 出错而将其注释:

    if (age >= minDrinkingAge)
    //    addBeerToCart();
    
    checkout(); // 意外变为条件执行
    

    若始终加块,上述问题均可避免。

  • C++23 引入的 if constexpr 要求必须使用块,因此加块可保持 ifif constexpr 的一致性。

反对加块的主要理由:

  • 加块会垂直拉长代码,减少一屏可见行数,降低可读性,可能引发更严重错误。

社区观点更倾向于始终加块,尽管该建议尚未成为普遍共识。

最佳实践

初学者宜将与 ifelse 关联的单条语句置于块内。经验丰富的开发者有时为获得更紧凑的垂直间距可不遵循此做法。

折中方案:将单行语句与 ifelse 置于同一行:

if (age >= minDrinkingAge) purchaseBeer();
else std::cout << "No drinky for you\n";

该方法在牺牲少量可读性的同时避免了前述两个问题。

对单行写法的一个合理批评是调试困难:

  • 条件与语句在同一执行步骤完成,难以判断语句是否实际执行。
  • 二者位于同一行,无法在语句处单独设断点。

若调试时受上述限制,可在条件与语句间插入换行(分占两行),调试后再恢复。

if-else 与连续 if 的选择

初学者常困惑何时使用 if-elseif 后接若干 else)与连续 if(多个并列 if)。

  • 若仅希望执行首个为真条件后的代码,应使用 if-else
  • 若希望执行所有为真条件后的代码,应使用连续 if

示例如下:

#include <iostream>

void ifelse(bool a, bool b, bool c)
{
    if (a)      // 始终求值
        std::cout << "a";
    else if (b) // 仅当先前条件为假时求值
        std::cout << "b";
    else if (c) // 仅当先前条件为假时求值
        std::cout << "c";
    std::cout << '\n';
}

void ifif(bool a, bool b, bool c)
{
    if (a) // 始终求值
        std::cout << "a";
    if (b) // 始终求值
        std::cout << "b";
    if (c) // 始终求值
        std::cout << "c";
    std::cout << '\n';
}

int main()
{
    ifelse(false, true, true);
    ifif(false, true, true);

    return 0;
}

调用 ifelse(false, true, true) 时,a 为假,不执行对应语句,转而执行 elseb 为真,输出 b,后续 else 不再执行。仅首个为真条件后的代码被执行。

调用 ifif(false, true, true) 时,a 为假,跳过;b 为真,输出 bc 为真,输出 c。所有为真条件后的代码均被执行。

再看下例:

char getFirstMatchingChar(bool a, bool b, bool c)
{
    if (a) // 始终求值
        return 'a';
    else if (b) // 仅当先前条件为假时求值
        return 'b';
    else if (c) // 仅当先前条件为假时求值
        return 'c';

    return 0;
}

由于使用 if-else,仅首个为真条件后的代码被执行。但当每条关联语句均返回值时,可改写为:

char getFirstMatchingChar(bool a, bool b, bool c)
{
    if (a) // 始终求值
        return 'a'; // 条件为真即返回
    if (b) // 仅当先前条件为假时求值
        return 'b'; // 条件为真即返回
    if (c) // 仅当先前条件为假时求值
        return 'c'; // 条件为真即返回

    return 0;
}

表面看似连续 if,然而一旦首个条件为真,函数即返回,后续 if 不再求值,因此行为与前述版本等价。当所有关联语句均返回值时,多数程序员倾向省略 else,以减少冗余并使条件对齐更佳。

核心洞见

若所有关联语句均返回值,可直接使用连续 if,此时 else 并无额外价值。

下一节将继续深入探讨 if 语句。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"