C++学习历程(十二)类的继承详解

一、前言 
继承是c++语言一个重要的机制,该机制自动地为一个类提供来自另一个类的操作和数据结构,这使得程序员只需在新类中定义已有的类中没有的成分来建立一个新类。

二、继承解释 
继承是类的重要特性。A类继承B类,我称B类为“基类”,A为“子类”。A类继承了B类之后,A类就具有了B类的部分成员,具体得到了那些成员,这得由两个方面决定: 
- 继承方式 
- 基类成员的访问权限 
三、三种继承方式

  • **公有继承方式(public)
#include<iostream>
#include<string>
using namespace std;


class Student
{
public :
    Student(string s, int g,int a)
    {
        cout << "Constuct Student" << endl;
        name = s;
        grade = g;
        age = a;
    }
    void print()
    {
        cout << "Student:" << endl;
        cout << "name=" << name << endl;
        cout << "grade=" << grade<<endl;
        cout << "age=" << age << endl;
    }
protected:
    string name;
    int grade;
private:
    int age;
};

class GraduateStudent :public Student                        //继承方式i
{
public:
    GraduateStudent(string s, int g, int a) :Student(s, g, a)   //调用基类的构造函数,构造基类
    {
        cout << "Constuct GraduateStudent" << endl;
    } 
    /*
    公有继承方式,会把基类的公有成员(变量和函数)继承到子类公有成员,保护成员
    变成基类的保护成员,但是私有成员子类也一样不可以访问
    */
    void print1()
    {
        cout << "GraduateStudent:" << endl;                           
        cout << "name= " << name << endl;
        cout << "grade= " << grade << endl;
    }

};

void main()
{
    GraduateStudent g("Ouyang", 95, 21);
    g.print();                     //子类可以直接访问基类公共成员成员
    g.print1();
    system("pause");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

输出结果: 
这里写图片描述 
注意事项: 
- 基类的私有成员,子类不可以访问 
- 基类的保护成员,子类可以继承为自己的保护成员,在派生类可以访问,在外部不可以访问。 
- 基类的公有成员,子类可以继承为自己的公有成员。在派生类可以访问,在外部也可以访问。

  • 保护继承(protected)
#include<iostream>
#include<string>
using namespace std;


class Student
{
public :
    Student(string s, int g,int a)
    {
        cout << "Constuct Student" << endl;
        name = s;
        grade = g;
        age = a;
    }
    void print()
    {
        cout << "Student:" << endl;
        cout << "name=" << name << endl;
        cout << "grade=" << grade<<endl;
        cout << "age=" << age << endl;
    }

    string name;   //公有成员
    int grade;
private:
    int age;
};

class GraduateStudent :protected Student                        //继承方式i
{
public:
    GraduateStudent(string s, int g, int a) :Student(s, g, a)   //调用基类的构造函数,构造基类
    {
        cout << "Constuct GraduateStudent" << endl;
    } 
    /*
    保护继承方式,会把基类的公有成员或者保护成员(变量和函数)变成子类的保护成员,但是私有成员子类也一样不可以访问
    */
    void print1()
    {
        cout << "GraduateStudent:" << endl;                           
        cout << "name= " << name << endl;
        cout << "grade= " << grade << endl;
    }

};

void main()
{
    GraduateStudent g("Ouyang", 95, 21);
    //g.print();   编译出错                  无法直接访问子类的公有成员了
    g.print1();
    system("pause");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

注意事项:

  • 基类公有成员,子类中继承为自己的保护成员,在派生类可以访问,在外部不可以访问

  • 基类保护成员,子类中继承为自己的保护成员,在派生类可以访问,在外部不可以访问

  • 基类私有成员,子类一样不可以访问基类的私有成员。

    私有继承(private) 
    私有继承方式的,就是在继承时,把protected变成private,它需要注意的事项为:

    (1) 基类公有成员,子类中继承为自己的私有成员,在派生类可以访问,在外部不可以访问。 
    (2). 基类保护成员,子类中继承为自己的私有成员,在派生类可以访问,在外部不可以访问。 
    (3) 基类私有成员,子类一样不可以访问基类的私有成员,

    三种继承方式比较 
    从上面的结果来看,私有继承和保护继承作用完全一样。仔细一想其实还是有区别,区别是如果派生类再一次去派生其它类时,对于刚才的私有继承来说,再派生的类将得不到任何成员。而对于刚才的保护继承,仍能够得到基类的公有和保护成员。 
    这里写图片描述

四、派生类的构造 
- 派生类是可以访问基类保护的数据成员,但是还有一些私有数据成员,派生类是无法访问的,并且为提醒类的独立性,我们还是希望通过调用基类的成员函数去初始化这些成员变量,所以派生类是通过调用基类的构造函数,实现对成员变量的初始化。具体代码示例,见上。

五、多态性 
(1):解释多态性: 
函数的多种不同的实现方式即为多态 
(2):必要性 
在继承中,有时候基类的一些函数在派生类中也是有用的,但是功能不够全或者两者的功能实现方式就是不一样的,这个时候就希望重载那个基类的函数,但是为了不再调用这个函数时,出现不知道调用基类的还是子类的情况出现,于是就提出了多态。如果语言不知多态,则不能称为面向对象的。 
(3):多态性是如何实现的 
多态是实现是依赖于虚函数来实现的,之所以虚函数可以分清楚当前调用的函数是基类的还是派生类的,主要在于基类和派生类分别有着自己的虚函数表,再调用虚函数时,它们是通过去虚函数表去对应的函数的。 
具体实现代码如下:

#include<iostream>
using namespace std;


class A
{
public :
     void print()
    {
         cout << "A" << endl;
    }
    virtual void print1()
    {
        cout << "Virtual A" << endl;
    }

};

class B:public A{
public:
    void print() { cout << "B" << endl; }
    virtual void print1() { cout << "Virtual B" << endl; }
};

void fn(A & s)
{
    s.print();
    s.print1();
}
int main()
{

    A a;
    B b;
    fn(a);
    fn(b);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

输出结果: 
这里写图片描述

从图片中,我们可以看出在使用了虚函数之后,程序就只当前该调用哪个类的函数了。其实虚函数表的本质就是一种迟后联编的过程,正常编译都是先期联编的,但是当代码遇到了virtual时,就会把它当做迟后联编,但是为了迟后编译,我么就生成了局部变量–虚函数表,这就增大了一些空间上的消耗。(前提是两个函数的返回类型,参数类型,参数个数都得相同,不然就起不到多态的作用)

#include<iostream>
#include<cmath>
using namespace std;

class A
{
public :
    virtual void fun(int x)
    {
        cout << "A:  " << x << endl;
    }
};
class B :public A
{
public :
    virtual void fun(float x)
    {
        cout << "B: " << x << endl;
    }
};

void test(A & x)
{
    int i = 1;
    x.fun(i);
    float a = 2.0;
    x.fun(a);
}


int main()
{
    A a;
    B b;
    test(a);
    test(b);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

输出结果: 
这里写图片描述

从输出结果可以看出,fun函数并没有起到多态的作用,这里主要是因为基类和派生类的fun在编译时,被看成了两个不同函数,因为他们的参数不同。如果返回的类型不同,也会出现同样的情况,但是有一种特殊的情况,那就是如果基类中虚函数返回一个基类指针或引用,派生类中返回一个派生类的指针或引用,则c++将其视为同名虚函数而进行迟后联编。

#include<iostream>
#include<cmath>
using namespace std;

class A
{
public :
    virtual A * fun()
    {
        cout << "A:  " << endl;
        return this;
    }
};
class B :public A
{
public :
    virtual B * fun()
    {
        cout << "B: " << endl;
        return this;
    }
};

void test(A & x)
{
    x.fun();
}


int main()
{
    A a;
    B b;
    test(a);
    test(b);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

输出结果: 
这里写图片描述

五、使用虚函数的一些限制 
(1):只有类成员函数才能声明为虚函数,这是因为虚函数只适用于有继承关系的类对象中。 
(2):静态成员函数不能说明为虚函数,因为静态成员函数不受限与某个对象,整个内存中只有一个,所以不会出现混淆的情况 
(3):内联函数不可以被继承,因为内联函数是不能子啊运行中动态的确认其位置的。 
(4):构造函数不可以被继承。 
(5):析构函数可以被继承,而且通常声明为虚函数。

六、纯虚函数 
(1):解释 
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载 
纯虚函数的声明有着特殊的语法格式:virtual 返回值类型成员函数名(参数表)=0; 
(2):必要性: 
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。 
(3):抽象类的解释 
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。 
在C++中,抽象类只能用于被继承而不能直接创建对象的类(Abstract Class)。

#include<iostream>
#include<cmath>
using namespace std;

class A
{
public :
    virtual void fun() = 0;
};
class B :public A
{
public :
    virtual void fun()
    {
        cout << "B: " << endl;

    }
};

int main()
{
    B b;
    b.fun();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhanganliu/article/details/80859419
今日推荐