北京大学MOOC C++学习笔记(四)继承和派生

继承和派生

概念:

  1. 继承:在定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),那么就可以把A作为一个 基类,而把B作为基类的一个 派生类( ( 也称子类) )。
  2. 派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。
  3. 派生类一经定义后,可以独立使用,不依赖于基类。
  4. 派生类拥有基类的全部成员函数和成员变量,不论是private、protected、public 。
    -但在派生类的各个成员函数中,不能访问基类中的private成员。

派生类的写法:

class 派生类名:public 基类名
{
};

下面是一个派生类的实例:

#include <iostream>
#include <string>
using namespace std;
class CStudent {
	private:
		string name;
		string id; //学号
		char gender; //性别,'F'代表女,'M'代表男
		int age;
	public:
		void PrintInfo();
		void SetInfo( const string & name_,const string & id_,
		int age_, char gender_ );
		string GetName() { return name; }
};
class CUndergraduateStudent:public CStudent
{//本科生类,继承了CStudent类
	private:
		string department; //学生所属的系的名称
	public:
		void QualifiedForBaoyan() { //给予保研资格
			cout << “qualified for baoyan” << endl;
		}
		void PrintInfo() {
			CStudent::PrintInfo(); //调用基类的PrintInfo
			cout << “Department:” << department <<endl;
		}
		void SetInfo( const string & name_,const string & id_,int age_,char gender_ ,const string & department_) {
			CStudent::SetInfo(name_,id_,age_,gender_); //调用基类的SetInfo
			department = department_;
		}
};
void CStudent::PrintInfo()
{
		cout << "Name:" << name << endl;
		cout << "ID:" << id << endl;
		cout << "Age:" << age << endl;
		cout << "Gender:" << gender << endl;
}
void CStudent::SetInfo( const string & name_,const string & id_,int age_,char gender_ )
{
		name = name_;
		id = id_;
		age = age_;
		gender = gender_;
}
int main()
{
		CUndergraduateStudent s2;
		s2.SetInfo(“Harry Potter ”, “118829212”,19,‘M’,“Computer Science”);
		cout << s2.GetName() << “ ” ;
		s2.QualifiedForBaoyan ();
		s2.PrintInfo ();
		return 0;
}

输出结果:
Harry Potter qualified for baoyan
Name:Harry Potter
ID:118829212
Age:19
Gender:M
Department:Computer Science

派生类对象的内存空间

派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。

继承关系和复合关系

继承:“是”关系。
– 基类A,B是基类A的派生类。
– 逻辑上要求:“一个B对象也是一个A对象”。
复合:“有”关系。
– 类C中“有”成员变量k,k是类D的对象,则C和D是复合关系
– 一般逻辑上要求:“D对象是C对象的固有属性或组成部分”。

继承关系的使用例子:写一个 CHuman类,代表“人”,然后CMan和CWoman都从CHuman派生。
复合关系的使用例子:几何形体程序中,需要写“点”类,也需要写“圆”类,两者的关系就是复合关系 ---- 每一个“圆”对象里都包含(有)一个“点”对象,这个“点”对象就是圆心。

派生类覆盖基类成员

覆盖:派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号::。

类的保护成员

另一种存取权限说明符:protected

• 基类的private成员:可以被下列函数访问
– 基类的成员函数
– 基类的友员函数
• 基类的public成员:可以被下列函数访问
– 基类的成员函数
– 基类的友员函数
– 派生类的成员函数
– 派生类的友员函数
– 其他的函数
• 基类的protected成员:可以被下列函数访问
– 基类的成员函数
– 基类的友员函数
– 派生类的成员函数可以访问当前对象的基类的保护成员

class Father {
	private: int nPrivate; // 私有成员
	public: int nPublic; // 公有成员
	protected: int nProtected; //  保护成员
};
class Son :public Father{
	void AccessFather () {
		nPublic = 1; // ok;
		nPrivate = 1; // wrong
		nProtected = 1; // OK ,访问从基类继承的protected 成员
		Son f;
		f.nProtected = 1; //wrong  ,f 不是当前对象
	}
};
int main()
{
	Father f;
	Son s;
	f.nPublic = 1; // Ok
	s.nPublic = 1; // Ok
	f.nProtected = 1; // error
	f.nPrivate = 1; // error
	s.nProtected = 1; //error
	s.nPrivate = 1; // error
	return 0;
}

派生类的构造函数

class Bug {
	private :
		int nLegs; int nColor;
	public:
		int nType;
		Bug ( int legs, int color);
		void PrintBug (){ };
};
class FlyBug: public Bug // FlyBug 是Bug 的派生类
{
		int nWings;
	public:
		FlyBug( int legs,int color, int wings);
};
Bug::Bug( int legs, int color)
{
		nLegs = legs;
		nColor = color;
}
// 错误的FlyBug 构造函数
FlyBug::FlyBug ( int legs,int color, int wings)
{
		nLegs = legs; //  不能访问
		nColor = color; //  不能访问
		nType = 1; // ok
		nWings = wings;
}
// 正确的FlyBug 构造函数:
FlyBug::FlyBug ( int legs, int color, int wings):Bug( legs, color)
{
		nWings = wings;
}
int main() {
	FlyBug fb ( 2,3,4);
		fb.PrintBug();
		fb.nType = 1;
		fb.nLegs = 2 ; // error. nLegs is private
		return 0;
}

• 在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
• 调用基类构造函数的两种方式
– 显式方式:在派生类的构造函数中,为基类的构造函数提供参数.
derived::derived(arg_derived-list):base(arg_base-list)
– 隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的默认构造函数.
• 派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数。

封闭派生类对象的构造函数执行顺序

在创建派生类的对象时:

  1. 先执行基类的构造函数,用以初始化派生类对象中从基类继承的成员;
  2. 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。
  3. 最后执行派生类自己的构造函数.
封闭派生类对象消亡时析构函数的执行顺序

在派生类对象消亡时:

  1. 先执行派生类自己的析构函数
  2. 再依次执行各成员对象类的析构函数
  3. 最后执行基类的析构函数
    析构函数的调用顺序与构造函数的调用顺序相反。

public继承的赋值兼容规则

class base { };
class derived : public base { };
base b;
derived d;
1) 派生类的对象可以赋值给基类对象
b = d;
2) 派生类对象可以初始化基类引用
base & br = d;
3) 派生类对象的地址可以赋值给基类指针
base * pb = & d;
• 如果派生方式是 private或protected,则上述三条不可行。

protected继承和private继承

class base {
};
class derived : protected base {
};
base b;
derived d;
• protected继承时,基类的public成员和protected成员成为派生类的protected成员。
• private继承时,基类的public成员成为派生类的private成员,基类的protected成员成
为派生类的不可访问成员。
• protected和private继承不是“是”的关系。

基类与派生类的指针强制转换

1 公有派生的情况下,派生类对象的指针可以直接赋值给基类指针
Base * ptrBase = &objDerived;
ptrBase指向的是一个Derived类的对象;
*ptrBase可以看作一个Base类的对象,访问它的public成员直接通过ptrBase即可,但不能通过ptrBase访问objDerived对象中属于Derived类而不属于Base类的
成员
2 即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有,而派生类中有的成员。
3 通过强制指针类型转换,可以把ptrBase转换成Derived类的指针
Base * ptrBase = &objDerived;
Derived *ptrDerived = (Derived * ) ptrBase;
程序员要保证ptrBase指向的是一个Derived类的对象,否则很容易会出错。

#include <iostream>
using namespace std;
class Base {
	protected:
		int n;
	public:
		Base(int i):n(i){
			cout << "Base " << n <<" constructed" << endl; }
		~Base() {
		cout << "Base " << n <<" destructed" << endl;
		}
		void Print() { cout << "Base:n=" << n << endl;}
};
class Derived:public Base {
	public:
		int v;
		Derived(int i):Base(i),v(2 * i) {
			cout << "Derived constructed" << endl;
		}
		~Derived()  {
			cout << "Derived destructed" << endl;
		}
		void Func() { } ;
		void Print() {
			cout << "Derived:v=" << v << endl;
			cout << "Derived:n=" << n << endl;
		}
};
int main() {
		Base objBase(5);
		Derived objDerived(3);
		Base * pBase = & objDerived ;
		//pBase->Func(); //err;Base 类没有Func() 成员函数
		//pBase->v = 5; //err; Base 类没有v 成员变量
		pBase->Print();
		//Derived * pDerived = & objBase; //error
		Derived * pDerived = (Derived *)(& objBase);
		pDerived->Print(); // 慎用,可能出现不可预期的错误
		pDerived->v = 128; // 往别人的空间里写入数据,会有问题
		objDerived.Print();
		return 0;
}

输出结果:
Base 5 constructed
Base 3 constructed
Derived constructed
Base:n=3
Derived:v=1245104 //pDerived->n 位于别人的空间里
Derived:n=5
Derived:v=6
Derived:n=3
Derived destructed
Base 3 destructed
Base 5 destructed

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值