C++ Primer Plus第五章笔记《循环和关系表达式》

news/2025/1/14 15:33:59 标签: c++, 笔记, 算法

这里的笔记区别于精简基础,会记录较多C++的细节

文章目录

  • 前言
  • 一、for循环
    • 1.1 for循环的组成部分
        • 关系表达式判断情况
    • 1.2 回到for循环
    • 1.3 修改步长
    • 1.4 使用for循环访问字符串
    • 1.5 递增运算符(++)和递减运算符(- -)
    • 1.6 副作用和顺序点
    • 1.7 前缀格式和后缀格式
    • 1.8 递增/递减运算符和指针
    • 1.9 组合赋值运算符
    • 1.10 复合语句(语句块)
        • 复合语句
    • 1.11 其他语法技巧——逗号运算符
    • 1.12 关系表达式
    • 1.13 赋值、比较和可能犯的错误
    • 1.14 C风格字符串的比较
    • 1.15 比较string类字符串
  • 二、while循环
    • 2.1 while循环
    • 2.2 for与while
    • 2.3 等待一段时间:编写延时循环
  • 三、do while循环
  • 四、基于范围的for循环
  • 五、循环和文本输入
    • 5.1 使用原始的cin进行输入
    • 5.2 文件尾条件
  • 六、嵌套循环和二维数组
    • 6.1 初始化二维数组
    • 6.2 使用二维数组
  • 总结


前言

程序需要有执行重复的操作和进行决策的工具。C++就提供了这样的工具,它使用与常规C语言相同的for循环、while循环、do while循环、if语句和switch语句。


一、for循环

1.1 for循环的组成部分

for循环为执行重复的操作提供了循环渐进的步骤。我们来具体看一下它是如何工作的。for循环的组成部分有以下这些步骤。

  1. 设置初始值。
  2. 执行测试,判断循环是否应当继续进行。
  3. 执行循环操作。
  4. 更新用于测试的值。

C++语法又将for看作一条语句——虽然循环体可以包含一条或多条语句。
如下例:

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

通常来说,循环只执行一次初始化。程序使用该表达式将变量设置成起始值,然后使用该变量计算循环周期。
下面是代码演示:

#include<iostream>
int main() {
	using namespace std;
	cout << "Enter the starting countdown value: ";
	int limit;
	cin >> limit;
	int i;
	for (i = limit; i; i--) //非0为真,0为假,所以写个i在条件判断时i减到0就会自己跳出循环
		cout << "i = " << i << "\n";
	cout << "Done now that i = " << i << "\n";

	return 0;
}

注意!循环在i变为0后结束
关系表达式(如i<5)是如何得到循环终止值0的呢?在引入bool类型之前,如果关系表达式为true,则被判定为1;如果false,则被判定为0。因此,表达式3<5的值为1,而5<5的值为0。
但需要注意到的是C++添加了bool类型后,关系表达式就判定为bool字面值true和false而不是1和0了。这种变化不会导致不兼容的问题,因为
C++程序在需要整数值的地方将把true和false分别转换为1和0,则在需要bool值的地方把0转换为false,非0转换成true。

下面程序将演示这种说法:

关系表达式判断情况
#include<iostream>
int main() {
	using namespace std;
	int x;
	cout << "The expression x = 100 has the value ";
	cout << (x = 100) << endl;
	cout << "Now x = " << x << endl;
	cout << "The expression x < 3 has the value ";
	cout << (x < 3) << endl;
	cout << "The expression x > 3 has the value ";
	cout << (x > 3) << endl;
	cout.setf(ios_base::boolalpha);//强制显示true和false
	cout << "The expression x < 3 has the value ";
	cout << (x < 3) << endl;
	cout << "The expression x > 3 has the value ";
	cout << (x > 3) << endl;

	return 0;
}

通常情况下,cout在显示bool值之前将它们转换为int,但cout.setf(ios::boolalpha)函数调用设置了一个标记,该标记命令cout显示true和false,而不是1和0。

表达式到语句的转变很容易,只要加分号即可。因此下面是一个表达式:

age = 100

而下面是一条语句:

age = 100;

更准确的说,这是一条表达式语句。只要加上分号,所有的表达式都可以成为语句,但不一定有编程意义。

1.2 回到for循环

下面将使用for循环完成更多的工作。下面的程序使用循环来计算并存储前16个阶乘。阶乘的计算方式如下:零的阶乘写作0!,被定义为1。1!是10!,即1。2!为21,即2。以此类推。每个乘数的阶乘都是该整数与前一个阶乘的乘积。
这里的程序用一个循环来计算连续阶乘的值,并将这些值存储在数组中。然后,用另一个循环来显示结果。

#include<iostream>
const int ArSize = 16;//设定一个常量
int main() {
	using namespace std;
	long long a[ArSize];
	a[1] = a[0] = 1LL;//1longlong类型
	for (int i = 2; i < ArSize; i++)
		a[i] = i * a[i - 1];
	for (int i = 0; i < ArSize; i++)
		std::cout << i << "! = " << a[i] << std::endl;

	return 0;
}

这个程序中我们使用了long long类型,为的是让阶乘的增大方式看起来明显。

提示:通常,定义一个const值来表示数组中的元素个数是个好办法。在声明数组和引用数组长度时(如在for循环中),可以使用const值。

另外这个示例还提醒了我们,可使用std::而不是编译指令using来让选定的标准名称可用。

1.3 修改步长

到现在为止,循环示例每一轮循环都将循环计数加1或减1。可以通过修改更新表达式来修改步长。
比如以下代码中,我们让程序按照用户选择的步长值将循环计数递增。它没有使用i++用作更新表达式,而是使用表达式i=i+by,其中by是用户选择的步长值。

#include<iostream>
int main() {
	using std::cout;
	using std::cin;
	using std::endl;
	cout << "Enter an integer: ";
	int by;
	cin >> by;
	cout << "Counting by " << by << "s:\n";
	for (int i = 0; i < 100; i = i + by)
		cout << i << endl;

	return 0;
}

==这里的重点是,更新表达式可以是任何有效的表达式。==例如,如果要求每轮递增以i的平方加10,则可以使用表达式i = i * i + 10。

1.4 使用for循环访问字符串

for循环提供了一种依次访问字符串中每个字符的方式。例如,下面程序让用户能够输入一个字符串,然后按相反的方向逐个字符地显示该字符串。为了反向计数,程序使用递减运算符(- -),在每轮循环后将数组下标减1。另外,使用关系运算符大于或等于(>=)来测试循环是否到达第一个元素。

#include <iostream>
#include <string>
int main() {
	using namespace std;
	cout << "Enter a word: ";
	string word;
	cin >> word;
	for (int i = word.size() - 1; i >= 0; i--)
		cout << word[i];
	cout << "\nBye.\n";

	return 0;
}

在上面代码中,word.size()返回word字符串长度,在后面减1是用户输入的字符串最后一个元素的下标位置。

1.5 递增运算符(++)和递减运算符(- -)

递增运算符,递减运算符的操作是:将循环计数加1或减1。然而,它们还有两种变体。前缀版本位于操作数前面,如++x;后缀版本位于操作数后面,如x++。两个版本对操作数的影响是一样的,但影响的时间不同。这就像吃饭的时候,先给钱后吃饭和先吃饭后给钱是一个道理。最终结果是一样的,但支付钱的时间点不同。

#include<iostream>
int main() {
	using namespace std;
	int a = 20;
	int b = 20;
	cout << "a = " << a << "; b = " << b << endl;
	cout << "a++ = " << a++ << "; ++b = " << ++b << endl;
	cout << "a = " << a << "; b = " << b << endl;
	return 0;
}

粗略来说,a++意味着使用a的当前值计算表达式,然后将a的值加1;而++b的意思是先将b的值加1,然后使用新的值来计算表达式。

1.6 副作用和顺序点

在C++中,语句中的分号就是一个顺序点,这意味着程序处理下一条语句之前,赋值运算符、递增运算符和递减运算符执行的所有修改都必须完成。

1.7 前缀格式和后缀格式

C++允许您针对类定义这些运算符,在这种情况下,用户这样定义前缀函数:将值返回1,然后返回结果;但后缀版本首先复制一个副本,将其加1,然后将复制的副本返回。因此,对于类而言,前缀版本的效率比后缀版本高。

1.8 递增/递减运算符和指针

可以将递增运算符用于指针和基本变量。本书前面介绍过,将递增运算符用于指针时,将把指针的值增加其指向的数据类型占用的字节数,这种规则适用于对指针递增和递减:

double arr[5] = {21.2, 32.8, 23.4, 45.2, 37.4};
double *pt = arr; //把arr数组首元素地址赋值给指针pt,此时pt指向arr[0]
++pt; //自增1后指向了32.8,此时pt指向arr[1]

我们也可以结合使用这些运算符和*运算符来修改指针指向的值。将*和++同时用于指针时,将什么解除引用,将什么递增。这取决于运算符的位置和优先级。前缀递增、递减和解引用运算的优先级相同,以从右到左的方式进行结合。后缀递增和后缀递减的优先级相同,但比前缀运算符的优先级高,所以也比解引用运算符优先级高,这两个运算符从左到右的方式结合。
前缀运算符的从右到左结合规则意味着*++pt的含义如下:先将++应用于pt(因为++位于*的右边)然后将*应用于被递增后的pt:

double x = *++pt; //书接上回,接上上面的代码后就是再把下标+1,此时pt指向arr[2],或者23.4

另一方面,++*pt意味着先取得pt指向的值,然后再把这个值+1:

++*pt; //23.4 + 1 = 24.4

这种情况下,pt依旧指向arr[2]。
接下来,请看下面的组合:

(*pt)++; //圆括号表示最优先

圆括号指出,首先对指针解引用,得到24.4,然后运算符++将这个值加到25.4,pt依然指向arr[2]。
最后,来看看下面的组合:

x = *pt++;

后缀运算符++的优先级更高,这意味着将运算符运用到pt,而不是*。因此这里表示的是下标+1后再进行解引用。此时指针指向的是arr[3]。

1.9 组合赋值运算符

懒狗,不想写了,直接上图看:
在这里插入图片描述

1.10 复合语句(语句块)

在for循环中使用两个花括号来构造一条复合语句(代码块)。代码块由一对花括号和它们包含的语句组成,被视为一条语句,从而满足句法的要求。下面程序就是一个例子:

复合语句
#include<iostream>

int main() {
	using namespace std;
	cout << "Pls enter five values:\n";
	double number;
	double sum = 0.0;
	for (int i = 1; i <= 5; i++) {
		cout << "Value " << i << ": ";
		cin >> number;
		sum += number;
	}
	cout << "They sum to " << sum << endl;
	cout << "and average to " << sum / 5 << ".\n";

	return 0;
}

如果省略掉花括号的话,只有第一条语句位于循环中。因此循环只会打印5条提示。
复合语句还有一种有趣的特性。如果在语句块中定义一个新的变量,则仅当程序执行该语句中的语句时,该变量才存在。执行完该语句后,变量将被释放。这表明此变量仅在该语句块才是可用的:

#include <iostream>
int main(){
	using namespace std;
	int x = 20;
	{
		int y = 100;
		cout << x << endl;
		cout << y << endl;
	}
	cout << x << endl;
	cout << y << endl;
	return 0;
}

注意!!在外部语句中定义的变量在内部语句块中也是被定义了的。
如果在语句块中声明一个变量,而外部语句块也由一个这种名称的变量,就会导致在声明变量位置到内部语句块结束的范围之内,新变量将隐藏旧变量;然后旧变量将再次可见。

#include<iostream>
int main(){
	using namespace std;
	int x = 20
	{
		cout << x << endl;
		int x = 100;
		cout << x << endl;
	}
	cout << x << endl;
	return 0;
}

1.11 其他语法技巧——逗号运算符

逗号并不总是逗号运算符。例如,下面这个声明中的逗号将变量列表中相邻的名称分开:

int i, j;

以下程序中,使用了两次逗号运算符,该程序将一个string类对象的内容反转。

#include <iostream>
#include <string>
int main() {
	using namespace std;
	cout << "Enter a word: ";
	string word;
	cin >> word;
	char temp;
	int i, j;//int i;int j
	for (j = 0, i = word.size() - 1; j < i; --i, ++j) {
		temp = word[i];
		word[i] = word[j];
		word[j] = temp;
	}

	return 0;
}

首先,它使用逗号运算符将两个初始化操作放进控制部分第一部分的表达式中。然后,再次使用逗号运算符将两个更新合并到控制部分最后一部分的表达式中。
另外,可以在for循环内部声明temp:这样,temp在每轮循环中都被分配和释放。这比在循环前声明temp的速度要慢一些。
逗号运算符花絮
C++为这个逗号运算符提供了两个特性。首先,它确保先计算第一个表达式,然后计算第二个表达式(换句话说,逗号表达式是一个顺序点),如下所示表达式是安全的。

i = 20; j = 2 * i;

其次,C++规定,逗号表达式的值是第二部分的值。例如,上述表达式的值为40,因为j = 2 * i的值为40。在所有运算符中,逗号运算符的优先级是最低的。下面表达式将把cats设置为240——逗号右侧的表达式值:

cats = (17, 240);

1.12 关系表达式

懒得写了,直接看图:
在这里插入图片描述

1.13 赋值、比较和可能犯的错误

不能混淆等于运算符(==)和赋值运算符(=)
下面两个表达式问了一个音乐问题——musicians是否等于4?

musicians == 4 //comparison

该表达式的值为true或false。下面的表达式将4赋给musicians:

musicians = 4 //assignment

在这里,整个表达式的值为4,因为该表达式左边的值为4。

for循环的灵活设计让用户很容易出错。如果不小心遗漏了一个= =运算符中的一个等号,则for循环的测试部分将是一个赋值表达式,而不是关系表达式。

以下代码将指出了可能出现这种错误的情况。

#include<iostream>

int main() {
	using namespace std;
	int quizscores[10] = { 20,20,20,20,20,19,20,18,20,20 };
	cout << "Doing it right:\n";
	int i;
	for (i = 0; quizscores[i] == 20; i++)
		cout << "quiz " << i << " is a 20\n";
	cout << "Doing it dangerously wrong:\n";
	for (i = 0; quizscores[i] = 20; i++)
		cout << "quiz " << i << " is a 20\n";
	//这里因为是赋值语句,所以会进入死循环

	return 0;
}

第一个循环会在显示前五个测试成绩后正确的终止,但第二个循环显示整个数组。更糟糕的是,显示每个值都是20。更糟糕的是,它到了数组末尾还不停止。最糟糕的是,该程序可能导致其他应用程序无法运行,你必须重新启动计算机。
当然,错误出在下面的测试表达式中:

quizscores[i] = 20

首先,由于它将一个非零值赋给数组元素,因此表达式始终为零,所以始终为true。其次,由于表达式将值赋给数组元素,它实际上修改了数据。最后,由于测试表达式一直给true,因此程序在到达数组结尾后,仍不断修改数据,它把一个又一个20放入内存中,这会带来不好的影响。

发现这种语法错误的困难之处在于,代码在语法上是正确的,因此编译器不会将其视为错误(然而,由于C和C++程序员频繁的犯这种错误,因此很多编译器会发出警告,询问这是否是设计者真正的意图)。

警告:
不要使用=来比较两个量是否相等,而要使用= =

1.14 C风格字符串的比较

假设要知道字符数组中的字符串是不是mate。如果word是数组名,下面的测试可能并不像我们预想的那样工作。

word == "mate"

请记住,数组名是数组的地址。同样,使用引号括起的字符常量也是其地址。因此,上面的关系表达式不是判断两个字符串是否相同,而是查看它们是否存储在相同的地址上。两个字符串的地址是否相同呢?答案是否定的,虽然它们包含相同的字符。

由于C++将C风格字符串视为地址,因此如果使用关系运算符来比较它们,将无法得到满意的结果。相反,应使用C风格字符串中的strcmp()来进行比较。该函数会接收两个字符串地址作为参数。这意味着参数可以是指针、字符串、字符串常量或字符数组名。如果两个字符串相同,该函数将返回零;如果第一个字符串按字母顺序排在第二个字符串之前,则strcmp()将返回一个负数值;如果第一个字符串按字母顺序排在第二个字符串之后,则返回一个正数值。实际上,“按系统排列顺序”比“按字母顺序“更准确。这意味着字符是根据字符的系统编码来进行比较的。例如,使用ASCII编码时,所有大写字母的编码都比小写字母小,所以按排列顺序,大写字母将位于小写字母之前。因此字符串“Zoo”在字符串“ariary”之前。根据编码进行比较还意味着大小写字母是不同的,因此,字符串“FOO”和字符串“foo”不同。

#include<iostream>
#include<cstring>
int main() {
	using namespace std;
	char word[5] = "?ath";
	char ch;
	for (ch = 'a'; strcmp(word, "math"); ch++) {
		cout << word << endl;//把上一次的结果打印出来
		word[0] = ch;//把word首元素替换成当前循环的字母
	}
	cout << "After loop ends, word is " << word << endl;
	cout << "ch = " << ch << endl;//打印结果为n,因为for循环最后的ch++还是有执行出来

	return 0;
}

程序说明
该程序最有趣的地方在于,我们只希望word不是mate,循环就继续进行。也就是说,我们只希望strcmp()判断出两个字符串不相同,测试就继续进行。最显而易见的测试是这样的:

strcmp(word, "mate") != 0

如果字符串不相同,则该语句的值为1(true),如果字符串相等,则该语句的值为0(flase)。但使用strcmp(word, “mate”)本身将如何呢?如果字符串不相等,则它的值为非零(true);如果字符串相等,则它的值为零(false)。实际上,如果字符串不同,该返回true,否则为false。因此,可以只用这个函数,而不是整个关系表达式。这样得到的结果将相同,还可以少输入几个字符。

1.15 比较string类字符串

如果使用string类字符串而不是C风格字符串,比较起来会更简单,因为类设计让我们使用关系运算符进行比较,这之所以可行,是因为类函数重载(重新定义)了这些运算符。我们现在只需要知道可以将关系运算符用于string对象即可。
以下代码是基于上面的1.14做的更改,它使用的是string对象而非char数组。

#include<iostream>
#include<cstring>
int main() {
	using namespace std;
	string word = "?ath";//改成了string类型
	char ch;
	for (ch = 'a'; word!="math"; ch++) {
		cout << word << endl;//把上一次的结果打印出来
		word[0] = ch;//把word首元素替换成当前循环的字母
	}
	cout << "After loop ends, word is " << word << endl;
	cout << "ch = " << ch << endl;//打印结果为n,因为for循环最后的ch++还是有执行出来

	return 0;
}

string类的设计让我们能够将string对象作为一个实体(在关系型测试表达式中),也可以将其作为一个聚合对象,从而使用数组表示法来提取其中的字符。

正如我们看到的那样,C风格字符串和string对象可获得相同的结果,但使用string对象更简单、更直观。

最后,和前面大多数的for循环不同,此循环不是计数循环,也就是说,他并不对语句执行指定次数。相反,此循环将根据情况(word是否为“mate”)来确定是否停止。对于这种测试,C++程序通常使用while循环,下面来看看这种循环。

二、while循环

2.1 while循环

while循环是没有初始化和更新部分的for循环,它只有测试条件和循环体

while(test-condition)
 body

首先,程序计算圆括号内的测试条件表达式。如果该表达式为true,则执行循环体中的语句。与for循环一样,循环体也由一条语句或两个花括号定义的语句块组成。执行完循环体后,程序返回测试条件,对它进行重新评估。如果该条件为非零,则再次执行循环体。测试和执行将一直进行下去,直到测试条件为false为止。显然,如果希望循环最终能够结束,循环体中的代码必须完成某种影响条件表达式的操作。例如,循环可以将测试条件中使用的变量加1或从键盘输入读取一个新值。和for循环一样,while循环也是一种入口条件循环。因此,如果测试条件一开始便为false,则程序将不会执行循环体。

#include<iostream>
const int ArSize = 20;
int main() {
	using namespace std;
	char name[ArSize];
	cout << "Your first name,pls: ";
	cin >> name;
	int i = 0;
	while (name[i] != '\0') {
	//这里还能直接使用(name[i]),结果是一样的,因为\0代表ASCII码的0,0为假就会跳出循环
		cout << name[i] << ": " << int(name[i]) << endl;
		i++;
	}

	return 0;
}

在上述程序中while (name[i] != ‘\0’)可以测试数组中特定的字符是不是空值字符。为使该测试最终能够成功,循环体必须修改i的值,这是通过在循环体结尾将i加1来实现的。省略这一步将导致循环停留在同一个数组元素上,打印该字符及其偏码,直到强行终止该程序。导致死循环是循环最常见的问题之一。通常,在循环体中忘记更新某个值时,便会出现这种情况。
我们可以可以这样修改这段程序:

while (name[i])

经过这种修改后,程序的工作方式将不变。这是由于name[i]是常规字符,其值为该字符的编码——非零或true。然而,当name[i]为空值字符时,其编码将为0或false。这种表示法更为简洁(也更常用),但没有上面的表述清晰。

2.2 for与while

在C++中,for和while本质是相同的,例如,下面这段for语句

for(init-exp;test-exp;updata-exp)
{
	stat(s);
}

可以写成这样:

init-exp;
while(test-exp)
{
	stat(s);
	updata-exp;
}

同样,下面的while循环

while(test-exp)
 body

可以改写成这样:

for( ;test-exp; )
{
	body
}

从上面我们不难发现,for循环需要3个表达式,不过它们可以是空表达式(语句),只有两个分号是必须的,另外,省略for循环中的测试表达式时,测试结果将为true,因此下面的循环将一直运行下去:

for( ; ; )
	body

2.3 等待一段时间:编写延时循环

有时候,让程序等待一段时间很有用。例如,显示一则信息,还没来得及阅读的时候又出现了下一则信息。这样我们怕错过重要的信息,此时我们利用延时循环情况将会好很多。
所以C++库里有一个函数有助于完成这样的工作,这个函数名为clock(),返回程序开始执行后所用的系统时间。下面代码就演示了如何使用clock()和头文件ctime来创建延迟循环:

#include<iostream>
#include<ctime>
int main() {
	using namespace std;
	cout << "Enter the delay time, in seconds: ";
	float secs;
	cin >> secs;//想延迟几秒
	clock_t delay = secs * CLOCKS_PER_SEC;//时钟节拍
	cout << "starting\a\n";
	clock_t start = clock();
	while (clock() - start < delay);
	cout << "done\a\n";

	return 0;
}

该程序以系统时间为单位计算延迟时间,避免了在每轮循环中将系统时间转换为秒。

三、do while循环

C++的第三种循环是do while,它不同于另外两种循环,因为它是出口条件循环,这意味着这种循环将首先执行循环体,然后再判定测试表达式,决定是否应继续执行循环。如果条件为false,则终止;否则进入新一轮的执行和测试。这样的循环至少执行一次,因为其程序流必须经过循环体后才能达到测试条件。下面是句法:

do 
	body
while(test-exp);

循环体是一条语句或用括号括起的语句块。通常,入口条件循环比出口条件循环好,因为入口条件循环在循环开始之前对条件进行检查。例如下面这段代码:

#include<iostream>

int main() {
	using namespace std;
	int n;
	cout << "Enter number in the range 1-10 to find my favorite number:\n";
	do {
		cin >> n;
	} while (n != 7);
	cout << "Yes, 7 is my favorite.\n";

	return 0;
}

这段代码请求用户输入时,程序必须先获得输入,然后对它进行测试。

四、基于范围的for循环

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; //20% off sale

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

五、循环和文本输入

5.1 使用原始的cin进行输入

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

#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;
}

程序说明:
请注意程序的结构,该程序在循环之前读取第一个输入字符,这样循环可以测试第一个字符。这很重要,因为第一个字符可能是#。由于上述代码中使用的是入口条件循环,因此在这种情况下,能够正确的跳过整个循环,由于前面已经将count变量设置为0,因此count的值也是正确的。
如果读取的第一个字符不是#,则程序进入该循环,显示字符,增加计数,然后读取下一个字符。最后一步是十分重要的,没有这一步循环将反复处理第一个输入字符,一直进行下去。有了这一步,程序就可以处理到下一个字符。
但为什么程序在输出时省略了空格呢?原因在cin,读取char值的时候,与读取其他基本类型一样,cin将忽略空格和换行符。因此输入中的空格没有被回显,也没有包括在计数内。

5.2 文件尾条件

使用诸如#等符号来表示输入结束很难令人满意,因为这样的符号可能就是合法输入的组成部分,其他符号也是如此。如果输入来自于文件,则可以使用一种功能更强大的计数——检测文件尾(EOF)。C++输入工具和操作系统协同工作,来检测文件尾并将这种信息告知程序。
由于有些系统不支持来自键盘的模拟EOF;有些系统对其支持不完整。cin.get()可以用来锁住屏幕,直到可以读取为止,但这种方法在这里并不适用,因为检测EOF时将关闭对输入的进一步读取。然而,可以使用上面计时循环来使屏幕在一段时间内可见。也可以使用cin.clear()来重置输入流,这将在后面介绍:

#include<iostream>

int main() {
	using namespace std;
	char ch;
	int count = 0;
	cin.get(ch);
	//while (cin.fail() == false) 
	//while (!cin.fail())//简化版
	while(cin.get(ch))//简简化版
	{//检测EOF(检测文件尾)
		cout << ch;
		++count;
		cin.get(ch);
	}
	cout << endl << count << " characters read\n";

	return 0;
}

六、嵌套循环和二维数组

6.1 初始化二维数组

创建二维数组时,可以初始化其所有元素。这项计数建立在一维数组初始化技术的基础上:理由由逗号分隔的花括号括起的值列表:

int btus[5] = {23, 26, 24, 31, 28};

对于二维数组来说,由于每个元素本身就是一个数组,因此可以使用与上述代码类似的格式来初始化每一个元素。因此,初始化由一系列逗号分隔的一维数组初始化(用花括号括起)组成。

int max[4][5] = 
{
	{90,100,80,20,30},
	{34,65,23,76,89},
	{34,65,23,76,89},
	{34,65,23,76,89},//偷懒复制黏贴嘿嘿
}

可将数组max包含4行,每行包含5个数字。

6.2 使用二维数组

下面的程序将演示初始化一个数组,并使用一个嵌套循环。

#include <iostream>
const int Cities = 5;
const int Years = 4;
int main() {
	using namespace std;
	//const char* cities[Cities] = 
	//const char cities[Cities][25] = //加const不可修改,25表示将5个字符串分别复制到5个包含25个元素的char数组中。
	string cities[Cities] = 
	{
		"Gribble City",
		"Gribbletown",
		"New Gribble",
		"San Gribble",
		"Gribble Vista"
	};//指针数组必须要加const

	int maxtemps[Years][Cities] =
	{
		{96,100,87,101,105},//maxtemps[0]
		{96,98,91,107,104},//maxtemps[1]
		{97,101,93,108,107},//maxtemps[2]
		{98,103,95,109,108}//maxtemps[3]
	};

	cout << "Maximum 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;
}

我们在这里使用了string对象数组,而不是字符串指针数组。在希望字符串是可修改的情况下,string类自动调整大小的特性将使这种方法比使用二维数组更为方便。


总结

C++提供了三种循环:for循环、while循环和do while循环。如果循环测试条件为true或非零,则循环将重复执行一组指令;如果测试条件为false或0,则结束循环。for循环和while循环都是入口循环,这意味着程序将在执行循环体中的语句之前检查测试条件。do while循环是出口循环,意味着其将在执行循环体中的语句之后检查条件。
每种循环的句法都要求循环体由一条语句组成。然而,这条语句可以是复合语句,也可以是语句块(由花括号括起的多条语句)。
cin.get(char)成员函数调用通过返回转换为false的bool值来指出已到达EOF,而cin.get()成员函数调用则通过返回EOF值来指出已到达EOF,EOF是在iostream中定义的


http://www.niftyadmin.cn/n/5822949.html

相关文章

C#基础 枚举 Enumeration从基础到进阶

目录 一、入门篇&#xff1a;枚举基础1. 枚举类型的定义2. 枚举类型的优点3. 枚举的基本使用3.1 枚举的类型转换3.2 遍历枚举成员3.3 判断枚举值是否有效3.4枚举的比较 4. 枚举的设计规范 二、深入底层篇&#xff1a;存储、值与继承限制1. 枚举的存储与表示2. 枚举底层类型选择…

2025华数杯国际赛A题完整论文讲解(含每一问python代码+数据+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2025“华数杯”国际大学生数学建模竞赛A题Can He Swim Faster的完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文…

Blender布料模拟动画制作

用blender做一个简单的布料模拟玩玩 建立一个plant 进入编辑模式 按A选中所有面&#xff0c;右键&#xff0c;进入曲面细分 将模型切为48&#xff08;当然越大效果越好&#xff0c;不过计算压力会越大&#xff09; 添加布料模拟&#xff08;Cloth&#xff09; 把Self collisio…

vue3+ts的<img :src=““ >写法

vue3ts的<img :src"" >写法<img :src"datasetImage" alt"数据分布示意图" /><script setup lang"ts">const datasetImage ref();datasetImage.value new URL(../../../assets/images/login-background.jpg, impo…

【股票数据API接口05】如何获取股票最新分时MA数据之Python、Java等多种主流语言实例代码演示通过股票数据接口获取数据

​ 如今&#xff0c;量化分析在股市领域风靡一时&#xff0c;其核心要素在于数据&#xff0c;获取股票数据&#xff0c;是踏上量化分析之路的第一步。你可以选择亲手编写爬虫来抓取&#xff0c;但更便捷的方式&#xff0c;莫过于利用专业的股票数据API接口。自编爬虫虽零成本&a…

从 Conda 到 Pip-tools:Python 依赖管理全景探索20250113

从 Conda 到 Pip-tools&#xff1a;Python 依赖管理全景探索 引言 在 Python 开发中&#xff0c;依赖管理是一个"常见但复杂"的问题&#xff1a;一次简单的版本冲突可能让团队调试数小时&#xff1b;一次不受控的依赖升级可能让生产环境瘫痪。随着项目规模的增加和…

python bs4 selenium 查找a href=javascript:();的实际点击事件和url

在使用 BeautifulSoup 和 Selenium 时&#xff0c;处理 href"javascript:;" 的链接需要一些额外的步骤&#xff0c;因为这些链接不直接指向一个 URL&#xff0c;而是通过 JavaScript 代码来执行某些操作。这可能包括导航到另一个页面、触发模态窗口、显示/隐藏内容等…

使用gtsam添加OrientedPlane3Factor平面约束因子

在基于地面约束的SLAM优化中&#xff0c;已知的地面信息&#xff08;如 plan.pcd 文件中的地面模型&#xff09;可以用作一个先验约束&#xff0c;以帮助优化位姿估计。具体而言&#xff0c;这个过程涉及将地面模型和每个帧的位姿结合&#xff0c;以创建一个因子模型&#xff0…