Cpp-1.15-继承和多态

Cpp-1.15-继承和多态

继承的概念和定义

继承:

在面向对象编程(Object - Oriented Programming,简称 OOP)中,继承是一种机制,它允许创建一个新的类(称为子类或派生类),这个子类可以继承现有类(称为父类或基类)的属性(包括变量和数据成员)和方法(函数成员)。子类能够在继承的基础上添加新的属性和方法,或者修改从父类继承来的属性和方法的行为。

//基类父类
class Person{
public:
    void print(){
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    strinf _name = "peter";
    int _age = 18;
};

//公有继承
class Student : public Person{
    protected:
    	int _stuid;//学号
};

class Teacher : public Person{
    protected:
    	int _jodid;//工号
};

//class默认继承方式为私有
class Student : Person{
    protected:
    	int _stuid;//学号
};

//struct默认继承方式为公有
struct Student : Person{
    protected:
    	int _stuid;//学号
};

继承即继承父类有的成员

继承基类成员访问方式的变化:

类成员/继承方式 public继承 protected继承 private继承
基类的public成员 派生类的public成员 派生类的protected成员 派生类的private成员
基类的protected成员 派生类的protected成员 派生类的protected成员 派生类的private成员
基类的private成员 派生类种不可见 派生类种不可见 派生类种不可见

权限:公有 > 保护 > 私有

即:在继承时,基类的成员在子类的访问方式为继承方式和成员在基类的访问限定符之较小者,值得一提的,对于基类的private成员,子类不可见。对于基类的protect成员,子类可见。在实际应用中,一般都是用public继承

基类和派生类的赋值兼容转换:

int main(){
    
    double d = 1.1;
    int i = d;//隐式类型转换
    const int& ri = d;
    //double和int为相似类型,可以直接赋值。
    //中间会产生一个**具有常性**的临时变量。
    
    
    Student s;
    Person p = s;//天然支持,可直接赋值,不存在类型转换,但会调用拷贝构造。
    Preson& rp = s;//rp为子类种父类部分的别名。
    Person* ptrp = &s//ptrp指向子类种父类部分。
    
    //父类不可以为子类赋值
}

继承中的作用域:

//父类和子类都有独立的作用域
class Person{
    string _name;
    int _num = 111;
};

class Student : public Person{
public:
    void func(){
        cout << _num << endl;//默认访问派生类
        cout << Person::_num << endl;//使用与作用限定符访问父类
    }
protected:
    string _name;
}


  1. 子类和父类都有独立的作用域
  2. 子类和父类种有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义
  3. 只要函数名相同就构成隐藏,不考虑函数参数。(建议:不要定义同名函数)

派生类的默认成员函数:

class Student : public Person{
public:
    //构造函数
    Student(const char* name, int num)
        :Person(name)
        , _num(num)
        {}
    //拷贝构造
    Student(const Student& s)
        :Person(s)//切片
        , _num(s._sum)
    {}
    
    //复制函数
    Student& operator=(const Student& s){
        if(this != &s){
            Person::operator=(s);
            num = s.sum;
        }
        return* this;
    }

//析构函数会被处理成destructor
    ~Student(){
        //person::~Person();
    }
//构造顺序:先父后子
//析构顺序:先子后父
protected:
    int _num;
}
  1. 父类的成员必须调用父类的构造函数初始化。
  2. 构造顺序先父后子,析构顺序为先子后父。

继承和友元

友元关系和静态成员变量(属于整个类)不能被继承

实现一个不能被继承的类:

class A{
private:
	A()
	{}
    ~A()
    {}
};

class B : public A
{};//B类无法继承

//如何调用A?
class A{
    
public:
    static A CreatObj(){
        return A();
    }
private:
	A()
	{}
    ~A()
    {}
};


多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。

菱形继承:菱形继承是多继承的一种特殊情况。

菱形继承会导致数据冗余和二义性。解决方式:虚继承。

继承的耦合度高于组合

多态

多态的概念:

在不同继承关系的类对象,去调用同一函数产生了不同行为。

相比于普通函数的实现继承,多态的继承是接口继承

虚函数:

以virtual开头(与虚继承的virtual标识符无关),使得父类和子类之间出现重写/覆盖。

class Person{
public:
    virtual void BuyTicket(){ cout << "买票-全价" << endl; }
};

class Studen: public Person{
public:
    //重写/覆盖
    virtual void BuyTicket(){ cout << "买票-半价" << endl;}
};

int main{
......
}

多态的条件:

  1. 虚函数的重写:三同(函数名、参数、返回值)

    ——例外(协变):返回值可以不同,必须是父类的指针或者引用,子类虚函数可以不加virtual

  2. 父类指针或者引用的调用。

PS.多态的一个特性:
//运行以下代码,会出现由于无法释放Student中的非Person成员变量导致的内存泄漏
class Person {
public:
     ~Person() {
        cout << "~Person()" << endl;
    }
};

class Student : public Person{
public:
    ~Student() {
        cout << "~Student() "<< endl;
    }
};
int main() {
    Person* a = new(Person);
    Person* b = new(Student);

    delete a;
    delete b;

    return 0;
}


//以下是利用虚函数改良后的代码
class Person {
public:
     virtual ~Person() {
        cout << "~Person()" << endl;
    }
};

class Student : public Person{
public:
    ~Student() {
        cout << "~Student() "<< endl;
    }
};
int main() {
    Person* a = new(Person);
    Person* b = new(Student);

    delete a;
    delete b;

    return 0;
}

抽象类:

包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。

class Car{
public:
    virtual void Drive() = 0;
};

int main(){
    
    Car car;//不能实例化。
    
    return 0;
}

//一个类在现实中没有对应的实体,我们就可以将其定义为抽象类。
//纯虚函数的存在强制子类重写

纯虚函数和override的区别

  • 一个在父类,是间接强制重写。
  • 一个在子类,是检查重写。

多态的原理:

多态如何做到指针指向父类对象时调用父类虚函数,指针指向子类对象时调用子类虚函数?

当指针指向父类对象时,通过父类对象的 vptr 找到父类虚函数表,调用父类虚函数。当指针指向子类对象时,通过子类对象的 vptr 找到子类虚函数表,调用子类虚函数。这是因为对象的 vptr 会根据对象的实际类型(父类或子类)指向相应的虚函数表。

  • 虚函数表(虚表):一个虚函数指针数组(顺序为声明顺序),同类的对象共用一张虚表。
  • 虚函数表指针:指向虚函数。
  • 覆盖的过程:本质是在派生类产生时覆盖构成重写的虚函数。

为什么实现多态需要指针传递?

用对象传递会发生对象切片(对象切片不会将子类虚表拷贝给父类)。

虚表生成的阶段?

编译。

对象中的虚表指针初始化的阶段?

构造函数的初始化列表阶段。

虚表存在哪里?

常量区(一般)。

(拓展)用程序打印虚表:
typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* table){
    int i = 0;
    for(i = 0; i table[i] != nullptr/* VS编译器在虚函数表的结尾放置了空指针*/; ++i){
        printf("[%d]:%p->\n", i, table[i]);
        VF_PTR f = table[i];
        f();//假设虚函数功能为打印自身虚函数的名字。
    }
}

int main(){
    Base b;
    Derive d;
    PrintVFTable((VF_PTR*)(*(int*)&b))//仅适用于16字节。
    PrintVFTable(*(VF_PTR**)&b);//更优解法。
    return 0;
}

多继承的虚函数表:

如何用程序判断多继承的虚函数表如何存放?

typedef void(*VF_PTR)();
void PrintVFTable(VF_PTR* table){
    int i = 0;
    for(i = 0; i table[i] != nullptr/* VS编译器在虚函数表的结尾放置了空指针*/; ++i){
        printf("[%d]:%p->\n", i, table[i]);
        VF_PTR f = table[i];
        f();//假设虚函数功能为打印自身虚函数的名字。
    }
}

int main(){
    Base1 base1;
    Base2 base2;
    Derive derive;
    PrintVFTable((VF_PTR*)(*(int*)&derive));//base1的虚函数表。
    PrintVFTable((VF_PTR*)(*(int*)((char*)&derive + sizeof(base1))));//base2的虚函数表。
	//也可利用多继承的指针偏移实现打印base2指针。
    base2* ptr2 = &derive;
    PrintVFTable((VF_PTR*)(*(int*)&derive));
    return 0;
}

多继承时派生类增加的虚函数存放在第一张虚表中!

为什么多继承时重写的函数会出现不同表的同一函数地址不同?

第二次调用需要修正this指针。

对于菱形虚拟继承时:D类只有覆盖B类和C类继承的A类的虚表,程序才能编译通过,若D有自己单独的虚函数,则它有三张虚表,两张虚基表。

你可能感兴趣的:(C++,c++)