C++面向对象程序设计有三大思想:封装、继承、多态
1、封装。把客观事物封装成抽象的类。并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
2、继承。它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
3、多态性。允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
1.总体介绍
虚函数相比普通函数多了一个 virtual 关键字,虚函数也是进行需要实现的,而虚函数=0且无结构体实现的形式就称为纯虚函数,纯虚函数自身无法实现函数功能,只能靠其子类继承重写父类中的纯虚函数实现对应的功能。有且仅有包含纯虚函数的类被称为抽象类,抽象类无法实例化自身对象。
在父类中声明为 virtual并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名 ( 参数表 ) { 函数体 };
实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。
好了,现在我们大概知道什么是虚函数,虚函数就是类中使用关键 virtual修饰的成员函数,其目的是为了实现多态性。
★那么什么是多态性呢?
所谓多态性,顾名思义就是“多个性态”。更具体一点的就是,用一个名字定义多个函数,这些函数执行不同但相似的工作。最简单的多态性的实现方式就是【函数重载】和【模板】,这两种属于{静态多态性}。还有一种是{动态多态性},其实现方式就是我们今天要说的【虚函数】。
//A类中的Print函数即为虚函数,且A类为B类的父类,当A类指针指向其子类B的对象时调用函数即实现多态
class A
{
public:
virtual void print(){ cout<<”This is A”<<endl;} //现在成了虚函数了
};
class B:public A
{
public:
void print()
{
cout<<”This is B”<<endl; //这里是子类重写父类对应的函数则不需要加上virtual了
}
};
上述总结:指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。
①定义一个函数为虚函数,不代表函数为不被实现的函数。
②定义为虚函数是为了允许用基类的指针来调用子类的这个函数。
③定义一个函数为纯虚函数,才代表函数没有被实现。
④定义纯虚函数是为了实现一个接口,起规范作用,规范继承这基类的程序员必须实现这个函数。
基类中的虚拟成员希望其派生类定义自己的版本。特别是基类通常应该定义一个虚拟析构函数,即使它不起作用,析构函数必须是虚拟的,以允许动态分配和销毁继承层次结构中的对象。
那么为什么析构函数必须是虚拟的,而我们新建程序时,默认的析构函数却不是虚拟的呢?
一、为什么析构函数必须是虚拟的?
因为指针指向的是一个派生类实例,我们销毁这个实例时,肯定是希望先清理派生类自己的资源,同时又清理从基类继承过来的资源。而当基类的析构函数为非虚函数时,删除一个基类指针指向的派生类实例时,只清理了派生类从基类继承过来的资源,而派生类自己独有的资源却没有被清理。
总结:如果一个类会被其他类继承,那么我们有必要将被继承的类(基类)的析构函数定义成虚函数。这样,释放基类指针指向的派生类实例时,清理工作才能全面进行,才不会发生内存泄漏。
二、为什么默认的析构函数不是虚函数?
虚函数不同于普通成员函数,当类中有虚成员函数时,类会自动进行一些额外工作。这些额外的工作包括生成虚函数表和虚表指针,虚表指针指向虚函数表。每个类都有自己的虚函数表,虚函数表的作用就是保存本类中虚函数的地址,我们可以把虚函数表形象地看成一个数组,这个数组的每个元素存放的就是各个虚函数的地址。
这样一来,就会占用额外的内存,当们定义的类不被其他类继承时,这种内存开销无疑是浪费的。
★虚函数与纯虚函数是否实现对比:虚函数在基类中一定要实现,如果基类中的虚函数不想实现,只想通过派生类来实现,需要将基类中的虚函数换成纯虚函数,需要在函数后加 = 0。因为虚函数的地址在链接的时候需要放到类的虚函数表中,所以即使你的代码里面没有调用这个函数,编译器也需要取它的地址,已经有对它的引用了,就必须要实现才行。
★抽象类的特性介绍
抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。1.抽象类的定义: 称带有纯虚函数的类为抽象类。
2.抽象类的作用:抽象类的主要作用是将有关的操作作为结果接口组织在一个继承层次结构中,由它来为派生类提供一个公共的根,派生类将具体实现在其基类中作为接口的操作。所以派生类实际上刻画了一组子类的操作接口的通用语义,这些语义也传给子类,子类可以具体实现这些语义,也可以再将这些语义传给自己的子类。
3.使用抽象类时注意:
1)抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体的类。
2)抽象类是不能定义对象的。
最后综述大总结:
1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
2、虚函数声明如下:virtual ReturnType FunctionName(Parameter);虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK****: unresolved external symbol “public: virtual void __thiscall ClassName::virtualFunctionName(void)”
3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
4、实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。
定义纯虚函数就是为了让基类不可实例化化
因为实例化这样的抽象数据结构本身并没有意义。