网站挂马怎么办,上海商地网站建设公司,知名企业排名,大型门户网站建设需要哪些技术和注意事项C#xff08;进阶) 第2章 多态 文章目录 前言一、多态的概念二、多态的定义及实现1.虚函数2.虚函数的重写3.多态的条件4.多态的细节 三、析构函数的重写四、重载/重写/隐藏的对比五、抽象类抽象类 六、相关题目题目1题目2 七、const修饰八、多态原理九、虚函数放在地方总结 前…C进阶) 第2章 多态 文章目录 前言一、多态的概念二、多态的定义及实现1.虚函数2.虚函数的重写3.多态的条件4.多态的细节 三、析构函数的重写四、重载/重写/隐藏的对比五、抽象类抽象类 六、相关题目题目1题目2 七、const修饰八、多态原理九、虚函数放在地方总结 前言
什么是多态多态其实就是多种形态的简写这篇博客会详细的介绍多态 一、多态的概念
多态(polymorphism)的概念通俗来说就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)这⾥我们重点讲运⾏时多态编译时多态(静态多态)和运⾏时多态(动态多态)。编译时多态(静态多态)主要就是我们前⾯讲的函数重载和函数模板他们传不同类型的参数就可以调⽤不同的函数通过参数不同达到多种形态之所以叫编译时多态是因为他们实参传给形参的参数匹配是在编译时完成的我们把编译时⼀般归为静态运⾏时归为动态。 通俗点来说就是不同的东西做不同的事情有多种形态 二、多态的定义及实现
1.虚函数
类成员函数前⾯加virtual修饰那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修饰。 这是构成多态的必备条件之一
class Person{public:virtual void BuyTicket() { cout 买票-全价 endl; }};2.虚函数的重写
这里学生类继承了person类那么这里按之前的理解应该构成隐藏关系但是这里不一样这里我们一般叫做重写
class Person{public:virtual void BuyTicket() { cout 买票-全价 endl; }};
class Student : public Person
{
public:virtual void BuyTicket() { cout 买票-打折 endl; }
};假如我现在要去买票
class Person{public:virtual void BuyTicket() { cout 买票-全价 endl; }};
class Student : public Person
{
public:virtual void BuyTicket() { cout 买票-打折 endl; }
};void func(Person p)
{p.BuyTicket();
}
int main()
{Person p;Student s;func(p);func(s);return 0;
}注意看这里传入的是父类 这里可能会有疑问为什么func函数参数写的父类但是这里传入子类也可以这是因为切片 这里可以看到传入不同的类型处理的方式也不一样这里就是一个简单的多态 3.多态的条件
虚函数的重写必须是父类的指针或者是引用上面俩个条件缺一不可
4.多态的细节
假如这里把父类的virtual去掉这里是多态吗
class Person{public:void BuyTicket() { cout 买票-全价 endl; }};
class Student : public Person
{
public:virtual void BuyTicket() { cout 买票-打折 endl; }
};void func(Person p)
{p.BuyTicket();
}
int main()
{Person p;Student s;func(p);func(s);return 0;
}这里的答案是不是多态 假如这里子类去掉virtual是多态吗 答案是是多态 面试题就很喜欢这样考但是这里是为什么父类去就不行子类就行呢
上面那样构成虚函数的重写就算不写virtual因为继承了父类子类哪里会把父类的声明拿下来也是属于多态 多态调用看指向对象类型指向谁调用谁的虚函数 普通调用看调用者的类型调用调用者的函数
三、析构函数的重写
假如这里有1父1子
class person
{
public:~person(){cout ~person() endl;}};class student:public person
{
public:~student(){cout ~student() endl;}
};int main()
{person* p1 new student;delete p1;
}这里定义了一个指针指向了student这里 但是这里调用的是父类的析构函数
上面这里会出现非常大的问题上面的代码是为了方便理解
假如我把代码改成这样
class person
{
public:~person(){cout ~person() endl;}};class student:public person
{
public:~student(){delete[] _ptr;cout ~student() endl;}
protected:int* _ptr new int[10];
};int main()
{person* p1 new student;delete p1;
}可以看到这里就出现了很严重的内存泄露 按正常关系来说studen继承了person这studen就应该有俩个析构函数一个自己的一个父类的但是这里为什么只调用了一个
这里是因为析构函数会被编译器统一处理成destructor那么这里就有俩个destructor函数这里就会成隐藏关系 所以这里就只会调用父类的析构函数 这里的解决方式就是加上virtual 四、重载/重写/隐藏的对比 五、抽象类
抽象类
class Car
{
public:virtual void Drive() 0;
};这就是一个抽象类在虚函数的后⾯写上 0 则这个函数为纯虚函数纯虚函数不需要定义实现(实现没啥意义因为要被派⽣类重写但是语法上可以实现)只要声明即可。 抽象类也无法实例化这里就会有一个疑问了那有个蛋用呢 如果我不想让别人实例化出car类出来但是可以实例化benz出来就可以用抽象派去重写继承的benz类是可以实例化的
class Car
{
public:virtual void Drive() 0;
};class Benz : public Car
{
public:virtual void Drive(){cout Benz-舒适 endl; }
};
int main()
{Benz a;
}先来俩题热热身
六、相关题目
题目1
class A
{
public:virtual void func(int val 1){cout A- val endl;}virtual void test(){func();}
};class B : public A
{
public:void func(int val 0){cout B- val endl;}
};int main()
{B* p new B;p-test();return 0;}答案是B 这里就是c语法的一个大坑用的是B类但是这里是继承的test会去找子类的但是可以发现子类哪里是可以不写virtual的用的是子类的定义但是声明依旧用的是父类的 题目2
class Base
{
public:virtual void Fun1(){cout Func1 endl;}
private:int _a 1;char ch 1;};int main()
{cout sizeof(Base) endl;
}定义成虚函数了以后这里就多要一个指针 所以说写成虚函数就会付出代价这个指针指向虚函数表虚函数的地址都会放在这张表里面多态就是靠这张虚函数表实现的 七、const修饰
纯虚函数无法实例化这里可以强制别人用引用或者指针
class Animal
{
public:virtual void sound() 0;
};class Cat : public Animal
{
public:virtual void sound(){cout Cat-喵喵 endl;}
};
class Dog : public Animal
{
public:virtual void sound(){cout Dog-汪汪 endl;}
};
void AnimalSound(Animal a)
{}这里加上const可以使用匿名对象
class Animal
{
public:virtual void sound() const 0;
};class Cat : public Animal
{
public:virtual void sound()const {cout Cat-喵喵 endl;}
};
class Dog : public Animal
{
public:virtual void sound()const {cout Dog-汪汪 endl;}
};
void AnimalSound(const Animal a)
{a.sound();
}
int main()
{AnimalSound(Cat());return 0;
}八、多态原理
上面说到多态必须是要虚函数并且形参还需要是父类的引用或者指针那么这个虚函数和普通的函数有什么区别呢 这里可以看到这里除了成员变量_a外还有一个_vfptr,这里面还放着成员函数的这个东西其实就是一个指针数组这个指针会指向一个表这个表就是虚表 下面俩个是虚函数一个不是这里用监视窗口查看一下
class Base
{
public:virtual void Func1(){cout Base::Func1 endl;}virtual void Func2(){cout Base::Func2 endl;}void Func3(){cout Base::Func3 endl;}
private:int _a;};int main()
{Base b;return 0;
}这里可以通过上面看到这个表里面只有俩个函数 用内存窗口是可以看见的 多态其实就是动态确定地址
举个例子
class Base
{
public:virtual void Func1(){cout Base::Func1 endl;}virtual void Func2(){cout Base::Func2 endl;}void Func3(){cout Base::Func3 endl;}
private:int _a;};
class Derive : public Base
{
public:virtual void Func1(){cout Derive::Func1 endl;}
private:int _b;
};
void Func1(Base* p)
{p-Func1();//虚函数p-Func3();//不是虚函数
}int main()
{Func1(new Base);Func1(new Derive);return 0;
}这里可以看见func1是重写后的那个func1但是这里的func3是不会重写的
第二个func1是重写后的那个func也就是谁调用那个虚表就指向 第二个调用的时候我这里给的参数是Derive这里会发生切片这里就会对func1进行重写但是这里收到的参数始终都是一个base指针只不过这里可能是子类切片出来的切片和不是切片出来的这里就会出现虚表地址不同
所以这里就叫运行时绑定也叫多态绑定这里就可以指向父类调用父类指向子类就调用子类它能调用不同的函数它本质的原因就是因为那个虚表的地址不同
这也就是为什么函数的参数必需要父类的指针或者引用的原因
九、虚函数放在地方
虚函数 ------------代码段虚函数表 --------代码段虚函数表的指针-类对象 总结
多态看起来复杂其实只要理解了虚表这个东西就不会复杂虚函数就有一个指针数组这里面放着虚表的地址然后这些虚表里面放着虚函数的地址然后在通过切片来看是哪一个虚表的地址这就是多态的原理