面向对象编程(OPP:Object-oriented programming)基于三个基本的概念:数据抽象、继承和动态绑定。之前在学习“基于对象的编程”时已经了解到了数据封装和抽象的作用,今天学习的这部分主要来讨论后两个重要的概念。如果说之前的类只是涉及一维层次的话,这里将涉及多个层次的类与类之间的关系。这部分的内容比较多,知识点繁杂,短时间不可能看透,所以自己就感觉相对重要的知识点简单进行一个梳理,作为日后继续研习提高的基石。
一、继承与动态绑定
谈到继承,熟悉C++的人一定不陌生。可以选定一个类作为基类给派生类继承,派生类将继承基类的所有非static成员。派生类中的成员天然地可以看作两个部分:继承自基类的部分以及自身特性覆盖定义或新增的部分。我觉得这里最重要的是形成这样一种认识,派生类逻辑上是由“基类部分”+“自身部分”组合而成的,虽然物理上未必如此,因为C++并不规定派生类的基类成员与新增成员必须统一存储。基类可以提供接口供派生类继承,也可以指定为virtual函数要求派生类重新定义,如:
class base
{
public:
virtual print()
{cout << "This is a base"<
};
class derived: public base
{
public:
print()
{cout << "This is a derived"<}
};
... ...
class derived D-obj;
class base &ref = D-obj;
D-obj.print();
... ...
例子虽然简单,但是可以看到一些最基本的知识点。首先在基类中可以定义一个虚函数,而后在派生类中对该虚函数进行“重定义”,这里会体现出派生类自己独特的属性;在用户代码中,定义了指向基类的指针绑定到派生类对象上,调用出派生类的print()行为,实现了动态绑定。其实动态绑定需要两个条件:一个条件时该操作被声明为虚函数;第二个条件则是利用指向基类的指针或者引用。由于派生类本身即包含着基类部分,因此可以使用基类的指针或者引用;另一个方面,也使得由派生类向基类的类型的自动转换成为可能,有些时候由派生类向基类转换时会舍弃派生类部分,比如:
void fun-1(base B-obj)
{
B-obj.print();
}
fun-1(D-obj); //实参传递时D-obj去掉派生类部分的副本
由于这里涉及到了虚函数(virtual),因此简单小结下虚函数需要注意的几个问题:
1. 派生类一般会重定义虚函数,否则使用基类中定义的版本;
2. 派生类中虚函数的声明必须与基类中的定义方式完全匹配,但是当虚函数返回基类型的引用或指针时除外;
3. 一旦声明为虚函数,则一直为虚函数,派生类重定义虚函数时,可以使用virtural关键字,也可以不用;
二、公有、私有和受保护的继承
在派生类继承基类时,涉及到public\private\protected三种层级。首先需要明确的是派生类的继承层级只能严格基类本身的访问层级,不能放宽。即对于基类而言,其public成员可以供所有用户访问,private成员则只允许自身的函数访问。对于派生类而言,同样不能够访问基类的private成员。当发生类继承关系时,遵循着“最严格”原则,即总是取基类约束与继承约束最严格的访问等级。这种规则下,public继承不改变原有的基类的访问层级,而基类的protected成员可以由派生类访问但是却不能由普通的用户代码访问。之所以引入了protected,就是为了提供一种派生类可以访问而其他代码不能访问的控制层级。
需要说明的是,基类声明为友元的对象当然可以访问基类的所有成员,包括private;但是友元关系不能继承,仅仅针对声明的对象成立。即:友元类的派生类继承对目标类的友元关系;目标类的派生类也不能继承被友元类访问的属性。说起来比较拗口,如下面简单的例子:
-
class Base {
-
friend class Frnd;
-
protected:
-
int i;
-
};
-
class D1: public Base {
-
protected:
-
int j;
-
};
-
class Frnd {
-
public:
-
int mem(Base b) {return b.i};
-
int mem(D1 d) {return d.i}; //Error:friendship doesn't inherit
-
};
-
-
class D2: public Frnd {
-
public:
-
int mem(Base b) {return b.i} //Error:friendship doesn't inherit
-
};
Frnd作为友元类可以方位Base的成员,但那是却不能范围Base的派生类成员;Frnd的派生类也不能继承父类的友元属性,同样不能访问Base类。
三、构造函数与复制函数
构造函数与复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。如同之前所说的情况,如果类不定义自己的默认构造函数和复制控制成员,就使用默认版本。自己觉得这里最重要的是认识到:派生类构造函数的构造顺序是先基类再派生类;析构函数是先派生类再基类;每个派生类的构造函数只能初始化其直接的父类,不能涉及其祖父辈的类。
阅读(256) | 评论(0) | 转发(0) |