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