这一章在干嘛?

循环解决的是”重复多少次”,这一章解决的是”该走哪条路”:用户选了哪个菜单项?输入的是不是 0?这个字符是字母还是数字?C++ 提供 if、if else、switch 三种分支语句做选择,用 &&、||、! 把多个条件拼成复杂判断,还有条件运算符 ? : 这种单行版选择器。最后把控制台 I/O 的本领平移到文本文件的读写上。加上前一章的循环,程序流程控制的工具箱就齐了。

6.1 if 语句与 if else 语句

6.2 逻辑运算符与短路求值

6.3 cctype 字符函数库

6.4 条件运算符

6.5 switch 语句

6.6 break、continue 与 goto

6.7 读取数字的循环

6.8 简单文件输入输出

6.1 if 语句与 if else 语句

简单 if

if 语句的语法与 while 相似:

if (test-condition)
    statement

条件为真执行 statement(单条语句或块),为假跳过。测试条件同样会被转换成 bool(0 假、非 0 真),整个 if 结构算一条语句。

▲ 图 6.1 if 语句的结构

程序清单 6.1 在逐字符读入的循环里用 if 统计空格:

// if.cpp -- 使用 if 语句
#include <iostream>
int main()
{
    using std::cin;    // using 声明
    using std::cout;
    char ch;
    int spaces = 0;
    int total = 0;
    cin.get(ch);
    while (ch != '.')    // 句号处结束
    {
        if (ch == ' ')   // 检查 ch 是不是空格
            ++spaces;
        ++total;         // 每次都执行
        cin.get(ch);
    }
    cout << spaces << " spaces, " << total;
    cout << " characters total in sentence\n";
    return 0;
}
The balloonist was an airhead with lofty goals. 6 spaces, 46 characters total in sentence

++spaces; 只在 ch 是空格时执行;++total; 在 if 之外,每轮都执行。总数还包括按回车产生的换行符。

if else

if 决定”做不做”,if else 决定”做这个还是做那个”:

if (test-condition)
    statement1
else
    statement2

条件为真执行 statement1 跳过 statement2,为假反之。两个分支各只能放一条语句,多条必须用花括号打包成块。

▲ 图 6.2 if else 语句的结构

程序清单 6.2 对文本做简单加密(每个字符码 +1),换行符保持原样——正好用 if else 分两条路:

// ifelse.cpp -- 使用 if else 语句
#include <iostream>
int main()
{
    char ch;
 
    std::cout << "Type, and I shall repeat.\n";
    std::cin.get(ch);
    while (ch != '.')
    {
        if (ch == '\n')
            std::cout << ch;   // 换行符原样输出
        else
            std::cout << ++ch; // 其他字符码加 1
        std::cin.get(ch);
    }
 
    std::cout << "\nPlease excuse the slight confusion.\n";
    return 0;
}
Type, and I shall repeat.
An ineffable joy suffused me as I beheld
Bo!jofggbcmf!kpz!tvggvtfe!nf!bt!J!cfifme
the wonders of modern computing.
uif!xpoefst!pg!npefso!dpnqvujoh
Please excuse the slight confusion.

(书里留了个思考题:把 ++ch 换成 ch+1 会发生什么?提示:cout 对不同类型的处理方式不同。)

格式提醒与 if else if else

分支里放多条语句必须加花括号,否则 else 会变成”无主之 else”,编译报错:

if (ch == 'Z')
{
    zorro++;
    cout << "Another Zorro candidate\n";
}
else
{
    dull++;
    cout << "Not a Zorro candidate\n";
}

超过两种选择时,把 if else 套进 else 里并排书写,就是清晰的 if else if else 结构——它其实是嵌套,只是排版让人看不出来:

if (ch == 'A')
    a_grade++;
else if (ch == 'B')
    b_grade++;
else
    soso++;

程序清单 6.3 用它做猜数字游戏:

// ifelseif.cpp -- 使用 if else if else
#include <iostream>
const int Fave = 27;
int main()
{
    using namespace std;
    int n;
 
    cout << "Enter a number in the range 1-100 to find ";
    cout << "my favorite number: ";
    do
    {
        cin >> n;
        if (n < Fave)
            cout << "Too low -- guess again: ";
        else if (n > Fave)
            cout << "Too high -- guess again: ";
        else
            cout << Fave << " is right!\n";
    } while (n != Fave);
    return 0;
}
Enter a number in the range 1-100 to find my favorite number: 50
Too high -- guess again: 25
Too low -- guess again: 37
Too high -- guess again: 31
Too high -- guess again: 28
Too high -- guess again: 27
27 is right!

防错小技巧:常量写左边

把 variable == value 反写成 value == variable,手滑漏成 value = variable 时编译器会立刻报错(不能给字面量 3 赋值);而正着写错成 myNumber = 3 是合法赋值,块照样执行,成了极难查找的暗病。

常见坑:if/else 与花括号

C++ 不会自动把 if 和 else 之间的内容当块,缩进只是给人看的。漏掉花括号轻则 else 找不到 if 报语法错误,重则某条语句悄悄落到分支之外,逻辑全乱。

6.2 逻辑运算符与短路求值

C++ 用三个逻辑运算符组合或反转已有表达式。

逻辑 OR:||

任一(或两个)表达式为真,结果就为真:

5 == 5 || 5 == 9   // 真,第一个为真
5 > 8 || 5 < 2     // 假,两个都为假

| expr1 \|\| expr2 的值 | expr1 为真 | expr1 为假 | |---|---|---| | expr2 为真 | 真 | 真 | | expr2 为假 | 真 | 假 |

|| 优先级低于关系运算符,不用加括号。程序清单 6.4 用一条 || 同时接受大小写:

// or.cpp -- 使用逻辑 OR 运算符
#include <iostream>
int main()
{
    using namespace std;
    cout << "This program may reformat your hard disk\n"
            "and destroy all your data.\n"
            "Do you wish to continue? <y/n>";
    char ch;
    cin >> ch;
    if (ch == 'y' || ch == 'Y')        // y 或 Y
        cout << "You were warned!\a\a\n";
    else if (ch == 'n' || ch == 'N')   // n 或 N
        cout << "A wise choice ... bye\n";
    else
        cout << "That wasn't a y or n! Apparently you "
                "can't follow\ninstructions, so "
                "I'll trash your disk anyway.\a\a\a\n";
    return 0;
}
This program may reformat your hard disk and destroy all your data.
Do you wish to continue? <y/n> N
A wise choice ... bye

程序只读一个字符,用户输入 NO! 时程序只看到 N(但剩下的 O! 还赖在输入队列里)。

逻辑 AND:&&

两个表达式都为真结果才为真:

5 == 5 && 4 == 4   // 真,两个都为真
5 > 3 && 5 > 10    // 假,第二个为假
expr1 \&\& expr2 的值expr1 为真expr1 为假
expr2 为真真假
expr2 为假假假

程序清单 6.5 用 && 同时检查”数组还有空位”和”输入非负”两个退出条件:

// and.cpp -- 使用逻辑 AND 运算符
#include <iostream>
const int ArSize = 6;
int main()
{
    using namespace std;
    float naaq[ArSize];
    cout << "Enter the NAAQs (New Age Awareness Quotients) "
         << "of\nyour neighbors. Program terminates "
         << "when you make\n" << ArSize << " entries "
         << "or enter a negative value.\n";
 
    int i = 0;
    float temp;
    cout << "First value: ";
    cin >> temp;
    while (i < ArSize && temp >= 0)  // 两个退出条件
    {
        naaq[i] = temp;
        ++i;
        if (i < ArSize)              // 数组还有空位,
        {
            cout << "Next value: ";
            cin >> temp;             // 就读下一个值
        }
    }
    if (i == 0)
        cout << "No data--bye\n";
    else
    {
        cout << "Enter your NAAQ: ";
        float you;
        cin >> you;
        int count = 0;
        for (int j = 0; j < i; j++)
            if (naaq[j] > you)
                ++count;
        cout << count;
        cout << " of your neighbors have greater awareness of\n"
             << "the New Age than you do.\n";
    }
    return 0;
}
Enter the NAAQs (New Age Awareness Quotients) of your neighbors.
Program terminates when you make 6 entries or enter a negative value.
First value: 28
Next value: 72
Next value: 15
Next value: 6
Next value: 130
Next value: 145
Enter your NAAQ: 50
3 of your neighbors have greater awareness of the New Age than you do.

注意程序先把输入读进临时变量 temp,验证合格后才存入数组——这样非法输入(负数)不会混进数组。

&& 的另一个妙用是划分区间,程序清单 6.6 按年龄段发”参赛资格”,用指针数组存一组提示字符串:

// more_and.cpp -- 使用逻辑 AND 运算符
#include <iostream>
const char * qualify[4] =    // 指针数组
{                            // 指向字符串
    "10,000-meter race.\n",
    "mud tug-of-war.\n",
    "masters canoe jousting.\n",
    "pie-throwing festival.\n"
};
int main()
{
    using namespace std;
    int age;
    cout << "Enter your age in years: ";
    cin >> age;
    int index;
 
    if (age > 17 && age < 35)
        index = 0;
    else if (age >= 35 && age < 50)
        index = 1;
    else if (age >= 50 && age < 65)
        index = 2;
    else
        index = 3;
 
    cout << "You qualify for the " << qualify[index];
    return 0;
}
Enter your age in years: 87
You qualify for the pie-throwing festival.

设计区间测试要保证区间之间无缝且不重叠(35 用 >= 接住,否则 35 会被所有区间漏掉)。

常见坑:数学式区间写法

if (17 < age < 35) 是合法 C++ 但语义全错:按左结合它等于 (17 < age) < 35,而 17 < age 的结果是 0 或 1,永远小于 35——整个条件恒为真!区间测试必须写成两个完整关系表达式用 && 相连。

逻辑 NOT:!

! 把后面的真值反转:真变假、假变真。多数时候不用它反而更清楚(!(x > 5) 不如 x <= 5 明白),但它配合返回真/假值的函数很好用:!strcmp(s1, s2) 为真即两串相同。程序清单 6.7 用 while (!is_int(num)) 拒绝超出 int 范围的输入——先把数读进范围更大的 double,用 climits 的 INT_MAX/INT_MIN 检查:

// not.cpp -- 使用 not 运算符
#include <iostream>
#include <climits>
 
bool is_int(double);
int main()
{
    using namespace std;
    double num;
 
    cout << "Yo, dude! Enter an integer value: ";
    cin >> num;
    while (!is_int(num))    // 不在 int 范围内就继续
    {
        cout << "Out of range -- please try again: ";
        cin >> num;
    }
    int val = int (num);    // 类型转换
    cout << "You've entered the integer " << val << "\nBye\n";
    return 0;
}
 
bool is_int(double x)
{
    if (x <= INT_MAX && x >= INT_MIN)    // 使用 climits 的值
        return true;
    else
        return false;
}
Yo, dude! Enter an integer value: 6234128679
Out of range -- please try again: -8000222333
Out of range -- please try again: 99999
You've entered the integer 99999
Bye

直接把大数读进 int 的话,很多实现会默默截断而不吭声;用 double 中转能提前拦住。

优先级与求值顺序

  • 关系运算符 > && > ||(&& 比 || 优先级高):age > 30 && age < 45 || weight > 300 意思是”(31~44 岁)或(体重超 300)“。拿不准就加括号——代码易读,还不怕记错规则。
  • ! 的优先级高于关系运算符:!x > 5 是 (!x) > 5,恒为假;想否定整个表达式必须写 !(x > 5)。
  • 短路求值:逻辑表达式从左到右算,一旦能定结果就停。x != 0 && 100.0 / x > 100.0 中若 x 为 0,右边根本不会求值——恰好避免了除零!反之 && 左边为真、|| 左边为假时右边一定会求值。&& 和 || 都是序列点,左边的副作用先完成。

替代表示

某些键盘打不出这些符号,C++ 提供了保留字替身:&& 可写 and、|| 可写 or、! 可写 not(C 里需包含 iso646.h,C++ 不用)。

6.3 cctype 字符函数库

判断”是不是字母”这类问题,手写 (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') 又长又依赖编码方案(它假设字母码值连续,ASCII 成立但并非处处成立)。C 从 C 继承了一包字符函数(头文件 cctype),isalpha(ch) 一句搞定且更通用。程序清单 6.8 逐字符分类统计:

// cctypes.cpp -- 使用 ctype.h 库
#include <iostream>
#include <cctype>    // 字符函数的原型
int main()
{
    using namespace std;
    cout << "Enter text for analysis, and type @"
            " to terminate input.\n";
    char ch;
    int whitespace = 0;
    int digits = 0;
    int chars = 0;
    int punct = 0;
    int others = 0;
 
    cin.get(ch);              // 读第一个字符
    while (ch != '@')         // 测试哨兵
    {
        if (isalpha(ch))      // 是字母吗?
            chars++;
        else if (isspace(ch)) // 是空白字符吗?
            whitespace++;
        else if (isdigit(ch)) // 是数字吗?
            digits++;
        else if (ispunct(ch)) // 是标点吗?
            punct++;
        else
            others++;
        cin.get(ch);          // 读下一个字符
    }
    cout << chars << " letters, "
         << whitespace << " whitespace, "
         << digits << " digits, "
         << punct << " punctuation, "
         << others << " others.\n";
    return 0;
}