C++ Elementary--Polymorphism

m0_71321720 2023-01-25 20:53:06 阅读数:97

c++elementary--polymorphismelementarypolymorphism

目录

1.多态的概念

2.多态的定义和实现

        2.1多态的构成条件

        2.2虚函数

        2.3重写(覆盖)

3.派生类的析构函数

4.override和final

5.重载、覆盖(重写)、隐藏(重定义)的对比

6.抽象类

7.接口继承和实现继承

8.多态的原理

        8.1虚函数表

        8.2打印虚函数表中内容


1.多态的概念

        多态:多种形态,different objects to accomplish the same thing,结果不同.

2.多态的定义和实现

2.1多态的构成条件

1.The virtual function must be called through a base class reference or pointer

2.被调用的函数必须是虚函数,And the derived class must rewrite the virtual function of the base class

2.2虚函数

        被virtualDecorated class non-static member function

2.3重写(覆盖)

        派生类中有一个跟基类完全相同的虚函数(返回值类型、函数名、参数列表完全相同),称子类的虚函数重写了基类的虚函数.
//多态:多种形态
//静态的多态:函数重载,看起来调用同一个函数有不同行为.静态:The principle is that compile-time
//动态的多态:一个父类的引用或指针去调用同一个函数,传递不同的对象,会调用不同的函数.动态:原理是运行时实现
//本质:不同人去做同一件事情,结果不同
class Person
{
public:
virtual void BuyTicket()
{
cout << "买票-全价" << endl;
}
};
class Student : public Person
{
public:
//子类中满足三同(函数名、参数、返回值)(协变除外) 、 虚函数, 叫做 重写(覆盖)
virtual void BuyTicket()
{
cout << "买票-半价" << endl;
}
};
//构成多态,跟p的类型没有关系,传的哪个类型的对象,调用的就是这个类型的虚函数 -- 跟对象有关
//不构成多态,调用的就是p类型的函数 -- 跟类型有关
void Func(Person& p)
{
p.BuyTicket(); //多态
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}

        重写要求返回值相同有一个例外:协变--The return value is required to be a reference or pointer to the parent-child relationship

//重写要求返回值相同有一个例外:协变--要求返回值是父子关系的指针或引用
class A{};
class B : public A{};
class Person
{
public:
virtual A* BuyTicket()
{
cout << "买票-全价" << endl;
return nullptr;
}
};
class Student : public Person
{
public:
virtual B* BuyTicket()
{
cout << "买票-半价" << endl;
return nullptr;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}

3.派生类的析构函数

        动态申请的父子对象,如果给了父类指针管理,Then the destructor needs to be a virtual function to complete the rewriting,构成多态,那么才能正确调用析构函数.析构函数名被特殊处理了,处理成了destructor.

//析构函数名被特殊处理了,处理成了destructor
class Person
{
public:
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
public:
virtual ~Student()
{
cout << "~Student()" << endl;
}
};
int main()
{
//普通对象,析构函数是否虚函数,是否完成重写,都正确调用了
//Person p;
//Student s;
//动态申请的父子对象,如果给了父类指针管理,Then the destructor needs to be a virtual function to complete the rewriting,构成多态,那么才能正确调用析构函数
Person* p1 = new Person; //operator new + 构造函数
Person* p2 = new Student;
//析构函数 + operator delete
delete p1; //p1->destructor()
delete p2; //p2->destructor()
return 0;
}

        虚函数的重写允许两个都是虚函数或者父类是虚函数,再满足三同,就构成重写

//虚函数的重写允许两个都是虚函数或者父类是虚函数,再满足三同,就构成重写
class Person
{
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
virtual ~Person() { cout << "~Person()" << endl;}
};
class Student : public Person
{
//虽然子类没写virtual,但是他是先继承了父类的虚函数的属性,再完成重写.also virtual function
void BuyTicket() { cout << "买票-半价" << endl;}
public:
~Student() { cout << "~Student()" << endl;}
};
void Func(Person& p)
{
p.BuyTicket();
}

4.override和final

        设计一个不能被继承的类 (C++98)

//设计一个不能被继承的类
//C++98
class A
{
private:
A(int a = 0)
:_a(a)
{}
public:
static A CreatOBj(int a = 0)
{
//new A;
return A(a);
}
protected:
int _a;
};
//间接限制,子类构成函数无法调用父类构造函数初始化成员,没办法实例化对象
class B : public A
{};
int main()
{
A aa = A::CreatOBj(10);
return 0;
}

        final修饰类  该类不能被继承

//C++11 直接限制
class A final
{
protected:
int _a;
};
//class B : public A
//{
//
//};

        final修饰虚函数  This virtual function cannot be overridden by subclass virtual functions

//C++11 final 修饰虚函数,限制他不能被子类中的虚函数重写
class C
{
public:
virtual void f() final
{
cout << "C::f()" << endl;
}
};
class D : public C
{
public:
//virtual void f() final
//{
// cout << "D::f()" << endl;
//}
};

        override:放在子类重写的虚函数后面,检查是否完成重写,没有重写就报错

//override放在子类重写的虚函数的后面,检查是否完成重写
//没有重写就报错
class Car
{
public:
void Drive()
{}
};
class Benz :public Car
{
public:
virtual void Drive() override
{
cout << "Benz-舒适" << endl;
}
};

5.重载、覆盖(重写)、隐藏(重定义)的对比

重载

1.两个函数在同一作用域

2.函数名相同,参数不同

覆盖(重写)

1.两个函数分别在基类和派生类作用域

2.函数名,参数,返回值都相同(协变例外)

3.Both functions are virtual functions

隐藏(重定义)

1.两个函数分别在基类和派生类作用域

2.函数名相同

3.Two base classes and derived classes of the same name function,不构成重写就是重定义

6.抽象类

        在虚函数后面加上=0,This function is a pure virtual function.包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象.派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象.Pure virtual functions require derived classes to override,更体现出了接口继承.

class Car
{
public:
//纯虚函数一般只声明,不实现
//实现没有价值
//virtual void Drive() = 0;
virtual void Drive() = 0
{
cout << "virtual void Drive() = 0" << endl;
}
void f()
{
cout << "void f()" << endl;
}
};
int main()
{
Car* p = nullptr;
//p->Drive();
return 0;
}

7.接口继承和实现继承

        普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现.虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成
多态,继承的是接口.所以如果不实现多态,不要把函数定义成虚函数.

8.多态的原理

        8.1虚函数表

//sizeof(Base)是多少?--12
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
virtual void Func2()
{
cout << "Func2()" << endl;
}
private:
int _b = 1;
char _ch = 'A';
};
int main()
{
cout << sizeof(Base) << endl;
Base bb;
return 0;
}

        对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)

class Person{
public:
virtual void BuyTicket(){cout << "买票-全价" << endl;}
void f(){cout << "f()" << endl;}
protected:
int _a = 0;
};
class Student : public Person{
public:
virtual void BuyTicket(){cout << "买票-半价" << endl;}
protected:
int _b = 0;
};
void Func(Person& p)
{
//多态调用,在编译时,不能确定调用的是哪个函数
//运行时,去pFind the address of the virtual function in the virtual table pointing to the object
p.BuyTicket();
p.f();
}
//Are ordinary functions and virtual functions stored in the same location??
//一样,都在代码段.The virtual function should store the address in the virtual table,方便实现多态
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
Person& r1 = Johnson;
Person p = Johnson;
//Person p1 = Mike;
//Person p2 = Johnson;
不是多态,编译时确定地址
//p1.BuyTicket();
//p2.BuyTicket();
return 0;
}
1. Pointer to a derived class object has a virtual table,由两部分构成,一部分是父类继承下来的成员.
2. Base class object and derived class object vtables are different,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数
的覆盖.重写是语法的叫法,覆盖是原理层的叫法.
4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr.
5. 派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后.
6. 虚函数存在哪?虚表存在哪?虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样,都是存在代码段的,只是他的指针又存到了虚表中.另外对象中存的不是虚表,存的是虚表指针.那么虚表存在哪的呢?vs下是存在代码段的. 

        8.2打印虚函数表中内容

typedef void(*VF_PTR)();
//打印虚函数表中内容
void PrintVFTable(VF_PTR* table)
{
for (int i = 0; table[i] != nullptr; ++i)
{
printf("vft[%d]:%p->", i, table[i]);
VF_PTR f = table[i];
f();
}
cout << endl;
}
typedef void(*VF_PTR)();
//打印虚函数表中内容
void PrintVFTable(VF_PTR* table)
{
for (int i = 0; table[i] != nullptr; ++i)
{
printf("vft[%d]:%p->", i, table[i]);
VF_PTR f = table[i];
f();
}
cout << endl;
}
//单继承
class Base
{
public:
Base()
{
a = 0;
}
virtual void func1(){ cout << "Base::func1" << endl; }
virtual void func2(){ cout << "Base::func2" << endl; }
private:
int a = -1;
};
class Derive :public Base
{
public:
virtual void func1(){ cout << "Derive::func1" << endl; }
virtual void func3(){ cout << "Derive::func3" << endl; }
void fun4(){ cout << "Derive::func4" << endl; }
private:
int b;
};
int main()
{
Base b;
//b.func1();
//#ifdef _WIN64
// PrintVFTable((VF_PTR*)(*(long long*)&b));
//#else
// PrintVFTable((VF_PTR*)(*(int*)&b));
//#endif
//
PrintVFTable((VF_PTR*)(*(void**)&b));
return 0;
}
1. inline函数可以是虚函数,不过编译器就忽略inline属性,这个函数就不再是inline,因为虚函数要放到虚表中去.调用时,如果不构成多态,这个函数保持inline属性,如果构成多态,这个函数就没有inline属性了.
2. 静态成员不可以是虚函数,因为静态成员函数没有this指针,使用类型::成员函数 的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表.
3. 构造函数不可以是虚函数.因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的.
4. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义成虚函数.
5. 对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的.如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找.
6. 虚函数表是在什么阶段生成的,存在哪的?虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的.
7.抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系.

版权声明:本文为[m0_71321720]所创,转载请带上原文链接,感谢。 https://qdmana.com/2023/025/202301252046267939.html