类的继承是代码的重用

面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。

派生类的特点

  • 派生类可以在基类的基础上包括新的类的成员。【添加新成员】
  • 新的类可以隐藏基类的成员函数。(注意这里是隐藏,并不是覆盖)【隐藏基类成员】
  • 可以为新的类重新定义成员函数。【改造基类成员】
  • 新的类包含着基类除了构造函数和析构函数,友元函数,重载运算符 的 其他所有成员
    【吸收基类成员】

基类与派生类的关系

  • 派生类是基类的具体化
  • 派生类是基类的定义的延续
  • 派生类是基类的组合(多重派生)

如何定义派生类

一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:

class <派生类类名>: <继承修饰符> <基类名1,基类名2.....>
{<类体>};

继承方式 是 public、protected 或 private 其中的一个;如果不指定继承修饰符 ,则默认为 private

继承方式

派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。

我们可以根据访问权限总结出不同的访问类型,如下所示:

继承方式\访问属性publicprotectedprivate
publicpublicproteced不可访问
protectedprotecedproteced不可访问
privateprivateprivatep不可访问

我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。

派生类的构造函数和析构函数

由于基类的构造函数不能被继承,那么如果我们想要对新加的成员来进行初始化,那我们写新的构造函数,但是继承的基类成员还是要基类的构造函数来进行初始化,同样负责扫尾工作的析构函数也需要我们来写。

构造函数

初始化派生类对象,我们要做如下工作

  1. 对基类数据成员初始化
  2. 对新增数据成员进行初始化
  3. 如果有客人(其他类的对象),隐式调用它的构造函数进行初始化

当然,我们的派生类是多重继承的话,我们只需要对直接基类进行负责即可
语法

<派生类构造函数>(参数列表1):<直接基类构造函数>(参数列表2),<对象成员1>(参数列表3),<对象.......>
{对新的成员初始化}

当然我们用参数列表的形式进行初始化,当然我们也可以用其他的方法

补充

  1. 当然基类(直接基类)的构造函数没有参数或者没有定义构造函数,我们的派生类也可以选择不定义构造函数;但是!!!!那么基类那么只有一个参数我们也需要定义,没有定义新的数据成员我们也要定义,函数体空的就好,这里仅仅就是传递参数
  2. 默认调用关系 ,我们在定义派生类的构造函数没有显示调用基类构造函数的时候,系统会自动帮我们调用无参数的构造函数的(或者是默认的构造函数)

析构函数

派生类的析构函数和构造函数一样,不会继承,所以是相互独立的。调用顺序是先自己再客人最后祖先。

派生类的对基类数据成员的继承

派生类继承基类的所有成员,当然我们很大可能去访问基类的私有成员,这里稍作总结一下。

访问私有成员

  1. 我们可以不用把基类的数据成员声明成private ,我们可以选择protected,这样我们就可以直接访问啦。
  2. 友元,我们可以把派生类声明成基类的友元类,或者我们所需函数声明成基类的友元函数。

作用域规则

当基类的成员名字在派生类中再次声明的时候,派生类的成员就会屏蔽掉基类的成员,也就是派生类成员优先原则。当然我们再次想用基类的成员的时候,我们就需要加上作用域操作符(类名::成员标识符)。

补充

这里为虚函数做一下铺垫,其实我们我们定义的公有派生类对象,其实也可以作为基类的对象,基类指针可以指向派生类对象,派生类指针不可以指向基类对象

  1. 派生类对象可以给基类对象赋值
  2. 派生类对象可以赋值基类对象的引用
  3. 派生类对象的地址可以赋值给指向基类的指针(反之不可以!反之不可以!反之不可以!
    当然这里的指针只能访问可以从基类的继承下来的公有成员

总结:

在公有继承方式下,每一个派生类对象都是基类的对象,他们就是继承了基类的所以成员,没有改变访问权限。

多重继承

多继承即一个子类可以有多个父类,它继承了多个父类的特性。
C++ 类可以从多个类继承成员,语法如下:

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};

二义性

当然这里我们会一个严重的问题
比如说下图
二义性
我们在调用派生类的时候会调用基类的构造函数,但是这里的直接基类有共同的基类,这样编译器就会在想:“我到底要如何调用基类的构造函数呢,传谁的参数?”,所以我们需要虚函数来解决这个问题

虚基类的作用

当一个基类被声明为虚基类后,即使它成为了多继承链路上的公共基类,最后的派生类中也只有它的一个备份,只让基类构造函数调用一次。例如:

class Base { };
class Derive1:virtual public Base{ };
class Derive2:virtual public Base{ };
class Derive12:public Derive1,Derive2{ };

则在类Derive12的对象中,仅有类Base的一个对象数据
我们在遇到这些情况就需要 把 路径重复的基类声明成虚基类,声明方式如上,在声明的时候 加上 virtual

虚基类的特点:

  1. 虚基类构造函数的参数必须由最新派生出来的类负责初始化(即使不是直接继承);
  2. 虚基类的构造函数先于非虚基类的构造函数执行。

注意⚠️!

虚基类和虚函数是完全无相关的两个概念
虚基类就是一种继承方式。

实例

薪水

#include<iostream>
#include<string>

class employee
{
protected:
    char* name;
    char* empID;
    double salary;
public:
employee(const char* n="noname",const char* id="oooo")
{
    name=new char[strlen(n)+1];//strlen忽略了‘/0’ 所以要补上去一位
    empID=new char[strlen(id)+1];
    strcpy(name,n);
    strcpy(empID,id);
    salary=0;
}
employee(const employee &emp)
{
    name=new char(strlen(emp.name)+1);
    empID=new char(strlen(emp.empID)+1);
    strcpy(name,emp.name);
    strcpy(empID,emp.empID);
    salary=emp.salary;
}
void setinfo(const char* n="noname",const char* id="oooo")
{
    delete name;
    delete empID;
    name=new char(strlen(n)+1);//strlen忽略了‘/0’ 所以要补上去一位
    empID=new char(strlen(id)+1);
    strcpy(name,n);
    strcpy(empID,id);
}

void show()
{
    std::cout<<"员工姓名:"<<name<<" "<<"ID:"<<empID<<" "<<"一周薪水:"<<salary<<std::endl;
}
double getsalary()
{
    return salary;
}
};
class hourlyworker:public employee
{
protected:
    int hours;
    double perhourpay;
public:
    hourlyworker(const char* n="noname",const char* id="0000",int h=0,double ppay=15)//设置名字 id 工作小时 每小时多少钱
    :employee(n,id),hours(h),perhourpay(ppay)
    {}
    void setperhourpay(double p)
    {
        perhourpay=p;
    }
    int gethours()
    {
        return hours;
    }
    void sethours(int h)
    {
        hours=h;
    } 
    double getperhourpay()
    {
        return perhourpay;
    }
    void computepay()
    {
        if(hours<50)
        salary=perhourpay*hours;
        else
        {
            salary=perhourpay*50+(hours-50)*perhourpay;
        }  
    }
};
class pieceworker:public employee
{
protected:
    int pieces;
    double paypiecepay;
public:
    pieceworker(const char* n="noname",const char* id="0000",int p=0,double pp=26):employee(n,id),pieces(p), paypiecepay(pp)   
    {}
    int getpieces()
    {
        return pieces;
    }
    void setpiece(int p)
    {pieces=p;}
    double getpaypiecepay()
    {return paypiecepay;}
    void setpaypiecepay(double pp)
    {paypiecepay=pp;}
    void computesalary()
    {salary=pieces*paypiecepay;}
};
class manager:public employee
{
public:
    void setsalary(double s)
    {salary=s;}
};
class commissionworker:public employee
{
protected:
    double basesalary;
    double total;
    double percent;
public:
    commissionworker(const char* n="noname",const char* id="0000",double b=3000,double t=0,double p=0.01)
    :employee(n,id),basesalary(b),total(t),percent(p)
    {}
    double getbasalary()
    {return basesalary;}
    void setbasalary(double b)
    {basesalary=b;}
    double gettotal()
    {return total;}
    void settotal(double t)
    {total=t;}
    double getpercent()
    {return percent;}
    void setpercent(double p)
    {percent=p;}
    void computepay()
    {salary=basesalary+total*percent;}
};
int main()
{
    hourlyworker h("111","0001",16);//小时工
    h.computepay();
    h.show();

    pieceworker p("222","0002",2);//计件工
    p.computesalary();
    p.show();

    commissionworker c("fff","0003",5000,8000);//佣金工
    c.computepay();
    c.show();

    manager m;                              //经理
    m.setinfo("zzz","6666");
    m.setsalary(99999999999999);
    m.show();

    return 0;
}

努力成长的程序员