北京免备案网站建设,老李网站建设,自己做的网站怎么维护,资源搜索引擎搜索神器网目录
1、多态
1.1多态的构成条件
1.2多态的好处
2、虚函数
2.1虚函数重写
2.2虚函数的默认参数
2.3纯虚函数重写
2.4抽象类
2.5虚析构#xff0c;纯虚析构重写
3、重载、覆盖(重写)、隐藏(重定义)的对比
编辑 多态是c面向对象三大特性之一
程序调用函数时#…目录
1、多态
1.1多态的构成条件
1.2多态的好处
2、虚函数
2.1虚函数重写
2.2虚函数的默认参数
2.3纯虚函数重写
2.4抽象类
2.5虚析构纯虚析构重写
3、重载、覆盖(重写)、隐藏(重定义)的对比
编辑 多态是c面向对象三大特性之一
程序调用函数时将使用哪个可执行代码块呢编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编。在c语言中在非常简单因为每个函数名都对应一个不同的函数。在c中由于函数重载的缘故这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而c/c编译可以在编译过程完成这种联编。在编译过程中进行联编被称为给静态联编又称为早期联编然而虚函数使这项工作变得更困难。编译器必须生成能够在程序运行时选择正确使虚方法的代码这被称为动态联编又称为晚期联编。
1、多态
多态就是函数调用的多种形态使用多态能够使得不同的对象去完成同一件事时产生不同的动作和结果。
多态分为静态多态和动态多态
1静态多态也成为静态绑定或前期绑定早绑定函数重载和运算符重载就属于静态多态。静态多态也成为编译期间的多态编译器在编译期间完成的编译器根据函数实参的类型可能会进行隐式类型转换可推断出要调用那个函数如果又对应的函数就调用该甘薯否则出现编译错误
2动态多态也成为动态绑定或后期绑定晚绑定:在程序运行期间根据具体拿到的类型确定程序的具体行为调用具体的函数即运行时的多态。在程序执行期间非编译期判断所英勇的实际类型根据其实际类型调用相应的方法。 父类指针或引用指向父类调用的就是父类的虚函数 父类指针或引用指向子类调用的就是子类的虚函数 静态多态和动态多态区别 静态多态的函数地址早绑定 - 编译阶段确定函数地址 动态多态的函数地址晚绑定 - 运行阶段确定函数地址 1.1多态的构成条件
同一操作作用于不同的对象可以有不同的执行结果产生不同的执行结果这就是多态性。简单的说就是用基类的指针指向子类的对象
所以在继承中要想构成多态需要满足两个条件 1、必须通过基类的指针或者引用指向子类的对象 2、被调用的函数必须是虚函数且派生类必须对基类的虚函数进行重写 1.2多态的好处
多态的优点 1、代码组织结构清晰 2、可读性强 3、利于前期和后期的扩展以及维护 2、虚函数
被virtual修饰的类成员函数被称作虚函数 1、只有类的非静态成员函数前可以加virtual普通函数前不能加virtual 2、虚函数的virtual和虚继承的vittual是同一个关键字但实际上并没有任何关系。虚函数的virtual是为了实现多态而虚继承的virtual是为了解决菱形继承的数据冗余和二义性 2.1虚函数重写
虚函数的重写也叫做虚函数的覆盖若派生类中有一个和基类完全相同的虚函数返回值类型相同函数名相同以及参数列表完全相同这种称为派生类的虚函数重写了基类的虚函数。
通过基类的指针或引用子类对象从而调用我们写的虚函数此时根据不同类型的对象调用的就是不同的函数产生的也是不同的结果进而实现了动态多态。 调用哪个类型的虚函数取决于基类指针指向或引用的对象是哪种类型的对象。 如果不使用基类的指针或引用去调用虚函数则只会调用基类的虚函数。 #include iostream
using namespace std;class Animal
{
public://Speak函数就是虚函数//函数前面加上virtual关键字变成虚函数那么编译器在编译的时候就不能确定函数调用了。virtual void speak(){cout 动物在说话 endl;}
};class Cat :public Animal
{
public:void speak(){cout 小猫在说话 endl;}
};class Dog :public Animal
{
public:void speak(){cout 小狗在说话 endl;}};void DoSpeak(Animal animal)
{animal.speak();
}void test01()
{Cat cat;DoSpeak(cat);Dog dog;DoSpeak(dog);
}int main() {test01();return 0;
} 注意在重写基类虚函数时派生类的虚函数不加virtual关键字也可以进行动态多态主要原因是因为继承后基类的虚函数被继承下来了在派生类中依旧保持虚函数属性。但是这种写法不是很规范因为派生类也有可能会被继承为了区分虚函数建议在派生类的虚函数前也加上virtual关键字。
2.2虚函数的默认参数
动态多态中虚函数默认参数调用者是哪个类就用对应类中的函数的默认参数如果不是使用默认参数那么就会用传递进去的参数
参数值的优先顺序传递进去的参数 基类的默认参数 派生的默认参数
测试代码如下
#include iostream
using namespace std;class Base
{
public:Base(){coutBase()endl;}~Base(){cout~Base()endl;}virtual void show(int a123) //基类的虚函数带默认参数{coutBase showaendl;}
private:
};//派生类
class Child:public Base{
public:Child(){coutChild()endl;}~Child(){cout~Child()endl;}void show(int a456) //派生类也带默认参数{coutChild showaendl;}
private:
};int main()
{//派生类访问自己的成员 不是多态Child mya;//mya.show();//Child show456//多态中虚函数默认参数调用者是哪个类就用对应类中的函数的默认参数Base *d mya;//Child show123d-show();//如果不是使用默认参数那么就会用传递进去的参数//参数值得优先顺序 传递进去的参数 基类的默认参数 派生类的默认参数// d-show(1000);return 0;
}
2.3纯虚函数重写
在堕胎中通常父类的虚函数的实现是某无意义的主要都是调用了子类重写的内容那我们就可以将虚函数写成纯虚函数。c通过使用纯虚函数提供未实现的函数。
纯虚函数的格式: virtual 函数返回类型 函数名参数表 0; 在虚函数声明的时候直接赋值为0这样虚函数就变成纯虚函数了 什么时候下使用纯虚函数 在基类本身生成对象时不合理的时候比如动物作为一个基类派生出老虎等子类但动物本身这基类直接生成对象时不合理的所以为解决这问题方便使用类的多态性引入了纯虚函数的概念将函数定义为纯虚函数则编译器要求在派生类中必须要重新给纯虚函数以实现多态性 纯虚函数并不需要实现如果一个类中有纯虚函数那么这个类就是抽象类如果派生类没有把基类的纯虚函数全部实现那么派生类还是抽象类。
2.4抽象类
在类中包含纯虚函数那么一个类中有纯虚函数那么这个类就是抽象类
抽象类的特点 1、无法实例化对象 2、子类必须重写抽象类中的纯虚函数否则也属于抽象类 实现继承 普通函数的继承是一种实现继承派生类继承了基类函数的实现可以使用该函数。 接口继承 虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口目的是为了重写达成多态。
建议 所以如果不实现多态就不要把函数定义成虚函数。
2.5虚析构纯虚析构重写
我们有时会让一个基类指针指向用 new 运算符动态生成的派生类对象同时用 new 运算符动态生成的对象都是通过 delete 指向它的指针来释放的。如果一个基类指针指向用 new 运算符动态生成的派生类对象而释放该对象时是通过释放该基类指针来完成的就会导致delete的时候只会调用基类的析构函数而不会调用派生类的析构函数如果派生类的析构函数中有释放成员内存空间可能会造成内存泄漏。所以C 规定需要将基类的析构函数声明为虚函数即虚析构函数。只要基类的析构函数是虚函数那么派生类的析构函数不论是否用virtual关键字声明都自动成为虚析构函数。一般来说一个类如果定义了虚函数则最好将析构函数也定义成虚函数。 为什么要需要虚析构函数呢 动态多态使用时如果子类中有属性开辟到堆区那么父类指针在释放时无法调用到子类的析构代码会造成内存泄漏解决这个问题就需要用到虚析构函数。 虚析构函数的格式 virtual ~类名{} 1、虚析构函数就是用来解决通过父类指针释放子类对象。
2、如果子类中没有堆区数据可以不写虚析构函数
纯虚函数的格式 virtual ~类名() 0; //类内定义 类名::~类名(){} //类外声明 和包含普通纯虚函数的类一样包含了纯虚析构函数的类也是抽象类不能被实例化测试代码如下
#includeiostream
#include unistd.h
#includecstring
using namespace std;//基类 --动物类
class Animal{
public:Animal(){coutAnimal()endl;}//将基类的析构函数声明为 虚析构函数//作用当使用基类的指针销毁 派生类对象的时候让派生类对象的析构函数也执行virtual ~Animal(){cout~Animal()endl;} //常用方法---派生类里面是否有指针//可以把基类的虚析构函数 声明定义为 纯虚析构函数//纯虚析构 函数必须在类内声明 类外实现 //virtual ~Animal() 0;//~Animal();//行为//如何让基类 不能实例化对象//有两种方法第一种 将虚函数声明定义为纯虚函数// 第二种方法将虚析构函数声明定义为纯析构函数但是纯析构函数必须在类外实现// virtual void speak(){// coutAnimal::speakendl;// }virtual void speak() 0;};// Animal::~Animal()
// {
// cout~Animal()endl;
// }//派生类 狗
class Dog:public Animal
{
public:Dog(const char*name 旺财){coutDog()endl;d_name new char[strlen(name) 1];strcpy(this-d_name,name);}//当基类的析构函数声明为 虚析构的时候派生类的析构函数也默认会加上关键字 virtual// ~Dog()virtual ~Dog(){cout~Dog()endl;//在析构函数中 释放 指针成员 指向的堆空间delete []this-d_name;}//派生类 中 实现 基类的 虚函数 virtual void speak(){coutDog::speakendl;}
private:char *d_name;
};int main()
{//Animal *p new Dog;//通过基类指针指向 派生类对象//p-speak();//通过基类指针 释放 对象的内存空间默认只会调用 基类的析构函数//delete p; //虚拟析构不仅释放了基类的new空间也释放了派生类的new空间//动物类 是 基类 实例化 对象 不合理 //因为基类中有纯虚函数(纯虚析构函数)所以该类是抽象类抽象类不能实例化//抽象类只能通过继承 在子类中 重写纯虚函数Animal p1;//p1.speak();return 0;
} //将基类的析构函数声明为 虚析构函数 /作用当使用基类的指针销毁 派生类对象的时候让派生类对象的析构函数也执行 //可以把基类的虚析构函数 声明定义为 纯虚析构函 //纯虚析构 函数必须在类内声明 类外实现 //如何让基类 不能实例化对象 //有两种方法第一种 将虚函数声明定义为纯虚函数 // 第二种方法将虚析构函数声明定义为纯析构函数但是纯析构函数必须在类外实现 3、重载、覆盖(重写)、隐藏(重定义)的对比