C++ 命名类型转换
目录
- 传统艺能😎
- 类型转换🤔
- 四大类型转换🤔
- static_cast😋
- reinterpret_cast😋
- const_cast😋
- dynamic_cast😋
- explicit🤔
- RTTI🤔
传统艺能😎
小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
1319365055
🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,满怀希望,所向披靡,打码一路向北
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我
类型转换🤔
任何一门语言里面都会遇到赋值符号两边不同的情况,或者形参和实参不匹配,这种情况就存在类型转换,特别是 C++ 这种强类型语言,即使不发生显示的强制转换也会进行隐式类型转换
其实在C语言阶段就早有提出两种类型转换方式:
- 显示类型转换:需要手动转换成需要的类型对象:(指定类型)变量
- 隐式类型转换:在代码编译阶段自动执行,能转就转,不能转就编译失败
当然并不是显示转换就可以随心所欲的转换, 只有相近类型之间才能发生隐式类型转换 \color{red} {只有相近类型之间才能发生隐式类型转换} 只有相近类型之间才能发生隐式类型转换,比如 int 和 double 表示的都是数值,不过它们的范围和精度不同。而指针类型表示的是地址编号,因此整型和指针类型之间不会进行隐式类型转换,如果需要转换则只能进行显式类型转换:
int main()
{
//显式类型转换
int* p = &i;
int address = (int)p;
cout << p << endl;
cout << address << endl;
return 0;
//隐式类型转换
int i = 1;
double d = i;
cout << i << endl;
cout << d << endl;
}
四大类型转换🤔
介于类型转换的场合非常多,且存在一些复杂场景的类型转换,单纯的 C 语言的类型转换虽然简单,但是存在精度丢失、可视性差等缺陷,于是 C++ 标准推出了四大命名类型转换操作符:
static_cast
reinterpret_cast
const_cast
dynamic_cast
static_cast😋
static_cast 用于相近类型之间的转换,编译器隐式执行的任何类型转换都可以用 static_cast,但它不能用于不相关类型之间的转换:
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
int* p = &a;
// int address = static_cast<int>(p); //error
return 0;
}
reinterpret_cast😋
reinterpret_cast 用于两个不相关类型之间的转换:
int main()
{
int a = 10;
int* p = &a;
int address = reinterpret_cast<int>(p);
cout << address << endl;
return 0;
}
他除了用于两个不相关类型的转换,还有一个强有力的功能:可以将带参带返回值的函数指针转换成了无参无返回值的函数指针,并且此时转换后函数指针还可以调用这个函数:
typedef void(*FUNC)();
int DoSomething(int i)
{
cout << "DoSomething: " << i << endl;
return 0;
}
int main()
{
FUNC f = reinterpret_cast<FUNC>(DoSomething);
f();
return 0;
}
用转换后的函数指针调用该函数时没有传入参数,因此这里打印出参数 i 的值是一个随机值
const_cast😋
const_cast 用于删除变量的 const 属性,转换后就可以对 const 变量的值进行修改:
int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl; //2
cout << *p << endl; //3
return 0;
}
代码中用 const_cast 删除了变量 a 地址的 const 属性,这时就可以通过这个指针来修改变量 a 的值
由于编译器认为 const 修饰的变量是不会被修改的,因此会将 const 修饰的变量存放到寄存器当中,当需要读取 const 变量时就会直接从寄存器中进行读取, 而我们修改的实际上是内存中的 a 的值,因此最终打印出 a 的值是未修改之前的值 \color{red} {而我们修改的实际上是内存中的 a 的值,因此最终打印出a的值是未修改之前的值} 而我们修改的实际上是内存中的a的值,因此最终打印出a的值是未修改之前的值
如果不想让 const 变量被优化到寄存器当中,可以用 volatile 关键字对 const 变量进行修饰,这时当要读取这个 const 变量时编译器就会从内存中进行读取,即保持了该变量在内存中的可见性
dynamic_cast😋
dynamic_cast 用于将父类的指针(或引用)转换成子类的指针(或引用),这就不得不提一下向上转型和向下转型了
其实早在继承和多态的博客中说过,向上转型是子类的指针(或引用)→ 父类的指针(或引用);向下转型是 父类的指针(或引用)→ 子类的指针(或引用)。向上转型就是所说的切割/切片,是语法天然支持的,不需要进行转换,而向下转型是语法不支持的,需要进行强制类型转换
- 其中向下转型是存在安全问题的:如果父类指向的是一个父类对象,那么将其转换为子类是不安全的,因为转换后可能会访问到子类的资源,而这个资源是父类对象所没有的
- 如果父类的指针(或引用)指向的是一个子类对象,那么将其转换为子类的指针(或引用)则是安全的
若果要用 C 语言的强制类型转换进行向下转型也是不安全的,因为无论父指向的是父类对象还是子类对象都会进行转换。而使用 dynamic_cast 进行向下转型则是安全的,但如果父类指向的是父类对象那么 dynamic_cast 会转换失败并返回空指针!
class A
{
public:
virtual void f()
{}
};
class B : public A
{};
void func(A* pa)
{
B* pb1 = (B*)pa; //不安全
B* pb2 = dynamic_cast<B*>(pa); //安全
cout << "pb1: " << pb1 << endl;
cout << "pb2: " << pb2 << endl;
}
int main()
{
A a;
B b;
func(&a);
func(&b);
return 0;
}
dynamic_cast 只能用于含有虚函数的类,因为运行时类型检查需要运行时的类型信息,而这个信息存储在虚函数表中,只有定义了虚函数的类才有虚函数表!
explicit🤔
explicit 用来修饰构造函数,从而禁止单参数构造函数的隐式转换:
class A
{
public:
explicit A(int a)
{
cout << "A(int a)" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
private:
int _a;
};
int main()
{
A a1(1);
//A a2 = 1; //error
return 0;
}
在语法上,代码中的A a2 = 1等价于以下两句代码:
A tmp(1); //先构造
A a2(tmp); //再拷贝构造
可见和上述这种用临时变量拷贝构造的方法不同,现在的编译器做了优化,当遇到 A a2 = 1这句代码时,会直接按照 A a2(1) 的方式进行处理,这也叫做隐式类型转换
对于单参数的自定义类型来说,A a2 = 1 这种代码可读性不是很好,因此可以用 explicit 修饰单参数构造函数,从而禁止单参数构造函数的隐式转换
RTTI🤔
RTTI 就是运行时类型识别,C++ 通过以下几种方式来支持 RTTI:
- typeid:在运行时识别出一个对象的类型。
- dynamic_cast:在运行时识别出一个父类的指针(或引用)指向的是父类对象还是子类对象。
- decltype:在运行时推演出一个表达式或函数返回值的类型