C++ 数据类型转换之终极无惑

程序开发环境:VS2017+Win32+Debugios


数据类型在编程中常常遇到,虽然可能存在风险,但咱们却乐此不疲地进行着。c++

1. 隐式数据类型转换

隐式数据类型转换,指不显示指明目标数据类型的转换,不须要用户干预,编译器私下进行的类型转换行为。例如:程序员

double d=4.48;
int i=d;   		//报告警告

实际上,数据类型转换的工做至关于一条函数调用,如有一个函数专门负责从double转换到int(假设函数是dtoi),则上面的隐式转换等价于i=dtoi(d)。函数dtoi的原型应该是:int dtoi(double)或者是int dtoi(const double&)。有些类型的转换是绝对安全的,因此能够自动进行,编译器不会给出任何警告,如由int型转换成double型。另外一些转换会丢失数据,编译器只会给出警告,并不算一个语法错误,如上面的例子。各类基本数据类型(不包括void)之间的转换都属于以上两种状况。web

隐式数据类型转换无处不在,主要出如今如下几种状况。
(1)算术运算式中,低类型可以转换为高类型。
(2)赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并完成赋值。
(3)函数调用传递参数时,系统隐式地将实参转换为形参的类型后,赋给形参。
(4)函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。express

编程原则: 请尽可能不要使用隐式类型转换,即便是隐式的数据类型转换是安全的,由于隐式类型数据转换下降了程序的可读性。编程

2. 显示数据类型转换

显示数据类型转换指显示指明目标数据类型的转换,首先考察以下程序。安全

#include <iostream>
using namespace std;

int main(int argc,char* argv[])
{
	short arr[]={65,66,67,0};
	wchar_t *s;
	s=arr;
	wcout<<s<<endl;
}

因为 short int 和 wchar_t 是不一样的数据类型,直接把 arr 表明的地址赋给 s 会致使一个编译错误:error C2440:“=”:没法从“short[4]”转换为“wchar_t”svg

为了解决这种“跨度较大”的数据类型转换,可使用显示的“强制类型转换”机制,把语句s=arr;改成s=(wchar_t*)arr;就能顺利经过编译,并输出:ABC。函数

强制类型转换在 C 语言中早已存在,到了 C++ 语言中能够继续使用。在 C 风格的强制类型转换中,目标数据类型被放在一对圆括号中,而后置于源数据类型的表达式前。在 C++ 语言中,容许将目标数据类型当作一个函数来使用,将源数据类型表达式置于一对圆括号中,这就是所谓的“函数风格”的强制类型转换。以上两种强制转换没有本质区别,只是书写形式略有不一样。即:优化

(T)expression  //C-style cast
T(expression)  //function-style cast

可将它们称为旧风格的强制类型转换。在上面的程序中,能够用如下两种书写形式实现强制类型转换:

s=(wchar_t*)arr;
typedef wchar_t* WCPTR; s= WCPTR(arr);

3.C++ 中新式类型转换

C++ 增长了四种内置的类型转换符:const_cast、static_cast、dynamic_cast和reinterpret_cast。它们具备统一的语法格式:

type_cast_operator<type>(expresiion)

3.1 const_cast

const_cast 主要用于解除常指针和常量的 const 和 volatile 属性。也就是说,把cosnt type*转换成type*类型或将const type&转换成type&类型,可是要注意,一个变量自己被定义为只读变量,那么它永远是常变量。const_cast 取消的是对间接引用时的改写限制(即只针对指针或者引用),而不能改变变量自己的 const 属性。以下面的语句是错误的。

const int i;
int j=const_cast<int>(i);	//编译出错

下面经过 const_cast 取消对间接引用的修改限制。

//示例1
void constTest1()
{
	const int a=5;
	int *p=NULL;
	p=const_cast<int*>(&a);
	(*p)++;
	cout<<a<<endl;			//输出5
}

//示例2
void constTest2()
{
	int i;
	cout<<"please input a integer:";	//输入5
	cin>>i;
	const int a=i;
	int& r=const_cast<int&>(a);
	r++;
	cout<<a<<endl;						//输出6
}

在函数 constTest1() 中输出 5,并不表明常变量 a 的值没有改变,而是编译器在代码优化时将 a 替换为字面常量5,实际上 a 的值已经变成了 6。在函数 constTest2() 中,因为常变量 a 的值由用户运行时输入决定,编译时没法将 a 转化为对应的字面常量,因此输出结果为修改后的值 6。

3.2 static_cast

static_cast至关于传统的C语言中那些“较为合理”的强制类型转换,较多地用于基本数据类型之间的转换、基类对象指针(或引用)和派生类对象指针(或引用)之间的转换、通常的指针和void*类型的指针之间的转换等。static_cast操做对于类型转换的合理性会做出检查,对于一些过于“无理”的转换会加以拒绝。例以下面的转换:

double d=3.14;
double* p=static_cast<double*>(d);

这是一种很是诡异的转换,在编译时会遭到拒绝。另外,对于一些看似合理的转换,也可能被static_cast拒绝,这时要考虑别的方法。以下面的程序。

#include <iostream>
using namespace std;

class A
{
	char ch;
	int n;
public:
	A(char c,int i):ch(c),n(i){}
};

int main(int argc,char* argv[])
{
	A a('s',2);
	char* p=static_cast<char*>(&a);
	cout<<*p;
}

这个程序没法经过编译,就是说,直接将A*类型转换为char*是不容许的,这时能够经过void*类型做为中介实现转换。修改后的程序以下。

void* q=&a;
char* p=static_cast<char*>(&q);

这样,程序就能够经过编译,输出s。可见,若是指针类型之间进行转换,必定要注意转换的合理性,这一点必须由程序员本身负责。指针类型的转换意为对原数据实体内容的从新解释。

虽然const_cast是用来去除变量的const限定,可是static_cast却不是用来去除变量的static引用。其实这是很容易理解的,static决定的是一个变量的做用域和生命周期,好比在一个文件中将变量定义为static,则说明这个变量只能在当前文件中使用;在方法中定义一个static变量,该变量在程序开始时存在,直到程序结束;类中定义一个static成员,能够被该类的全部对象使用。对static限定的改变必然会形成范围性的影响,而const限定的只是变量或对象自身。但不管是哪个限定,它们都是在变量一出生(完成编译的时候)就决定了变量的特性,因此实际上都是不容许改变的。这点在const_cast那部分就已经有体现出来。

在实践中,static_cast多用于类类型之间的转换。这时,被转换的两种类型之间必定存在派生与继承的关系。见以下程序。

#include <iostream>
using namespace std;

class A{};
class B{};
int main(int argc,char* argv[])
{
	A* pa;
	B* pb;
	A a;
	pa=&a;
	pb=static_cast<B*>(pa);
}

改程序没法经过编译,缘由是类A与类B没有任何关系。综上所述,使用static_cast进行类型转换时要注意以下几点。
(1)static_cast操做符的语法形式是static_cast(expression),其中,expression外面的圆括号不能省略,哪怕expression是一个简单的变量。

(2)经过static_cast只能进行一些相关类型之间的“合理”转换。若是是类类型之间的转换,源类型和目标类型之间必须存在继承关系,不然会获得编译错误。

(3)static_cast所进行的是一种静态转换,是在编译时决定的。经过编译后,空间和时间效率实际上等价于C方式的强制类型转换。

(4)在C++中,只想派生类对象的指针能够隐式转换为指向基类对象的指针。而要把指向积累对象的指针转换为指向派生类对象的指针,就须要借助static_cast操做符来完成,其转换的风险是须要程序员本身来承担。固然使用dynamic_cast更为安全。

(5)static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

3.3 dynamic_cast

dynamic_cast是一个彻底的动态操做符,只能用于指针或者引用间的转换。并且dynamic_cast运算符所操做的指针或引用的对象必须拥有虚成员函数,不然出现编译错误。

缘由是dynamic_cast牵扯到面向对象的多态性,其做用就是在程序运行的过程当中动态的检查指针或者引用指向的实际对象是什么以肯定转换是否安全,而C++的类的多态性则依赖于类的虚函数。

具体的说,dynamic_cast能够进行以下的类型转换。

(1)在指向基类的指针(引用)与指向派生类的指针(引用)之间进行的转换。基类指针(引用)转换为派生类指针(引用)为向下转换,被编译器视为安全的类型转换,也可使用static_cast进行转换。派生类指针(引用)转换为基类指针(引用)时, 为向上转换,被编译器视为不安全的类型转换,须要dynamic_cast进行动态的类型检测。固然,static_cast也能够完成转换,只是存在不安全性。

(2)在多重继承的状况下,派生类的多个基类之间进行转换(称为交叉转换:crosscast)。如父类A1指针实际上指向的是子类,则能够将A1转换为子类的另外一个父类A2指针。

3.3.1 dynamic_cast的向下转换

dynamic_cast在向下转换时(downcast),即将父类指针或者引用转换为子类指针或者引用时,会严格检查指针所指的对象的实际类型。参见以下程序。

#include <iostream>
using namespace std;
class A
{
public:
	int i;
	virtual void show(){
	cout<<"class A"<<endl;
	}
	A(){int i=1;}
};

class B:public A
{
public:
	int j;
	void show()
	{
		cout<<"class B"<<endl;
	}
	B(){j=2;}
};

class C:public B
{
public:
	int k;
	void show()
	{
		cout<<"class C"<<endl;
	}
	C(){k=3;}
};

int main(int argc,char* argv[])
{
	A* pa=NULL;
	B b,*pb;
	C *pc;
	pa=&b;
	pb=dynamic_cast<B*>(pa);
	if(pb)
	{
		pb->show();
		cout<<pb->j<<endl;
	}
	else
	{
		cout<<"Convertion failed"<<endl;
	}

	pc=dynamic_cast<C*>(pa);
	if(pc)
	{
		pc->show();
		cout<<pc->k<<endl;
	}
	else
	{
		cout<<"Convertion failed"<<endl;
	}
}

程序输出结果是:

class B
2
Convertion failed

因为指针pa所指的对象的实际类型是class B,因此将pa转换为B*类型没有问题,而将pa转换成C*类型时则失败。当指针转换失败时,返回NULL。

3.3.2 dynamic_cast的交叉转换

交叉转换(Crosscast)是在两个“平行”的类对象之间进行。原本它们之间没有什么关系。将其中的一种转换为另外一种是不可行的。可是,若是类A和类B都是某个派生类C的基类,而指针所指的对象自己就是一个类C的对象,那么该对象既能够被视为类A的对象,也能够被视为类B的对象,类型A*(A&)和B*(B&)之间的转换就成为可能。见下面的例子。

#include <iostream>
using namespace std;

class A
{
public:
	int num;	
	A(){num=4;}
	virtual void funcA(){}
};

class B
{
public:
	int num;
	B(){num=5;}
	virtual void funcB(){}
};

class C:public A,public B{};

int main(int argc,char* argv[])
{
	C c;
	A* pa;
	B* pb;
	pa=&c;
	cout<<pa->num<<endl;
	pb=dynamic_cast<B*>(pa);
	cout<<"pa="<<pa<<endl;
	if(pb)
	{
		cout<<"pb="<<pb<<endl;
		cout<<"Conversion succeeded"<<endl;
		cout<<pb->num<<endl;
	}
	else
	{
		cout<<"Conversion failed"<<endl;
	}
}

程序输出结果是:

4
pa=003BFE8C
pb=003BFE94
Conversion succeeded
5

能够看出,pa转换成pb以后,其值产生了变化,也就是说,在类C的对象中,类A的成员和类B的成员所占的位置(距离对象首地址的偏移量)是不一样的。类B的成员要靠后一些,因此将A*转换为B*的时候,要对指针的位置进行调整。若是将程序中的dynamic_cast替换成static_cast,则程序没法经过编译,由于编译器认为类A和类B是两个“无关”的类。

3.4 reinterpret_cast

reinterpret_cast是一种最为“狂野”的转换。它在C++四中新的转换操做符中的能力是最强的,其转换能力与C的强制类型转换不分上下。正是由于其过于强大的转换能力,reinterpret_cast是C++语言中最不提倡使用的一种数据类型转换操做符,应该尽可能避免使用。

主要用于转换一个指针为其余类型的指针,也容许将一个指针转换为整数类型,反之亦然。这个操做符可以在非相关的类型之间进行。不过其存在必有其价值,在一些特殊的场合,在确保安全性的状况下,能够适当使用。它通常用于函数指针的转换。见以下程序。

#include <iostream>
using namespace std;

typedef void (*pfunc)();
void func1()
{
	cout<<"this is func1(),return void"<<endl;
}

int func2()
{
	cout<<"this is func2(),return int"<<endl;
	return 1;
}

int main(int argc,char* argv[])
{
	pfunc FuncArray[2];
	FuncArray[0]=func1;
	FuncArray[1]=reinterpret_cast<pfunc>(func2);
	for(int i=0;i<2;++i)
	{
		(*FuncArray[i])();
	}
}

程序输出结果:

this is func1(),return void
this is func2(),return int

由函数指针类型int(*)()转换为void(*)(),只能经过reinterpret_cast进行,用其余的类型转换方式都会遭到编译器的拒绝。并且从程序的意图来看,这里的转换是“合理”的。不过,C++是一种强制类型安全的语言,即便使用interpret_cast,也不能任意地将某种类型转换为另外一种类型,C++编译器会设法保证“最低限度”的合理性。

语言内置的类型转换操做符没法胜任的工做须要程序员手动重载相关转换操做符来完成类型转换。

4. 手动重载相关类型转换操做符

在各类各样的类型转换中,用户自定义的类类型与其余数据类型间的转换要引发注意。这里要重点考察以下两种状况。

4.1不一样类对象的相互转换

由一种类对象转换成另外一种类对象。这种转换没法自动进行,必须定义相关的转换函数,其实这种转换函数就是类的构造函数,或者将类类型做为类型转换操做符函数进行重载。此外,还能够利用构造函数完成类对象的相互转换,见以下程序。

#include <iostream>
using namespace std;

class Student
{
	char name[20];
	int age;
public:
	Student(){};
	Student(char *s, int a)
	{
		strcpy(name,s);
		age=a;
	}
	friend class Team;
};

class Team
{
	int members;
	Student monitor;
public:
	Team(){};
	Team(const Student& s):monitor(s),members(0){};
	void Display()const
	{
		cout<<"members' number :"<<members<<endl;
		cout<<"monitor's name :"<<monitor.name<<endl;
		cout<<"monitor's age :"<<monitor.age<<endl;
	}
};
ostream& operator<<(ostream& out,const Team &t)
{
	t.Display();
	return out;
}

int main(int argc,char* argv[])
{
	Student s("阿珂",23);
	cout<<s;
}

程序输出结果:

members' number :0
monitor's name :阿珂
monitor's age :23

原本,输出操做符operator<<并不接受Student类对象做为参数,但因为可经过类Team的构造函数将Student类对象转换成Team类对象,因此输出操做能够成功进行。类的单参数构造函数实际上充当了类型转换函数。

4.2基本数据类型与类对象的相互转换

4.2.1基本数据类型转换为类对象

这种转换仍能够借助于类的构造函数进行的。也就是说,在类的若干重载的构造函数中,有一些接受一个基本数据类型做为参数,这样就能够实现从基本数据类型到类对象的转换。

4.2.2类对象转换为基本数据类型

因为没法为基本数据类型定义构造函数,因此由对象想基本数据类型的转换必须借助于显示的转换函数。这些转换函数名由operator后跟基本数据类型名构成。下面是一个具体的例子。

#include <iostream>
using namespace std;

class A
{
public:
	operator int()
	{
		return 1;
	}
	operator double()
	{
		return 0.5;
	}
};

int main(int argc,char* argv[])
{
	A obj;
	cout<<"Treating obj as an interger, its value is: "<<(int)obj<<endl;
	cout<<"Treating obj as a double, its value is: "<<(double)obj<<endl;
}

程序输出结果:

Treating obj as an interger, its value is: 1
Treating obj as a double, its value is: 0.5

在一个类中定义基本类型转换的函数,须要注意如下几点:
(1)类型转换函数只能定义为一个类的成员函数,而不能定义为外部函数。类型转换函数与普通成员函数同样,也能够在类体中声明,在类外定义;
(2)类型转换函数一般是提供给类的客户使用的,因此应将访问权限设置为public,不然没法被显示的调用,隐式的类型转换也没法完成;
(3)类型转换函数既没有参数,也不显示的给出返回类型;
(4)转换函数必须有“return目的类型数据;”的语句,即必将目的类型数据做为函数的返回值;
(5)一个类能够定义多个类型转换函数。C++编译器将根据目标数据类型选择合适的类型转换函数。在可能出现二义性的状况下,应显示地使用类型转换函数进行类型转换。

5.总结

(1)综上所述,数据类型转换至关于一次函数调用。调用的的结果是生成了一个新的数据实体,或者生成一个指向原数据实体但解释方式发生变化的指针(或引用)。

(2)编译器不给出任何警告也不报错的隐式转换老是安全的,不然必须使用显示的转换,必要时还要编写类型转换函数。

(3)使用显示的类型转换,程序猿必须对转换的安全性负责,这一点能够经过两种途径实现:一是利用C++语言提供的数据类型动态检查机制;而是利用程序的内在逻辑保证类型转换的安全性。

(4)dynamic_cast转换符只能用于含有虚函数的类。dynamic_cast用于类层次间的向上转换和向下转换,还能够用于类间的交叉转换。在类层次间进行向上转换,即子类转换为父类,此时完成的功能和static_cast是相同的,由于编译器默认向上转换老是安全的。向下转换时,dynamic_cast具备类型检查的功能,更加安全。类间的交叉转换指的是子类的多个父类之间指针或引用的转换。

dynamic_cast可以实现运行时动态类型检查,依赖于对象的RTTI(Run-Time Type Information),经过虚函数表找到RTTI肯定基类指针所指对象的真实类型,来肯定可否转换。

(5)interpre_cast相似于C的强制类型转换,多用于指针(和引用)类型间的转换,权利最大,也最危险。static_cast权利较小,但大于dynamic_cast,用于普通的转换。进行类层次间的下行转换若是没有动态类型检查,是不安全的。

(6)const_cast只用于去除指针或者引用类型的const和volatile属性,变量自己的属性不能被去除。

在进行类型转换时,请坚持以下原则:
(1)子类指针(或引用)转换为父类指针(或引用)编译器认为老是是安全的,即向上转换,请使用static_cast,而非dynamic_cast,缘由是static_cast效率高于dynamic_cast。

(2)父类指针(或引用)转换为子类指针(或引用)时存在风险,即向下转换,必须使用dynamic_cast进行动态类型检测。

(3)不要使用C风格的强制类型转换,使用标准C++中的四个类型转换符static_cast、dynamic_cast、reinterpret_cast、和const_cast。


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008.1.4const_cast的用法.P10-P12