C++ 继承与派生
定义
类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。派生与继承,是同一种意义两种称谓。
1. 继承
1.1 语法
class 派生类名:[继承方式] 基类名
{
派生类成员声明;
};
一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类,称为单继承。下面从单继承讲起。
1.2 继承方式
继承方式规定了如何访问基类继承的成员。继承方式有 public, private, protected。继承方式不影响派生类的访问权限,影响了从基类继承来的成员的访问权限,包括派生类内的访问权限和派生类对象。
公有继承:基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类的私有成员。
私有继承:基类的公有成员和保护成员在派生类中成了私有成员,其私有成员仍为基类的私有成员。
保护继承:基类的公有成员和保护成员在派生类中成了保护成员,其私有成员仍为基类的私有成员。
pretected 对于外界访问属性来说,等同于私有,但可以派生类中可见。
1.3 派生类的组成
派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。
1.4 注意事项
1,全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基类是需设计的。
2,派生类有了自己的个性,使派生类有了意义。
2. 派生类的构造
派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,然后派生类中新增的成员在派生类的构造函数中初始化。
2.1 派生类构造函数的语法
派生类名::派生类名(参数总表)
:基类名(参数表),内嵌子对象(参数表)
{
派生类新增成员的初始化语句; //也可出现地参数列表中
}
- 构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化。
- 如果基类中没有默认构造函数(无参),那么在派生类的构造函数中必须显示调用基类构造函数,以初始化基类成员。
- 派生类构造函数执行的次序:
基类-->成员-->子类
a 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右);
b 调用内嵌成员对象的构造函数,调用顺序按照它们在类中声明的顺序;
c 派生类的构造函数体中的内容。
2.2 代码示例
祖父类
student.h
class Student
{
public:
Student(string sn,int n,char s);
~Student();
void dis();
private:
string name;
int num;
char sex;
};
student.cpp
Student::Student(string sn, int n, char s)
:name(sn),num(n),sex(s)
{
}
Student::~Student()
{
}
void Student:: dis()
{
cout<<name<<endl;
cout<<num<<endl;
cout<<sex<<endl;
}
父类
graduate.h
class Graduate:public Student
{
public:
Graduate(string sn,int in,char cs,float fs);
~Graduate();
void dump()
{
dis();
cout<<salary<<endl;
}
private:
float salary;
};
graduate.cpp
Graduate::Graduate(string sn, int in, char cs, float fs)
:Student(sn,in,cs),salary(fs)
{
}
Graduate::~Graduate()
{
}
类成员
birthday.h
class Birthday
{
public:
Birthday(int y,int m,int d);
~Birthday();
void print();
private:
int year;
int month;
int day;
};
bithday.cpp
Birthday::Birthday(int y, int m, int d)
:year(y),month(m),day(d)
{
}
Birthday::~Birthday()
{
}
void Birthday::print()
{
cout<<year<<month<<day<<endl;
}
子类
doctor.h
class Doctor:public Graduate
{
public:
Doctor(string sn,int in,char cs,float fs,string st,int iy,int im,int id);
~Doctor();
void disdump();
private:
string title; //调用的默认构造器,初始化为”” Birthday birth;
Birthday birth; //类中声明的类对象
};
doctor.cpp
Doctor::Doctor(string sn, int in, char cs, float fs, string st, int iy,int im, int id)
:Graduate(sn,in,cs,fs),birth(iy,im,id),title(st)
{
}
Doctor::~Doctor()
{
}
void Doctor::disdump()
{
dump();
cout<<title<<endl;
birth.print();
}
main.cpp
int main()
{
Student s("1",2001,'m');
s.dis();
cout<<"----------------"<<endl;
Graduate g("2",2001,'x',2000);
g.dump();
cout<<"----------------"<<endl;
Doctor d("3",2001,'y',3000,"doctor",2001,8,16);
d.disdump();
return 0;
}
2.3 小结
子类构造器中,要么显示的调用父类的构造器(传参),要么隐式的调用。发生隐式调用时,父类要有无参构造器或是可以包含无参构造器的默认参数函数。子类对象亦然。
3.派生类的拷贝构造
3.1 格式
派生类::派生类(const 派生类& another)
:基类(another),派生类新成员(another.新成员)
{
}
3.2 代码示例
父类
student.h
class Student
{
public:
Student(string sn,int n,char s);
Student(const Student & another);
~Student();
void dis();
private:
string name;
int num;
char sex;
};
student.cpp
Student::Student(string sn, int n, char s)
:name(sn),num(n),sex(s)
{
}
Student::~Student()
{
}
void Student:: dis()
{
cout<<name<<endl;
cout<<num<<endl;
cout<<sex<<endl;
}
Student::Student(const Student & another)
{
name = another.name;
num = another.num;
sex = another.sex;
}
子类
graduate.h
class Graduate:public Student
{
public:
Graduate(string sn,int in,char cs,float fs);
~Graduate();
Graduate(const Graduate & another);
void dump()
{
dis();
cout<<salary<<endl;
}
private:
float salary;
};
graduate.cpp
Graduate::Graduate(string sn, int in, char cs, float fs)
:Student(sn,in,cs),salary(fs)
{
}
Graduate::~Graduate()
{
}
Graduate::Graduate(const Graduate & another)
:Student(another),salary(another.salary)
{
}
main.cpp
int main()
{
Graduate g("123",2001,'x',2000);
g.dump();
Graduate gg = g;
gg.dump();
return 0;
}
3.3 小结
派生类中的默认拷贝构造器会调用父类中默认或自实现拷贝构造器,若派生类中自实现拷贝构造器,则必须显示的调用父类的拷贝构造器。
4. 派生类的赋值运算符重载
赋值运算符函数不是构造器,所以可以继承,语法上就没有构造器的严格一些。
4.1 格式
子类& 子类::operator=(const 子类& another)
{
if(this == &another)
return *this; //防止自赋值
父类::operator =(another); // 调用父类的赋值运算符重载
this->salary = another.salary;//子类成员初始化
return * this;
}
4.2 代码示例
基类
student.h
Student & operator=(const Student & another);
student.cpp
Student & Student::operator=(const Student & another)
{
this->name = another.name;
this->num = another.num;
this->sex = another.sex;
return * this;
}
派生类
graduate.h
Graduate & operator=(const Graduate & another);
graduate.cpp
Graduate & Graduate::operator=(const Graduate & another)
{
if(this == &another)
return *this;
Student::operator =(another);
this->salary = another.salary;
return * this;
}
4.3 小结
派生类的默认赋值运算符重载函数,会调用父类的默认或自实现函数。派生类若自实现,则不会发生调用行为,也不报错(区别拷贝),赋值错误,若要正确,需要显示的调用父类的自实现函数。
5. 派生类友元函数
由于友元函数并非类成员,因此不能被继承,在某种需求下,可能希望派生类的友元函数能够使用基类中的友元函数。为此可以通过强制类型转换,将派生类的指针或是引用强转为其基类的引用或是指针,然后使用转换后的引用或是指针来调用基类中的友元函数。
#include <iostream>
using namespace std;
class Student
{
friend ostream &operator<<(ostream & out, Student & stu);
private:
int a;
int b;
};
ostream &operator<<(ostream & out, Student & stu)
{
out<<stu.a<<"--"<<stu.b<<endl;
}
class Graduate:public Student
{
friend ostream &operator<<(ostream & out, Graduate & gra);
private:
int c;
int d;
};
ostream &operator<<(ostream & out, Graduate & gra)
{
out<<(Student&)gra<<endl;
out<<gra.c<<"**"<<gra.d<<endl;
}
int main()
{
// Student a;
// cout<<a<<endl;
Graduate g;
cout<<g<<endl;
return 0;
}
6. 派生类析构函数的语法
派生类的析构函数的功能是在该对象消亡之前进行一些必要的清理工作,析构函数没有类型,也没有参数。析构函数的执行顺序与构造函数相反。
析构顺序
子类->成员->基类
7. 派生类成员的标识和访问
7.1 作用域分辨符
格式
基类名::成员名; 基类名::成员名(参数表);
如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在这种情况下**,派生类成员将 shadow(隐藏)所有基类的同名成员**。这就需要这样的调用方式才能调用基类的同名成员。
#include <iostream>
using namespace std;
class Base
{
public:
void func(int)
{
cout<<"haha"<<endl;
}
};
class Drive:public Base
{
public:
void func()
{
Base::func(3); //被 shadow 的成员,可以这样访问
cout<<"hehe"<<endl;
}
};
int main()
{
Drive d;
d.func(); // 访问派生类成员
d.Base::func(3); //访问基类成员
return 0;
}
7.2 小结
重载:同一作用域 ,函数同名不同参(个数,类型,顺序);
隐藏:父子类中,标识符(函数,变量)相同,无关乎返回值和参数(函数),或声明类型(变量)。
8. 多继承
从继承类别上分,继承可分为单继承和多继承,前面讲的都是单继承。
8.1 意义
俗话讲的,鱼与熊掌不可兼得,而在计算机就可以实现,生成一种新的对象,叫熊掌鱼,多继承自鱼和熊掌即可。还比如生活中,“兼”。
8.2 语法
派生类名::派生类名(参数总表)
:基类名 1(参数表 1),基类名(参数名 2)....基类名 n(参数名 n),
内嵌子对象 1(参数表 1),内嵌子对象 2(参数表 2)....内嵌子对象 n(参数表 n)
{
派生类新增成员的初始化语句;
}
8.3 代码示例
床类
bed.h
#ifndef BED_H
#define BED_H
class Bed
{
public:
Bed();
~Bed();
void sleep();
};
#endif // BED_
bed.cpp
#include "bed.h"
#include "iostream"
using namespace std;
Bed::Bed()
{
}
Bed::~Bed()
{
}
void Bed::sleep()
{
cout<<"take a good sleep"<<endl;
}
沙发类
sofa.h
#ifndef SOFA_H
#define SOFA_H
class Sofa
{
public:
Sofa();
~Sofa();
void sit();
};
#endif // SOFA_H
sofa.cpp
#include "sofa.h"
#include "iostream"
using namespace std;
Sofa::Sofa()
{
}
Sofa::~Sofa()
{
}
void Sofa::sit()
{
cout<<"take a rest"<<endl;
}
沙发床类
sofabed.h
#ifndef SOFABED_H
#define SOFABED_H
#include "sofa.h"
#include "bed.h"
class SofaBed:public Sofa,public Bed
{
public:
SofaBed();
~SofaBed();
};
#endif // SOFABED_H
sofabed.cpp
#include "sofabed.h"
SofaBed::SofaBed()
{
}
SofaBed::~SofaBed()
{
}
main.cpp
#include <iostream>
#include "sofa.h"
#include "bed.h"
#include "sofabed.h"
using namespace std;
int main()
{
Sofa s;
s.sit();
Bed b;
b.sleep();
SofaBed sb;
sb.sit();
sb.sleep();
return 0;
}
8.4 二义性问题
多个父类中重名的成员,继承到子类中后,为了避免冲突,携带了各父类的作用域信息, 子类中要访问继承下来的重名成员,则会产生二义性,为了避免冲突,访问时需要还有父类的作用域信息。
#include <iostream>
using namespace std;
class X
{
public:
X(int d):_data(d){}
void setData(int i)
{
_data = i;
}
int _data;
};
class Y
{
public:
Y(int d):_data(d){}
int getData()
{
return _data;
}
int _data;
};
class Z:public X,public Y
{
public:
Z():X(2),Y(3){}
void dis()
{
cout<<X::_data<<endl;
cout<<Y::_data<<endl;
}
};
int main()
{
Z z;
z.dis();
z.setData(2000);
cout<<z.getData()<<endl;
return 0;
}
9. 虚继承
9.1 虚继承的意义
在多继承中,保存共同基类的多份同名成员,虽然有时是必要的,可以在不同的数据成员中分别存放不同的数据,但在大多数情况下,是我们不希望出现的。因为保留多份数据成员的拷贝,不仅占有较多的存储空间,还增加了访问的困难。
为此,c++提供了,虚基类和虚继承机制,实现了在多继承中只保留一份共同成员。 虚基类,需要设计和抽象,虚继承,是一种继承的扩展。
9.2 语法
a.M 类称为虚基类(virtual base class ),是抽象和设计的结果。
b.虚继承语法
class 派生类名:virtual 继承方式 基类
c.虚基类及间接类的实始化
class A{
A(int i)
{}
};
class B:virtual public A
{
B(int n):A(n){}
};
class C:virtual public A
{
C(int n):A(n){}
};
class D:public B,public C
{
D(int n)
:A(n),B(n),C(n)
{}
};
9.3 代码示例
#include <iostream>
using namespace std;
class M
{
public:
M(int d):_data(d){}
int _data;
};
class X :virtual public M
{
public:
X(int d):M(d){}
void setD(float d)
{
_data = d;
}
};
class Y:virtual public M
{
public:
Y(int d):M(d){}
int getD()
{
return _data;
}
};
class Z:public X,public Y
{
public:
Z(int _x,int _y):X(_x),Y(_y),M(100){}
void dis()
{
cout<<X::_data<<endl;
cout<<Y::_data<<endl;
cout<<_data<<endl;
}
};
int main()
{
Z z(200,100);
z.dis();
z.setD(2000);
cout<<z.getD()<<endl;
return 0;
}