Cpt5 循环loop和关系表达式
5.1 for循环(定量循环,while则属于条件循环)
a. for是一个C++关键字;
基本的循环结构表示为,
for(initialization初始化,test-expression循环测试,update-expression循环更新)
{
loop body循环体;
}
其中循环测试作为进入循环体的条件(入口条件循环),可以用循环体里的break实现;
循环更新在执行循环体后自动执行,一般用于表述循环条件更新;
理论上这些元素都可以缺省;
for(; ?{};//循环测试缺省时默认为真;
C++将整个for循环看作一条语句;
b. 尽管C++实际上不允许使用关键字for作为函数名,更常用的for格式是在for和括号之间增加空白以区分函数;此外还可以引入缩进增强格式;
c. 表达式和语句,任何值或任何有效的值和运算符的组合都可以看成表达式,表达式都有值;
对赋值表达式,其值等于左值;
关系表达式的值一般为true和false,默认输出为1/0,可以通过cout设置格式,cout.setf(ios_base::boolalpha)使后续输出表示为bool型;
通常表达式中的运算符优先级低于插入流操作符<<,因此在流操作中使用表达式值需要增加括号;
d. 赋值表达式的一个副作用,使用赋值表达式的值时应注意该表达式实际上包含了一个赋值语句,
e. 表达式+分号即构成了一个表达式语句;表达式语句不一定有编程意义,例如a+b;该表达式没有接受值的变量,a+b也没有影响;
删除分号之后不能构成表达式的语句称为非表达式语句;
由于非表达式语句不能等效为数值,因此使用条件有限,例如返回语句、声明语句、for循环等;
f. C++允许在()里声明变量,实现采用声明语句表达式的形式;或者采用for-init-statement表达式形式,即将for(int a=10;a<5;a–)里的int a=10;a<5看作是一个表达式整体;
(有些实现中a在循环之后可用);
通常使用一个const值显式的定义数组的元素个数是一个好办法,便于数组的声明和数组长度的引用;
g. 对循环测试部分,使用不等关系式优于等号关系条件,因为判别变量值可能是有限制的;
h. for循环字符串访问,例如字符串倒序;
参数传递是值传递;
i. ++x(前缀格式,先++,表达式等于++后的x,即表达式等价为x=x+1)和x++,区别是先执行++操作还是先给表达式赋值;
不建议在同一条语句中对同一个值进行多次++/–运算,可能导致结果的不确定性;
例如
j. 副作用和顺序点
副作用t指的是在计算表达式时对某些东西进行了修改;
顺序点是程序执行过程中的一个点,进入下一步之前将确保对所有的副作用进行处理和评估(判断),例如语句中的分号;
对于复杂语句例如循环判断等,完整的表达式末尾同样是顺序点;在顺序点,表达式的状态稳定,完整表达式确保执行完当前语句后才会执行后续语句;
例如while(a++<10) cout<<a<<endl;这里a++<10判断后a其实已经增加了,所以最后一次循环输出的a是10,C++确保这一操作在进入cout之前完成;
而y=(4+x++)+(6+x++);整条语句整条语句是一个完整表达式,即分号位置时C++确保该语句对x执行两次递增,但没有保证是在计算每个子表达式(4+x++)和(6+x++)之后递增,还是整个表达式计算完成后递增,因此应该避免使用这类表述;
k. 在不用表达式的值的情况下前缀格式和后缀格式没有区别,但执行速度存在细微的差别;对内置类型基本没有差别,对自定义类的运算符重载,++a是将值加一,返回结果;a++是先复制一个副本,然后加1,然后返回复制的副本,效率较低;
i. 递增/递减运算符和指针
仍然遵循指针运算符以type作为基本单位的规则;
考虑到可能存在类似*(++p)这样的运算,在不用括号表示()的情况下需要考虑运算符优先级的问题,一般后缀运算符优先级相同(L-R运算),优先级高于前缀运算符,前缀运算符优先级相同,R-L运算
j. 组合赋值运算符
x+=by;等价于x=x+by,要求x是允许作为左值的内容;
此外还有-=、*=、/=、%=;
k. 复合语句
代码块由一对花括号和它们包含的语句组成,被视为一条语句,从而满足句法的要求;
语句块中定义的变量其生存期为语句块范围;
语句块是由花括号定义的而不是缩进缩进是一种空白对C++源码是没有实际意义的;
在不采用语句块的情况下,for循环只执行其后的第一句代码作为循环体;
(话说怎么让一个代码段里定义的变量对另一个代码段可见啊,这样生存期可以表示的更直观);
l. 逗号运算符,逗号运算符的作用是构造包含多个子操作的实际上仍然属于单条语句的表达式,例如for(int i=0,j=1;i<5,j<6;i–)这种(但int I,j=0这条语句是错误的,可能会被当成逗号运算,这样后边的变量会被看成是没有声明);
int I,j=0;实际上等于int I;j=0;
在内部声明的变量在每次循环过程中都会分配-释放内存,可能会降低运行效率;
C++补充特性,对逗号运算符,确保先计算第一个表达式,然后计算第二个表达式,其整体值为第二个表达式的值;逗号运算符优先级最低;
cats=(17,240);cats=240;
m. 关系表达式
关系运算符可以用于字符、string类对象,不可以用于char数组;其返回值包括true和false,可以用作循环测试表达式(老式实现会将true等价为1,但实际上bool true是一种整型实现);
<、<=、>、>=、、!=;这6种关系运算符可以在C++中完成对数据的所有比较;
关系运算符的优先级低于算数运算符;
注意区分比较运算符和=赋值运算符;
n.对于类似for(;i=x;)这样的语句,当x=0时,表达式i=x整体值为0,表达式退出;但是这种操作显然很可能导致循环溢出,例如for(;*pi=1;pi++),这个语句不断地将内存写入1,可能会导致计算机故障;因此需要注意这类数组越界问题;
o. 针对数组越界问题需要采用不同的措施;
p. C-style字符串,字符数组的比较,由于字符数组继承数组的特性,char a[]==char b[]实际上是判断a和b是否是相同的地址,因此C提供了库函数strcmp(a,b)进行比较,其参数为两个字符指针,a和b一致,返回0,若a按照字母(编码)顺序排在b之前(a<b),返回负值,a和b可以有不同的长度;
关系运算符对单个字符有效;
q. 对于string类,关系运算符进行重载,要求至少有一个操作数为string对象,char数组上转换为string类对象;可以看出,string类对象在关系测试表达式中表现为整体的形式,而在数组表示法中表示为聚合对象的形式;
5.2 while循环(入口条件循环)
a. while循环不具有固定位置的初始化和更新,其样式为
while(test-condition){body};
while循环执行循环体返回测试条件,判断是否进行下次循环,可以看成是
if(true) while;else break;
因此要求循环体中的代码必须完成某种影响测试条件表达式的操作;
例如用while循环处理char[]字符串,可以将循环条件设置为while(a[i])空字符’\0’其值为0;
(注意string类对象不使用’\0’表示字符串末尾);
b. for循环等价于
init-expression;
while(test-expression)
{
statement(a);
update-expression;
}
使用上的区别是,for循环允许在初始化时创建一个for循环的局部变量;省略测试条件时,for默认为true,while不允许缺省测试条件;
计数循环一般使用for;
c. 设计循环的指导原则,指定循环终止的条件;在首次测试之前初始化条件;在条件被再次测试之前更新条件;
需要注意,在程序层次上,while循环和for循环被看成是一个复合代码,但仍然等价为一条语句;
d. 延时循环,定量延时基于clock()函数,clock()函数返回程序开始执行后所用的系统时间,结合头文件ctime中的CLOCKS_PER_SEC,每秒时钟数,可以得到程序运行时间(s);
ctime显式的定义了clock()返回值的别名clock_t,这允许该类型变量的存在
可以看出在循环体里面重复执行clock()/CLOCKS_PER_SEC会降低效率;
e. 类型别名
构造类型别名的方式一般包括#define 别名 原名和typedef 原名 别名;
例如#define a int*
a x,y==int* x,y,x是int*,y是int;
typedef int * pp;pp a,b;a是int*,b是int*;
别名只能由单个单词构成;
一般来说typedef是更标准的操作;
5.3 do while循环
a. 尽管表现上实际没什么差别,do while循环被称为出口循环,首先执行一次循环体,然后判断是否执行下一次循环(也就是可以将循环条件看成是在循环体末尾);
其格式为
do{}while();
通常更常用的循环是入口循环,但出口循环一般用于要求输入等条件,采用入口循环表示可能要重复一遍循环体;
b. 例如for(;;)类的表示方式在while和do while循环中有更清晰的结构;通常,编写清晰、容易理解的代码比使用语言的晦涩特性来显示自己的能力更为有用;
5.4 基于范围的for循环(C++11)
a. ranged-based for loop,
类似自动根据容器的参数构造for循环;其中x是指向a中元素的地址以char类型读取的变量,x=char(a[i]);
采用这种方式不能对数组内容进行修改;修改需要采用引用格式访问数组元素,即
5.5 循环和文本输入
a. cin输入,cin会忽略空白,不进行回显,也不包含到计数内;
发送给cin的输入被放入缓冲区,等待回车键后发送给程序,也就是不回车可能就不接收输入;
这个程序有一点就是#后边的1仍然是缓冲区的有效输入(回车发送),通过#跳出while循环后,#仍然赋值给tch,1赋值给第一个getchar(),所以第一个getchar()实际上不是有效的暂停函数(;
b. cin.get(tch)(istream成员函数)可以正确读取空白并进行赋值;cin.get实际上是一个通过引用修改变量的例子;
cin.get()重载,get(char* a,int num)/get(char)/get();
c. EOF文件尾条件
EOF文件尾条件是命令行环境的遗留功能,作用是检测文件是否结束;C++输入工具和操作系统协同工作,检测文件尾并反馈给程序;
EOF不表示输入中的字符,而是表示没有字符;
针对键盘输入,允许模拟文件尾条件,对Windows命令提示符模式,可以在任意位置通过CtrlZ+Enter仿真文件尾条件;
检测到EOF后,cin将eofbit和failbit都设置为1,可以通过成员函数cin.eof()查看eofbit是否为1,是则返回bool true;cin.fail()在EOF条件和Fail条件(只有failbit设置为1)时都返回true;
fail()比eof()更常用;
d. cin.get()读取字符时,实际上可以根据读取字符的情况抛出异常(例如文件尾、磁盘故障等),因此可以将循环条件改成
增加了cin.clear()清空缓冲区异常就可以正常输入了;
e. C语言中的getchar()和putchar()可以通过头文件stdio.h使用,在C++中其替代版本是iostream类成员函数cin.get()和cout.put();
ch=cin.get();类似getchar(),get返回一个int字符编码,(cin.get(ch)返回的是一个istream对象);
cout.put(ch);类似putchar();(参数类型要求为char)
cin,get()将EOF表示为一个不同于任何有效值的字符值,例如-1;
关于cin.get()的EOF有一个细节是,对于某些实现,可能char采用的是unsigned的编码形式,因此EOF表示为-1是无效的,需要将cin.get()赋值给int类型;
而采用cin.get(cha)时,EOF并不会被赋值给cha,cha不会被用来存储任何非char值;
5.6 嵌套循环和二维数组
a. 易见,二维数组是数组的数组;
int a[4][5];a可以看成由4个int[5]组成的数组,或者5个由4个int组成的数组的数组,但调用时需要注意参数次序一致性;
则a[i][j]表示第i-1个int[5]的第[j-1]个元素;
可以通过嵌套循环遍历二维数组;
b. 可以直接对二维数组进行初始化,
int a[2][4]={{a,b,c,d},{e,f,g,h}};
c. 强调一hia,使用字符串常量初始化char指针要求使用const修饰,因为字符串常量通常实现为const char*类型;
d. 放个错题,
int x={1,024};有效,x={},{1,024}=逗号运算符右值=024, 024是8进制,转换成10进制20;
int y=1,024;这里的优先级是(y=0),024;表达式整体值是20;