继承与多态
# 1 类与类的关系
# 1.1 继承(inheritance) 表示 is-a
父类和派生类被称为继承。
class _List_node_base{...}; // base class
class _Lisr_node: public _List_node_base {...} // derived class
# 1.2 组合 (composition) 表示has-a
类中包含另一个类的对象,所占空间累加在当前对象上
template <class T, class Sequence = deque<T>>
class queue {
...
protected:
Sequence c; // 底层容器
public:
// 以下完全利用 c 的操作函數完成
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference front() { return c.front(); }
reference back() { return c.back(); }
// deque 是兩端可進出,queue 是末端進前端出(先進先出)
void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
构造由内而外,析构由外而内:构造时先调用里边对象的构造函数,然后再调用当前类的构造函数,析构时正好相反。
非共有继承也是一种has-A的组合方式,非公有继承中,子类不能转化为父类,因为创建父类指针指向子类对象的时候,会调用子类构造函数,再调用父类构造函数,但此时父类构造函数的访问权限是非共有的,因此访问不到父类的构造函数,即无法用这种方式创建对象。主要在基类中没有普通成员属性的时候使用这种继承方式,可以减少内存占用。
# 1.3 委托 (delegation) 表示composition by reference
类内含有另一个类的指针。
class StringRep;
class String
{
public:
... // 一些公有接口
private:
StringRep* rep; //类内含有类的指针
}
class StringRep{...};
组合与委托的区别
- 组合生命是一起出现的,有queue对象就有了deque对象。
- 而委托,类内含有指针,只有需要用到类指针的时候才会有指向的实现类,不同步。
# 2 继承
单继承:同时有多个父类 多继承:只有一个父类
# 2.1
# 2.2 虚函数
在基类和派生类对应的函数前添加virtual关键字。 override关键字 派生类可以在它覆盖的函数前使用virtual关键字,但不是非得这么做。C++11中,允许显示注明它使用某个成员函数覆盖了它继承的虚函数,在函数后添加关键字override,这样做的好处是,使得程序员的意图更加清晰的同时,还可以让编译器发现一些错误。
与重定义进行区分: 重定义:派生类的函数屏蔽了与其同名的基类函数,规则如下:
- 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
- 如果派生类的函数与基类的函数同名,并且参数也相同,但是基函数没有virtual关键字,此时,基类的函数被隐藏。
class A {
public:
A() { cout << "A constructor" << endl; }
virtual ~A(){}
virtual void test() { cout << "A test()" << endl; }
};
class B : public A {
public:
B() { cout << "B constructor" << endl; }
virtual ~B() {}
void test(int a) { cout << "B test(int)" << endl; }
};
int main(){
B* pB = new B;
pB->test();//error : 函数中调用的参数太少,因为此时基类的函数被隐藏.
}
# 2.3 纯虚函数
virtual void test() = 0; 一个具有纯虚函数的基类称为抽象类.
# 2.3 虚继承
菱形继承(继承路径上有一个公共的基类),会产生二义性,公共基类会在派生类的对象中产生多个基类子对象,为了解决以上问题,必须对这个基类声明为虚继承,使这个基类成为虚基类。
class A{public: int a;};
class B : virtual public A{public: int b;};
class C : virtual public A{public: int c;};
class D : public B, public C{public: int d;}
//调用类的顺序为:A-B-C-D
虚继承会生成虚基表和虚基表指针。 虚继承的内存结构 虚继承的内存结构和普通继承的内存结构是不同的,虚基类会在子类结构中被整合到后部。 上述案例中的内存结构为
内存 | 数据 |
---|---|
ec0 | B类虚基表指针 |
ec8 | b |
ed0 | C类虚基表指针 |
ed8 | c |
ee0 | d |
eec | a |
具体情况可写代码进行测试
# 3 多态
动态绑定只有通过指针或引用调用虚函数时才会发生。 当通过一个具有普通烈性(非引用非指针)的表达式调用虚函数时,在编译时就会将调用的版本确定下来。
# 3.1 多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表时一个存储类成员函数指针的数据结构,虚函数表时由编译器自动生成与维护的.
声明在内层作用域的函数不会重载声明在外层作用域的函数。
# 4 衍生出的设计模式
# 4.1 单例模式
# 4.2 观察者模式
委托+继承
class Subject{
int m_value;
vector<Observer*> m_views;
public:
void attach(Observer* obs){
m_views.push_back(obs);
}
void set_val(int value){
m_value = value;
notify();
}
void motify(){
for(int i = 0; i < m_views.size(); ++i){
m_views[i]->update(this, m_value);
}
}
};
class Observer{
public:
virtual void update(Subject* sub, int value) = 0;
};