复习笔记(四)——C++继承

基类与派生类

在原有类的基础上派生出新的类,新类继承原有类的属性和方法,称原有的类为基类, 又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。

假定有一个类A,要创建一个新类B,它是类A的一个特殊版本。类A就称为基类,类B则称为派生类。类A是父,类B就是子类。

单继承和多继承

一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。
在这里插入图片描述

继承和派生(难点)

  • 继承允许以现有的类为基础来构建新类
  • 派生类继承基类的属性和行为
  • 派生类可以修改继承的属性和行为
  • 派生类可以增加新的属性和行为
  • 派生类对象也是基类对象
  • 派生类对象和基类对象可以被统一管理

在这里插入图片描述
继承和派生有什么区别?

继承与派生其实是同一过程从不同的角度看,我们将保持已有类的特性而构造新类的过程称为继承,说白了继承的目的就是实现原来设计与代码的重用,希望尽量利用原有的类。然而当新的问题出现,原有程序无法解决或不能完全解决时,需要对原有程序进行改造,在已有类的基础上新增自己的特性而产生新类的过程称为派生。

何时使用继承?

  • 规则1:如果类A和类B毫不相关,不可以为了使B的功能更多些而让B继承A的功能和属性。
  • 规则2:若在逻辑上B是A的“一种”,则允许B继承A的功能和属性。
  • 规则3:看起来很简单,但是实际应用时可能会有意外,继承的概念在程序世界与现实世界并不完全相同。

所以更加严格的继承规则应当是:若在逻辑上B是A的“一种”,并且A的所有功能和属性对B而言都有意义,只是B类多了一些独有的特性,则允许B继承A的功能和属性。

继承的意义

  • 便于管理系统中的对象。
  • 系统扩充比较容易- 利用程序代码的再用性。
    -加快程序发展的速度
    -减少程序的错误

派生类的声明

单继承派生类的声明语法为:

class 派生类名 : 继承方式  基类名
{
          派生类新增成员的声明;
};

继承要考虑的部分:①派生类不同于基类的部分;②派生类扩充基类的部分。

示例:

class CForm{
public:
	CForm();
	~CForm();
	//其他省略……
};
class CFormMainMenu:public CForm   //公有方式继承
{
public:
	CFormMainMenu();
	~CFormMainMenu();
	 //其他省略……
};

类成员的访问权限(回顾)

在类中,我们是设计师,public, private, protected三种成员可以访问。

在对象中,我们是使用者,只有规定的接口public才可以访问;protected, private不能访问。

私有成员(private):可以被类自身的成员和友元访问,但不能被包括派生类在内的其他任何类和任何普通函数访问 。

公有成员(public):可以被任何普通函数和任何类的成员函数访问。

保护成员(protected):可以被类自身的成员和友元访问外,还可以被派生类的成员函数访问,但不能被任何非友元的普通函数访问

基类中的私有成员

能被派生类继承

无论是公有继承还是私有继承,都不能被派生类的成员函数直接访问

能通过基类的public或protect成员函数访问。

实例:

#include<iostream>
using namespace std;
 
class A
{
private :
	int a;
public :
	//构造函数,默认将a赋值为2;
	A(){a = 2;}
	//获得私有成员a的值
	int getNum(){return a;}
};
 
class B : private A
{
public:
	//因为是私有继承,无法访问基类的公有成员函数,
	//只能自定义一个成员函数来访问该类B从基类A继承下来的属性。
	int get()
	{
		return getNum();//getNum函数是从基类私有继承下来的私有函数。
	}
};
 
int main()
{
	B b;
	cout<<b.get()<<endl;
}

执行结果:
2

派生类的三种继承方式

类中的成员被派生类继承后成员对外的可见性会有所不同。

  • 公有继承(public):特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的
  • 受保护继承(protected):特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问基类的私有成员仍然是私有的
  • 私有继承(private):默认继承方式,特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问

三种继承方式的区别:
在这里插入图片描述

公有继承(public)

基类中公有成员和受保护成员被继承后可见性不变

public方式继承成员存取权限等级的变化
在这里插入图片描述

受保护继承(protected)

基类中公有成员和受保护成员被继承后都是受保护的。
在这里插入图片描述

私有继承(private)

基类中公有成员和受保护成员被继承后都是私有的

private方式继承成员存取权限等级的变化
在这里插入图片描述

继承关系中构造和析构函数

继承关系中构造函数之间的关系

派生类不继承基类的构造函数

派生类和基类的构造函数之间是一种自动调用的关系

创建派生类对象时,派生类构造函数要调用基类的构造函数对基类的数据成员进行初始化
-先执行基类构造函数,然后执行派生类构造函数体
-基类构造函数需要参数时需要显式调用,格式:

派生类名::派生类名(派生类构造参数表)
	:基类(基类构造函数参数表)	

-基类构造函数不需要参数时(有默认构造函数),隐式调用

继承关系中析构函数之间的关系

派生类不继承基类的析构函数

派生类和基类的析构函数之间是一种自动调用的关系

派生类的析构函数也需要调用基类的析构函数做一些和基类相关的清理工作

销毁派生类对象时,先执行派生类析构函数,然后执行基类析构函数体

继承中构造和析构函数的执行顺序

构造函数的执行顺序:
①先调用基类的构造函数初始化从基类继承的数据成员
②再执行自己的函数体初始化定义于派生类的数据成员

析构函数的执行顺序:
①派生类的析构函数先执行自己的函数体
②再调用基类的析构函数

构造和析构函数实例

#include<iostream>
using namespace std;
 
class People 
{
public:
   People(char *str);	//构造函数
   ~People();	//析构函数
protected:
   char *name;
};

//构造函数的实现
People::People(char *str) 
{
   name = new char[strlen(str)+1];
   strcpy(name, str);
   cout<<"People construct: "<<name<<endl;
}
//析构函数的实现
People::~People() 
{
   cout<<"People destroy: "<<name<<endl;
   delete []name;
}


class Teacher : public People 
{
public:
   Teacher(char *str, char *sch);	//构造函数
   ~Teacher();	//析构函数
protected:
   char *school;
};
Teacher::Teacher(char *str, char *sch) : People(str) //调用基类的构造函数
{  
	school = new char[strlen(sch)+1];
   	strcpy(school,sch);
   	cout<<"Teacher construct: "<<name<<" in "<<school<<endl;
}
Teacher::~Teacher() 
{
	cout<<"Teacher destroy: "<<name<<" in "<<school<<endl;
	delete []school;  
}
int main ()
{
   People tmp("Zhang San");
   People p("Li Si");
   Teacher t("Wang Wu", "FuZhou University");
   return 0;
}

执行结果:
People construct: Zhang San
People construct: Li Si
People construct: Wang Wu
Teacher construct: Wang Wu in FuZhou University
Teacher destroy: Wang Wu in FuZhou University
People destroy: Wang Wu
People destroy: Li Si
People destroy: Zhang San

类的层次(难点)

类的层次:
①一个类可以是某个继承关系中的基类,也可以是另一个继承关系中的派生类
②类A派生出类B,类B又派生出类C,则类B是类C的直接基类,类A是类C的间接基类

定义派生类时,直接基类要明确列出,间接基类不用列出

例子:

class A 
{
protected:
	int aMember;
	// 其他省略......
};
class B: protected A 
{
protected:
	int bMember;
	// 其他省略......
};
class C: private B 
{
protected:
	int cMember;
};

派生类重定义基类函数

在派生类中重定义基类的函数:
①派生类自动继承基类的所有成员
②重定义函数的函数原型和基类中被重定义函数的函数原型必须完全相同
③基类与派生类成员函数同名可能导致基类的成员函数被隐藏
④重定义之后两个函数共存,但调用方法不同
-调用基类函数:基类名 + :: + 函数名
-调用派生类函数:直接调用

如果派生类中于出现相同的成员需要编程时注意其存取的范围。不能运用基类的成员函数来设定派生类的数据成员, 而只能在派生类中重新定义存取数据成员的成员函数。

重定义实例:

#include <iostream>
#include <vector>
#include <string.h>

using namespace std;

class CForm
{
public:
    CForm(string title,vector<string>& menu);
    CForm(string title);
    void Load();
protected:
    void PrintTitle();
    void PrintMenu();
private:
    string m_title;
    vector<string> m_menu;
};
CForm::CForm(string title)
{
    m_title=title;
}
CForm::CForm(string title,vector<string> &menu)
{
    m_title = title;
    m_menu = menu;
}
void CForm::Load()
{
    PrintTitle();
    PrintMenu();
}
void CForm::PrintMenu()
{
    cout<<"menu = ";
    for(vector<string>::iterator it = m_menu.begin();it!=m_menu.end();it++)
    {
        cout<<*it<<" ";
    }
    cout<<endl;
}
void CForm::PrintTitle()
{
    cout<<"m_title = "<<m_title<<endl;
}

//派生类
class CFormMainMenu:public CForm
{
public:
    CFormMainMenu(string title,vector<string>& menu,int color);
    CFormMainMenu(string title,int color);
    void Load();
protected:
private:
    int m_color;
};
void CFormMainMenu::Load()
{
    CForm::Load();
    //cout<<"m_menu = "<<m_menu<<endl; //错误  想获得基类的m_menu值,只能通过调用基类的保护成员函数或公有成员函数
    cout<<"my background color is yellow!"<<endl;
}
CFormMainMenu::CFormMainMenu(string title,int color):CForm(title)
{
    m_color=color;
}
CFormMainMenu::CFormMainMenu(string title,vector<string>& menu,int color):CForm(title,menu)
{
    m_color=color;
}


int main()
{
    vector<string> menu;
    menu.push_back("you");
    menu.push_back("are");
    menu.push_back("my");
    menu.push_back("son");
    CFormMainMenu from("Main Menu",menu,12);
    from.Load();

    return 0;
}

执行结果:
m_title = Main Menu
you are my son
my background color is yellow!

类指针

类名也可以用来声明指针

类的指针可以操作类的对象,也可以操作派生类的对象(派生类对象也是基类对象)

派生类对象和基类对象可以通过指针统一操作和管理

类指针的使用

类指针操作类对象的几种可能:
①基类指针操作基类对象(自然)
②派生类指针操作派生类对象(自然)
③基类指针操作派生类对象——把派生类对象作为基类对象看(安全)
④派生类指针操作基类对象——把基类对象作为派生类对象看(危险)

派生类对象到基类对象的转换

当派生类以Public方式继承基类时,编译器可自动执行的转换(向上转型 upcasting 安全转换)
①派生类对象自动转换为基类对象(特有的成员消失)
②派生类对象指针自动转化为基类对象指针
③派生类对象引用自动转化为基类对象引用

当派生类以private/protected方式继承基类时
①派生类对象指针(引用)转化为基类对象指针(引用)需用强制类型转化
(基类&)派生类对象
(基类*)派生类对象指针

②基类对象指针(引用)可用强制类型转换为派生类对象指针(引用), 而基类对象无法执行这类转换。

向下转型不安全,没有自动转换的机制。

可以使用static_cast运算符进行强制类型转换,但仅限派生类以public方式继承基类。

实例:

#include <iostream>
#include <string.h>
using namespace std;

//基类People
class People
{
public:
   People(char *str, int s); //构造函数中参数s大于0表示男性,否则为女性
   ~People();
   int getSex();
   char* getName();
private:
   char *name;
   int sex;
};
People::People(char *str, int s)
{
    name = new char[strlen(str)+1];
    strcpy(name, str);
    if (s>0)
        sex = 1;	//男性
    else
        sex = 0;	//女性
}
People::~People()
{
   delete []name;
}
int People::getSex()
{
   return sex;
}
char * People::getName()
{
   static char str[128];
   strcpy(str,name);
   return str;
}

//派生类Teacher
class Teacher : public People
{
public:
   Teacher(char *str, int s, char *sch, int y);
   ~Teacher();
   int getWorkYears();
   char *getSchool();
private:
   char *school;
   int years;
};
Teacher::Teacher(char *str,int s,char *sch,int y):People(str, s)
{ //调用基类的构造函数
    school = new char[strlen(sch)+1];
    strcpy(school,sch);
    years = (y>0) ? y : 0;
}
Teacher::~Teacher()
{
    delete []school;
}
int Teacher::getWorkYears()
{
    return years;
}
char * Teacher::getSchool()
{
    static char str[1024];
    strcpy(str,school);
    return str;
}

int main()
{
    People p("Zhang San", 1), *pptr;
    Teacher t("Li Si",0,"Wuhan University",20), *tptr;

    pptr = &p;//用基类指针指向基类对象
    cout<< "People p: " << pptr->getName()<<", "<<(pptr->getSex()?"male":"female")<<endl;

    pptr=&t;//用基类指针指向派生类对象
    cout<<"Teacher t: "<<pptr->getName()<<","<<(pptr->getSex()?"male":"female")<<endl;
        //要调用定义于派生类中的函数必须进行类型的强制转换
    cout<<"\tin "<<((Teacher*)pptr)->getSchool()<<"for "<<((Teacher*)pptr)->getWorkYears()<<" years."<<endl;

    tptr = (Teacher*)&p;	//用派生类指针指向基类对象
    cout<<"People p: "<<tptr->getName()<<", "<<(tptr->getSex()?"male":"female")<<endl;

    //危险,访问了不存在的属性
    cout<<"\tin(危险,访问了不存在的属性) "<<tptr->getSchool()<<" for "<< tptr->getWorkYears()<<" years."<<endl;

    tptr = &t;	//用派生类指针指向派生类对象
    //调用基类中定义的函数
    cout<<"People p: "<<tptr->getName()<<", "<<(tptr->getSex()?"male":"female")<<endl;
    cout<<"\tin "<<tptr->getSchool()<<" for "<<tptr->getWorkYears()<<" years."<<endl;

    return 0;
}

执行结果:
People p: Zhang San, male
Teacher t: Li Si,female
        in Wuhan Universityfor 20 years.
People p: Zhang San, male
        in(危险,访问了不存在的属性) # for 9699072 years.
People p: Li Si, female
        in Wuhan University for 20 years.

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值