C++ —— 继承

标签: 继承 多态 虚继承 隐藏
54人阅读 评论(0) 收藏 举报
分类:

C++知识总结目录索引

1. 三种继承关系public、protected和private

  在学习类的时候我们便接触了这三个关键字,当时它们是作为类的成员访问限定符,在继承当中也要用到这三个关键字,此时它们表示的是三种继承方式。对于刚学习继承的人来说,肯定都会有一个疑惑——为什么有了private还有用proected的?它俩有什么区别?

  1. 含有private成员的类:其无论通过哪种继承方式,其子类对象在类的外部和内部都不能访问继承来 private成员。

  2. 含有protected成员的的类:其子类虽然在类外也不能通过子类对象访问父类protected成员,但在子类的类内,通过public和protected这两种继承方式,可以在内部访问到继承而来的父类protected成员;但如果是private继承,那么在子类内部无法访问继承自父类的protected的成员。

继承方式 基类的public成员 基类的protected成员 基类的private成员 继承引起的访问控制关系变化
public继承 仍为public成员 仍为protected成员 不可见 基类的非私有成员在子类的访问属性都不变
protected继承 变为protected成员 变为protected成员 不可见 基类的非私有成员都成为子类的保护成员
private继承 变为private成员 变为private成员 不可见 基类的非私有成员都成为子类的私有成员

< 示例 >

class father {
public:
    char* MyName;    //名字是可以公开的
protected:
    int MyAsset;     //财产是受保护的,只有我和我的继承者才能使用
private:
    char* MyPrivacy; //隐私是保密的,只有我自己能知道
};

class son :public father{ //继承方式:public
public:
    char* MyName;
protected:
    int Asset;
private:
    char* MyPrivacy;

public:
    void Inherit() 
    {
        Asset = MyAsset; //可以在子类中访问父类的protected成员
    }
};

总结:

  1. 基类中的private成员,不管通过何种方式继承,在其子类中均不能被访问。
  2. 某个成员不想被基类对象直接访问,但要在子类中能被访问,就定义成protected成员。
  3. public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
  4. protetced/private继承是一个实现继承,基类的部分成员并未完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
  5. 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但是在子类中不可见(不能访问)。
  6. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
  7. 在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承。

2. 继承与转换–赋值兼容规则(public继承)

  1. 子类对象可以赋值给父类对象(切割/切片,不是强制类型转换)。
  2. 父类对象不能赋值给子类对象。
  3. 父类的指针/引用可以指向子类对象。
  4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成,但有隐患)。
class Human{
public:
    string Name;
protected:
    int Asset;
};

class Student :public Human{
public:
    int num;
};
int main()
{
    Human h;
    Student s; 

    //子类对象可以赋值给父类对象(切割/切片,不是强制类型转换)
    h = s;

    //父类对象不能赋值给子类对象
    //s = h; 

    //父类的指针/引用可以指向子类对象
    Human* h1 = &s;
    Human& r1 = s;

    //子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
    Student* p2 = (Student*)& h;
    Student& r2 = (Student&)h;
}

关于切割:
这里写图片描述


3. 隐藏

隐藏是什么?
答:子类和父类中有同名成员,子类成员将屏蔽对父类成员的直接访问。

关于隐藏:

  1. 如果子类的函数与父类的函数同名,但是参数列表不同。此时,不论有无virtual关键字,父类的函数在子类中都将被隐藏(注意别与重载混淆);

  2. 如果子类的函数与父类的函数同名,并且参数列表也相同,但是父类函数没有virtual关键字。此时,父类的函数在子类中将被隐藏(注意别与覆盖混淆)。

  3. 总结来说:非override(重写)的情况下,派生类对象将屏蔽基类同名函数。

class Base{
public:
    void func(int x);
};

class Derived : public Base{
public:
    void func(char* str);
    //void func(int x){ Base::func(x); } 修改2
};

void Test(void)
{
    Derived* p = new Derived;
    //p->func(10);  //报错
    p->Base::func(10); //修改1
}

  上述代码中,原意是想调用函数Base::func(int),但Base::func(int)已经被Derived::func(char*)隐藏了。可以通过指定类作用域或者修改Derived类来改正。


4. 子类的默认成员函数

  在继承关系里面,在派生类中如果没有显示定义六个成员函数,编译系统则会默认合成这六个默认的成员函数。(1.构造函数、2.拷贝构造函数、3.析构函数、4.赋值操作符重载、5.取地址操作符重载、6.const修饰的取地址操作符重载)

  1. 子类的构造函数(包括拷贝构造函数)应在其初始化列表也只能在其初始化列表里(不能在子类构造函数内调用父类的构造函数)显示地调用父类的构造函数(除非父类的构造函数不可访问)。

  2. 如果父类是多态类,那么必须把父类的析构函数定义为虚函数(后面解释)。

  3. 实现子类的赋值函数时,子类中继承自父类的数据成员可以直接调用父类的赋值函数实现。

  4. 子类的析构函数会隐藏父类的析构函数。(编译器会把所有析构函数名换成destructor,这样就构成了隐藏)参考下下图。

< code >

class Person
{
public :
    Person(const char* name = "")
        : _name(name)
    {
        cout<<"Person()构造" <<endl;
    }

    Person(const Person& p)
        : _name(p ._name)
    {
        cout<<"Person拷贝构造" <<endl;
    }

    Person& operator=(const Person& p )
    {
        cout<<"Person赋值运算符重载"<< endl;

        if (this != &p)
        {
            _name = p ._name;
        }

        return *this ;
    }

    ~Person()
    {
        cout<<"~Person()析构" <<endl;
    }
protected :
    string _name ;     
};

class Student : public Person
{
public:
    Student(const char* name, int num)
        :Person(name)
        ,_num(num)
    {
        cout<<"Student()构造" <<endl;
    }


    Student(const Student& s)
        :Person(s)
        ,_num(s._num)
    {
        cout << "Student()拷贝构造" << endl;
    }

    Student& operator=(const Student& s)
    {
        if (this != &s)
        {
            Person::operator=(s);
            _num = s._num;
        }
        cout << "Student()赋值运算符重载" << endl;
        return *this;
    }

    ~Student()
    {
        cout<<"~Student()析构" <<endl;
    }

protected:
    int _num; 
};

int main()
{
    Student s1("Tianzez", 20);
    cout << endl;

    Student s2(s1);
    cout << endl;

    Student s3("Wangye", 21);
    cout << endl;

    s1 = s3;
    cout << endl;

    system("pause");
    return 0;
}

这里写图片描述

关于子类的析构函数隐藏父类的析构函数:
这里写图片描述


5. 单继承与多继承

  单继承是一般的单一继承,一个子类只 有一个直接父类时称这个继承关系为单继承。这种关系比较简单是一对一的关系:
这里写图片描述

  多继承是指 一个子类有两个或以上直接父类时称这个继承关系为多继承。这种继承方式使一个子类可以继承多个父类的特性。多继承可以看作是单继承的扩展。派生类具有多个基类,派生类与每个基类之间的关系仍可看作是一个单继承。
  多继承下派生类的构造函数与单继承下派生类构造函数相似,它必须同时负责该派生类所有基类构造函数的调用。同时,派生类的参数个数必须包含完成所有基类初始化所需的参数个数。在子类的内存中它们是按照声明定义的顺序存放的,下面的截图将清晰看到。
这里写图片描述

6. 菱形继承 & 虚继承

菱形继承是多继承的特殊情况,这种多继承会带来两个问题:
1. 数据冗余
2. 二义性
这里写图片描述

class Base
{
protected:
    int _base;
public:
    void fun()
    {
        cout << "Base::fun" << endl;
    }
};

class A : public Base
{
protected:
    int _a;
};

class B : public Base
{
protected:
    int _b;
};

class D : public A, public B
{
private:
    int _d;
};

int main()
{
    D d;
    d.fun();//编译器报错:调用不明确  

    system("pause");
    return 0;
}

这里写图片描述

  D的对象模型里面保存了两份Base,当我们想要调用从Base里继承的fun函数时就会出现调用不明确问题,并且会造成数据冗余的问题,明明可以只要一份就好,而我们却保存了两份。

  此时可以域作用限定符调用我们所需的函数:

int main()  
{  
    D d;  
    d.A::fun();  
    d.B::fun();  
    return 0;
}  

但这种写法也是很麻烦的,这时候就需要一个新的解决方案——虚继承

关于虚继承:

  1. 虚继承即让A和B在继承Base时加上virtural关键字,记住不是D继承A、B使用虚继承。

  2. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。

  3. 虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的损耗。

  4. 虚继承和虚函数虽然用的是同一个关键字,但是!但是!但是!它俩之间没半毛钱关系。

class A : virtual public Base
class B : virtual public Base
  使用了虚继承后,调用这句代码d.fun();就能编译通过。但是虚继承是如何实现的呢?它的底层机制又是如何?

  前面说到虚继承是能解决数据冗余问题的,现在我们就来测测非虚继承和虚继承下,D对象的大小。

//虚继承:
cout << sizeof(D) << endl;
//结果是:24

//非虚继承:
cout << sizeof(D) << endl;
//结果是:20

???不是说虚继承可以节省空间吗,这咋空间开销还变大了?那就来看看虚继承情况下,一个D对象的实例内存里面到底存了些什么。

//虚继承情况下:
int main()
{
    D d;
    d._base = 1;
    d._a = 2;
    d._b = 3;
    d._d = 4;

    system("pause");
    return 0;
}

d的内存
这里写图片描述

  我们看到地面确实存的是d._a(2), d._b(3), d._d(4), d._base(1),但是d._a(2)上一行的90 dc af 00以及d._b(3)上一行的b8 dd af 00又是什么呢?可以看出这是两个地址,那就再来看看这两个地址里面存的是啥。
这里写图片描述
这里写图片描述
  一个是数值20,一个是12。这时候看看内存1这张图片,我们发现d._a(2)的地址和 d._base(1)地址之差是20,d._b(3)的地址和 d._base(1)地址之差是12。扯到这,就不继续推了,放大招吧。

  每一个继承自父类对象的实例中都存有一个指针(虚基表指针),这个指针指向指向虚基表的其中一项,里面存的是一个偏移量。对象的实例可以通过自身的地址加上这个偏移量找到存放继承自父类对象的地址。

  写到这也就能理解上面关于虚继承的第三点,为什么说虚继承会带来性能上的损耗,所以一般情况下,尽可能不要定义多继承这种结构。

查看评论

C++继承与多态课件

  • 2010年08月07日 18:59
  • 1.35MB
  • 下载

c++的继承详解

一、前言 继承是c++语言一个重要的机制,该机制自动地为一个类提供来自另一个类的操作和数据结构,这使得程序员只需在新类中定义已有的类中没有的成分来建立一个新类。二、继承解释 继承是类...
  • qq_35644234
  • qq_35644234
  • 2016-10-15 21:12:51
  • 2221

C++继承(一) 常用的继承方式

继承的一些基本知识
  • zyl_1102179268
  • zyl_1102179268
  • 2017-03-07 23:28:53
  • 4379

C++继承部分总结

C++是一门面向对象的语言,所以它的大部分操作都与类和方法息息相关,而C++语言也具有三大特性:封装、继承和多态。刚好我学习到的内容涉及到了继承,为了防止遗忘在此进行一下相关的总结。     继承从字...
  • Exziro
  • Exziro
  • 2017-02-27 16:28:37
  • 905

【c++知识归纳】继承与多态(一)

c++是基于面向对象的编程语言,面向对象的三大特性为:封装、继承和多态。本文将我对继承与多态的理解进行了总结归纳,这部分内容对于学习c++编程语言是非常重要的,文章加入我的个人理解,希望能给大家带来帮...
  • XHfight
  • XHfight
  • 2016-08-24 02:45:19
  • 1810

c++ 继承重要理解

首先,为什么c++需要继承,理解继承到底是什么意思?继承分为哪三种继承方式? 为什么需要继承? 在编写大型程序时,往往有很多的类,每个类都有自己的数据成员和函数,但有些类之间的数据成员和函数却相同,为...
  • xiaoyuxianshenging
  • xiaoyuxianshenging
  • 2017-03-03 11:18:53
  • 2919

C++精进篇(九)之―继承及经典示例

继承是C++的重要属性:         在C++中有:         公有继承(public)、私有继承(private)、保护继承(protected)是常用的三种继承方式。 1. 公有继承(p...
  • fanyun_01
  • fanyun_01
  • 2016-03-26 09:20:59
  • 6514

c++ 继承与静态成员

#include using namespace std; class Base{ public : static int getValue() { ...
  • jianguo_liao19840726
  • jianguo_liao19840726
  • 2013-01-28 18:24:26
  • 1065

关于C++中的继承和重载的区别

C++中的很多特性光从概念上的话,很难做区分。或者说,概念让人容易模糊,比如说函数重载和函数继承。        先说重载,重载分为操作符重载和函数名重载,其中,操作符重载就是对运算操作符的原有功能...
  • helinlin007
  • helinlin007
  • 2016-06-02 18:33:46
  • 7834

lua继承c++类

local MyScrollView =class("MyScrollView",function () local scrollview = cc.ScrollView:create() ...
  • five50
  • five50
  • 2014-02-28 10:36:51
  • 1687
    个人资料
    持之以恒
    等级:
    访问量: 2万+
    积分: 1531
    排名: 3万+
    最新评论