C++学习笔记之九 循环和关系表达式

关于各种循环语句,大家应该很熟悉了。凡是学习过C语言的或者有过编程经验的朋友都能熟练的应用循环语句了,所以,本章中我们就不仔细的介绍各种语法。快速浏览过去,就其中一些不常见的或者以前疏漏的知识点进行整理总结。

1、基于范围的for循环(C++11)

在C++11中,新增加了一种循环:基于范围的for循环。这简化了一种常见的循环任务:对数组(或容器类,如vector和array)的每个元素执行相同的操作,如下例所示:

double  prices[5]  =  {4.99,  10.99,  6.87,  7.99,  8.49};

for (double x : prices)

cout << x  << std::endl;

其中,x最初表示数组prices的第一个元素。显示第一个元素后,不断执行循环,而x依次表示数组的其他元素。因此,上述代码显示全部5个元素,每个元素占据一行。总之,该循环显示数组中每个值。

要修改数组的元素,需要使用不同的循环变量语法:

for (double &x : prices)

x = x * 0.80;

符号&表明x是一个引用变量。就这里而言,这种声明让接下来的代码能够修改数组的内容,而第一种语法不能。

还可结合使用基于范围的for循环和初始化列表:

for (int x :  {3, 5, 2, 8, 6})

cout << x << " ";

cout << '\n';


2、循环和文本输入

了解了循环的工作原理之后,我们看一个循环完成的一项最常见、最重要的任务:逐字符地读取来自文件或键盘的文本。例如,读者可能想编写一个能够计算输入中的字符数、行数和字数的程序。尽管C++中的while循环与C语言中的while循环一样,但是由于C++的I/O工具不同,所以我们需要了解三种不同的while循环中的输入模式。

事实上,cin对象支持3种不同模式的单字符输入,其用户接口各不相同。

(1)使用原始的cin进行输入

如果程序要使用循环来读取来自键盘的文本输入,则必须有办法知道何时停止读取。如何知道这一点呢?一种方法是选择某个特殊字符---有时被称为哨兵字符,将其作为停止标记。例如,下面程序中遇到#字符时停止读取输入。

#include <iostream>

int main()

{

using namespace std;

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;

}

提醒:由于ch是char类型,所以只能存储一个字符。我们在键盘通过cin输入一串字符串时,实际上字符串存储在输入流缓冲文件中,只有一个字符读入ch变量。所以才会有接下来的连续循环读入。

该程序计算读取的字符数,并回显这些字符,即在屏幕上显示读取的字符。按下键盘上的键不能自动将字符显示在屏幕上,程序必须通过回显输入字符来完成这项工作。

下面是该程序的运行情况:

Enter  characters ; enter # to quit:

see ken run#really  fast

seekenrun

9 characters read

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

*(2)使用cin.get(char)进行补救(*******最常用的方式,推荐使用)

通常,逐个字符读取输入的程序需要检查每个字符,包括空格、制表符和换行符。cin所属的istream类(在iostream中定义)中包含一个能够满足这种要求的成员函数。具体地说,成员函数cin.get(ch)读取输入中的下一个字符(即使它是空格),并将其赋值给变量ch。使用这个函数替代cin>>ch,可以修补上面程序丢失空格的问题。

#include <iostream>

int main()

{

using namespace std;

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:

Did you use a #2 pencil?

Did you use a

14 characters read

现在,该程序回显了每个字符,并将全部字符计算在内,其中包括空格。输入仍被缓冲,因此输入的字符个数仍可能比最终到达程序的要多。

(3)文件尾条件

其实使用诸如#等符号来表示输入结果很难令人满意,因为这样的符号可能就是合法输入的组成部分,其他符号(如@和%)也如此。如果输入来自于文件,则可以使用一种功能更强大的技术---检测文件尾(EOF)。C++输入工具和操作系统协同工作,来检测文件尾并将这种信息告知程序。

咋一看,读取文件中的信息似乎同cin和键盘输入没什么关系,但其实存在两个相关的地方。

首先,很多操作系统都支持重定向,允许用文件替换键盘输入。例如,假设在Windows中有一个名为gofish.exe的可执行程序和一个名为fishtale的文本文件,则可以在命令提示符模式下输入命令:gofish < fishtale 这样,程序将从fishtale文件而不是键盘获取输入。<符号是UNIX和Windows命令提示符模式的重定向运算符。

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

如果编程环境能够检测EOF,那么就可以在键盘输入中模拟EOF代替我们使用的#等字符。这一点很有用。

我们看一下具体是如何实现的:检测到EOF后,cin将两位(eofbit和failbit)都设置为1.可以通过成员函数eof()来查看eofbit是否被设置;如果检测到EOF,则cin.eof()将返回bool值true,否则返回false。同样,如果eofbit或failbit被设置为1,则fail()成员函数返回true,否则返回false。注意,eof()和fail()方法报告最近读取的结果:也就是说,它们在事后报告,而不是预先报告。因此应将cin.eof()或cin.fail()测试放在读取后。

还是前面的例子,这里我们使用EOF结尾符代替#符号:

#include <iostream>

int main()

{

using namespace std;

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;

}

下面是该程序的运行情况:

The green bird sings in the winter.<Enter>

The green bird sings in the winter.

Yes, but the crow files in the dawn.<Enter>

Yes, but the crow files in the dawn.

<CTRL>+<Z><ENTER>

73 characters read

需要注意的几点:
第一,EOF结束输入。cin方法检测到EOF时,将设置cin对象中一个指示EOF的标记。设置这个标记后,cin将不读取输入,再次调用cin也不管用。对于文件输入,这是有道理的,因为程序不应该读取超出文件尾的内容。然而,对于键盘输入,有可能使用模拟EOF来结束循环,但稍后要读取其他输入。cin.clear()方法可能清除EOF标记,使输入继续进行。

第二,常见的字符输入做法

每次读取一个字符,直到遇到EOF的输入循环的基本设计如下:

cin.get(ch);

while(cin.fail() == false)

{

. . .

cin.get(ch);

}

可以在上述代码中使用一些简洁方法。

比如将上述while测试改写成这样: while (!cin.fail() )

另外,cin.get(char)的返回值是一个cin对象。然而,istream类提供了一个可以将istream对象(如cin)转换为bool值的函数;当cin出现在需要bool值的地方时,该转换函数将被调用。另外,如果最后一次读取成功了,则转换得到的bool值为true;否则为false。这意味着可以将上述while测试改写成这样:while(cin).这比!cin.fail()或!cin.eof()更通用,因为它可以检测到其他失败原因,如磁盘故障。

最后,由于cin.get(char)的返回值为cin,因此可以将循环精简成这种格式:

while(cin.get(ch))

{

. . .

}

这样,cin.get(char)只被调用一次,而不是两次:循环前一次,循环后一次。

(4)另一个cin.get()版本

不接受任何参数的cin.get()成员函数返回输入中的下一个字符。也就是说,可以这样使用它:
ch = cin.get();
该函数的工作方式与C语言中的getchar()相似,将字符编码作为int值返回;而cin.get(ch)返回一个对象,而不是读取的字符。同样,可以使用cout.put()函数来显示字符:
cout.put(ch);
该函数的工作方式类似C语言中的putchar(),只不过其参数类型为char,而不是int。
为成功地使用cin.get(),需要知道其如何处理EOF条件。当该函数到达EOF时,将没有可返回的字符。相反,cin.get()将返回一个用符号常量EOF表示的特殊值。该常量是在文件iostream中定义的。EOF值必须不同于任何有效的字符值,以便程序不会将EOF与常规字符混淆。通常,EOF被定义为值-1,因为没有ASCII码为-1的字符,但并不需要知道实际的值,而只需在过程中使用EOF即可。
例如还是上面的程序,可以使用int ch,并用cin.get()代替cin.get(char),用cout.put()代替cout,用EOF测试代替cin测试:
int  ch;
ch = cin.get();
while (ch != EOF)
{
cout.put(ch);
++count;
ch = cin.get();
}

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

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

如下程序所示:

#include <iostream>

int main()

{

using namespace std;

int ch;

int count = 0;

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

{

cout.put(char(ch));

++count;

}

cout << endl << count <<" characters read\n";

return 0;

}

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

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

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

get()的主要用途是能够将stdio.h的getchar()和putchar()函数转换为iostream的cin.get()和cout.put()方法。只要用头文件iostream替换stdio.h,并使用相似的方法替换所有的getchar()和putchar()即可。

3、嵌套循环和二维数组

下面来讨论一下如何使用嵌套for循环处理二维数组。

其实,二维数组就是每个元素本身都是数组的数组。例如:假设要存储5个城市在4年间的最高温度。可以声明:int  maxtemps[4][5];

该声明意味着maxtemps是一个包含4个元素的数组,其中每个元素都是一个由5个整数组成的数组。可以将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;

}

(1) 初始化二维数组

格式这样:

int  maxtemps[4][5]  =

{

{96, 100, 87, 101, 105},

{96, 98, 91, 107, 104},

{97, 101, 93, 108, 107},

{98, 103, 95, 109, 108}

};

(2) 使用二维数组

还是通过例子说明:

#include  <iostream>

const  int  Cities  = 5;

const  int  Years  = 4;

int  main()

{

using namespace  std;

const  char*  cities[Cities] =

{

"Gribble  City",

"Gribbletown",

"New  Gribble",

"San  Gribble",

"Gribble  Vista"

};

int maxtemps[Years][Cities] = 

{

{96, 100, 87, 101, 105},

{96, 98, 91, 107, 104},

{97, 101, 93, 108, 107},

{98, 103, 95, 109, 108}

};

cout << "Maxinum  temperatures for 2008 - 2011\n\n";

for (int city = 0; city < Cities;  ++ city)

{

cout << cities[city] << " :\t";

for (int year = 0; year < Years; ++year)

cout << maxtemps[year][city]  << "\t";

cout << endl;

}

return  0;

}

下面是该程序的输出:

Maxinum  temperatures for 2008 - 2011

Gribble  City:  96 96 97 98

Gribbletown:   10098101 103

New  Gribble:  87 91 93 95

San  Gribble:   101 107 108 109

Gribble  Vista:  105 104 107 108


在输出中使用制表符比使用空格可使数据排列更有规则。然而,制表符设置不相同,因此输出的外观将随系统而异。

另外,在这个例子中,可以使用char数组的数组,而不是字符串指针数组。在这种情况下,声明如下:

char cities[Cities][25]  = 

{

"Gribble City",

"Gribbletown",

"New Gribble",

"San  Gribble",

"Gribble  Vista"

};

上述方法将全部5个字符串的最大长度限制为24个字符。指针数组存储5个字符串的地址,而使用char数组的数组时,将5个字符串分别复制到5个包含25个元素的char数组中。因此,从存储空间的角度说,使用指针数组更为经济;然而,如果要更改其中的任何一个字符串,则二维数组是更好的选择。令人惊讶的是,这两种方法使用相同的初始化列表,显示字符串的for循环代码页相同。

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

const  string cities[Cities] =

{

"Gribble City",

"Gribbletown",

"New Gribble",

"San  Gribble",

"Gribble  Vista"

};

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




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值