定义
继承,是从已经存在的类中获得属性,再此基础之上添加自己属性的过程。原有的类成为基类或父类,产生的新类则为派生类或子类。
class deriveClass:deriveMode baseClass
{
statement;
}
当然也可以从多个基类中进行派生,此时叫多重继承。从一个基类中进行派生,称为单继承。
继承方式
- 有三种继承方式:public,protected,private
- 继承方式规定了访问基类成员的方式
- 影响派生类从基类继承成员的访问权限,但不影响派生类成员的访问权限
- 需要注意的是,派生类会全部继承基类的成员,因此在定义时需要特别注意
成员\继承方式 | public | protected | private |
public | public | protected | private |
protected | protected | protected | inaccessable |
private | inaccessable | inaccessable | inaccessable |
- 虽然派生类能够继承基类中的全部成员,但是对于基类中的成员却存在四种访问方式:public,protected,private,inaccessable
- public 继承:基类中的公有成员和保护成员的访问属性在派生类中不变,私有成员不可访问
- protected 继承:基类中的公有成员和保护成员的访问属性在派生类中都为 protected,私有成员不可访问
- private 继承:基类中的公有成员和保护成员的访问属性在派生类中为 private,私有成员不可访问
派生类构造
派生类构造函数分为两部分,一部分是从基类继承来的成员的初始化,此部分初始化由基类的构造函数完成,另一部分则是派生类自身的成员,此部分初始化由派生类的构造函数完成。
格式
deriveClass::dericeClass(argument):baseClass(argument),var(argument)
{
statement;
}
- 上述派生类构造函数格式中的 var 表示派生类新增的数据成员,函数体当然也就是派生类新增的数据成员
- 构造函数的初始化顺序同样不是根据参数初始化列表的顺序,而是根据数据成员的声明顺序
- 基类中如果不存在无参形式的构造(无参或默认),那么在派生类的构造函数中需要显示调用基类的构造函数,对基类成员进行初始化
- 如果含有多层类继承关系,各个构造函数之间的执行顺序为:
- 调用基类构造函数,调用顺序为被继承时的声明顺序(多重继承)
- 调用内嵌成员对象的构造函数,调用顺序为在对应类中声明的顺序
- 派生类的构造函数
实例
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
using std::cout;
using std::endl;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_)
{
cout<<"PERSON(char * name_ = \"***\",char sex_ = '*')"<<endl;
}
void displayp();
~PERSON()
{
cout<<"~PERSON()"<<endl;
}
private:
char * name;
char sex;
};
class JOB
{
public:
JOB(char * name_ = "***"):name(name_)
{
cout<<"JOB(char * name_ = \"***\"):name(name_)"<<endl;
}
void displayj();
~JOB()
{
cout<<"~JOB()"<<endl;
}
private:
char * name;
};
class CLUB
{
public:
CLUB(char * name_ = "***"):name(name_)
{
cout<<"CLUB(char * name_ = \"***\"):name(name_)"<<endl;
}
void displayc();
~CLUB()
{
cout<<"~CLUB()"<<endl;
}
private:
char * name;
};
class STUDENT:public PERSON
{
public:
STUDENT(char * name_ = "***",char sex_ = '*',char *num_ = "***",char * club_ = "***")
:PERSON(name_,sex_),num(num_),club(club_)
{
cout<<"STUDENT(char * name_ = \"***\",char sex_ = '*',char *num_ = \"***\",char * club_ = \"***\")"<<endl;
}
STUDENT(PERSON person_,char *num_ = "***",char * club_ = "***"):PERSON(person_),num(num_),club(club_){}
void displays();
~STUDENT()
{
cout<<"~STUDENT()"<<endl;
}
private:
char *num;
CLUB club;
};
class POSTGRADUATE:public STUDENT,public JOB
{
public:
POSTGRADUATE(char * name_ = "***",char sex_ = '*',char *num_ = "***",char * club_ = "***",char *job_ = "***",int salary_ = 0)
:STUDENT(name_,sex_,num_,club_),JOB(job_),salary(salary_)
{
cout<<"POSTGRADUATE(char * name_ = \"***\",char sex_ = '*',char *num_ = \"***\",char * club_ = \"***\",char *job_ = \"***\",int salary_ = 0)"<<endl;
}
POSTGRADUATE(STUDENT student,JOB job,int salary_ = 0):STUDENT(student),JOB(job),salary(salary_){}
void displayps();
~POSTGRADUATE()
{
cout<<"~POSTGRADUATE()"<<endl;
}
private:
int salary;
};
#endif // MYCLASS_H
// myclass.cpp
#include <iostream>
#include "myclass.h"
using std::cout;
using std::endl;
void PERSON::displayp()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
void JOB::displayj()
{
cout<<"The job name is "<<name<<"."<<endl;
}
void CLUB::displayc()
{
cout<<"The club name is "<<name<<"."<<endl;
}
void STUDENT::displays()
{
displayp();
club.displayc();
cout<<"The student num is "<<num<<"."<<endl;
}
void POSTGRADUATE::displayps()
{
displays();
displayj();
cout<<"The postgarduate saraly is "<<salary<<"."<<endl;
}
// main.cpp
#include <iostream>
#include "myclass.h"
using namespace std;
int main()
{
POSTGRADUATE st("zhangsan",'x',"math","100","paper",100);
st.displayps();
return 0;
}
结果为:
PERSON(char * name_ = "***",char sex_ = '*')
CLUB(char * name_ = "***"):name(name_)
STUDENT(char * name_ = "***",char sex_ = '*',char *num_ = "***",char * club_ = "***")
JOB(char * name_ = "***"):name(name_)
POSTGRADUATE(char * name_ = "***",char sex_ = '*',char *num_ = "***",char * club_ = "***",char *job_ = "***",int salary_ = 0)
The person name is zhangsan.
The person sex is x.
The club name is 100.
The student num is math.
The job name is paper.
The postgarduate saraly is 100.
~POSTGRADUATE()
~JOB()
~STUDENT()
~CLUB()
~PERSON()
在上边的例子中,我们构造了两个基类 PERSON 和 JOB,其中 PERSON 派生出 STUDENT,STUDENT 和 JOB 派生出 POSTGRADUATE,在 STUDENT 中还存在一个 CLUB 的成员,从上边的结果可以看出:
- 虽然我们只定义了一个 POSTGRADUATE,但却调用了包括该派生类和基类的构造函数
- 派生类的构造顺序为:基类->类成员->派生类
- 上边结果中第一行为基类 STUDENT 的基类 PERSON 的构造函数
- 上边结果中第二行为基类 STUDENT 的成员 CLUB 对象的构造函数
- 上边结果中第三行为基类 STUDENT 的构造函数
- 上边结果中第四行为基类 JOB 的构造函数
- 上边结果中第五行为派生类 POSTRADUATE 的构造函数
- 调用函数 st.displayps() 时按次序调用各自的 display()
- 析构函数的调用顺序与构造函数的调用顺序相反
- 需要注意的是,在子类的构造函数中,要么显示调用父类的构造器,要么就会进行隐式调用。而为了隐式调用时程序能够正常运行,在基类中需要定义构造函数的无参类型或者默认类型。上边的程序中就是采用了默认参数的形式
派生类的拷贝构造
定义
deriveclass::deriveclass(const deriveclass &obj):baseclass(obj),deriveclass_var(obj.var)
{
statement;
}
实例
#include <iostream>
using namespace std;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_)
{
cout<<"PERSON(char * name_ = \"***\",char sex_ = '*')"<<endl;
}
PERSON(const PERSON &obj):name(obj.name),sex(obj.sex)
{
cout<<"PERSON(const PERSON &obj)"<<endl;
}
void displayp()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
~PERSON()
{
cout<<"~PERSON()"<<endl;
}
private:
char * name;
char sex;
};
class STUDENT:public PERSON
{
public:
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
:PERSON(name_,sex_),num(num_)
{
cout<<"STUDENT(char *name_ = \"***\",char sex_ = \"***\",char *num_ = \"***\")"<<endl;
}
STUDENT(PERSON person_,char *num_= "***"):PERSON(person_),num(num_){}
STUDENT(const STUDENT &obj)
:PERSON(obj),num(obj.num)
{
cout<<"STUDENT(const STUDENT &obj)"<<endl;
}
void displays()
{
displayp();
cout<<"The student num is "<<num<<"."<<endl;
}
~STUDENT()
{
cout<<"~STUDENT()"<<endl;
}
private:
char *num;
};
int main()
{
STUDENT st("zhangsan",'x',"100");
st.displays();
STUDENT sd(st);
sd.displays();
return 0;
}
结果为:
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = "***",char *num_ = "***")
The person name is zhangsan.
The person sex is x.
The student num is 100.
PERSON(const PERSON &obj)
STUDENT(const STUDENT &obj)
The person name is zhangsan.
The person sex is x.
The student num is 100.
~STUDENT()
~PERSON()
~STUDENT()
~PERSON()
上边的结果为:
- 定义的 STUDENT 对象 st 首先调用基类的构造函数,然后调用派生类的构造函数,最后打印
- 定义的 STUDENT 对象 sd 首先调用基类的拷贝构造函数,然后调用派生类的拷贝构造函数,最后打印
- 析构函数仍然与构造函数的调用顺序相反
- 派生类中的默认拷贝构造函数会调用基类中的拷贝构造函数(默认或自实现)
- 派生类中如果自实现了拷贝构造函数,则必须显式调用基类的拷贝构造函数(此时基类中的拷贝构造函数不一定是自实现的)
派生类的赋值运算符重载
赋值运算符重载本身不算是构造器,因此可以被继承。
定义
deriveclass &deriveclass::operator=(const deriveclass &obj)
{
if(this == &obj)
return *this;
baseclass::operator=(obj);
this->var = obj->var;
return *this;
}
实例
#include <iostream>
using namespace std;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_)
{
cout<<"PERSON(char * name_ = \"***\",char sex_ = '*')"<<endl;
}
PERSON(const PERSON &obj):name(obj.name),sex(obj.sex)
{
cout<<"PERSON(const PERSON &obj)"<<endl;
}
PERSON &operator=(const PERSON &obj)
{
cout<<"PERSON &operator=(const PERSON &obj)"<<endl;
this->name = obj.name;
this->sex = obj.sex;
return *this;
}
void displayp()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
~PERSON()
{
cout<<"~PERSON()"<<endl;
}
private:
char * name;
char sex;
};
class STUDENT:public PERSON
{
public:
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
:PERSON(name_,sex_),num(num_)
{
cout<<"STUDENT(char *name_ = \"***\",char sex_ = '*',char *num_ = \"***\")"<<endl;
}
STUDENT(PERSON person_,char *num_ = "***"):PERSON(person_),num(num_){}
STUDENT(const STUDENT &obj)
:PERSON(obj),num(obj.num)
{
cout<<"STUDENT(const STUDENT &obj)"<<endl;
}
STUDENT &operator=(const STUDENT &obj)
{
cout<<"STUDENT &operator=(const STUDENT &obj)"<<endl;
if(this == &obj)
return *this;
PERSON::operator=(obj);
this->num = obj.num;
return *this;
}
void displays()
{
displayp();
cout<<"The student num is "<<num<<"."<<endl;
}
~STUDENT()
{
cout<<"~STUDENT()"<<endl;
}
private:
char *num;
};
int main()
{
STUDENT st("zhangsan",'x',"100");
st.displays();
STUDENT sd;
sd.displays();
sd= st;
sd.displays();
return 0;
}
结果为:
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is zhangsan.
The person sex is x.
The student num is 100.
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is ***.
The person sex is *.
The student num is ***.
STUDENT &operator=(const STUDENT &obj)
PERSON &operator=(const PERSON &obj)
The person name is zhangsan.
The person sex is x.
The student num is 100.
~STUDENT()
~PERSON()
~STUDENT()
~PERSON()
从上边的结果可以看出,利用 = 重载可以实现派生类之间的相互赋值。
- 上边 main 函数中语句如果变成:
int main()
{
STUDENT st("zhangsan",'x',"100");
st.displays();
STUDENT sd= st;
sd.displays();
return 0;
}
结果为:
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is zhangsan.
The person sex is x.
The student num is 100.
PERSON(const PERSON &obj)
STUDENT(const STUDENT &obj)
The person name is zhangsan.
The person sex is x.
The student num is 100.
~STUDENT()
~PERSON()
~STUDENT()
~PERSON()
可以发现在前者的实现中,并没有调用赋值运算符重载函数。可以理解为只有当对象存在时才能够调用运算符重载函数。
- 派生类中的默认赋值运算符重载函数会调用基类中的赋值运算符重载函数(默认或自实现)
- 派生类中如果自实现了赋值运算符重载函数,则必须显式调用基类的赋值运算符重载函数(此时基类中的赋值运算符重载函数不一定是自实现的)
派生类友元函数
之前我们说过友元函数可以是全局函数或者类函数,但两者均不是类的成员函数,因此不能够被继承。但通过强制类型转换,将派生类的指针或者引用强制转换为基类的指针或者引用,然后就可以使用转换后的指针或者引用调用基类中的友元函数。
#include <iostream>
using namespace std;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_)
{
cout<<"PERSON(char * name_ = \"***\",char sex_ = '*')"<<endl;
}
PERSON(const PERSON &obj):name(obj.name),sex(obj.sex)
{
cout<<"PERSON(const PERSON &obj)"<<endl;
}
PERSON &operator=(const PERSON &obj)
{
cout<<"PERSON &operator=(const PERSON &obj)"<<endl;
this->name = obj.name;
this->sex = obj.sex;
return *this;
}
void displayp()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
~PERSON()
{
cout<<"~PERSON()"<<endl;
}
friend ostream &operator<<(ostream &out,const PERSON &obj);
private:
char * name;
char sex;
};
ostream &operator<<(ostream &out,const PERSON &obj)
{
out<<"The person name is "<<obj.name<<"."<<endl;
out<<"The person sex is "<<obj.sex<<"."<<endl;
return out;
}
class STUDENT:public PERSON
{
public:
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
:PERSON(name_,sex_),num(num_)
{
cout<<"STUDENT(char *name_ = \"***\",char sex_ = '*',char *num_ = \"***\")"<<endl;
}
STUDENT(PERSON person_,char *num_ = "***"):PERSON(person_),num(num_){}
STUDENT(const STUDENT &obj)
:PERSON(obj),num(obj.num)
{
cout<<"STUDENT(const STUDENT &obj)"<<endl;
}
STUDENT &operator=(const STUDENT &obj)
{
cout<<"STUDENT &operator=(const STUDENT &obj)"<<endl;
if(this == &obj)
return *this;
PERSON::operator=(obj);
this->num = obj.num;
return *this;
}
void displays()
{
displayp();
cout<<"The student num is "<<num<<"."<<endl;
}
~STUDENT()
{
cout<<"~STUDENT()"<<endl;
}
private:
char *num;
};
int main()
{
STUDENT st("zhangsan",'x',"100");
st.displays();
STUDENT sd("zhangsan",'x',"100");
cout<<sd;
return 0;
}
结果为:
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is zhangsan.
The person sex is x.
The student num is 100.
PERSON(char * name_ = "***",char sex_ = '*')
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***")
The person name is zhangsan.
The person sex is x.
~STUDENT()
~PERSON()
~STUDENT()
~PERSON()
上边的程序没有进行显式转换,但也调用了流输出符。也就是其中经过了某种程度的强制转换,但是在实际编程的时候,最好还是显式转换,为了安全也为了可读性。
派生类析构函数
派生类的析构函数进行的工作也是和之前提到过的析构函数作用一样:
- 进行内存的释放
- 没有返回值
- 没有参数
- 没有类型
- 析构函数的执行顺序与构造函数的执行顺序相反:派生类->成员类对象->基类
派生类成员的标识和访问
作用域运算符
作用域除了可以用于命名空间中变量的定位之外,还能够用来标识派生类中的成员。也可以说类的继承关系本来就算做是命名空间的一部分。
如果派生类和基类中存在同名的成员,派生类成员会覆盖掉同名的成员,因此就需要作用域运算符来解决这个问题。
#include <iostream>
using namespace std;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*'):name(name_),sex(sex_){}
PERSON(const PERSON &obj):name(obj.name),sex(obj.sex){}
void display()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
private:
char * name;
char sex;
};
class STUDENT:public PERSON
{
public:
STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "***"):PERSON(name_,sex_),num(num_){}
STUDENT(PERSON person_,char *num_ = "***"):PERSON(person_),num(num_){}
STUDENT(const STUDENT &obj):PERSON(obj),num(obj.num){}
void display()
{
PERSON::display();
cout<<"The student num is "<<num<<"."<<endl;
}
private:
char *num;
};
int main()
{
STUDENT st("zhangsan",'x',"100");
st.display();
return 0;
}
结果为:
The person name is zhangsan.
The person sex is x.
The student num is 100.
在之前的程序中,我们在基类和派生类中在 display 后加上不重名的字符来区分不同类中的 display 函数,但是这样的函数命名不容易进行区分,因此可以使用作用域运算符进行限定标识,一样能够得到同样的结果。
公有继承
在我们之前提到的例子当中,都没有见到过除了公有继承之外的继承方式,根据之前提到的继承方式之间的差别:
- 基类中的私有成员不管怎么被继承,都不能被派生类访问,基类中的私有成员只能被基类的成员函数访问
- 公有继承不管继承多少级,都能够保证基类中的 public 和 protected 访问属性不变,保证了数据的隐蔽性,接口传递和数据传递
- 保护继承经过多次继承之后,基类中的成员都不能在类外调用,保证了数据的隐蔽性和数据传递
- 私有继承经过多次继承之后,基类中的成员都不能被访问,此时数据的隐蔽性就显得没有用了
多继承
之前提到过,如果只是从一个类中进行派生就是单继承,而如果要同时从多个类中进行派生,这就是多继承问题了。
定义
class deriveclass:derivemode baseclass1,derivemode baseclass2,...
{
statement;
}
参数初始化列表
deriveclass::deriveclass(argument):baseclass1(argument),baseclass2(argument),classmember1(argument),classmember2(argument),...
{
statement;
}
实例
在最初的例子中,我们就简单说到过多继承的问题:
// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
using std::cout;
using std::endl;
class PERSON
{
public:
PERSON(char * name_ = "***",char sex_ = '*')
:name(name_),sex(sex_){}
void display();
private:
char * name;
char sex;
};
class CLUB
{
public:
CLUB(char * name_ = "***"):name(name_){}
void display();
private:
char * name;
};
class STUDENT:public PERSON,public CLUB
{
public:
STUDENT(char * name_ = "***",char sex_ = '*',char * club_ = "***",char *num_ = "***")
:PERSON(name_,sex_),CLUB(club_),num(num_){}
STUDENT(PERSON person_,CLUB club_,char *num_ = "***"):PERSON(person_),CLUB(club_),num(num_){}
void display();
private:
char *num;
};
#endif // MYCLASS_H
// myclass.cpp
#include <iostream>
#include "myclass.h"
using std::cout;
using std::endl;
void PERSON::display()
{
cout<<"The person name is "<<name<<"."<<endl;
cout<<"The person sex is "<<sex<<"."<<endl;
}
void CLUB::display()
{
cout<<"The club name is "<<name<<"."<<endl;
}
void STUDENT::display()
{
PERSON::display();
CLUB::display();
cout<<"The student num is "<<num<<"."<<endl;
}
// main.cpp
#include <iostream>
#include "myclass.h"
using namespace std;
int main()
{
STUDENT st("zhangsan",'x',"math","100");
st.display();
return 0;
}
结果为:
The person name is zhangsan.
The person sex is x.
The club name is math.
The student num is 100.
上边的例子中,STUDENT 类继承了 PERSON 和 CLUB 类,两个类中都含有 sidplay 函数,因此在这种问题中如果想要调用某个类中的该函数时,需要加上作用域运算符进行限定。
上边的例子中说明的是上图左边的情况,此种情况下,派生类的基类之间没有发生过交叉。但是如果是右图中的继承关系,就有点不太一样了,此时 B 中含有 A 的成员,C 中也含有 A 的成员,D 同时含有 B 和 C 的成员,也就说含有 A 的两份成员,这样的情况是需要避免的。
#include <iostream>
using namespace std;
class A
{
public:
A(int i):data(i){}
int data;
};
class B:public A
{
public:
B(int d):A(d){}
void setData(int i){data = i;}
};
class C:public A
{
public:
C(int d):A(d){}
int getData(){return data;}
};
class D:public B,public C
{
public:
D():B(2),C(3){}
void dis()
{
cout<<B::data<<endl;
cout<<C::data<<endl;
}
};
int main()
{
D d;
d.dis();
d.setData(5);
cout<<d.getData()<<endl;
return 0;
}
结果为:
2
3
3
上边的例子中,我们想要改变借用 B 中的 setData 函数改变 D 中的 data 值,但是由于特殊的继承结构却得不到正确的结果。C++ 提出了虚继承的概念来解决这个问题。
虚继承
#include <iostream>
using namespace std;
class A
{
public:
A(int i):data(i){}
int data;
};
class B:virtual public A
{
public:
B(int d):A(d){}
void setData(int i){data = i;}
};
class C:virtual public A
{
public:
C(int d):A(d){}
int getData(){return data;}
};
class D:public B,public C
{
public:
D():B(2),C(3),A(10){}
void dis()
{
cout<<B::data<<endl;
cout<<C::data<<endl;
cout<<data<<endl;
}
};
int main()
{
D d;
d.dis();
d.setData(5);
cout<<d.getData()<<endl;
return 0;
}
结果为:
10
10
10
5
上边的程序中将 B 和 C 的继承关系声明为 virtual,在 D 的构造函数中顺便构造了 A 的参数列表,避免了二义性的出现。这种继承关系就叫做虚继承。
定义
class deriveclass:virtual derivemode baseclass
{
statement;
}
参数初始化列表
需要在交叉点处(上例为 D)为虚基类构建参数初始化列表。
D():B(2),C(3),A(10){}
作用
虚继承作为继承方式的一种扩展,可以避免特殊继承结构之间造成的多重数据成员问题,有效避免程序的二义性。