C++学习心得(5)继承与派生

来源:互联网 发布:嵌入式linux打开图片 编辑:程序博客网 时间:2024/04/30 15:29

C++学习心得(5)继承与派生

——from 谭浩强.C++面向对象程序设计(第一版)
2014/10/9
面向对象的程序设计的4个主要特点:
抽象、封装、继承、多态性

5.1 继承与派生的概念

在C++中,继承就是在一个已存在的类的基础上建立一个新的类。
一个新类从已有的类那里获得其已有特性,这种现象称为继承。
从已有的类产生一个新的子类,称为类的派生。
一个派生类只从一个基类派生称为单继承;
一个派生类有两个或多个基类称为多继承。

5.2 派生类的声明方式

声明派生类的一般形式为:

class 派生类名: [继承方式] 基类名{    派生类新增加的成员;};

继承方式包括:

  • public,公用的;
  • private,私有的;
  • protected,受保护的;
  • -

5.3 派生类的构成

构造一个派生类的3部分工作:

  1. 从基类接收成员;接收基类的全部成员,但不包括构造函数和析构函数。
  2. 调整从基类接收的成员。指定继承方式调整访问属性。在派生类中声明一个与基类成员同名的成员,则新成员会覆盖基类的同名成员。
  3. 在声明派生类时增加成员。自己搞定构造函数和析构函数。

5.4 派生类成员的访问属性

需要考虑的情况:

  • 派生类的成员函数访问基类的成员;
  • 在派生类外访问基类的成员;
    三种继承方式:
    1. 公用继承 基类的公用成员和保护成员保持原有的访问属性,其私有成员仍为基类所私有,派生类不可访问。
    2. 私有继承 基类的公用成员和保护成员成为派生私有成员,只有派生类成员函数能够访问,派生类外不能访问,其私有成员仍为基类所私有,派生类不可访问。
    3. 保护继承 公用成员和保护成员成为派生类保护成员,其私有成员仍为基类所私有,派生类不可访问。

保护成员的意思是,不能被外界引用,但可以被派生类的成员引用,有点类似于遗产。
继承
新增

函数
数据

5.4.1 公用继承

class Student                                        //声明基类{    public:       voidget_value()       {           cin>> num >> name >> sex;       }       voiddisplay()       {           cout<< "num: " << num << endl;           cout<< "name: " << name << endl;           cout<< "sex: " << sex << endl;       }    private:       intnum;       stringname;       charsex;};class Student1: public Student                       // 以public方式声明派生类Student1{    public:       voiddisplay_1()       {           //cout<< "num: " << num << endl;      错误           //cout<< "name: " << name << endl;       错误           //cout<< "sex: " << sex << endl;      错误           cout<< "age: " << age << endl;           cout<< "address: " << addr << endl;       }    private:       intage;       stringaddr;};class Student1: public Student{    public:       voiddisplay_1()       {           cout<< "age: " << age << endl;           cout<< "address: " << addr << endl;       }    private:       intage;       stringaddr;};

5.4.2 私有继承

class Student1: private Student{    public:       voiddisplay_1()       {           cout<< "age: " << age << endl;           cout<< "address: " << addr << endl;       }    private:       intage;       stringaddr;};

不能够通过派生类对象引用从私有基类继承过来的任何成员。

5.4.3 保护成员和保护继承

从类的用户角度来看,保护成员等价于私有成员,但有一点不同,保护成员可以被派生类的成员函数引用。

比较私有继承和保护继承
在直接派生类中,两种继承方式的作用是相同的:在类外不能访问任何成员,而在派生类中可以通过成员函数访问基类中的公用成员和保护成员。如果继续派生(如果是以公用继承的方式派生),原来的私有基类中的成员在在新的派生类中都成为不可访问的成员。//

保护成员和私有成员的不同之处在于把保护成员的访问范围拓展到派生类函数中。

私有继承和保护继承一般不常用。

5.4.4 多级派生时的访问属性

直接派生类、间接派生类、直接基类、间接基类
在多级派生时都采用公用继承的方式,那么直到最后一级派生类都能访问基类的公用成员和保护成员。

5.5 派生类的构造函数和析构函数

希望在在执行派生类构造函数时,使派生类的数据成员和基类的数据成员同时初始化。解决问题的思路是:在执行派生类的构造函数时,调用基类的构造函数。

5.5.1 简单的派生类的构造函数

#include <iostream>#include <string>using namespace std;class Student{    public:       Student(intn, string nam, char s)               // 基类构造函数       {           num= n;           name= nam;           sex= s;       }       ~Student() {}                                 //析构函数    protected:       intnum;       stringname;       charsex;};class Student1: public Student                          // 声明公用派生类Student1{    public:       Student1(intn, string nam, char s, int a, string ad);       voidshow()       {           cout<< "num: " << num << endl;           cout<< "name: " << name << endl;           cout<< "sex: " << sex << endl;           cout<< "age: " << age << endl;           cout<< "address: " << addr << endl;       }       ~Student1(){}    private:       intage;       stringaddr;};Student1::Student1(int n, string nam, char s, inta, string ad):Student(n, nam, s), age(a), addr(ad) {}int main(){    Student1stud1(10010, "Wang_li", 'f', 19, "115 BeijingRoad,Shanghai");    Student1stud2(10011, "Zhang_fun", 'm', 21, "213 Shanghai Road,Beijing");    stud1.show();    stud2.show();    return 0;}

其中重要的一部分是:
声明:
Student1(intn, string nam, char s, int a, string ad);
定义:
Student1::Student1(intn, string nam, char s, int a, string ad):Student(n, nam, s), age(a), addr(ad) {}
也可以这么写:

Student1(int n, string nam, char s, int a, stringad):Student(n, nam, s){    age = a;    addr = ad;}

其一般形式为:

派生类构造函数名(总参数表列):基类构造函数名(参数列表){    派生类中新增数据成员初始化语句;}

基类构造函数名(参数列表)里只有参数名而不包括参数类型,因为在这里不是定义基类构造函数而是调用基类构造函数,因此这些参数是实参而不是形参。

    在类中对派生类构造函数作声明时,不包括基类构造函数名及其参数列表。只在定义函数是才列出。

调用基类构造函数时的实参是从派生类构造函数的总参数列表中得到的,也可以直接使用常量或全局变量。
Student1(string nam, char s, int a, stringad):Student(10010, nam, s)

5.5.2 有子对象的派生类的构造函数

内嵌对象,称为子对象,即对象中的对象。结构体类型的成员还可以是结构体变量。

#include<iostream>#include<string>using namespacestd;class Student{    public:       Student(int n, string nam)       {           num = n;           name = nam;       }       void display()       {           cout << "num: "<< num << endl << "name: " << name <<endl;       }    protected:       int num;       string name;};class Student1:public Student{    public:      Student1(int n, string nam, int nl, stringnam1, int a, string ad):Student(n, nam), monitor(nl, nam1)       {           age = a;           addr = ad;       }       void show()       {           cout << "This Student is:" << endl;           display();           cout << "age: "<< age << endl;           cout << "address: "<< addr << endl << endl;       }       void show_monitor()       {           cout << endl <<"Class monitor is : " << endl;           monitor.display();       }    private:       Student monitor;       int age;       string addr;};int main(){    Student1 stud1(10010, "Wang_Li",10011, "Li_sun", 19, "115 BEIJING");    stud1.show();    stud1.show_monitor();    return 0;}

子对象的初始化是在建立派生类时通过调用派生类构造函数来实现的。派生类构造函数的任务包括三个部分:

  1. 对基类数据成员初始化;
  2. 对对象数据成员初始化;
  3. 对派生类数据成员初始化;
Student1(int n,string nam, int nl, string nam1, int a, string ad):Student(n, nam), monitor(nl,nam1){    age = a;    addr = ad;}

定义派生类构造函数的一般形式为

派生类构造函数名(总参数列表):基类构造函数名(参数列表),子对象名(参数列表){    派生类中新增数据成员初始化语句;}

执行顺序是:基类,子对象,派生类
编译系统是根据相同的参数名而不是参数顺序来确定传递关系的。

5.5.3 多层派生时的构造函数

#include<iostream>#include<string>using namespacestd;class Student{    public:       Student(int n, string nam)       {           num = n;           name = nam;       }       void display()       {           cout << "num: "<< num << endl;           cout << "name: "<< name << endl;       }    protected:       int num;       string name;};class Student1:public Student{    public:       Student1(int n, string nam, inta):Student(n, nam)       {           age = a;       }       void show()       {           display();           cout << "age: "<< age << endl;       }    private:       int age;};class Student2:public Student1{    public:       Student2(int n, string nam, int a, ints):Student1(n, nam, a)       {           score = s;       }       void show_all()       {           show();           cout << "score: "<< score << endl;       }    private:       int score;};int main(){    Student2 stud(10010, "Li", 17,89);    stud.show_all();    return 0;}

注意观察:

基类的构造函数首部:

Student(int n, string nam)

派生类Student1的构造函数首部

Student1(int n, string nam, int a):Student(n, nam)

派生类Student2的构造函数首部

Student2(int n, string nam, int a, int s):Student1(n,nam, a)

只需写出自己直接基类的构造函数即可!!!

5.5.4 派生类构造函数的特殊形式

  1. 当不需要对派生类新增成员初始化时(根本就没有新增的!),派生类构造函数的函数体可以为空,即构造函数是空函数。
  2. 基类中没有定义构造函数或者定义了没有参数的构造函数,那么定义派生类构造函数时可以不写基类构造函数。(派生类构造函数没有向基类构造函数传递参数的任务!无参的基类构造函数也是不需要传递的。)如果基类既定义了无参的构造函数,有定义了有参的构造函数(构造函数重载了。。。),那就随便你传不传了。
  3. 3.

5.5.5 派生类的析构函数

先执行派生类自己的析构函数,再执行子对象的,最后执行基类的。

5.6 多重继承

5.6.1 声明多重继承的方法

class D: public A,private B, protected C{    类D中新增的成员;}

5.6.2 多重继承派生类的构造函数

在初始化函数列表中包含多个基类构造函数

派生类构造函数列表(总参数列表):基类1构造函数(参数列表),基类2构造函数(参数列表),基类3构造函数(参数列表){    派生类中新增的数据成员初始化语句;}

问题:在多重继承时,从不同的基类会继承一些重复的数据。

5.6.3 多重继承引起的二义性问题

A. 两个基类有同名的成员;

class A{    public:       int a;       voiddisplay();};class B{    public:       int a;       voiddisplay();};class C: public A, public B{    public:       int b;       voidshow();};c1.A::a = 3;c1.A::display();

B. 两个基类和派生类三者都有同名成员;

class C: public A, public B{    int a;    voiddisplay();};c1.A::a = 3;c1.A::display();

基类的同名成员在派生类中被屏蔽。
C. 如果类A和类B是从同一个基类派生的

class N{    public:       int a;       voiddisplay()       {           cout<< "A::a = " << a << endl;       }};class A: public A{    public:       int a1;};class B: public B{    public:       int a2;};class C: public A, public B{    public:       int a3;       voidshow()       {           cout<< "a3 = " << a3 << endl;       }};

应当通过类N的直接派生类名来指出要访问的是类N的哪个派生类中的基类成员。

c1.A::a = 3;c1.A::display();

5.6.4 虚基类

  1. 虚基类的作用

C++提供虚基类的方法,使得在继承间接共同基类时只保留一份成员。虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。
声明虚基类的一般形式为:

class 派生类名:virtual继承方式 基类名

应当在该基类的所有直接派生类中声明为虚基类。
2. 虚基类的初始化
在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。

#include<iostream>#include<string>using namespacestd;class Person{    public:       Person(string nam, char s, int a)       {           name = nam;           sex = s;           age = a;       }    protected:       string name;       char sex;       int age;};class Teacher:virtual public Person{    public:       Teacher(string nam, char s, int a, stringt):Person(nam, s, a)       {           title= t;       }    protected:       string title;};class Student:virtual public Person{    public:       Student(string nam, char s, int a, floatsco):Person(nam, s, a), score(sco) {}    protected:       float score;};class Graduate:public Teacher, public Student{    public:       Graduate(string nam, char s, int a,string t, float sco, float w):Person(nam, s, a), Teacher(nam, s, a, t),Student(nam, s, a, sco), wage(w) {}       void show()       {           cout << "name: "<< name << endl;           cout << "age: "<< age << endl;           cout << "sex: "<< sex << endl;           cout << "score: "<< score << endl;           cout << "title: "<< title << endl;           cout << "wage: "<< wage << endl;       }    private:       float wage;};int main(){    Graduate grad1("wang_li", 'f', 24,"assistant", 89.5, 12345.5);    grad1.show();    return 0;}

5.7 基类与派生类的转换

只有公用继承才是基类真正的子类型,它完整地继承了基类的功能。
不同数据类型之间的自动转换和赋值,称为赋值兼容。

  • 派生类对象可以向基类对象赋值;单向的,不可逆的
  • 派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化;
  • 如果函数的实参是基类对象或基类对象的引用,相应的实参可以用子类对象;
  • 指向基类对象的指针变量也可以指向派生类对象;
#include<iostream>#include<string>using namespacestd;class Student{    public:       Student(int, string, float);       void display();    private:       int num;       string name;       float score;};Student::Student(intn, string nam, float s):num(n), name(nam), score(s) {}voidStudent::display(){    cout << endl << "num:" << num << endl;    cout << "name: " <<name << endl;    cout << "score: " <<score << endl;}class Graduate:public Student{    public:       Graduate(int, string, float, float);       void display();    private:       float pay;};Graduate::Graduate(intn, string nam, float s, float p):Student(n, nam, s), pay(p) {}voidGraduate::display(){    Student::display();    cout << "pay = " <<pay << endl;}int main(){    Student stud1(1001, "Li", 87.5);    Graduate grad1(2001, "Wang", 98.5,5635.4);    Student *pt = &stud1;    pt->display();    pt = &grad1;    pt->display();    return 0;}

实际上pt指向的是grad1中从基类继承的部分,通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。

下一章解决的问题:通过使用基类指针调用基类和派生类对象的成员。

5.8 继承与组合

分清楚两个名词:子对象、子类对象
在一个类中以另一个类的对象作为数据成员的现象,称为类的组合。

类的继承:“是”的关系,纵向的
类的组合:“有”的关系,横向的

0 0
原创粉丝点击