C++之循环、表达式和语句(五)
本文记录了C++中与循环、表达式和语句相关的容易遗忘的一些知识。
表达式和语句
定义
一个C++表达式是一个值,或者是值与运算符的组合,并且每个C++表达式都有一个值。从C++的构建方式来看,
对表达式进行求值是主要作用,改变变量是副作用。
语句没有值。不能将语句赋值给变量,比如for语句、声明语句等。
从表达式到语句只是一小步,只需加一个分号。
打破规则
C++ 为 C 语言的循环增加了一项特性,这需要对 for 循环的语法进行一些巧妙的调整。以下是原来的语法:
1 | for (expression; expression; expression) |
特别是,for结构的控制部分由三个表达式组成,这些表达式用分号分隔。不过,C++循环允许你做如下之类的事情:
1 | for (int i = 0; i < 5; i++) |
也就是说,你可以在for循环的初始化区域声明一个变量。这可能很方便,但它不符合原有的语法,因为声明不是表达式。
for语句的语法已修改为如下形式:
1 | for (for-init-statement condition; expression) |
乍一看,这似乎有些奇怪,因为这里只有一个分号,而不是两个。但这是没问题的,因为for初始化语句被认定为一个语句,而一个语句本身就带有分号。至于for-init-statement,它被识别为表达式语句或声明。此语法规则用一个自带分号的语句取代了后跟分号的表达式。
归根结底,这意味着C++程序员希望能够在for循环的初始化部分声明并初始化一个变量,而且他们会不惜对C++语法乃至英语语言做出任何必要的改动来实现这一点。
逗号表达式
- 逗号表达式本身是序列点
- 且有最低的运算符优先级
- 逗号表达式的值是该表达式第二部分的值
1 | i = 20, j = 2 * i // i set to 20, then j set to 40 |
空语句
- 使用空语句时应该加上注释,从而让读这段代码的人知道该语句是有意省略的。
1 | while(cin >>s && s != sought ) |
跳转语句
C++提供了4种跳转语句:break、continue、goto和return。
break语句负责终止离它最近的while、do while、for或switch语句,并从这些语句之后的第一条语句开始继续执行。
continue语句终止最近的循环中的当前迭代并立即开始下一次迭代。和break语句类似的是,出现在嵌套循环中的continue语句仅作用于离它最近的循环。和break语句不同的是,只有当switch语句嵌套在迭代语句内部时,才能在switch里使用continue。
goto语句无条件跳转到同一函数内的另一条语句。语法形式为:1
goto label;
label是作用于标识一条语句的标示符。带标签语句是一种特殊的语句,在它之前有一个标示符以及一个冒号:1
end: return; // 带标签语句,可以作为goto的目标
标签标示符独立于变量或其他标示符的名字,因此,标签标示符可以和程序中其他实体的标示符使用同一个名字而不会相互干扰。goto语句和控制权转向的那条带标签的语句必须位于同一个函数之内。
尽量不要在程序中使用goto语句,因为它使得程序既难理解又难修改。
循环
前缀++与后缀++的效率
如果将值用于某种用途(例如作为函数参数或赋值给变量),那么使用前缀形式还是后缀形式会有所不同。但是,如果递增或递减表达式的值未被使用,情况会怎样呢?例如,x++;和++x;有什么区别呢?
从逻辑上讲,在这两种情况下,使用前缀形式还是后缀形式并没有区别。表达式的值并未被使用,因此唯一的影响就是副作用。这里使用这些运算符的表达式都是完整表达式,所以到程序执行下一步时,对x进行递增的副作用肯定已经完成;前缀形式和后缀形式会产生相同的最终结果。
不过,尽管前缀形式和后缀形式的选择对程序行为没有影响,但这种选择可能会对执行速度产生微小影响。对于内置类型和现代编译器来说,这似乎不是什么问题。但C++允许为类定义这些运算符。在这种情况下,用户定义的前缀函数的工作方式是先递增一个值,然后返回该值。而后缀版本的工作方式是先保存该值的一个副本,对该值进行递增,然后返回保存的副本。因此,对于类来说,前缀版本比后缀版本效率要高一些。
1 | int i = 0, j; |
类型别名
C++有两种方法可以为一种类型建立新名称作为别名。一种是使用预处理器(在编译时进行宏替换):
1 |
第二种方法是使用C++(以及C语言)的关键字typedef来创建别名。例如,要让byte成为char的别名,可以这样写:
1 | typedef char byte; // 使byte成为char的别名 |
其一般形式如下:
1 | typedef typeName aliasName; |
换句话说,如果你想让别名成为某一特定类型的别名,你可以先像声明该类型的变量一样声明这个别名,然后在声明前面加上typedef关键字。例如,要让byte_pointer成为指向char的指针的别名,你可以先将byte_pointer声明为指向char的指针,然后在前面加上typedef:
1 | typedef char * byte_pointer; // 指向char类型的指针 |
基于范围的for循环
1 | double prices[5] = {4.99, 10.99, 6.87, 7.99, 8.49}; |
键盘模拟EOF
1 | Ctrl + Z // Windows |
请注意,在Unix及类Unix系统(包括Linux和Cygwin)中,Ctrl+Z会暂停程序的执行;fg命令可使执行恢复。
常用的字符输入习语
1 | while (cin.get(ch)) // while input is successful |
首先必须执行对cin.get(ch)的调用,若调用成功,就会将一个值存入ch中。然后程序会从该函数调用中获取返回值,这个返回值就是cin。接着,程序会对cin进行bool类型转换,如果输入成功,转换结果为true,否则为false。三条准则(确定终止条件、初始化条件以及更新条件)都被压缩到了一个循环测试条件中。