C++继承与多态

        C++是一款面向对象的语言,对象是可以物化的实体,拥有自己的特性与行为,像车子有外形,内核,可以加速减速。而车子又分为保时捷与宝马、奔驰等等。我们想要描述一个车子对象的特征,就需要创建一个类,若为每一种车创建一个类,会使效率下降,而不同车子之间既有共性又有特性,因此,便产生了派生类。

一、派生类的继承方式

派生类(子类)继承了基类(父类)的特性,同时它可以添加自己的诸多特点。继承的方式有以下三种

 

 既:派生类对基类的继承小于等于基类的数据(包括成员变量与成员函数)

无论用那种方式继承,派生类对基类数据的访问都只限于public和protected区域,而不能访问private区域。如果要访问基类的private区域,可以在基类的public或protectd区域里面创建接口

#inlcude<iostream>
using namespace std;
class base
{
  public:
     int func()
        {return m_a}
private:
    int m_a;
};
class derived:public base
{
......
};

派生类derived正常情况下访问不到m_a,但添加了func这个接口,即可通过func来访问。

二、构造派生类对象

构造一个对象,首先调用的是构造函数,派生类继承了除构造函数和析构函数的其他非私有部分,但派生类仍是属于基类的,所以如果创建一个派生类对象,那么构造函数和析构函数的调用顺序如下

#include <iostream>
using namespace std;
class Base
{
public:
	Base(int a)
	{
		ma = a;
		cout<<"Base::base() ";
	}
	~Base()
	{
		cout<<"Base::~base() ";
	}
private:
	int ma;
	Test t;
};
 
class Derive : public Base
{
public:
	Derive(int b):Base(b)
	{
		mb = b;
		cout<<"Derive::derive() ";
	}
	~Derive()
	{
		cout<<"Derive::~derive() ";
	}
private:
	int mb;
};
 
int main()
{
	Derive d(2);
	 
	return 0;
}

Base::base() Derive::derive() Derive::~derive() Base::~base()

可知,派生类的创建先调用了基类的构造函数和其部分成员变量,再调用派生类构造函数及其部分成员变量,如果基类中还有对象,则应先调用成员对象的构造函数,其中,析构函数的调用顺序与构造函数相反。 

三、多态的初步形成

如果派生类中有和基类中重命名的成员,则有三种可能性:重载,隐藏和覆盖。

1、重载

void Swap1(int* a, int* b);
void Swap2(float* a, float* b);
void Swap3(char* a, char* b);
void Swap4(double* a, double* b);

以上是重载的实现,C++允许在同一作用域中声明几个类似的同名函数,通过函数的参数个数,种类与排列顺序来区分。函数的返回值类型可相同可不同(函数的返回值类型不是区分函数的条件)。C++的函数重载是在编译时完成的,既C++编译器为根据参数的种类,数量或者排列顺序不同将函数区分开来。

2、函数隐藏 

函数隐藏,是指C++中派生类覆盖基类同名成员函数(不考虑参数列表)

class  Base
{
public:
	Base(int a) :ma(a){}
	 void Show()
	{
		std::cout << "Base::ma:" << ma << std::endl;
	}
protected:
	int ma;
};
class Derived :public Base
{
public:
	Derived(int b) :mb(b), Base(b){}
	void Show()
	{
		std::cout << "Derived::mb:" << mb << std::endl;
	}
protected:
	int mb;
};
int main()
{
	Derived a(2);
    a.Show();
    Base* pb = new Derived(10);
	pb->Show();
	return 0;
}

 运行结果:Derived::mb:2

Base::ma:10        

 即:即使派生类继承了基类的show函数,但派生类中的show函数将基类中的show函数替换掉了,但如果用Base类的指针指向show函数,还是能找到Base类的原函数的,即使它指向的是一个派生类对象(派生类对象本就属于基类,基类指针优先在基类中寻找函数,找到所需函数即停止寻找)。

三、函数覆盖

class  Base
{
public:
	Base(int a) :ma(a){}
	 virtual void Show()
	{
		std::cout << "Base::ma:" << ma << std::endl;
	}
protected:
	int ma;
};
class Derived :public Base
{
public:
	Derived(int b) :mb(b), Base(b){}
	void Show()
	{
		std::cout << "Derived::mb:" << mb << std::endl;
	}
protected:
	int mb;
};
int main()
{
	Base* pb = new Derived(10);
	pb->Show();
	return 0;
}

运行结果:Derived::mb:10

        当父类函数show()为virtual函数时,调用它的对象类型为指针类型指向的类型,这是一个动态的过程,当类中出现virtual函数时,在编译时期会为对象产生一个虚函数表,将virtual的成员放在里面,我们已经知道,基类的构造早于派生类,所以在创建虚函数表时,将基类的Show函数先放入改表中,到派生类时,派生类中的Show函数(若基类存在virtual函数类型,则系统自认派生类函数为虚函数)将基类的Show函数覆盖,因此,我们用Base虚指针创建Derived对象,再调用其Show函数时,看到的是派生类中的Show函数。

在次,我对基类和派生类中一些指针和引用情况进行简述。

派生类对象可以赋值给基类对象,但基类对象就不可以赋值给派生类对象。好比保时捷肯定是一个车,但你不能见到一个车就说它是保时捷。

基类指针(引用)可以指向派生类对象,但只能访问派生类中基类部分的方法,不能访问派生类部分的方法,派生类指针(引用)不可以指向基类对象,解引用可能出错,因为派生类的一些方法可能基类没有。

 四、虚函数

 下面我们借助一段代码来说明虚函数的一些问题

class A
{
public:
    virtual void foo() { cout << "A::foo() is called" << endl;}
};

class B: public A
{
public:
    virtual void foo() { cout << "B::foo() is called" << endl;}
};
int main()
{A * a = new B();
a->foo(); }

 我们运行程序,得到的会是B的foo函数,如果基类中foo函数不加virtual,那么A的指针将无法访问B中的foo,在将A中的foo定义为虚函数后,如果想用B创建的对象来访问A中的foo函数,在foo函数的前面加上A::作用域即可。

#include <iostream.h>  
#include <string.h>  
class Thing  
{ public:  
virtual void what_Am_I( ) {cout << "My name is Wangyibo./n";}  
 ~Thing(){cout<<"Thing destructor"<<endl;}  
};  
class Person : public Thing  
{    
public:  
virtual void what_Am_I( ) {cout << "I am Wangyibo./n";}  
~Person(){cout<<"Person destructor"<<endl;}  
};  
void main( )  
{  
   Thing *t =new Thing;        
   Person*x = new Person;  
   Thing* a[2];  
   a[0] = t;                                  
   a[1] = x;                 
    for (int i=0; i<2; i++)  array->what_Am_I( ) ;  
   delete a[0];  
   delete a[1];  
   return ;  
}  

该段代码中,派生类没有定义虚析构函数,会造成delete父类的指针时只调用父类的析构函数,而不调用子类的析构函数。(前面提到过,基类的构造函数和析构函数是不被派生类继承的,基类的指针也就无法指向派生类的析构函数),此时delete a[0],就不会调用虚析构函数,如果在派生类构造函数中存在内存释放,不被调用,就会造成内存泄漏。所以,我们需要在基类中的析构函数前加上virtual时,基类的指针就会先调用派生类的析构函数(存在于虚函数表中),再返回自己的基类中调用基类析构函数。也就是说虚函数的指针和引用可以找到对应函数,而不仅是执行定义类的函数。但,构造函数不能声明为虚函数,因为在执行构造函数前对象没有创建,因此编译时产生的虚函数表里没有构造函数,我们也无法得知调用那个构造函数了。

纯虚函数

纯虚函数的简单模板如下:

class base
{  public:
    virtual base()=0;
};
base::base()
{}

纯虚函数可以是类中的任意函数,只要类中出现纯虚函数,则该类被称为抽象类,即不可以创建对象,只能作为基类模板使用。但即使是抽象类,编译器仍会编译改类,所以我们需要在类外对纯虚函数进行声明。

虚继承

虚继承主要解决的是一个派生类如果继承多个基类,同时基类中又有几类继承同一类的话,在创建对象时,会产生二义性。

class A{
protected:
    int m_a;}
class B:virtual public A{
protected:
    int m_b;
};
class C:virtual public A{
protected:
    int m_c;
};
class D: public B, public C{
public:
    void seta(int a){ m_a = a; }  
    void setb(int b){ m_b = b; }  
    void setc(int c){ m_c = c; }  
    void setd(int d){ m_d = d; }  
private:
    int m_d;
};
int main(){
    D d;
    return 0;
}

 如本段代码,如果我们不在类B和类C继承A时加上virtual  seta函数在运行时,将发生错误,因为它有两个可选的途径,这会造成二义性。添加virtual之后,D类创建对象时,则会选择最近的A类来创建,并且只产生一份m_a的数据。

  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
设计一个图书管理系统,可以采用面向对象的思想,使用C++语言来实现,以下是一个简单的设计思路: 1. 首先,创建一个基类 Book,包含一些共同的属性和方法,例如书名、作者、出版社、价格等属性,以及借阅、归还等方法。 2. 然后,创建派生类 FictionBook 和 NonFictionBook,分别表示小说类和非小说类图书。这两个派生类继承了基类的属性和方法,并可以添加自己特有的属性和方法。 3. 接着,创建一个管理图书的类 BookManager,用于添加、删除和查询图书信息等操作。这个类中可以包含一个 Book 类型的数组或链表,用于存储所有的图书信息。 4. 最后,实现多性,通过虚函数实现不同类型的图书的借阅和归还操作,将这些函数定义为虚函数,让不同类型的图书派生类去实现。 下面是一个简单的类图: ``` +------------------------+ | Book | +------------------------+ | -title: string | | -author: string | | -publisher: string | | -price: double | +------------------------+ | +borrow() | | +return() | +------------------------+ /\ || || +------------------------+ | FictionBook | +------------------------+ | -genre: string | | -rating: int | +------------------------+ | +borrow() | | +return() | +------------------------+ /\ || || +------------------------+ | NonFictionBook | +------------------------+ | -subject: string | | -level: string | +------------------------+ | +borrow() | | +return() | +------------------------+ /\ || || +------------------------+ | BookManager | +------------------------+ | -books: Book[] | +------------------------+ | +addBook() | | +removeBook() | | +searchBook() | +------------------------+ ``` 这样设计的好处是,可以扩展新的图书类型,同时也方便管理和查询图书信息。同时,使用多性可以让代码更加灵活,同时也更加易于维护。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值