封装
对类的封装,将类成员变量私有化,提供公共的接口以访问成员变量,避免了外部函数直接访问,以达到封装的目的。
允许任何类或外部函数直接访问的设为public
允许子类访问的设为protected
只允许本类访问的设为private
protected与private单从一个类来看几乎没有区别,他们均不能被直接调用,而是要通过公共接口来访问。他们的明显区别要放到继承中才能得以体现,protected能被子类的公共接口直接调用,private不能被子类公共接口直接访问(除非子类接口调用了父类可访问该私有成员的公共接口)。
继承
继承是一个普遍到特殊的过程
因为子类会在父类原有功能的基础上进行扩展,不同的子类在保持了父类的一些相同特性后又会根据需要衍生出其他子类所没有的内容,因此父类看作是一个共性的集合体,而子类则是该集合体的一种特殊分支。
以鸟类来举例,所有鸟类都具有相同的鸟类特征因此才会被归为鸟,而不同的鸟种又有不同的特点,如啄木鸟的特点是能啄木,企鹅的特点是不会飞等,这时鸟类作为父类,而啄木鸟企鹅等则是为子类。
类的继承有三种方式:共有继承,私有继承以及保护继承
class A : public B
class A : private B
class A : protected B
下面这张表将展示三种继承后,父类的成员访问属性将发生何种变化:
父类 pubilc | 父类 protected | 父类 private | |
---|---|---|---|
公有继承 | 子类 public | 子类 protected | 父类 private |
保护继承 | 子类 protected | 子类 protected | 父类 private |
私有继承 | 子类 private | 子类 private | 父类 private |
注意上述父类private,不管哪种继承,父类私有始终是父类的私有,子类无法直接访问,需调用父类公有函数才能访问。而protected会变成子类的成员,此时子类的公有函数也能访问他了。这里就是封装中我们提到的二者的差别。
class Base
{
public:
int Base_pub = 1;
protected:
int Base_pro = 2;
private:
int Base_pri = 3;
public:
show_Base_pub(){cout << Base_pub;};
show_Base_pro(){cout << Base_pro;};
show_Base_pri(){cout << Base_pri;};
}
class Child : public Base
{
public:
show_Child_pub(){cout << Base_pub}; //合法
show_Child_pro(){cout << Base_pro}; //合法,Base_pro以及为子类的保护成员
show_Child_pri(){cout << Base_pri}; //非法,Base_pri仍然是父类私有属性
show_Child_pri(){show_Base_pri()}; //修改,调用父类公有函数,合法
}
子类在创建以及销毁时,调用构造与析构的顺序严格如下:
父类构造-----》子类构造-----》子类析构-----》父类析构
多态
多态也分静态多态以及动态多态;静态多态以重载和模板来实现,动态多态以虚函数方式来实现,当然这里最重要的无疑是动态多态,以下只讨论这种动态的方式。
C++多态的实现需要满足两个条件:
1.父类指针指向子类对象
2.调用的函数为虚函数
Base *base = new Child();
base->virtual_fun() //此时调用的是子类的对应虚函数
C++支持这种父类指针指向子类对象的操作,因此在看到下面这种写法时也不用疑惑
void func(Base * b){ /*函数体*/ }
Child c;
func(&c); //在我看来这种写法更好地向初学者展示了何为多态,即我们通过一个写定的func接口,传入不同的对象参数就能做到不一样的处理逻辑了。
我们在上面的继承中提到过创建子类时构造与析构的顺序,在多态时其顺序则有些变化:
父类构造-----》子类构造-----》父类析构
可以看到这里并不会调用到子类的虚构,这就很可能引发泄露问题,解决该现象的方法是将父类析构函数设为虚函数,之后在销毁对象时便会调用到子类的虚构。事实上,将析构设为虚函数这是非常常见的一种写法。
引申疑问:为什么构造函数不能像析构一样设为虚函数呢?
我们知道具有虚函数的类中会多一个虚表指针,虚函数要执行就必须通过这个指针先找到虚表,有意思的地方在于,虚表指针是在构造函数中进行初始化工作的,这就类似死锁的状态,虚表指针需要先执行构造函数,但构造函数又需要虚表指针先被初始化。因此显然不适合将构造函数设为虚函数。