《C++面向对象程序设计》课程笔记 lessen5

1. 继承和派生的概念

1 继承的概念

在定义一个新的类 B 时,如果该类与某个已有的类 A 相似(指的是 B 拥有 A 的全部特点),那么就可以把 A 作为一个基类,而把 B 作为基类的一个派生类(也称子类)。 

  • 派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。修改指的是派生类中写名称与基类相同的成员函数,而该函数所进行的行为不太相同。
  • 派生类一经定义后,可以独立使用,不依赖于基类。 
  • 派生类拥有基类的全部成员函数和成员变量,不论是 private、protected、public。
  • 虽然继承了基类的 private 成员变量,但在派生类新定义的各个成员函数中,不能访问基类中的 private 成员。 

 2 派生类的写法

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

3 继承实例程序:学籍管理

#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; }
};

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_;
}

class CUndergraduate:public CStudent
{//本科生类,继承了CStudent 类
private:
	string department; //学生所属的系的名称
public:
	void QualifiedForBaoyan() //给予保研资格
	{
		cout<<"qualified for baoyan"<<endl;
	}
	void PrintInfo()  //对基类 PrintInfo()函数的覆盖,即为对基类 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_;
	}
};

int main()
{
	CUndergraduate s2;
	s2.SetInfo("Harry Potter","118829212",19,'M',"Computer Science");
	cout<<s2.GetName()<<" ";
	s2.QualifiedForBaoyan();
	s2.PrintInfo();

	system("pause");
	return 0;
}

2. 继承关系和复合关系

1 C++类之间的关系

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

   复合关系使用的例子 :

class CPoint
{
	double x,y;
	friend class CCircle;  
	//便于 CCircle 类操作其圆心,因为 CPoint 类的成员对象是其私有的
};

class CCircle
{
	double r;
	CPoint center;
};

   小区养狗的管理程序:两个类:主人类、狗类。狗只有一个主人,而每个业主最多可拥有10条狗。

class CDog;
class CMaster
{
	CDog dogs[10];
};
class CDog
{
	CMaster m;
};

上面这种处理方法是错误的,循环定义。CDog 类和CMaster 类的字节大小是多少?

另一种写法:为“狗”类设一个“业主”类的成员变量,为“业主”类设一个“狗”类的对象指针数组。

class CDog;
class CMaster
{
	CDog * dogs[10];
};
class CDog
{
	CMaster m;
};

该种写法避免了循环定义,但还是错误的。无法维护不同的狗中主人信息一致性的问题。如果改变了一条狗中的主人信息,另一条狗中的主人信息也应进行相应的改变。而这种写法无法做到。

第三种写法,凑合的写法。 为“狗”类设一个“业主”类的对象指针,为“业主”类设一个“狗”类的对象数组。

class CMaster; //CMaster必须提前声明,不能先写CMaster类,后写CDog类
class CDog
{
	CMaster * m;
};
class CMaster
{
	CDog dogs[10];
};

这种写法有两点不好的地方:一、狗不是主人的一部分,狗不是主人的固有属性。二、“狗”类失去了自由。对“狗”对象进行操作时要通过“主人”对象。 

正确的写法: 为“狗”类设一个“业主”类的对象指针,为“业主”类设一个“狗”类的对象指针数组。

class CMaster; //CMaster必须提前声明,不能先写CMaster类,后写CDog类
class CDog
{
	CMaster * m;
};
class CMaster
{
	CDog * dogs[10];
};

3. 覆盖和保护成员 

1 覆盖的概念

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

基类和派生类不要定义同名的成员变量。

2 保护成员

基类的 private 成员可以被下列函数访问:

  • 基类的成员函数
  • 基类的友元函数

基类的 public 成员可以被下列函数访问:

  • 基类的成员函数
  • 基类的友元函数
  • 派生类的成员函数
  • 派生类的友元函数
  • 其它的函数

基类的 protected 成员可以被下列函数访问:

  • 基类的成员函数
  • 基类的友元函数
  • 派生类的成员函数可以访问当前对象的基类的保护成员。
#include <iostream>
using namespace std;

class Father
{
private:
	int nPrivate; //私有成员
public:
	int nPublic;  //公有成员
protected:
	int nProtected; //保护成员
};

class Son:public Father
{
	void AccessFather()
	{
		nPublic = 1; //ok
//		nPrivate = 1; //error
		nProtected = 1; //ok ,访问当前对象从基类继承的 protected 成员。
		Son f;
//		f.nProtected = 1; //error。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

	system("pause");
	return 0;
}

 4. 派生类的构造函数

在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数。 

调用基类构造函数的两种方式:

  • 显式方式:在派生类的构造函数中,为基类的构造函数提供参数。derived::derived(arg_derived-list):base(arg_base-list) 
  • 隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的默认构造函数。(若基类没有无参构造函数,则编译出错

 派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数。

封闭派生类对象(含有成员对象)的构造函数执行顺序:

  • 先执行基类的构造函数,用以初始化派生类对象 中从基类继承的成员。
  • 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象。
  • 最后执行派生类自己的构造函数。

在封闭派生类对象消亡时:

  • 先执行派生类自己的析构函数。
  • 再依次执行各成员对象类的析构函数。
  • 最后执行基类的析构函数。 
#include <iostream>
using namespace std;

class Bug
{
private:
	int nLegs;
	int nColor;
public:
	int nType;
	Bug(int legs, int color);
	void PrintBug(){ };
};

Bug::Bug(int legs, int color)
{
	nLegs = legs;
	nColor = color;
}

class FlyBug:public Bug //FlyBug 是 Bug 的派生类
{
	int nWings;
public:
	FlyBug(int legs,int color, int wings);
};
//错误的派生类 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) //派生类的初始化列表里直接初始化基类的 Bug 对象
{
	nWings = wings;
}

int main()
{
	FlyBug fb(2,3,4);
	fb.PrintBug();
	fb.nType = 1;
//	fb.nLegs = 2; //error.nLegs是基类的私有成员变量,派生类不能访问

	system("pause");
	return 0;
}

5. public 继承的赋值兼容规则

class base { };
class derived:public base { };
base b;
derived d;
  • 派生类的对象可以赋值给基类对象。b = d 。而基类对象不能赋值给派生类对象。
  • 派生类对象可以初始化基类引用。 base & br = d 。
  • 派生类对象的地址可以赋值给基类的指针。 base * pb = &d 。

如果派生方式是 private 或 protected ,则上述三条不可行。 

2 直接基类和间接基类

在声明派生类时,只需要列出它的直接基类。

 派生类沿着类的层次自动向上继承它的间接基类。

派生类的成员包括:

  • 派生类自己定义的成员
  • 直接基类中的所有成员
  • 所有间接基类的全部成员 

猜你喜欢

转载自blog.csdn.net/sinat_35483329/article/details/85046673