前言
记笔记~
一、C语言类型转换
隐式类型转换
意义相近的类型,如整型和浮点型,不需要用户干预,由编译器默认进行的类型转换,使得两个变量的数据类型一致。
int a = 3.14; // 浮点型隐式转换为整型
显式类型转换
// 格式:TYPE a = (TYPE)b;
double a = 3.14;
int b = (int)a;
存在的问题
C风格的强制类型转换是不安全的,可能会导致数据丢失,出现越界访问等问题。但是对父子类对象之间的强制转换是合法的,编译器不会检查错误。
class T1
{
public:
virtual void func() { cout << "T1::func()" << endl; }
};
class T2 : public T1
{
public:
virtual void func() { cout << "T2::func()" << endl; }
};
int main()
{
T1* t1 = new T1();
T2* t2 = (T2*)t1;
t2->func();
return 0;
}
/*
打印:
T1::func()
*/
t1指向的对象并不是T2类型的,将t1强制转换为T2类型,编译虽然通过,但会出现未定义的行为,因此不能将t1强制转换为T2的指针。
二、C++类型转换
const_cast
用于去除const/volatile属性,其返回值为非const的原类型。
const_cast只能用于指针/引用,并且改变的是底层const。
顶层const:指针/引用本身就是const
底层const:指针/引用指向的对象是const
注意常量指针/引用被转换为非常量指针/引用,仍然指向原来的对象,我们修改指针/引用,也是对原常量的修改。
class A
{
public:
A(int v) : val(v) {}
int val;
};
int main()
{
const A a1(10);
// a1.val = 100; // 错误,常量无法修改
A* a2 = const_cast<A*>(&a1); // 转换为指针
a2->val = 100;
cout << a1.val << endl; // 修改a2的成员,也就是修改a1的成员
A& a3 = const_cast<A&>(a1); // 转换为引用
a3.val = 1000;
cout << a1.val << endl;
return 0;
}
/*
打印:
100
1000
*/
static_cast
隐式类型转换,可以用于C++内置基本数据类型的相互转换,如int、char、float、enum等,也可以用于自定义类的向上转换和向下转换。
1.基本数据类型转换
需要开发人员确保转换的安全性
double d = 3.14;
int a = static_cast<int>(d);
cout << a << endl; // 打印 3
2.自定义类的转换
static_cast仅能用于向上转换和向下转换。向上转换,即派生类的指针/引用转换为基类的指针/引用,static_cast的向上转换是安全的;向下转换,即基类指针/引用转换为派生类的指针/引用,由于static_cast没有进行动态类型检查,因此是不安全的。
static_cast无法进行没有继承关系的类指针之间的转换
class A { };
class B : public A { };
class C { };
int main()
{
A a;
B b;
A* ap = static_cast<A*>(&b); // 向上转换,安全
B* bp = static_cast<B*>(&a); // 向下转换,不安全
// C* cp = static_cast<C*>(&a); // 错误,C和A没有继承关系
return 0;
}
3.void*转换
// void*转换为目标类型
void* vp = nullptr;
char* cp = static_cast<char*>(vp);
// 任意类型转换为void*
shared_ptr<char*> s_cp = make_shared<char*>(cp);
vp = static_cast<void*>(s_cp.get());
dynamic_cast
动态类型转换,dynamic_cast用于自定义类的向下转换,由于会在运行时进行安全性检查,因此相比于static_cast的向下转换更加安全。dynamic_cast也可以进行向上转换,与static_cast类似。
向下转换是安全的,也就是说如果基类指针/引用指向的就是派生类对象,转换成功,返回的是对应派生类的指针/引用;如果指针转换失败则返回空指针,引用转换失败则抛出bad_cast异常。
class Parent
{
public:
virtual void func() { cout << "Parent::func()" << endl; }
};
class Child : public Parent
{
public:
virtual void func() override { cout << "Child::func()" << endl; }
};
void Test(Parent* p)
{
Child* cp = dynamic_cast<Child*>(p);
if (cp)
{
cout << "Cast succeed!!!" << endl;
cp->func();
}
else
{
cout << "Cast failed..." << endl;
}
}
int main()
{
Parent p;
Child c;
Test(&p);
Test(&c);
return 0;
}
/*
打印:
Cast failed...
Cast succeed!!!
Child::func()
*/
注意:使用dynamic_cast,基类一定要有虚函数,否则编译不通过。这是因为dynamic_cast会在运行时通过虚函数表来检查类型是否是对应的派生类对象。
class T1
{
public:
virtual void func() { }
};
class T2
{
public:
virtual void func() { }
};
class T3 : public T1, public T2 { };
int main()
{
T3 t3;
T1* t1 = &t3;
T2* t2 = &t3;
cout << t1 << endl << t2 << endl << endl;
T3* t3_ptr1 = (T3*)t1;
T3* t3_ptr2 = (T3*)t2;
cout << t3_ptr1 << endl << t3_ptr2 << endl << endl;
T3* t3_ptr3 = dynamic_cast<T3*>(t1);
T3* t3_ptr4 = dynamic_cast<T3*>(t2);
cout << t3_ptr3 << endl << t3_ptr4 << endl;
return 0;
}
/*
打印:
0000000E7C0FF458
0000000E7C0FF460
0000000E7C0FF458
0000000E7C0FF458
0000000E7C0FF458
0000000E7C0FF458
*/
从打印结果来看,多继承情况下,指向T3对象的基类指针t1、t2按照继承顺序分别对t3的内存区域进行了切片,因此直接打印t1、t2的地址,二者是不同的。在进行强制类型转换/dynamic_cast之后,它们都指向了T3类型对象t3的起始地址。
reinterpret_cast
reinterpret_cast是C++中的强制类型转换,reinterpret有重新解释之意,因此该标识符在不改变值的情况下对数据的二进制形式进行重新解释。即reinterpret_cast可以将任何指针类型转换成任何其他的指针类型,不进行类型检查,可能会造成未定义行为,因此不到万不得已不要使用。
int main()
{
T3 t3;
cout << &t3 << endl
<< reinterpret_cast<T2*>(&t3) << endl
<< static_cast<T2*>(&t3) << endl
<< dynamic_cast<T2*>(&t3) << endl;
return 0;
}
/*
打印:
0000004A3F51F538
0000004A3F51F538
0000004A3F51F540
0000004A3F51F540
*/
上述代码中,前两个地址相同,后两个地址相同,原因在于static_cast和dynamic_cast在进行向上转换时实际计算了基类T2在派生类T3中的偏移量,并计算出正确的地址;而reinterpret_cast只进行强制转换,不会修改地址。
三、练习
1.C++有哪些类型转换的方法(关键字),各自有什么作用?
(1)const_cast,去掉const属性,但需要注意,const_cast只能用于指针或引用,修改的是底层const而不是顶层const,即修改的是指向的对象的const属性。
(2)static_cast,基本数据类型转换,如int、float、char、enum等。static_cast能对自定义类进行向上转换和向下转换,由于不进行动态类型检查,因此向下转换是不安全的。static_cast无法进行无关类型之间的转换。
(3)dynamic_cast,能安全地将基类指针或引用转换为派生类指针或引用,即向下转换。如果转换指针失败,则返回空指针,如果转换引用失败,则抛出bad_cast异常。dynamic_cast在运行时进行类型安全性检查;也可以进行向上转换;基类必须有虚函数,否则编译不通过。
(4)reinterpret_cast,强制类型转换,它对数据的二进制形式进行重新解释,但并不改变其值。它不会考虑类型安全,可以将任何内置数据类型转换为其他类型甚至指针,也可以将指针转换为其他类型,因此不到万不得已不要使用。
2.static_cast和dynamic_cast的异同点?
(1)static_cast和dynamic_cast都能进行向上转换和向下转换,但static_cast的向下转换是不安全的,dynamic_cast向下转换失败则返回空指针或抛出异常;
(2)static_cast和dynamic_cast都会进行类型检查,static_cast在编译期进行类型检查,而dynamic_cast在运行期进行类型检查;
(3)对于自定义类,dynamic_cast需要基类有虚函数,而static_cast则不需要。
总结
const_cast:修改底层const
static_cast:基本数据类型转换,向上转换(安全)、向下转换(不安全)
dynamic_cast:安全的向下转换
reinterpret_cast:强制转换,不安全