包括隐式类型转换(算数类型隐式转换与其他类型隐式转换),强制类型转换。隐式转换发生在从小到大的转换中,比如从char转换为int,int->long。C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换,关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换。
在C++语言中static_cast用于数据类型的强制转换,强制将一种数据类型转换为另一种数据类型。例如将整型数据转换为浮点型数据。任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
C语言所采用的类型转换方式:int a = 10;int b = 3;double result = (double)a / (double)b;
将整型变量a和b转换为双精度浮点型然后相除。在C++语言中可以采用static_cast关键字来进行强制类型转换:
static_cast关键字的使用:static_cast <类型说明符> (变量或表达式)
int a = 10;int b = 3;double result = static_cast
同样是将整型变量a转换为双精度浮点型。采用static_cast进行强制数据类型转换时,将想要转换成的数据类型放到尖括号中,将待转换的变量或表达式放在元括号中。
它主要有如下几种用法:
(1)用于类层次结构中基类和派生类之间指针或引用的转换
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
(2)用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证
(3)把空指针转换成目标类型的空指针
(4)把任何类型的表达式转换为void类型
注意:static_cast不能转换掉expression的const、volitale或者__unaligned属性。
static_cast:可以实现C++中内置基本数据类型之间的相互转换。如果涉及到类的话,static_cast只能在有相互联系的类型中进行相互转换,不一定包含虚函数。
为什么要用static_cast转换而不用c语言中的转换?
更加安全;
更直接明显,能够一眼看出是什么类型转换为什么类型,容易找出程序中的错误;可清楚地辨别代码中每个显式的强制转;可读性更好,能体现程序员的意图
在C语言中,const限定符通常被用来限定变量,用于表示该变量的值不能被修改。而const_cast则正是用于强制去掉这种不能被修改的常数特性,但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。const_cast 只能改变运算对象的底层const。
用法:const_cast
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
错误例子:
const int a = 10;
const int * p = &a;
*p = 20; //compile error
int b = const_cast
在本例中出现了两个编译错误,第一个编译错误是*p因为具有常量性,其值是不能被修改的;另一处错误是const_cast强制转换对象必须为指针或引用,而例3中为一个变量。
const_cast关键字的使用
#include
using namespace std;
int main()
{
const int a = 10;
const int * p = &a;
int *q;
q = const_cast(p);
*q = 20; //fine
cout <
本例将变量a声明为常量变量,同时声明了一个const指针指向该变量(此时如果声明一个普通指针指向该常量变量的话是不允许的,Visual Studio 2010编译器会报错)。之后定义了一个普通指针*q。将p指针通过const_cast去掉其常量性,并赋给q指针。之后再修改q指针所指地址的值时就不会有问题。最后将结果打印出来,运行结果如下:
10 20 20
002CFAF4 002CFAF4 002CFAF4
查看运行结果,问题来了,指针p和指针q都是指向a变量的,指向地址相同,而且经过调试发现002CFAF4地址内的值确实由10被修改成了20,为什么a的值打印出来还是10呢?
变量a一开始就被声明为一个常量变量,不管后面的程序怎么处理,它就是一个常量,是不会变化的。试想一下如果这个变量a最终变成了20会有什么后果呢?对于这些简短的程序而言,如果最后a变成了20,我们会一眼看出是q指针修改了,但是一个项目工程非常庞大的时候,在程序某个地方出现了一个q这样的指针,它可以修改常量a,可以说是一个程序的漏洞,毕竟将变量a声明为常量就是不希望修改它。
称“*q=20”语句为未定义行为语句,未定义行为是指在标准的C++规范中并没有明确规定这种语句的具体行为,该语句的具体行为由编译器来自行决定如何处理。对于这种未定义行为的语句应该尽量予以避免。
可以看出我们不想修改变量a的值,既然如此定义一个const_cast关键字强制去掉指针的常量性有什么用呢?
#include
using namespace std;
const int * Search(const int * a, int n, int val);
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int val = 5;
int *p;
p = const_cast(Search(a, 10, val));
if(p == NULL)
cout<<"Not found the val in array a"<(new base);
p->m();
p->f();
return 0;
}
本例中定义了两个类:base类和derived类,这两个类构成继承关系。在base类中定义了m函数,derived类中定义了f函数。本例主函数中定义的是一个派生类指针,当将其指向一个基类对象时会导致编译错误。但是通过强制类型转换可以将派生类指针指向一个基类对象,这样的一种强制类型转换合乎C++语法规定但是会带来一定的危险。
在程序中p是一个派生类对象,将其强制指向一个基类对象,首先通过p指针调用m函数,因为基类中包含有m函数,这一句没有问题,之后通过p指针调用f函数。因为p指针是一个派生类类型的指针,而派生类中拥有f函数,因此p->f();这一语句不会有问题,但是本例中p指针指向的是基类的对象,而基类中并没有声明f函数,虽然p->f();这一语句虽然仍没有语法错误,但是它却产生了一个运行时的错误。换言之,p指针是派生类指针,这表明可以通过p指针调用派生类的成员函数f,但是在实际的程序设计过程中却误将p指针指向了一个基类对象,这就导致了一个运行期错误。
产生这种运行期的错误原因在于static_cast强制类型转换时并不具有保证类型安全的功能,而C++提供的dynamic_cast却能解决这一问题,dynamic_cast可以在程序运行时检测类型转换是否类型安全。当然dynamic_cast使用起来也是有条件的,它要求所转换的操作数必须包含多态类类型(即至少包含一个虚函数的类)。
#includeusing namespace std;
class base
{public :
void m(){cout<<"m"<(new base);
p->m();
p->f();
return 0;
}
本例中利用dynamic_cast进行强制类型转换,因为base类中并不存在虚函数,因此p = dynamic_cast
#include
#include
using namespace std;
class A
{
public:
virtual void f()
{cout<<"hello"<(a1);//结果为not null,向下转换成功,a1之前指向的就是B类型的对象,所以可以转换成B类型的指针。
if(b==NULL)
{cout<<"null"<(a2);//结果为null,向下转换失败
if(b==NULL)
{cout<<"null"<(a);//结果为null,向下转换失败
if(c==NULL)
{cout<<"null"<
类型别名(type alias)是一个名字,它是某种类型的同义词。使用类型别名让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
新标准规定了一种新的方法, 使用别名声明( alias declaration)来定义类型的别名:
using SI = Sales_item; // SI是Sales_item的同义词
using double = wages;
这种方法用关键字using作为别名声明的开始,其后紧跟别名和等号,其作用是把等号左侧的名字规定成等号右侧类型的别名。类型别名和类型的名字等价,只要是类型的名字能出现的地方,就能使用类型别名:
wages hourly, weekly; // 等价于double hourly. weekly;
SI item; // 等价于Sales_item item
编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚地知道表达式的类型。然而要做到这一点并非那么容易, 有时甚至根本做不到。为了解决这个问题C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。和原来那些只对应一种特定类型的说明符(比如double) 不同,auto 让编译器通过初始值来推算变量的类型。auto 定义的变量必须有初始值:
auto item = vall + val2; //item 初始化为vall和va12相加的结果
此处编译器将根据vall和val2相加的结果来推断item的类型。如果这两个变量的类型是double,则item的类型就是double,以此类推。使用auto也能在一条语句中声明多个变量。因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须-样:
auto i=0,*p=&1; //正确: 1是整数、p是整型指针
auto sz=0, pi=3.14; //错误: sz和pi的类型不一致
有时希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。为了满足这一要求, C++11 新标准引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值:
decltype(f()) sum = x; // sum 的类型就是函数f的返回类型
const int ci=0,&cj=ci;
decltype(ci) x = 0; // x的类型是const int
decltype(cj) y = x; // y的类型是const int&, y绑定到变量x .
decltype(cj) z; //错误: z是一个引用,必须初始化.
因为cj是一个引用,decltype(cj)的结果就是引用类型,因此作为引用的z必须被初始化。引用从来都作为其所指对象的同义词出现,只有用在decltype 处是一个例外。
// decltype 的结果可以是引用类型
int i = 42,*p = &i, &r = i;
decltype(r + 0) b; // 正确:加法的结果是int,因此b是一个(未初始化的) int
decltype(*p) c; //错误: c是int&,必须初始化
因为r是一个引用,因此decltype (r)的结果是引用类型。如果想让结果类型是r所指的类型,可以把r作为表达式的一部分,如r+0,显然这个表达式的结果将是一个具体值而非一个引用。另一方面,如果表达式的内容是解引用操作,则decltype将得到引用类型。解引用指针可以得到指针所指的对象,而且还能给这个对象赋值。因此,decltype (*p)的结果类型就是int&,而非int。
如果decltype 使用的是一个不加括号的变量,则得到的结果就是该变量的类型:如果给变量加上了一层或多层括号,编译器会把它当成表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型:
// decltype的表达式如果是加上了括号的变量,结果将是引用
decltype((i)) d; //错误: d是int&,必须初始化
decltype(i) e; //正确: e是一个(未初始化的) int