在C语言中,类型转换是一个非常重要的概念,它发生在多种情况下:当赋值运算符左右两侧的变量类型不同时(如int赋值给float);当函数调用时实参类型与形参类型不匹配时;或者函数返回值类型与接收返回值的变量类型不一致时。C语言提供了两种类型转换机制:
由编译器在编译阶段自动完成
遵循特定的转换规则,按照数据类型优先级(如double > float > int > char)自动提升
转换失败会导致编译错误
示例:
int i = 10;
float f = i; // 隐式将int转换为float需要程序员明确指定转换类型
使用强制类型转换运算符:(type)expression
可以用于指针类型转换等复杂场景
示例:
float f = 3.14;
int i = (int)f; // 显式将float强制转换为int在实际编程中,应该谨慎使用类型转换,特别是强制类型转换,因为它可能掩盖潜在的类型不匹配问题,导致运行时错误。
int main()
{
int i = 1;
// 隐式类型转换
// 隐式类型转换主要发生在整形和整形之间,整形和浮点数之间,浮点数和浮点数之间
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显式的强制类型转换
// 强制类型转换主要发生在指针和整形之间,指针和指针之间
int address = (int)p;
printf("%p, %d\n", p, address);
// malloc返回值是void*,被强转成int*
int* ptr = (int*)malloc(8);
// 编译报错:类型强制转换: 无法从“int *”转换为“double”
// 指针是地址的编号,也是一种整数,所以可以和整形互相转换
// 但是指针和浮点数毫无关联,强转也是不支持的
// d = (double)p;
return 0;
}运行结果:

• C++兼容C语言,因此支持C语言的所有隐式类型转换和显式强制类型转换方式。
• C++还支持内置类型与自定义类型之间的相互转换:
如:
// 内置类型和自定义类型之间
// 1、自定义类型 = 内置类型 ->构造函数支持
// 2、内置类型 = 自定义类型 ->operator 内置类型 支持
class A
{
public:
// 构造函数加上explicit就不支持隐式类型转换了
//explicit A(int a)
A(int a)
:_a1(a)
, _a2(a)
{}
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
// 加上explicit就不支持隐式类型转换了
// explicit operator int()
operator int() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 1;
};
class B
{
public:
B(int b)
:_b1(b)
{}
private:
int _b1 = 1;
};
int main()
{
// 单参数的转换
string s1 = "1111111";
A aa1 = 1;
A aa2 = (A)1;
// 多参数的转换
A aa3 = { 2,2 };
const A& aa4 = { 2,2 };
// 自定义类型转内置类型
int z = aa1.operator int();
int x = aa1;
int y = (int)aa2;
cout << x << endl;
cout << y << endl;
cout << z << endl;
std::shared_ptr<int> foo;
std::shared_ptr<int> bar(new int(34));
//if (foo.operator bool())
if (foo)
std::cout << "foo points to " << *foo << '\n';
else
std::cout << "foo is null\n";
if (bar)
std::cout << "bar points to " << *bar << '\n';
else
std::cout << "bar is null\n";
return 0;
}运行结果:

注意:单参数和多参数转换的本质,是通过隐式类型转换调用构造函数,构造一个临时对象,然后再拷贝给原对象,不过编译器优化成直接构造。
如果我们加上explicit就不支持隐式类型转换,我们这么写就会报错,因为单参数和多参数转换此时并不能通过隐式类型转换来构造,如下图所示:

• 对于自定义类型之间的转换,只需在目标类型中定义接收源类型参数的构造函数即可实现。例如,要将A类型对象转换为B类型,只需在B类中定义接收A类型参数的构造函数。
如:
class B
{
public:
B(int b)
:_b1(b)
{}
// 支持A类型对象转换为B类型对象
B(const A & aa)
: _b1(aa) // 调用aa.operator int()
{}
private:
int _b1 = 1;
};
int main()
{
// A类型对象隐式转换为B类型
B bb1 = aa1;
B bb2(2);
bb2 = aa1;
const B& ref1 = aa1; // 必须加const,引用临时对象,临时对象具有常性
return 0;
}通过调试窗口查看结果:

• 类型安全是指编程语言在编译和运行时提供保护机制,避免非法的类型转换和操作,导致出现内存访问错误等,从而减少程序运行时的错误。类型安全的语言通常具有以下特征:
• C语言不是类型安全的语言,主要表现在:
允许广泛的隐式类型转换,如int和指针之间的转换
强制类型转换(cast)操作过于自由,缺乏安全性检查
典型问题示例:
int a = 10;
double* p = (double*)&a; // 潜在的内存访问问题
*p = 3.14; // 可能导致未定义行为• C++虽然兼容C语言,支持隐式类型转换和强制类型转换,但为了改进类型安全性,引入了四种显式的命名强制类型转换:
static_cast:用于基本类型转换和具有继承关系的类之间的转换
double d = 3.14;
int i = static_cast<int>(d);reinterpret_cast:用于低级别的类型重新解释
int* p = new int(10);
long addr = reinterpret_cast<long>(p);const_cast:用于添加或移除const/volatile限定符
const int a = 10;
int* p = const_cast<int*>(&a);dynamic_cast:用于安全地进行多态类型转换
Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b);这些显式转换操作符的目的是:
void insert(size_t pos, char ch)
{
// 这里当pos==0时,就会引发由于隐式类型转换
// end跟pos比较时,提升为size_t导致判断结束逻辑出现问题
// 在数组中访问挪动数据就会出现越界,经典的类型安全问题
int end = 10;
while (end >= pos)
{
// ...
cout << end << endl;
--end;
}
}
int main()
{
insert(5, 'x');
//insert(0, 'x');
// 这里会本质已经出现了越界访问,只是越界不一定能被检查出来
int x = 100;
double* p1 = (double*)&x;
cout << *p1 << endl;
const int y = 0;
int* p2 = (int*)&y;
(*p2) = 1;
// 这里打印的结果是1和0,也是因为我们类型转换去掉了const属性
// 但是编译器认为y是const的,不会被改变,所以会优化编译时放到
// 寄存器或者直接替换y为0导致的
cout << *p2 << endl;
cout << y << endl;
return 0;
}insert在0下标处插入就会出现越界,也会造成死循环
这里我们来看看y的示例运行结果:

这里打印的结果是1和0,也是因为我们类型转换去掉了const属性,但是编译器认为y是const的,不会被改变,所以会优化编译时放到寄存器或者直接替换y为0导致的
编译器默认会优化代码(如缓存变量到寄存器、省略"冗余"访问)。如果你不想编译器这么做,可以使用volatile 关键字,volatile 强制每次访问都直接从内存读取/写入,确保数据是最新值。
volatile const int y = 0;
int* p2 = (int*)&y;
(*p2) = 1;
cout << *p2 << endl;
cout << y << endl;
static_cast - 静态类型转换用途:最常用的类型转换,用于编译时确定的类型转换 特点:
示例:
// 基本类型转换
double d = 3.14;
int i = static_cast<int>(d); // 3
// 类层次结构中的向上转换(安全)
class Base {};
class Derived : public Base {};
Derived* d = new Derived;
Base* b = static_cast<Base*>(d); // 向上转换
// 类层次结构中的向下转换(不安全!)
Base* base = new Base;
Derived* derived = static_cast<Derived*>(base); // 可能引发未定义行为
// void* 转换
int x = 10;
void* v = static_cast<void*>(&x);
int* p = static_cast<int*>(v);注意事项:
reinterpret_cast - 重新解释转换用途:低级别的位模式重新解释 特点:
示例:
// 指针与整数互转
int* p = new int(42);
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
// 不同类型指针互转
float f = 3.14f;
unsigned int bits = reinterpret_cast<unsigned int&>(f);
// 函数指针转换
using FuncPtr = void(*)();
auto func = reinterpret_cast<FuncPtr>(&someFunction);
// 不相关类指针转换(危险!)
class A {};
class B {};
A* a = new A;
B* b = reinterpret_cast<B*>(a); // 高风险风险提示:
const_cast - 常量性移除用途:添加或移除 const/volatile 属性
特点:
示例:
// 移除 const
const int ci = 10;
int* modifiable = const_cast<int*>(&ci);
*modifiable = 20; // 危险!原始常量对象被修改
// 合法使用:调用非 const 重载
const std::string str = "hello";
auto& nonConstStr = const_cast<std::string&>(str);
nonConstStr.clear(); // 合法前提是 str 原本非常量
// 添加 const
int x = 5;
const int* cx = const_cast<const int*>(&x);注意事项:
dynamic_cast - 动态类型转换用途:在继承层次结构中进行安全的向下转换 特点:
示例:
class A
{
public:
virtual void f() {}
int _a = 1;
};
class B : public A
{
public:
int _b = 2;
};
void fun1(A* pa)
{
// 指向父类转换时有风险的,后续访问存在越界访问的风险
// 指向子类转换时安全
B* pb1 = (B*)pa;
cout << "pb1:" << pb1 << endl;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
pb1->_a++;
pb1->_b++;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
}
void fun2(A* pa)
{
// dynamic_cast会先检查是否能转换成功(指向子类对象),能成功则转换,
// (指向父类对象)转换失败则返回nullptr
B* pb1 = dynamic_cast<B*>(pa);
if (pb1)
{
cout << "pb1:" << pb1 << endl;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
pb1->_a++;
pb1->_b++;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
}
else
{
cout << "转换失败" << endl;
}
}
void fun3(A& pa)
{
// 转换失败,则抛出bad_cast异常
try {
B& pb1 = dynamic_cast<B&>(pa);
cout << "转换成功" << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
}
int main()
{
A a;
B b;
//fun1(&a);
//fun1(&b);
fun2(&a);
fun2(&b);
fun3(a);
fun3(b);
return 0;
}注意:
向下转换分为两种情况:
实现原理:
特殊要求:
static_cast:满足大部分常规转换需求
reinterpret_cast:除非绝对必要且理解所有风险
(int)3.14,它可能意外执行 reinterpret_cast
• RTTI的英文全称是"Runtime Type Identification",中文称为"运行时类型识别",它指的是程序在运行时期确定对象类型信息的机制。与静态类型识别不同,RTTI允许程序在运行时(而不是编译时)动态获取对象的类型信息,这在多态场景下特别有用。例如,在处理基类指针指向派生类对象时,RTTI可以帮助确定实际的对象类型。
• RTTI主要由两个运算符实现:
• typeid运算符的详细说明:
• typeid运算符的行为分析:
当运算对象是以下情况时,返回静态类型(编译时确定):
当运算对象满足以下条件时,返回动态类型(运行时确定):
是定义了至少一个虚函数的类的左值
例如:
class Base { virtual void foo() {} };
class Derived : public Base {};
Base* b = new Derived;
// 这里typeid(*b)将在运行时返回Derived的类型信息• RTTI的应用场景:
注意:过度使用RTTI可能表明设计存在问题,良好的面向对象设计应该尽量通过虚函数实现多态行为。
int main()
{
int a[10];
int* ptr = nullptr;
cout << typeid(10).name() << endl;
cout << typeid(a).name() << endl;
cout << typeid(ptr).name() << endl;
cout << typeid(string).name() << endl;
cout << typeid(string::iterator).name() << endl;
cout << typeid(vector<int>).name() << endl;
cout << typeid(vector<int>::iterator).name() << endl;
return 0;
}运行结果:
// vs2019下的运行结果
int
int[10]
int*
class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >
class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char> > >
class std::vector<int, class std::allocator<int> >
class std::_Vector_iterator<class std::_Vector_val<struct std::_Simple_types<int> > >
// gcc 9.4下运行结果
i
A10_i
Pi
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
N9__gnu_cxx17__normal_iteratorIPcNSt7__cxx1112basic_stringIcSt11char_traitsIcES
aIcEEEEE
St6vectorIiSaIiEE
N9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE