本文学习自 狄泰软件学院 唐佐林老师的 C++课程
实验1:用C方式强制类型转换所带来的的问题
实验2:通过 dynamic_cast<>强制类型转换
实验3:单继承+实现多个接口
实验1:用C方式强制类型转换所带来的的问题
#include <iostream>
#include <string>
using namespace std;
class BaseA
{
public:
virtual void funcA()
{
cout << "BaseA::funcA()" << endl;
}
};
class BaseB
{
public:
virtual void funcB()
{
cout << "BaseB::funcB()" << endl;
}
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbe = (BaseB*)pa; // oops!!
cout << "sizeof(d) = " << sizeof(d) << endl; //32位系统为8 64位系统为16,因为继承了两个虚函数表指针
cout << "Using pa to call funcA()..." << endl;
pa->funcA();//通过虚函数表指针找到 BaseA成员函数
cout << "Using pb to call funcB()..." << endl;
pb->funcB();//通过虚函数表指针找到 BaseB成员函数
cout << "Using pbc to call funcB()..." << endl;
/*
实际调用的是 BaseA::funcA()
*/
pbe ->funcB();
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "pbe = " << pbe << endl;
return 0;
}
mhr@ubuntu:~/work/c++$ g++ 54-1.cpp
mhr@ubuntu:~/work/c++$ ./a.out
sizeof(d) = 16
Using pa to call funcA()...
BaseA::funcA()
Using pb to call funcB()...
BaseB::funcB()
Using pbc to call funcB()...
BaseA::funcA()
pa = 0x7ffc714dab10
pb = 0x7ffc714dab18
pbe = 0x7ffc714dab10
mhr@ubuntu:~/work/c++$
BaseA的定义和 BaseB 的定义 几乎是一样的,编译过后的结果是一样的,仅仅是名字不同而已, 所以 BaseB* pbe指针还是指向 pa指向的位置的,那么为什么这条语句 pbe ->funcB(); 结果最后调用到了 BaseA::funcA() 成员函数???
是由于强制类型转换引起的,BaseA的定义和 BaseB 的定义 几乎是一样的,编译过后的结果是一样的,仅仅是名字不同而已,所以这两个类所生成的虚函数表也是一模一样的,而虚函数表的索引过程又是这样的:
pa->funcA();
首先从 pa 中拿到对象的地址,通过地址找到虚函数表指针vptr1,进而通过虚函数表指针到虚函数表中去寻找对应的函数地址,就找到了 funcA()的函数地址。
pbe ->funcB();
首先得到对象的地址,然后通过对象地址找到虚函数表指针vptr1,进而通过虚函数表指针vptr1去寻找对应虚函数表中对应的funcB()函数地址,而 vptr1 指向的虚函数表中没有 funcB()函数地址,只有funcA()函数地址。但是两个类所生成的虚函数表在结构上面是一模一样的。因此通过pbe 指针调用 funcB() 的时候,没有funcB() ,直接调用了funcA()。
实验2:通过 dynamic_cast<>强制类型转换
#include <iostream>
#include <string>
using namespace std;
class BaseA
{
public:
virtual void funcA()
{
cout << "BaseA::funcA()" << endl;
}
};
class BaseB
{
public:
virtual void funcB()
{
cout << "BaseB::funcB()" << endl;
}
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbe = (BaseB*)pa; // oops!!
BaseB* pbc = dynamic_cast<BaseB*>(pa);
cout << "sizeof(d) = " << sizeof(d) << endl; //32位系统为8 64位系统为16,因为继承了两个虚函数表指针
cout << "Using pa to call funcA()..." << endl;
pa->funcA();
cout << "Using pb to call funcB()..." << endl;
pb->funcB();
cout << "Using pbc to call funcB()..." << endl;
pbc->funcB();
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "pbe = " << pbe << endl;
cout << "pbc = " << pbc << endl;
return 0;
}
mhr@ubuntu:~/work/c++$
mhr@ubuntu:~/work/c++$ g++ 54-1.cpp
mhr@ubuntu:~/work/c++$ ./a.out
sizeof(d) = 16
Using pa to call funcA()...
BaseA::funcA()
Using pb to call funcB()...
BaseB::funcB()
Using pbc to call funcB()...
BaseB::funcB()
pa = 0x7ffe5094f790
pb = 0x7ffe5094f798
pbe = 0x7ffe5094f790
pbc = 0x7ffe5094f798
mhr@ubuntu:~/work/c++$
pbc->funcB(); 如愿以偿的的调用了BaseB::funcB()。因为使用了 dynamic_cast<>强制类型转换,所以编译器会做一个类型检查,并对指针根据类型做一个修正,本例中,编译器会查找d对象有BaseA, BaseB 两个父类,编译器会认为这里的强制类型转换是合法的:
BaseB* pbc = dynamic_cast<BaseB*>(pa);
在强制类型转换的时候,就会对指针有一个修正的过程,让指针pbc 指向子类结构中 BaseB 所在的位置,即pb指针指向的位置,不会像之前C语言强制类型转换后 让指针pbc 指向BaseA* pa 指向的位置。
实验3:单继承+实现多个接口
#include <iostream>
#include <string>
using namespace std;
class Base
{
protected:
int mi;
public:
Base(int i)
{
mi = i;
}
int getI()
{
return mi;
}
/*
判断参数指针 是否是指向当前对象
*/
bool equal(Base* obj)
{
return (this == obj);
}
};
class Interface1
{
public:
virtual void add(int i) = 0;
virtual void minus(int i) = 0;
};
class Interface2
{
public:
virtual void multiply(int i) = 0;
virtual void divide(int i) = 0;
};
//单继承 多接口(其实也是继承)
/*
表象还是多重继承,既然是多重继承,那我们这两节提到的三个典型问题
*/
class Derived : public Base, public Interface1, public Interface2
{
public:
Derived(int i) : Base(i)
{
}
void add(int i)
{
mi += i;
}
void minus(int i)
{
mi -= i;
}
void multiply(int i)
{
mi *= i;
}
void divide(int i)
{
if( i != 0 )
{
mi /= i;
}
}
};
int main()
{
Derived d(100);
Derived* p = &d;
Interface1* pInt1 = &d;//接口指针(起始本质也是父类指针)
Interface2* pInt2 = &d;
cout << "p->getI() = " << p->getI() << endl; // 100
//由于是虚函数 所以编译器选择调用的是 目标子类的成员函数
pInt1->add(10);
pInt2->divide(11);
pInt1->minus(5);
pInt2->multiply(8);
cout << "p->getI() = " << p->getI() << endl; // 40
cout << endl;
/*
pInt1 是 对象 Derived d的接口,本质就是父类之一,pInt1 原本应该是指向 对象d中的父类Interface1的地址,但是经过 dynamic_cast<Base*> 类型转换,编译器对指针做了修正,使得指针指向了 对象d中的父类Base的地址的地址。
*/
cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
return 0;
}
mhr@ubuntu:~/work/c++$
mhr@ubuntu:~/work/c++$ g++ 54-2.cpp
mhr@ubuntu:~/work/c++$ ./a.out
p->getI() = 100
p->getI() = 40
pInt1 == p : 1
pInt2 == p : 1
mhr@ubuntu:~/work/c++$
.
//单继承 多接口(其实也是继承)
class Derived : public Base, public Interface1, public Interface2
表象还是多重继承,既然是多重继承,那我们这两节提到的三个典型问题都是有可能出现的
问题1:同一个对象,地址不同
问题2:数据冗余
问题3: 继承关系中类之间的类型转换
所以这里使用了单继承+多接口的方案,直接避免了问题1和问题2,再使用 dynamic_cast<Base*>
类型转换 避免了 继承关系中类之间的类型转换问题。
而
cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
pInt1 是 对象 Derived d的接口,本质就是父类之一,pInt1 原本应该是指向 对象d中的父类Interface1的地址,但是经过 dynamic_cast<Base*> 类型转换,编译器对指针做了修正,使得指针指向了 对象d中的父类Base的地址的地址。