目录
C++ 2 组合与继承
类的组合
- “有”的关系,组合是横向的
- 类的数据成员可以包含对象,称为子对象,即对象中的对象
类的继承
- “是”的关系,继承是纵向的
- 继承是C++ 和 C 的最重要的区别之一
- 一个新类从已有类那里获得其已有特征,称为类的继承
- 派生类接收基类全部成员(除构造函数和析构函数)
- 在派生类中声明一个与基类成员同名的成员,则派生类的新成员会覆盖基类的同名成员,若为成员函数参数个数、参数类型也要相同,否则就成重载
- 派生类是基类的具体化,基类是派生类的抽象
- 一代一代地派生下去,形成类的继承层次结构
- 单继承:一个派生类只从一个基类派生,树形结构
- 多重继承:一个派生类有两个或多个的基类
- 箭头表示继承方向,派生类
->
基类 - 继承方式:
public
private
protected
,不写默认为private
继承方式
公有继承
- 公有和保护成员保持原有访问属性,私有成员仍为基类私有
- 不可访问,如:不能使用
Student.sno
Student.privatefun()
在基类的访问属性 | 继承方式 | 在派生类中的访问属性 |
---|---|---|
private | public | 不可访问 |
public | public | public |
protected | public | protected |
class Student{};
class Graduate: public Student{};
私有继承
- 公有和保护成员在派生类中成了私有,私有成员仍为基类私有
在基类的访问属性 | 继承方式 | 在派生类中的访问属性 |
---|---|---|
private | private | 不可访问 |
public | private | private |
protected | private | private |
class Student{};
class Graduate: private Student{};
保护继承
- 公有和保护成员在派生类中成了保护成员,私有成员仍为基类私有
在基类的访问属性 | 继承方式 | 在派生类中的访问属性 |
---|---|---|
private | protected | 不可访问 |
public | protected | protected |
protected | protected | protected |
class Student{};
class Graduate: protected Student{};
多重继承
- 一个派生类同时继承多个类
class D: public A, private B, protected C{};
派生类的构造函数
- 当不需要对派生类新增成员进行任何初始操作时,派生构造类的函数体可以为空,即构造函数是空函数
- 若基类(和子对象)没有定义构造函数,或定义了没有参数的构造函数,则派生类构造函数可以不写基类构造函数。因为此时派生类构造函数没有向基类构造函数传递参数的任务,系统会首先调用基类(和子对象)的默认构造函数
- 若基类(和子对象)定义了带参数的构造函数,则派生类构造函数必须写基类(和子对象)构造函数。
- 若基类(和子对象)没有定义构造函数,或定义了没有参数的构造函数,又定义了带参数的构造函数,则派生类构造函数可以不写基类(和子对象)构造函数,也可以写基类(和子对象)构造函数
简单的派生类的构造函数
- 先调用基类构造函数,所以先执行派生类析构函数,再执行基类析构函数
class Student{
private:
int num;
string name;
public:
Student(int n, string nam){
num = n;
name = nam;
}
};
class Graduate: public Student{
private:
int age;
string addr;
public:
//类内定义
Graduate(int n, string nam, int a, string ad): Student(n, nam){
age = a;
addr = ad;
}
//用参数初始化表
Graduate(int n, string nam, int a, string ad): Student(n, nam), age(a), addr(ad){}
//也可直接使用常量或全局变量
Graduate(string nam, int a, string ad): Student(001, nam), age(a), addr(ad){}
//类外定义的声明
Graduate(int n, string nam, int a, string ad);
};
//类外定义
Graduate::Graduate(int n, string nam, int a, string ad): Student(n, nam){
age = a;
addr = ad;
}
有子对象的派生类的构造函数
- 类的数据成员可以包含对象,称为子对象,即对象中的对象
- 先调用基类构造函数,再调用子对象构造函数(看谁写在前面,习惯上基类构造函数写在前面)
- 编译系统是根据相同的参数名(而不是根据参数的顺序)来确定传递关系
class Student{
private:
int num;
string name;
public:
Student(int n, string nam){
num = n;
name = nam;
}
};
class Graduate: public Student{
private:
Student monitor; //子对象
int age;
string addr;
public:
//类内定义
Graduate(int n, string nam, int n1, string nam1, int a, string ad): Student(n, nam), monitor(n1, nam1){
age = a;
addr = ad;
}
//用参数初始化表
Graduate(int n, string nam, int n1, string nam1, int a, string ad): Student(n, nam), monitor(n1, nam1), age(a), addr(ad){}
//顺序可调
Graduate(int n, string nam, int n1, string nam1, int a, string ad): monitor(n1, nam1), Student(n, nam), age(a), addr(ad){}
};
多层派生的构造函数
- 不要列出每一层派生类的构造函数,只须写出其上一层派生类(即它的直接基类)的构造函数
//设都为 public 继承,以参数初始化表为例
class A{
private:
int num1, cno1;
public:
A(int n1, int c1): num1(n1), cno1(c1){}
};
class B:public A{
private:
int num2, cno2;
public:
B(int n1, int c1, int n2, int c2): A(n1, c1), num2(n2), cno2(c2){}
};
class C:public B{
private:
int num3, cno3;
public:
C(int n1, int c1, int n2, int c2, int n3, int c3): B(n1, c1, n2, c2), num3(n3), cno3(c3){}
};
多重继承的派生类的构造函数
//设都为 public 继承,以参数初始化表为例
class A{
private:
int num1, cno1;
public:
A(int n1, int c1): num1(n1), cno1(c1){}
};
class B{
private:
int num2, cno2;
public:
B(int n2, int c2): num2(n2), cno2(c2){}
};
class C:public A, public B{
private:
int num3, cno3;
public:
C(int n1, int c1, int n2, int c2, int n3, int c3): A(n1, c1), B(n2, c2), num3(n3), cno3(c3){}
};
虚基类的构造函数
见下文虚基类部分
继承引起的二义性问题
- 两类的变量同名,出现同名覆盖,若调用基类的要标明范围
#include<iostream>
using namespace std;
class A{
protected: //使 B 类能够访问 A 类的 num
int num, cno1;
public:
A(int n1, int c1): num(n1), cno1(c1){}
};
class B:public A{
private:
int num, cno2;
public:
B(int n1, int c1, int n2, int c2): A(n1, c1), num(n2), cno2(c2){}
void show(){
cout<<A::num<<endl;
cout<<num<<endl;
}
};
int main(){
B b(21, 22, 23, 24);
b.show();
return 0;
}
- 多重继承的二义性
- 要区别
n
是类A中从基类N继承下来的,还是类B中从基类N继承下来的
- 要区别
#include<iostream>
using namespace std;
class N{
public:
int num0, cno0;
N(int n0, int c0): num0(n0), cno0(c0){}
};
class A: public N{ //声明类 A 是类 N 的公用派生类
public:
int num1, cno1;
A(int n0, int c0, int n1, int c1): N(n0, c0), num1(n1), cno1(c1){}
};
class B: public N{ //声明类 B 是类 N 的公用派生类
public:
int num2, cno2;
B(int n0, int c0, int n2, int c2): N(n0, c0), num2(n2), cno2(c2){}
};
class C:public A, public B{
public:
int num3, cno3;
C(int n0, int c0, int n1, int c1, int n2, int c2, int n3, int c3): A(n0, c0, n1, c1), B(n0, c0, n2, c2), num3(n3), cno3(c3){}
};
int main(){
C c(31, 32, 33, 34, 35, 36, 37, 38);
//cout<<c.num0<<endl; //不合法
//cout<<c.N::num0<<endl; //不合法
cout<<c.A::num0<<endl;
cout<<c.B::num0<<endl;
return 0;
}
虚基类
- 虚基类在继承间接共同基类时只保留一份成员
- 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则仍会出现对基类的多次继承
#include<iostream>
using namespace std;
class N{
public:
int num0, cno0;
N(int n0, int c0): num0(n0), cno0(c0){}
};
class A:virtual public N{ //声明类 A 是类 N 的公用派生类,N 是 A 的虚基类
public:
int num1, cno1;
A(int n0, int c0, int n1, int c1): N(n0, c0), num1(n1), cno1(c1){}
};
class B:virtual public N{ //声明类 B 是类 N 的公用派生类,N 是 B 的虚基类
public:
int num2, cno2;
B(int n0, int c0, int n2, int c2): N(n0, c0), num2(n2), cno2(c2){}
};
class C:public A, public B{
public:
int num3, cno3;
C(int n0, int c0, int n1, int c1, int n2, int c2, int n3, int c3): N(n0, c0), A(n0, c0, n1, c1), B(n0, c0, n2, c2), num3(n3), cno3(c3){}
};
int main(){
C c(31, 32, 33, 34, 35, 36, 37, 38);
cout<<c.num0<<endl;
cout<<c.N::num0<<endl;
cout<<c.A::num0<<endl;
cout<<c.B::num0<<endl;
return 0;
}
基类与派生类的转换
- 只有公有派生类才是基类真正的子类型,它完整地继承了基类的功能
- 派生类对象可以向基类对象赋值,所谓赋值只是对数据成员赋值
- 同一基类的不同派生类对象之间不能赋值
A a;
B b; //B 为 A 的公用派生类
b = a; //错误,不能用基类对象对子类对象赋值
a = b;
//设 age 是 b 中的新增公用数据成员
a.age = 1; //错误
b.age = 1; //正确
- 派生类对象可以替代基类对象的引用进行赋值或初始化
A a;
A &r = a; //a 的别名为 r
A a;
B b;
A &r = b; //一个类 A 对象的别名为 r,用 b 赋值,r 为 b 中基类部分的别名
A a;
B b;
A &r = a; //a 的别名为 r
r = b; //r 用 b 赋值,r 为 b 中基类部分的别名
- 派生类对象的地址可以赋值给指向基类对象的指针变量,但该指针不能访问派生类自己增加的成员
A a;
B b;
A *p;
p = &a; //定义指针 p 指向 a
p = &b; //p 也可指向 b
- 如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象
A a;
B b;
//定义
void fun(A& r){}
//调用
fun(b);
参考资料
- [1]谭浩强.C++面向对象程序设计(第2版)北京:清华大学出版社,2014.
C++ 1 基础知识
C++ 2 组合与继承
C++ 3 构造函数与析构函数
C++ 4 引用、指针、常量、静态、友元
C++ 5 多态性
C++ 6 输入输出流