阅读耗时大概 10分钟

C++ 面向对象的特性也是分为三种: 封装、继承、多态

类对象

万物皆为对象,对象上有属性和行为

  1. #define PI 3.14
  2. class Circle{
  3. //访问权限 公共权限 都可以访问
  4. public :
  5. int m_r; //半径
  6. double calculate(){
  7. return 2 * PI * m_r;
  8. }
  9. };
  10. int main(){
  11. Circle c1;
  12. c1.m_r = 10;
  13. cout << "圆的周长: " << c1.calculate() << endl;
  14. return 0;
  15. }

m_r 为属性 , calculate 为行为。

访问权限

和java一样,每个类和属性、方法都有其作用域,C++ 中有三种类型,public、private、protected ,范围和java一样。
public 代表都可以访问,private 都不能访问,只有当前类内可以访问,protected 只有当前类内、子类可以访问。默认权限为私有

class Person{
    int love_person;
    private:
        string m_Name;
        int m_Age;
    public:
        void setName(string name){
            m_Name = name;
        }
};
这两个属性就访问不到
只有通过person.setName(xx)才能访问到

class 和 struct的区别

既然有了struct ,再引入class 不就是多余的吗?两者有什么区别?看起来都一样

class ClassTest{
    int m_a;
}
struct StructTest{
    int m_a;
}
int main(){
    ClassTest ct;
    StructTest st;
    ct.m_a = 10;//报错
    st.m_a = 20;//正确
    reutnr 0;
}

区别就在于 class中默认权限是私有的,外部访问不到,而struct 默认权限是公共的。
struct是作为数据结构体的实现体,而class是作为对象的实现体。两者的本质如上,看了公司内部一圈C代码,大多都是使用class, 就是和java类对象一样,在使用struct 的地方,大多和基本数据类型的一些操作相似。

构造函数和析构函数

构造函数和析构函数主要是用于对象的初始化和清理,并只会调用一次
构造函数主要用来初始化赋值等操作
析构函数在对象销毁前会自动调用析构函数,无须手动调用,只会调用一次
析构语法 : ~类名(){}

class Person{
    public:
        Person(){
            cout << "Person构造函数调用 " << endl;
        }
        Person(int a){
            cout << "Person构造函数调用(int a) a = " << a << endl;
        }
        ~Person(){
            cout << "Person析构函数 " << endl;
        }
};
void test01(){
    //栈上操作,方法调用结束后就会被释放,会调用析构函数
    Person p;
    Person(1);
}
int main(){
    test01();
    Person p(3);
    return 0;
}
输出
Person构造函数调用 
Person构造函数调用(int a) a = 1
Person析构函数
Person析构函数
Person构造函数调用(int a) a = 3
Person析构函数

构造函数使用

构造函数分为有参、无参,其中调用方式也是有几种类型,其中需要留意的是拷贝构造函数
调用方式有三种,括号法、显示法、隐式转换法

#include<iostream>
#include<string.h>
using namespace std;
#define PI 3.14
class Person{
    public:
        int m_age;
        Person(){
            cout << "Person()构造函数调用" << endl;
        }
         Person(int age){
             m_age = age;
            cout << "Person(int a)构造函数调用" << age << endl;
        }
        ~Person(){
            cout << "Person析构函数 " << endl;
        }
        //拷贝构造函数 加 const 是为了防止被修改 常量引用
        Person(const Person &p){
            m_age = p.m_age;
            cout << "拷贝构造函数" << endl;
        }
};
void test01(){
    //1.括号
    Person p; //无参
    Person p2(10); //有参
    Person p3(p2); //拷贝函数
    cout << "p2 age = " << p2.m_age << endl;
    cout << "p3 age = " << p3.m_age << endl;

    Person p4();//不能这样写,编译器不会执行,会认为是函数申明

    //2.显示法
    Person p5 = Person(16); 
    Person p6= Person(p5);//拷贝
    cout << "p5 age = " << p5.m_age << endl;
    cout << "p6 age = " << p6.m_age << endl;
    Person(10);//和java一样,匿名内部类,匿名对象,执行完,系统就回收了
    //Person(p5);//错误编译器会认为是对象声明,Person(p5) = Person p5; p5已经存在了

    //3.隐式转换法
    Person p7 = 18; //Person p7 = Person(10);
    Person p8 = p7; //拷贝
}
int main(){
    test01();

    return 0;
}
输出结果:
Person()构造函数调用
Person(int a)构造函数调用10
拷贝构造函数
p2 age = 10
p3 age = 10
Person(int a)构造函数调用16
拷贝构造函数
p5 age = 16
p6 age = 16
Person(int a)构造函数调用10
Person析构函数
Person(int a)构造函数调用18
拷贝构造函数
Person析构函数
Person析构函数
Person析构函数
Person析构函数
Person析构函数
Person析构函数
Person析构函数

建议使用括号法来调用构造函数

拷贝构造函数

调用时机以及调用规则
当你使用一个已经创建完的对象来初始化一个新对象或者是需要返回局部对象时,都可以使用拷贝构造函数

#include<iostream>
#include<string.h>
using namespace std;
#define PI 3.14
class Person{
    public:
        int m_age;
        int * m_height;
        Person(){
            cout << "Person()构造函数调用" << endl;
        }
        Person(int age){
            m_age = age;
            cout << "Person(int a)构造函数调用" << age << endl;
        }
        Person(int age, int height){
            m_age =  age;
            this->m_height = new int(height);
        }
        ~Person(){
            if(m_height != NULL){
                delete m_height;
                m_height = NULL;
            }
            cout << "Person析构函数 " << endl;
        }
        //拷贝构造函数 加 const 是为了防止被修改 常量引用
        Person(const Person &p){
            m_age = p.m_age;
            m_height = p.m_height;
            cout << "拷贝构造函数" << endl;
        }
};
Person doWork2(){
    Person p1;
    return p1; //p1 是局部对象,返回的Person 是走的构造函数搞的副本,所以这里会看到调用拷贝函数 先执行p1的拷贝,再是析构函数
}
void doWork(Person p){
  //会搞一个新的Person副本 , 拷贝构造函数
}
void test01(){
    //1.使用已经创建的对象来初始化一个对象,默认是浅拷贝
    Person p1(20);
    Person p2(p1);

    //2.值传递方式给函数参数传值 
    Person p3;
    doWork(p3);

    //3.值方式返回局部对象
    Person p4 = doWork2();

}
int main(){
    test01();

    return 0;
}
输出结果
Person(int a)构造函数调用20
拷贝构造函数
Person()构造函数调用
拷贝构造函数
Person析构函数
Person()构造函数调用
Person析构函数
Person析构函数
Person析构函数
Person析构函数

上面说的拷贝都是浅拷贝,如何实现深拷贝呢?浅拷贝毕竟不安全,都要拷贝成新的对象了,肯定要用深拷贝。
还是之前的Person类,我们运行以下代码,模拟浅拷贝 m_height 身高。目前Person类的拷贝构造函数和编译器生成的拷贝构造函数实现一致。

int main(){
     Person p1(18,178);
    cout << "p1 年龄 age = " << p1.m_age << " 身高 height = " << *p1.m_height << endl;

    Person p2(p1);
    cout << "p2 年龄 age = " << p2.m_age << " 身高 height = " << *p2.m_height << endl;
    return 0;
}
运行结果:
p1 年龄 age = 18 身高 height = 178
p2 年龄 age = 18 身高 height = 178
Person析构函数
我用vscode run ,编译器没报错,但是程序卡了几秒才结束,只是错误没炮出来。
因为通过拷贝构造后, p1 和 p2 中 的m_height 指向的同一个地址。p1 和 p2 析构时会对同一个地址进行内存释放。导致异常。

浅拷贝带来的问题是数据操作安全、堆区数据重复释放的问题,所以我们需要在拷贝构造函数时做到深拷贝。
所谓深拷贝,就是拷贝出来地址不一样,数据值可能一样。
我们在拷贝构造函数里这样写就可以了

        Person(const Person &p){
            m_age = p.m_age;
            m_height = new int(*p.m_height);
            cout << "拷贝构造函数" << endl;
        }

构造函数生成默认规则

默认情况下,C++编译器会给我们的类添加3个函数,分别是 无参构造函数、无参析构函数、默认拷贝函数(对属性进行拷贝)
如果我们自动了有参构造函数,C++将不提供默认无参构造函数,不影响默认拷贝构造函数。和Java是有差异的。
如果自定义了拷贝构造函数,C++不会提供其他构造函数,包括默认构造函数

构造函数参数快速初始化

构造函数和函数一样,也可以有默认值

public:
    //属性
    int m_a;
    int m_b;
    int m_c;
Person(int a, int b , int c){
    m_a  = a;
    m_b = b;
    m_c = c;
}
//初始化列表
Person(int a, int b, int c):m_a(a), m_b(b),m_C(c){
    //赋值实现不用写
}
Person():m_a(10), m_b(20),m_C(30){

}
Person p; //这里面这 三个值就是默认值 10 20 30
Person p2(20,30,40);

类和内部类的构造和析构顺序

#include <iostream>
#include <string.h>
using namespace std;

class A{
    public:
        A(){
            cout << "A的构造函数" << endl;
        }
        ~A(){
            cout << "A的析构函数" << endl;
        }

};
class B{
    public :
        A a;
        B(){
            cout << "B的构造函数" << endl;
        }
        ~B(){
            cout << "B的析构函数" << endl;
        }
};

int main(){
    B b;
    return 0;
}
输出结果:
A的构造函数
B的构造函数
B的析构函数
A的析构函数

可以看出来先是内部类的构造函数初始化,初始化完后再是自己,销毁的时候,是先由外向内,可以理解是外部触发,先触发外层。构造和析构顺序相反,对称。

类中的静态成员

加了static的成员变量即为静态成员
静态成员变量

  • 全局区,所有对象共享同一份数据
  • 编一阶段分配内存
  • 类内声明,类外初始化

静态成员函数

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量

其实和JAVA等其他语言一模一样

class Person{
    public:
        static void func(){
            m_A = 100;
            //m_B = 200;//报错 不能访问
            cout << "静态成员函数调用" << endl;
        }
        static int m_A;
        int m_B;
};
//类外要初始化
int Person::m_A = 0;
int main(){
    //1.通过对象访问
    Person p;
    p.func(); //这个方法不属于这个对象,属于这个类,建议不要这样调

    //1.1通过类名访问
    Person::func();
    return 0;
 }

类占用空间大小

#include <iostream>
#include <string.h>
using namespace std;

class Person{

};
int main(){
    Person p;
    cout << "p sizeof is " << sizeof(p) << endl;
    return 0;
}
输出结果
p sizeof is 1

空对象占用1个字节
如果有数据呢?

#include <iostream>
#include <string.h>
using namespace std;

class Person{
    int m_A; //加了一个int型数据

};
int main(){
    Person p;
    cout << "p sizeof is " << sizeof(p) << endl;
    return 0;
}
输出结果过
p sizeof is 4

如果m_A是static呢?

#include <iostream>
#include <string.h>
using namespace std;

class Person{
    static int m_A;

};
int main(){
    Person p;
    cout << "p sizeof is " << sizeof(p) << endl;
    return 0;
}
运行结果
p sizeof is 1

如果类中加一个函数呢?

#include <iostream>
#include <string.h>
using namespace std;

class Person{
    public:
        static int m_A;
        void func(){

        }
};
int main(){
    Person p;
    cout << "p sizeof is " << sizeof(p) << endl;
    return 0;
}
运行结果
p sizeof is 1

所以,类占用空间只跟非静态成员变量有关,空对象占一个字节。

利用this实现链式调用

#include <iostream>
#include <string.h>
using namespace std;

class Person{
    public:
        int m_A;
        Person(int a){
            this->m_A = a;
        }
        Person add(Person &p){
            this->m_A += p.m_A; 
            //this指向了p2指针,*this解引用,指的就是p2对象本体
            return *this;   //这里走了默认的拷贝构造函数
        }
};
int main(){
    Person p1(10);
    Person p2(20);
    cout << "p2 = " << &p2 << endl;
    //这个地址已经变了,经过了拷贝函数
    Person p21 = p2.add(p1);
    cout << "p21 = " << &p21 << endl;
    cout << "p2 m_A = " << p2.m_A << endl;
    return 0;
}
输出结果
p2 = 0x61fe18
p21 = 0x61fe14
p2 m_A = 30

上面实现的链式调用,每次经过 add 后,都会返回一个新的对象,这显然是不符合要求的,我们要在同一个对象上链式操作数据。那么如何修改呢?
我们只需要改一下返回值,返回引用就好了,不信,你可以修改run一下,最有p2.add(p1).add(p1)…. 结果将是递增的,通过p2.m_A打印,验证是ok的。你把引用理解成地址也行(引用自身只是别名,没有内存的),最好理解成和 this 一对一绑定的,或者就是this,返回地址。那肯定是同一个咯。

        Person& add(Person &p){
            this->m_A += p.m_A; 
            //this指向了p2指针,*this解引用,指的就是p2对象本体
            return *this;   //这里返回了引用
        }

我运行后发现,数据是对了,但是 p21 和 p2 的地址不应该是一样的吗?疑惑中

int main(){
    Person p1(10);
    Person p2(20);
    cout << "p2 = " << &p2 << endl;

    Person p21 = p2.add(p1).add(p1).add(p1); //这里无论add几个,地址都是0x61fe14
    cout << "p21 = " << &p21 << endl;
    cout << "p2 m_A = " << p2.m_A << endl;
    return 0;
}
结果
p2 = 0x61fe18
p21 = 0x61fe14
p2 m_A = 50

加了一行打印,结果一致,确实是一个对象,问题出现在Person p21 = xxx,这行代码上,Person p21 这是一个新对象,走了构造函数,将p2的值赋值给p21。

cout << "test = " << &p2.add(p1).add(p1) << endl;
结果返回
0x61fe18

空指针访问成员函数

C++空指针是可以调用成员函数,使用时为了健壮性,最好加一些判断

#include <iostream>
#include <string.h>
using namespace std;

class Person{
    public:
        int m_Age;
        void showClassName(){
            cout << "showClassName" << endl;
        }
        void showPerson(){
            //加个判断
            if(this == NULL){
                return;
            }
            cout << "showPerson " << this->m_Age << endl; 
        }
};
int main(){
    Person *p = NULL;
    p->showClassName();
    return 0;
}
运行结果:
showClassName

如果 showPerson 是这样的

     void showPerson(){
            //加个判断
            // if(this == NULL){
            //     return;
            // }
            cout << "showPerson " << this->m_Age << endl;
        }

增加一个调用
p->showPerson();

运行后,结果是
showClassName
showPerson

showPerson 竟然能打印,但是 m_Age 却是空的,我是用vscode 运行的,如果你用visual studio ,这里运行就会报nullptr 读取访问权限冲突。

const修饰成员函数

成员函数后加上const 后我们称为这个函数为常函数
常函数内不可以修改成员属性
但是如果成员属性声明时 加上关键字 mutable后,在常函数中依旧可以修改
声明对象前加上const称该对象为常对象常对象只能调用常函数

class Person{
    public :
    //Person * const this
    //const Person * const this == 下面函数,修饰的是this指针
    void showPerson() const{
        this->m_A = 100; //this指针的本质是指针常量,指针指向不能修改
        this->m_B = 100;//
    }
    int m_A;
    mutable int m_B;
    void func(){
    }
}

const Person *p;
p.showPerson();//ok
p.func();//no ok 常对象不能调用常函数

友元函数

使用场景
在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要到友元的技术
其目的就是让一个函数或者类,访问到另一个类中的私有成员
关键字 friend

全局函数做友元

#include<iostream>
#include<string.h>
using namespace std;

class Building{
    //声明我有那些好朋友
    friend void goodGay(Building *building);
    public:
        Building(){
            m_Data = "公有数据";
            m_private_Data = "私有数据";
        }
    public:
        string m_Data;
    private:
        string m_private_Data;
};
//全局函数
void goodGay(Building *building){
    cout << "好基友全局函数 正在访问 :" << building->m_Data << endl;
    //m_private_Data ,如果在Building类中不添加全局函数是好朋友的定义,将不能访问
    cout << "好基友全局函数 正在访问 :" << building->m_private_Data << endl;
}
int main(){
    Building b;
    goodGay(&b);
    return 0;
}

类做友元

一个类能访问另一个类中的私有成员

class Building{
    //声明 本类的好朋友
    friend class GoodGay;
     public:
        Building();
    public:
        string m_Data;
    private:
        string m_private_Data;
};
//类外也可以写实现
Building::Building(){
     m_Data = "公有数据";
     m_private_Data = "私有数据";
}
class GoodGay{
    public:
        GoodGay();
        void visit();
        Building *building;
}
GoodGay::GoodGay(){
    building = new Building();
}
void GoodGay::visit(){
    cout << "好基友在访问 : " << building->m_Data <<endl;
    //想访问私有属性,同理要在Building类中声明是好基友
    cout << "好基友在访问 : " << building->m_private_Data <<endl;
}
int main(){
    GoodGay gg;
    gg.visit();
    return 0;
}

成员函数做友元

class Building;
class GoodGay{
    public:
        GoodGay();
        Building * building;
        void visit(); //测试访问私有成员
};
class Building {
    //加上作用域GoodGay,否则会认为是全局函数
    friend void GoodGay::visit(); 
    public:
        Building();
    public:
        string m_Data;
    private:
        string m_private_Data;
}
Building::Building(){
    m_Data = "公有数据";
    m_private_Data = "私有数据";
}
GoodGay::GoodGay(){
    building = new Building;
}
void GoodGay::visit(){
    cout << "好基友在访问 : " << building->m_Data <<endl;
    //想访问私有属性,同理要在Building类中声明成员函数是好基友
    cout << "好基友在访问 : " << building->m_private_Data <<endl;
}

运算符重载

通过成员函数重载+ 号

class Person{
    public:
        int m_A;
        int m_B;
    //实现两个对象相加后返回新的对象
    Person add(Person &p){
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;
    }
    //我们通过重载运算符可以这样写
    Person operator+(Person &p){
        Person temp;
        temp.m_A = this->m_A + p.m_A;
        temp.m_B = this->m_B + p.m_B;
        return temp;
    }
};
//调用方法 p1.operator+(p2)简化成
Person p3 = p1 + p2;

通过全局函数重载+、<<号

Person operator+(Person &p1, Person &p2){
    Person temp;
    temp.m_A = p1.m_A + p2.m_A;
    temp.m_B = p1.m_B + p2.m_B;
    reutrn temp;
}
//Person p3 = operator+(p1,p2);
Person p3= p1 + p2;

ostream& operator<<(ostream &cout , Person &p){
    cout << "m_A = " << p.m_A << " p.m_B = " << p.m_B ;
    return cout;
}
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p <<endl;

继承

class Base{
    public:
        void header(){
            cout << "通用顶部" << endl;
        }
        void footer(){
            cout << "通用底部" << endl;
        }
        void content(){
        }
};
//继承
class Java:public Base{
    public:
        void conent(){
            cout << "Java 内容" << endl;
        }
}
int main(){
    Java p;
    p.header();
    p.conetnt();
    p.footer();
    return 0;
}

继承的方式有三种 public、protected、private 继承

#include<iostream>
#include<string.h>
using namespace std;
class Base{
    public: int a;
    protected: int b;
    private : int c;
};
class A:public Base{
    // a 依旧是public 可以访问
    //b依旧是 protected 子类中可以访问,但是c是私有的,访问不了
};
class B:protected Base{
    //a 和 b 都是 protected 子类可以方法
    //c是私有的
};
class C:private Base{
    //都是私有的,访问不了, a和 b 子类可以访问
};
int main(){
    A aClass;
    aClass.a;
    //aClass.b; 报错 note: declared protected here  protected: int b;
    //aClass.c; 报错 note: declared private here  private : int c;
    B bClass;
    //bClass.a;// error: 'int Base::a' is inaccessible within this context bClass.a;
    return 0;
}

总结起来就是
公有public继承,父类私有字段依旧访问不到,父类protected字段依旧子类才可以访问
保护继承,父类public、protected字段仅子类才能访问,私有字段依旧不能访问
私有继承,父类字段,子类都访问不到

继承中的对象模型

从父类继承过来的成员,哪些属于子类对象中?
看下子类大小就清楚了

class Son:public Base{
    public: int d;
};
int main(){
    cout << "sizeof son = " << sizeof(Son) << endl;
    return 0;
}
输出结果:
sizeof son = 16
//Base类去掉 int c 后,输出 12

所以,只要是非静态的,子类就会继承
可以通过VS 开发人员命令提示符工具,在该文件目录下 执行 c1 /d1 reportSingleClassLayout类名 查看对象模型,可自行搜索。

构造和析构的顺序

之前讲过成员属性是对象时的构造和析构顺序,是否还记得?
现在是继承模式下,父类、子类的构造和析构顺序

#include<iostream>
#include<string.h>
using namespace std;
class Base{
    public: int a;
    protected: int b;
    public:
        Base(){
            cout << "Base构造函数!" << endl;
        }
        ~Base(){
            cout << "Base析构函数!" << endl;
        }
 };
class Son:public Base{
    public: int d;
    public:
        Son(){
            cout << "Son构造函数!" << endl;
        }
        ~Son(){
            cout << "Son析构函数!" << endl;
        }
};

int main(){
    Son son;
    return 0;
}
输出结果:
Base构造函数!
Son构造函数!
Son析构函数!
Base析构函数!

继承模式下,构造函数先父类的构造函数再子类的,析构的话,先内后外

子类和父类同名成员

class Base{
    public:
        int m_A;
        Base(){
            m_A = 100;
        }
};
class Son:public Base{
    public :
        int m_A;
        Son(){
            m_A = 200;
        }
};
int main(){
    Son son;
    cout << son.m_A << endl;
    return 0;
}
结果输出
200

同名成员,访问的是子类的。同名函数呢?也是一样的规则,如果想调用父类需要使用作用域 son.Base::func();
还有一个点,如果子类中出现和父类同名的成员函数,子类的同名成员函数会隐藏掉父类的所有同名成员函数,如果父类和子类都有void func()函数,父类还有一个void func(int a),通过子类访问不到func(int a) 这个函数

同名静态成员的访问

静态成员和非静态成员出现同名,处理方式一样
访问子类,可以直接访问
但是访问父类同名成员,需要加作用域

#include<iostream>
#include<string.h>
using namespace std;
class Base{
    public: 
        static int m_A;
 };
int Base::m_A = 200; //类外需要定义
class Son:public Base{

    public: 
        static int m_A;

};
int Son::m_A = 100; //类外需要定义
int main(){
    cout << Son.m_A << endl;
    cout << Son.Base::m_A << endl;
    return 0;
}

多继承

C++中允许一个类继承多个类
语法: class 子类 : 继承方式 父类 1, 继承方式 父类2
多继承中,如果两个父类都有同名属性如何处理,也是加作用域区分
不管是C还是Java都不建议实际开发中,多继承模式下有同名成员属性。

class Base1{
    public: int m_A;
    Base1(){
        m_A  = 100;
    }
};
class Base2{
    public: int m_A;
    Base2(){
        m_A = 200;
    }
};
class Son:public Base1, public Base2{
};
int main(){
    Son son;
    cout << "Base1 m_A " << son.Base1::m_A << endl;
    cout << "Base2 m_A " <<son.Base2::m_A <<endl;
    return 0;
}
输出结果
Base1 m_A 100
Base2 m_A 200

菱形继承

copy网上的例子,动物、羊

//动物
class Animal{
    public:
        int m_Age;
};
//羊类
class Sheep:public Animal{};

//驼类
class Tuo:public Animal{};

//羊驼类
class SheepTuo:public Sheep, public Tuo{
};
int main(){
    SheepTuo st;
    //m_Age 有两份,父类两份都 继承 了该字段,这里加上作用域就能访问了
    st.Sheep::m_Age = 18;
    st.Tuo::m_Age = 20;

    return 0;
}

捕获sheep_tuo.PNG 类的对象模型如图
那么问题来了,SheepTuo ``m_Age应该只需要一份,如何处理。利用虚继承

//羊类
class Sheep : virtual public Animal{};
//驼类
class Tuo:virtual public Animal{};
//调用方法不变
int main(){
    SheepTuo st;
    st.Sheep::m_Age = 18;
    st.Tuo::m_Age = 20;
    cout << st.m_Age << endl;

    return 0;
}
输出结果
20

加了 virtual 关键字后,m_Age 共享一份数据了,对象的模型结构如图
捕获vbpr.PNG
vbptr虚基类表,指向的是下面的 vbtable表,两个vbptr指向了对应的vbtable表,表中记录了偏移量,最后访问的都是 8 位置上的m_Age

多态

多态分为静态多态动态多态
静态多态

  • 函数重载和运算符重载、复用函数名都属于静态多态

动态多态

  • 派生类和虚函数实现运行时多态

两者的区别在于

  • 静态多态的函数地址在编译阶段就确定了
  • 而动态多态的函数地址在运行阶段才确定 ```cpp

    include

    include

    using namespace std;

class Animal{ public: void speak(){ cout << “动物在说话” << endl; } }; class Cat :public Animal{ public: void speak(){ cout << “猫在说话” << endl; } }; //编译阶段确定了函数地址 Animal 早绑定 void doSpeak(Animal &animal){ animal.speak(); } int main(){ Cat cat; doSpeak(cat); return 0; } 输出结果 动物在说话

我们本意是想让猫说话,想让地址在运行时确定,如何处理?
```cpp
class Animal{
    public:
        virtual void speak(){
            cout << "动物在说话" << endl;
        }
};
再次运行
猫在说话

所在在实现多态时,在继承父类后,需要重写父类虚函数

虚函数

我们先看下Animal speak 函数加virtual 后的空间大小

int main(){
    cout << sizeof(Animal) << endl;
}
去掉 virtual 后占1个字节, 加上后占 8个字节(window 64 位系统)

所以加了 virtual后,其实内部多了一个虚函数指针vfptr
vfptr指向vftable表,表中基类了 speak虚函数地址,子类继承后,虚函数vftable表也会继承过来,由于子类重写了虚函数,该vftable表中函数地址替换成子类 speak函数地址。

纯虚函数

纯虚函数有什么用?
在多态里面,通常父类中虚函数的实现都是没有意义的,主要是通过子类来重写内容,所以在父类中这部分内容可以替换成纯虚函数,拥有纯虚函数的类称为抽象类。你可以理解为纯虚函数就是Java里的abstract方法。

class AbstractBase{
    public:
        virtual void func() = 0;//纯虚函数,子类不实现,也属于抽象类
}

虚析构和纯虚析构

也和多态有关
如果子类中有属性开辟到堆区,父类指针在释放时无法调用到子类的析构代码。来看下

#include<iostream>
#include<string.h>
using namespace std;
class Animal{
    public:
        Animal(){
            cout << "Animal 构造" << endl;
        }
        ~Animal(){
            cout << "Animal 析构" << endl;
        }
    public:
        virtual void speak() = 0;
};
class Cat :public Animal{
    public:
        string *m_Name;
        Cat(string name){
            cout << "Cat 构造" << endl;
            m_Name = new string(name);
        }
        ~Cat(){
            if(m_Name != NULL){
                cout<< "Cat 析构 "<< endl;
                delete m_Name;
                m_Name = NULL;
            }
        }
    public:
        void speak(){
            cout << *m_Name <<"说话" << endl;
        }
};
int main(){
    Animal * animal = new Cat("Tom");
    animal->speak();
    delete animal;
    return 0;
}
输出结果
Animal 构造
Cat 构造
Tom说话
Animal 析构

可以看到Cat的析构没有执行!原因就是开头说的,父类指针在析构时,不会调用子类中析构,导致可能会内存泄漏。如何处理呢?
和之前虚函数一样,运行时绑定地址,我们只需要加一个virtual就可以解决

class Animal{
    public:
        Animal(){
            cout << "Animal 构造" << endl;
        }
        virtual ~Animal(){
            cout << "Animal 析构" << endl;
        }
    public:
        virtual void speak() = 0;
};
运行结果
Animal 构造
Cat 构造
Tom说话
Cat 析构
Animal 析构

那什么是纯虚构? 和纯虚函数一样。父类不需要实现,不需要释放内存,我们直接将父类析构改成纯虚构,但是我们定义了纯虚析构,必须要有实现。一般建议使用虚析构就可以了。

class Animal{
    public:
        Animal(){
            cout << "Animal 构造" << endl;
        }
        virtual ~Animal() = 0;
    public:
        virtual void speak() = 0;
};
Animal::~Animal(){
    cout << "纯虚析构" << endl;
}