第19课 - 专题三经典问题解析
一.当多态遇见对象数组会发生什么?
1.1 指针的运算时通过指针的类型进行的,在编译时进行的
1.2 多态通过虚函数表实现的,在程序运行时进行的
Source Example 1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Parent{ protected: int i; int k; public: Parent() { } virtual void func() { printf("Parent->"); printf("func!\n"); } }; class Child : public Parent { protected: int j; int n; public: Child(int i, int j) { this->i = i; this->j = j; } virtual void func() { printf("i = %d, j = %d\n", i , j ); } }; int main(int argc, char** argv) { Parent* p = NULL; Child* c = NULL; Child ca[3] = {Child(1,2), Child(2,3), Child(3,4)}; /* 输出16, int i, int k, VPTR指针8个字节*/ printf("sizeof(Parent) = %d\n", sizeof(Parent)); /* 输出24 , 父类的16个字节加上int j,共20字节,4字节对齐,因此输出24字节*/ printf("sizeof(Child) = %d\n", sizeof(Child)); p = ca; c = ca; /* 会发生多态,根据指针实际指向的类型来确定调用函数 */ /* p指向了数组中第一个对象 */ p->func(); /* c指向了数组中第一个对象 */ c->func(); p++; c++; p->func(); c->func(); return 0; }p++ <==> p = p + 1 <==> (unsigned int)p + 1 * sizeof(*p);
最终结论: 不要再数组上使用多态!
二.为什么没有讲解多重继承?
2.1 C++在语法上直接支持多重继承
Source Example2.1: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Parent1 { protected: int i; }; class Parent2 { protected: int j; }; class Child : public Parent1, public Parent2 { public: Child(int i, int j) { this->i = i; this->j = j; } void print() { printf("i = %d, j = %d\n", i ,j); } }; int main(int argc, char** argv) { Child c(1,2); c.print(); return 0; }
2.2 被实际开发经验抛弃的多继承
2.2.1 工程开发中真正意义上的多继承几乎不被使用的
2.2.2 多重继承带来的代码复杂性远多于其带来的便利
2.2.3 多充继承对代码维护性上的影响是灾难性的
2.2.4 在设计方法上,任何多继承都可以用单继承代替
Source Example 2.2(多继承复杂性示例): #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Object { protected: int d; public: void fun() { printf("Object->fun()!\n"); } }; class Parent1 : public Object{ protected: int i; }; class Parent2 : public Object{ protected: int j; }; class Child : public Parent1, public Parent2 { public: Child(int i, int j) { /* 编译提示此处有二义性,由于Child不是直接继承Object */ /* Child是多重继承P1,P2,而P1,P2都继承了Object,因此相当于拥有了两个d */ this->d = 0; this->i = i; this->j = j; } void print() { printf("i = %d, j = %d\n", i ,j); } }; int main(int argc, char** argv) { Child c(1,2); c.print(); /* 编译出错,具有二义性 */ c.fun(); return 0; }
只有在单继承的系统中,类之间继承关系为一个树。
在引入多重继承的系统中,类之间的继承关系呈现为一张图。
2.3 C++中对多继承二义性的解决方案
虚继承:
为了解决从不同途径继承来的同名数据成员二义性的万恶本土,可以将共同基类设置 为虚基类,这是从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝。
/* 虚继承 */ class Parent1 : virtual public Object{ protected: int i; }; class Parent2 : virtual public Object{ protected: int j; };但是在实际工程中,很多类,不一定那个要加上虚继承,但是如果添加虚继承会对程序效率有所影响。
三.C++中是否有Java中的接口的概念
3.1 绝大多数面向对象语言都不支持多继承
3.2 绝大多数面向对象语言都支持接口的概念
3.3 C++中没有接口的概念
3.4 C++中可以使用纯虚函数实现接口
class Interface { public: virtual void fun1() = 0; virtual void fun2(int i) = 0; virtual void fun3(int i, int j) = 0; };
接口类中只有函数原型定义,没有任何数据的定义。
3.5 实际工程经验证明
3.5.1 多重继承接口不会带来二义性和复杂性等问题
3.5.2 多重继承可以通过精心设计用单继承和接口来代替
注意:接口只是一个功能说明,而不是功能实现。子类需要根据功能说明定义功能实现.
Source Example 3: #include <iostream> /* run this program using the console pauser or add your own getch, system("pause") or input loop */ class Interface1{ public: virtual void print() = 0; virtual int add(int i, int j) = 0; }; struct Interface2{ /* 函数只是一个功能的说明,因此在子类中实现一个即可 */ virtual int add(int i, int j) = 0; virtual int minus(int i, int j) = 0; }; class Child : public Interface1, public Interface2{ public: virtual void print() { printf("Child :: print!\n"); } virtual int add(int i, int j) { return i + j; } virtual int minus(int i, int j) { return i - j; } }; /* 编译通过,没有二义性 */ int main(int argc, char** argv) { Child c; c.print(); /* 并没有二义性 */ printf("%d\n", c.add(1,2)); printf("%d\n", c.add(2,3)); /* 调用了同一个add函数,没有任何二义性 */ printf("%d\n", i1->add(3,5)); printf("%d\n", i2->add(3,5)); return 0; }多态是实现设计模式的基本技术!