主要介绍了类的继承、虚函数、类继承的动态内存分配问题、继承与友元函数。


公有派生

  • 基类的公有成员和私有成员都会成为派生类的一部分。
  • 基类的私有成员只能通过基类的公有或者保护方法访问。但是,基类指针或引用只能用于调用基类方法,不能调用派生类方法。(这种兼容性使得可以用派生类对象来初始化基类对象,也可以将派生类对象赋给基类对象。)
  • 基类的指针或引用可以在不显示类型转换的情况下指向派生类。
  • 派生类的构造函数。
    • 首先会创建基类的对象,派生类的构造函数应通过成员初始化列表将基类信息传递给基类的构造函数。
    • 基类有指针成员或者动态内存分配,则需要定义基类的复制构造函数。
      1
      2
      3
      4
      5
      Point::Point(int tk,const Base &b):Base(b)
      {
      k=tk;
      }
  • 派生类的析构函数

    • 派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数。

多态-动态多态

定义

  • 同一个方法在派生类和基类中的行为是不同的。
  • 方法取决于调用该方法的对象。
  • 前面所学的重载和函数模板是在编译时间便确定的联编,称为静态多态。

重写基类方法

  • 基类的方法可以在派生类中重写 – 使用classname::来说明是基类的还是派生类的。
  • 可在派生类中使用基类名作为限定符调用同名的基类函数。

    1
    2
    3
    4
    5
    void Point::show()
    {
    Base::show();
    cout<<z<<endl;
    }
  • 如果基类中的函数有多个重载,则继承过来的时候不能只重新定义一个版本的,则会导致另外的会被隐藏。

    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
    class 
    {
    public:
    void show(int a)const
    {
    cout<<a<<endl;
    }
    void show()const
    {
    cout<<10<<endl;
    }
    void show(double a)const
    {
    cout<<a<<endl;
    }
    };
    class P : public Base
    {
    public:
    void show(int a)const
    {
    cout<<a+10<<endl;
    }
    /*void show()const
    {
    cout<<20<<endl;
    }
    void show(double a)const
    {
    cout<<a+10<<endl;
    }*/
    };
    int main()
    {
    P a;
    a.show();//invalid
    }

实现多态公有继承的两种方法

  • 在派生类重新定义基类的方法。
  • 使用虚方法。

虚函数

  • 声明前加virtual,定义不用加。
  • 如果方法是通过引用或指针而不是对象调用的,程序将根据引用或指针指向的对象的类型来选择方法。反之如果没用virtual声明,程序直接使用声明的对象的类型的方法。
  • 如果没有加virtual(一般方法),那么程序将根据引用或指针的类型来选择方法。
  • 友元函数不能是虚函数,因为友元函数不是类成员,而只有成员函数才能是虚函数。
  • 可以创建指向基类的指针数组,那么这个数组既可以指向基类,也可以指向派生类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int main() {
    Base b(10, 20);
    Point x(1, 2, 3);
    Base* bs[3];
    bs[0] = new Base(1, 2);
    bs[1] = &x;
    bs[2] = &b;
    rep(i, 0, 3) bs[i]->show(), cout << endl;
    return 0;
    }
    /*
    output:
    大专栏  C++ Primer Plus 学习之 类继承ss="line">1 2
    1 2 3
    10 20
    */
  • 注意:基类需要声明一个虚析构函数,这样做是为了保证在释放对象时,可以调用相应对象类型的析构函数。

  • 没有重新定义
    • 重新定义一个不接受参数的show函数,那么将会隐藏同名基类的方法。
    • 两条经验规则
      • 如果重新定义继承的方法,应确保与原来的原型完全相同。
      • 如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针(这种例外是新出现的)。这种特性被称为返回类型协变。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        class 
        {
        public:
        virtual void show(int a)const;
        ...
        };
        class Point : public Base
        {
        public:
        virtual void show()const;
        ...
        };
        Point tmp;
        tmp.show()//valid
        tmp.show(1);//invalid

静态联编和动态联编

  • 将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编。
  • 函数重载和函数模板C/C++编译器可以在编译过程完成这种联编。在编译过程中进行联编被称为静态联编。
  • 虚函数,因为编译器不知道用户将选择哪种类型的对象。所以编译器必须生成能够在程序运行时选择正确的虚函数方法,这被称为动态联编。
  • C++默认选择为静态联编,因为效率更高。

虚函数的实现原理

  • 给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。
  • 该地址数组称为虚函数表。
  • 对于基类来说,基类对象包含了一个指针,该指针指向基类中所有虚函数的地址表。
  • 对于派生类来说,派生类对象也包含了一个指针,该指针指向派生类中所有虚函数的地址表。
    • 如果派生类没有重新定义某虚函数,则此表保留此虚函数基类版本的地址。
    • 如果派生类重新定义了某虚函数,则此表将更新此虚函数的新地址。
    • 如果派生类增加了新的虚函数,则此表也增加该函数的地址。
  • 不管虚函数有多少个,都只需要在对象里添加一个指针成员。
  • 调用虚函数时:
    • 程序将查看存储在对象中的虚函数表头地址。
    • 然后转身相应的函数地址表。
    • 然后根据该虚函数在类中声明的位置找到其在表中的位置。
    • 然后跳到该地址指向的函数地址,执行函数。

抽象基类(abc)

  • abstract base class
  • 使用纯虚函数提供未实现的函数 – 在声明的结尾处加 =0

    1
    2
    3
    4
    5
    6
    class  {
    private:
    ...;
    public:
    virtual double get_area() = 0;
    };
  • 包含纯虚函数的类只用作基类 ,不能实例化 , 但是能声明(但不初始化)指针。

  • 即ABC必须至少包含一个纯虚函数。
  • 如果在基类中声明了纯虚函数,而派生类中并没有对其定义,则该函数仍为纯虚函数,派生类也为抽象类。
  • 基类中也可以定义(实现)纯虚函数,但在派生类中必需也要定义并且显示地调用(使用类名限定符)。
    • 这样可以将不同子类中公共的事务放在父类中完成。
    • 只有声明而没有定义的纯虚函数派生类是无法调用的。
    • 如果要把基类的析构函数声明为纯虚函数(有时候这么做只是为了说明此类为抽象类),则必须定义这个纯虚析构函数的实现 ,因为派生类析构时会自动调用它。
  • 纯虚函数作为一种“接口约定”, 在基于组件的编程模式中很常见。
    • 派生组件至少会实现基类组件的所有接口(纯虚函数)。

继承和动态内存分配

如果基类使用了动态内存分配 – 即在构造中使用new分配空间

  • 该基类需要声明其构造函数, 析构函数,复制构造,赋值运算符。

如果此时派生类中没有使用new分配的内存

  • 此派生类默认的复制构造会显式地调用基类的复制构造, 同时根据成员变量类型进行复制。
  • 此派生类默认的赋值运算符会显式地调用基类的赋值运算符。
  • 此派生类不需要显示定义构造函数, 析构函数,复制构造,赋值运算符。

如果子类使用new分配的内存

  • 必须为子类定义显式析构函数。
  • 必须为子类定义复制构造函数。
    • 基类的复制构造函数中的参数是基类的引用,所以可以传递进来派生类对象。
      1
      2
      3
      4
      5
      Point::Point(const Point &a):Base(a)	
      {
      z=a.z;
      }
      `
  • 必须为子类重载赋值运算符。

    • 显式调用基类的赋值运算符,以完成基类部分的赋值。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      String& String::operator=(const String& s) {    
      if(this == &s) return *this;
      Base::operator=(s); // 注意此句 -- 必须显示调用基类的赋值运算符
      delete[] str;
      len = s.len;
      str = new char[len+1];
      strcpy(str, s.str);
      return *this;
      }

继承与友元函数

  • 在派生类的友元函数中, 只能访问派生类的派生部分,而不能访问基类的私有成员。
  • 可使用基类的友元函数来负责对派生类的基类部分的访问。
  • 因为友元函数不是成员函数,不能使用作用域解析运算符,因此需要强制类型转换。

C++ Primer Plus 学习之 类继承的更多相关文章

  1. C++ primer(十三)--类继承、构造函数成员初始化、虚函数、抽象基类

    一.基类     从一个类派生出另一个类时,原始类称为基类,继承类称为派生类. 派生类对自身基类的private成员没有访问权限,对基类对象的protected成员没有访问权限,对派生类对象的(基类之 ...

  2. Javascript学习6 - 类、对象、继承

    原文:Javascript学习6 - 类.对象.继承 Javasciprt并不像C++一样支持真正的类,也不是用class关键字来定义类.Javascript定义类也是使用function关键字来完成 ...

  3. iOS学习——iOS 整体框架及类继承框架图

    整理自:IOS 整体框架类图值得收藏 一 整体框架 在iOS开发过程中,对iOS的整理框架的了解和学习是必不可少的一个环节,今天我们就好好来了解一下iOS的整体框架.首先贴一个关于iOS的框架介绍:i ...

  4. C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类

    类继承 在C++类继承中,一个派生类可以从一个基类派生,也可以从多个基类派生. 从一个基类派生的继承称为单继承:从多个基类派生的继承称为多继承. //单继承的定义 class B:public A { ...

  5. Android(java)学习笔记118:类继承的注意事项

    /* 继承的注意事项: A:子类只能继承父类所有非私有的成员(成员方法和成员变量) B:子类不能继承父类的构造方法,但是可以通过super(马上讲)关键字去访问父类构造方法. C:不要为了部分功能而去 ...

  6. 重新开始学习javase_类再生(类的合成和继承)

    一.合成在新类里简单地创建原有类的对象.我们把这种方法叫作“合成” 为进行合成,我们只需在新类里简单地置入对象句柄即可.举个例子来说,假定需要在一个对象里容纳几个 String对象.两种基本数据类型以 ...

  7. C++学习之路—继承与派生(三):多重继承与虚基类

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 多重继承是指一个派生类有两个或多个基类.例如,有 ...

  8. C++学习之路—继承与派生(一):基本概念与基类成员的访问属性

    (本文根据<c++程序设计>(谭浩强)总结而成,整理者:华科小涛@http://www.cnblogs.com/hust-ghtao,转载请注明) 1   基本思想与概念 在传统的程序设计 ...

  9. C++学习之路—继承与派生(二):派生类的构造函数与析构函数

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 由于基类的构造函数和析构函数是不能被继承的,所以 ...

随机推荐

  1. Python笔记_第四篇_高阶编程_py2与py3的区别

    1. 性能: py3.x起始比py2.x效率低,但是py3.x现有极大的优化空间,效率正在追赶. 2. 编码: py3.x原码文件默认使用的utf-8编码,使得变量名更为宽阔. 3. 语法: * 去除 ...

  2. 【收藏】每天更新!全网热门公共BT种子 BitTorrent Tracker 列表合集

    每天更新!全网热门公共 BitTorrent Tracker 列表合集. 该项目仅将全网热门的公共 Tracker 列表制作成合集方便大家使用,无需再一个个导入了~. 「English」(tracke ...

  3. Educational Codeforces Round 80 (Rated for Div. 2)D E

    D枚举子集 题:https://codeforces.com/contest/1288/problem/D题意:给定n个序列,每个序列m个数,求第i个和第j个序列组成b序列,b序列=max(a[i][ ...

  4. Equal Cut

    Equal Cut 题目描述 Snuke has an integer sequence A of length N. He will make three cuts in A and divide ...

  5. sin之舞---蓝桥杯练习

    问题描述 最近FJ为他的奶牛们开设了数学分析课,FJ知道若要学好这门课,必须有一个好的三角函数基本功.所以他准备和奶牛们做一个“Sine之舞”的游戏,寓教于乐,提高奶牛们的计算能力. 不妨设 An=s ...

  6. skip-list(跳表)原理及C++代码实现

    跳表是一个很有意思的数据结构,它实现简单,但是性能又可以和平衡二叉搜索树差不多. 据MIT公开课上教授的讲解,它的想法和纽约地铁有异曲同工之妙,简而言之就是不断地增加“快线”,从而降低时间复杂度. 当 ...

  7. ready vs onload

    1 ready事件:当DOM载入就绪,可以查询,操纵时绑定一个要执行的函数.它可以极大地提高web应用程序的响应速度. 2  onload事件:js中的方法,网页的所有元素.图片全部加载完毕才执行这个 ...

  8. BGP2

    1) 按照拓扑搭建网络,在所有AS间使用直连接口建立EBGP邻居关系: 2) 在公司总部AS400中,R4与R5,R5与R7,R7与R6,R6与R4间使用环回接口建立IBGP邻居关系,IGP协议使用O ...

  9. 第19届亚太零售商大会 | 奇点云CEO行在受邀出席发表演讲

    2019年9月5日—7日,第19届亚太零售商大会在重庆举行. 亚太零售商大会作为世界三大零售盛会之一,是亚太地区零售行业最具规模.最具影响力的零售行业盛会.本次大会以“新零售·新消费·新动力·合作与共 ...

  10. OpenCL介绍

    OpenCL(全称Open Computing Language,开放运算语言)是第一个面向异构系统通用目的并行编程的开放式.免费标准,也是一个统一的编程环境,便于软件开发人员为高性能计算服务器.桌面 ...