C++PrimerPlus 第五章 循环和关系表达式 - 5.1 for循环

2023-11-07

5.1 for循环

很多情况下都需要程序执行重复的任务,如将数组中的元素累加起来或将歌颂生产的赞歌打印20份,C++中的for循环可以轻松地完成这种任务。我们来看看程序清单5.1中,以了解for循环所做的工作,然后讨论它是如何工作的。

程序清单5.1 forloop.cpp

//forloop.cpp -- introducing the for loop
#include<iostream>
int main()
{
	using namespace std;
	int i;	//create a counter
//initialize; test; update
	for (i = 0; i < 5; i++)
		cout << "C++ knows loop.\n";
	cout << "C++ knows when to stop.\n";
	return 0;
}

下面是该程序的输出:
C++ knows loop.
C++ knows loop.
C++ knows loop.
C++ knows loop.
C++ knows loop.
C++ knows when to stop.

该循环首先将整数变量i设置为0:

i = 0

这是循环的初始化(loop initialization)部分。然后,循环测试(loop test)部分检查i是否小于5:

i < 5

如果确实小于5,则程序将执行接下来的语句——循环体(loop body):

cout << “C++ knows loop.\n”;

然后,程序使用循环更新(loop update)部分将i加1:

i++

这里使用了++运算符——递增运算符(increment operator),它将操作数的值加1。递增运算符并不仅限于用于for循环。例如,在程序中,可以使用i++;来替换语句i = i + 1;。将i加1后,便结束了循环的第一个周期。

接下来,循环开始了新的周期,将新的i值与5进行比较。由于新值(1)也小于5,因此循环打印另一行。然后再次将i加1,从而结束这一周期。这样又进入了新的一轮测试、执行语句和更新i的值。这一过程将一直进行下去,直到循环将i更新为5为止。这样,接下来的测试失败,程序将接着执行循环后的语句。

5.1.1 for循环的组成部分

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

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

C++循环设计中包括了这些要素,很容易识别。初始化、测试和更新操作构成了控制部分,这些操作由括号括起。其中每部分都是一个表达式,彼此由分号隔开。控制部分后面的语句叫作循环体,只要测试表达式为true,它便被执行:

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

C++语法将整个for看作一条语句——虽然循环体可以包含一条或多条语句。(包含多条语句时,需要使用复合语句或代码块,这将在本章后面进行讨论。)

循环只执行一次初始化。通常,程序使用该表达式将变量设置为起始值,然后用该变量计算循环周期。

test-expression(测试表达式)决定循环体是否被执行。通常,这个表达式是关系表达式,即对两个值进行比较。这个例子将i的值同5进行比较,看i是否小于5。如果比较结果为真,则程序将执行循环体。实际上,C++并没有将test-expression的值限制为只能为真或假。可以使用任意表达式,C++将结果强制转换为bool类型。因此,值为0的表达式将被转换为bool值false,导致循环结束。如果表达式的值为非零,则被强制转换为bool值true,循环将继续进行。程序清单5.2通过将表达式i用作测试条件来演示了这一特点。更新部分的i–与i++相似,只是每使用一次,i值就减1。

程序清单5.2 num_test.cpp

//num_test.cpp -- use numeric test in for loop
#include<iostream>
int main()
{
	using namespace std;
	cout << "Enter the starting countdown value: ";
	int limit;
	cin >> limit;
	int i;
	for (i = limit; i; i--)	//quite when i is 0
		cout << "i = " << i << "\n";
	cout << "Done now that i = " << i << "\n";
	return 0;
}

下面是该程序的输出:
Enter the starting countdown value: 4
i = 4
i = 3
i = 2
i = 1
Done now that i = 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。

for循环是入口条件(entry-condition)循环。这意味着在每轮循环之前,都将计算测试表达式的值,当测试表达式为false时,将不会执行循环体。例如,假设重新运行程序清单5.2中的程序,但将起始值设置为0,则由于测试条件在首次被判定时便为false,循环体将不被执行:

Enter the starting countdown value: 0
Done now that i = 0

这种在循环之前进行检查的方式可避免程序遇到麻烦。

update-expression(更新表达式)在每轮循环结束时执行,此时循环体已经执行完毕。通常,它用来对跟踪循环轮次的变量的值进行增减。然而,它可以是任何有效的C++表达式,还可以是其他控制表达式。这使for循环的功能不仅仅是从0数到5(这是第一个循环示例所做的工作),稍后将介绍一些例子。

for循环体有一条语句组成,不过很快将介绍如何扩展这条规则。下图对for循环设计进行了总结。

在这里插入图片描述
for语句看上去有些像函数调用,因为它使用一个后面跟一对括号的名称。然而,for是一个C++关键字,因此编译器不会将for视为一个函数,这还将防止将函数命名为for。

提示:

C++常用的方式是,在for和括号之间加上一个空格,而省略函数名与括号之间的空格。

for (i = 6; i < 10; i++)
	smart_function(i);

对于其他控制语句(如if和while),处理方式与for相似。这样从视觉上强化了控制语句和函数调用之间的区别。另外,常见的做法是缩进for语句体,使它看上去比较显著。

5.1.1.1 表达式和语句

for语句的控制部分使用3个表达式。由于其自身强加的句法限制,C++成为非常具有表现力的语言。任何值或任何有效的值和运算符的组合都是表达式。例如,10是值为10的表达式(一点都不奇怪),28*20的值为560的表达式。在C++中,每个表达式都有值。通常值是很明显的。例如,下面的表达式由两个值和一个加号组成,它的值为49:

22 + 27

有时值不这么明显,例如,下面是一个表达式,因为它由两个值和一个赋值运算符组成:

x = 20

C++将赋值表达式的值定义为左侧成员的值,因此这个表达式的值为20。由于赋值表达式有值,因此可以编写下面这样的语句:

maids = (cooks = 4) + 3;

表达式cooks = 4的值为4,因此maids的值为7。然而,C++虽然允许这样做,但并不意味着应鼓励这种做法,允许存在上述语句存在的原则也允许编写如下的语句?

x = y = z = 0;

这种方法可以快速地将若干个变量设置为相同的值。优先级表(见附录D)表明,赋值运算符从右向左结合的,因此首先将0赋给z,然后将z = 0赋给y,依次类推。

最后,正如前面指出的,像x < y这样的关系表达式将被判定为bool值true或false。程序清单5.3中的小程序指出了有关表达式值的一些重要方面。<<运算符的优先级比表达式中使用的运算符高,因此代码使用括号来获得正确的运算顺序。

程序清单5.3 express.cpp

//express.cpp -- values of expressions
#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);		//a newer C++ feature
	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;
}

注意:

老式C++实现可能要求使用ios:boolalpha,而不是ios_base::boolalpha来作为cout.setf()的参数。有些老式实现甚至无法识别这两种形式。

下面是该程序的输出:
The expression x = 100 has the value 100
Now x = 100
The expression x < 3 has the value 0
The expression x > 3 has the value 1
The expression x < 3 has the value false
The expression x > 3 has the value true

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

注意:

C++表达式是值或值与运算符的组合,每个C++表达式都有值。

为判定表达式x = 100,C++必须将100赋给x。当判定表达式的值这种操作改变了内存中的数据的值时,我们说表达式有副作用(side effect)。因此,判定赋值表达式会带来这样的副作用,即修改被赋值者的值。有可能把赋值看作预期的效果,但从C++的构造方式这个角度来看,判定表达式才是主要作用。并不是所有的表达式都有副作用。例如,判定x + 15将计算出一个新的值,但不会修改x的值。然而,判定++x + 15就有副作用,因为它将x加1。

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

age = 100

而下面是一条语句:

age = 100;

更准确地说,这是一条表达式语句。只要加上分号,所有的表达式都可以成为语句,但不一定有编程意义。例如,如果rodents是个变量,则下面就是一条有效的C++语句:

rodents + 6;		//valid, but useless, statement

编译器允许这样的语句,但它没有完成任何有用的工作。程序仅仅是计算和,而没有使用得到的结果,然后便进入下一条语句(智能编译器甚至可能跳过这条语句)。

5.1.1.2 非表达式和语句

有些概念对于理解C++至关重要,如了解for循环的结构。不过句法中也有一些相对次要的内容,让认为自己理解语言的人突然觉得不知所措。下面来看看这样的内容。

对任何表达式加上分号都可以成为语句,但是这句话反过来说就不对了。也就是说,从语句中删除分号,并不一定能将它转换为表达式。就我们目前使用的语句而言,返回语句、声明语句和for语句都不满足“语句 = 表达式 + 分号”这种模式。例如,下面是一条语句:

int toad;

但int toad并不是表达式,因为它没有值。因此,下面的代码是非法的:

eggs = int toad * 1000;	//invalid, not an expression
cin >> int toad;		//can’t combine declaration with cin

同样,不能把for循环赋给变量。在下面的示例中,for循环不是表达式,因此没有值,也不能给它赋值:

int fx = for (i = 0; i < 4; i++)
			cout << i;	//not possible

5.1.1.3 修改规则

C++在C循环的基础上添加了一项特性,要求对for循环句法做一些微妙的调整。

这是原来的句法:

for (expression; expression; expression)
statement

具体地说,正如本章前面指出的,for结构的控制部分由3个表达式组成,它们由分号分隔。然而,C++循环允许像下面这样做:

for (int i = 0; i < 5; i++)

也就是说,可以在for循环的初始化部分中声明变量。这很方便,但并不适用于原来的句法,因为声明不是表达式。这种一度是非法的行为最初是通过定义一种新的表达式——声明语句表达式(declaration-statement expression)——来合法化的,声明语句表达式不带分号声明,只能出现在for语句中。然而,这种调整已经被取消了,代之以将for语句的句法修改成下面这样:

for (for-init-statement condition; expression)
statement

乍一看很奇怪,因为这里只有一个分号(而不是两个分号)。但是这是允许的,因为for-init-statement被视为一条语句,而语句有自己的分号。对于for-init-statement来说,它既可以是表达式语句,也可以是声明。这种句法规则用语句替换了后面跟分号的表达式,语句本身有自己的分号。总之,C++程序员希望能够在for循环初始化部分中声明和初始化变量,他们会做C++句法需要和英语所允许的工作。

在for-init-statement中声明变量还有其实用的一面,这也是应该知道的。这种变量只存在于for语句中,也就是说,当程序离开循环后,这种变量将消失:

for (int i = 0; i < 5; i++)
	cout << “C++ knows loop.\n”;
cout << i << endl;	//cops! i no longer defined

您还应知道的一点是,有些较老的C++实现遵循以前的规则,对于前面的循环,将把i视为是在循环之前声明的,因此在循环结束后,i仍可用。

5.1.2 回到for循环

下面使用for循环完成更多的工作。程序清单5.4使用循环来计算并存储前16个阶乘。阶乘的计算方式如下:零阶乘写作0!,被定义为1。1!是1*0!,即1。2!为2*1!,即2。3!为3*2!,即6,依此类推。每个整数的阶乘都是该整数与前一个阶乘的乘积(钢琴家Victor Borge最著名的独白以其语音特点为特色,其中,惊叹号的发音就像phffft pptz,带有濡湿的口音。然而,刚才提到的“!”读作“阶乘”)。该程序用一个循环来计算连续阶乘的值,并将这些值存储在数组中。然后,用另一个循环来显示结果。另外,该程序还在外部声明了一些值。

程序清单5.4 formore.cpp

//formore.cpp -- more looping with for
#include<iostream>
const int ArSize = 16;		//example of external declaration
int main()
{
	long long factorials[ArSize];
	factorials[1] = factorials[0] = 1LL;
	for (int i = 2; i < ArSize; i++)
		factorials[i] = i * factorials[i - 1];
	for (int i = 0; i < ArSize; i++)
		std::cout << i << "! = " << factorials[i] << std::endl;
	return 0;
}

下面是该程序的输出:
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000

阶乘增加得很快!

注意:

这个程序清单使用了类型long long。如果您的系统不支持这种类型,可使用double。然而,整型使得阶乘的增大方式看起来更明显。

程序说明

该程序创建了一个数组来存储阶乘值。元素0存储0!,元素1存储1!,依此类推。由于前两个阶乘都等于1,等于1,因此程序将factorials数组的前两个元素设置为1(记住,数组第一个元素的索引值为0)。然后,程序用循环将每个阶乘设置为索引号与前一个阶乘的乘积。该循环表明,可以在循环体中使用循环计数。

该程序演示了for循环如何通过提供一种访问每个数组成员的方便途径来与数组协同工作。另外,formore.cpp还使用const创建了数组长度的符号表示(ArSize)。然后,它在需要数组长度的地方使用ArSize,如定义数组以及限制循环如何处理数组时。现在,如果要将程序扩展处理20个阶乘,则只需要将ArSize设置为20并重新编译程序即可。通过使用符号常量,就可以避免将所有的10修改为20。

提示:

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

表达式i < ArSize反映了这样一个事实,包含ArSize个元素的数组的下标从0到ArSize - 1,因此数组索引应在ArSize减1的位置停止。也可以使用i <= ArSize - 1,但它看上去没有前面的表达式好。

该程序在main()的外面声明const int变量ArSize。第4章末尾提到过,这样可以使ArSize成为外部数据。以这种方式声明ArSize的两种后果是,ArSize在整个程序周期内存在、程序文件中所有的函数都可以使用它。在这个例子中,程序只有一个函数,因此在外部声明ArSize几乎没有任何实际用处,但包含多个函数的程序都会受益于共享外部常量,因此我们现在就开始练习使用外部变量。

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

5.1.3 修改步长

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

程序清单5.5 bigstep.cpp

//bigstep.cpp -- count as directed
#include<iostream>
int main()
{
	using std::cout;	//a using declaration
	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;
}

下面是该程序的运行情况:
Enter an integer: 17
Counting by 17s:
0
17
34
51
68
85

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

需要指出的另一点是,检测不等通常比检测相等好。例如,在这里使用条件i == 100不可行,因为i的取值不会为100。

最后,这个示例使用了using声明,而不是using编译指令。

5.1.4 使用for循环访问字符串

for循环提供了一种依次访问字符串中每个字符的公式。例如,程序清单5.6让用户能够输入一个字符串,然后按相反的方向逐个字符地显示该字符串。在这个例子中,可以使用string对象,也可以使用char数组,因为它们都让您能够使用数组表示法来访问字符串中的字符。程序清单5.6使用的是string对象。string类的size()获得字符串中的字符数;循环在其初始化表达式中使用这个值,将i设置为字符串中最后一个字符的索引(不考虑空值字符)。为了反向计数,程序使用递减运算符(–),在每轮循环后将数组下标减1。另外,程序清单5.6使用关系运算符大于或等于(>=)来测试循环是否到达第一个元素。稍后我们将对所有的关系运算符做一总结。

程序清单5.6 forstr1.cpp

//forstr1.cpp -- using for with a string
#include<iostream>
#include<string>
int main()
{
	using namespace std;
	cout << "Enter a word: ";
	string word;
	cin >> word;

	//display letters in reverse order
	for (int i = word.size() - 1; i >= 0; i--)
		cout << word[i];
	cout << "\nBye.\n";
	return 0;
}

注意:

如果所用的实现没有添加新的头文件,则必须使用string.h,而不是cstring。

下面是该程序的运行情况:
Enter a word: animal
lamina
Bye.

程序成功地按相反的方向打印了animal;与回文rotator、redder或stats相比,animal能更清楚地说明这个程序的作用。

5.1.5 递增运算符(++)和递减运算符(–)

C++中有多个常被用在循环中的运算符,因此我们花一点时间来讨论它们。前面已经介绍了两个这样的运算符:递增运算符(++)(名称C++由此得到)和递减运算符(–)。这两个运算符执行两种极其常见的循环操作:将循环计数加1或减1。然而,它们还有很多特点不为读者所知。这两个运算符都有两种变体。前缀(prefix)版本位于操作数前面,如++x;后缀(postfix)版本位于操作数后面,如x++。两个版本对操作数的影响是一样的,但是影响的时间不同。这就像对于钱包来说,清理草坪之前付钱和清理草坪之后付钱的最终结果是一样的,但支付钱的时间不同。程序清单5.7演示递增运算符的这种差别。

程序清单5.7 plus_one.cpp

//plus_one.cpp -- the increment operator
#include<iostream>
int main()
{
		using std::cout;
		int a = 20;
		int b = 20;
		cout << "a   = " << a << ":   b = " << b << "\n";
		cout << "a++ = " << a++ << ": ++b = " << ++b << "\n";
		cout << "a   = " << a << ":   b = " << b << "\n";
		return 0;
}

下面是该程序的输出:
a = 20: b = 20
a++ = 20: ++b = 21
a = 21: b = 21

粗略地将,a++意味着使用a的当前值计算表达式,然后将a的值加1;而++b的意思是先将b的值加1,然后使用新的值来计算表达式。例如,我们有下面这样的关系:

int x = 5;
int y = ++x;		//change x, then assign to y
					//y is 6, x is 6
int z = 5;
int y = z++;		//assign to y, then change z
					//y is 5, z is 6

递增和递减运算符是处理将值加减1这种常见任务的一种简约、方便的方法。

递增运算符和递减运算符都是漂亮的小型运算符,不过千万不要失去控制,在同一条语句对同一个值递增或递减多次。问题在于,规则“使用后修改”和“修改后使用”可能会变得模糊不清。也就是说,下面这条语句在不同的系统上将生成不同的结果:

x = 2 * x++ * (3 - ++x);		//don’t do it except as an experiment

对这种语句,C++没有定义正确的行为。

5.1.6 副作用和顺序点

下面更详细地介绍C++就递增运算符何时生效的哪些方面做了规定,哪些方面没有规定。首先,副作用(side effect)指的是在计算表达式时对某些东西(如存储在变量中的值)进行了修改;顺序点(sequence point)是程序执行过程中的一个点,在这里,进入下一步之前将确保对所有的副作用都进行了评估。在C++中,语句中的分号就是一个顺序点,这意味着程序处理下一条语句之前,赋值运算符、递增运算符和递减运算符执行的所有修改都必须完成。本章后面将讨论的有些操作也有顺序点。另外,任何完整的表达式末尾都是一个顺序点。

何为完整表达式呢?它是这样一个表达式:不是另一个更大表达式的子表达式。完整表达式的例子有:表达式语句中的表达式部分以及用作while循环中检测条件的表达式。

顺序点有助于阐明后缀递增何时进行。例如,请看下面的代码:

while (guests++ < 10)
	cout << guests << endl;

while循环将在本章后面讨论,它类似于只有测试表达式的for循环。在这里,C++新手可能认为“使用值,然后递增”意味着先在cout语句中使用guests的值,再将其值加1。然而,表达式guests++ < 10是一个完整表达式,因为它是一个while循环的测试条件,因此该表达式的末尾是一个顺序点。所以,C++确保副作用(将guests加1)在程序进入cout之前完成。然而,通过使用后缀格式,可确保将guests同10进行比较后再将其值加1。

现在来看下面的语句:

y = (4 + x++) + (6 + x++);

表达式4 + x++不是一个完整表达式,因此,C++不保证x的值在计算子表达式4 + x++后立刻增加1。在这个例子中,整条赋值语句是一个完整表达式,而分号标示了顺序点,因此C++只保证程序执行到下一条语句之前,x的值将被递增两次。C++没有规定是在计算每个子表达式之后将x的值递增,还是在整个表达式计算完毕后才将x的值递增,有鉴于此,您应避免使用这样的表达式。

在C++11文档中,不再使用术语“顺序点”了,因为这个概念难以用于讨论多线程执行。相反,使用了术语“顺序”,它表示有些事件在其他事件前发生。这种描述方法并非要改变规则,而旨在更清晰地描述多线程编程。

5.1.7 前缀格式和后缀格式

显然,如果变量被用于某些目的(如用作函数参数或给变量赋值),使用前缀格式和后缀格式的结果将不同。然而,如果递增表达式的值没有被使用,情况又如何呢?例如,下面两条语句的作用是否不同?

X++;
++X;

下面两条语句的作用是否不同?

for (n = lim; n > 0; --n)
...;

for (n = lim; n > 0; n--)
...;

从逻辑上说,在上述两种情形下,使用前缀格式和后缀格式没有任何区别。表达式的值未被使用,因此只存在副作用。在上面的例子中,使用这些运算符的表达式为完整表达式,因此将x加1和n减1的副作用将在程序进入下一步之前完成,前缀格式和后缀格式的最终效果相同。

然而,虽然选择使用前缀格式还是后缀格式对程序的行为没有影响,但执行速度可能有细微的差别。对于内置类型和当代的编译器而言,这看似不是什么问题。然而,C++允许您针对类定义这些运算符,在这种情况下,用户这样定义前缀函数:将值加1,然后返回结果;但后缀版本首先复制一个副本,将其加1,然后将复制的副本返回。因此,对于类而言,前缀版本的效率比后缀版本高。

总之,对于内置类型,采用哪种格式不会有差别;但对于用于定义的类型,如果有用户定义的递增和递减运算符,则前缀格式的效率更高。

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

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

double arr[5] = {21.1, 32.8, 23.4, 45.2, 37.4};
double *pt = arr;	//pt points to arr[0], i.e. to 21.1
++pt;				//pt points to arr[1], i.e. to 32.8

也可以结合使用这些运算符和运算符来修改指针指向的值。将和++同时用于指针时提出了这样的问题:将什么解除引用,将什么递增。这取决于运算符的位置和优先级。前缀递增、前缀递减和解除引用运算符的优先级相同,以从右到左的方式进行结合。后缀递增和后缀递减的优先级相同,但比前缀运算符的优先级高,这两个运算符以从左到右的方式进行结合。

前缀运算符的从右到左结合规则意味着*++p的含义如下:现将++应用于pt(因为++位于右边),然后将应用于被递增后的pt:

double x = *++pt;		//increment pointer, take the value; i.e.,arr[2], or 23.4

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

++*pt;			//increment the pointed to value; i.e., change 23.4 to 24.4

在这种情况下,pt仍然指向arr[2]。

接下来,请看下面的组合:

(*pt)++;		//increment pointed-to value

圆括号指出,首先对指针解除引用,得到24.4。然后,运算符++将这个值递增到25.4,pt仍然指向arr[2]。

最后,来看看下面的组合:

x = *pt++;		//dereference original location, then increment pointer

后缀运算符++的优先级更高,这意味着将运算符用于pt,而不是*pt,因此对指针递增。然后后缀运算符意味着将对原来的地址(&arr[2])而不是递增后的新地址解除引用,因此*pt++的值为arr[2],即25.4,但该语句执行完毕后,pt的值将为arr[3]的地址。

注意:

指针递增和递减遵循指针算术规则。因此,如果pt指向某个数组的第一个元素,++pt将修改pt,使之指向第二个元素。

5.1.9 组合赋值运算符

程序清单5.5 使用了下面的表达式来更新循环计数:

i = i + by

C++有一种合并了加法和赋值操作的运算符,能够更简洁地完成这种任务:

i += by

+=运算符将两个操作数相加,并将结果赋给左边地操作数。这意味着左边的操作数必须能够被赋值,如变量、数组元素、结构成员或通过对指针解除引用来标识的数据:

int k = 5;
k += 3;						//ok, k set to 8
int* pa = new int[10];		//pa points to pa[0]
pa[4] = 12;
pa[4] += 6;					//ok, pa[4] set to 18
*(pa + 4) += 7;				//ok, pa[4] set to 25
pa += 2;					//ok, pa points to the former pa[2]
34 += 10;					//quite wrong

每个算术运算符都有其对应的组合赋值运算符,下表对它们进行了总结。其中每个运算符的工作方式都和+=相似。因此,下面的语句将k与10相乘,再将结果赋给k:

k *= 10;

在这里插入图片描述

5.1.10 复合语句(语句块)

编写C++ for语句的格式(或句法)看上去可能比较严格,因为循环体必须是一条语句。如果要在循环体中包含多条语句,这将很不方便。所幸的是,C++提供了避开这种限制的方式,通过这种方式可以在循环体中包含任意多条语句。方法是用两个花括号来构造一条复合语句(代码块)。代码块由一对花括号和它们包含的语句组成,被视为一条语句,从而满足句法要求。例如,程序清单5.8中的程序使用花括号将3条语句合并为一个代码块。这样,循环体便能够提示用户、读取输入并进行计算。该程序计算用户输入的数字的和,因此有机会使用+=运算符。

程序清单5.8 block.cpp

//block.cpp -- use a block statement
#include<iostream>
int main()
{
	using namespace std;
	cout << "The Amazing Accounto will sum and average ";
	cout << "five numbers for you.\n";
	cout << "Please enter five values.\n";
	double number;
	double sum = 0.0;
	for (int i = 1; i <= 5; i++)
	{										//block starts here
		cout << "Value " << i << ": ";
		cin >> number;
		sum += number;
	}										//block ends here
	cout << "Five exquisite choices indeed! ";
	cout << "They sum to " << sum << endl;
	cout << "and average to " << sum / 5 << ".\n";
	cout << "The Amazing Accounto bids you adieu!\n";
	return 0;
}

下面是该程序的运行情况:
The Amazing Accounto will sum and average five numbers for you.
Please enter five values.
Value 1: 1942
Value 2: 1948
Value 3: 1957
Value 4: 1974
Value 5: 1980
Five exquisite choices indeed! They sum to 9801
and average to 1960.2.
The Amazing Accounto bids you adieu!

假设对循环体进行了缩进,但省略了花括号:

for (int i = 1; i <= 5; i++)
	cout << "Value " << i << ": ";		//loop ends here
	cin >> number;				//after the loop
	sum += number;
cout << "Five exquisite choices indeed! ";

编译器将忽略缩进,因此只有第一条语句位于循环中。因此,该循环将只打印出5条提示,而不执行其他操作。循环结束后,程序移到后面几行执行,只读取和计算一个数字。

复合语句还有一种有趣的特性。如果在语句块中定义一个新的变量,则仅当程序执行该语句块中的语句时,该变量才存在。执行完该语句块后,变量将被释放。这表明此变量仅在该语句块中才是可用的:

#include<iostream>
int main()
{
	using namespace std;
	int x = 20;
	{							//block starts
		int y = 100;
		cout << x << endl;		//ok
		cout << y << endl;		//ok
	}							//block ends
	cout << x << endl;			//ok
	cout << y << endl;			//invalis, won't compile
	return 0;
}

注意,在外部语句块中定义的变量在内部语句块中也是被定义了的。

如果在一个语句块中声明一个变量,而外部语句块中也有一个这种名称的变量,情况将如何呢?在声明位置到内部语句块结束的范围之内,新变量将隐藏旧变量;然后就变量再次可见,如下例所示:

#include<iostream>
int main()
{
	using std::cout;
	using std::endl;
	int x = 20;					//original x
	{							//block starts
		cout << x << endl;		//use original x
		int x = 100;			//new x
		cout << x << endl;		//use new x
	}							//block ends
	cout << x << endl;			//use original x
	return 0;
}

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

正如读者看到的,语句块允许把两条或更多条语句放到按C++句法只能放一条语句的地方。逗号运算符对表达式完成同样的任务,允许将两个表达式放到C++句法只允许放一个表达式的地方。例如,假设有一个循环,每轮都将一个变量加1,而将另一个变量减1。在for循环控制部分的更新部分中完成这两项工作将非常方便,但循环句法只允许这里包含一个表达式。在这种情况下,可以使用逗号运算符将两个表达式合并为一个:

++j, --i;		//two expressions count as one for syntax purposes

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

int i, j;		//comma is a separator here, not an operator

程序清单5.9在一个程序中使用了两次逗号运算符,该程序将一个string类对象的内容反转。也可以使用char数组来编写该程序,但可输入的单词长度将受char数组大小的限制。注意,程序清单5.6按相反的顺序显示数组的内容,而程序清单5.9将数组中的字符顺序反转。该程序还使用了语句块将几条语句组合成一条。

程序清单5.9 forstr2.cpp

//forstr2.cpp -- reversing an array
#include<iostream>
#include<string>
int main()
{
	using namespace std;
	cout << "Enter a word: ";
	string word;
	cin >> word;

	//physically modify string object
	char temp;
	int i, j;
	for (j = 0, i = word.size() - 1; j < i; --i, ++j) 
	{						//start block
		temp = word[i];
		word[i] = word[j];
		word[j] = temp;
	}						//end block
	cout << word << "\nDone\n";
	return 0;
}

下面是该程序运行情况:
Enter a word: stressed
desserts
Done

顺便说一句,在反转字符串方面,string类提供了更为简洁的方式,这将在第16章介绍。

5.1.11.1 程序说明

来看程序清单5.9中的for循环控制部分。

首先,它使用逗号运算符将两个初始化操作放进控制部分第一部分的表达式中。然后,再次使用逗号运算符将两个更新合并到控制部分最后一部分的表达式中。

接下来看循环体。程序用括号将几条语句合并为一个整体。在循环体中,程序将数组第一个元素和最后一个元素调换,从而将单词反转过来。然后,它将j加1,将i减1,让它们分别指向第二个元素和倒数第二个元素,然后将这两个元素调换。注意,测试条件j < i使得到达数组的中间时,循环将停止。如果过了这一点后,循环仍继续下去,则便开始将交换后的元素回到原来的位置(参见下图)。

在这里插入图片描述
需要注意的另一点是,声明变量temp、i、j的位置。代码在循环之前声明i和j,因为不能用逗号运算符将两个声明组合起来。这是因为声明已经将逗号用于其他用途——分隔列表中的变量。也可以使用一个声明语句表达式来创建并初始化两个变量,但是这样看起来有些乱:

int j = 0, i = word.size() - 1;

在这种情况下,逗号只是一个列表分隔符,而不是逗号运算符,因此该表达式对j和i进行声明和初始化。然而,看上去好像只声明了j。

另外,可以在for循环内部声明temp:

int temp = word[i];

这样,temp在每轮循环中都将被分配和释放。这比在循环前声明temp的速度要慢一些。另一方面,如果在循环内部声明temp,则它将在循环结束后被丢弃。

5.1.11.2 逗号运算符花絮

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

i = 20, j = 2 * i		//i set to 20, then j set to 40

其次,C++规定,逗号表达式的值是第二部分的值。例如,上述表达式的值为40,因为j = 2 * i的值为40。

在所有运算符中,逗号运算符的优先级是最低的。例如,下面的语句:

cats = 17,240;

被解释为:

(cats = 17), 240;

也就是说,将cats设置为17, 240不起作用。然而,由于括号的优先级最高,下面的表达式将把cats设置为240——逗号右侧的表达式值:

cats = (17, 240);

5.1.12 关系表达式

计算机不只是机械的数字计数器。它能够对值进行比较,这种能力是计算机决策的基础。在C++中,关系运算符是这种能力的体现。C++提供了6种关系运算符来对数字进行比较。由于字符用其ASCII码表示,因此也可以将这些运算符用于字符。不能将它们用于C-风格字符串,但可用于string类对象。对于所有的关系表达式,如果比较结果为真,则其值将为true,否则为false,因此可将其用作循环测试表达式。(老式实现认为结果为true的关系表达式的值为1,而结果为false的关系表达式为0。)下表对这些运算符进行了总结。

在这里插入图片描述
这6种关系运算符可以在C++中完成对数字的所有比较。如果要对两个值进行比较,看看哪个值更漂亮或更幸运,则这里的运算符就派不上用场了。

下面是一些测试示例:

for (x = 20; x > 5; x--) //continue while x is greater than 5
for (x = 1; y != x; ++x) //continue while y is not equal to x
for (cin >> x; x == 0; cin >> x) //continue while x is 0

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

x + 3 > y - 2		//Expression 1

对应于:

(x + 3) > (y - 2)		//Expression 2

而不是:

x + (3 > y) - 2		//Expression 3

由于将bool值提升为int后,表达式(3 > y)要么为1,要么为0,因此第二个和第三个表达式都是有效的。不过我们更希望第一个表达式等价于第二个表达式,而C++正是这样做的。

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

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

musicians == 4		//comparison

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

musicians = 4		//assignment

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

for循环的灵活设计让用户很容易出错。如果不小心遗漏了–运算符中的一个等号,则for循环的测试部分将是一个赋值表达式,而不是关系表达式,此时代码仍是有效的。这是因为可以将任何有效的C++表达式用作for循环的测试条件。别忘了,非零值为true,零值为false。将4赋给musicians的表达式的值为4,因此被视为true。如果以前使用过用=判断是否相等的语言,如Pascal或BASIC,则尤其可能出现这样的错误。

程序清单5.10中指出了可能出现这种错误的情况。该程序试图检查一个存储了测试成绩的数组,在遇到第一个不为20的成绩时停止。该程序首先演示了一个正确进行比较的循环,然后是一个在测试条件中错误地使用了赋值运算符的循环。该程序还有另一个重大的设计错误,稍后将介绍如何修复(应从错误中吸取教训,而程序清单5.10在这方面很有帮助)。

程序清单5.10 equal.cpp

//equal.cpp -- equality vs assignment
#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";
	//Warning: you may prefer reading about this program
	//to actually running it.
	cout << "Doing it dangerously wrong:\n";
	for (i = 0; quizscores[i] = 20; i++)
		cout << "quiz " << i << " is a 20\n";

	return 0;
}

由于这个程序存在一个严重的问题,读者可能希望了解它,以便真正运行它。下面是该程序的一些输出:
Doing it right:
quiz 0 is a 20
quiz 1 is a 20
quiz 2 is a 20
quiz 3 is a 20
quiz 4 is a 20
Doing it dangerously wrong:
quiz 0 is a 20
quiz 1 is a 20
quiz 2 is a 20
quiz 3 is a 20
quiz 4 is a 20
quiz 5 is a 20
quiz 6 is a 20
quiz 7 is a 20
quiz 8 is a 20
quiz 9 is a 20
quiz 10 is a 20
quiz 11 is a 20
quiz 12 is a 20
quiz 13 is a 20

第一个循环在显示了前5个测试成绩后正确地终止,但第二个循环显示整个数组。更糟糕的是,显示的每个值都是20。更加糟糕的是,它到了数组末尾还不停止。最糟糕的是,该程序可能导致其他应用程序无法运行,您必须重新启动计算机。

当然,错误出在下面的测试表达式中:

quizscores[i] = 20

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

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

警告:

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

和C语言一样,C++比起大多数编程语言来说,赋予程序员更大的自由。这种自由以程序员应付的更大责任为代价。只有良好的规划才能避免程序超出标准C++数组的边界。然而,对于C++类,可以设计一种保护数组类型来防止这种错误,第13章提供了一个这样的例子。另外,应在需要的时候在程序中加入保护措施。例如,在程序清单5.10的循环中,应包括防止超出最后一个成员的测试,这甚至对于“好”的循环来说也是必要的。如果所有的成绩都是20,“好”的循环也会超出数组边界。总之,循环需要测试数组的值和索引的值。第6章将介绍如何使用逻辑运算符将两个这样的测试合并为一个条件。

5.1.14 C-风格字符串的比较

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

word == “mate”

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

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

在有些语言(如BASIC和标准Pascal)中,存储在不同长度的数组中的字符串彼此不相等。但是C-风格字符串是通过结尾的空值字符定义的,而不是由其所在数组的长度定义的。这意味着两个字符串即使被存储在长度不同的数组中,也可能是相同的:

char big[80] = “Daffy”;		//5 letters plus \0
char litter[6] = “Daffy”;		//5 letters plus \0

顺便说一句,虽然不能用关系运算符来比较字符串,但却可以用它们来比较字符,因为字符实际上是整型。因此下面的代码可以用来显示字母表中的字符,至少对于ASCII字符集和Unicode字符集来说是有效的:

for (ch = ‘a’; ch <= ‘z’; ch++)
	cout << ch;

程序清单5.11在for循环的测试条件中使用了strcmp()。该程序显示一个单词,修改其首字母,然后再次显示这个单词,这样循环往复,直到strcmp()确定该单词与字符串“mate”相同为止。注意,该程序清单包含了文件cstring,因为它提供了strcmp()的函数原型。

程序清单5.11 compstr1.cpp

//compstr1.cpp -- comparing strings using arrays
#include<iostream>
#include<cstring>		//prototype for strcmp()
int main()
{
	using namespace std;
	char word[5] = "?ate";
	for (char ch = 'a'; strcmp(word, "mate"); ch++)
	{
		cout << word << endl;
		word[0] = ch;
	}
	cout << "After loop ends, word is " << word << endl;
	return 0;
}

下面是该程序的输出:
?ate
aate
bate
cate
date
eate
fate
gate
hate
iate
jate
kate
late
After loop ends, word is mate

程序说明

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

strcmp(word, “mate”) != 0		//strings are not the same

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

检测相等或排列顺序:

可以使用strcmp()来测试C-风格字符串是否相等(排列顺序)。如果str1和str2相等,则下面的表达式为true:
strcmp(str1, str2) == 0
如果str1和str2不相等,则下面两个表达式都为true:
strcmp(str1, str2) != 0
strcmp(str1, str2)
如果str1在str2的前面,则下面的表达式为true:
strcmp(str1, str2) < 0
如果str1在str2的后面,则下面的表达式为true:
strcmp(str1, str2) > 0
因此,根据要如何设置测试条件,strcmp()可以扮演==、!=、<和>运算符的角色。

接下来,compstr1.cpp使用递增运算符使变量ch遍历字母表:

ch++

可以对字符变量使用递增运算符和递减运算符,因为char类型实际上是整型,因此这种操作实际上将修改存储在变量中的整数编码。另外,使用数组索引可使修改字符串中的字符更为简单:

word[0] = ch;

5.1.15 比较string类字符串

如果使用string类字符串而不是C-风格字符串,比较起来将简单些,因为类设计让您能够使用关系运算符进行比较。这之所以可行,是因为类函数重载(重新定义)了这些运算符。第12章将介绍如何将这种特性加入到类设计中,但从应用的角度说,读者现在只需直到可以将关系运算符用于string对象即可。程序清单5.12是在程序清单5.11的基础上修改而成的,它使用的是string对象而不是char数组。

程序清单5.12 compstr2.cpp

//compstr2.cpp -- comparing strings using arrays
#include<iostream>
#include<string>		//string class
int main()
{
	using namespace std;
	string word = "?ate";
	for (char ch = 'a'; word != "mate"; ch++)
	{
		cout << word << endl;
		word[0] = ch;
	}
	cout << "After loop ends, word is " << word << endl;
	return 0;
}

该程序的输出与程序清单5.11相同。

程序说明

在程序清单5.12中,下面的测试条件使用了一个关系运算符,该运算符的左边是一个string对象,右边是一个C-风格字符串:

word != “mate”

string类重载运算符!=的方式让您能够在下述条件下使用它:至少有一个操作数为string对象,另一个操作数可以是string对象,也可以是C-风格字符串。

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++PrimerPlus 第五章 循环和关系表达式 - 5.1 for循环 的相关文章

  • 以编程方式在网格视图列上显示数据

    我有一个产品数量列表和一个网格视图 网格视图已经绑定到一些数据 但我想在网格视图的第三列显示产品数量列表 以下是如何将数据绑定到网格视图的代码 gvProduct DataSource distSPUItem gvProduct DataB
  • 在派生自 Control 的 ASP.NET 自定义控件中呈现自关闭标记

    我正在开发 Facebook FBML 控件库 并希望创建类似于 ASP NET WebControls 库的 FBML 控件 我有一个默认处理渲染的基类 这是我的渲染方法 protected override void Render Ht
  • 强制 const 存储返回的值 value

    这就是我想要实现的目标 struct test const test returnconst return test test returnnonconst return test int main test t1 returnnoncon
  • 递归显式模板实例化以导出库的符号

    在我之前的问题中我问过递归显式模板实例化是否可能 https stackoverflow com questions 7395113 is recursive explicit template instantiation possible
  • ctypes.ArgumentError:不知道如何转换参数

    我在C库中定义了一个函数 如下所示 int Test char str1 int id1 char str2 float val float ls 我想在Python中使用它 所以我编写了以下Python代码 str1 a str2 b i
  • iPhone 编程游戏

    使用 Objective C 还是 C 为 iPhone 编写游戏最好 像 Flight Control 这样的游戏会用什么语言编写 图形应采用什么格式才能在 iPhone 上正确显示并快速加载 像 Flight Control 这样的游戏
  • C# 从整数反序列化枚举

    是否可以从 C 中的 int 反序列化枚举 例如如果我有以下课程 class Employee public string Name get set public int EmployeeTypeID get set 我可以轻松地从 XML
  • C# 委托实例化与仅传递方法引用 [重复]

    这个问题在这里已经有答案了 我有一个简单的问题 与仅传递函数引用相比 实例化 C 委托有什么优势 我的意思是 Why do Thread t new Thread new ThreadStart SomeObject SomeMethod
  • 终止以 System.Diagnostic.Process.Start("FileName") 启动的进程

    我正在尝试创建一个将在特定时间执行操作的应用程序 很像 Windows 任务计划程序 我当前正在使用 Process Start 来启动任务所需的文件 或 exe 我通过调用文件 mp3 启动一个进程 该进程启动 WMP 因为它是默认应用程
  • flowlayoutpanel和水平滚动条问题

    我正在使用一个 flowlayoutpanel 它有很多逻辑上的按钮 我遇到的问题是 当我调整窗口大小时 当窗口变小时 我无法看到所有水平排列的按钮 相反 当窗口变小时 按钮会下降到下一行 谁能帮我解决这个问题 我只是希望按钮水平排列 当窗
  • 在 C++ 中从另一个数组初始化结构内的数组[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions struc
  • 从动态服务器中抓取 html 列表数据

    哈喽大家好 抱歉提出转储问题 这是我最后的手段 我发誓我尝试了无数其他 Stackoverflow 问题 不同的框架等 但这些似乎没有帮助 我有以下问题 一个网站显示一个数据列表 前面有大量的 div li span 等标签 它是一个很大的
  • DLR 返回类型

    我需要 DLR 的帮助 我正在实现 IDynamicMetaObjectProvider 和 DynamicMetaObject 但在获取预期返回类型时遇到一些问题 我在元对象中覆盖 BindInvokeMember 我可以看到所有参数类型
  • 将控制台输入和输出重定向到文本框

    您好 提前致谢 我正在尝试 非常努力 将控制台输入和输出重定向到文本框 到目前为止 输出工作正常 但问题在于输入 例如 我无法执行一个简单的程序来执行以下操作 Console WriteLine 请输入您的姓名 字符串名称 Console
  • OpenCV:处理每一帧

    我想使用 OpenCV 编写一个跨平台应用程序进行视频捕获 在所有示例中 我发现来自相机的帧是使用抓取功能进行处理并等待一段时间 我想处理序列中的每一帧 我想定义自己的回调函数 每次当一个新帧准备好处理时都会执行该函数 例如直播对于 Win
  • 为什么泛型 IList<> 不继承非泛型 IList

    IList
  • 如何从 MongoDB 中的 ChangeStream 过滤对特定字段的更新

    我正在设置一个 ChangeStream 以便在集合中的文档发生更改时通知我 以便我可以将该文档的 LastModified 元素更新插入到事件发生的时间 由于此更新将导致 ChangeStream 上发生新事件 因此我需要过滤掉这些更新以
  • 所有区域的启用保护模式必须设置为相同的值(启用或禁用)

    我正在尝试使用Selenium Internet Explorer驱动程序 但当我尝试实例化它时它崩溃了 TestInitialize public void TestInitialise ieDriver new InternetExpl
  • 为 winforms ComboBox 中的单个项目着色?

    我遇到了一个困境 我有一个表单 其中包含许多组合框 其中包含在某些情况下可能无效 过时的信息 选项 项目 我不能简单地从项目中删除过时的信息 但我确实想在选项无效时为用户提供视觉线索 我正在考虑对项目进行着色 可能是红色 来指示它们是否无效
  • Xamarin Forms:System.Reflection.TargetInitationException:调用目标已引发异常

    我正在为这个问题苦苦挣扎 我只是创建了一个简单的跨平台页面 这里是 XAML 代码

随机推荐

  • 【数学建模】青少年犯罪问题

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 一 逐步回归分析法 1 1 逐步回归分析定义 最优回归方程 1 2 stepwise函数介绍 二 例题 青少年犯罪问题 2 1 题目简述 2 2 问题一建模与求解
  • 定制化UINavigationBar

    FirstViewController 定制化UINavigationBar void customNavigationBar 按钮被挡住 原因是 navigationbar的透明度默认为YES 先取出navigationbar UINav
  • java中countinue,.random(用于产生一个随机数)入门可看

    1 countinue 用在循环中 基于条件控制 跳过其次循环体内容的执行 继续下一次的执行 break 用在循环中 基于条件控制 终止循环体内容的执行 也就是结束当前的整个循环 eg public class zhou public st
  • php提取css生成单独文件,webapck将css 打包后单独提取到一个css文件中

    webpack4 提倡 一旦用了这个 不能使用style loader 以及css module 安装 npm install save dev mini css extract plugin rules rules test css us
  • SQLServer 中实现类似MySQL中的group_concat()函数的功能

    SQLServer中没有MySQL中的group concat函数 可以把分组的数据连接在一起 后在网上查找 找到了可以实现此功能的方法 特此记录下 SELECT a stuff SELECT b FROM tb WHERE a t a F
  • STM32 USB_SPI_FLASH 简易U盘设计

    这是大三下学期EDA实训的内容 设计制作了一个简易的U盘 主要涉及STM32单片机 W25Q128闪存芯片 电路板绘制 文件系统移植等内容 发布出来供大家参考 仅供学习交流使用 实验步骤仅供参考 转载请注明原文链接 https blog c
  • java数据传到前端页面_02 前端页面数据传递到后台java

    本文将阐述如何将前端页面数据传递到后台java代码 1 环境约束 win10 64位操作系统 idea2018 1 5 jdk 8u162 windows x64 spring4 2 4 前提约束 操作步骤 基本类型传输 RequestMa
  • Android使用AES加解密

    AES为对称加密 加解密用一套秘钥 秘钥自己随便定义 长度必须为16 或 32 个字符 1 AES工具类 AES 对称加密算法 加解密工具类 RequiresApi api Build VERSION CODES KITKAT public
  • getline()与get()

    1 getline getline 函数读取整行 通过换行符来确定输入结尾 然后丢弃换行符 1 作为istream中的类方法 char name 20 cin getline name 20 把一行读入到name数组中 这行字符不超过19个
  • python opencv 同窗口显示多个图像

    为了便于比对图像处理前后的效果 特别是算法处理前后的效果图 我们需要同时显示多张图片 这里采用opencv图像拼接的方法来实现我们想要的效果 1 定义函数show multi img 定义图片显示函数show multi img 共包括5参
  • Android:Serializable和Parcelable的持久化保存

    转自 https www cnblogs com duoshou articles 8856111 html 前言 Serializable和Parcelable能够将对象序列化 使之能够在一些特殊场景中进行传输 再进行数据的恢复 Seri
  • uhd驱动安装(usrpB210,usrpX310等)

    sdr技术与设备介绍 参考博文地址 上述博文介绍了什么是SDR技术 以及sdr设备的分类应用 问题描述 使用sdr设备 usrpx系列 usrpb系列 的准备 驱动 镜像 方案一 推荐方案 资源下载地址 一个UHD源码 一个UHD对应的镜像
  • 华灯初上

    华灯初上老城楼 将军夜赏新兜鍪 宝剑久藏不曾试 兵书战策俱成空
  • 大数据技术原理——期末复习spark

    1 Spark的基本概念 RDD DAG Executor 应用 任务 作业 阶段 RDD 是Resillient Distributed Dataset 弹性分布式数据集 的简称 是分布式内存的一个抽象概念 提供了一种高度受限的共享内存模
  • Aspose最版本aspose-words:jdk17:23.6 版本,代码分析心得

    Aspose最版本aspose words jdk17 23 6 版本 代码分析心得 aspose 为收费软件 以下仅仅用于学习技术 请勿做任何商业用途 如果需要请到官网购买正版 本文的逆向分析参考了了博客 https blog csdn
  • CAP定理含义

    分布式系统 Distributed System 最大的难点就是各个节点的状态如何同步 CAP定理就是这方面最基本的定理 也是理解分布式的起点 同时也是 NoSql数据库的基石 一 分布式的三个指标 1 Consistency k n s
  • STM32学习笔记——HC05

    STM32 HAL库 CubeMX串口通讯HC 05 前言 软件及硬件准备 硬件连接 蓝牙模块调试 CubeMX配置 keil软件编写 实验 注意事项 源码及注意事项2023 3 11 前言 我所使用的开发板为STM32F405RG 蓝牙模
  • C++学习(四六二)Parsing of CMake project failed: Connection to CMake server lost.

    环境 Qt5 12 cmake3 12 64位 工程为 原因分析 可能是版本不一致 qt工程为32位 cmake是64位的 将cmake换3 1 32位 就可以了
  • UniCode 下char*转CString ,利用MultiByteToWideChar进行转换,中文乱码的解决方案

    使用A2W A2T这些宏函数是常见的方法 但是中文会乱码 所以采用MultiByteToWideChar进行转换 计算char 数组大小 以字节为单位 一个汉字占两个字节 int charLen strlen sText 计算多字节字符的大
  • C++PrimerPlus 第五章 循环和关系表达式 - 5.1 for循环

    C PrimerPlus 第五章 循环和关系表达式 5 1 for循环 5 1 for循环 5 1 1 for循环的组成部分 5 1 1 1 表达式和语句 5 1 1 2 非表达式和语句 5 1 1 3 修改规则 5 1 2 回到for循环