Android NDK开发之旅25--C++--类型转换
C++--类型转换
C风格的强制类型转换(Type Cast)很简单,不管什么类型的转换统统是:TYPE b = (TYPE)a,但是c 风格的类型转换有不少的缺点,有的时候用c风格的转换是不合适的,因为它可以在任意类型之间转换,比如你可以把一个指向const对象的指针转换成指向非 const对象的指针,把一个指向基类对象的指针转换成指向一个派生类对象的指针,这两种转换之间的差别是巨大的,但是传统的c语言风格的类型转换没有区 分这些。还有一个缺点就是,c风格的转换不容易查找,他由一个括号加上一个标识符组成,而这样的东西在c++程序里一大堆。所以c++为了克服这些缺点,引进了4种类型转换操作符(C++风格的强制转换其他的好处是,它们能更清晰的表明它们要干什么)。程序员只要扫一眼这样的代码,就能立即知道一个强制转换的目的。
1、static_cast 静态转换
可以实现C++中内置基本数据类型之间的相互转换,enum、struct、 int、char、float等。它不能进行无关类型(如非基类和子类)指针之间的转换。
int c=static_cast<int>(7.987);
如果涉及到类的话,static_cast只能在有相互联系的类型中进行相互转换,不一定包含虚函数。
#include <iostream>
using namespace std;
class A
{
public:
A() {}
~A() {}
private:
int i, j;
};
class C
{
public:
C() {}
~C() {}
void printC()
{
std::cout << "call printC() in class C" << std::endl;
}
private:
char c1, c2;
};
void main() {
A *ptrA = new A();
C *ptrC = (C *)(ptrA);
ptrC->printC(); //"call printC() in class C"
//ptrC = static_cast<C*>(ptrA); //编译报错:error: invalid static_cast from type 'A*’ to type C*’
system("pause");
}
结果如下:
call printC() in class C
上面A C是两个无关的类,然而使用Clike可以实现这种类型的强制转换,这是十分危险的! 使用static_cast可以将这种潜在的危险在编译器找出来.
在同一继承体系中:
-
upcast(向上转换即子类转成父类):没有问题.因为父类的行为都包含在子类中;
-
downcast(向下转换):有可能会出现问题,编译时可能不会发现.
一个类的行为和自身的类型相关.也就是一个A类型的指针总会优先调用自己A类内的函数,当然发生继承中的重写(虚继承等)例外.
示例如下:
#include <iostream>
#include <cstdio>
using namespace std;
class A
{
public:
A() :i(1), j(1) {}
~A() {}
void printA()
{
std::cout << "call printA() in class A" << std::endl;
}
void printSum()
{
std::cout << "sum = " << i + j << std::endl;
}
private:
int i, j;
};
class B : public A
{
public:
B() :a(2), b(2) {}
~B() {}
void printB()
{
std::cout << "call printB() in class B" << std::endl;
}
void printSum()
{
std::cout << "sum = " << a + b << std::endl;
}
void Add()
{
a++;
b++;
}
private:
double a, b;
};
int main()
{
B *ptrB = new B;
ptrB->printSum();
A *ptrA = static_cast<B *>(ptrB);
ptrA->printA();
ptrA->printSum();
//打印结果:sum = 2
//在进行upcast的时候,指针指向的对象的行为与指针的类型相关。
ptrA = new A;
ptrB = static_cast<B *>(ptrA);
ptrB->printB();
ptrB->printSum();
//打印结果:sum = -1.45682e+144
//在进行downcast的时候,其行为是“undefined”。
B b;
B &rB = b;
rB.printSum();
//打印结果: sum = 4
A &rA = static_cast<A &>(b);
rA.printA();
rA.printSum();
//打印结果: sum = 2
//在进行upcast的时候,指针指向的对象的行为与引用类型相关.
A a;
A &rA1 = a;
rA.printSum();
B &rB1 = static_cast<B &>(a);
rB1.printB();
//打印结果:sum = 4
rB1.printSum();
//打印结果 :sum = 1.45863e-316
//在进行downcast的时候,其行为是“undefined”。
system("pause");
return 0;
}
结果如下:
sum = 4
call printA() in class A
sum = 2
call printB() in class B
sum = -1.45682e+144
sum = 4
call printA() in class A
sum = 2
sum = 2
call printB() in class B
sum = -1.85119e+62
这里其实很明显,在downcast转换的时候,会出现一些跟指针或者引用类型相关的函数调用,但是因为指针或者引用(父类)
没有定义这些行为,因为调用到了这些行为导致出现了未定义的行为.
明显解决这个问题的办法就是,虚函数!如果声明A类中的printSum未 虚函数,那么子类B就会有一个虚表,虚表中的第一个函数就是printSum函数其实是
B类的该函数.所以,A类指针调用该函数就会调用B类中的该函数 显示结果sum= 4. 在未定义之前sum = 2(A类中的该函数).
PS:引用类型必须被初始化,这是引用和指针类型的重要区别.
总之,就是尽可能不要使用downcast也就是 使用子类的指针指向父类.
感觉这里又不得不说,c++内存对象的对齐方式.所以 ,在另外一篇blog<c++内存的对齐方式>中理清楚这些问题.
2、const_cast 去常转换
它主要作用同一个类型之间的去常和添加常属性之间的转换.不能用做不同的类型之间的转换.
它可以把一个不是常属性的转换成常属性的,同时它也可以对一个本是常属性的类型进行去常.
示例如下:
#include <iostream>
using namespace std;
void func(const char c[]) {
//通过指针间接赋值
//其他人并不知道,这次转型是为了去常量
/*char* c_p = (char*)c;
c_p[1] = 'x';*/
//提高了可读性
char* c_p = const_cast<char*>(c);
c_p[1] = 'Y';
cout << c << endl;
}
void main() {
char c[] = "hello";
func(c);
system("pause");
}
结果如下:
hYllo
3、dynamic_cast 动态类型转换
也是向下安全转型;是在运行的时候执行;通常用于基类和派生类之间的转换.转换时会进行类型安全检查。
示例如下:
#include <iostream>
using namespace std;
class Person {
public:
void virtual print(){
cout << "Person" << endl;
}
};
class Man :public Person {
public:
void print() {
cout << "Man" << endl;
}
void chasing() {
cout << "love girl!" << endl;
}
};
class Woman :public Person {
public:
void print() {
cout << "Woman" << endl;
}
void careBaby() {
cout << "love baby!" << endl;
}
};
void func(Person* obj) {
obj->print();
//调用子类的特有的函数,转为实际类型
//Man* m = (Man*)obj;
//m->print();
Man* m = dynamic_cast<Man*>(obj);
if (m !=NULL) {
m->chasing();
}
Woman* w = dynamic_cast<Woman*>(obj);
if (w !=NULL)
{
w->careBaby();
}
}
void main() {
Woman w1;
Person *p1 = &w1;
func(p1);
system("pause");
}
结果如下:
Woman
love baby!
-
dynamic_cast是在运行时检查的,用于在集成体系中进行安全的向下转换downcast(当然也可以向上转换,但没必要,因为可以用虚函数实现)
即:基类指针/引用 -> 派生类指针/引用
如果源和目标没有继承/被继承关系,编译器会报错!
-
dynamic_cast是4个转换中唯一的RTTI操作符,提供运行时类型检查。
-
dynamic_cast不是强制转换,而是带有某种”咨询“性质的,如果不能转换,返回NULL。这是强制转换做不到的。
-
源类中必须要有虚函数,保证多态,才能使用dynamic_cast<source>(expression)
示例如下:
#include <iostream>
#include <typeinfo>
#include <cstdio>
using namespace std;
class A {
public:
virtual void foo() {
cout << "A foo" << endl;
}
//虚函数的出现会带来动态机制 Class A 至少要有一个虚函数
void pp() {
cout << "A pp" << endl;
}
};
class B : public A {
public:
void foo() {
cout << "B foo" << endl;
}
void pp() {
cout << "B PP" << endl;
}
void functionB() {
cout << "Excute FunctionB!" << endl;
}
};
int main()
{
B b;
A *pa = &b;
pa->foo();
pa->pp();
//基类指针可以指向派生类,但是只能调用基类和派生类都存在的成员,也就是说不能调用派生类中新增的成员!
//pa->FunctionB();//error: 'class A' has no member named 'FunctionB'
if (dynamic_cast<B*>(pa) == NULL) {
cout << "NULL" << endl;
}
else {
cout << typeid((dynamic_cast<B*>(pa))).name() << endl;
dynamic_cast<B*>(pa)->foo();
dynamic_cast<B*>(pa)->pp();
dynamic_cast<B*>(pa)->functionB();
}
A aa;
//B *pb = &aa;派生类不能指向基类
B *pbNull = NULL;
pbNull->functionB();//fine
pbNull->pp();//fine
//pbNull->foo(); //crash!foo调用了虚函数,编译器需要根据对象的虚函数指针查找虚函数表,但为空,crash!
system("pause");
return 0;
}
结果如下:
B foo
A pp
class B *
B foo
B PP
Excute FunctionB!
Excute FunctionB!
B PP
4、reinterpret_cast 重解释类型转换
reinterpret_cast转换一个指针为其它类型的指针。它也允许从一个指针转换为整数类型。反之亦然。(译注:是指针具体的地址值作为整数值?)
这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。
如果情况是从一个指针到整型的拷贝,内容的解释是系统相关的,所以任何的实现都不是方便的。一个转换到足够大的整型能够包含它的指针是能够转换回有效的指针的。
示例如下:
#include <iostream>
using namespace std;
class A {
public:
void print() {
cout << "I'm A" << endl;
}
};
class B {
public:
void print() {
cout << "I'm B" << endl;
}
};
A * a = new A;
B * b = reinterpret_cast<B *>(a);
void main() {
A * a = new A;
B * b = reinterpret_cast<B *>(a);
b->print();
system("pause");
}
结果如下:
I'm B
reinterpret_cast就像传统的类型转换一样对待所有指针的类型转换。