目录
写在前面
鸽了好多天了,这几天惰性使然博主休息了一下。磨刀不误砍柴工,这几天会逐渐赶上之前的学习进度。今天带来的是cpp11风格类型转换的使用方法,对于各种使用方法给出了相应的测试代码。总体来说应该是覆盖了cpp中的重点强制转换的使用场景。对于cpp风格转换的使用当然是值得鼓励的,但是也要避免这类操作的滥用,在执行操作之前一定要清楚自己的行为。接下来就同博主一起练习一下吧。
类型转换的方法
-
c风格的强制类型转换:
-
type var1 = (type) var;
-
eg:
int a = (int) b;
-
-
cpp新标准的强制类型转换:
-
type var1 = type_operator<type> (var);
-
type_operator = static_cast|| reinterpret_cast|| const_cast || dynamic_cast <>
-
static_cast
-
static_cast
用于静态类型转换,比较温和的转换例如int >> char
-
主要用法:
-
对于基本类型中可能会发生数据的丢失问题如
double >> int
,这种情况需要开发人员对自己的操作有着深刻的理解。 -
用于类层次结构中基类和派生类之间指针或者引用的转换。
-
在类层次结构转换中从派生类到基类的转换是安全的,而反方向可能不安全。
-
static_cast
方法可以吧空指针转换成任意类型的空指针。 -
也可以把任意类型的表达式转换成
void
类型。
-
-
下面依次对以上用法进行举例说明(基本都可以运行成功,有问题的部分均会解释):
-
基本类型转换:
cpp#include <iostream> void test01(){ //convert the basic type int src = 888; char tag = static_cast<char> (src); }
-
有继承关系的类型转换,首先定义三个类,基类Animal是一个纯虚基类(虚基类不可实例化,但是可以通过强转转化对象,比如多态的实现其实就是基类的一种隐式转换),内含一个纯虚函数cry,Dog 和 Cat是两个派生类分别重写这个方法,下面是这三个类的定义:
cpp#include <iostream> class Animal{ public: virtual void cry() = 0; }; class Cat:public Animal{ public: void cry(){ std::cout << "meow meow meow~~~" << std::endl; } }; class Dog:public Animal{ public: void cry() override{ std::cout << "woof woof woof~~~" << std::endl; } };
-
首先是指针类型变量的强制转换,派生类转换成基类是安全的,但是基类转换成派生类可能不安全,尤其是多个派生类。
-
比如当一个Dog* 的对象被转换成Animal*这是安全的,但是反向转回,有可能会有Dog* -> Animal* -> Cat*。这样就会导致未定义行为,且这种行为不会被编译器察觉,因此这种转换应当被避免。
cppvoid test02(){ //type of pointer Animal* dog1 = new Dog(); // Animal* dog2 = static_cast<Animal*> (dog1); //using auto to avoid the duplicate declaration auto* dog2 = static_cast<Animal*> (dog1); //derived to base //============================ auto* dog3 = static_cast<Dog*> (dog2); //base to derived, reversely(downcast is dangerous) auto* cat = static_cast<Cat*> (dog2); //reason for danger, this may not be detected by the compiler! }
-
其次是引用类型的强制转换,一样的原理,依旧不推荐进行基类到派生类的强制转换。
-
但是这里注意一件事,在默认构造实例化一个对象时
Dog dog1();
这种定义方法会造成类对象实例化和函数声明的歧义问题。Dog dog1; 和 Dog dog{};
这种定义方法是可以的。
cppvoid test03(){ // Dog dog1{}; //allowed // Dog dog1(); //not allowed, ambiguous error occurrence //since it can be explained as both function declaration and class instantiation Dog dog1; //allowed Animal& a = static_cast<Animal&> (dog1); // Animal a(); //instantiation is not allowed in pure virtual base class, //using type conversion can be fine }
-
最后是关于
NULL 和 void*
类型的转换,即将nullptr或者 NULL
转换成任意类型,或者将任意类型转换成void*
类型都是可以的。
cppvoid test04(){ //convert nullptr to any type of pointer int* pInt = static_cast<int*> (nullptr); double* pDouble = static_cast<double *> (nullptr); Dog* pDog = static_cast<Dog *> (nullptr); //convert any type into a void type int* a = new int(10); void* v = static_cast<void* >(a); int* my_array = new int[10]; void* va = static_cast<void*> (my_array); }
- 最后出去注释的部分运行全部成功。
-
reinterpret_cast
-
一种非常低级别的强制类型转换方法,可以实现数值和指针的转换以及不同类型之间的转换。
-
滥用很可能造成风险,除非是对于此操作完全理解或者是操作本身就是底层低级别。
-
如果可以使用
static_cast
就尽量不要使用reinterpret_cast
-
下面分别对
整形转换成指针类型, 指针类型的强制转换以及引用类型的强制转换
进行代码测试。 -
其中
test06(), test07()
输出应当一致,而test05()
没有输出:
cpp
void test05(){
//converting one number to one pointer
int nu = 0x666666;
int* pnu = reinterpret_cast<int*> (nu);
}
void test06(){
//converting between different type of pointer
//Dog* dog; //initialization for one pointer pointing at nullptr
std::cout << "--------------test for the pointer--------------" << std::endl;
Dog* dog = new Dog;
auto* ar = reinterpret_cast<Animal*> (dog);
auto* as = static_cast<Animal*> (dog); //if possible, using the static_cast is preferable
ar->cry();
as->cry();
}
void test07(){
//converting between different type of reference
std::cout << "--------------test for the reference--------------" << std::endl;
Dog dog;
auto& ar = reinterpret_cast<Animal&> (dog);
auto& as = static_cast<Animal&> (dog); //if possible, using the static_cast is preferable
ar.cry();
as.cry();
}
-
最终输出如下所示,此处类型强转成功:
bash
/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project01/cmake-build-debug/project01
--------------test for the pointer--------------
woof woof woof~~~
woof woof woof~~~
--------------test for the reference--------------
woof woof woof~~~
woof woof woof~~~
Process finished with exit code 0
dynamic_cast
-
dynamic_cast
即动态类型转换,其最常见的用途是在安全地将基类指针或引用转换为派生类指针或引用。这种转换在运行时检查对象的实际类型,以确保转换是有效的。-
当转换类型为指针:根据基类指针是否指向继承类指针来相应地进行处理,如果转换失败会返回一个空指针,成功则返回一个正常转换成功的对象指针。
-
当转换类型为引用:如果转换失败会抛出一个异常
bad_cast
。 -
因为有检查过程,
dynamic_cast
的运行速率要慢于static_cast和reinterpret_cast
。
-
-
注意:
dynamic_cast
将父类转换成子类时,**基类中必须至少有一个虚函数。**如果没有则无法支持运行时类型信息,RTTI(Run-Time Type Information),此时会导致编译错误。 -
现在开始进行代码测试部分,我们设定两个测试函数分别测试指针和引用的参数传入。
-
首先我们创建一个新的方法
play()
来辅助测试,并在基类中添加纯虚函数,更新后的类定义如下所示:
cpp
#include <iostream>
class Animal{
public:
virtual void cry() = 0;
virtual void play() = 0;
};
class Cat:public Animal{
public:
void cry() override{
std::cout << "meow meow meow~~~" << std::endl;
}
void play() override{
std::cout << "climbing the tree for fun~~~" << std::endl;
}
};
class Dog:public Animal{
public:
void cry() override{
std::cout << "woof woof woof~~~" << std::endl;
}
void play() override{
std::cout << "catching the plates for fun~~~" << std::endl;
}
};
-
然后对于引用和指针类型的转换分别设计不同的判断类型:
-
对于引用类型,转换失败会抛出异常,因此要进行异常处理。
-
对于指针类型,转换失败会返回一个空指针,因此仅对于返回值进行判断即可判断其类型。
-
测试函数的代码如下所示:
cppvoid AnimalPlay(Animal& item){ item.cry(); try{ Dog& dog = dynamic_cast<Dog&> (item); dog.play(); }catch(std::bad_cast &e){ std::cout << "this is a cat" << std::endl; Cat& cat = dynamic_cast<Cat&> (item); cat.play(); } } void AnimalPlay(Animal* pitem){ pitem->cry(); auto* pDog = dynamic_cast<Dog*> (pitem); if(pDog){ pDog->play(); }else{ std::cout << "this is a cat" << std::endl; auto* pCat = dynamic_cast<Cat*> (pitem); pCat->play(); } }
-
为了配合上述上述函数进行测试,一个测试函数被定义如下,对于两种类型的指针和引用均进行了测试:
cppvoid test08(){ Dog* dog1 = new Dog(); Cat* cat1 = new Cat(); Animal* a1 = dog1; Animal* a2 = cat1; std::cout << "=========================test for references=========================" << std::endl; Dog dog2; Cat cat2; AnimalPlay(dog2); AnimalPlay(cat2); std::cout << "=========================test for pointers=========================" << std::endl; AnimalPlay(a1); AnimalPlay(a2); }
-
最终输出结果如下,判断和异常处理均成功。:
cpp/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project01/cmake-build-debug/project01 =========================test for references========================= woof woof woof~~~ catching the plates for fun~~~ meow meow meow~~~ this is a cat climbing the tree for fun~~~ =========================test for pointers========================= woof woof woof~~~ catching the plates for fun~~~ meow meow meow~~~ this is a cat climbing the tree for fun~~~ Process finished with exit code 0
-
const_cast
-
用于去除其const的属性,但是仅适用于指针或者引用。
-
下面开始进行测试:
-
对于常指针类型的测试,对于常指针转换成指针类型,
static_cast<char*>以及reinterpret_cast<char*>
均失效,只有const_cast
是有效的,各位可以自行测试: -
下面是相关代码:
cppvoid test_4_const_pointer(const char* p){ //change the const type pointer into non-const and change the value //both are not allowed while const_cast is the only choice // char* ptr = static_cast<char*> (p); // char* ptr = reinterpret_cast<char*> (p); char* ptr = const_cast<char*> (p); ptr[0] = 'A'; //directly change the object const_cast<char*> (p)[1] = 'B'; std::cout << "================end of conversion function================" << std::endl; std::cout << p << std::endl; } void test09(){ //test for const_cast //string array char p[] = "12345678"; std::cout << "================before of conversion function================" << std::endl; std::cout << p << std::endl; test_4_const_pointer(p); std::cout << "================out of conversion function================" << std::endl; std::cout << p << std::endl; }
-
输出结果如下,在进行转换后成功地对指针指向的内容进行了修改:
cpp/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project01/cmake-build-debug/project01 ================before of conversion function================ 12345678 ================end of conversion function================ AB345678 ================out of conversion function================ AB345678 Process finished with exit code 0
-
接下来是对于传值转换的测试,按照之前说的只可以转换引用或者指针,可以遇见这种操作会引发编译错误:
cppvoid test_4_const_value(const int p) { int q = p; const_cast<int>(p) = 888;// NO ! no conversion using const_cast converting a value std::cout << p << std::endl; } void test10(){ int a = 10; test_4_const_value(a); std::cout << a << std::endl; }
-
最终输出结果如下:
bash====================[ Build | project01 | Debug ]=============================== /home/herryao/Software/clion-2023.2/bin/cmake/linux/x64/bin/cmake --build /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project01/cmake-build-debug --target project01 -- -j 10 [ 50%] Building CXX object CMakeFiles/project01.dir/main.cpp.o /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project01/main.cpp: In function 'void test_4_const_value(int)': /media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project01/main.cpp:181:5: error: invalid use of 'const_cast' with type 'int', which is not a pointer, reference, nor a pointer-to-data-member type 181 | const_cast<int>(p) = 888;// NO ! no conversion using const_cast converting a value | ^~~~~~~~~~~~~~~~~~ gmake[3]: *** [CMakeFiles/project01.dir/build.make:76: CMakeFiles/project01.dir/main.cpp.o] Error 1 gmake[2]: *** [CMakeFiles/Makefile2:83: CMakeFiles/project01.dir/all] Error 2 gmake[1]: *** [CMakeFiles/Makefile2:90: CMakeFiles/project01.dir/rule] Error 2 gmake: *** [Makefile:124: project01] Error 2
-
接下来是对传引用修改常量的测试,其测试逻辑类似于传指针修改对象类型:
cppvoid test_4_const_reference(const int& p) { int q = p; const_cast<int&>(p) = 888; std::cout << "================end of conversion function================" << std::endl; std::cout << p << std::endl; } void test11(){ int a = 10; std::cout << "================before of conversion function================" << std::endl; std::cout << a << std::endl; test_4_const_reference(a); std::cout << "================out of conversion function================" << std::endl; std::cout << a << std::endl; }
-
输出结果如下,可以看出成功地对对象进行了修改。
cpp/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project01/cmake-build-debug/project01 ================before of conversion function================ 10 ================end of conversion function================ 888 ================out of conversion function================ 888 Process finished with exit code 0
-
最后一个是一个特例,即不可以对常量字符串进行修改,简单做一个测试,依旧使用之前用到的常量指针类型的测试函数进行测试:
cppvoid test_4_const_pointer(const char* p){ //change the const type pointer into non-const and change the value //both are not allowed while const_cast is the only choice // char* ptr = static_cast<char*> (p); // char* ptr = reinterpret_cast<char*> (p); char* ptr = const_cast<char*> (p); ptr[0] = 'A'; //directly change the object const_cast<char*> (p)[1] = 'B'; std::cout << "================end of conversion function================" << std::endl; std::cout << p << std::endl; } void test12(){ test_4_const_pointer("hello, world"); }
-
输出结果如下,报了段错误,这是由于常量字符串位于常量区,这个位置的内容不支持修改:
cpp/media/herryao/81ca6f19-78c8-470d-b5a1-5f35b4678058/work_dir/Document/computer_science/QINIU/projects/week05/Mon/project01/cmake-build-debug/project01 Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
- **警告:**在进行常量修改时,首先要清楚此处的内存是可修改的。
-
关于类型转换的使用建议
-
static_cast静态类型转换,编译的时c++编译器会做编译时的类型检查;隐式转换;基本类型转换,父子类之间合理转换。
-
在c语言中可以隐式转换的情况一般都可以用
static_cast
进行替换。 -
若不同类型之间,进行强制类型转换,用
reinterpret_cast
进行重新解释。 -
static_cast
和reinterpret_cast
基本上把C语言中的 强制类型转换覆盖,但值得注意的是reinterpret_cast
很难保证移植性,而且其级别很低滥用容易出错。 -
dynamic_cast
,动态类型转换,安全的虚基类和子类之间转换;运行时类型检查,运行速度相比其他两种略慢,值得注意的是dynamic_cast转换失败的处理方法,大家可以多多联系保证熟练度。 -
const_cast
,取出变量的只读属性。 -
最后的忠告:程序员必须清楚的知道: 要转的变量,类型转换前是什么类型,类型转换后是什么类型,转换后有什么后果。
-
C++大牛建议 :一般情况下,不建议进行类型转换;避免进行类型转换。
致谢:
-
感谢各位的支持和坚持,希望大家的cpp水平越来越强。
-
感谢Martin老师的课程。
-
抱歉之前的懒惰,我会尽力在这段时间赶回进度,大家共勉。