文章目录
- C/C++类型转换
-
-
- [1. C语言中的类型转换](#1. C语言中的类型转换)
- [2. C++中的类型间的隐式转换](#2. C++中的类型间的隐式转换)
- [3. C++4种类型转换](#3. C++4种类型转换)
- [4. C++类型转换对比C语言类型转换](#4. C++类型转换对比C语言类型转换)
-
C/C++类型转换
1. C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化。C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
- 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败 。具有强关联的数据之间才可以进行转换。比如整形家族(char,double等),都可以转为int。
- 显式类型转化:需要用户自己处理 ,但是也需要具有关联,比如不同类型的指针之间转换。
cpp
void Test ()
{
int i = 1;
// 隐式类型转换
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显示的强制类型转换
int address = (int) p;
printf("%x, %d\n", p, address);
}
缺陷:
转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换
在C++中也兼容C语言的类型转换,同时还加入了内置类型转为自定义类型,以及自定义类型转为内置类型。
2. C++中的类型间的隐式转换
内置转内置
C++中内置类型之间的隐式转换与C语言中完全相同,强相关的类型可以直接转化
内置转自定义
在C++中,我们经常使用一个内置类型去给自定义类型复制,比如使用字符串复制给string,或者
cpp
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A(int val = 0):_val(val){}
private:
int _val;
};
class B {
public:
B(int val = 0, string data = string()) :_val(val), _data(data) {}
private:
int _val;
string _data;
};
int main()
{
string obj1;
obj1 = "this is const char";
A obj2;
obj2 = 3;
B obj3;
obj3 = { 2, "the val is 2" };
return 0;
}
单参数的构造函数支持隐式类型转换,在这种情况下,我们可以使用参数直接去给自定义类型对象赋值,编译器可以帮助我们将参数自己的类型转换为自定义类型,在进行赋值。其本质是构造临时对象在赋值。
以obj1为例,当使用一个字符串 "this is const char"给string 对象赋值的时候,会先使用该字符串构建出一个临时的对象,在使用该临时的对象去给obj1进行赋值拷贝,达到"转化类型"的目的。
等价于
obj1 = string("this is const char");
多参数的构造函数可以支持列表初始化,只需将多个参数用花括号{}扩起来,就可以进行隐式类型转换。本质也是使用花括号列表里的参数构造出临时对象在复制。
obj3的赋值等价于
obj3 = B( { 2, "the val is 2" } );
自定义转内置
要想将自定义类型转为内置类型,达到以下目的:
A obj1(5);
int a = obj1;
B obj2( { 2, "the val is 2" } );
int b = obj2;
可以理解为我们想要做到以下效果:
int a = (int)obj1;
int b = (int)obj2;
那么我们就可以在类内写这样的函数来支持
int operator( )
{ //reutrn ......; }
但是重载()来进行类型转换则无法与仿函数进行区分,函数调用的参数也是用()符号来使用,则C++为了实现类型转换,为其做了特殊处理,可以在operator后面带上类型,表示返回值类型,在类型后面加上括号,表示这是类型转化,则可以达到自定义转为内置类型的目的
operator int( )
{ //reutrn ......; }
代码示例:
cpp
#include <iostream>
#include <string>
using namespace std;
class A {
public:
A(int val = 0):_val(val){}
operator int()
{
return _val;
}
private:
int _val;
};
class B {
public:
B(int val1 = 0,int val2 = 0, string data = string()) :_val1(val1), _val2(val2), _data(data) {}
operator int()
{
//return _val1;返回val1
//自定义返回值,只要是int即可
//也可以根据实际需要返回想要的内容
// 返回两数之和
return _val1 + _val2;
}
private:
int _val1;
int _val2; //加第二个成员为了演示类型转换的返回值是自定义的
string _data;
};
int main()
{
A obj1(3);
int a = obj1;
B obj2({ 2, 3, "the val is 2" });
int b = obj2;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
return 0;
}
运行结果:

自定义转自定义
当一个类的构造函数中重载了另一个类为参数的函数时,此时就可以发生类型转换
cpp
class C {
public:
C(const string& s1,const string& s2):_s1(s1),_s2(s2){}
string gets1() const
{
return _s1;
}
string gets2() const
{
return _s2;
}
private:
string _s1;
string _s2;
};
class D {
public:
D(const string& str = string()):_str(str){}
//使用C类对象为参数重载构造函数
D(const C& obj):_str(obj.gets1() + obj.gets2()){}
string getstr() const
{
return _str;
}
private:
string _str;
};
int main()
{
C obj1({ "one", "two" });
D obj2;
obj2 = obj1; //调用构造函数,传参obj1,生成临时对象后再赋值
cout << obj2.getstr() << endl;
return 0;
}
运行结果:

3. C++4种类型转换
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast, reinterpret_cast, const_cast, dynamic_cast
static_cast
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换
cpp
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout<<a<<endl;
return 0;
}
reinterpret_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型
cpp
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
// 这里使用static_cast会报错,应该使用reinterpret_cast
//int *p = static_cast<int*>(a);
int *p = reinterpret_cast<int*>(a);
return 0;
}
const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值
cpp
void Test()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl;
int b = 3;
cout << b << endl;
}
int main() {
Test();
return 0;
}

const修饰的变量叫做常变量,在栈上存储,按道理不应该被修改,但是我们可以通过指针强转去修改,如上述代码。
但是修改之后为什么值没有变?
我们先看一下内存是如何变化的:
执行前:

执行后:

可以看到,在内存里已经成功将a的值修改,即将常变量修改,那么为什么打印出来的a还是2呢?
通过反汇编,我们可以很清楚的看到,在定义的时候,a与b相同,都是在栈上定义,但是在输出的时候,a输出的时候直接将2写入寄存器edx ,在进行输出edx内容,就类似于宏定义一样,直接将a替换为2,而b的输出则是去内存里将b的数据读取到edx,在输出edx内容。

常变量在vs下,直接替换变量的值,就类似于宏定义一样,因此使用指针修改了,但是再去访问常变量a还是没有修改的内容,这也是为什么我们打印a看到的还是2的原因。
使用volatile关键字,表示变量不稳定,强制去内存里拿数据,如果使用该关键字修饰常变量a,那么打印出来将是修改后的内存数据3.

dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)

存在上图的类模型,那么有以下类型转换:
-
B* -> A* 赋值兼容,如下图A* ptr1 = ptr0,
-
A* -> B* 存在两种情况:
-
B* -> A* -> B*, 由赋值兼容规则转换得到的的A *指针,其本身指向的就是B类型对象,因此在转换回B *也没有任何问题,
对应下图B * ptr2 = (B*) ptr1.
-
A* -> B*, 如下图的 B * ptr4 = (B*)ptr3,强行转化会导致发生越界访问问题,这是不被允许的!
-


因此C++加入了dynamic_cast,用于多态体系中的类型转化,当有A* -> B*的时候,即父类指针转为基类指针的时候,其内部会做判断。
- 可以转换则进行转换,即其指针本身指向的对象是派生类对象,由于赋值兼容变为基类对象的指针,会进行转换
- 不可以转换则转换失败返回nullptr,对于引用抛出std::bad_cast异常,即其本身指向的对象是基类对象,转换会越界,因此转换失败。
注意:
- dynamic_cast只能用于父类含有虚函数的类
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回nullptr,对于引用抛出std::bad_cast异常
代码示例:
cpp
class A
{
public:
virtual void f(){}
};
class B : public A
{};
void fun(A* pa)
{
//使用static_cast进行转换,不做检查,与强转相同,存在风险
B* pbl = static_cast<B*>(pa);
// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回nullptr
B* pb2 = dynamic_cast<B*>(pa);
cout << "pbl:" << pbl << endl;
cout << "pb2:" << pb2 << endl;
}
int main() {
A a;
B b;
fun(&a);
fun(&b);
return 0;
}

注意
强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是否还有其他不同的方法达到同一目的。如果非强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会。强烈建议:避免使用强制类型转换
dynamic_cast底层依赖了多态中的虚表,C++类的多态特性是运行时多态,因此该类型转化也是运行时转化,编译器在运行时进行类型识别,也被称作RTTI(Run-time Type identification),除了dynamic_cast之外,C++中还有typeid运算符和decltype来支持RTTI。
4. C++类型转换对比C语言类型转换
C语言中的类型转换只有隐式类型转换和强制类型转换,虽然简单,但是不直观也不安全,而C++引入了4种类型转换,在一些方面解决了C语言类型转换的不足:
- 隐式类型转化有些情况下可能会出现问题:比如数据精度丢失
- 显式类型转换将所有情况混合在一起,代码不够清晰
因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。 他们之间也有对应关系,如下所示:
| 转换种类 | C语言 | C++ | 主要优点 | 主要缺点 |
|---|---|---|---|---|
| 隐式/显式安全转换 | 隐式类型转换 | static_cast | 明确安全转换意图,编译时检查 | 不能用于无关类型指针转换 |
| 重新解释转换 | 强制类型转换 | reinterpret_cast | 明确标识危险操作,代码中易识别 | 与C语言强制转换一样危险 |
| 常量性转换 | 强制类型转换 | const_cast | 职责单一,仅用于常量性消除 | 滥用会导致未定义行为 |