第五章 循环与关系表达式
小目录
- for循环
- 表达式和语句
- 增减操作符:++和--
- 赋值操作符组合
- 复合语句(区块)
- 逗号运算符
- 关系运算符:>,>=,==,<=,<和!=
- while循环
- typedef功能
- do while循环
- get()字符输入方法
- end-of-file(文件尾)状况
- 嵌套循环和二维数组
for循环介绍
示例代码:
#include <iostream>
int main()
{
using namespace std;
int i;
for(i = 0;i < 5;i++)
cout << "loop" << i << endl;
cout << "loop end." << endl;
}
输出:
loop0
loop1
loop2
loop3
loop4
loop end.
for循环从i=0开始,这是循环初始化(loop initialization)部分。然后是循环测试(loop test),在这部分程序会测试i<5是否为真。如果为真,则执行循环内的代码,也即是执行循环体(loop body)。再之后执行循环更新(loop update)部分,将i的值加一。
for循环的每个部分
for循环通常包含以下部分:
- 值初始化
- 进行测试以决定是否继续循环
- 执行循环操作
- 更新测试用到的值
其格式为:
for(initialization;test-expression;update-expression)
body
表达式和语句
C++中任意值或任意有效的值和操作符的组合都可以组成为表达式。而每个表达式都含有一个值。一般来说这个值会比较明显。比如22+27这个表达式的值就是49.但有一些的值会不那么明显,比如x=10.C++将一个赋值表达式的值定义为其左边的成员,所以x=10的值为10.这就使得连续赋值成为可能。
x = y = 10; //y=10的值为10,左式相当于y=10,x=y
而最后关于关系表达式比如x>y,它们的值则是布尔类型值true或false。
非表达式和语句
虽说给表达式加上分号就可以得到一条语句。但把一条语句去掉分号并不总能得到一个表达式。比如int a;是一条语句,去掉分号得到的int a却不是一个表达式。故而它也没有值。你不能采用下列的写法:
b = int a;
cout << int a;
同样的,for循环也不是一个表达式。
折叠规则
其实for循环里的测试用变量不一定要在循环初始化前声明,而可以直接在循环初始化部分声明,如下:
for(int i = 0;i < 5;i++)
改变步幅
至今写的for循环的循环更新部分都是将测试变量加(减)1,但实际上我们是可以任意对测试变量进行更新的,简单的比如加2:
for(int i = 0;i < 5;i = i + 2)
增减运算符
在上面写的for循环有用到了增减运算符++和--。其中++实际上就是C++这个名字的来源。
这两个操作符有两种用法,那就是放在值的前面或后面。其不同在于对值的操作是在值被应用前还是应用后。看如下示例代码:
#include <iostream>
int main()
{
using namespace std;
int a = 1, b = 1;
int c = a++, d = ++b;
cout << "a:" << a << " b:" << b
<< " c:" << c << " d:" << d << endl;
}
输出:
a:2 b:2 c:1 d:2
可以看出,表达式a++的值是a增值后的值2。而表达式++b的值则是b增值前的值1.
边效应(side effect)和序列点(sequence point)
首先,边效应是在表达式进行变更,比如对变量,的时候起作用。而序列点则是边效应的作用结束点。在C++里,语句末的分号就起到序列点的作用。也即是所有的包括赋值操作,增减操作都必须在该句的分号之前完成。同样,一个完整表达式的末尾也是一个序列点。
完整表达式是指一个不作为另一个表达式的子表达式的表达式。完整表达式包括表达式语句的表达式部分还有循环测试部分的表达式。
关于前后缀
当你不使用表达式a++的值的时候,a++和++a似乎没有任何区别,但实际上,这二者在效率上有着轻微的不同。对于C++自有类型自然就无关紧要,但C++同样允许你将这个运算符定义给类。而这时就要注意前置和后置的区别。前置运算符的步骤是先把操作对象增值,然后将其返回。而后置则是将对象复制,然后将其增值,在将复制的对象返回。也即是后置运算符会多出一步复制。在对复杂对象操作时就会产生效率差距。不过对于自有类型就没有太大关系了。
增减运算符与指针
对指针同样可以使用++与--。其效果将是使指针指向的地址增(减)一个它指向的类型的长度。
而当你同时对指针使用++和*时,就涉及到优先级问题。你究竟是让地址增值还是让地址内的变量增值?粗略来说就是这两个运算符有着相同优先级,它们遵循从右至左原则。
具体来讲就是:
++*a = ++(*a)
*++a = *(++a)
*a++ = *(a++)
但要注意的是,后置运算符虽然先运算,但a++这个表达式的值是a增值前的值。也即是*a++的取到的值与*a相同,只是*a++结束后a的值加一。
赋值操作符组合
a += b; //a = a + b
a -= b; //a = a - b
a *= b; //a = a * b
a /= b; //a = a / b
a %= b; //a = a % b
复合语句(区块)
for循环的循环体只能写一句似乎并不能满足需要。所以C++允许使用大括号来构建一个复合语句或者说区块。区块由一对大括号和其中的多句代码组成。一整个区块可以算作一条语句。所以最开始的for循环示例可以改成:
#include <iostream>
int main()
{
using namespace std;
int i;
for(i = 0;i < 5;i++)
{
cout << "loop" ;
cout << i << endl;
}
cout << "loop end." << endl;
}
逗号操作符
C++允许你通过区块在只允许写一条语句的地方塞进去多条语句。而对于表达式,C++同样提供了逗号操作符在达成相同效果,在只允许写一个表达式的地方写多个表达式。比如for循环头部的三个组成部分,每个部分都只允许写一个表达式。而逗号操作符就可以在当中添加多个表达式:
for(int i = 0;i < 5;j++, i++)
但逗号并不总是一个逗号操作符,有时它仅仅是用于分隔变量列表里的名字:
int i,j; //仅用于分隔
关系表达式
关系表达式用于进行值的比较,C++提供了六种关系操作符用于比较数字。而字符由于char类型里存的实际是ASCII码,所以也同样可以用关系操作符。关系操作符不能用于C式字符串,但可以用于string类。每个关系表达式会返回一个布尔值,表达式为真时返回true,否则返回false。
操作符 | 作用 |
< |
小于 |
<= | 小于等于 |
== | 等于 |
> | 大于 |
>= | 大于等于 |
!= | 不等于 |
C式字符串比较
比如说有个C式字符串word,其值为“mate”,那么或许你会觉得以下表达式为真:
word == “mate”
但实际上要记住数组名指代的是数组地址。同样的,双引号括起来的字符串常量也指代其地址。以上语句中两个字符串地址自然是不同的,所以表达式也就不会为真。
如果你需要比较两个C式字符串,你需要使用C式字符串库里的strcmp方法。这个方法接收两个字符串作为参数。其参数可以是指针,字符串常量,字符数组名。如果两个字符串相同,那么方法返回0.如果第一个字符串从字母表来说先于第二个字符串,那么方法返回一个负数,反之返回正数。其实更准确来说应该是按系统字符表码来排。比如两个字符串的ASCII码大小。因为在ASCII中大写字母的码是小于小写字母的,也即是首先大小写是不同的,其次大写字母是先于小写字母的。所以“Zoo”是先于“abcd”的。
在某些语言比如BASIC里,存储于不同长度字符数组里的相同字符串是不相等的。但C++里的C式字符串比较是以字符串的空字符为结尾的,所以以下两个字符串对于strcmp是相等的:
char long[80] = "abc";
char short[4] = "abc";
还有,虽然不能对C式字符串使用关系运算符,但你可以对字符使用。
string类对象比较
string类对象可以用关系操作符比较是因为类允许通过“重载”(overload)或者说重定义来定义操作符。十二章将会讲到如何在类设计时引入这个特性。
while循环
while循环是for循环去掉了初始化和测试参数更新部分。格式:
while(test-condition)
body
do while循环
这个循环和前两个不同在于,它是在退出时进行测试(exit-condition)。也即是这个循环会先把循环体执行一遍,然后再进行测试。这种循环总是会执行至少一次。格式:
do
body
while(test-expression)
面向范围的for循环(C++11)
C++11新添了面向范围的for循环(range-based for loop)。它主要是针对常见的需求——对数组内每个元素进行操作——提供了简化的for循环。例子:
double d[5] = {1.0, 1.1, 1.2, 1.3, 1.4};
for (double dou : d)
cout << dou << endl;
如果要对数组元素进行操作,则需要改写为:
for(double &dou : d)
dou = dou + 2;
&符号将dou标识为一个引用变量。这部分会在第八章讲到。
这种for循环还能用于初始化列表:
for(int i : {1, 2, 3, 4})
cout << i;
二维数组
C++并没有提供特别的二维数组类型。而是通过定义一个数组的数组来创建一个二维数组。即数组内每个元素是一个数组。
声明格式:
int two_dimen [4] [5] =
{
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5},
{1, 2, 3, 4, 5}
};
总结
C++提供了三种循环:for循环、while循环和do while循环。只要循环测试的值为true或非零,循环就会反复执行同样的一个代码块,而测试值为false或0循环就会终止。for循环和while循环是进入测试的循环,也即是在每次执行前进行测试。而do while循环式退出测试的循环,也即是在每次执行后进行测试。
每个循环的循环体由一条语句组成。但这条语句可以是使用大括号括起的复合语句(或称为代码块)。
循环测试通常是一条关系表达式,这条语句会比对两个值。关系表达式使用六种关系运算符:<,<=,==,>,>=,!=。关系表达式的值为true或false。
许多程序从键盘输入或文件逐个字符地读入。可以使用cin >> ch的方式进行逐个字符读入,但这种方式会跳过空格、换行符和缩进。如果需要严格读入每个字符则需要用到cin.get(ch)。而没有参数的get方法则会返回其获取的字符,所以其用法应为ch = cin.get()。
cin.get(char)方法在会在遇到eof(end-of-file)时返回一个false,而cin.get()则返回值EOF,一个在iostream中定义的值。