C++ 2 组合与继承

C++ 2 组合与继承

类的组合

  • “有”的关系,组合是横向的
  • 类的数据成员可以包含对象,称为子对象,即对象中的对象

类的继承

  • “是”的关系,继承是纵向的
  • 继承是C++ 和 C 的最重要的区别之一
  • 一个新类从已有类那里获得其已有特征,称为类的继承
    • 派生类接收基类全部成员(除构造函数和析构函数)
    • 在派生类中声明一个与基类成员同名的成员,则派生类的新成员会覆盖基类的同名成员,若为成员函数参数个数、参数类型也要相同,否则就成重载
  • 派生类基类具体化基类派生类抽象
  • 一代一代地派生下去,形成类的继承层次结构
    • 单继承:一个派生类只从一个基类派生,树形结构
    • 多重继承:一个派生类有两个或多个的基类
  • 箭头表示继承方向,派生类->基类
  • 继承方式:public private protected ,不写默认为private

继承方式

公有继承
  • 公有和保护成员保持原有访问属性,私有成员仍为基类私有
  • 不可访问,如:不能使用Student.sno Student.privatefun()
在基类的访问属性继承方式在派生类中的访问属性
privatepublic不可访问
publicpublicpublic
protectedpublicprotected
class Student{};
class Graduate: public Student{};
私有继承
  • 公有和保护成员在派生类中成了私有,私有成员仍为基类私有
在基类的访问属性继承方式在派生类中的访问属性
privateprivate不可访问
publicprivateprivate
protectedprivateprivate
class Student{};
class Graduate: private Student{};
保护继承
  • 公有和保护成员在派生类中成了保护成员,私有成员仍为基类私有
在基类的访问属性继承方式在派生类中的访问属性
privateprotected不可访问
publicprotectedprotected
protectedprotectedprotected
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 输入输出流

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值