C++ 第四章 多态

多态

C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数
在这里插入图片描述

普通的成员跟着类走
虚函数跟着对象走

//1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。 
//2: 存在虚函数的类中右一个以为的虚函数表焦躁虚函数表,类的对象有一个指向虚函数表开始的虚指针。虚表和类是是对应的,虚表指针是和对象对应的。
//3: 多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性
//4: 多态用虚函数来实现,结合动态绑定. 
//5: 纯虚函数是虚函数再加上 = 0; 
//6: 抽象类是指包括至少一个纯虚函数的类。
   纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数
   一旦某个函数在基类中声明为virtual,那么在所有的派生类中该函数都是virtual,而不需要再显式地声明为virtual。

虚函数

C++允许用户使用虚函数 (virtual function) 来完成 运行时决议 这一操作,这与一般的 编译时决定 有着本质的区别。

虚函数的实现是由两个部分组成的,虚函数指针与虚函数表。

虚函数是实现多态的最关键的手段

有虚函数时,就是调用的相关对象方法;c->run()调用的是Cat中的run方法了;

虚函数是跟着对象走的;如果有是个对象run,那么就会不知道是调用的哪个;

纯虚函数

纯虚函数类似接口;子类中必须得实现的方法;

语义 : 子类中必须得实现得方法
应用场景 : 定义接口

虚函数与纯虚函数区别

虚函数声明后,子类可以不写此方法

纯虚函数声明后,子类必须写此方法

抽象类-{接口类}

在虚函数的后面写上 = 0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象。只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现了接口继承。

class Car{
public:
	//纯虚函数
	virtual void Drive() = 0;
};

class Benz :public Car{
public:
	virtual void Drive(){
		cout << "Benz-舒适" << endl;
	}
};

class BMW :public Car{
public:
	virtual void Drive(){
		cout << "BMW-操控" << endl;
	}
};

void Test(){
	Car* pBenz = new Benz;
	pBenz->Drive();

	Car* pBMW = new BMW;
	pBMW->Drive();
}

int main(){
	Test();
	system("pause");
	return 0;
}

抽象类不可能产生对象的类
抽象类也叫纯虚函数

语义 : 子类肯定会有这个方法,而父类只能说抱歉
应用场景: 定义接口
Animal 即为抽象类, 抽象类不生成对象

VIRTUAL关键字

因为类方法只能跟着类走;
override更加明确的告诉编译器覆盖父亲类的虚函数;起到报错作用.
父类中析构函数必须加virtual

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
using namespace std;

class Animal{
public: 
    virtual void run() {
        cout << "I don't hnow how to run" << endl;
    }
};

class Cat : public Animal {
public :
    void run() override {
        cout << "I can run with four legs" << endl;
    }
};

class Dog : public Animal {
public :
    void run() override {
        cout << "I can run with four legs,yao~ yao~ yao~" << endl;
    }
};

class Bat : public Animal {
public :
    void run() override {
        cout << "I can fly" << endl;
    }
};


class A {
public :
    A() = default;
    A(string s) {
        cout << "param constructor" << endl;
    }
    A(const A &a) {
        cout << "copy constructor" << endl;
    }
    int x;
    virtual void say(int x) {
        cout << this << endl;
        cout << "class A : I can say, x = "<< x <<endl;
    }
};

typedef void (*func)(void *, int);

A retA() {
    A temp_a("hello world");
    return temp_a;
}


class Base{
    public :
    Base() {
        cout << "Base constructor" << endl;
        this->x = new int;
    }
    virtual ~Base() {//父类中析构函数必须加virtual
        cout << "Base destructor" << endl;
        delete[] this->x;
    }
    private:
    int *x;
};

class Base_A : public Base {
    public :
    Base_A() {
        cout << "Base_A constructor" << endl;
        this->y = new int;
    }
    ~Base_A() {
        cout << "Base_A destructor" << endl;
        delete this->y;
    }
    private:
    int *y;
};

int main() {
    Base *ba = new Base_A();
    delete ba;


    cout << sizeof(A) << endl;
    A temp_a, temp_b;
    temp_a.x = 9973;
    temp_b.x = 10000;
    temp_a.say(67);
    A temp_c = retA();
    ((func **)(&temp_a))[0][0](&temp_b, 6);
    srand(time(0));
    Cat a;
    Animal &b = a;
    Animal *c[10];
    cout << sizeof(Cat) << endl;

    for(int i = 0; i < 10; i++) {
        int op = rand() % 3;
        switch(op) {
            case 0:c[i] = new Cat();break;
            case 1:c[i] = new Dog();break;
            case 2:c[i] = new Bat();break;
        }
    }
    for(int i = 0; i < 10; i++) {
        c[i]->run();
    }
    return 0;
}

虚继承&final关键字

虚函数指针

/*虚函数指针 (virtual function pointer) 从本质上来说就只是一个指向函数的指针,与普通的指针并无区别。它指向用户所定义的虚函数,具体是在子类里的实现,当子类调用虚函数的时候,实际上是通过调用该虚函数指针从而找到接口。
虚函数指针是确实存在的数据类型,在一个被实例化的对象中,它总是被存放在该对象的地址首位,这种做法的目的是为了保证运行的快速性。与对象的成员不同,虚函数指针对外部是完全不可见的,除非通过直接访问地址的做法。
 只有拥有虚函数的类才会拥有虚函数指针,每一个虚函数也都会对应一个虚函数指针。所以拥有虚函数的类的所有对象都会因为虚函数产生额外的开销,并且也会在一定程度上降低程序速度。*/
在构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。虚函数表

虚函数表

每一个类的实例化对象都会拥有虚函数指针并且都排列在对象的地址首部。而它们也都是按照一定的顺序组织起来的,从而构成了一种表状结构,称为虚函数表。

1:每一个类都有虚表 2:虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。 3:派生类的虚表中虚地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。 c++的多态性就是通过晚绑定技术来实现的。

class Base{
    public:
    virtual void f(){cout<<"Base::f"<<endl;}
    virtual void g(){cout<<"Base::g"<<endl;}
    virtual void h(){cout<<"Base::h"<<endl;}
};
//首先对于基类Base它的虚函数表记录的只有自己定义的虚函数

在这里插入图片描述

//子类一般覆盖继承,首先是最常见的继承,子类Derived对基类的虚函数进行覆盖继承
class Derived:public Base{
	public:
    virtual void f(){cout<<"Derived::f"<<endl;}
    virtual void g1(){cout<<"Derived::g1"<<endl;}
    virtual void h1(){cout<<"Derived::h1"<<endl;}
}

在这里插入图片描述

当多重继承的时候,表项将会增多,顺序会体现为继承的顺序,并且子函数自己的虚函数将跟在第一个表项后

在这里插入图片描述
在这里插入图片描述

C++中一个类是公用一张虚函数表的,基类有基类的虚函数表,子类是子类的虚函数表,这极大的节省了内存

//实例:
string rand_name(int n) {
    string name = "";
    for (int i = 0; i < n; i++) {
        name = name + (char)(rand() % 26 + 'A');
    }
    return name;
}

class Animal {
public :
    Animal(string name) : name(name) {}
    string tell_me_your_name() { return this->name; }
    virtual void run() {
        cout << "I don't know how to run" << endl;
    }
    virtual void say() = 0;
    virtual void func0() {}
    virtual ~Animal() {
        cout << "Animal destructor" << endl;
    }
private :
    string name;
};

class Cat : public Animal {
public :
    Cat() : Animal(rand_name(5) + " Cat") {}
    void run() override {
        cout << "I can run with four legs" << endl;
    }
    void func0() override {
        cout << "this is funco" << endl;
    }
    void say() override {
        cout << "miao~ miao~ miao~" << endl;
    }
    ~Cat() {
        cout << "Cat destructor" << endl;
    }
};

class Bat : public Animal {
public :
    Bat() : Animal(rand_name(5) + " Bat") {}
    void run() override {
        cout << "I can fly" << endl;
    }
    void say() override {
        cout << "zzz~ zzz~ zzz~" << endl;
    }
    ~Bat() {
        cout << "Bat destructor" << endl;
    }
};

int main() {
    Cat a;
    Bat b;
    cout << a.tell_me_your_name() << endl;
    a.run();
    cout << b.tell_me_your_name() << endl;
    b.run();
    Animal ** arr = new Animal*[10];
    for (int i = 0; i < 10; i++) {
        if (rand() % 2) {
            arr[i] = new Cat();
        } else {
            arr[i] = new Bat();
        }
    }
    for (int i = 0; i < 10; i++) {
        cout << arr[i]->tell_me_your_name() << endl;
        arr[i]->run();
        arr[i]->say();
    }
    for (int i = 0; i < 10; i++) {
        delete arr[i];
    }
    delete[] arr;
    return 0;
}

this指针

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

当我们调用成员函数时,实际上是替某个对象调用它。成员函数通过一个名为 this 的额外隐式参数来访问调用它的那个对象,当我们调用一个成员函数时,用请求该函数的对象地址初始化 this。

//类中用const修饰的函数通常用来防止修改对象的数据成员,函数末尾的const是用来修饰this指针,防止在函数内对数据成员进行修改,而静态函数中是没有this指针的,无法访问到对象的数据成员,与C++ static语义冲突,所以不能。 1.C++中this关键字是一个指向对象自己的一个常量指针,不能给this赋值; 2.只有成员函数才有this指针,友元函数不是类的成员函数,没有this指针; 3.同样静态函数也是没有this指针的,静态函数如同静态变量一样,不属于具体的哪一个对象; 4.this指针作用域在类成员函数内部,在类外也无法获取; 5.this指针并不是对象的一部分,this指针所占的内存大小是不会反应在sizeof操作符上的。

>this指针是什么时候创建的?对象new的过程中创建的
class Animal {
public :
    Animal() {
        x = 8827, y = 65123;
    }
    virtual void say(int x) {
        cout << "I don't know how to say" << endl;
    }
    virtual void run() {
        cout << "I don't know how to run" << endl;
    }
protected :
    int x, y;
};

class Cat : public Animal {
public :
    void say(int x) override {
        cout << this << endl;
        cout << this->x  << " " << this->y << endl;
        cout << x << endl;
        cout << "miao~ miao~ miao~" << endl;
    }
    void run() override {
        cout << "I can run with four legs" << endl;
    }
};

void output_raw_data(void *q, int n) {
    printf("%p : ", q);
    unsigned char *p = (unsigned char *)q;
    for (int i = 0; i < n; i++) {
        printf("%02X ", p[i]);
    }
    printf("\n");
    return ;
}

typedef void (*func)(void *, int x);

int main() {
    Cat a, b;
    output_raw_data(&a, sizeof(a));
    output_raw_data(&b, sizeof(b));
    ((func **)(&a))[0][0](&a, 123);
    return 0;
}

类型转化

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
using namespace std;

class A{
public:
    virtual ~A(){

    }
private:

};

class B:public A{
    public:
    void sayB() {
        cout << "this is class B, x = " << x  << endl;
    }
    int x;
};
class C:public A{
    public :
    void sayC() {
        cout << "this is class C, x = " << x << endl;
    }
    double x;
};
class D:public A{
    public :
    void sayD() {
        cout << "this is class D, x = " << x << endl;
    }
    string x;
};


int my_dynamic_cast(A *ta) {
    char  **pa = (char **)(ta);
    char  **pb = (char **)(new B());
    char  **pc = (char **)(new C());
    char  **pd = (char **)(new D());
    int ret = -1;
    if(pa[0] == pb[0]) ret = 0;
    else if(pa[0] == pc[0]) ret = 1;
    else if(pa[0] == pd[0]) ret = 2;
    return ret;
}

int main() {
    srand(time(0));
    A *pa;
    B *pb;
    C *pc;
    D *pd;
    switch(rand() % 3) {
        case 0 : pb = new B();pa = pb;pb->x = 123;break;
        case 1 : pc = new C();pa = pc;pc->x = 45.6;break;
        case 2 : pd = new D();pa = pd;pd->x = "hello haize";break;
    }
    if((pb = dynamic_cast<B *>(pa))) {
        cout << "Class B : ";
        pb->sayB();
    } else if((pc = dynamic_cast<C *>(pa))) {
        cout << "Class C : ";
        pc->sayC();
    } else if((pd = dynamic_cast<D *>(pa))) {
        cout << "Class D : ";
        pd->sayD();
    }

    switch(my_dynamic_cast(pa)) {
        case 0: ((B *)(pa))->sayB();break;
        case 1: ((C *)(pa))->sayC();break;
        case 2: ((D *)(pa))->sayD();break;
    }
    return 0;
}

在这里插入图片描述

auto关键字

不能用于:

函数参数

模板参数

定义数组

非静态成员变量

如果表达式为数组,且auto带上&,则推导出的类型为数组类型,如:
  int32_t a[3] = {};
  auto& arr = a;
  std::cout <<typeid(arr).name() std::endl; // 这里输出int [3]

constexpr关键字

编译期常量
在这里插入图片描述
在这里插入图片描述12是编译期常量所以合法了
普通变量,修饰函数,构造函数

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
using namespace std;

constexpr int f(int x) {//修饰函数
    if(x == 1) return 1;
    return x * f(x - 1);
}

class A {
    public:
    constexpr A(int x,int y) : x(x), y(y) {}//修饰构造函数
    int x, y;
};

int main() {
    int n;
    cin >> n;
    cout << f(n) << endl;
    constexpr int m = f(12);
    cout << m << endl;
    constexpr A a(2, 3);
    cout << a.x << " "<< a.y << endl;
    return 0;
}

final关键字

防止子类的相关覆盖重写,防止子类的继承

class A :public  map<int, int> {
    public :
    virtual void say() {
        cout << "Class A : hello world" << endl;
    }
};

class B final : public A{
    public : 
    void say() final override{
        cout << "Class B : hello world" << endl;
    }
};

/*
class C : public B{
    public :
    void say() override {
        cout << "Class :hello world" << endl;
    }
};*/

int main() {
    A a;
    a[123] =456;
    a[987] = 46513;
    for(auto x : a) {
        cout << x.first << " " << x.second << endl;
    }
    return 0;
}

nullptr关键字

NULL在C++与C之间的区别

C++中NULL所带来的歧义:func(int), func(int *)

nullptr是空指针更准确的、无歧义的语义表达

nullptr是真正的空地址

NULL是一个强转的0//(void *)0

override关键字

override更加明确的告诉编译器覆盖父亲类的虚函数; 起到报错作用.

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
using namespace std;

int f(int x) {
    cout << "output int value : ";
    cout  << x << endl;
    return 0;
}

int f(int *x) {
    cout << "output address : ";
    cout << x << endl;
    return 0;
}

int main() {
    printf("%lld", (long long)(nullptr));
    //cout << nullptr << endl;
    //cout << NULL << endl;
    int n, *p = &n;
    f(n);
    f(p);
    f(nullptr);//<---- 地址
    f((int)NULL);//0 <---- 当成地址 (void *)0
    return 0;
}

右值引用

在这里插入图片描述

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
#define TEST(a, f) { \
    cout << #a <<  " : " << #f << " "; \
    f(a); \
}

void f2(int &x) {//左值引用
    cout << "left value" << endl;
}

void f2(int &&x) {//右值引用
    cout << "right value" << endl;
}

void f(int &x) {
    cout << "left value" << endl;
    TEST(move(x), f2);
}

void f(int &&x) {
    cout << "right value" << endl;
    //TEST(forward<int &&>(x), f2);//强制将x的属性传递下去
    TEST(move(x), f2);//强制转换为右值传递下去
}

int main() {
    int a, b = 1, c = 3;
    (++a) = b + c;
    cout << a << endl;
    (a++);
    (a = b) = c;
    cout << a << " " << b << endl;
    int arr[10];
    arr[3] = 12;
    (a += 3) = 67;
    cout << a << endl;
    TEST(a += 3, f);
    TEST(1 + 4, f);
    TEST(b + c, f);
    TEST(a++, f);
    TEST(++a, f);
    return 0;
}

移动构造拷贝

    string(const string &s): __length(s.__length){
        cout << "copy constructor" << endl;
        this->__buff_size = s.__buff_size;        
        this->buff = new char[s.__buff_size];
        strcpy(this->buff, s.buff);
    }

    string (string &&s) { //O(1),用于匿名变量直接的拷贝,把要释放的临时空间直接做成需要开辟的空间
        cout<< "move constructor" << endl;
        this->__buff_size = s.__buff_size;
        this->__length = s.__length;
        this->buff = s.buff;
        s.buff = nullptr;
    }
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<map>
#include<vector>
#include<set>
using namespace std;

namespace haizei {

class string {
public :
    string() {
        cout << "string :default constructor, " << this << endl;
        this->__buff_size = 10;
        this->buff = new char[this->__buff_size];
        this->__length = 0;
    }
    string(const char *str) {
        cout << "string :const char constructor, " << this << endl;
        this->__buff_size = strlen(str) + 1;
        this->buff = new char[this->__buff_size];
        strcpy(this->buff, str);
        this->__length = this->__buff_size - 1;
    }

    string(const string &s){
        cout <<"copy constructor : " <<endl;
        this->__buff_size = s.__buff_size;
        this->__length = s.__length;
        this->buff = new char[this->__buff_size];
        strcpy(this->buff, s.buff);
    }
    string(string &&s){//用于匿名变量直接的拷贝,把要释放的临时空间直接做成需要开辟的空间
        cout<< "move constructor" << endl;
        this->__buff_size = s.__buff_size;
        this->__length = s.__length;
        this->buff = s.buff;
        s.buff = nullptr;
    }

    char &at(int ind) {
        if(ind < 0 || ind >= __length) {
            cout << "String Error : out of range" << endl;
            return __end;
        }
        return this->operator[](ind);
    }
    char &operator[](int ind) {
        return buff[ind];
    }
    const char *c_str() const {
        return buff;
    }
    string operator+(const string &s) {
        int size = this->__length + s.__length + 1;
        char *temp = new char[size];
        strcpy(temp, this->buff);
        strcat(temp, s.buff);
        return temp;
    }
    int size() {return this->__length;}

    ~string(){
        cout << "string : destructor, " << this << endl;
        if(this->buff) delete this->buff;
    }


friend istream &operator>>(istream &in, const haizei::string &s);

private:
    int __length, __buff_size;
    char *buff;
    char __end;
};
istream &operator>>(istream &in, const haizei::string &s) {
    in >> s.buff;
    return in;
}

}

ostream &operator<<(ostream &out, const haizei::string &s) {
    out << s.c_str() << endl;
    return out;
}


int main() {
    haizei::string s1 = "hello world", s2 = ", haizei", s3 = "harbin.";
    cout << s1 << endl;
    //cin >> s1;
    cout <<"=====s4.being===="<< endl;
    haizei::string s4 = s1 + s2 + s3;
    haizei::string s5 = s4;
    cout << s4 << endl;
    cout << s5 << endl;
    s4[3] = '=';
    cout << s4 << endl;
    cout << s5 << endl;
    cout <<"====s4.end====" << endl;
    s1[3] = '6';
    cout << s1 << endl;
    cout << s1 + s2 + s3 <<endl;
    for(int i = 0; i < s1.size(); i++) {
       cout << s1[i] << endl; 
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值