一.继承的概念和意义
1.组合关系
组合关系:整体与部分的关系
组合关系示例:
#include <iostream>
#include <string>
using namespace std;
class Memory
{
public:
Memory()
{
cout << "Memory()" << endl;
}
~Memory()
{
cout << "~Memory()" << endl;
}
};
class Disk
{
public:
Disk()
{
cout << "Disk()" << endl;
}
~Disk()
{
cout << "~Disk()" << endl;
}
};
class CPU
{
public:
CPU()
{
cout << "CPU()" << endl;
}
~CPU()
{
cout << "~CPU()" << endl;
}
};
class MainBoard
{
public:
MainBoard()
{
cout << "MainBoard()" << endl;
}
~MainBoard()
{
cout << "~MainBoard()" << endl;
}
};
class Computer
{
Memory mMem;
Disk mDisk;
CPU mCPU;
MainBoard mMainBoard;
public:
Computer()
{
cout << "Computer()" << endl;
}
void power()
{
cout << "power()" << endl;
}
void reset()
{
cout << "reset()" << endl;
}
~Computer()
{
cout << "~Computer()" << endl;
}
};
int main()
{
Computer c;
return 0;
}
编译结果:
Memory()
Disk()
CPU()
MainBoard()
Computer()
~Computer()
~MainBoard()
~CPU()
~Disk()
~Memory()
组合关系的特点:
将其他类的对象作为当前类的成员使用
当前类的对象与成员对象的生命期相同
成员对象在用法上与普通对象完全一致
2.继承关系
继承关系:父子关系
父类(基类) 电脑
子类(派生类) 惠普电脑 苹果电脑 华硕电脑
面向对象中的继承指类之间的父子关系:
- 子类拥有父类的所有属性和行为
- 子类就是一种特殊的父类
- 子类对象可以当做父类对象使用
- 子类中可以添加父类没有的方法和属性
C++描述继承关系的方式:
class Parent
{
int mv;
public:
void method();
};
class child : public Parent //描述继承关系
{
};
继承示例:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
int mv;
public:
Parent()
{
cout << "Parent()" << endl;
mv = 100;
}
void method()
{
cout << "mv = " << mv << endl;
}
};
class Child : public Parent
{
public:
void hello()
{
cout << "I'm Child calss!" << endl;
}
};
int main()
{
Child c;
Parent p1 = c;
Parent p2;
c.hello();
c.method();
p2 = c;
return 0;
}
编译结果:
Parent()
I'm Child calss!
mv = 100
重要规则:
子类就是一个特殊的父类
子类对象可以直接初始化父类对象
子类对象可以直接赋值给父类对象
继承的意义:
继承是C++中代码复用的重要手段。通过继承,可以获得父类的所有功能,并且可以在子类中重写已有的功能,或者添加新功能。
示例:
#include <iostream>
#include <string>
using namespace std;
class Memory
{
public:
Memory()
{
cout << "Memory()" << endl;
}
~Memory()
{
cout << "~Memory()" << endl;
}
};
class Disk
{
public:
Disk()
{
cout << "Disk()" << endl;
}
~Disk()
{
cout << "~Disk()" << endl;
}
};
class CPU
{
public:
CPU()
{
cout << "CPU()" << endl;
}
~CPU()
{
cout << "~CPU()" << endl;
}
};
class MainBoard
{
public:
MainBoard()
{
cout << "MainBoard()" << endl;
}
~MainBoard()
{
cout << "~MainBoard()" << endl;
}
};
class Computer
{
Memory mMem;
Disk mDisk;
CPU mCPU;
MainBoard mMainBoard;
public:
Computer()
{
cout << "Computer()" << endl;
}
void power()
{
cout << "power()" << endl;
}
void reset()
{
cout << "reset()" << endl;
}
~Computer()
{
cout << "~Computer()" << endl;
}
};
class HPBook : public Computer
{
string mOS;
public:
HPBook()
{
mOS = "Windows 8"; //预装的操作系统
}
void install(string os)
{
mOS = os;
}
void OS()
{
cout << mOS << endl;
}
};
class MacBook : public Computer
{
public:
void OS()
{
cout << "Mac OS" << endl;
}
};
int main()
{
HPBook hp;
hp.power();
hp.install("Ubuntu 16.04 LTS");
hp.OS();
cout << endl;
MacBook mac;
mac.OS();
return 0;
}
Memory()
Disk()
CPU()
MainBoard()
Computer()
power()
Ubuntu 16.04 LTS
Memory()
Disk()
CPU()
MainBoard()
Computer()
Mac OS
~Computer()
~MainBoard()
~CPU()
~Disk()
~Memory()
~Computer()
~MainBoard()
~CPU()
~Disk()
~Memory()
总结:
继承是面向对象中类之间的一种关系
子类拥有父类的所有属性和行为
子类对象可以当作父类对象使用
子类中可以添加父类没有的方法和属性
继承是面向对象中代码复用的重要手段
二. 继承中的访问级别
1. 思考:
子类是否可以直接访问父类的私有成员?
可以,protected关键字替换private即可
思考过程:
根据面向对象理论:
根据C++语法:
继承中的访问级别示例:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
private:
int mv;
public:
Parent()
{
mv = 100;
}
int value()
{
return mv;
}
};
class Child : public Parent
{
public:
int addValue(int v)
{
mv = mv + v; // 如何访问父类中的非公有成员? 使用protected关键字
}
};
int main()
{
return 0;
}
编译报错
面向对象中的访问级别不只是public和private
可以定义protected访问级别
2. 关键字protected的意义:
修饰的成员不能被外界直接访问
修饰的成员可以被子类直接访问
protected示例:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
protected: //可以让子类访问
int mv;
public:
Parent()
{
mv = 100;
}
int value()
{
return mv;
}
};
class Child : public Parent
{
public:
int addValue(int v)
{
mv = mv + v;
}
};
int main()
{
Parent p;
cout << "p.mv = " << p.value() << endl;
// p.mv = 1000; // error,仅仅只有子类可以访问,外界不能访问
Child c;
cout << "c.mv = " << c.value() << endl;
c.addValue(50);
cout << "c.mv = " << c.value() << endl;
// c.mv = 10000; // error,仅仅只有子类可以访问,外界不能访问
return 0;
}
运行结果
p.mv = 100
c.mv = 100
c.mv = 150
定义类时访问级别的选择:
3.组合与继承的综合实例
Point类 继承 Object类
Line类 继承 Object类
Point类组合成Line类
示例:
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
class Object
{
protected:
string mName;
string mInfo;
public:
Object()
{
mName = "Object";
mInfo = "";
}
string name()
{
return mName;
}
string info()
{
return mInfo;
}
};
class Point : public Object
{
private:
int mX;
int mY;
public:
Point(int x = 0, int y = 0) //默认值0 0
{
ostringstream s;
mX = x;
mY = y;
mName = "Point";
s << "P(" << mX << ", " << mY << ")";
mInfo = s.str();
}
int x()
{
return mX;
}
int y()
{
return mY;
}
};
class Line : public Object
{
private:
Point mP1;
Point mP2;
public:
Line(Point p1, Point p2)
{
ostringstream s;//格式化
mP1 = p1;
mP2 = p2;
mName = "Line";
s << "Line from " << mP1.info() << " to " << mP2.info();
mInfo = s.str();
}
Point begin()
{
return mP1;
}
Point end()
{
return mP2;
}
};
int main()
{
Object o;
Point p(1, 2);
Point pn(5, 6);
Line l(p, pn);
cout << o.name() << endl;
cout << o.info() << endl;
cout << endl;
cout << p.name() << endl;
cout << p.info() << endl;
cout << endl;
cout << l.name() << endl;
cout << l.info() << endl;
return 0;
}
运行结果
Object
Point
P(1, 2)
Line
Line from P(1, 2) to P(5, 6)
小结:
面向对象中的访问级别不只是public和private
protected修饰的成员不能被外界所访问
protected使得子类能够访问父类的成员
protected关键字是为了继承而专门设计的
没有protected就无法完成真正意义上的代码复用
三.不同的继承方式
细节:
冒号( : )表示继承关系,Parent表示被继承的类,public的意义是什么?
class Parent
{
};
class Child : public Parent //public可以换成protected或者private吗?
{
};
示例:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
};
class Child_A : public Parent
{
};
class Child_B : protected Parent
{
};
class Child_C : private Parent
{
};
int main()
{
return 0;
}
1.C++中支持三种不同的继承方式:
public继承
父类成员在子类中保持原有访问级别
private继承
父类成员在子类中变为私有成员
protected继承
父类中的公有成员变为保护成员,其他成员保持不变
继承成员的访问属性 = Max{继承方式,父类成员访问属性}
C++中的默认继承方式为private
继承与访问级别深度实践:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
protected:
int m_a;
protected:
int m_b;
public:
int m_c;
void set(int a, int b, int c)
{
m_a = a;
m_b = b;
m_c = c;
}
};
class Child_A : public Parent
{
public:
void print()
{
cout << "m_a" << m_a << endl;
cout << "m_b" << m_b << endl;
cout << "m_c" << m_c << endl;
}
};
class Child_B : protected Parent
{
public:
void print()
{
cout << "m_a" << m_a << endl;
cout << "m_b" << m_b << endl;
cout << "m_c" << m_c << endl;
}
};
class Child_C : private Parent
{
public:
void print()
{
cout << "m_a" << m_a << endl;
cout << "m_b" << m_b << endl;
cout << "m_c" << m_c << endl;
}
};
int main()
{
Child_A a;
Child_B b;
Child_C c;
a.m_c = 100;//共有继承
//b.m_c = 100;
//Child_B保护继承自Parent,所有的public成员全部变成了protected成员,因此外界无法访问
//c.m_c = 100;
//Child_C私有继承自Parent,所有的Parent成员全部变成了private成员,因此外界无法访问
a.set(1,1,1);
//b.set(2,2,2);
//c.set(3,3,3);
//继承方式影响了父类中的成员函数
a.print();
b.print();
c.print();
//继承方式仅仅影响父类中的成员,对子类自己定义的成员没有影响
return 0;
}
2.遗憾的事实
一般而言,C++工程项目中只使用public继承
C++的派生语言只支持一种继承方式(public继承)
protected和private继承带来的复杂性远大于实用性
小结
C++中支持3种不同的继承方式
继承方式直接影响父类成员在子类中的访问属性
一般而言,工程中只使用public的继承方式
C++的派生语言中只支持public继承方式
四.继承中的构造与析构
1.子类对象的构造
(1)子类中可以定义构造函数
(2)子类构造函数
必须对继承而来的成员进行初始化:
直接初始化: 1) 直接通过初始化列表或者赋值的方式进行初始化设置
调用初始化: 2) 调用父类构造函数进行初始化
(3)父类构造函数在子类中的调用方式
默认调用: 1) 适用于无参构造函数 2) 使用默认参数的构造函数
显式调用: 1) 通过初始化列表进行调用 2) 适用于所有父类构造函数
父类构造函数的调用示例:
class Child : public Parent
{
public:
Child() /*隐式调用*/
{
cout << "Child()" << endl;
}
Child(string s) : Parent("parameter to Parent") /*显式调用*/
{
cout << "Child() : " << s << endl;
}
}
子类的构造初探示例:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
Parent()
{
cout << "Parent()" << endl;
}
Parent(string s)
{
cout << "Parent(string s) : " << s << endl;
}
};
class Child : public Parent
{
public:
Child()
{
cout << "Child()" << endl;
}
Child(string s):Parent(s) //显式调用父类的构造函数
{
cout << "Child(string s) :" << s << endl;
}
};
int main()
{
Child c; //需要父类中有无参构造函数
Child cc("cc");
return 0;
}
运行结果
Parent()
Child()
Parent(string s) : cc
Child(string s) : cc
子类对象的构造规则:
- 子类对象在创建时首先调用父类的构造函数
- 先执行父类构造函数再执行子类的构造函数
- 父类构造函数可以被隐式调用或者显式调用
对象创建时构造函数的调用顺序:
- 调用父类的构造函数
- 调用成员变量的构造函数
- 调用类自身的构造函数
先父母,后客人,再自己
子类构造深度解析示例:
#include <iostream>
#include <string>
using namespace std;
class Object
{
public:
Object(string s)
{
cout << "Object(string s ) : " << s << endl;
}
};
class Parent : public Object
{
public:
Parent() : Object("Default")
{
cout << "Parent()" << endl;
}
Parent(string s) : Object(s)
{
cout << "Parent(string s) : " << s << endl;
}
};
class Child : public Parent
{ //继承自Parent,组合使用了Object类
Object m01; //声明成员变量
Object m02;
public:
Child() :m01("Default 1"), m02("Default 2")
{
cout << "Child()" << endl;
}
Child(string s) : Parent(s), m01(s + "1"), m02(s + "2")
{
cout << "Child(string s) : " << s << endl;
}
};
int main()
{
Child cc("cc");
return 0;
}
运行结果
Object(string s ) : cc
Parent(string s) : cc
Object(string s ) : cc1
Object(string s ) : cc2
Child(string s) : cc
2.子类对象的析构
析构函数的调用顺序与构造函数相反
- 执行自身的析构函数
- 执行成员变量的析构函数
- 执行父类的析构函数
代码示例:
#include <iostream>
#include <string>
using namespace std;
class Object
{
string ms;
public:
Object(string s)
{
cout << "Object(string s ) : " << s << endl;
ms = s;
}
~Object()
{
cout << "~Object() : " << ms <<endl;
}
};
class Parent : public Object
{
string ms;
public:
Parent() :Object("Default")
{
cout << "Parent()" << endl;
ms = "Default";
}
Parent(string s) : Object(s)
{
cout << "Parent(string s) : " << s << endl;
ms = s;
}
~Parent()
{
cout << "~Parent() : " << ms <<endl;
}
};
class Child : public Parent
{
Object m01;
Object m02;
string ms;
public:
Child() :m01("Default 1"), m02("Default 2")
{
cout << "Child()" << endl;
ms = "Default";
}
Child(string s) : Parent(s),m01(s + "1"),m02(s + "2")
{
cout << "Child(string s) : " << s << endl;
ms = s;
}
~Child()
{
cout << "~Child() : " << ms <<endl;
}
};
int main()
{
Child cc("cc");
cout << endl;
return 0;
}
运行结果
Object(string s ) : cc
Parent(string s) : cc
Object(string s ) : cc1
Object(string s ) : cc2
Child(string s) : cc
~Child() : cc
~Object() : cc2
~Object() : cc1
~Parent() : cc
~Object() : cc
小结:
子类对象在创建时需要调用父类构造函数进行初始化
先执行父类构造函数然后执行成员的构造函数
父类构造函数显示调用需要在初始化列表中进行
子类对象在销毁时需要调用父类析构函数进行清理
析构顺序与构造顺序对称相反
五.父子间的冲突
1.成员变量的冲突
子类可以定义父类中的同名成员
子类中的成员将隐藏父类中的同名成员
父类中的同名成员依然存在于子类中
通过作用域分辨符(::)访问父类中的同名成员
Child c;
c.mi = 100; //子类中的mi
c.Parent::mi = 100; //父类中的mi
同名成员的示例:
#include<iostream>
#include<string>
using namespace std;
class Parent
{
public:
int mi;
Parent()
{
cout << "Parent() : " << "&mi = " << &mi<< endl;
}
};
class Child : public Parent
{
public:
int mi;
Child()
{
cout << "Child() : " << "&mi = " << &mi << endl;
}
};
int main()
{
Child c;
c.mi = 100;
c.Parent::mi = 1000;
cout << "&c.mi = " << &c.mi << endl;
cout << "c.mi = " << c.mi << endl;
cout << "&c.Parent::mi = " << &c.Parent::mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl;
return 0;
}
运行结果
Parent() : &mi = 0x77777777
Child() : &mi = 0x99999999
&c.mi = 0x99999999
c.mi = 100
&c.Parent::mi = 0x77777777
c.Parent::mi = 1000
2.成员函数的冲突
再论重载:
类中的成员函数可以进行重载
(1)重载函数的本质为多个不同的函数
(2)函数名和参数列表是唯一的标识
(3)函数重载必须发生在同一个作用域中
子类中的函数将隐藏父类的同名函数
子类无法重载父类中的成员函数,不在同一个作用域当中
使用作用域分辨符访问父类中的同名函数
子类可以定义父类中完全相同的成员函数
示例:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int v)
{
mi += v;
}
void add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mi;
void add(int v)
{
mi += v;
}
void add(int a, int b)
{
mi += (a + b);
}
void add(int x, int y, int z) //子类只定义这一个,父类的同名函数就会被隐藏
{
mi += (x + y + z);
}
};
int main()
{
Child c;
c.mi = 100;
c.Parent::mi = 1000;
cout << "c.mi = " << c.mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl;
c.add(1);
c.add(2,3);
c.add(4,5,6);
cout << "c.mi = " << c.mi << endl;
cout << "c.Parent::mi = " << c.Parent::mi << endl;
return 0;
}
运行结果
c.mi = 100
c.Parent::mi = 1000
c.mi = 121
c.Parent::mi = 1000
小结:
子类可以定义父类中的同名成员
子类中的成员将隐藏父类中的同名成员
子类和父类中的函数不能构成重载关系
子类可以定义父类中完全相同的成员函数
使用作用域分辨符访问父类中的同名成员
六.同名覆盖引发的问题
1.父子间的赋值兼容
子类对象可以当作父类对象使用(兼容性)
- 子类对象可以直接赋值给父类对象
- 子类对象可以直接初始化父类对象
- 父类指针可以直接指向子类对象
- 父类引用可以直接引用子类对象
子类对象的兼容性示例:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
int add(int i)
{
mi += i;
}
int add(int a, int b)
{
mi += (a + b);
}
};
class Child : public Parent
{
public:
int mv;
int add(int x, int y, int z)
{
mv += (x + y + z);
}
};
int main()
{
Parent p;
Child c;
p = c; //子类对象可以直接赋值给父类对象
Parent p1(c); //子类对象可以直接初始化父类对象
Parent* pp = &c; //父类指针可以直接指向子类对象
Parent& rp = c; //父类引用可以直接引用子类对象
rp.mi = 100;
rp.add(5); //没有发生同名覆盖
rp.add(10, 10); //没有发生同名覆盖
//pp->mv = 1000; //编译报错,显示父类中没有此变量
//pp->add(1, 10, 100); //编译报错,显示父类中没有此函数
return 0;
}
注意事项: !!!!!!
当使用父类指针(引用)指向子类对象时:
- 子类对象退化为父类对象
- 只能访问父类中定义的成员
- 可以直接访问被子类覆盖的同名成员
2.特殊的同名函数
子类中可以重定义父类中已经存在的成员函数
这种重定义发生在继承中,叫做函数重写
函数重写是同名覆盖的一种特殊情况
class Parent
{
public:
void print()
{
cout << "I'm Parent." << endl;
}
};
函数重写 -->
class Child : public Parent
{
public:
void print()
{
cout << "I'm Child." << endl;
}
};
函数重写对赋值兼容示例:
#include <iostream>
#include <string>
using namespace std;
class Parent
{
public:
int mi;
void add(int i)
{
mi += i;
}
void add(int a, int b)
{
mi += (a + b);
}
void print()
{
cout << "I'm Parent." << endl;
}
};
class Child : public Parent
{
public:
int mv;
void add(int x, int y, int z)
{
mv += (x + y + z);
}
void print()
{
cout << "I'm Child." << endl;
}
};
//全局函数,
void how_to_print(Parent* p)
{
p->print();
}
int main()
{
Parent p;
Child c;
//c.print(); //函数重写,子类没有的话就调用父类的
how_to_print(&p); // Expected to print: I'm Parent.
how_to_print(&c); // Expected to print: I'm Child.
return 0;
}
运行结果
I'm Parent.
I'm Parent.
问题分析:
编译期间,编译器只能根据指针的类型判断所指向的对象
根据赋值兼容,编译器认为父类指针指向的是父类对象
因此,编译结果只可能是调用父类中定义的同名函数
void how_to_print(Parent* p)
{
p->print();
}
在编译这个函数的时候,编译器不可能知道指针p究竟指向了什么。但是编译器没有理由报错。可是,编译器认为最安全的做法是调用父类的print函数,因为父类和子类肯定都有相同的print函数。
小结:
子类对象可以当做父类对象使用(赋值兼容)
父类指针可以正确的指向子类对象
父类引用可以正确的代表子类对象
子类中可以重写父类中的成员函数
七.被遗弃的多重继承
1.C++支持编写多重继承的代码
- 一个子类可以拥有多个父类
- 子类拥有所有父类的成员变量
- 子类继承所有父类的成员函数
- 子类对象可以当做任意父类对象使用
多重继承的语法规则:
class Derived : public BaseA,
public BaseB,
public BaseC
{
...
};
多重继承的本质与单继承相同。
示例:
#include <iostream>
#include <string>
using namespace std;
class BaseA
{
int ma;
public:
BaseA(int a)
{
ma = a;
}
int getA()
{
return ma;
}
};
class BaseB
{
int mb;
public:
BaseB(int b)
{
mb = b;
}
int getB()
{
return mb;
}
};
class Derived : public BaseA, public BaseB
{
int mc;
public:
Derived(int a, int b, int c) :BaseA(a), BaseB(b)
{
mc = c;
}
int getC()
{
return mc;
}
void print()
{
cout << "ma =" << getA() << ","
<< "mb =" << getB() << ","
<< "mc =" << mc << endl;
}
};
int main()
{
cout << "sizeof(Derived) = " << sizeof(Derived) << endl; //12
Derived d(1,2,3);
d.print();
cout << "d.getA() = " << d.getA() << endl;
cout << "d.getB() = " << d.getB() << endl;
cout << "d.getC() = " << d.getC() << endl;
cout << endl;
BaseA* pa = &d;
BaseB* pb = &d;
cout << "pa->getA() = " << pa->getA() << endl;
cout << "pb->getB() = " << pb->getB() << endl;
cout << endl;
void* paa = pa;
void* pbb = pb;
if( paa == pbb)
{
cout << "Pointer to the same object!" << endl;
}
else
{
cout << "Error" << endl;
}
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "paa = " << paa << endl;
cout << "pbb = " << pbb << endl;
return 0;
}
运行结果
sizeof(Derived) = 12
ma =1,mb =2,mc =3
d.getA() = 1
d.getB() = 2
d.getC() = 3
pa->getA() = 1
pb->getB() = 2
Error
pa = 0x7fffe245f4b0
pb = 0x7fffe245f4b4
paa = 0x7fffe245f4b0
pbb = 0x7fffe245f4b4
多重继承问题一:
通过多重继承得到的对象可能拥有“不同的地址”
解决方案:无
实质:指向同一个对象的不同位置(一个指向头,一个指向腿)
Derived d(1, 2, 3);
BaseA* pa = &d;
BaseB* pb = &d;
多重继承的问题二:
多重继承可能产生冗余的成员
示例:
#include <iostream>
#include <string>
using namespace std;
class People
{
string m_name;
int m_age;
public:
People(string name, int age)
{
m_name = name;
m_age = age;
}
void print()
{
cout << "Name = " << m_name << ","
<< "Age = " << m_age << endl;
}
};
class Teacher : virtual public People //中间父类加上virtual
{
public:
Teacher(string name, int age) : People(name, age)
{
}
};
class Student : virtual public People //中间父类加上virtual
{
public:
Student(string name, int age) : People(name, age)
{
}
};
class Doctor : public Teacher, public Student
{
public:
Doctor(string name, int age) : Teacher(name + "1", age + 1), Student(name + "2", age + 2), People(name, age)
{
}
};
int main()
{
Doctor d("Jenius", 33);
//d.print(); //有两个print函数
d.Teacher::print(); //使用作用域分辨符做选择
d.Student::print();
return 0;
}
运行结果
Name = Jenius1,Age = 34
Name = Jenius2,Age = 35
当多重继承关系出现闭合时将产生数据冗余的问题
解决方案:虚继承
class People();
class Teacher : virtual public People {};
class Student : virtual public People {};
class Doctor : public Teacher, public Student
{
};
虚继承能够解决数据冗余问题
中间层父类不再关心顶层父类的初始化
最终子类必须直接调用顶层父类的构造函数
问题:当架构设计中需要继承时,无法确定使用直接继承还是虚继承.
牺牲效率,牺牲移植性,架构师不喜欢多继承,多重继承仅作学术研究。
小结
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* pbb = (BaseB*)pa; //oops 不用这种方法
BaseB* pbc = dynamic_cast<BaseB*>(pa); //use this
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "Using pa to call funcA()..." << endl;
pa->funcA();
cout << "Using pb to call funcB()..." << endl;
pb->funcB();
cout << "Using pbb to call funcB()..." << endl;
pbb->funcB();
cout << "Using pbc to call funcB()..." << endl;
pbc->funcB();
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "pbb = " << pbe << endl;
cout << "pbc = " << pbc << endl;
return 0;
}
运行结果
sizeof(d) = 8 //同事拥有两个虚函数表指针,占用8个字节
Using pa to call funcA()...
BaseA::funcA()
Using pb to call funcB()...
BaseB:: funcB()
Using pbb to call funcB()...
BaseB:: funcA() //强制类型的使用问题导致
Using pbb to call funcB()...
BaseB:: funcB()
pa = 0x7fff843450d0
pb = 0x7fff843450d8
pbb = 0x7fff843450d0
pbc = 0x7fff843450d8
需要进行强制类型转换时,C++中推荐使用新式类型转换关键字!!
解决方案:dynamic_cast //继承,虚函数的转换
Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbb = (BaseB*)pa;
pa ----------> vptr1 <---------- pbb
<---------- pb
vptr2
程序运行本质:
通过pa调用funcA的过程:从pa里面拿到pa的地址,通过地址找到虚函数表指针vptr1,进而通过虚函数表指针找对象的函数地址,进而找到funA的地址。
通过pb调用funcB的过程:从pb里面拿到pb的地址,通过地址找到虚函数表指针vptr2,进而通过虚函数表指针所指向的虚函数表里面得到虚函数地址,进而找到funB的地址。
vptr1,vptr2两个虚函数表指针所指向的虚函数表在结构上是一样的,因此通过pbb调用funcB的过程:首先得到地址,得到的地址在pbb所指向的位置,然后找虚函数表指针,找到的虚函数表指针vptr1,就在pbb所指向的虚函数表里面找funcB()的地址,在这里面是找不到funcB()的地址,能够找到的是funcA()的地址,并且这个地址是完全正确没有误差的。
工程开发中的“多重继承”方式:
正确的多继承方式—单继承、多接口进行面向对象的设计示例:
#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); //110
pInt2->divide(11); //10
pInt1->minus(5); //5
pInt2->multiply(8);//40
cout << "p->getI() = " << p->getI() << endl; //40
cout << endl;
cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
return 0;
}
运行结果
p->getI() = 100
p->getI() = 40
pInt1 == p : 1
pInt2 == p : 1
一些有用的工程建议:
先继承自一个父类,然后实现多个接口
父类中提供equal()成员函数
equal()成员函数用于判断指针是否指向当前对象
与多重继承相关的强制类型转换用dynamic_cast完成
小结:
多继承中可能出现多个虚函数表指针
与多重继承相关的强制类型转换用dynamic_cast完成
工程开发中采用“单继承多接口”的方式使用多继承
父类提供成员函数用于判断指针是否指向当前对象