类的继承是代码的重用
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
派生类的特点
- 派生类可以在基类的基础上包括新的类的成员。【添加新成员】
- 新的类可以隐藏基类的成员函数。(注意这里是隐藏,并不是覆盖)【隐藏基类成员】
- 可以为新的类重新定义成员函数。【改造基类成员】
- 新的类包含着基类除了构造函数和析构函数,友元函数,重载运算符 的 其他所有成员
【吸收基类成员】
基类与派生类的关系
- 派生类是基类的具体化
- 派生类是基类的定义的延续
- 派生类是基类的组合(多重派生)
如何定义派生类
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
class <派生类类名>: <继承修饰符> <基类名1,基类名2.....>
{<类体>};
继承方式 是 public、protected 或 private 其中的一个;如果不指定继承修饰符 ,则默认为 private。
继承方式
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
我们可以根据访问权限总结出不同的访问类型,如下所示:
继承方式\访问属性 | public | protected | private |
---|---|---|---|
public | public | proteced | 不可访问 |
protected | proteced | proteced | 不可访问 |
private | private | private | p不可访问 |
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
派生类的构造函数和析构函数
由于基类的构造函数不能被继承,那么如果我们想要对新加的成员来进行初始化,那我们写新的构造函数,但是继承的基类成员还是要基类的构造函数来进行初始化,同样负责扫尾工作的析构函数也需要我们来写。
构造函数
初始化派生类对象,我们要做如下工作
- 对基类数据成员初始化
- 对新增数据成员进行初始化
- 如果有客人(其他类的对象),隐式调用它的构造函数进行初始化
当然,我们的派生类是多重继承的话,我们只需要对直接基类进行负责即可
语法
<派生类构造函数>(参数列表1):<直接基类构造函数>(参数列表2),<对象成员1>(参数列表3),<对象.......>
{对新的成员初始化}
当然我们用参数列表的形式进行初始化,当然我们也可以用其他的方法
补充
- 当然基类(直接基类)的构造函数没有参数或者没有定义构造函数,我们的派生类也可以选择不定义构造函数;但是!!!!那么基类那么只有一个参数我们也需要定义,没有定义新的数据成员我们也要定义,函数体空的就好,这里仅仅就是传递参数
- 默认调用关系 ,我们在定义派生类的构造函数没有显示调用基类构造函数的时候,系统会自动帮我们调用无参数的构造函数的(或者是默认的构造函数)
析构函数
派生类的析构函数和构造函数一样,不会继承,所以是相互独立的。调用顺序是先自己再客人最后祖先。
派生类的对基类数据成员的继承
派生类继承基类的所有成员,当然我们很大可能去访问基类的私有成员,这里稍作总结一下。
访问私有成员
- 我们可以不用把基类的数据成员声明成private ,我们可以选择protected,这样我们就可以直接访问啦。
- 友元,我们可以把派生类声明成基类的友元类,或者我们所需函数声明成基类的友元函数。
作用域规则
当基类的成员名字在派生类中再次声明的时候,派生类的成员就会屏蔽掉基类的成员,也就是派生类成员优先原则。当然我们再次想用基类的成员的时候,我们就需要加上作用域操作符(类名::成员标识符)。
补充
这里为虚函数做一下铺垫,其实我们我们定义的公有派生类对象,其实也可以作为基类的对象,基类指针可以指向派生类对象,派生类指针不可以指向基类对象
- 派生类对象可以给基类对象赋值
- 派生类对象可以赋值基类对象的引用
- 派生类对象的地址可以赋值给指向基类的指针(反之不可以!反之不可以!反之不可以!)
当然这里的指针只能访问可以从基类的继承下来的公有成员
总结:
在公有继承方式下,每一个派生类对象都是基类的对象,他们就是继承了基类的所以成员,没有改变访问权限。
多重继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
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
虚基类的特点:
- 虚基类构造函数的参数必须由最新派生出来的类负责初始化(即使不是直接继承);
- 虚基类的构造函数先于非虚基类的构造函数执行。
注意⚠️!
虚基类和虚函数是完全无相关的两个概念
虚基类就是一种继承方式。
实例
薪水
#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;
}