首页 > 学院 > 开发设计 > 正文

面向对象程序设计之基类,派生类与虚函数

2019-11-11 04:57:12
字体:
来源:转载
供稿:网友

面向对象程序设计的核心思想是:

数据抽象:实现类与接口的分离。

继承:定义相似类型并进行相似关系建模。通过继承联系在一起的类构成一种层次关系。通常在在层次关系的根部有一个基类,其他类直接或间接由基类继承而来。这些继承的类称为派生类基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自特有的成员。对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时的基类就将这些函数声明成虚函数

class Quote{public: std::string isbn() const; virtual double net_PRice(std::size_t n) const;};

派生类必须使用派生列表,必须将其继承而来的成员函数中需要覆盖的那些重新声明:

class Bulk_Quote : public Quote//Bulj_Quote继承了Quote{ double net_price(std::size_t n) const override;};

动态绑定:一定程序忽略相似类型的区别,而以统一的方式使用他们的对象。 在C++语言中,当我们使用基类引用或指针调用一个虚函数将发生动态绑定。

double print_total(ostream &os, const A &item,size_t n){ double ret=item.net_price(n);//根据传入的item的对象类型调用Quote::net_price,还是Bulk_Quote::net_price return ret;}

一个派生类对象包含多个组成部分:一个含有派生类自己定义的(非静态)成员的子对象,以及一个与该派生类继承的基类的子对象。因为在派生类对象中含有与其基类对应的组成部分,所以我们能把派生类的对象当成基类对象来使用,而且我们也能把基类的指针或引用绑定到派生类对象中的基类部分上。

Quote item;//基类对象Bulk_Quote bulk;//派生类对象Quote *p=&item;//p指向基类对象p=&bulk;//p指向派生类对象的基类部分Quote &r=bulk;//r绑定到派生类对象的基类部分

Quote类的定义:

这里写图片描述

派生类Bulk_Quote定义;

这里写图片描述

派生类对象的基类部分与派生类对象自己的数据成员都是在构造函数的初始化阶段执行初始化操作的。派生类构造函数同样是通过构造函数初始化列表来将实参传递给基类构造函数的。

这里写图片描述 每个类负责定义各自的接口。要想与类的对象交互必须使用类的接口,即使这个对象是派生类的基类部分也如此。尽管从语法上我们可以在派生类构造函数体内给它的 公有成员或受保护的基类成员赋值,但不建议这么做。


如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义。如果基类中的成员是private的,则派生类无权访问它。如果某静态成员是可访问的,则我们既能通过基类使用它也能通过派生类使用它。


派生类的声明:

class Bulk_Quote::public Quote;//错误,不需要派生列表class Bulk_ Quote;//正确

如果我们想将某个类作为基类,则该类必须是已经定义而非仅声明。

class Quote;//声明未定义class Bulk_Quote:public Quote{};//错误

防止继承的发生: C++11标准提供了一种防止继承发生的方法,即在类名后跟一个关键字final.

这里写图片描述


静态类型与动态类型: 表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型。动态类型则是变量或表达式表示的内存的对象的类型。动态类型直到运行时才知道。基类的指针或引用的静态类型可能与其动态类型不一致,取决于传递的实参。


因为一个基类的对象可能是派生类对象的一部分,也可能不是,所以不存在基类向派生类的自动类型转换即使一个基类指针或引用绑定在一个派生类对象上,我们也不能执行从基类向派生类的转换。

当我们用一个派生类对象为一个基类对象初始化或者赋值时,只有该派生类对象的基类部分会被拷贝、移动或赋值,它的派生类部分被忽略掉。

Bulk_Quote bulk;//派生类对象Quote item(bulk);//调用Quote::Quote(const Quote&)item=bulk;//调用Quote::Operator=(const Quote&)

虚函数: 当我们使用基类引用或指针调用一个虚成员函数时会执行动态绑定。因为我们直到运行时才能知道到底调用了哪个版本的虚函数,所以所有虚函数必须有定义。

基类中的虚函数在派生类中隐含地也是一个虚函数。当派生类覆盖了某个虚函数时,该函数在基类中的形参必须与派生类中的形参严格匹配

override说明符说明派生类中的虚函数,final说明之后任何尝试覆盖该函数的操作都将引发错误。

如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类的函数版本也是如此即传入派生类函数的是基类函数定义的默认实参。如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。

#include <iostream>class A{public: A(int v):v(v){}//A构造函数 virtual void print(int x=100)//基类虚函数默认实参 { std::cout<<x<<std::endl; }private: int v;};class B:public A//派生类 {public: B(int v):A(v){}//B构造函数 void print(int x=10) override//默认实参与基类不一致 { std::cout<<x+100<<std::endl;//传入基类虚函数的默认实参 }private: };int main(){ B b(10); A &aptr=b; aptr.print();//200 return 0;}

如果我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本,可以使用作用域运算符。

//强行调用基类中定义的函数版本。double undiscounted=baseP->Quote::net_price(42);

如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归。


抽象基类:

我们可以在函数体的位置书写=0来说明一个虚函数为纯虚函数=0只能出现在类内部的虚函数声明语句处。我们可以为纯虚函数提供定义,不过函数体必须定义在类外

double net_price(std::size_t)const=0;//纯虚函数

含有(未经覆盖直接继承)纯虚函数的类是抽象基类。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。我们不能直接创建一个抽象基类的对象这里写图片描述



发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表