本章内容包括:
- for 循环;
- 表达式和语句;
- 递增运算符和递减运算符: ++ 和 --;
- 组合赋值运算符;
- 复合语句(语句块);
- 逗号运算符;
- 关系运算符:>、>=、==、<=、< 和 !=;
- while 循环;
- typedef 工具;
- do while 循环;
- 字符输入方法 get() ;
- 文件尾条件;
- 嵌套循环和二维数组。
for循环为执行重复的操作提供了循序渐进的步骤。组成部分完成下面这些步骤:
- 设置初始值;
- 执行测试,判断循环是否应当继续进行;
- 执行循环操作;
- 更新用于测试的值。
初始化、测试和更 新操作构成了控制部分,这些操作由括号括起。其中每部分都是一个表 达式,彼此由分号隔开。控制部分后面的语句叫作循环体,只要测试表 达式为true,它便被执行:
for (initialization; test-expression; update-expression)
body;
循环体如果大于一条语句,需要加花括号:
for (initialization; test-expression; update-expression) {
body;
}
C++语法将整个for看作一条语句—虽然循环体可以包含一条或多条 语句。循环只执行一次初始化。
for
是一个 C++ 关键字,编译器不会将 for
视为一个函 数,这还将防止将函数命名为 for
。
【编程风格 Tips】C++ 常用的方式是,在 for 和括号之间加上一个空格,但省略函数名与括号之间的空格。
看这个表达式赋值逻辑:
x = y = z = 0;
赋值运算符是从右向左结合的,因此首先将0赋给z,然 后将z = 0赋给y,依此类推。
C++在C循环的基础上添加了一项特性,要求对for循环句法做一些 微妙的调整。
这是原来的句法:
for (expresson; expression; expression)
statement;
但,C++循环允许像下面这样做:
for (int i=0; i<5; ++i)
也就是说,可以在for循环的初始化部分中声明变量。这很方便,但并不适用于原来的句法,因为声明不是表达式。这个变量的生存时间只存在于for语句中,也就是说,当程序离开循环后,i
这个变量将消失。
很简单,略。
循环示例每一轮循环都将循环计数加1或减1。可以通 过修改更新表达式来修改步长,例如改为表达式 i = i + by
,其中 by
是用户选择的步长值。
很简单,略。
这两个运算符执行两种极其常见 的循环操作:将循环计数加1或减1。这两个运算符都有两种变体。前缀(prefix)版本位于操作数前 面,如++x;后缀(postfix)版本位于操作数后面,如x++。两个版本对操作数的影响是一样的,但是影响的时间不同。这就像对于钱包来说, 清理草坪之前付钱和清理草坪之后付钱的最终结果是一样的,但支付钱 的时间不同。
粗略地讲,a++意味着使用a的当前值计算表达式,然后将a的值加 1;而++b的意思是先将b的值加1,然后使用新的值来计算表达式。 递减操作符也同理。
C++就递增运算符何时生效的哪些方面做了规 定,哪些方面没有规定。首先,副作用(side effect)指的是在计算表达式时对某些东西(如存储在变量中的值)进行了修改;顺序点 (sequence point)是程序执行过程中的一个点,在这里,进入下一步之 前将确保对所有的副作用都进行了评估。在C++中,语句中的分号就是 一个顺序点,这意味着程序处理下一条语句之前,赋值运算符、递增运算符和递减运算符执行的所有修改都必须完成。
另外,任何完整的表达式末尾都是一个顺序点。
何为完整表达式呢?它是这样一个表达式:不是另一个更大表达式 的子表达式。完整表达式的例子有:表达式语句中的表达式部分以及用 作while循环中检测条件的表达式。
如果变量被用于某些目的(如用作函数参数或给变量赋 值),使用前缀格式和后缀格式的结果将不同。
然而,虽然选择使用前缀格式还是后缀格式对程序的行为没有影 响,但执行速度可能有细微的差别。对于内置类型和当代的编译器而 言,这看似不是什么问题。然而,C++允许您针对类定义这些运算符, 在这种情况下,用户这样定义前缀函数:将值加1,然后返回结果;但后缀版本首先复制一个副本,将其加1,然后将复制的副本返回。因 此,对于类而言,前缀版本的效率比后缀版本高。
可以将递增运算符用于指针和基本变量。将递增 运算符用于指针时,将把指针的值增加其指向的数据类型占用的字节数,这种规则适用于对指针递增和递减。
前缀递增、前缀递减和解引用运算符的优先级相同,以从右到左的方式进行结合。后缀递增和后缀递减的优先级相同,但比前缀运算符的优先级高,这两个运算符以从左到右的方式进行结合。
前缀运算符的从右到到结合规则意味着 *++pt
的含义如下:现将 ++
应用于 pt
(因为 ++
位于 *
的右边),然后将 *
应用于被递增后的 pt
。 另一方面,++*pt
意味着先取得 pt
指向的值,然后将这个值加 1,pt
依然指向原来的地址;
而这个组合 (*pt)++
圆括号指出,首先对指针解引用,得到值。然后,运算符++将这个值递增 1,但 pt
仍然指在原来的地址。
+=
运算符将两个操作数相加,并将结果赋给左边的操作数。这意味着左边的操作数必须能够被赋值,如变量、数组元素、结构体成员或通过对指针解引用来标识的数据。
只有这个地方要注意:假设对循环体进行了缩进,但省略了花括号,编译器将忽略缩进,因此只有第一条语句位于循环中。
语句块允许把两条或更多条语句放到按C++句法 只能放一条语句的地方。逗号运算符对表达式完成同样的任务,允许将 两个表达式放到C++句法只允许放一个表达式的地方。如:
++j, --i你 // 没有分号,合法,但不能独立运行,要在另外语句中,如 for 里
但逗号并不总是逗号运算符。 如:
int i, j; // 个声明中的逗号将变量列 表中相邻的名称分开
到目前为止,逗号运算符最常见的用途是将两个或更多的表达式放 到一个for循环表达式中。不过C++还为这个运算符提供了另外两个特 性。首先,它确保先计算第一个表达式,然后计算第二个表达式(换句 话说,逗号运算符是一个顺序点)。
i = 20, j = 2 * i // 这是合法的,这里 y 是 40
在所有运算符中,逗号运算符的优先级是最低的。
很简单,略。
假设要知道字符数组中的字符串是不是mate。如果word是数组名, 下面的测试可能并不能像我们预想的那样工作:
word == "mate"
请记住,数组名是数组的地址。同样,用引号括起的字符串常量也 是其地址。因此,上面的关系表达式不是判断两个字符串是否相同,而 是查看它们是否存储在相同的地址上。两个字符串的地址是否相同呢? 回答是否定的,虽然它们包含相同的字符。
由于C++将C-风格字符串视为地址,因此如果使用关系运算符来比 较它们,将无法得到满意的结果。需要用 C 字符串库中的 strcmp() 函数来比较。 该函数接受两个字符串地址作为参数。这意味着 参数可以是指针、字符串常量或字符数组名。如果两个字符串相同,该 函数将返回零;如果第一个字符串按字母顺序排在第二个字符串之前, 则strcmp( )将返回一个负数值;如果第一个字符串按字母顺序排在第二 个字符串之后,则strcpm( )将返回一个正数值。实际上,“按系统排列顺 序”比“按字母顺序”更准确。这意味着字符是根据字符的系统编码来进 行比较的。例如,使用ASCII码时,所有大写字母的编码都比小写字母 小,所以按排列顺序,大写字母将位于小写字母之前。因此,字符 串“Zoo”在字符串“aviary”之前。
存储在不同长度的数组 中的字符串彼此不相等。但是C-风格字符串是通过结尾的空值字符定义 的,而不是由其所在数组的长度定义的。这意味着两个字符串即使被存 储在长度不同的数组中,也可能是相同的。
如果使用string类字符串而不是C-风格字符串,比较起来将简单些。直接通过运算符比较:>, <, == 等。
while
循环是一个没有初始化和更新部分的 for
循环,它只有测试条件和循环体,与for循环一样,循环体也由一条语句或两个花括号定义的语句块组成。句法如下:
while (test-condition)
body
如果循环体有多条语句,需要花括号:
while (test-condition) {
body
}
首先,程序计算圆括号内的测试条件(test-condition)表达式。如 果该表达式为true,则执行循环体中的语句。执行完循环体后,程序返 回测试条件,对它进行重新评估。如果该条件为非零,则再次执行循环 体。测试和执行将一直进行下去,直到测试条件为false为止。
while 循环和 for 循环一样,也是一 种入口条件循环。因此,如果测试条件一开始便为false,则程序将不会 执行循环体。
在C++中,for 和 while 循环本质上是相同的。
但它们之间存在三个差别:
- 在
for
循环中省略了测试条件时,将认为条件为true
; - 在
for
循环中,可使用初始化语句声明一个局部变量,但在while
循环中不能这样做; - 如果循环体中包括
continue
语句,情况将稍有不同,continue
语句将在第6章讨论。
通常,程序员使用 for
循环来为循环计数,因为 for
循环格式允许将所有相关的信息—初始值、终止值和更新计数器的方法—放在同一个地方。在无法预先知道循环将执行的次数时,程序员常使用 while
循环。
函数 clock()
可以返回程序开始执行后所用的系统时间。但,首先,clock( )返回时间的单位不一定是秒;其次,该函数的返回类 型在某些系统上可能是long,在另一些系统上可能是unsigned long或其 他类型。
头文件 ctime
提供了这些问题的解决方案。首先,它定义了一个符号常量—CLOCKS_PER_SEC
,该常量等于 每秒钟包含的系统时间单位数。因此,将系统时间除以这个值,可以得 到秒数。或者将秒数乘以 CLOCK_PER_SEC
,可以得到以系统时间单位 为单位的时间。其次,ctime
将 clock_t
作为 clock()
返回类型的别名,这意味着可以将变量声明为 clock_t
类型,编译器将把它转换为long、unsigned int 或适合系统的其他类型。
它 不同于另外两种循环,因为它是出口条件(exit condition)循环。这意 味着这种循环将首先执行循环体,然后再判定测试表达式,决定是否应 继续执行循环。如果条件为false,则循环终止;否则,进入新一轮的执 行和测试。这样的循环通常至少执行一次,循环体是一条语句或用括号括起的语句块。句法:
do {
body
} while (test-condition);
C++11新增了一种循环:基于范围(range-based)的for循环。这简化了一种常见的循环任务:对数组(或容器类,如vector和array)的每 个元素执行相同的操作。
为什么程序在输出时省略了空格呢?原因 在cin。读取char值时,与读取其他基本类型一样,cin将忽略空格和换行 符。因此输入中的空格没有被回显,也没有被包括在计数内。
更为复杂的是,发送给cin的输入被缓冲。这意味着只有在用户按 下回车键后,他输入的内容才会被发送给程序。这就是在运行该程序 时,可以在#后面输入字符的原因。按下回车键后,整个字符序列将被 发送给程序,但程序在遇到#字符后将结束对输入的处理。
通常,逐个字符读取输入的程序需要检查每个字符,包括空格、制 表符和换行符。cin
所属的 istream
类(在 iostream
中定义)中包含一个能 够满足这种要求的成员函数。具体地说,成员函数cin.get(ch)读取输入 中的下一个字符(即使它是空格),并将其赋给变量ch。使用这个函数 调用替换cin>>ch,可以修补程序清单5.16的问题。
在C语言中, 要修改变量的值,必须将变量的地址传递给函数。但程序清单5.17调用 cin.get( )时,传递的是ch,而不是&ch。在C语言中,这样的代码无效, 但在C++中有效,只要函数将参数声明为引用即可。引用是C++在C语 言的基础上新增的一种类型。头文件iostream将cin.get(ch)的参数声明为 引用类型,因此该函数可以修改其参数的值。
函数重载允许创建多个同名函数,条件是它们 的参数列表不同。例如,如果在C++中使用 cin.get(name,ArSize)
, 则编译器将找到使用 char*
和 int
作为参数的 cin.get()
版本;如果使用 cin.get(ch)
,则编译器将使用接受一个 char
参数的版本;如果没有提 供参数,则编译器将使用不接受任何参数的cin.get()
版本。函数重载允 许对多个相关的函数使用相同的名称,这些函数以不同方式或针对不同类型执行相同的基本任务。
如果输入来自于文件,则可以使用一种功能更强大的技 术—检测文件尾(EOF)。C++输入工具和操作系统协同工作,来检测文件尾并将这种信息告知程序。
检测到EOF后,cin将两位(eofbit和failbit)都设置为1。可以通过 成员函数eof( )来查看eofbit是否被设置;如果检测到EOF,则cin.eof( )将 返回bool值true,否则返回false。同样,如果eofbit或failbit被设置为1, 则fail( )成员函数返回true,否则返回false。
为成功地使用cin.get( ),需要知道其如何处理EOF条件。当该函数 到达EOF时,将没有可返回的字符。相反,cin.get( )将返回一个用符号 常量EOF表示的特殊值。该常量是在头文件iostream中定义的。EOF值 必须不同于任何有效的字符值,以便程序不会将EOF与常规字符混淆。 通常,EOF被定义为值−1,因为没有ASCII码为−1的字符,但并不需要知道实际的值,而只需在程序中使用EOF即可。
另一方面,使用cin.get(ch)(有一个参数)进行输入时,将不会导 致任何类型方面的问题。前面讲过,cin.get(char)函数在到达EOF时,不 会将一个特殊值赋给ch。事实上,在这种情况下,它不会将任何值赋给 ch。ch不会被用来存储非char值。
那么应使用cin.get( )还是cin.get(char)呢?使用字符参数的版本更符 合对象方式,因为其返回值是istream对象。这意味着可以将它们拼接起 来。例如,下面的代码将输入中的下一个字符读入到ch1中,并将接下 来的一个字符读入到ch2中:
cin.get(ch1).get(ch2);
C++没有提供二维数组类型,但用户可以创建每个元素本身都是数 组的数组。可以这样声明数组:
int maxtemps[4][5];
假设要打印数组所有的内容,可以用一个for循环来改变行,用另一 个被嵌套的for循环来改变列:
for (int row = 0; row < 4; ++row) {
for (int col=0; col < 5; ++col) {
cout << maxtemps[row][col] << "\t";
}
cout << endl;
}
实际上,我更喜欢用 vector 来创建动态二维数据,只要创建 vector 的vector 就行,用起来更简单。
C++提供了3种循环:for循环、while循环和do while循环。如果循环 测试条件为true或非零,则循环将重复执行一组指令;如果测试条件为 false或0,则结束循环。for循环和while循环都是入口条件循环,这意味 着程序将在执行循环体中的语句之前检查测试条件。do while循环是出 口条件循环,这意味着其将在执行循环体中的语句之后检查条件。
关系表达式对两个值进行比较,常被用作循环测试条件。关系表达 式是通过使用6种关系运算符之一构成的:<
、<=
、==
、>=
、>
或 !=
。 关系表达式的结果为bool类型,值为true或false。