c++面向对象的程序设计
面向对象程序设计的核心思想是数据抽象(封装)、继承、动态绑定(多态)。通过数据抽象,我们将类的接口(函数)与实现(数据成员)分离;使用继承,可以定义相似的类型并对相似关系建模;使用动态绑定,一定程度上忽略相似类型的区别,以同一的方式使用它们的对象。
派生类继承基类,基类定义所有类共有的成员,而每个派生类定义各自特有的成员。
1、基类和派生类
(1)基类
基类与一般类的定义相同:
class A{
int a;
};
如果将某个类用作基类,则该类必须已经定义而非声明。在类名后添加final关键字可以防止继承。
(2)派生类
派生类需要使用派生类列表,来指明继承自哪些基类,每个基类前面可以由访问说明符private、protected、public:
class B:public A{
double b;
};
派生类继承基类的所有成员,但是对基类成员的访问权限与访问说明符有关。派生类可以添加新的成员。
2、虚函数
对于某些函数,派生类需要定义自己的函数与基类的版本做适当改变,此时基类可以将这些函数声明添加virtual关键字变成虚函数:
virtual int fun(int)const;
- 任何构造函数之外的非静态函数都可以是虚函数,
- 关键字virtual只能出现在类内部的声明语句中,而不能用于外部的函数定义。
- 如果接类把一个函数声明成虚函数,该函数在派生类中也是虚函数。
- 如果函数被声明成虚函数,则其解析过程发生在运行时,否则发生在编译时。
派生类可以不改写基类的虚函数,直接继承;如果有需要覆盖,则必须要对重新定义的虚函数进行声明:
int fun(int)const override;;
具体做法是在形参列表后面、const成员函数的const关键字后面、引用成员函数的引用限定符后面添加一个关键字override。
(1)虚函数与默认实参
如果我们通过基类的引用或指针调用了虚函数,即使实际运行的是派生类的版本,我们也使用基类中定义的默认实参,此时传入派生类函数的是基类函数定义的默认实参。因此,如果虚函数使用了默认实参,基类和派生类中定义的默认实参最好一致。
(2)回避虚函数机制
如果我们希望代码执行某个版本的虚函数,使用作用域运算符实现这一目的:
p->A::fun();
该代码强行调用A的fun函数,而不管实际指向的对象类型是什么。通常当一个派生类的虚函数调用它覆盖的基类虚函数版本时,我们需要用到回避虚函数的默认机制。
(3)纯虚函数
纯虚函数是在虚函数的基础上发展而来,通过对继承的虚函数重新声明,通过在参数列表后面后面添加=0来声明一个纯虚函数:
void fun()=0;//fun必须在其基类中是虚函数
- =0只能出现在类内部的虚函数声明语句处。
- 纯虚函数可以只声明不定义。
- 含有纯虚函数的类是抽象类,派生类对象如果没有覆盖抽象的基类,则其也是抽象类。
- 我们不能创建一个抽象类的对象,可以定义其派生类的对象,前提是派生类覆盖了基类的纯虚函数。
3、动态绑定
(1)派生类到基类的类型转换
因为派生类对象中含有与其基类对应的组成部分,所以我们能把派生类对象当作基类来用:
A ItemA;
B itemB;
A *pa=&itemA;
pa=&itemB;//pa指向itemB的A类部分
A &ra=itemB;//ra绑定到itemB的A类部分
上述的转换称为派生类到基类的类型转换。
函数的运行版本根据实参的具体类型(指针或引用指向的是基类还是派生类)决定使用哪个虚函数,这种在运行时选择函数的版本被称为动态绑定(运行时绑定)。动态绑定只有在我们通过指针或引用调用虚函数时才会发生。
(1)静态类型和动态类型
表达式的静态类型在编译时知道,它是变量声明是的类型或表达式生成的类型;动态类型是变量或表达式表示的内存中的对象的类型,动态类型在运行时才知。
如果表达式既不是引用也不是指针,则它的类型永远与静态类型一致。
之所以存在派生类向基类的类型转换时因为每个派生类对象都包含了基类的那部分,而基类的引用或指针可以绑定到该基类部分上。而不能将派生类的指针或引用指向基类,因为指针可能访问基类不存在的派生类成员。
基类指向派生类——可以
派生类指向基类——不行
3、派生类构造函数
尽管从语法上来说我们可以在派生类构造函数体内给它的公有或受保护的基类成员赋值,但最好不要这么做,要想与类的对象交互必须使用该类的接口,即使是派生类,在初始化对象的基类部分时也只能调用基类的构造函数来初始化那些从基类继承而来的成员。
每个类控制它自己的成员初始化过程。
派生类构造函数是通过构造函数初始化列表来将实参传递给基类构造函数的:
B( int va,double vb):A(va),b(vb){}
首先初始化基类部分,然后按照声明顺序初始化派生类的成员。
4、继承与静态成员
如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义,不论从基类继承了多少个派生类,对于每个静态成员来说只有一个。
5、继承与访问说明符
访问说明符public、protected、private。
(1)基类中成员的访问说明符
public:谁都能访问。
private:只有自己类的成员函数或其友元能访问。
protected:自己类、友元、其派生类的成员、派生类的友元都能访问。但派生类的成员或派生类的友元只能访问派神类继承基类的那部分受保护成员,不能访问基类对象中的受保护成员。但是基类的友元可以访问基类对象中的所有成员。
总结就是:
- 成员函数能访问自己类的所有成员,
- 友元也能访问友元类的所有成员。
- 派生类能和其友元能访问从基类继承来的的public和protected成员。
- 派生类和其友元不能访问基类对象的protected和private成员。
(2)派生列表的访问说明符
派生列表的访问说明符的作用是将继承的基类中成员的访问说明符升级,限制派生类的对象(用户)对这些成员所能进行的操作;派生类的成员函数对基类所能进行的操作没有任何影响,有权限的还是有权限,无权限的还是没有权限。
public:成员的访问说明符没变,派生类用户可以访问基类中的public成员。
protected:public变成protected,派生类用户不能访问基类的所有成员。
private:所有成员变成private,派生类用户不能访问基类的所有成员。
(3)struct和class的区别
前面说过二者的区别是默认的访问说明符,一个是public一个是private
现在添加一个区别是默认的派生访问说明符,一个是public继承,一个是private继承。