一、前语

最近刚好有空,趁这段时刻,温习一下C++言语,进一步夯实根底,为以后的底层开发音视频开发跨渠道开发算法等方向的进一步学习埋下伏笔

咱们在上一篇文章中,现已充分阐明,C++言语是对C的扩展,树立在对C言语常识掌握的根底上学习C++是事半功倍的
假如你对C言语现已淡忘,或许没有学过C言语,且一时半会没有思路怎样挑选牢靠的C言语学习资料,能够学习我的这几篇文章:

1. C言语中心常识

  • 01-温习C言语中心常识|总述
  • 02-温习C言语中心常识|根本语法、数据类型、变量、常量、存储类、根本句子(判别句子、循环句子、go to句子)和运算
  • 03-温习C言语中心常识|函数、效果域规矩、数组、枚举、字符与字符串、指针
  • 04-温习C言语中心常识|结构体、共用体、位域、输入&输出、文件读写
  • 05-温习C言语中心常识|预处理、头文件、强制类型转化、错误处理、递归、内存办理

二、承继和派生

1. 承继概述

1.1 为什么需求承继

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

1.2 承继根本概念

  • C++最重要的特征是代码重用,经过承继机制能够运用 已有的数据类型来界说新的数据类型,新的类不只具有旧类的成员,还具有新界说的成员
  • 一个B类承继于A类,或称从类A派生类B。这样的话,类A成为基类(父类), 类B成为派生类(子类)。
  • 派生类中的成员,包括两大部分:
    • 一类是从基类承继过来的,一类是自己添加的成员。
    • 从基类承继过过来的表现其共性,而新增的成员表现了其个性
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

1.3 派生类界说

  • 派生类界说格局:
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    Class 派生类名 :  承继办法 基类名{
        //派生类新增的数据成员和成员函数
    }
    
  • 三种承继办法:
    • public : 公有承继
    • private : 私有承继
    • protected : 维护承继
  • 从承继源上分:
    • 单承继:
      • 指每个派生类只直接承继了一个基类的特征
    • 多承继:
      • 指多个基类派生出一个派生类的承继联系,多承继的派生类直接承继了不止一个基类的特征

2. 派生类拜访操控

  • 派生类承继基类
    • 派生类具有基类中全部成员变量和成员办法(除了结构和析构之外的成员办法)
    • 可是在派生类中,承继的成员并不一定能直接拜访,不同的承继办法会导致不同的拜访权限
  • 派生类的拜访权限规矩如下:
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 代码示例:
    //基类
    class A{
        public:
            int mA;
        protected:
            int mB;
            private:
            int mC;
    };
    // 公有(public)承继
    class B : public A{
        public:
        void PrintB(){
            cout<< mA << endl; //可拜访基类public特点
            cout<< mB << endl; //可拜访基类protected特点
            //cout << mC <<endl; //不行拜访基类private特点
        }
    };
    class SubB : public B{
        void PrintSubB(){
            cout<< mA << endl; //可拜访基类public特点
            cout<< mB << endl; //可拜访基类protected特点
            //cout << mC <<endl; //不行拜访基类private特点
        }
    };
    void test01(){
        B b;
        cout<< b.mA << endl; //可拜访基类public特点
        //cout << b.mB <<endl; //不行拜访基类protected特点
        //cout << b.mC <<endl; //不行拜访基类private特点
    }
    //2. 私有(private)承继
    class C : private A{
        public:
            void PrintC(){
            cout<< mA << endl; //可拜访基类public特点
            cout<< mB << endl; //可拜访基类protected特点
            //cout << mC <<endl; //不行拜访基类private特点
        }
    };
    class SubC : public C{
        void PrintSubC(){
            //cout << mA <<endl; //不行拜访基类public特点
            //cout << mB <<endl; //不行拜访基类protected特点
            //cout << mC <<endl; //不行拜访基类private特点
        }
    };
    void test02(){
        Cc;
        //cout << c.mA <<endl; //不行拜访基类public特点
        //cout << c.mB <<endl; //不行拜访基类protected特点
        //cout << c.mC <<endl; //不行拜访基类private特点
    }
    //3. 维护(protected)承继
    class D : protected A{
        public:
            void PrintD(){
                cout<< mA << endl; //可拜访基类public特点
                cout<< mB << endl; //可拜访基类protected特点
                //cout << mC <<endl; //不行拜访基类private特点
            }
    };
    class SubD : public D{
        void PrintD(){
            cout<< mA << endl; //可拜访基类public特点
            cout<< mB << endl; //可拜访基类protected特点
            //cout << mC <<endl; //不行拜访基类private特点
        }
    };
    void test03(){
        Dd;
        //cout << d.mA <<endl; //不行拜访基类public特点
        //cout << d.mB <<endl; //不行拜访基类protected特点
        //cout << d.mC <<endl; //不行拜访基类private特点
    }
    

3. 承继中的结构和析构

3.1 承继中的目标模型

在C++编译器的内部能够理解为结构体,子类是由父类成员叠加子类新成员而成:

class Aclass{
    public:
        int mA;
        int mB;
};
class Bclass : public Aclass{
    public:
        int mC;
};
class Cclass : public Bclass{
    public:
        int mD;
};
void test(){
    cout << "A size:" << sizeof(Aclass) << endl;
    cout << "B size:" << sizeof(Bclass) << endl;
    cout << "C size:" << sizeof(Cclass) << endl;
}

3.2 目标结构和析构的调用准则

承继中的结构和析构

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

  • 子类目标在创立时会首要调用父类的结构函数
  • 父类结构函数履行结束后,才会调用子类的结构函数
  • 当父类结构函数有参数时,需求在子类初始化列表(参数列表)中显现调用父类结构函数
  • 析构函数调用顺序和结构函数相反
  • 代码示例:
    class A{
        public:
            A(){
                cout<< "A类结构函数!" << endl;
            }
            ~A(){
                cout<< "A类析构函数!" << endl;
            }
    };
    class B : public A{
        public:
            B(){
                cout<< "B类结构函数!" << endl;
            }
            ~B(){
                cout<< "B类析构函数!" << endl;
            }
    };
    class C : public B{
        public:
            C(){
                cout<< "C类结构函数!" << endl;
            }
            ~C(){
                cout<< "C类析构函数!" << endl;
            }
    };
    void test(){
        C c;
    }
    
  • 承继与组合混搭的结构和析构
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 代码示例:
    class D{
        public:
            D(){
                cout<< "D类结构函数!" << endl;
            }
            ~D(){
                cout<< "D类析构函数!" << endl;
            }
    };
    class A{
        public:
        A(){
            cout<< "A类结构函数!" << endl;
        }
        ~A(){
            cout<< "A类析构函数!" << endl;
        }
    };
    class B : public A{
        public:
            B(){
                cout<< "B类结构函数!" << endl;
            }
            ~B(){
                cout<< "B类析构函数!" << endl;
            }
    };
    class C : public B{
        public:
            C(){
                cout<< "C类结构函数!" << endl;
            }
            ~C(){
                cout<< "C类析构函数!" << endl;
            }
        public:
            D c;
    };
    void test(){
        C c;
    }
    
  • 留意: 假如从头界说了基类中的重载函数,将会产生什么?
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    class Base{
        public:
            void func1(){
                cout<< "Base::void func1()" << endl;
            };
            void func1(int param){
                cout<< "Base::void func1(int param)" << endl;
            }
            void myfunc(){
                cout<< "Base::void myfunc()" << endl;
            }
    };
    class Derived1 : public Base{
        public:
            void myfunc(){
                cout<< "Derived1::void myfunc()" << endl;
            }
    };
    class Derived2 : public Base{
        public:
        //改动成员函数的参数列表
        void func1(int param1, int param2){
            cout<< "Derived2::void func1(int param1,intparam2)" << endl;
        };
    };
    class Derived3 : public Base{
        public:
        //改动成员函数的回来值
        int func1(int param){
            cout<< "Derived3::int func1(intparam)" << endl;
            return 0;
        }
    };
    int main(){
        Derived1derived1;
        derivedfunc1();
        derivedfunc1(20);
        derivedmyfunc();
        cout<< "-------------" << endl;
        Derived2derived2;
        //derived2.func1();  //func1被躲藏
        //derived2.func1(20); //func2被躲藏
        derived2.func1(10,20); //重载func1之后,基类的函数被躲藏
        derived2.myfunc();
        cout<< "-------------" << endl;
        Derived3derived3;
        //derived3.func1();  没有从头界说的重载版别被躲藏
        derived3.func1(20);
        derived3.myfunc();
        return EXIT_SUCCESS;
    }
    
    • Derive1重界说了Base类的myfunc函数,derive1可拜访func1及其重载版别的函数。
    • Derive2经过改动函数参数列表的办法从头界说了基类的func1函数,则从基类中承继来的其他重载版别被躲藏,不行拜访
    • Derive3经过改动函数回来类型的办法从头界说了基类的func1函数,则从基类承继来的没有从头界说的重载版别的函数将被躲藏。

4. 承继中同名成员的处理办法

  • 当子类成员和父类成员同名时,子类依然从父类承继同名成员
  • 假如子类有成员和父类同名,子类拜访其成员默许拜访子类的成员(本效果域,就近准则)
  • 在子类经过效果域::进行同名成员区分(在派生类中运用基类的同名成员,显现运用类名限定符)
  • 代码示例:
        class Base{
            public:
                Base():mParam(0){}
                void Print(){ cout << mParam << endl; }
            public:
                int mParam;
        };
        class Derived : public Base{
            public:
                Derived():mParam(10){}
                void Print(){
                    //在派生类中运用和基类的同名成员,显现运用类名限定符
                    cout<< Base::mParam << endl;
                    cout<< mParam << endl;
                }
                //回来基类重名成员
                int& getBaseParam(){ return  Base::mParam; }
            public:
                int mParam;
        };
        int main(){
            Derived derived;
            //派生类和基类成员特点重名,子类拜访成员默许是子类成员
            cout<< derived.mParam << endl; //10
            derived.Print();
            //类外怎样取得基类重名成员特点
            derived.getBaseParam() = 100;
            cout<< "Base:mParam:" << derived.getBaseParam() << endl;
            return EXIT_SUCCESS;
       }
    

5 非自动承继的函数

  • 不是一切的函数都能自动从基类承继到派生类中
  • 结构函数和析构函数用来处理目标的创立和析构操作,结构和析构函数只知道对它们的特定层次的目标做什么,也便是说结构函数和析构函数不能被承继,有必要为每一个特定的派生类分别创立
  • 另外operator=也不能被承继,因为它完结类似结构函数的行为。也便是说尽管咱们知道怎样由=右边的目标怎样初始化=左面的目标的一切成员,可是这个并不意味着对其派生类依然有效
  • 在承继的过程中,假如没有创立这些函数,编译器会自动生成它们

6. 承继中的静态成员特性

  • 静态成员函数和非静态成员函数的共同点:
      1. 他们都能够被承继到派生类中
      1. 假如从头界说一个静态成员函数,一切在基类中的其他重载函数会被躲藏
      1. 假如咱们改动基类中一个函数的特征,一切运用该函数名的基类版别都会被躲藏。
    • 静态成员函数不能是虚函数(virtual function)
      class Base{
          public:
              static int getNum(){ return sNum; }
              static int getNum(int param){
                  return sNum + param;
              }
          public:
              static int sNum;
      };
      int Base::sNum = 10;
      class Derived : public Base{
          public:
              static int sNum; //基类静态成员特点将被躲藏
          #if 0
          //重界说一个函数,基类中重载的函数被躲藏
          static int getNum(int param1, int param2){
              return sNum + param1 + param2;
          }
          #else
          //改动基类函数的某个特征,回来值或许参数个数,将会躲藏基类重载的函数
          static void getNum(int param1, int param2){
              cout <<  sNum + param1 + param2 << endl;
          }
          #endif
      };
      int Derived::sNum = 20;
      

7. 多承继

7.1 多承继概念

  • 咱们能够从一个类承继,咱们也能够能一起从多个类承继,这便是多承继。
  • 可是因为多承继是十分受争议的,从多个类承继可能会导致函数、变量等同名导致较多的歧义。
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 代码示例:
    class Base1{
        public:
            void func1(){ cout << "Base1::func1" << endl; }
    };
    class Base2{
        public:
        void func1(){ cout << "Base2::func1" << endl; }
        void func2(){ cout << "Base2::func2" << endl; }
    };
    //派生类承继Base1、Base2
    class Derived : public Base1, public Base2{};
    int main(){
        Derived derived;
        //func1是从Base1承继来的仍是从Base2承继来的?
        //derived.func1(); 
        derived.func2();
        //处理歧义:显现指定调用那个基类的func1
        derived.Base1::func1(); 
        derived.Base2::func1();
        return EXIT_SUCCESS;
    }
    
  • 多承继会带来一些二义性的问题, 假如两个基类中有同名的函数或许变量,那么经过派生类目标去拜访这个函数或变量时就不能明确究竟调用从基类1承继的版别仍是从基类2承继的版别?
  • 处理办法便是显现指定调用那个基类的版别。

7.2 菱形承继虚承继

  • 两个派生类承继同一个基类而又有某个类一起承继者两个派生类,这种承继被称为菱形承继,或许钻石型承继
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 这种承继所带来的问题:
      1. 羊承继了动物的数据和函数,鸵同样承继了动物的数据和函数,当草泥马调用函数或许数据时,就会产生二义性。
      1. 草泥马承继自动物的函数和数据承继了两份,其实咱们应该清楚,这份数据咱们只需求一份就能够
        05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    • 上述问题怎样处理?关于调用二义性,那么可经过指定调用那个基类的办法来处理,那么重复承继怎样处理?
    • 关于这种菱形承继所带来的两个问题,c++为咱们供给了一种办法,选用虚基类。那么咱们选用虚基类办法将代码修正如下
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 以上程序Base1 ,Base2选用虚承继办法承继BigBase,那么BigBase被称为虚基类。
  • 经过 虚承继 处理了菱形承继所带来的二义性问题。
  • 可是虚基类是怎样处理二义性的呢?并且derived巨细为12字节,这是怎样回事?
  • 菱形承继|总结
    • 菱形承继带来的问题
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      • 最底下子类从基类承继的成员变量冗余、重复
      • 最底下子类无法拜访基类的成员,有二义性

7.3 虚承继完结原理

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

  • 经过内存图,咱们发现 普通承继虚承继 的目标内存图是不一样的。 咱们也能够猜测到编译器必定对咱们编写的程序做了一些手脚
    • BigBase 菱形最顶层的类,内存布局图没有产生改动。
    • Base1和Base2经过虚承继的办法派生自BigBase
      • 这两个目标的布局图中能够看出编译器为咱们的目标中添加了一个vbptr (virtual base pointer)
      • vbptr指向了一张表,这张表保存了当时的虚指针相关于虚基类的首地址的偏移量。
    • Derived派生于Base1和Base2,承继了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量
    • 由此可知编译器帮咱们做了一些幕后作业
      • 使得这种菱形问题在承继时分能只承继一份数据,并且也处理了二义性的问题。
      • 现在模型就变成了Base1和Base2 Derived三个类目标同享了一份BigBase数据
  • 当运用 虚承继 时,虚基类 是被同享的
    • 也便是在承继系统中不管被承继多少次,目标内存模型中均只会呈现一个虚基类的子目标(这和多承继是彻底不同的)。
  • 即使同享 虚基类 ,可是 有必要要有一个类来完结基类的初始化
    • (因为一切的目标都有必要被初始化,哪怕是默许的)
  • 一起还不能够重复进行初始化,那究竟谁应该担任完结初始化呢?
    • C++标准中挑选在每一次承继子类中都有必要书写初始化句子(因为每一次承继子类可能都会用来界说目标),可是虚基类的初始化是由最终的子类完结,其他的初始化句子都不会调用
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 留意
    • 虚承继只能处理具有公共先人的多承继所带来的二义性问题,不能处理没有公共先人的多承继的.
  • 工程开发中真正意义上的多承继是简直不被运用,因为多重承继带来的代码复杂性远多于其带来的便利,多重承继对代码维护性上的影响是灾难性的,在设计办法上,任何多承继都能够用单承继替代
  • 虚承继|总结
    • 虚承继能够处理菱形承继带来的问题
    • Person类被称为 虚基类
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

8. 承继|总结

  • 承继,能够让子类具有父类的一切成员(变量\函数)
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 联系描述
    • Student是子类(subclass,派生类)
    • Person是父类(superclass,超类)
  • C++中没有像Java、Objective-C的基类
    • Java:java.lang.Object
    • Objective-C:NSObject

9. 父类指针、子类指针

  • 父类指针能够指向子类目标,是安全的,开发中经常用到(承继办法有必要是public)
  • 子类指针指向父类目标是不安全的
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

三、多态

1. 多态根本概念

  • 默许状况下,编译器只会依据指针类型调用对应的函数,不存在多态
  • 多态是面向目标程序设计言语中数据笼统和承继之外的第三个根本特征,是面向目标十分重要的一个特性
    • 同一操作效果于不同的目标,能够有不同的解说,产生不同的履行成果
    • 在运转时,能够识别出真正的目标类型,调用对应子类中的函数
  • 多态的要素
    • 子类重写父类的成员函数(override)
    • 父类指针指向子类目标
    • 运用父类指针调用重写的成员函数
  • 多态性(polymorphism)供给接口与详细完结之间的另一层隔离,从而将”what”和”how”别离开来。
  • 多态性改进了代码的可读性和组织性,一起也使创立的程序具有可扩展性,项目不只在最初创立时期能够扩展,并且当项目在需求有新的功用时也能扩展。
  • C++支撑编译时多态(静态多态)和运转时多态(动态多态)
    • 运算符重载和函数重载便是编译时多态
    • 派生类和虚函数完结运转时多态
  • 静态多态和动态多态的区别
    • 静态多态和动态多态的区别便是函数地址是早绑定(静态联编)仍是晚绑定(动态联编)。
    • 假如函数的调用,在编译阶段就能够确认函数的调用地址,并产生代码,便是静态多态(编译时多态),便是说地址是早绑定的
    • 而假如函数的调用地址不能编译不能在编译期间确认,而需求在运转时才能决议,这这就归于晚绑定(动态多态,运转时多态)
    • 代码示例:
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

2. 向上类型转化及问题

2.1 问题抛出

  • 目标能够作为自己的类或许作为它的基类的目标来运用。还能经过基类的地址来操作它。取一个目标的地址(指针或引证),并将其作为基类的地址来处理,这种称为向上类型转化。
  • 也便是说:父类引证或指针能够指向子类目标,经过父类指针或引证来操作子类目标。
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    • 运转成果: 动物在唱歌
    • 问题抛出: 咱们给DoBussiness传入的目标是dog,而不是animal目标,输出的成果应该是Dog::speak

2.2 问题处理思路

  • 处理这个问题,咱们需求了解下绑定(绑缚,binding)概念。
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 当绑定在程序运转之前(由编译器和连接器)完结时,称为早绑定(early binding).C言语中只要一种函数调用办法,便是早绑定。
  • 上面的问题便是因为早绑定引起的,因为编译器在只要Animal地址时并不知道要调用的正确函数。编译是依据指向目标的指针或引证的类型来挑选函数调用。这个时分因为DoBussiness的参数类型是Animal&,编译器确认了应该调用的speak是Animal::speak的,而不是真正传入的目标Dog::speak
  • 处理办法便是迟绑定(迟绑缚,动态绑定,运转时绑定,latebinding),意味着绑定要依据目标的实践类型,产生在运转。
  • C++言语要完结这种动态绑定,有必要有某种机制来确认运转时目标的类型并调用合适的成员函数。关于一种编译言语,编译器并不知道实践的目标类型(编译器并不知道Animal类型的指针或引证指向的实践的目标类型)。

2.3 问题处理计划(虚函数,vitual function)

  • 虚函数
    • C++中的多态经过虚函数(virtual function)来完结
    • 虚函数:被virtual润饰的成员函数
    • 只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也便是说子类中能够省略virtual关键字)
    • 虚函数允许子类(派生类)从头界说父类(基类)成员函数
    • 而子类(派生类)从头界说父类(基类)虚函数的做法称为掩盖(override),或许称为重写
  • 关于特定的函数进行动态绑定,C++要求在基类中声明这个函数的时分运用virtual关键字,动态绑定也就对virtual函数起效果
    • 为创立一个需求动态绑定的虚成员函数,能够简略在这个函数声明前面加上virtual关键字,界说时分不需求.
    • 假如一个函数在基类中被声明为virtual,那么在一切派生类中它都是virtual的.
    • 在派生类中virtual函数的重界说称为重写(override).
    • Virtual关键字只能润饰成员函数.
    • 结构函数不能为虚函数
  • 留意:
    • 仅需求在基类中声明一个函数为virtual.调用一切匹配基类声明行为的派生类函数都将运用虚机制。
    • 虽然能够在派生类声明前运用关键字virtual(这也是无害的),但这个样会使得程序显得冗余和凌乱。(我主张写上)
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

3. C++怎样完结动态绑定

  • 动态绑定什么时分产生?一切的作业都是由编译器在幕后完结。当咱们告诉经过创立一个virtual函数来告诉编译器要进行动态绑定,那么编译器就会依据动态绑定机制来完结咱们的要求, 不会再履行早绑定

问题:C++的动态绑缚机制是怎样样的?

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

  • 首要,咱们看看编译器怎样处理虚函数
    • 虚表
      • 虚函数的完结原理是虚表,这个虚表里面存储着最终需求调用的虚函数地址,这个虚表也叫虚函数表
        05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
        05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      • 虚表(x86环境的图)
        05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
        • 一切的Cat目标(不管在大局区、栈、堆)共用同一份虚表
      • 虚表汇编剖析
        05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
        05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      • 虚表(x86环境的图)
        05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    • 当编译器发现咱们的类中有虚函数的时分,编译器会创立一张虚函数表,把虚函数的函数进口地址放到虚函数表中,并且在类中隐秘添加一个指针,这个指针便是vpointer(缩写vptr),这个指针是指向目标的虚函数表
    • 在多态调用的时分,依据vptr指针,找到虚函数表来完结动态绑定
  • 在编译阶段,编译器隐秘添加了一个vptr指针,可是此时vptr指针并没有初始化指向虚函数表(vtable),什么时分vptr才会指向虚函数表?
    • 在目标构建的时分,也便是在目标初始化调用结构函数的时分。
    • 编译器首要默许会在咱们所编写的每一个结构函数中,添加一些vptr指针初始化的代码。
    • 假如没有供给结构函数,编译器会供给默许的结构函数,那么就会在默许结构函数里做此项作业,初始化vptr指针,使之指向本目标的 虚函数表
  • 起先,子类承继基类,子类承继了基类的vptr指针,这个vptr指针是指向基类虚函数表,当子类调用结构函数,使得子类的vptr指针指向了子类的 虚函数表

当子类无重写基类虚函数时:

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

  • 过程剖析:
    • Animal*animal = new Dog;
    • animal->fun1();
    • 当程序履行到这里,会去animal指向的空间中寻觅vptr指针,经过vptr指针找到func1函数,此时因为子类并没有重写也便是掩盖基类的func1函数,所以调用func1时,依然调用的是基类的func1.履行成果:
  • 履行成果:
    • 我是基类的func1
  • 测验定论:
    • 无重写基类的虚函数,无意义
  • 多态的建立条件:
    • 有承继
  • 子类重写父类虚函数函数
    • a) 回来值,函数姓名,函数参数,有必要和父类彻底共同(析构函数除外)
    • b) 子类中virtual关键字可写可不写,主张写
    • 类型兼容,父类指针,父类引证 指向 子类目标

4. 笼统基类和纯虚函数(purevirtual function)

  • 在设计时,常常期望基类仅仅作为其派生类的一个接口
  • 这便是说,仅想对基类进行向上类型转化,运用它的接口,而不期望用户实践的创立一个基类的目标
  • 一起创立一个纯虚函数允许接口中放置成员原函数,而不一定要供给一段可能对这个函数毫无意义的代码
  • 做到这点,能够在基类中参加至少一个纯虚函数(pure virtualfunction),使得基类称为笼统类(abstract class).
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    • 纯虚函数运用关键字virtual,并在其后边加上=0。假如企图去实例化一个笼统类,编译器则会阻止这种操作。
    • 当承继一个笼统类的时分,有必要完结一切的纯虚函数,否则由笼统类派生的类也是一个笼统类。
    • Virtual void fun() =0;告诉编译器在vtable中为函数保留一个位置,但在这个特定位置不放地址
  • 纯虚函数|笼统类
    • 纯虚函数:没有函数体且初始化为0的虚函数,用来界说接口标准
    • 笼统类(Abstract Class)
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      • 含有纯虚函数的类,不行以实例化(不行以创立目标)
      • 笼统类也能够包括非纯虚函数、成员变量
      • 假如父类是笼统类,子类没有彻底重写纯虚函数,那么这个子类依然是笼统类

事例: 模板办法形式

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

  • 代码示例:
        //笼统制造饮品
        class AbstractDrinking{
            public:
                //烧水
                virtual void Boil() = 0;
                //冲泡
                virtual void Brew() = 0;
                //倒入杯中
                virtual void PourInCup() = 0;
                //参加辅料
                virtual void PutSomething() = 0;
                //规定流程
                void MakeDrink(){
                    Boil();
                    Brew();
                    PourInCup();
                    PutSomething();
                }
        };
       //制造咖啡
        class Coffee : public AbstractDrinking{
            public:
                //烧水
                virtual void Boil(){
                    cout<< "煮农夫山泉!" << endl;
                }
                //冲泡
                virtual void Brew(){
                    cout<< "冲泡咖啡!" << endl;
                }
                //倒入杯中
                virtual void PourInCup(){
                    cout<< "将咖啡倒入杯中!" << endl;
                }
                //参加辅料
                virtual void PutSomething(){
                    cout<< "参加牛奶!" << endl;
                }
        };
        //制造茶水
        class Tea : public AbstractDrinking{
            public:
                //烧水
                virtual void Boil(){
                    cout<< "煮自来水!" << endl;
                }
                //冲泡
                virtual void Brew(){
                    cout<< "冲泡茶叶!" << endl;
                }
                //倒入杯中
                virtual void PourInCup(){
                    cout<< "将茶水倒入杯中!" << endl;
                }
                //参加辅料
                virtual void PutSomething(){
                    cout<< "参加食盐!" << endl;
                }
        };
        //事务函数
        void DoBussiness(AbstractDrinking* drink){
            drink->MakeDrink();
            delete drink;
        }
        void test(){
            DoBussiness(new Coffee);
            cout<< "--------------" << endl;
            DoBussiness(new Tea);
        }
    

5. 纯虚函数和多承继

  • 多承继带来了一些争议,可是接口承继能够说一种毫无争议的运用了
  • 绝大数面向目标言语都不支撑多承继,可是绝大数面向目标目标言语都支撑接口的概念,C++中没有接口的概念,可是能够经过纯虚函数完结接口
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 多重承继接口不会带来二义性和复杂性问题。接口类只是一个功用声明,并不是功用完结,子类需求依据功用阐明界说功用完结
  • 留意:除了析构函数外,其他声明都是纯虚函数
  • 多承继
    • C++允许一个类能够有多个父类(不主张运用,会添加程序设计复杂度)
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    • 多承继系统下的结构函数调用
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    • 多承继-虚函数
      05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
      • 假如子类承继的多个父类都有虚函数,那么子类目标就会产生对应的多张虚表

6. 虚析构函数

6.1 虚析构函数效果

虚析构函数是为了处理基类的指针指向派生类目标,并用基类的指针删除派生类目标

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

6.2 虚析构函数

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

  • 假如存在父类指针指向子类目标的状况,应该将析构函数声明为虚函数(虚析构函数)
  • delete父类指针时,才会调用子类的析构函数,确保析构的完整性

6.3 纯虚析构函数

  • 纯虚析构函数在c++中是合法的,可是在运用的时分有一个额定的限制:有必要为纯虚析构函数供给一个函数体。
  • 那么问题是:假如给虚析构函数供给函数体了,那怎样还能称作纯虚析构函数呢?
  • 纯虚析构函数和非纯析构函数之间仅有的不同之处在于纯虚析构函数使得基类是笼统类,不能创立基类的目标
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

7. 重写 重载 重界说

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

  • 重载,同一效果域的同名函数
      1. 同一个效果域
      1. 参数个数,参数顺序,参数类型不同
      1. 和函数回来值,没有联系
      1. const也能够作为重载条件 //do(const Teacher&t){} do(Teacher& t)
  • 重界说(躲藏)
      1. 有承继
      1. 子类(派生类)从头界说父类(基类)的同名成员(非virtual函数)
  • 重写(掩盖)
      1. 有承继
      1. 子类(派生类)重写父类(基类)的virtual函数
      1. 函数回来值,函数姓名,函数参数,有必要和基类中的虚函数共同

8. 指向类成员的指针

8.1 指向成员变量的指针

  • 界说格局
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 赋值/初始化
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 解引证
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

8.2 指向成员函数的指针

  • 界说格局
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 赋值/初始化
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 解引证
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
  • 代码示例:
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

8.3 指向静态成员的指针

  • 指向类静态数据成员的指针
    • 指向静态数据成员的指针的界说和运用与普通指针相同,在界说时无须和类相关联,在运用时也无须和详细的目标相关联。
  • 指向类静态成员函数的指针
    • 指向静态成员函数的指针和普通指针相同,在界说时无须和类相关联,在运用时也无须和详细的目标相关联
  • 代码示例:
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

8.4 调用父类的成员函数完结

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

9. 同名函数

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

10. 同名成员变量

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

四、静态成员(static)

  • 静态成员:被static润饰的成员变量\函数
    • 能够经过目标(目标.静态成员)、目标指针(目标指针->静态成员)、类拜访(类名::静态成员)
  • 静态成员变量
    • 存储在数据段(大局区,类似于大局变量),整个程序运转过程中只要一份内存
    • 比照大局变量,它能够设定拜访权限(public、protected、private),达到局部同享的目的
    • 有必要初始化,有必要在类外面初始化,初始化时不能带static,假如类的声明和完结别离(在完结.cpp中初始化)
  • 静态成员函数
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    • 内部不能运用this指针(this指针只能用在非静态成员函数内部)
    • 不能是虚函数(虚函数只能对错静态成员函数)
    • 内部不能拜访非静态成员变量\函数,只能拜访静态成员变量\函数
    • 非静态成员函数内部能够拜访静态成员变量\函数
    • 结构函数、析构函数不能是静态
    • 当声明和完结别离时,完结部分不能带static
  • 静态成员经典运用 – 单例形式
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

五、const成员

  • const成员:被const润饰的成员变量、非静态成员函数
  • const成员变量
    • 有必要初始化(类内部初始化),能够在声明的时分直接初始化赋值
    • 非static的const成员变量还能够在初始化列表中初始化
  • const成员函数(非静态)
    • const关键字写在参数列表后边,函数的声明和完结都有必要带const
    • 内部不能修正非static成员变量
    • 内部只能调用const成员函数、static成员函数
    • 非const成员函数能够调用const成员函数
    • const成员函数和非const成员函数构成重载
    • 非const目标(指针)优先调用非const成员函数
    • const目标(指针)只能调用const成员函数、static成员函数

六、引证类型成员

  • 引证类型成员变量有必要初始化(不考虑static状况)
    05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
    • 在声明的时分直接初始化
    • 经过初始化列表初始化

七、VS的内存窗口

05-C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】

专题系列文章

1. iOS底层原理前常识

  • 01-探求iOS底层原理|总述
  • 02-探求iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
  • 03-探求iOS底层原理|LLDB
  • 04-探求iOS底层原理|ARM64汇编

2. 根据OC言语探索iOS底层原理

  • 05-探求iOS底层原理|OC的实质
  • 06-探求iOS底层原理|OC目标的实质
  • 07-探求iOS底层原理|几种OC目标【实例目标、类目标、元类】、目标的isa指针、superclass、目标的办法调用、Class的底层实质
  • 08-探求iOS底层原理|Category底层结构、App启动时Class与Category装载过程、load 和 initialize 履行、关联目标
  • 09-探求iOS底层原理|KVO
  • 10-探求iOS底层原理|KVC
  • 11-探求iOS底层原理|探索Block的实质|【Block的数据类型(实质)与内存布局、变量捕获、Block的种类、内存办理、Block的润饰符、循环引证】
  • 12-探求iOS底层原理|Runtime1【isa详解、class的结构、办法缓存cache_t】
  • 13-探求iOS底层原理|Runtime2【消息处理(发送、转发)&&动态办法解析、super的实质】
  • 14-探求iOS底层原理|Runtime3【Runtime的相关运用】
  • 15-探求iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
  • 16-探求iOS底层原理|RunLoop的运用
  • 17-探求iOS底层原理|多线程技能的底层原理【GCD源码剖析1:主行列、串行行列&&并行行列、大局并发行列】
  • 18-探求iOS底层原理|多线程技能【GCD源码剖析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
  • 19-探求iOS底层原理|多线程技能【GCD源码剖析2:栅栏函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
  • 20-探求iOS底层原理|多线程技能【GCD源码剖析3:线程调度组dispatch_group、事情源dispatch Source】
  • 21-探求iOS底层原理|多线程技能【线程锁:自旋锁、互斥锁、递归锁】
  • 22-探求iOS底层原理|多线程技能【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
  • 23-探求iOS底层原理|内存办理【Mach-O文件、Tagged Pointer、目标的内存办理、copy、引证计数、weak指针、autorelease

3. 根据Swift言语探索iOS底层原理

关于函数枚举可选项结构体闭包特点办法swift多态原理StringArrayDictionary引证计数MetaData等Swift根本语法和相关的底层原理文章有如下几篇:

  • 01-Swift5常用中心语法|了解Swift【Swift简介、Swift的版别、Swift编译原理】
  • 02-Swift5常用中心语法|根底语法【Playground、常量与变量、常见数据类型、字面量、元组、流程操控、函数、枚举、可选项、guard句子、区间】
  • 03-Swift5常用中心语法|面向目标【闭包、结构体、类、枚举】
  • 04-Swift5常用中心语法|面向目标【特点、inout、类型特点、单例形式、办法、下标、承继、初始化】
  • 05-Swift5常用中心语法|高档语法【可选链、协议、错误处理、泛型、String与Array、高档运算符、扩展、拜访操控、内存办理、字面量、形式匹配】
  • 06-Swift5常用中心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、响应式编程、Swift源码剖析】

4. C言语中心语法

  • 01-温习C言语中心常识|总述
  • 02-温习C言语中心常识|根本语法、数据类型、变量、常量、存储类、根本句子(判别句子、循环句子、go to句子)和运算
  • 03-温习C言语中心常识|函数、效果域规矩、数组、枚举、字符与字符串、指针
  • 04-温习C言语中心常识|结构体、共用体、位域、输入&输出、文件读写
  • 05-温习C言语中心常识|预处理、头文件、强制类型转化、错误处理、递归、内存办理

5. C++中心语法

  • 01-C++中心语法|C++概述【C++简介、C++来源、可移植性和标准、为什么C++会成功、从一个简略的程序开始认识C++】
  • 02-C++中心语法|C++对C的扩展【::效果域运算符、姓名操控、struct类型加强、C/C++中的const、引证(reference)、函数】
  • 03-C++中心语法|面向目标1【 C++编程标准、类和目标、面向目标程序设计事例、目标的结构和析构、C++面向目标模型初探】
  • 04-C++中心语法|面向目标2【友元、内部类与局部类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转化、 C++标准、错误&&反常、智能指针】
  • 05-C++中心语法|面向目标3【 承继和派生、多态、静态成员、const成员、引证类型成员、VS的内存窗口】

6. Vue全家桶

  • 01-Vue全家桶中心常识|Vue根底【Vue概述、Vue根本运用、Vue模板语法、根底事例、Vue常用特性、归纳事例】
  • 02-Vue全家桶中心常识|Vue常用特性【表单操作、自界说指令、核算特点、侦听器、过滤器、生命周期、归纳事例】
  • 03-Vue全家桶中心常识|组件化开发【组件化开发思维、组件注册、Vue调试工具用法、组件间数据交互、组件插槽、根据组件的
  • 04-Vue全家桶中心常识|多线程与网络【前后端交互形式、promise用法、fetch、axios、归纳事例】
  • 05-Vue全家桶中心常识|Vue Router【根本运用、嵌套路由、动态路由匹配、命名路由、编程式导航、根据vue-router的事例】
  • 06-Vue全家桶中心常识|前端工程化【模块化相关标准、webpack、Vue 单文件组件、Vue 脚手架、Element-UI 的根本运用】
  • 07-Vue全家桶中心常识|Vuex【Vuex的根本运用、Vuex中的中心特性、vuex事例】

7. 音视频技能中心常识

  • 01-音视频技能中心常识|了解音频技能【移动通信技能的发展、声响的实质、深入了解音频】
  • 02-音视频技能中心常识|建立开发环境【FFmpeg与Qt、Windows开发环境建立、Mac开发环境建立、Qt开发根底】
  • 03-音视频技能中心常识|Qt开发根底【.pro文件的配置、Qt控件根底、信号与槽】
  • 04-音视频技能中心常识|音频录制【命令行、C++编程】
  • 05-音视频技能中心常识|音频播映【播映PCM、WAV、PCM转WAV、PCM转WAV、播映WAV】
  • 06-音视频技能中心常识|音频重采样【音频重采样简介、用命令行进行重采样、经过编程重采样】
  • 07-音视频技能中心常识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】
  • 08-音视频技能中心常识|成像技能【重识图片、详解YUV、视频录制、显现BMP图片、显现YUV图片】
  • 09-音视频技能中心常识|视频编码解码【了解H.264编码、H.264编码、H.264编码解码】
  • 10-音视频技能中心常识|RTMP服务器建立【流媒体、服务器环境】

其它底层原理专题

1. 底层原理相关专题

  • 01-核算机原理|核算机图形烘托原理这篇文章
  • 02-核算机原理|移动终端屏幕成像与卡顿

2. iOS相关专题

  • 01-iOS底层原理|iOS的各个烘托框架以及iOS图层烘托原理
  • 02-iOS底层原理|iOS动画烘托原理
  • 03-iOS底层原理|iOS OffScreen Rendering 离屏烘托原理
  • 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和处理计划

3. webApp相关专题

  • 01-Web和类RN大前端的烘托原理

4. 跨渠道开发计划相关专题

  • 01-Flutter页面烘托原理

5. 阶段性总结:Native、WebApp、跨渠道开发三种计划功能比较

  • 01-Native、WebApp、跨渠道开发三种计划功能比较

6. Android、HarmonyOS页面烘托专题

  • 01-Android页面烘托原理
  • 02-HarmonyOS页面烘托原理 (待输出)

7. 小程序页面烘托专题

  • 01-小程序框架烘托原理