赞
踩
先看如下代码:
class Base { public: void base_fun1() {} virtual void base_fun2() { cout << "this is Base fun2()" << endl; } public: int a_a; protected: int a_b; private: int a_c; }; class Son :public Base { public: void son_fun1() {} void base_fun2()//重写父类虚函数 { cout << "this is Son fun2()" << endl; } public: int b_a; protected: int b_b; private: int b_c; }; int main() { Base* ba = new Son();//父类指针指向子类 //父类指针只能访问从父类继承而来的成员变量和成员函数 //只能访问从父类继承而来的成员变量 ba->a_a = 10; //只能访问从父类继承过来的成员函数 ba->base_fun1(); //由于子类对象用的是从父类继承而来的虚函数表,所以父类指针可以通过查找父类虚函数表的方式,调用被子类重写后的函数 ba->base_fun2(); //子类指针对所有的成员变量和成员函数随意访问(受访问权限限制) Son* so = new Son();//子类指针指向子类 so->a_a = 10; so->b_a = 10; so->base_fun1(); so->son_fun1(); so->base_fun2(); so->Base::base_fun2(); return 0; }
分析:
父类指针可以指向子类对象,是安全的,开发中经常用到(继承方式必须是public)
#include <iostream> using namespace std; class Person { public: int m_age; }; class Student : public Person { public: int m_score; }; int main() { Person* p = new Student(); // 父类指针可以指向子类对象,只看左边定义的指针类型 p->m_age = 10; //p->m_score = 100; // 报错,Person类中没有m_score这个成员变量 return 0; }
子类指针不可以指向父类对象,是不安全的。
#include <iostream> using namespace std; class Person { public: int m_age; }; class Student : public Person { public: int m_score; }; int main() { //Student* p = new Person(); // 报错,子类指针不可以指向父类对象 Student* p = (Student*) new Person(); // 强制类型转换 p->m_age = 10; p->m_score = 100; // 不报错,但不安全 return 0; }
类型兼容性原则 : C++ 的 " 类型兼容性原则 “ 又称为 ” 赋值兼容性原则 " ;
子类代替父类 : 需要 基类 ( 父类 ) 对象的 地方 , 都可以使用 " 公有继承 " 的 派生类 ( 子类 ) 对象 替代 , 该 派生类 ( 子类 ) 得到了 除 构造函数 和 析构函数 之外的 所有 成员变量 和 成员方法 ;
功能完整性 : " 公有继承 " 的 派生类 ( 子类 ) 本质上 具有 基类 ( 父类 ) 的 完整功能 , 使用 基类 可以解决的问题 , 使用 公有继承派生类 都能解决 ;
特别注意 : " 保护继承 " 和 " 私有继承 " 的 派生类 , 是 不具有 基类 的 完整功能的 , 因为 最终继承 后的派生类 , 无法在 类外部调用 父类的 公有成员 和 保护成员 ;
" 类型兼容性原则 " 应用场景 :
#include "iostream" using namespace std; class Parent { public: void funParent() { cout << "父类 funParent 函数" << endl; } private: int c; }; // 子类 公有继承 父类 class Child : public Parent { public: void funChild() { cout << "子类 funChild 函数" << endl; } }; // 函数接收父类指针类型 // 此处可以传入子类对象的指针 void fun_pointer(Parent* obj) { obj->funParent(); } // 函数接收父类引用类型 // 此处可以传入子类对象的引用 void fun_reference(Parent& obj) { obj.funParent(); } int main() { // 父类对象 Parent parent; // 子类对象 Child child; // 父类对象 可以调用 父类公有函数 parent.funParent(); // 子类对象 可以调用 子类自身公有函数 child.funChild(); // 子类对象 可以调用 父类公有函数 child.funParent(); // 将指向子类对象的指针传给接收父类指针的函数也是可以的 fun_pointer(&child); // 接收父类引用 , 此处传入子类引用 fun_reference(child); // 赋值兼容性原则 : cout << "\n赋值兼容性原则示例 : \n" << endl; // 常规操作 : 父类指针 指向 父类对象 Parent* p_parent = NULL; p_parent = &parent; // 通过父类指针调用父类函数 p_parent->funParent(); // 将指向子类对象的指针传给接收父类指针的函数也是可以的 fun_pointer(p_parent); // 接收父类引用参数 fun_reference(*p_parent); // I. 类型兼容性原则 : 父类指针 指向 子类对象 Parent* p_parent2 = NULL; p_parent2 = &child; // 通过父类指针调用父类函数 p_parent2->funParent(); // II. 类型兼容性原则 : 使用 子类对象 为 父类对象 进行初始化 Parent parent3 = child; return 0; } /* 输出结果: * 父类 funParent 函数 * 子类 funChild 函数 * 父类 funParent 函数 * 父类 funParent 函数 * 父类 funParent 函数 * * 赋值兼容性原则示例 : * * 父类 funParent 函数 * 父类 funParent 函数 * 父类 funParent 函数 * 父类 funParent 函数 */
virtual
虚函数首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
默认情况下,编译器只会根据指针类型调用对应的函数。
#include <iostream> using namespace std; class Animal { public: void run() { cout << "Animal::run()" << endl; } }; class Dog : public Animal { public: void run() { cout << "Dog::run()" << endl; } }; class Cat : public Animal { public: void run() { cout << "Cat::run()" << endl; } }; int main() { Animal* p1 = new Animal(); // 只看左边定义的指针类型 p1->run(); delete p1; Animal* p2 = new Dog(); // 只看左边定义的指针类型 p2->run(); delete p2; Animal* p3 = new Cat(); // 只看左边定义的指针类型 p3->run(); delete p3; return 0; } /* 输出结果: * Animal::run() * Animal::run() * Animal::run() */
可以通过一个父类指针调用父类和子类中的同名同参的函数,只需要在父类中该函数声明之前加上 virtual
关键字使其成为虚函数即可。
只要在父类中声明为虚函数,那么在所有子类中重写的函数也自动变成虚函数,也就是说子类中可以省略 virtual
关键字(为了阅读代码方便,建议加上)。
子类继承父类时,子类可以不重写父类的虚函数,则相当于原样继承了父类的虚函数;也可以重写,则相当于覆盖了父类的虚函数实现。不论是否重写虚函数都不影响子类的实例化。
调用虚函数执行的是“动态绑定”,如下代码所示,在程序运行的时候才能知道调用了哪个子类的 run()
函数。
#include <iostream> using namespace std; class Animal { public: virtual void run() { cout << "Animal::run()" << endl; } }; class Dog : public Animal { public: virtual void run() { cout << "Dog::run()" << endl; } }; class Cat : public Animal { public: virtual void run() { cout << "Cat::run()" << endl; } }; int main() { Animal* p1 = new Animal(); p1->run(); delete p1; Animal* p2 = new Dog(); p2->run(); delete p2; Animal* p3 = new Cat(); p3->run(); delete p3; return 0; } /* 输出结果: * Animal::run() * Dog::run() * Cat::run() */
override
重写函数的签名包括:函数名、参数列表、const属性。
C++11 增加了 override
关键字,保证子类重写的虚函数与父类的虚函数有相同的签名。也就是说,加了 override
,明确表示子类的这个虚函数是重写父类的,如果子类与父类虚函数的签名不一致,编译器就会报错。
因此,为了减少程序运行时的错误,重写的虚函数都建议加上 override
。
class Animal { public: virtual void run() { cout << "Animal::run()" << endl; } }; class Dog : public Animal { public: virtual void run() override // 正确,若与父类虚函数的签名不一致,编译器就会报错 { cout << "Dog::run()" << endl; } }; class Cat : public Animal { public: virtual void run() override // 正确,若与父类虚函数的签名不一致,编译器就会报错 { cout << "Cat::run()" << endl; } };
final
阻止重写C++11 增加了 final
关键字,阻止类的进一步派生和虚函数的进一步重写。
如果不希望某个类被继承或者不希望某个虚函数被重写,则可以在类名和虚函数后加上 final
关键字,加上 final
关键字后,若再被继承或重写,编译器就会报错。
class Animal { public: virtual void run() final { cout << "Animal::run()" << endl; } }; class Dog : public Animal { public: virtual void run() override // 报错,一旦父类的虚函数被声明为final,则子类不能再重写它 { cout << "Dog::run()" << endl; } }; class Cat : public Animal { public: virtual void run() override // 报错,一旦父类的虚函数被声明为final,则子类不能再重写它 { cout << "Cat::run()" << endl; } };
多态是面向对象非常重要的一个特性:
随着虚函数的提出,面向对象编程中的“多态性”就浮出了水面。多态性体现在具有继承关系的父类和子类之间,父类把成员函数声明为虚函数,子类重写父类的成员函数,在程序运行时期,找到动态绑定到父类指针上的对象(可能是某个子类对象,也可能是父类对象),然后系统内部查一个虚函数表,找到函数的入口地址,从而通过父类指针调用父类或者子类的成员函数,这就是运行时期的多态性。
#include <iostream> using namespace std; class Animal { public: virtual void speak() { cout << "Animal::speak()" << endl; } virtual void run() { cout << "Animal::run()" << endl; } }; class Dog : public Animal { public: void speak() { cout << "Dog::speak()" << endl; } void run() { cout << "Dog::run()" << endl; } }; class Cat : public Animal { public: void speak() { cout << "Cat::speak()" << endl; } void run() { cout << "Cat::run()" << endl; } }; class Pig : public Animal { public: void speak() { cout << "Pig::speak()" << endl; } void run() { cout << "Pig::run()" << endl; } }; void fun(Animal* p) { p->speak(); p->run(); } int main() { fun(new Dog()); fun(new Cat()); fun(new Pig()); return 0; } /* 输出结果: * Dog::speak() * Dog::run() * Cat::speak() * Cat::run() * Pig::speak() * Pig::run() */
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。
在基类中实现纯虚函数的方法是在函数原型后加 =0:
virtual void funtion1()=0
在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。
纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。