首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

计算机只能对相同类型的值进行算术操作。它可以把两个整型数相加,把两个浮点数相加,但不能把一个整型数和一个浮点数直接相加。例如,表达式2 + 7.5就不能直接计算,因为2是一个整数,而7.5是一个浮点数。

要执行这种计算,惟一的方法是把一个值转换为与另一个值相同的类型。一般情况下,是把整数值转换为其相应的浮点数类型,这样,上面的表达式就等价于2.0 + 7.5。这个规则适用于C++中的混合表达式。每个二元算术运算都要求两个操作数的类型相同。如果它们的类型不同,就必须把其中一个操作数转换为另一个操作数的类型。考虑下面的语句块:

int value1=10;

long value2=25L;

float value3=30.0f;

double result= value1+ value2+ value3; // Mixed calculation

result的值应计算为不同类型的变量总和。对于每个相加操作,在进行相加之前,必须把一个操作数的类型转换为另一个操作数的类型,对操作数所进行的转换取决于一组规则,该组规则将按顺序检查表达式,直到操作可以进行为止。上面语句的执行步骤如下:

● 计算value1+value2时,先把value1转换为long类型,再计算其和。结果也是long类型,这样计算过程是10L+25L=35L。

● 下一个操作就计算35L+ value3。把前面的结果35L转换为float类型,再加到value3上。结果是float类型,所以操作过程是35.0f+30.0f=65.0f。

● 最后,把前面的结果转换为double类型,存储在result中。

只有当二元运算符的操作数的类型不同时,才能应用处理混合表达式的规则。这些规则按照应用的顺序,如下所述:

(1) 如果一个操作数的类型是long double,则另一个操作数就转换为long double类型。

(2) 如果一个操作数的类型是double,则另一个操作数就转换为double类型。

(3) 如果一个操作数的类型是float,则另一个操作数就转换为float类型。

(4) 只要int类型可以表示原操作数类型的所有值,类型为char、signed char、unsigned char、short或unsigned short的操作数就转换为int类型。否则,操作数就转换为unsigned int类型。

(5) 枚举类型转换为可包容该枚举范围的int、unsigned int、long或unsigned long中的第一个类型。

(6) 如果一个操作数的类型是unsigned long,则另一个操作数就转换为unsigned long类型。

(7) 如果一个操作数的类型是long,另一个操作数的类型是unsigned int,且类型long可表示unsigned int类型中的所有值,就把unsigned int类型的操作数转换为long类型。否则,两个操作数都转换为unsigned long类型。

(8) 如果一个操作数的类型是long,另一个操作数就转换为long类型。

前面没有介绍枚举类型,本章稍后将讨论它。这里提到这种类型,是为了列出完整的规则集。这些规则看起来相当复杂,实际上并非如此。其中一些规则表面看起来比较复杂,是因为整数类型的取值范围是彼此相关的,因而规则需要容纳它们。编译器将对代码按顺序检查这些规则,直到找出可应用的规则为止。如果在应用该规则后,操作数的类型相同,就执行操作。否则就应用另一个规则。

基本理念是非常简单的。如果两个操作数的类型不同,就把取值范围较窄的那个值的类型转换为另一个值的类型。正式的规则如下:

(1) 如果操作涉及到两个不同的浮点数类型,就把精度较低的浮点数提升为另一个浮点数的类型。

(2) 如果操作涉及到一个整数和一个浮点数,就把整数提升为浮点数类型。

(3) 如果操作涉及到混合的整数类型,就把取值范围较小的整数提升为另一个整数类型。

(4) 如果操作涉及到枚举类型,就把它们转换为合适的整数类型。

术语“转换”(coversion)表示从一种类型到另一种类型的自动转换。术语“提升”(promotion)一般表示把一个数据值从一种取值范围较窄的类型转换为另一种取值范围较宽的类型。稍后将介绍如何把一种数据类型显式转换为另一种数据类型。这种转换称为“强制转换”(cast),把一个值显式转换为另一种类型的过程称为“强制转换过程”(casting)。

C++支持涉及混合类型的表达式,这并不是说使用混合类型的表达式很好,因为结果常常不是我们希望的,特别是在混合带符号和不带符号的类型时,结果更是出人意料。所以应尽可能避免编写混合表达式。

3.1.1 赋值和不同的类型

如果赋值运算符右边的表达式的类型不同于赋值运算符左边的变量类型,计算右边表达式所得的结果就会自动转换为左边变量的类型,之后才存储在该变量中。在许多情况下,这样会丢失信息。例如,假定定义了一个浮点数值:

double root=1.732;

若要编写下面的语句:

int value=root;

则把root的值转换为int,就会在value中存储1。因为int类型的变量只能存储整数,所以存储在root中的小数部分就会在转换为int类型的过程中舍弃。甚至在不同类型的整数之间使用等号,也会丢失信息:

long count=60000;

short value=count;

如果short是两个字节,long是4个字节,前者就没有足够的空间存储count的值,因而就会产生不正确的值。

许多编译器都会检测出这些转换,并发出警告消息,但我们不应依赖这些警告消息。为了尽可能避免这类问题,应避免把一种类型的数值赋予另一种取值范围较窄的类型。如果无法避免这类赋值,则可以指定进行显式转换,为的是说明这不是偶然事件,而是确实要这么做。下面看看其工作过程。

3.1.2 显式强制转换

混合的算术表达式涉及到基本类型,编译器会在需要时自动转换操作数,也可以使用显式转换方式,强制从一种类型转换为另一种类型。要把表达式的值强制转换为给定的类型,应编写如下格式的转换语句:

static_cast<转换后的类型>(表达式)

关键字static_cast表示这个强制转换要进行静态检查,也就是说,在程序编译时进行检查。后面在介绍类的处理时,会遇到动态的强制转换,这种转换要进行动态检查,即在程序执行时进行检查。强制转换的结果是把从表达式中计算的值转换为尖括号中指定的类型。表达式可以 是任何内容,包括从单个变量到包含许多嵌套括号的复杂表达式等所有内容。

下面是使用static_cast< >()的一个例子:

double value1=10.5;

double value2=15.5;

int whole_number= static_cast<int>( value1)+ static_cast<int>( value2);

因为变量whole_number的初始值是value1和value2的整数部分之和,所以它们必须分别显式强制转换为int类型。变量whole_number的初始值应为25。强制转换不会影响存储在value1和value2中的值,它们仍然是10.5和15.5。由强制转换得到的值10和15只是临时存储,在计算中使用后就删除。这两个强制转换会在计算过程中丢失信息,但编译器总是假定在显式指定强制转换时,用户知道会发生什么。

在前面的例子中,根据赋予不同的类型,可以把强制转换设置为显式执行,清晰地说明该强制转换是必须的:

int value= static_cast<int>( root);

一般情况下,很少需要显式强制转换,特别是在数据为基本类型时。如果必须在代码中包含大量的显式强制转换,则通常表明应为变量选择更合适的类型。但仍有一些情况需要进行强制转换。下面就介绍一个这类情况的例子。

程序示例3.1—— 显式强制转换

假定需要把单位为码的长度(小数值)转换为码、英尺和英寸(整数值),可以编写如下的程序:

//Program 3.1 Using Explicit Casts

#include <iostream>

using std::cin;

using std::cout;

using std::endl;

int main() {

const long feet_per_yard=3;

const long inches_per_foot=12;

double yards=0.0; // Length as decimal yards

long yds=0; // Whole yards

long ft=0; // Whole feet

long ins=0; // Whole inches

cout << "Enter a length in yards as a decimal: ";

cin >> yards;

//Get the length as yards, feet and inches

yds = static_cast<long>( yards);

ft = static_cast<long>((yards-yds)*feet_per_yard);

ins = static­_cast<long>

(yards*feet_per_yard*inches_per_foot)% inches_per_ foot;

cout << endl

<< yards << "yards converts to "

<<yds << "yards "

<<ft << "feet "

<<ins << "inches.";

cout << endl;

retuen 0;

}

这个程序的输出如下:

Enter a length in yards as a decimal: 2.75

2.75 yards coverts to 2 yards 2 feet 3 inches.

例子的说明

main()中的前两个语句声明了后面要使用的两个转换常量:

const long feet_per_yard=3;

const long inches_per_foot=12;

把这些变量声明为const,以防止程序不经意地修改它们,使用类型long是为了同其他值保持一致。虽然这里使用类型short就足以存储这些值了,但从长远来看,使用short会增加程序文件的长度(而不是减小)。这是因为在表达式中使用short和其他整型值时,需要进行隐式的转换。

接着的4个声明定义了要在计算过程中使用的变量:

double yards=0.0; // Length as decimal yards

long yds=0; // Whole yards

long ft=0; // Whole feet

long ins=0; // Whole inches

利用下面的语句,提示用户输入数据,再从键盘中读取一个值:

cout << "Enter a length in yards as a decimal: ";

cin >> yards;

下一个语句先对输入的值进行显式的强制转换,再计算yards的整型值:

yds= static_cast<long>(yards);

强制转换为long类型时,会舍弃yards值的小数部分,把整数部分存储在yds中。如果这里省略了显式强制转换过程,一些编译器就会编译该程序,而不是发出警告,说明已插入了需要的强制转换。但是,显然在这个转换过程中存在潜在的信息丢失,在这种情况下,应总是编写一个显式强制转换语句,以表明需要这么做。如果忽略了这一步,就不清楚这个转换的必要性如何,也没有注意到潜在的数据丢失。

用下面的语句获得长度的英尺值:

ft= static_cast<long>((yards-yds)*feet_per_yard);

因为我们希望英尺数不包含在码数中,所以从yards中减去yds的值。编译器会把yds中的值自动转换为double类型,进行减法运算,其结果也是double类型。再把feet_per_yard的值自动转换为double类型,进行乘法运算,最后对乘积进行显式的强制转换,把它从double类型转换为long类型。

最后一部分计算是获得剩余长度的英寸值:

ins= static­_cast<long>

(yards*feet_per_yard*inches_per_foot)% inches _ per _ foot;

计算原长度的总英寸值,再利用显式的强制转换,将它转换为long类型,接着除以每英尺的英寸数,得到了剩余的英寸值。最后,用下面的语句输出结果:

cout << std::endl

<< yards << "yards converts to "

<< yds << "yards "

<< ft << "feet "

<< ins << "inches.";

3.1.3 老式的强制转换

前面介绍了C++中的static_cast<>()(以及本书后面要介绍的const_cast<>()、dynamic _ cast <>()和reinterpret_cast<>()),把表达式的结果显式强制转换为另一种类型的过程可以表示为:

(转换后的类型)表达式

表达式的结果强制转换为括号中的类型。例如,前面例子中计算ins的语句可以改写为:

ins= (long)(yards*feet_per_yard*inches_per_foot)% inches_per_foot;

基本上,有4种不同类型的强制转换,老式的强制转换语法包含了这4种转换。所以,使用老式强制转换的代码更容易出错—— 它并不是很清晰,可能得不到希望的结果。尽管老式强制转换语法目前仍使用得很广泛(它仍是语言的一部分),但最好在代码中使用新型的强制转换语法。

伪随机数的生成

了解了强制转换后,就可以确保在算术表达式中使用rand()返回的值时不出问题。上一章提到,rand()返回0到RAND_MAX之间的值,RAND_MAX可以定义为int取值范围内任何正的int值。假定类型long的取值范围比int大,在用随机整数执行算术操作时,把rand()返回的值强制转换为long类型,就可以避免可能的问题,例如:

long even = 2*static_cast<long>(std::rand());

rand()返回的值是long类型,乘法运算就会在值2之转换相同类型之后进行。因此,乘法运算的结果就总是在long类型的取值范围之内。把字面量定义为long类型,可以得到相同的效果:

long even = 2L* std::rand();

2L是long类型,所以编译器会把rand()返回的值强制转换为long类型,再执行乘法运算。

可以使用rand()函数获得取值范围比0到RAND_MAX更小的随机整数,例如,假定希望随机整数在0到10之间,就可以从rand()返回的值中得到如下结果:

const int limit = 11;

int random_value = static_cast<int>(

(limit*static_cast<long>(std::rand()))

/(RAND_MAX+1L));

这里把0到RAND_MAX的取值范围分成limit部分,在给定的部分中,rand()返回的所有值都在0到limit–1的范围内。具体方法是,把limit乘以rand()/(RAND_MAX+1L),除以(RAND_MAX+1L)而不是RAND_MAX,是为了处理rand()正好返回RAND_MAX的情况。如果除以RAND_MAX,结果就是limit,而不是limit–1。加到RAND_MAX上的常量1L是long类型,所以RAND_MAX也会在执行加法操作之前转换为long类型。前面说过,在rand()的某些实现方式中,RAND_MAX定义为int类型的最大整数。在这种情况下,不先把RAND_MAX转换为long类型,就不能给它加1,也得不到正确的结果。

如果希望随机值在1和某个上限之间,而不是下限为0,就应使用下面的代码:

const int limit = 100;

int random_value = static_cast<int>

(1L +(limit*static_cast<long>(std::rand()))/

(RAND_MAX+1L));

这里使用与前面相同的表达式生成0到limit–1之间的值,然后给它加1,得到1到limit之间的值。

开头说过,这些都建立在类型long的取值范围比int大的假定基础之上。如果不是这样,就需要生成int类型取值范围之外的随机值,此时只能把rand()返回的值强制转换为浮点数,并把计算的结果存储为浮点数。例如,要产生0到limit之间的随机值,可以使用下面的语句:

const double limit = 11;

double random_value = limit*std::rand()/(RAND_MAX+1.0);

这里把limit声明为double类型,编译器会把rand()返回的整数提升为double类型,因此,不需要插入显式的强制转换。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论