C++ 大学MOOC 北大课程(郭炜老师)听课整理 第五周(类的关系)

继承与派生概念

1)如果新定义的类B和已有的类A有相似的地方,则可以将类A作为类B的基类,类B是类A的派生类
2)派生类是对基类的扩充
3)派生类继承基类中所有的成员,包括成员变量和成员函数
4)在派生类中的成员函数中不可访问基类的私有成员变量
派生类的写法:
class 派生类名:public 基类名{

}
5)派生类的内存空间包括了基类的成员变量和新定义的成员变量
例如:

class cstudent{ //基类
private:
 string name; //学生姓名
 string id; //学生学号 
 char gender;  //性别
 int age;  //年龄
public:
 void printinfo();  //打印学生信息
 void setinfo(const string& name_, const string& id_, char gender_, int age_);  //录入学生信息
 string getname(){ return name; }  //返回学生姓名
};
void cstudent::printinfo(){
 cout << "Name:" << name << endl;
 cout << "ID:" << id << endl;
 cout << "Gender:" << gender << endl;
 cout << "Age:" << age << endl;
}
void cstudent::setinfo(const string& name_, const string& id_, char gender_, int age_){
 name = name_;
 id = id_;
 gender = gender_;
 age = age_;
}
class cundergraduatestudent :public cstudent{  //派生类 本科生
private:
 string department;  //所在系
public:
 void canbaoyan(){
  cout << "qualified for baoyan" << endl;
 }
 void printinfo(){
  cstudent::printinfo();  //调用基类函数打印本科生学生信息
  cout << "Department:" << department << endl;
 }
 void setinfo(const string& name_, const string& id_, char gender_, int age_, const string& department_){
  cstudent::setinfo(name_, id_, gender_, age_);  //调用基类函数例如本科生学生信息
  department = department_;
 }
};
int main(){
 cundergraduatestudent s1;
 s1.setinfo("Hery", "123456", 'M', 16, "Vehicle Engineering");
 cout<<s1.getname()<<' ';
 s1.canbaoyan();
 s1.printinfo();
 return 0;
}

输出:

Hery qualified for baoyan
Name:Hery
ID:123456
Gender:M
Age:16
Department:Vehicle Engineering

继承和复合关系

1)类和类之间有两种关系:继承和复合
2)继承关系讲究”是“:类A是类B,则类A是类B的派生
3)复合关系讲究”有“:类A有类B,即类A当中包含类B对象成员
例1:继承的使用
男人类和女人类都是”人“,则可以定义人类,将男人类和女人类定义为人类的派生类
例2:复合的使用
圆类和点类,圆可以通过两个元素来确定:半径和圆心坐标,圆心属于点类的对象,所以圆类和点类是复合关系,圆类中有点类成员
例3:复合的使用
需要写一个小区的养狗程序,需要写”业主“类和”狗“类。规定每个狗只有一个主人,一个主人最多有十只狗
问题写法一:

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

问题所在:出现”狗中有人,人中有狗“循环定义现象,比如定义类CDog对象时,编译器分配内存空间时需要分配一个类CMaster对象的空间,而类CMaster对象又包含是个定义类CDog对象的空间。
问题写法二:

class CDog;
class CMaster{
 CDog *dogs[10];  //定义指针数组 每一个指针元素指向一个类CDog对象
};
class CDog{
 CMaster m;
};

问题所在:每一个狗都包含一个人,但当存在不同狗都属于同一个主人的情况时,其中一个狗的主人的信息一旦修改,就需要设法保证同属一个主人的其他狗中的主人对象的信息也得修改。
问题写法三:

class CDog;
class CMaster{
 CDog dogs[10];
};
class CDog{
 CMaster * m;  //狗中存放指向主人的指针
};

问题所在:必须通过主人对象修改狗的信息,将致成诸多不便
正确写法:

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

狗的对象包含指向人的指针,人的最想包含指向狗的指针 既保证人与狗的关系 又能保证两者的相互独立。

覆盖和保护成员

覆盖

1)当派生类中定义了和基类同名的成员,直接调用时缺省为派生类的成员
2)要在派生类中访问与基类同名成员时,需要使用作用域符号::
例如:

class base{
 int j;
public:
 int i;
 void func();
};
class derived :public base{
public:
 int i;  //与基类成员变量同名
 void access();
 void func();  //与基类成员函数同名
};
void derived::access(){
 j = 5;  //编译出错,派生类成员函数不能访问从基类继承的私有成员变量
 i = 5;  //访问的是派生类的名 i 的成员变量
 base::i = 8;  //访问从基类继承的同名为 i 的成员变量
 func();  //调用派生类的名 func 的成员函数
 base::func();  //调用从基类继承的同名为 func 的成员函数
}
int main(){
 derived r;
 r.i = 1;  //修改派生类对象中名 i 的成员变量
 r.base::i = 1;  //修该派生类对象中继承于基类的同名为 i 的成员变量
 return 0;
}

一般 不要将成员变量覆盖

保护成员

1)用成员访问范围说明符protected标识 的成员为保护成员,其访问范围:基类成员函数、基类友元函数、派生类对象成员函数(能访问所作用的对象继承的基类保护成员)
2)由private标识的成员为私有成员,其只有基类的成员函数和基类友元函数能访问
3)由public标识的成员为公有成员,其可以在任何地方访问
例如:

class father{
private:
 int nprivate;
public:
 int npublic;
protected:
 int nprotected;
};
class son :public father{
 void access(){
  npublic = 1;  
  nprotected = 3;  //正确 可以访问基类的保护成员
  nprivate = 4;    //错误 不可访问基类私有成员
  son f;
  f.nprotected = 8;  //错误  f不是当前对象
 }
};

派生类的构造函数

1)初始化派生类对象时,需要调用基类的构造函数:先执行基类的构造函数,再执行派生类的构造函数
2)调用基类构造函数的方式有两种
3)一种是显式方式:使用初始化列表调用基类构造函数
4)一种是隐式方式:在派生类的构造函数中省略基类的构造函数,则缺省调用基类的无参构造函数
5)调用析构函数时:先调用派生类的析构函数,再调用基类的析构函数
例如:

class Base{
public:
 int n;
 Base(int i) :n(i){
  cout << "Base " << n << " constructed" << endl;
 }
 ~Base(){
  cout << "Base " << n << " destructed" << endl;
 }
};
class Derived :public Base{
public:
 Derived(int i) :Base(i){  //显式调用基类构造函数
  cout << "Derived " << "constructed" << endl;
 }
 ~Derived(){
  cout << "Derived " << "destructed" << endl;
 }
};
int main(){
 Derived obj(4);
 return 0;
}

输出:

Base 4 constructed
Derived constructed
Derived destructed
Base 4 destructed

封闭派生类对象构造函数

1)封闭派生类对象初始化时,先执行基类的构造函数,再执行成员对象类的构造函数,最后执行自己的构造函数
2)封闭派生类对象消亡时,先执行自己的析构函数,再执行成员对象类的析构函数,最后执行基类的析构函数
例如:

class Bug{
private:
 int nLegs;
 int nColor;
public:
 int nType;
 Bug(int legs, int color);
 void PrintBug();
};
class skill{
public:
 skill(int n);
};
class FlyBug :public Bug{  //这是一个封闭派生类
private:
 int nWings;   //成员变量
 skill sk1, sk2;  //含有成员对象
public:
 FlyBug(int legs, int color, int wings);  //声明构造函数
};
FlyBug::FlyBug(int legs, int color, int wings)
 :Bug(legs,color),sk1(4),sk2(color),nWings(wings){} //初始化列表对基类、对象、成员变量初始化

公有继承的赋值兼容规则

1)派生类的对象可以赋值给基类对象
2)派生类对象可以初始化基类引用
3)派生类对象地址可以赋值给基类指针
例如:

Base * ptrBase = &objDrived;

objDrived 是类Base的派生类对象,可以通过指针 ptrBase 直接访问基类的公有成员,但不能访问在 objDrived 对象中属于派生类而不属于基类的成员。

直接基类和间接基类

1)类A派生类B,类B派生类C,则称类A是类C的间接基类而类B是类C的直接基类
2)派生类继承直接类和所有间接类的所有成员
3)声明派生类时,只需要列出它的直接基类
例如:

class Base{
public:
 int n;
 Base(int i):n(i){
  cout << "Base " << n << " constructed" << endl;
 }
 ~Base(){
  cout << "Base " << n << " destructed" << endl;
 }
};
class Derived :public Base{
public:
 Derived(int i) :Base(i){
  cout << "Derived " << "constructed" << endl;
 }
 ~Derived(){
  cout << "Derived " << "destructed" << endl;
 }
};
class moreDerived :public Derived{  //间接基类是 Base 
public:
 moreDerived(int i) :Derived(i){
  cout << "moreDerived " << "constructed" << endl;
 }
 ~moreDerived(){
  cout << "moreDerived " << "destructed" << endl;
 }
};
int main(){
 moreDerived obj(4);
 return 0;
}

输出:

Base 4 constructed
Derived constructed
moreDerived constructed
moreDerived destructed
Derived destructed
Base 4 destructed
发布了16 篇原创文章 · 获赞 17 · 访问量 755

猜你喜欢

转载自blog.csdn.net/weixin_45644911/article/details/104210561