C++类型转换
C++类型转换主要分为两种:隐式类型转换、显式类型转换(强制类型转换)。
【1】隐式类型转换
所谓隐式类型转换,是指不需要用户干预,编译器默认进行的类型转换行为(很多时候用户可能都不知道到底进行了哪些转换)。
隐式类型转换一般分为两种:内置数据类型、自定义数据类型。
[1.1] 内置数据类型(基本数据类型)
例1:混合类型的算术运算表达式中
1 int nValue = 8; 2 double dValue = 10.7; 3 double dSum = nValue + dValue; // nValue会被自动转换为double类型,用转换的结果再与dValue相加
例2:不同类型的赋值操作时
1 int nValue = true; // bool类型被转换为int类型
例3:函数参数传值时
1 void func(double dArg); // 声明函数 2 func(1); // 调用函数。整型数值1被转换为double类型数值1.0
例4:函数返回值时
1 double add(int na, int nb) 2 { 3 return (na + nb); // 运算结果会被隐式转换为double类型返回 4 }
以上各种情况的隐式类型转换,都满足了一个基本原则:由低精度向高精度的转换。
若不满足该原则,编译器会提示编译警告。如下:
1 double dValue = 100.2; 2 int nValue = dValue; // : warning C4244: “初始化”: 从“double”转换到“int”,可能丢失数据
当然,这时我们若不想看到警告,可以选择显式类型转换(又称强制类型转换)。如下:
1 double dValue = 100.2; 2 int nValue = (int)dValue;
[1.2] 自定义数据类型
隐式类型转换的风险一般存在于自定义类型转换间。尤其需要注意自定义类的构造函数。例如:
1 class MyString 2 { 3 public: 4 MyString(int n) {}; // 本意:预先分配n个字节给字符串 5 MyString(const char* p) {}; // 用C风格的字符串p作为初始化值 6 // ...... 7 }; 8 9 void main() 10 { 11 MyString s1 = "China"; //OK 隐式转换,等价于MyString s1 = MyString(”China”) 12 MyString s2(10); // OK 分配10个字节的空字符串 13 MyString s3 = MyString(10); // OK 分配10个字节的空字符串 14 15 MyString s4 = 10; // OK,编译通过。也是分配10个字节的空字符串 16 MyString s5 = 'A'; // 编译通过。分配int('A')个字节的空字符串 17 // s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。 18 }
如上例,要想禁止此种隐式类型转换,可以使用C++关键字explicit(详细请参见随笔《explicit关键字》)。
【2】显式类型转换(强制类型转换)
四种强制类型转换操作符:static_cast、const_cast、dynamic_cast、reinterpret_cast
[2.1] static_cast
(1)主要用于内置数据类型之间的相互转换。
1 double dValue = 12.12; 2 float fValue = 3.14; // VS2013 warning C4305: “初始化”: 从“double”到“float”截断 3 int nDValue = static_cast<int>(dValue); // 12 4 int nFValue = static_cast<int>(fValue); // 3
(2)也可以转换自定义类型。如果涉及到类,static_cast只能在有相互联系(继承)的类型间进行转换,且不一定包含虚函数。
1 class A 2 {}; 3 4 class B : public A 5 {}; 6 7 class C 8 {}; 9 10 void main() 11 { 12 A *pA = new A; 13 B *pB = static_cast<B*>(pA); // 编译不会报错, B类继承于A类 14 pB = new B; 15 pA = static_cast<A*>(pB); // 编译不会报错, B类继承于A类 16 C *pC = static_cast<C*>(pA); // 编译报错, C类与A类没有任何关系。error C2440: “static_cast”: 无法从“A *”转换为“C *” 17 }
(3)把void类型指针转换成目标类型的指针(不安全)
[2.2] const_cast
关于操作符const_cast。详细请参见随笔《强制类型转换const_cast》
[2.3] dynamic_cast
(1)其他三种都是编译时完成的。dynamic_cast是运行时处理的,运行时要进行类型检查。
(2)不能用于内置基本数据类型间的强制转换。例如:
1 double dValue = 12.12; 2 int nDValue = dynamic_cast<int>(dValue); // error C2680 : “int” : dynamic_cast 的目标类型无效。目标类型必须是指向已定义类的指针或引用
(3)使用dynamic_cast进行转换时,基类中一定要有虚函数,否则编译不通过。
需要有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的必要,此时转换才有意义。
由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。
1 class A 2 {}; 3 4 class B : public A 5 {}; 6 7 class C 8 {}; 9 10 void main() 11 { 12 A *pA = new A; 13 B *pB = dynamic_cast<B*>(pA); // 编译错误error C2683: “dynamic_cast”:“A”不是多态类型 14 }
(4)dynamic_cast转换若成功,返回的是指向类的指针或引用;若失败则会返回NULL。
(5)在类的转换时,在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的。
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
向上转换即为指向子类对象的向上转换,即将子类指针转化父类指针。
向下转换的成败取决于将要转换的类型,即要强制转换的指针所指向的对象实际类型与将要转换后的类型一定要相同,否则转换失败。
关于(4)、(5)两条的代码示例如下:
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 class A 6 { 7 public: 8 virtual void f() 9 { 10 cout << "A::f()" << endl; 11 } 12 }; 13 14 class B : public A 15 { 16 public: 17 void f() 18 { 19 cout << "B::f()" << endl; 20 } 21 22 void bf() 23 { 24 cout << "B::bf()" << endl; 25 } 26 }; 27 28 class C 29 { 30 void pp() 31 { 32 return; 33 } 34 }; 35 36 int fun() 37 { 38 return 1; 39 } 40 41 void main() 42 { 43 A* pAB = new B; // pAB是A类型的指针指向一个B类型的对象 44 A* pAA = new A; // pAA是A类型的指针指向一个A类型的对象 45 B* pB = nullptr; 46 C* pC = nullptr; 47 pB = dynamic_cast<B*>(pAB); // 结果为not nullptr,向下转换成功,pAB指向的就是B类型的对象,所以可以转换成B类型的指针。 48 if (nullptr == pB) 49 { 50 cout << "dynamic_cast :: nullptr" << endl; 51 } 52 else 53 { 54 cout << "dynamic_cast :: not nullptr" << endl; 55 } 56 // 等价于static_cast 57 pB = static_cast<B*>(pAB); // 结果为not nullptr,向下转换成功,pAB指向的就是B类型的对象,所以可以转换成B类型的指针。 58 if (nullptr == pB) 59 { 60 cout << "static_cast :: nullptr" << endl; 61 } 62 else 63 { 64 cout << "static_cast :: not nullptr" << endl; 65 } 66 67 pB = dynamic_cast<B*>(pAA); // 结果为nullptr,向下转换失败。pAA指向的是A类型的对象,所以无法转换为B类型的指针。 68 if (nullptr == pB) 69 { 70 cout << "dynamic_cast :: nullptr" << endl; 71 } 72 else 73 { 74 cout << "dynamic_cast :: not nullptr" << endl; 75 } 76 77 // static_cast的不安全性测试 78 pB = static_cast<B*>(pAA); // 结果为not nullptr,向下转换成功。pAA指向的是A类型的对象,竟然转换为B类型的指针! 79 if (nullptr == pB) 80 { 81 cout << "static_cast :: nullptr" << endl; 82 } 83 else 84 { 85 cout << "static_cast :: not nullptr" << endl; // 不安全性 86 pB->f(); // A::f() 87 pB->bf(); // B::bf() 88 } 89 90 pC = dynamic_cast<C*>(pAB); // 结果为nullptr,向下转换失败。pAB指向的是B类型的对象,所以无法转换为C类型的指针。 91 if (nullptr == pC) 92 { 93 cout << "dynamic_cast :: nullptr" << endl; 94 } 95 else 96 { 97 cout << "dynamic_cast :: not nullptr" << endl; 98 } 99 100 // pC = static_cast<C*>(pAB); 101 // error C2440: “static_cast”: 无法从“A *”转换为“C *” 与指向的类型无关;转换要求 reinterpret_cast、C 样式转换或函数样式转换 102 103 delete pAB; 104 delete pAA; 105 106 system("pause"); 107 } 108 // run out: 109 /* 110 dynamic_cast :: not nullptr 111 static_cast :: not nullptr 112 dynamic_cast :: nullptr 113 static_cast :: not nullptr 114 A::f() 115 B::bf() 116 dynamic_cast :: nullptr 117 */
由程序运行结果分析:static_cast的不安全性显而易见。
1 pB = static_cast<B*>(pAA);
向下转换结果为not nullptr。pAA指向的是A类型的对象,竟然可以转换为B类型的指针!相当危险!
(6)使用dynamic_cast的类型转换,其转换结果几乎都是执行期定义(implementation-defined)。因此,使用reinterpret_casts的代码很难移植。
[2.4] reinterpret_cast
有着与C风格的强制转换同样的能力。
它可以转化任何内置的数据类型为其他任何的数据类型,也可以转化任何指针类型为其他的类型。
它甚至可以转化内置的数据类型为指针,无须考虑类型安全或者常量的情形。不到万不得已绝对不用。
【3】总结
(1)C风格是(type)expression
(2)C++风格是static_cast<type>(expression)
Good Good Study, Day Day Up.
顺序 选择 循环 总结