1. C语言中的类型转换
在 C 语言中,如果 赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与 接收返回值类型不一致时,就需要发生类型转化 , C 语言中总共有两种形式的类型转换: 隐式类型
转换和显式类型转换 。
1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
2. 显式类型转化:需要用户自己处理,格式为 (type_name)
expression,其中 type_name 是目标类型,expression是要转换的表达式。
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 风格的转换格式很简单,但是有不少缺点的:
1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
2. 显式类型转换将所有情况混合在一起,代码不够清晰
2. C++的类型转换
C语言中的类型转换都是内置类型之间的转换,到C++后,出现了类的概念,于是又多出了以下与类相关的转换:
- 内置类型 → 自定义类型,依赖构造函数
- 自定义类型 → 自定义类型,依赖构造函数
- 自定义类型 → 内置类型,依赖操作符重载
前两种比较简单,如下
class A
{
public:
A(const int& sta)
:_sta(sta)
{}
private:
int _sta;
};
int main()
{
A tmp = 10;
return 0;
}
第三种,则需要通过操作符重载来实现,如下
class A
{
public:
A(const int& a, const int& b)
:_a(a)
,_b(b)
{}
operator int()
{
return _a + _b;
}
operator double()
{
return _a - _b;
}
private:
int _a;
int _b;
};
int main()
{
A tmp(5, 10);
int t1 = tmp;
double t2 = tmp;
return 0;
}
通过操作符重载让类A
支持两种类型转换:A->int
和A->double
,格式为operator type_name (),operator type_name就可以完成到type_name类型的转换。注意的是,类型转换的返回值是固定的,所以operator type_name左侧不用写函数返回类型。
标准 C++ 为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast 、 reinterpret_cast 、 const_cast 、 dynamic_cast
2.1 static_cast
static_cast用于非多态类型的转换(静态转换),例如基本数据类型之间的转换,或者在类层次结构中进行上行转换(基类指针或引用转换为派生类指针或引用),这种上行转换是安全的。但其不进行运行时类型检查,因此在进行下行转换(派生类指针或引用转换为基类指针或引用)时,如果转换不合法,编译器不会报错,但在运行时可能会导致未定义行为. 编译器隐式执行的任何类型转换都可用 static_cast ,但它不能用于两个不相关的类型进行转换.
它的作用包括:
- 基本数据类型之间的转换,如将
double
转换为int
。
int main()
{
double a = 5.5;
int b = static_cast<int>(a);
return 0;
}
- 类层次结构中的向上转型,即将子类类型的指针或引用转换为基类类型。
class Base
{
//...
};
class Derived : public Base
{
//...
};
int main()
{
Derived a;
Base* b = static_cast<Base*>(&a);
return 0;
}
- 指针或引用类型之间的转换,但不支持多态类型的向下转型。
int main()
{
void* a = new int;
int* b = static_cast<int*>(a);
return 0;
}
static_cast 不执行运行时类型检查,因此在使用时需要程序员确保转换安全。即 static_cast 用在类型之间进行安全的转换
2.2 reinterpret_cast
reinterpret_cast 是 C++ 中用于执行底层的、不安全的类型转换的运算符。它可以将指针类型转换为整数类型,或者将整数类型转换为指针类型,也可以在指针类型之间进行转换,但不进行类型安全检查。 其通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型.
- 将一个指针转换为整型
int main()
{
int* a = new int(5);
int b = reinterpret_cast<int>(a);
return 0;
}
- 在不同的指针类型之间进行转换
int main()
{
int* a = new int(5);
double* b = reinterpret_cast<double*>(a);
return 0;
}
2.3 const_cast
const_cast 是 C++ 中的一个类型转换运算符,用于移除或添加 const 或 volatile
限定符。需要注意的是,const_cast 中的类型必须是指针、引用或指向对象类型成员的指针。
它主要用于以下几种情况:
- 当你需要传递一个
const
对象到一个期望非const
参数的函数时,可以使用const_cast
来去除const
限定符。
void func(int& tmp)
{}
int main()
{
const int a = 5;
int* b = const_cast<int*>(&a);
func(*b);
return 0;
}
- 在某些设计中,可能需要修改一个原本声明为
const
的对象,这时可以使用const_cast
来去除const
限定符,但这种做法应该非常谨慎,因为它可能会导致未定义行为,尤其是当原始对象实际上是存储在常量内存区域时。
int main()
{
const int a = 5;
int* b = const_cast<int*>(&a);
*b = 10; // 修改常量对象的值
cout << "a:" << a << endl;
cout << "b:" << *b << endl;
return 0;
}
如上代码中a为const对象,我们使用const_cast
来去除 const
限定符得到b,并修改b的值为10,再打印出来时却发现a仍为5。为什么会这样呢?
实际上,C++的常量不存储在常量区,而是存储在栈区,而编译器会对const
变量优化,把const
变量放到寄存器中,后续只要访问变量a
,都去寄存器中查找。我们通过指针b修改的变量,其实修改的是栈区中的数据,没有修改寄存器的数据,因此访问a
还是从寄存器中读出了5。
为了解决这个问题,可以用volatile
关键字修饰const变量,此时就不会把常量存在寄存器中了。
int main()
{
volatile const int a = 5;
int* b = const_cast<int*>(&a);
*b = 10; // 修改常量对象的值
cout << "a:" << a << endl;
cout << "b:" << *b << endl;
return 0;
}
同样的可以使用 const_cast
来去除 volatile
限定符
int main()
{
volatile const int a = 5;
const int* b = const_cast<const int*>(&a);
return 0;
}
2.4 dynamic_cast
dynamic_cast 用于将一个父类对象的指针 / 引用转换为子类对象的指针或引用 ( 动态转换 )
向上转型:子类对象指针 / 引用 -> 父类指针 / 引用 ( 不需要转换,赋值兼容规则 )
向下转型:父类对象指针 / 引用 -> 子类指针 / 引用 ( 用 dynamic_cast 转型是安全的 )
注意:
1. dynamic_cast 只能用于父类含有虚函数的类
2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回空指针(对于指针类型)或抛出std::bad_cast异常(对于引用类型)。
进行向下转型时, dynamic_cast 提供类型安全检查,这是 static_cast 不具备的。 static_cast 在编译时进行类型检查,但不进行运行时检查,因此可能导致未定义行为。相比下 dynamic_cast 更加安全,尤其是在处理复杂的类层次结构时.
class Base
{
public:
virtual void func()
{}
};
class Derived : public Base
{
//...
};
int main()
{
Base a;
Derived b;
Base* c = dynamic_cast<Base*>(&b);//向上转型
Derived* d = dynamic_cast<Derived*>(&a);//向下转型
cout << "c:" << c << endl;
cout << "d:" << d <<endl;
return 0;
}

我们可以看到的是c转型成功,而d转型失败,为空指针。
3.运行时类型信息(RTTI)
运行时类型信息(RTTI)是C++中的一项特性,它允许程序在运行时识别和检查对象的类型。
C++ 通过以下方式来支持 RTTI :
1. typeid 运算符
2. dynamic_cast 运算符
3. decltype