第3章 表达式和交互 主要内容: 3.1 cin对象 3.2 数学表达式 3.3 数据类型转换和类型强制转换 3.4 溢出和下溢 3.5 命名常量 3.6 多变量和组合赋值 3.7 格式化输出 3.8 处理字符和字符串 3.9 更多数学库函数 3.10 随机数字 3.11 关于调试:手动跟踪程序 3.12 Green Fields Landscaping案例研究——第1部分 3.13 综合演练:单词游戏 3.1 cin 对 象 核心概念:cin可以用于读取键盘输入的数据。 到目前为止,本书已经介绍了内置信息的程序的编写方法。可以使用必要的起始值初始化变量,而无须让用户输入自己的数据。这些类型的程序仅限于使用一组启动信息执行任务。如果决定要更改任何变量的初始值,则必须对该程序进行修改并重新编译。 实际上,大多数程序都需要获得某些值,然后将它们分配给变量。这意味着即使用户希望使用不同的信息来多次运行程序,也不需要对它进行修改。例如,计算圆面积的程序可能会要求用户输入圆的半径。当完成圆面积的计算和打印时,程序可以再次运行,并且可以输入不同的半径。 正如C++提供了cout对象来产生控制台输出一样,它还提供了一个名为cin的对象用于读取控制台的输入。可以将cin这个词看作是来源于console input(控制台输入)。程序3-1演示了如何使用cin来读取用户输入的值。请注意,在第2行中有一个包含iostream文件的#include语句。该文件必须包含在使用cin的任何程序中。 程序3-1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // This program calculates and displays the area of a rectangle. #include using namespace std; int main() { int length, width, area; cout << "This program calculates the area of a rectangle.\n"; // Have the user input the rectangle's length and width cout << "What is the length of the rectangle? "; cin >> length; cout << "What is the width of the rectangle? "; cin >> width; // Compute and display the area area = length * width; cout << "The area of the rectangle is " << area << endl; return 0; } 程序输出结果和以粗体显示的输入示例 This program calculates the area of a rectangle. What is the length of the rectangle? 10【按回车键】 What is the width of the rectangle? 20【按回车键】 The area of the rectangle is 200. 该程序并不仅限于计算一个矩形的面积,而是可以用于计算任何矩形的面积。length和width变量中存储的值,在程序运行时由用户输入。请看第12行和第13行。 cout << "What is the length of the rectangle? "; cin >> length; 在第12行中,cout用于显示问题“What is the length of the rectangle?”,这称为提示(Prompt)。它让用户明白这里需要输入,并提示必须输入什么信息。当使用cin来从用户获取输入时,它应该始终在一个提示之前。 第13行使用cin从键盘读取一个值。>>符号是流提取运算符(Stream Extraction Operator),它从输入流中提取(Extract)字符,从而可以在程序中使用。更具体地说,流提取运算符从左侧的流对象获取字符,并将其存储在其右侧出现的变量中。在该行示例中,由cin读取来自cin对象的字符并将它存储到length变量中。 从用户收集输入通常有两个步骤: (1)使用cout在屏幕上显示提示。 (2)使用cin从键盘读取值。 提示应该向用户提出一个问题,或者告诉用户输入一个特定的值。例如,程序3-1的代码显示了以下提示: What is the length of the rectangle? 这就是告诉用户应输入矩形的长度。显示提示之后,程序使用cin从键盘读取值并将其存储在length变量中。 请注意,<<和>>运算符看起来像是在指示数据流动的方向。将它们视为箭头将有助于理解。在使用cout的语句中,<<运算符总是指向cout,如下所示,表示数据是从变量或常数流动到cout对象: cout << "What is the length of the rectangle? "; cout ← "What is the length of the rectangle? "; 在使用cin的语句中,>>运算符总是指向接收值的变量,表示数据是从cin对象流动到变量: cin >> length; cin → length; cin对象导致程序等待,直到在键盘上输入数据按回车键。在cin接收到输入之前,不会有其他的行被执行。 当用户从键盘输入字符时,它们暂时放置在称为输入缓冲区(Input Buffer)或键盘缓冲区(Keyboard Buffer)的内存区域中。当cin读取它们时,会自动将它们转换为要存储输入数据的变量的数据类型。例如,如果用户键入10,它将被读取为字符'1'和'0',但是cin足够聪明,知道在存储到length变量之前必须将其转换为int值10。但是,如果用户输入像10.7这样的浮点数,则会出现问题。cin知道这样的值不能存储在整数变量中,所以当它读到小数点时即停止读取,在输入缓冲区中保留小数点和其余数字。当下一个值被读入时,这可能会导致出现问题。程序3-2演示了这个问题。 程序3-2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // This program illustrates what can happen when a // floating-point number is entered for an integer variable. #include using namespace std; int main() { int intNumber; double floatNumber; cout << "Input a number. "; cin >> intNumber; cout << "Input a second number.\n"; cin >> floatNumber; cout << "You entered: " << intNumber << " and " << floatNumber << endl; return 0; } 程序输出结果和以粗体显示的输入示例 Input a number. 12.3【按回车键】 Input a second number. You entered: 12 and 0.3 现在来看一看程序3-2中发生的情况。当提示输入第一个数字时,用户从键盘输入12.3。但是,因为cin正在读取的值将存入intNumber这个整型变量中,所以,当它遇到小数点,就会停止读取,并且将12存储到intNumber变量中。当第二个cin语句需要读取一个值存入floatNumber变量中时,它发现在输入缓冲区中已经有一个值,也就是从用户第一次输入中遗留下来的“.3”。于是就不必等待用户输入第二个数字,而是读入“.3”并将它存储到floatNumber变量中。 后面将介绍如何防止这样的事情发生,在此只是为了说明,需要为用户提供清晰的提示。如果在用户第一次输入时特意提示必须输入整数,那么发生问题的几率就会小得多。 注意:在任何使用cout或cin的程序中都别忘记加入iostream文件。 3.1.1 输入多个值 可以使用cin一次输入多个值。程序3-3是程序3-1的修改版本,它演示了该方法。 程序3-3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // This program calculates and displays the area of a rectangle. #include using namespace std; int main() { int length, width, area; cout << "This program calculates the area of a rectangle.\n"; // Have the user input the rectangle's length and width cout << "Enter the length and width of the rectangle "; cout << "separated by a space.\n"; cin >> length >> width; // Compute and display the area area = length * width; cout << "The area of the rectangle is " << area << endl; return 0; } 程序输出结果和以粗体显示的输入示例 This program calculates the area of a rectangle. Enter the length and width of the rectangle separated by a space. 10 20【按回车键】 The area of the rectangle is 200 第14行等待用户输入两个值。第一个被赋值给length,第二个则赋值给width: cin >> length >> width; 在示例输出中,用户输入10和20,因此10存储在length中,而20存储在width中。 注意,用户在输入数字时要用空格分隔数字。这样cin才能知道每个数字的开始和结束位置。在每个数字之间输入多少空格并不重要。例如,用户可以输入: 10 20 注意:在最后一个数字输入之后,必须按回车键。 还可以使用单个cin语句读取不同数据类型的多个值。程序3-4即显示了这种用法。 程序3-4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // This program demonstrates how cin can read multiple values // of different data types. #include using namespace std; int main() { int whole; double fractional; char letter; cout << "Enter an integer, a double, and a character: "; cin >> whole >> fractional >> letter; cout << "whole: " << whole << endl; cout << "fractional: " << fractional << endl; cout << "letter: " << letter << endl; return 0; } 程序输出结果和以粗体显示的输入示例 Enter an integer, a double, and a character: 4 5.7 b【按回车键】 whole: 4 fractional: 5.7 letter: b 在上述示例输出和图3-1中均可见,这些值将按照输入的顺序存储到相应的变量中。 图3-1 输入的值将按顺序存储到相应的变量中 但是如果用户像以下示例一样,以错误的顺序输入了值,那么结果会怎么样呢? 程序输出结果和以粗体显示的输入示例 Enter an integer, a double, and a character: 5.7 4 b【按回车键】 whole: 5 fractional: 0.7 letter: 4 因为没有按照指定的顺序输入数据,所以每个变量存储的值被完全打乱了。图3-2已清楚说明究竟发生了什么。 图3-2 未按顺序输入值将造成变量存储的值完全乱套 第13行的cin语句读取5存入int类型的whole变量,读取“.7”存入double类型的fractional变量,读取4存入char类型的letter变量。字符b则遗留在输入缓冲区中。所以,为了使程序正常工作,用户按照程序期望接收的顺序输入数据值是非常重要的,并且在需要整数时不能输入浮点数。 3.1.2 思考题 3.1 在使用cin的程序中必须包含什么头文件? 3.2 符号>>被称为什么? 3.3 cin从哪里读取其输入? 3.4 判断题:cin要求用户在输入数据后按回车键。 3.5 假设value是一个整型变量。如果用户输入3.14以响应以下编程语句,那么value中存储的将是什么值? cin >> value; 3.6 某程序具有以下变量定义。 long miles; int feet; double inches; 请编写一个cin语句,读取值存入以上每个变量。 3.7 以下程序将运行,但用户难以理解该做什么。请问该如何改进此程序? //该程序将两个数字相乘并显示结果 #include using namespace std; int main() { double first, second, product; cin >> first >> second; product = first * second; cout << product; return 0; } 3.8 完成以下main函数,以便它能询问用户的体重(以磅为单位),并显示相应的以千克为单位的体重。 int main() { double pounds, kilograms; // 编写提示让用户输入他/她的体重 // 以磅为单位 // 在此编写代码以读取用户输入的以磅为单位的体重值 // 以下行将进行体重单位的转换 kilograms = pounds / 2.2; // 在此编写代码显示用户的体重(以公斤为单位) return 0; } 3.2 数学表达式 核心概念:C++允许使用多个运算符和分组符号来构造复杂的数学表达式。 在第2章中介绍了基本的数学运算符,用于构建数学表达式。表达式(Expression)是一个可以通过评估或计算产生一个值的语句。在编程中,数学表达式通常由运算符及其操作数组成。请看以下语句: sum = 21 + 3; 由于21 + 3可以通过计算产生一个值,所以它是一个表达式。其值24将被存储在变量sum中。但是,表达式并不一定包含数学运算符。在下面的语句中,3也是一个表达式,它可以被评估为单个值3: number = 3; 以下是一些其他类型的赋值语句,变量result被赋予了表达式的值: result = x; result = 4; result = 15 / 3; result = 22 * number; result = sizeof(int); result = a + b + c; 在上述每个语句中,数字、变量名或数学表达式均出现在=符号的右侧。从这些表达式中获得的值将存储在变量result中。这些都是变量被赋予表达式值的示例。 虽然有些教师建议不要在cout语句中执行数学运算,但实际上这样做也是可行的。程序3-5说明了如何做到这一点。 程序3-5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // This program displays the decimal value of a fraction. #include using namespace std; int main() { double numerator, denominator; cout << "This program shows the decimal value of a fraction.\n"; // Have the user enter the numerator and denominator cout << "Enter the numerator: "; cin >> numerator; cout << "Enter the denominator: "; cin >> denominator; //Compute and display the decimal value cout << "The decimal value is " << (numerator / denominator) << endl; return 0; } 程序输出结果和以粗体显示的输入示例 This program shows the decimal value of a fraction. Enter the numerator: 3【按回车键】 Enter the denominator: 16【按回车键】 The decimal value is 0.1875 cout对象可以显示C++中任何合法表达式的值。在程序3-5中,显示的就是表达式numerator / denominator的值。 注意:程序3-5输入示例显示用户输入的是3和16。由于这些值是被分配给double变量的,所以它们被存储为3.0和16.0。 注意:当发送一个包含运算符的表达式到cout时,使用括号把表达式括起来始终是一个好主意,否则有些运算符可能会产生意想不到的结果。 3.2.1 运算符的优先级 可以用多个运算符来构建数学表达式。以下语句将计算17、x、21和y的总和,然后将它赋给变量answer: answer = 17 + x + 21 + y; 但是,有些表达式就没那么简单。例如以下语句: outcome = 12 + 6/3; outcome中将会存储什么值?如果加法先算,结果是6;如果除法先算,结果是14。答案是14,因为除法运算符的优先级(Precedence)高于加法运算符。这与代数中的运算符优先级完全相同。 数学表达式从左到右进行评估。但是,当有两个运算符并且其中一个运算符的优先级高于另外一个时,则优先级高的先算。由于乘法和除法的优先级高于加法和减法,因此示例语句的工作原理如下: ? 首先,将6除以3,得到结果为2。 ? 然后,将12加2,得到结果为14。 ? 最后,14被存储在outcome变量中。 这些步骤可以用以下方式图示: outcome = 12 + 6 / 3 outcome = 12 + 2 outcome = 14 算术运算符的优先级如表3-1所示。表中运算符的优先级从上到下按高低排序。 表3-1 算术运算符的优先级(最高到最低) ( ) 括号内的表达式先计算和评估 – 一元 负值,例如 –6 * / % 二元 乘法、除法和求余数 + – 二元 加法和减法 乘法、除法和求余数运算符具有相同的优先级。加法和减法运算符也是如此。表3-2显示了一些表达式及其值的示例。 表3-2 表达式示例 表达式 值 5 + 2 * 4 13 10 / 2 – 3 2 8 + 12 * 2 – 4 28 4 + 17 % 2 – 1 4 6 – 3 * 2 + 7 – 1 6 3.2.2 关联性 关联性是运算符处理其操作数的顺序。关联性可以是从左到右(Left to Right),也可以是从右到左(Right to Left)。除法运算符的关联性是从左到右的,所以它将左边的操作数除以右边的操作数。算术运算符及其关联性如表3-3所示。 表3-3 算术运算符的关联性 运算符 关联性 (一元) – 从右到左 * / % 从左到右 + – 从左到右 3.2.3 用圆括号分组 数学表达式的部分可以用括号分组,以迫使某些运算在其他运算之前执行。当遇到一对括号时,括号内的表达式将在其之外的任何表达式之前进行评估。因此,在下面的语句中,首先评估 a + b,然后将它们的和除以4: average =(a + b)/ 4; 在没有括号的情况下,b将在添加到结果之前除以4,因为除法运算符的优先级高于加法运算符。表3-4显示了更多的此类表达式及其值。 表3-4 使用圆括号分组的表达式示例 表达式 值 (5 + 2) * 4 28 10 / (5 – 3) 5 8 + 12 * (6 – 2) 56 (4 + 17) % 2 – 1 0 (6 – 3) * (2 + 7) / 3 9 3.2.4 将代数表达式转换为编程语句 在代数中,乘法并不总是需要使用运算符。但是,C++的任何数学运算都需要运算符。表3-5显示了一些执行乘法运算的代数表达式以及等效的C++表达式。 表3-5 代数和C++乘法表达式 代数表达式 运??算 C++等效表达式 6B 6乘以B 6 * B (3)(12) 3乘以12 3 * 12 4xy 4乘以x乘以y 4 * x * y 将某些代数表达式转换为C++表达式时,可能需要插入未出现在代数表达式中的括号。例如,来看以下表达式: 要将其转换为C++语句,则a+ b必须包含在括号中,这样才能先求和再除以c。 x =(a + b)/ c; 表3-6显示了更多的代数表达式及其C++等价表达式。 表3-6 代数和C++表达式 代数表达式 C++表达式 y = 3x/2 y = x / 2 * 3 z = 3bc + 4 z = 3 *b * c + 4 a = 3x + 2/4a – 1 a = (3 * x + 2) / (4 * a – 1) 3.2.5 指数问题详解 与许多编程语言不同的是,C++没有指数运算符。计算数字的幂需要使用库函数(Library Function)。C++库包含一系列专门的函数,可以将库函数视为执行特定操作的“例程”。其中一个库函数叫作pow,其目的就是计算数字的幂。以下是其用法示例: area = pow(4.0,2); 该语句包含对pow函数的调用(call)。括号内的数字是实参(argument)。实参是发送到函数的信息。pow函数总是以第一个参数为底数,第二个参数为指数。在本示例中,4.0是底数,2是指数。结果从该函数返回(Return),并在调用了该函数的语句中使用。pow函数需要浮点参数。在某些C++编译器上,整数参数也可以工作,但是许多编译器要求至少第一个参数是double类型的,本书也遵守此约定。从该函数返回的值始终是double类型数字。在这种情况下,pow将返回16.0并赋值给变量area,如图3-3所示。 图3-3 pow函数的返回结果 语句area = pow(4.0, 2)与以下代数表达式是等效的: Area = 42 以下是另外一个使用pow函数的示例,它将3×63的结果赋值给x: x = 3 * pow(6.0, 3); 以下语句显示了底数为5、指数为4的幂值: cout << pow(5.0, 4); 可以将pow函数视为一个“黑箱”,它接收2个数字,然后发送出第3个数字。发出来的数字是以第一个数字为底数、第二个数字为指数的幂值,如图3-4所示。 图3-4 可以将pow函数理解为一个“黑箱” 使用pow函数时应遵循一些指导原则。首先,程序必须包含cmath头文件。其次,在传递给函数的两个参数中,至少第一个参数应该是double类型的,当然也可以两个都是。第三,因为pow函数返回一个double值,所以被赋值的任何变量也应该是double类型的。例如,在下面的语句中,变量area应该定义为double类型: area = pow(4.0,2); 程序3-6解决了一个简单的代数问题。它要求用户输入圆的半径,然后计算圆的面积。该公式如下。 area = π r 2 这在程序中表示为: area = 3.14159 * pow(radius, 2); 程序3-6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // This program calculates the area of a circle. The formula for the // area of a circle is PI times the radius squared. PI is 3.14159. #include #include // Needed for the pow function using namespace std; int main() { double area, radius; cout << "This program calculates the area of a circle.\n"; // Get the radius cout << "What is the radius of the circle? "; cin >> radius; // Compute and display the area area = 3.14159 * pow(radius, 2); cout << "The area is " << area << endl; return 0; } 程序输出结果和以粗体显示的输入示例 This program calculates the area of a circle. What is the radius of the circle? 10【按回车键】 The area is 314.159 注意:程序3-6仅作为pow函数的用法演示。实际上,没有理由在这样简单的操作中使用这个函数。第18行可以很简单地写作: area = 3.14159 * radius * radius; 当然,pow函数在涉及较大指数的运算中是很有用的。 3.2.6 思考题 3.9 请指出以下每组运算符谁的优先级更高或者相同。 A)+ 和 * B)* 和 / C)/ 和 % 3.10 请在“值”列中填写“表达式”列中对应表达式的值。 表达式 值 A)6 + 3 * 5 B)12 / 2 – 4 C)9 + 14 * 2 – 6 D)5 + 19 % 3 – 1 E)(6 + 2) * 3 F)14 / (11 – 4) G)9 + 12 * (8 – 3) H)(6 + 17) % 2 – 1 I)(9 – 3) * (6 + 9) / 3 3.11 请编写与以下代数表达式相对应的C++表达式。 ?????????????? y = 6x a = 2b + 4c ?????????????? y = x3 g = ?????????????? y = 3.12 请仔细阅读以下代码,然后完成下表。 double value1, value2, value3; cout << "Enter a number: "; cin >> value1; value2 = 2 * pow(value1, 2); value3 = 3 + value2 / 2 - 1; cout << value3; 如果用户输入… 程序将显示什么数字? (存储在value3中) 2 5 4.3 6 3.13 完成以下程序框架,以显示圆柱形油箱的体积。圆柱体的体积公式如下。 Volume = πr2h 其中: ? π是3.14159 ? r是油箱的半径 ? h是油箱的高度 #include #include int main() { double volume, radius, height; cout << "This program will tell you the volume of\n"; cout << "a cylinder-shaped fuel tank.\n"; cout << "How tall is the tank? "; cin >> height; cout << "What is the radius of the tank? "; cin >> radius; // 请完成该程序 return 0; } 3.3 数据类型转换和类型强制转换 核心概念:有时需要将值从一种数据类型转换为另一种数据类型。C++提供了这样做的方法。 如果将一个浮点值分配给一个int整型变量,该变量会接收什么值?如果一个int整数乘以一个float浮点数,结果将会是什么数据类型?如果一个double浮点数除以一个unsigned int无符号整数会怎么样?是否有办法预测在这些情况下会发生什么?答案是肯定的。当运算符的操作数具有不同的数据类型时,C++会自动将它们转换为相同的数据类型。当它这样做时,它遵循一组规则。理解这些规则将有助于程序员防止一些细微的错误蔓延到自己的程序中。 就像军队的军官有军阶一样,数据类型也可以按等级排名。如果一个数字数据类型可以容纳的数字大于另一个数据类型,那么它的排名就高于后者。例如,float类型就超越了int类型,而double类型又超越了float类型。表3-7列出了从高到低排列的数据类型。 表3-7 数据类型排名 long double double float unsigned long long int long long int unsigned long int long int unsigned int int 表3-7中排名的一个例外是当int和long int的大小相同时。在这种情况下,unsigned int将超越long int,因为它可以保存更高的值。 当C++使用运算符时,它会努力将操作数转换为相同的类型。这种隐式或自动的转换称为类型强制(Type Coercion)。当一个值被转换为更高的数据类型时,称之为升级(Promote)。反之,降级(Demote)则意味着将其转换为更低的数据类型。现在来看一看管理数学表达式评估的具体规则。 规则1:char、short和unsigned short值自动升级为int值。 细心的读者可能已经注意到,char、short和unsigned short都未出现在表3-7中。这是因为,无论何时在数学表达式中使用这些数据类型的值,它们都将自动升级为int类型 。 规则2:当运算符使用不同数据类型的两个值时,较低排名的值将被升级为较高排名值的类型。 在下面的表达式中,假设years是一个int变量,而interestRate是一个double变量: years * interestRate 在乘法发生之前,years中的值将升级为double类型。 规则3:当表达式的最终值分配给变量时,它将被转换为该变量的数据类型。 在下面的语句中,假设area是一个long int长整型变量,而length和width都是int整型变量: area = length * width; 因为存储在length和width中的值是相同的数据类型,所以它们都不会被转换为任何其他数据类型。但是,乘法的结果将被升级为long int类型,这样才可以存储到area中。 但是,如果接收值的变量的数据类型低于接收的值,那该怎么办呢?在这种情况下,值将被降级为变量的类型。如果变量的数据类型没有足够的存储空间来保存该值,则该值的一部分将丢失,并且该变量可能会收到不准确的结果。如第2章所述,如果接收值的变量想要的是一个整数,而赋给它的值是一个浮点数,那么当转换为int并存储在变量中时,浮点值将被截断(Truncate)。这意味着小数点后的所有内容都将被丢弃。示例如下。 int x; double y = 3.75; x = y; // x 被赋值为3,y 仍然保留 3.75 但是,重要的是要了解,当变量值的数据类型更改时,它不会影响变量本身。例如,来看下面的代码段。 int quantity1 = 6; double quantity2 = 3.7; double total; total = quantity1 + quantity2; 在C++执行上述加法之前,它会将一个quantity1值的副本移动到其工作空间中,并将其转换为double类型。然后把6.0和3.7相加,并且将结果值9.7存储到total中。但是,变量quantity1保持为int,存储在存储器中的值保持不变,它仍然是整数6。 3.3.1 类型强制转换 有时程序员想要自己更改值的数据类型,这可以通过使用类型强制转换表达式(Type Cast Expression)来完成。类型强制转换表达式允许手动升级或降级值。它的一般格式如下。 static_cast(Value) 其中Value是要转换的变量或文字值,DataType是要转换的目标数据类型。以下是使用类型转换表达式的代码示例: double number = 3.7; int val; val = static_cast(number); 上述代码定义了两个变量:double类型的number和int类型的val。第3个语句中的类型转换表达式返回number中的值的副本,转换为int。当double或float类型的值转换为int时,小数部分被截断,因此该语句将3存储在val中。而number的值仍为3.7,保持不变。 类型转换表达式在C++不能自动执行所需转换的情况下很有用。 程序3-7显示了使用类型强制转换表达式来防止发生整除法的示例。使用类型强制转换表达式的语句是: booksPerMonth = static_cast(books) / months; 程序3-7 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // This program uses a type cast to avoid an integer division. #include using namespace std; int main() { intbooks, months; double booksPerMonth; // Get user inputs cout << "How many books do you plan to read? "; cin >> books; cout << "How many months will it take you to read them? "; cin >> months; // Compute and display books read per month booksPerMonth = static_cast(books) / months; cout << "That is " << booksPerMonth << " books per month.\n"; return 0; } 程序输出结果和以粗体显示的输入示例 How many books do you plan to read? 30【按回车键】 How many months will it take you to read them? 7【按回车键】 That is 4.28571 books per month. 变量books是一个整数,但是它的值的副本在除法运算之前被转换为double类型。如果没有第18行中的类型转换表达式,则将执行整除法,导致错误的答案。 值得一提的是,如果按以下语句改写第18行,则整除法仍然会发生。 booksPerMonth = static_cast (books / months); 因为括号内的操作在其他操作之前完成,所以除法运算符将对其两个整数操作数执行整除法,books / month表达式的结果将是4,然后将4转换为double类型的值4.0,这就是将赋给booksPerMonth的值。 警告:为了防止发生整除法,在除法运算之前,其中一个操作数应该转换为一个double双精度值。这将强制C++自动将其他操作数的值也转换为双精度值。 程序3-8显示了类型强制转换的另一种用法。 程序3-8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // This program prints a character from its ASCII code. #include using namespace std; int main() { int number = 65; // Display the value of the number variable cout << number << endl; // Use a type cast to display the value of number // converted to the char data type cout << static_cast(number) << endl; return 0; } 程序输出结果 65 A 现在来仔细看一看这个程序。在第7行中,int变量number的值被初始化为值65。在第10行中,将number发送到cout,导致显示65。在第14行中,类型强制转换表达式用于将number的值转换为char数据类型,然后再将其发送到cout。在本书第2章已经介绍过,字符作为整数ASCII代码存储在内存中。因为数字65是字母A的ASCII码,所以第14行的语句会显示字母A。 注意:C++提供了若干种不同类型的强制转换表达式。static_cast是最常用的类型强制转换表达式,所以这将是在本书中主要使用的表达式。关于类型转换的其他信息,请参考本书配套站点pearsonhighered.com/gaddis提供的附录K。 C风格和预标准C++类型强制转换表达式 虽然static_cast是目前使用最多的类型强制转换表达式,但是C++还支持两种较旧的形式,这也是程序员应该有所了解的,即:C风格形式和预标准C++形式。C风格的转换将要转换的数据类型放在括号中,位于值要转换的操作数的前面。因为类型转换运算符在操作数前面,所以这种类型转换表示法被称为前缀表示法(Prefix Notation),示例如下: booksPerMonth = (double)books / months; 预标准C++形式类型强制转换表达式也是将要转换的数据类型放在其值要转换的操作数之前,但它将括号放在操作数周围,而不是围绕数据类型。这种类型转换表示法被称为功能性表示法(Functional Notation),示例如下: booksPerMonth = double(books) / months; 3.3.2 思考题 3.14 假设有以下变量定义: int a = 5, b = 12; double x = 3.4, z = 9.1; 以下表达式的值是什么? A)b / a B)x * a C)static_cast(b / a) D)static_cast(b) / a E)b / static_cast(a) F)static_cast(b) / static_cast(a) G)b / static_cast(x) H)static_cast(x) * static_cast(z) I)static_cast(x * z) J)static_cast(static_cast(x) * static_cast(z)) 3.15 当cin语句要求用户输入一个字母时输入大写字母B,那么以下程序代码将显示什么内容? char letter; cout << "The ASCII values of uppercase letters are " << static_cast('A') << " - " << static_cast('Z') << endl; cout << "The ASCII values of lowercase letters are " << static_cast('a') << " - " << static_cast('z') << endl << endl; cout << "Enter a letter and I will tell you its ASCII code: "; Cin >> letter; cout << "The ASCII code for " << letter << " is " << static_cast(letter) << endl; 3.16 以下程序代码将显示什么内容? int integer1 = 19, integer2 = 2; double doubleVal; doubleVal = integer1 / integer2; cout << doubleVal << endl; doubleVal = static_cast(integer1) / integer2; cout << doubleVal << endl; doubleVal = static_cast(integer1 / integer2); cout << doubleVal << endl; 3.4 溢出和下溢 核心概念:当变量的数据类型所提供的位数无法适应某个值时,就会发生溢出或下溢。 往水桶里装水,水满则溢,变量也是这样。如果要存储的值超过了变量所能提供的位数,就会出现问题。不妨来看一个例子。假设在一个使用了2个字节内存的short int类型变量中存储了以下值。 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 这是32 767的二进制表示,也是能存储在该数据类型中的最大值。这里先不讲负数如何存储的细节,只要知道short int数据类型既可以存储正数也可以存储负数就可以了。高阶位(High-Order bit)(即最左侧位)是0的数字被解释为正数,高阶位为1的数字则被解释为负数。如果上面示例中存储的数字加1,则该变量将变成以下位模式。 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 但这不是32 768。相反,它被解释为负数,所以这不是预期的结果。二进制1已经“流入”到高阶位的位置,这就是所谓的溢出(Overflow)。 同样地,当一个整数变量保存的数值在其数据类型负值范围的最远端(即最小负值),那么当它被减去1时,其高位中的1将变为0,结果数将被解释为正数。这是溢出的另一个例子。 除了溢出以外,浮点值还会遇到下溢(underflow)的情况。当一个值太接近于零时,就可能会发生这种问题,过小的数字需要更多数位的精度来表示它,因而无法存储在保存它的变量中。程序3-9演示了溢出和下溢。 程序3-9 1 2 3 4