类型及类型转换.docx
- 文档编号:5504504
- 上传时间:2022-12-17
- 格式:DOCX
- 页数:13
- 大小:25.03KB
类型及类型转换.docx
《类型及类型转换.docx》由会员分享,可在线阅读,更多相关《类型及类型转换.docx(13页珍藏版)》请在冰豆网上搜索。
类型及类型转换
第九章类型及类型转换
C/C++作为强类型语言,类型及类型转换的重要性在JAVA课中已做过介绍(JAVA也是强类型语言)。
这一章集中讲一下C/C++的类型及类型转换,内容包括:
⏹C语言类型
⏹C++类型
⏹C/C++中基本的类型转换
⏹C++对象的类型转换
⏹类型转换函数
一、C语言类型
C语言的基本类型以及struct等复合类型,同学们已经很熟悉了,不再赘述,下面讨论几个课本中讲得不多的类型。
1枚举enum
C语言提供了一种称为“枚举”的类型。
在“枚举”类型的定义中列举出所有可能的取值,而且被说明为该“枚举”类型的变量取值不能超过定义的范围。
枚举类型定义的一般形式为:
enum枚举名{枚举值表};
在枚举值表中应罗列出所有可用值。
这些值也称为枚举元素。
例如:
enumweekday
{sun,mou,tue,wed,thu,fri,sat};
枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为0,1,2…。
如在weekday中,sun值为0,mon值为1,…,sat值为6。
枚举类型是一种基本数据类型,而不是一种复合类型,因为它不能再分解为任何基本类型。
枚举变量的使用如下例所示:
main(){
enumweekday
{
sun,mon,tue,wed,thu,fri,sat
}a,b,c,d;
a=sun;//
b=mon;//
c=2;//错。
枚举类型不等同于整数
d=(enumweekday)3;//需要强制类型转换
}
枚举的语法不多做介绍,主要讲一下它在实际编程中怎么用:
有些可以定义为整型的变量,取值被限定在一个有限的范围内,可取的值都有一个熟知的名字。
例如,一个星期内的七天,一年的十二个月等。
如果把这些量说明为字符串,则对这些变量进行关系运算或算术运算不方便(比如比较大小),而说明为整型每个值又没有名字也不太方便。
这时后可考虑将其定义为枚举。
使用枚举的好处是:
不仅帮助程序员和用户记忆,而且使程序可靠,可以防止变量取非法取值。
枚举在实际编程中非常常见,比如JAVA线程库中线程的状态(wait,start,run…,)等等就是用枚举(而不是整型或字串),还有很多C程序对设备状态、寄存器状态等往往也用枚举标识。
Q1:
在什么情况下使用枚举enum类型?
好处是什么?
2寄存器变量register
registerinti;按语法的意思,是把i放到寄存器内,这比放到内存中快得多,如果i频繁使用的话,会提高效率。
问题是C语言是可移值的—某种程度上有跨平台的特性,不同平台编译registerinti时会有不同的处理,为了防止副作用,有的C编译器可能不把i放到寄存器内。
大多数C语言编译器都含有优化器,对常使用的局部变量等等进行优化,有时没有声明为register的变量也会放到寄存器中,所以没必要使用registerinti这种可移值性不好的代码。
3typedef
见“第四章预处理指令”
4共同体union
在C语言里,union可以看作是一种特殊的struct,而在C++里,struct和union可以看作是特殊的类,它们可以有构造函数。
无论如何,union的基本特性是:
使若干变量共享一段内存。
比如:
unionmyunion{
intx;charch;floaty;
}a,b//
定义类型myunion。
x、ch、y共享其中类型最宽的字节的内存。
最后定义了myunion类型变量a,b
a.x;b.ch;b.y;//引用共同体成员
union在实际编程中有什么用?
下面例举几种用途;
(1)有人认为:
不同类型变量共用一段内存,类型转换时不用强制转换。
的确如此,但这绝不是好的编程风格,如果只是为了类型转换方便,最好不要用union。
(2)有时需要操作几个固定字节数、固定地址的硬件或软件接口(比如设备I/O端口),这个接口对不同代码的访问需要呈现不同的界面(比如有时是字符串,有时是整数,有时是控制字),可以使用union让几个成员共享该界面。
比如:
unionportX{
lontintx;//4字节端口,I/O整数
charstr[4];//同一个4字节端口,I/O字串
charc_word[4];//同一个4字节端口,I/O控制字
}myPort
(3)节约内存,在一些微小系统里,确实需要使用union节约内存,比如让多个数组共享一段内存。
在C++中,union用作类的成员变量,由于类的构造函数的问题,可能会有一些需要特殊处理的问题,略过不讲。
其它有关union语法的问题也略过不讲,我们只强调该怎么用它。
在一些微小系统和底层程序中,union的出镜率还是挺高的。
5volatile
C语言关键字volatile(易变的)表示不经过赋值,其值也可能被改变(例如,表示时钟的变量、表示端口的变量等)。
声明为volatile的变量会阻止编译器对其优化(有时编译器会把变量优化到我们不希望的地方),在与硬件很密切的编程中使用较多。
比如以下声明:
volatileinta;
a在运行期间不经过赋值也可能被改变。
下面例举volatile变量的几种用途;
1)设备的硬件寄存器(如:
状态寄存器)
2)一个中断服务子程序中会访问到的非自动变量
3)多线程应用中被几个任务共享的变量
volatile还可以用来修饰常量:
volatileconstinta;
这是什么意思?
a不允许程序员修改,但会在程序运行过程中被系统修改,比如只读的状态寄存器。
以前讲过,const真正的含义是“只读”,而不是“常数”。
对嵌入式编程或对硬件直接编程感兴趣的同学应注意volatile。
6浮点数的判等和比较
浮点数采用科学计数法,其值的精度不是100%精确,因此用于判等和比较时要特别小心。
常用判等的方法是,两个浮点数相减的绝对值小于一个很小很小的数就认为相等。
Q2:
浮点数在判断是否为0时会出现什么问题?
原因是什么?
二、C++类型
C++主要增加了类、泛型等类型,对struct也作了面向对象的升级。
1C++的class与struct
Q3:
C++类与结构体区别?
在C++里,struct可以看作是一个特殊的类,区别在于:
class中默认的成员访问权限是private的,而struct中则是public的。
从class继承默认是private继承,而从struct继承默认是public继承。
例:
#include
#include
usingnamespace std;
struct ST{
ST() {……} //结构体可以有构造函数和其它成员函数
int a; int b;//默认的成员访问权限是public
};
//结构体成员函数实现的语法与类相同
intmain(int argc,char*argv[]) {
STst1();//定义结构体栈对象
ST*st2=newST();//定义结构体堆对象
……
}
2.C++的bool类型
C++的bool类型可取值为false和true,而旧式方式是0表示false,非0表示true.
三、C/C++基本的类型转换
这一小节主要是复习性质的,大多数内容在C语言中学过。
C/C++允许在一个表达式中参与运算的操作数的数据类型不一致,即支持不同数据类型的数据之间的混合运算。
在对这样的表达式求值时,C++语言需要对其中的一些操作数进行类型转换。
表达式中的类型转换有两种方式:
自动转换和强制转换。
1自动转换
一般地,双目运算中的算术运算符、关系运算符、逻辑运算符和位操作运算符组成的表达式,要求两个操作数的类型一致,如果操作数类型不一致,则自动将低类型转换为高的类型。
各种类型的高低顺序如下所示:
高double←←float
↑↑
↑long
↑↑
↑unsigned
↑↑
低int←←char,short
自动转换的特点:
●自动转换由编译系统自动完成。
●自动转换是一种保值映射,即在转换中数据的精度不受损失。
●自动类型转换相当于“类型升级”,如果原来的数是无符号数,那么在扩展的时候,高位填充的是0;如果是有符号数,那么高位填充的是符号位。
●可以自动转换的类型是“类型兼容”的,它们反过来可以进型强制转换。
自动转换一般只发生在算术表达式的运算中,因此,一般类型转换都应该用强制类型转换。
2强制转换
分为显式强制转换和隐式强制转换。
(1)显式强制转换
有两种格式,例如:
doublef=3.55;
inth;
h=int(f);//第一种格式,函数风格
h=(int)f;//第二种格式,C风格
这两种格式的作用一是通知编译器,二是告诉程序的阅读者这里有类型转换,有些程序员把可以自动转换的类型也写成上述两种风格之一,这是好习惯。
比如:
doublef;
inth=5;
f=double(h);
本来是可以自动转换的,但写成强制转换对程序的阅读者有更强的提示作用。
(2)隐式强制转换
主要有两种情况:
在赋值表达式中,右值类型可以隐式强制转换为左值类型。
例如:
doublef=3.55;
inth1;
h=f;//隐式强制转换
另为在函数返回类型中,可以隐式强制转换,例如:
intf1(){return36.8;},实际返回36。
但上述两种情况的有些类型转换必需进行显式强制转换,比如:
int*p=100;//不能隐式强制转换,编译器会报错,应改为:
int*p=(int*)100;
强制类型转换是不安全的,因为将高类型转为低类型会造成丢失小数部分精度损失或整数部分。
这种情况只有程序员自已小心,在写强制类型转换代码时,最好不要写成隐式的,因为这会让程序阅读者忽略转换可能带来的潜在危险,应该试一试可不可以写成显式的,对自动类型转换最好也试试可不可以写成显式的。
强制类型转换是“临时性”或“副本转换”的,比如:
doublef=3.55;
inth1;
h=(int)f;//f“临时性”转换为3,转换完后f仍为3.55。
Q4:
为什么说强制类型转换是不安全的?
3.函数形参有类型转换功能
函数实参传给形参,相当于赋值,所以只要形参和实参满足隐式强制转换条件(或自动转换条件),都能进行隐式强制转换(或自动转换)。
比如:
Fun(int,int);//实参为float时可进行隐式强制转换
Fun(float,float);//实参为int时可进行自动转换
上述特性在C语言中就有,在C++中的成员函数和构造函数也具有这些特性。
但是,有时这种隐式类型转换可能带来一些问题,这时可用到C++关键字explicit,将类的构造函数声明为"显示",也就是在声明构造函数的时候,前面添加上explicit即可,这样就可以防止这种转换操作。
explicit只对C++构造函数有效。
Q5:
了解函数形参的类型转换功能
四、C++对象的类型转换
这一小节介绍C++特有的类型转换。
C++的类型转换首先兼容C,也做了很多扩充,比C语言更精确,因此也更复杂。
C++有了类和对象的概念,对象的形式是指针,因此C++对象的类型转换具有指针的一般特性,但是远比指针复杂,很少有一本编程书能介绍完整。
1RTTI
Q6:
什么是RTTI?
学习C++类型转换,要知道一个概念:
RTTI(Run-TimeTypeIdentification,运行时刻类型识别)。
RTTI是面向对象一项重要技术,标准的C++以及很多编译器厂商的C++都实现了这项技术,另外JAVA/C#等也实现了这项技术,比如JAVA关键字instanceof需要RTTI支持。
通过使用RTTI,程序可以在运行时通过基类指针或者引用来得到所指对象的实际类型。
C++的RTTI主要有两个操作:
(1)typeid操作符:
返回指针或者引用所指对象的实际类型。
(2)dynamic_cast操作符:
将基类类型的指针或引用安全地转换为派生类型的指针或者引用。
2C++新增的typecasting操作符
类型转换,英语也叫typecasting。
C++新增了几个与typecasting有关的操作符:
static_cast
dynamic_cast
reinterpret_cast
const_cast
typeid
(1)static_cast(不使用RTTI)
用法:
static_cast
该运算符把expression转换为TypeName类型,它是在编译时进行类型检查,而不使用RTTI。
它主要有如下几种用法:
①用于类层次结构中基类和子类之间指针或引用的转换。
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。
相当于C语言的强制类型转换,是不安全的。
比如:
doubled;
inta=static_cast
③把某类型的空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
(2)dynamic_cast(使用RTTI)
用法:
dynamic_cast
该运算符把expression转换成TypeName类型的对象。
TypeName必须是类的指针、类的引用或者void*;
如果TypeName是类指针类型,那么expression也必须是一个指针,如果TypeName是一个引用,那么expression也必须是一个引用。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
下面的例子说明dynamic_cast,static_cast
classA{
public:
inta;
virtualvoidfun();};
classB:
publica{
public:
floatb;};
voidfunc(A*pa){//形参是RTTI要求的基类指针
B*pb1=static_cast(pa);//不用RTTI
B*pb2=dynamic_cast(pa);//使用RTTI
}
讨论一下上面的代码段:
(a)如果func形参pa指向一个子类B的对象,那么转换后pb1和pb2是一样的,并且对这两个指针执行B类型的任何操作都是安全的。
(b)如果func形参pb指向一个父类A的对象,那么转换后:
pd1是将父类A对象pa转换来的子类B对象,对它进行B类型的操作将是不安全的(比如不能访问B的成员floatb),这是static_cast的特点,它类似于C语言的强制类型转换,是不安全的。
pd2也是将父类A对象pa转换来的子类B对象,但RTTI知道这种转换不安全,将pd2设为B类型的NULL,而不是指向一个B对象,也就是说pd2有类型而无对象,这是dynamic_cast的特点,它更安全,它可以防止程序继续访问不该访问的成员(比如访问B的成员floatb)。
[注意:
]上例中基类A一定要有虚函数,否则dynamic_cast时会编译出错;RTTI要求基类必需有至少有一个虚函数才能进行dynamic_cast。
static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。
上例中的dynamic_cast是父类子类间的转换,dynamic_cast还支持交叉转换。
如下代码所示:
classA{
public:
inta;
virtualvoidf(){}};
classB:
publicA{};
classD:
publicA{};
voidfoo(){
B*pb=newB;
pb->a=100;
D*pd1=static_cast(pb);//compileerror
D*pd2=dynamic_cast(pb);//pd2isNULL
deletepb;
}
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错,因为语法上不允许static_cast交叉转换;
而使用dynamic_cast的转换则是允许的,结果是空指针,这是dynamic_cast的特点,它更安全,它可以防止程序继续访问不该访问的成员(比如访问B的特有成员)。
Q7:
static_cast和dynamic_cast主要区别是什么?
(3)reinpreter_cast
用法:
reinpreter_cast
reinterpret_cast可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。
Reinterpret的含义就是“重新解释”。
reinterpret_cast“通常为操作数的位模式提供较低层的重新解释”,也就是说将数据以二进制存在形式的重新解释。
比如:
char *p = "This is a example.";
int i = reinterpret_cast
此时结果,i与p的二进制值是完全相同的,但解释不同:
reinterpret_cast的作用是说将字符指针p的值以二进制(位模式)的方式被解释为整型,并赋给i,一个明显的现象是在转换前后没有数位损失。
实际编程中,不推荐使用reinterpret_cast。
(4)const_cast
用法:
const_cast
该运算符用来去除expression中的const或volatile属性。
常见的转换是:
●常量指针被转化成非常量指针,并且仍然指向原来的对象;
●常量引用被转换成非常量引用,并且仍然指向原来的对象;
●常量对象被转换成非常量对象。
例如:
classB{
public:
inta;}
voidfoo(){
constBb1;
b1.a=100;//comileerror
Bb2=const_cast(b1);//fine
b2.a=200;}
上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;但使用const_cast把它转换成一个非常量对象,就可以对它的数据成员任意改变。
C++另一个不常用的关键字mutalbe有时可以替代const_cast的作用,mutalbe的中文意思是“可变的,易变的”,跟constant(const)是反义词。
在C++中,mutable也是为了突破const的限制而设置的。
被mutable修饰的变量,将永远处于可变的状态量。
mutalbe常用在以下的成员函数内:
classA{
private:
inta;
public:
voidfun()const//
{//含有修改类成员a的代码}
}
以前讲过,voidfun()const这种形式的函数,内部不允许修改类的成员变量,但有时又确实需要,这时可把inta声明为mutalbeinta。
5.typeid操作符(使用RTTI)
typeid可以在运行时进行类型判断,表达式形如:
typeid(expression);
expression是任意表达式或者类型名。
如果表达式的类型是类类型且至少包含有一个虚函数(可以RTTI),则typeid操作符返回表达式的动态类型,需要在运行时计算;否则,typeid操作符返回表达式的静态类型,在编译时就可以计算。
typeid操作符的返回结果是名为type_info的标准库类型的对象的引用(在头文件typeinfo中定义)。
C++标准并没有确切定义type_info,它的确切定义是与编译器相关的,但是标准却规定了其实现必需提供如下四种操作:
t1==t2
如果两个对象t1和t2类型相同,则返回true;否则返回false
t1!
=t2
如果两个对象t1和t2类型不同,则返回true;否则返回false
t.name()
返回类型的C-style字符串,类型名字用系统相关的方法产生
t1.before(t2)
返回指出t1是否出现在t2之前的bool值
type_info类提供了public虚析构函数,以使用户能够用其作为基类。
它的默认构造函数和拷贝构造函数及赋值操作符都定义为private,所以不能定义或复制type_info类型的对象。
程序中创建type_info对象的唯一方法是使用typeid操作符(由此可见,如果把typeid看作函数的话,其应该是type_info类的友元)。
type_info的name成员函数返回C-style的字符串,用来表示相应的类型名。
下面看一个例子(同学们可以自已上机看运行结果,不同的编译器运行结果可能不同):
#include
usingnamespacestd;
classBase{};
classDerived:
publicBase{};
intmain(){
cout< < < < < < < < < <
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 类型 转换