天天看点

【C++ Primer Plus】第五章:循环与关系表达式第五章 循环与关系表达式

文章目录

  • 第五章 循环与关系表达式
    • 5.1 循环
    • 5.2 关系表达式
      • 5.2.1 逗号运算符
      • 5.2.2 类型别名
    • 5.3 编程实例:循环和文本输入
      • 5.3.1 使用原始的cin进行输入
      • 5.3.2 使用cin.get(char)进行补救
      • 5.3.3 文件尾条件
      • 5.3.4 结合输入
      • 5.3.5 如何精简程序?
    • 5.4 二维数组
    • 5.5 总结

第五章 循环与关系表达式

5.1 循环

for(initialization; test-expression; update-expression){
body;
}
           

1.设置初始值。

2.执行测试,看看循环是否应当继续进行。

3.执行循环操作。

4.更新用于测试的值。

5.2 关系表达式

C++并没有将test-expression的值限制为只能为真或假。可以使用任意表达式,C++将把结果强制转换为bool类型。

C++将赋值表达式的值定义为左侧成员的值,所以:

x = 20;//的值为20
maids = (cooke = 4)+3;//的值为7
           

cout.setf(ios::boolalpha)函数调用设置了一个标记,该标记命令cout显示true和false,而不是1和0。

int x = 10;
	while (x++ < 12) {
		cout << x << endl;
	}
	cout << endl;
	cout << x << endl;

	11
    12
        
    13
           

用户这样定义前缀函数:将值加1,然后返回结果;但后缀版本首先复制一个副本,将其加1,然后将复制的副本返回。因此,对于类而言,前缀版本的效率比后缀版本高。

单说运算符的话,后缀运算符的优先级大于前缀运算符。

*++pt;//运算符++先作用于pt
++*pt;//*先作用于pt
*pt++;//++先作用于pt
           

5.2.1 逗号运算符

逗号运算符最常见的用途是将两个或更多的表达式放到一个for循环表达式中。不过C++还为这个运算符提供了另外两个特性。首先,它确保先计算第一个表达式,然后计算第二个表达式(换句话说,逗号运算符是一个顺序点)。

int x = 10;
cout << (x = 11, x = x * 20);//220

cats = (17,240);//240
           

关系运算符的优先级比算术运算符低。这意味着表达式:

x+3>y-2;
相当于:
(x+3)>(y-2);
而不是:
x+(3>y)-2;
           

5.2.2 类型别名

C++为类型建立别名的方式有两种。一种是使用预处理器:

#define BYTE char
           

这样,预处理器将在编译程序时用char替换所有的BYTE,从而使BYTE成为char的别名。

第二种方法是使用C++(和C)的关键字typedef来创建别名。例如,要将byte作为char的别名,可以这样做:

typedef char byte;
           

通用格式为:

typedef typeName aliasName;
           

换句话说,如果要将aliasName作为某种类型的别名,可以声明aliasName,如同将aliasName声明为这种类型的变量那样,然后在声明的前面加上关键字typedef。例如,要让byte_pointer成为char指针的别名,可将byte_pointer声明为char指针,然后在前面加上typedef:

typedef char * byte_pointer;
           

也可以使用#define,不过声明一系列变量时,这种方法不适用。例如,请看下面的代码:

#define FLOAT_POINTER float*
FLOAT_POINTER pa,pb;
float *pa,pb;//pa是个指针,pb只是个float变量;
           

typedef方法不会有这样的问题。它能够处理更复杂的类型别名,这使得与使用#define相比,使用typedef是一种更佳的选择—有时候,这也是唯一的选择。

**注意:**typedef不会创建新类型,而只是为已有的类型建立一个新名称。如果将word作为int的别名,则cout将把word类型的值视为int类型。

5.3 编程实例:循环和文本输入

**任务描述:**逐字符地读取来自文件或键盘的文本

5.3.1 使用原始的cin进行输入

**分析:**如果程序要使用循环来读取来自键盘的文本输入,则必须有办法知道何时停止读取。

如何知道这一点呢?

一种方法是选择某个特殊字符—有时被称为哨兵字符(sentinel character),将其作为停止标记。例如,

在遇到#字符时停止读取输入。该程序计算读取的字符数,并回显这些字符,即在屏幕上显示读取的字符。按下键盘上的键不能自动将字符显示到屏幕上,程序必须通过回显输入字符来完成这项工作。通常,这种任务由操作系统处理。运行完毕后,该程序将报告处理的总字符数。

#include <iostream>
#include <cstring>
using namespace std;

int main() {
	char ch;
	int count = 0;
	cout << "Enter characters;enter # to quit:\n";
	cin >> ch;
	while (ch != '#') {
		cout << ch;
		++count;
		cin >> ch;
	}
	cout << endl << count << "characters read\n";
	return 0;
}
           

运行结果:

Enter characters;enter # to quit:
May your pain clean your eyes # this character
Mayyourpaincleanyoureyes
24characters read

--------------------------------
Process exited after 122.7 seconds with return value 0
请按任意键继续. . .
           

请仔细观察运行结果:

上面的做法合情合理。但为什么程序在输出时省略了空格呢?原因出在cin。读取char值时,与读取其他基本类型一样,cin将忽略空格和换行符。因此输入中的空格没有被回显,也没有被包括在计数内。

更为复杂的是,发送给cin的输入被缓冲。这意味着只有在用户按下回车键后,他输入的内容才会被发送给程序。这就是在运行该程序时,可以在#后面输入字符的原因。按下回车键后,整个字符序列将被发送给程序,但程序在遇到#字符后将结束对输入的处理。

5.3.2 使用cin.get(char)进行补救

通常,逐个字符读取输入的程序应该需要检查每个字符,包括空格、制表符和换行符

cin所属的istream类(在iostream中定义)中包含一个能够满足这种要求的成员函数。成员函数cin.get(ch)读取输入中的下一个字符(即使它是空格),并将其赋给变量ch。使用这个函数调用替换cin>>ch,可以修补这个漏洞。

#include <iostream>
#include <cstring>
using namespace std;

int main() {
	char ch;
	int count = 0;
	cout << "Enter characters;enter # to quit:\n";
	cin.get(ch);
	while (ch != '#') {
		cout << ch;
		++count;
		cin.get(ch);
	}
	cout << endl << count << "characters read\n";
	return 0;
}
           

运行结果:

Enter characters;enter # to quit:
May your pain clean your eyes # this character
May your pain clean your eyes
30characters read

--------------------------------
Process exited after 331.4 seconds with return value 0
请按任意键继续. . .
           

5.3.3 文件尾条件

使用诸如#等符号来表示输入结束很难令人满意,因为这样的符号可能就是合法输入的组成部分,其他符号(如@

和%)也如此。如果输入来自于文件,则可以使用一种功能更强大的技术—检测文件尾(EOF)。C++输入工具和操作系统协同工作,来检测文件尾并将这种信息告知程序。

乍一看,读取文件中的信息似乎同cin和键盘输入没什么关系,但其实存在两个相关的地方。首先,很多操作系统都支持重定向,允许用文件替换键盘输入。例如,假设在Windows中有一个名为gofish.exe的可执行程序和一个名为fishtale的文本文件,则可以在命令提示符模式下输入下面的命令:

gofish <fishtable
           

程序将从fishtale文件(而不是键盘)获取输入。< 符号是Unix和Windows命令提示符模式的重定向运算符。

其次,很多操作系统都允许通过键盘来模拟文件尾条件。在Unix中,可以在行首按下Ctrl+D来实现;在Windows命令提示符模式下,可以在任意位置按Ctrl+Z和Enter。

键盘输入的EOF概念实际上是命令行环境遗留下来的。然而,用于Mac的Symantec C++模拟了UNIX,将Ctrl+D视为仿真的EOF。Metrowerks Codewarrior能够在Macintosh和Windows环境下识别Ctrl+Z。用于PC的Microsoft Visual C++、Borland C++ 5.5和GNU C++ 都能够识别行首的Ctrl + Z,但用户必须随后按下回车键。总之很多PC编程环境都将Ctrl+Z视为模拟的EOF。

综上:如果编程环境能够检测EOF,可以在类似于程序清单5.17的程序中使用重定向的文件,也可以使用键盘输入,并在键盘输入中模拟EOF。这一点似乎很有用,因此我们来看看究竟如何做。

  1. 检测到EOF后,cin将两位(eofbit和failbit)都设置为1。
  2. 通过成员函数eof( )来查看eofbit是否被设置:
    • 如果检测到EOF,则cin.eof( )将返回bool值true,否则返回false。
    • 同样,如果eofbit或failbit被设置为1,则fail( )成员函数返回true,否则返回false。

什么是重定向?

修改标准输入和标准输出关联的工具。

  • 输出到文件,而不是显示器
  • 允许用文件替换键盘输入
  • Windows允许通过键盘模拟文件尾:Ctrl+Z

注意,eof( )和fail( )方法报告最近读取的结果;也就是说,它们在事后报告,而不是预先报告。因此应将cin.eof( )或cin.fail( )测试放在读取后。下面的程序体现了这一点。它使用的是fail( ),而不是eof( ),因为前者可用于更多的实现中。

#include <iostream>
#include <cstring>
using namespace std;

int main() {
	char ch;
	int count = 0;
	cout << "Enter characters;enter # to quit:\n";
	cin.get(ch);
	while (cin.fail() == false) {
		cout << ch;
		++count;
		cin.get(ch);
	}
	cout << endl << count << "characters read\n";
	return 0;
}
           

运行结果如下:

Enter characters;enter # to quit:
May your pain clean your eyes.<ENTER>
May your pain clean your eyes.
tes<ENTER>
tes
^Z<ctrl+Z>
<ENTER>

35characters read

--------------------------------
Process exited after 21.74 seconds with return value 0
请按任意键继续. . .
           

通过使用重定向,可以用该程序来显示文本文件,并报告它包含的字符数。下面,我们在Windows系统运行该程序,并对一个四行的文件进行读取、回显和计算字数:

D:\Program Files (x86)\Dev-Cpp\code>test2.exe < stuff.txt
Enter characters;enter # to quit:
我是一个
txt文档。
我有
四行。
30characters read
           

5.3.4 结合输入

前面指出过,cin方法检测到EOF时,将设置cin对象中一个指示EOF条件的标记。设置这个标记后,cin将不读取输入,再次调用cin也不管用。对于文件输入,这是有道理的,因为程序不应读取超出文件尾的内容。然而,对于键盘输入,有可能使用模拟EOF来结束循环,但稍后要读取其他输入。cin.clear( )方法可能清除EOF标记,使输入继续进行。这将在第17章详细介绍。不过要记住的是,在有些系统中,按Ctrl+Z实际上将结束输入和输出,而cin.clear( )将无法恢复输入和输出。

5.3.5 如何精简程序?

方法cin.get(char)的返回值是一个cin对象。然而,istream类提供了一个可以将istream对象(如cin)转换为bool值的函数;当cin出现在需要bool值的地方(如在while循环的测试条件中)时,该转换函数将被调用。另外,如果最后一次读取成功了,则转换得到的bool值为true;否则为false。

这意味着可以将上述while测试改写为这样:

#include <iostream>
#include <cstring>
using namespace std;

int main() {
	char ch;
	int count = 0;
	cout << "Enter characters;enter # to quit:\n";
	cin.get(ch);
	while (cin) {//注意到了吗?cin可以被转换成一个布尔值
		cout << ch;
		++count;
		cin.get(ch);
	}
	cout << endl << count << "characters read\n";
	return 0;
}
           

这比! cin.fail( )或!cin.eof( )更通用,因为它可以检测到其他失败原因,如磁盘故障。最后,由于cin.get(char)的返回值为cin,因此可以将循环精简成这种格式:

while (cin.get(ch)) {//注意到了吗?cin.get()返回的就是一个cin对象
		cout << ch;
		++count;
	}
           

cin.put()

cin.put(ch);
           
最初,put( )成员只有一个原型—put(char)。可以传递一个int参数给它,该参数将被强制转换为char。C++标准还要求只有一个原型。然而,有些C++实现都提供了3个原型:put(char)、put(signed char)和put(unsigned char)。在这些实现中,给put( )传递一个int参数将导致错误消息,因为转换int的方式不止一种。使用显式强制类型转换的原型(如cin.put(char(ch)))可使用int参数。

cin.get( )将返回一个用符号常量EOF表示的特殊值。该常量是在头文件iostream中定义的。EOF值必须不同于任何有效的字符值,以便程序不会将EOF与常规字符混淆。通常,EOF被定义为值−1,因为没有ASCII码为−1的字符,但并不需要知道实际的值,而只需在程序中使用EOF即可。

可以使用int ch,并用cin.get( )代替cin.get(char),用cout.put( )代替cout,用EOF测试代替cin.fail( )测试:

int ch;
ch = cin.get();
while(ch != EOF){
    cout.put(ch);
    ++count;
    ch = cin.get();
}
           

如果ch是一个字符,则循环将显示它。如果ch为EOF,则循环将结束。

需要知道的是,EOF不表示输入中的字符,而是指出没有字符。

除了当前所做的修改外,关于使用cin.get( )还有一个微妙而重要的问题。由于EOF表示的不是有效字符编码,因此可能不与char类型兼容。例如,在有些系统中,char类型是没有符号的,因此char变量不可能为EOF值(−1)。由于这种原因,如果使用cin.get( )(没有参数)并测试EOF,则必须将返回值赋给int变量,而不是char变量。另外,如果将ch的类型声明为int,而不是char,则必须在显示ch时将其强制转换为char类型。

那么如何针对以上问题修改代码呢?

int ch;
int count = 0;
while((ch = cin.get()) != EOF){
    cout.put(char(ch));
    ++count;
}
           

分析一下循环条件:

while((ch = cin.get()) != EOF)
           

子表达式ch=cin.get( )两端的括号导致程序首先计算该表达式。为此,程序必须首先调用cin.get( )函数,然后将该函数的返回值赋给ch。由于赋值语句的值为左操作数的值,因此整个子表达式变为ch的值。如果这个值是EOF,则循环将结束,否则继续。该测试条件中所有的括号都是必不可少的。如果省略其中的一些括号:

while(ch = cin.get() != EOF)
           

由于!=运算符的优先级高于=,因此程序将首先对cin.get( )的返回值和EOF进行比较。比较的结果为false或true,而这些bool值将被转换为0或1,并直接赋给ch。

另一方面,使用cin.get(ch)(有一个参数)进行输入时,将不会导致任何类型方面的问题。前面讲过cin.get(char)函数在到达EOF时,不会将一个特殊值赋给ch。事实上,在这种情况下,它不会将任何值赋给ch。ch不会被用来存储非char值。

cin.get(ch)与cin.get()的差异:

属性 cin.get(ch) cin = cin.get()
传递输入字符的方式 赋给参数ch 将函数返回值赋给ch
用于字符输入时函数的返回值 istream对象(执行bool转换后为true) int类型的字符编码
到达EOF时函数的返回值 istream对象(执行bool转换后为false) EOF

那么应使用cin.get( )还是cin.get(char)呢?使用字符参数的版本更符合对象方式,因为其返回值是istream对象。这意味着可以将它们拼接起来。例如,下面的代码将输入中的下一个字符读入到ch1中,并将接下来的一个字符读入到ch2中:

cin.get(ch1).get(ch2);
           

这是可行的,因为函数调用cin.get(ch1)返回一个cin对象,然后便可以通过该对象调用get(ch2)。

5.4 二维数组

如何声明字符串数组?
方案一:将一个指针数组初始化为一组字符串常量。也就是说,将cities声明为一个char指针数组。这使得每个元素(如cities [0])都是一个char指针,可被初始化为一个字符串的地址。

const int Cityes = 5;
const char* cityes[Cityes] = {
"Gribble City",
"Gribbletown",
"San Gribble",
"New Gribble",
"Gribble Vista"
};

方案二:将全部5个字符串的最大长度限制为24个字符。指针数组存储5个字符串的地址,而使用char数组的数组时,将5个字符串分别复制到5个包含25个元素的char数组中。

char cityes[Cityes][25] = {
"Gribble City",
"Gribbletown",
"San Gribble",
"New Gribble",
"Gribble Vista"
};
           

从存储空间的角度说,使用指针数组更为经济;然而,如果要修改其中的任何一个字符串,则二维数组是更好的选择。令人惊讶的是,这两种方法使用相同的初始化列表,显示字符串的for循环代码页相同。

另外,还可以使用string对象数组,而不是字符串指针数组。在这种情况下,声明如下:

const string cityes[Cityes] = {
"Gribble City",
"Gribbletown",
"San Gribble",
"New Gribble",
"Gribble Vista"
};
           

如果希望字符串是可修改的,则应省略限定符const。使用string对象数组时,初始化列表和用于显示字符串的for循环代码与前两种方法中相同。在希望字符串是可修改的情况下,string类自动调整大小的特性将使这种方法比使用二维数组更为方便。

5.5 总结

  1. C++提供了3种循环:for循环、while循环和do while循环。如果循环测试条件为true或非零,则循环将重复执行一组指令;如果测试条件为false或0,则结束循环。for循环和while循环都是入口条件循环,这意味着程序将在执行循环体中的语句之前检查测试条件。do while循环是出口条件循环,这意味着其将在执行循环体中的语句之后检查条件。
  2. 每种循环的句法都要求循环体由一条语句组成。然而,这条语句可以是复合语句,也可以是语句块(由花括号括起的多条语句)。
  3. 关系表达式对两个值进行比较,常被用作循环测试条件。关系表达式是通过使用6种关系运算符之一构成的:<、<=、= =、>=、>或! =。关系表达式的结果为bool类型,值为true或false。
  4. 许多程序都逐字节地读取文本输入或文本文件,istream类提供了多种可完成这种工作的方法。如果ch是一个char变量,则下面的语句将输入中的下一个字符读入到ch中:
cin>>ch;
           

然而,它将忽略空格、换行符和制表符。下面的成员函数调用读取输入中的下一个字符(而不管该字符是什么)并将其存储到ch中:

cin.get(ch);
           

成员函数调用cin.get( )返回下一个输入字符—包括空格、换行符和制表符,因此,可以这样使用它:

ch = cin.get()
           
  1. cin.get(char)成员函数调用通过返回转换为false的bool值来指出已到达EOF,而cin.get( )成员函数调用则通过返回EOF值来指出已到达EOF,EOF是在文件iostream中定义的。
  2. 嵌套循环是循环中的循环,适合用于处理二维数组。