文章目录
- C语言类型转换
- 隐式类型转换
- 显式类型转换
- C++ 强制类型转换
- static_cast
- reinterpret_cast
- const_cast
- dynamic_cast
- 类的隐式类型转换
- 概念
- 只允许一步类类型转换
- explicit 抑制构造函数定义地隐式转换
- 可以通过显式转换使用explicit构造函数
C语言类型转换
隐式类型转换
编译器在编译阶段自动进行,通常适用于相近的类型,如果不能转换则会编译失败。
何时发生隐式类型转换:
- 在大多数表达式中,比int类型小的整数值提升为较大的整数类型
- 在条件中,非布尔值转换成布尔类型
- 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转化成左侧运算对象的类型
- 算术运算或关系运算对象有多种类型,需要转换成同一种类型
- 函数调用时也会发生类型转换
显式类型转换
需要用户自己处理,通常用于不相近类型的转换。
int main()
{int i = 9;int* p = &i;//将指针转换为地址int addr = (int)p;cout << addr;
}
C语言的类型转换使用起来很简单,但是也有很大的缺点:
- 隐式类型转换 可能会因为整形提升或者数据截断导致 精度的丢失,并且有时候会因为 忽略隐式类型转换导致错误发生 。
- 显示类型转换 代码不够清晰,没有很好的将各种情况划分开,而是全部混在一起使用。
C++ 强制类型转换
C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast
static_cast
static_cast
用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast ,但它不能用于两个不相关的类型进行转换。(即对应C语言中的隐式类型转换)
int main()
{double d = 1.9;int i = static_cast<int>(d);cout << i;
}
reinterpret_cast
reinterpret_cast
是一种较为危险的类型转换,通常为操作数的位模式提供较低层次的重新解释,用于 将一种类型转换为另一种不同的类型 ,通常适用于指针、引用、以及整数之间的类型转换。
int main()
{int i = 9;int* p = &i;double* p2 = reinterpret_cast<double*>(p);cout << *p2 << ' ' << *p;
}
如上面所说,这种转换十分容易导致错误的发生,因为指针类型其实是其指向的地址的类型,决定了指针看待这段地址的方式,它该如何读取数据。这里我把 int
改成了 double
,使得他原本应该读取 4个字节
,而变成了 8个字节
,就导致了数据的变化。如果使用不当很容易会造成越界访问导致程序崩溃。
这篇文章也很有意思。
const_cast
const_cast
通常用于删除变量的const属性。
如果想要修改 const变量的值
,就需要用 volatile
来取消编译器优化。因为 const变量
创建后会被放入寄存器中,只有我们取 const变量
的地址时,他才会在内存中申请空间。通常我们想要修改的是 const变量在内存中的值
,但是由于 编译器优化,当使用 const变量
时就会到 寄存器 中去取值,所以需要用 volatile
取消编译器优化,让其每次在内存中取值。
不加 volatile
时:
int main()
{const int ci = 10;int* pi = const_cast<int*>(&ci); // 对应c语言强制类型转换中去掉const属性的(不相近类型)*pi = 20;cout << ci << ' ' << *pi;
}
可以看出 ci
的 const属性
仍在,其值并未更改,只改了 *pi
的值。
加 volatile
时:
int main()
{volatile const int ci = 10;int* pi = const_cast<int*>(&ci); // 对应c语言强制类型转换中去掉const属性的(不相近类型)*pi = 20;cout << ci << ' ' << *pi;
}
dynamic_cast
dynamic_cast
是一种动态的类型转换,是C++新增的概念,用于将一个 父类对象的指针/引用转换为子类对象的指针/引用 。
派生类
可以赋值给 基类
的对象、指针或者引用,这样的赋值也叫做对象切割。
例如Human类派生出的Student类:
当把 子类 赋值给 父类 时,可以通过切割掉多出来的成员 _stuNum
的方式来完成赋值。
但是 基类 对象如果想赋值给 派生类 ,则不可以,因为他不能凭空多一个 _stuNum
成员出来。
但是基类的 指针或者引用 却可以强制类型转换赋值给派生类对象。
- 这个过程有可能成功,也有可能会因为越界导致出现问题。 如果使用C语言的强制类型转换,很可能就会出现问题,因为其没有安全保障。
- 而如果使用
dynamic_cast
,则能够保证安全,因为其会先检查转换是否能够成功,如果不能成功则返回0
,能则直接转换。但是dynamic_cast
的向下转换只支持继承中的 多态 类型,也就是 父类之中必须包含虚函数 。
int main()
{Human h1;Student s1;Human* hPtrs = &s1; // 指向派生类对象Human* hPtrh = &h1; // 指向基类对象//传统方法Student* pPtr = (Student*)hPtrs; // 没问题Student* pPtr = (Student*)hPtrh; // 有时候没有问题,但是会存在越界风险//dynamic_castStudent* pPtr = dynamic_cast<Student*>(hPtrh);return 0;
}
dynamic_cast
是如何识别父类的指针指向的是父类对象还是子类对象的呢?
其原理就是在运行时通过查找虚函数表上面的标识信息,来确认其指向的到底是父类还是子类,这也就是为什么只能用于含有虚函数的类。
这种在 运行中进行类型识别的方法 ,也叫做 RTTI
,C++中有很多支持 RTTI
的方法,如 dynamic_cast
、typeid
、decltype
。
类的隐式类型转换
概念
类的隐式类型转换: 如果类有需要 一个实参 的构造函数(或者构造函数虽然有 N
个参数,但这些参数都有缺省值),那么可以使用 该实参类型 构造一个 具有常量属性的临时类。这样的行为看起来像从 实参类型 到 类类型 的隐式转换。
示例如下:
class People{int age;string name;
public:People(int a){age = a;name = "zhangsan";}People(string b){age = 10;name = b;}People(){age = 21;name = "lihua";}People& add(const People &temp){age += temp.age;return *this;}People& revise(const People &temp){name = temp.name;return *this;}int getage(){return this->age;}string getname(){return this->name;}
};
int main()
{int num = 30;string who = "zhaoliu";People p1,p2(12);p1.add(num);cout << p1.getage() << endl;p2.revise(who);cout << p2.getname() << endl;
}
输出结果:
在 People
类中,接收 int
和 string
的构造函数分别运用了People类类型的隐式转换机制(即分别定义了从 int
、string
类型向 People
隐式转化的规则)。换言之,在需要People的地方,我们可以使用string或者int作为替代。
在上述代码main函数中:
int num = 30;
p1.add(num);
// 用num构造一个临时的People对象,该对象的name为空串,age等于30
我们用int实参充当了add的成员。该操作是合法的,编译器用给定的int自动创建了一个People对象。新生成的这个临时People对象被传递给add。因为add的参数是一个常量引用,所以我们可以给该参数传递一个临时量。(因为临时量具有常量属性)
那如果add的参数不是常量引用,以上操作还可以进行吗?
答案是否定的,因为num构造临时的people经过了形如下式的转化:
const People temp = People(num);
之后是将temp临时量作为实参传入add函数
p1.add(temp)
而如若add的形参只是一个普通引用的话,是无法绑定具有常量属性的临时量temp的。
只允许一步类类型转换
编译器只会自动地执行一步类型转换。
下面的调用是错误的:
p2.revise("huangba");
// error:隐式地使用了两种转换规则
// (1)把"huangba"转换成string
// (2)再把临时的string转换成People
如果想完成上述调用,可以显式地将字符串转换为string或者People对象:
// 显式地转换成string,隐式地转化成Peoplep2.revise(string("huangba"));// 显式地转换成People,隐式地转化成stringp2.revise(People("huangba"));
explicit 抑制构造函数定义地隐式转换
我们之前提到,如果构造函数有多个参数,但这些参数都有默认实参,那么同样遵循类的隐式转换规则:
class Date
{
public:Date(int year, int month = 4, int day = 24):_year(year),_month(month),_day(day){}int _year;int _month;int _day;
};int main()
{Date d1(2020, 4, 24);Date d2 = 2020;//C++98Date d3 = { 2020, 5 }; //C++11Date d4 = { 2020, 5, 26 }; //C+11
}
这里其实是先用这个整型值来调用了缺省的构造函数来创建了一个临时对象,再使用这个对象来为 d2、d3、d4
赋值 。
如果想要禁止隐式转换,可以通过将构造函数声明为 explicit
加以阻止:
此时,没有任何构造函数能用于隐式地创建 Date 对象,隐式转换就不会发生了。
- 关键字
explicit
只对需要一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit
。 - 只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复。
explicit
构造函数只能用于 直接初始化 ,而且,编译器不会在自动转换过程中使用该构造函数。如下:
可以通过显式转换使用explicit构造函数
编译器不会将具有 explicit
属性的构造函数用于隐式转换过程,但是我们可以显式地进行转换(强制类型转换):