C++中的多态
一、学习笔记
1.若子类中实现的函数和父类中的函数签名且函数名字一样,是复写。若函数参数不一样是重载。
2.虚函数
在函数声明前加virtual关键字的函数称为虚函数,若子类复写这个虚函数(前面加不加virtual声明无所谓),无论其前面有没有加virtual都是虚函数。
3.多态中的虚函数编译器实现机制:动态链编个静态链编
静态链编:非虚函数,编译时就确定好了调用哪一个函数。
动态链编:运行时才确定是去调用哪一个函数,编译器实现原理:
如果一个类中有虚函数,那么它的对象里面有一个虚函数指针,指向虚函数表,当去调用函数的时候,就通过这个虚函数指针调用虚函数表里面的虚函数。由于多了一个virtual指针,对象的大小变化了,可以使用sizeof(对象/类)查看。
测试证明:多出一个指针大小,而且生成的可执行文件也变大了(此例中变大1/2)。
4.使用虚函数时注意事项
(1) 只有在使用指针或引用来使用对象的时候会体现出多态,若是使用传值的方式不会体现出多态。eg:
test_func(Human *h); //可以体现出多态
test_func(Human &h); //可以体现出多态
test_func(Human h); //不能体现出多态
传值无法体现出多态的原因:例如Chinese是Human的子类,里面有继承Human的成员变量,还有自己的部分(包括虚函数指针),传值相当于直接将Chinese强制转化为Human,此时丢失了自己的部分(丢失了虚函数指针),因此无法表现出多态。
(2) 只有类的成员函数才能声明为虚函数
(3) 静态成员函数不能是虚函数
(4) 内联函数(inline修饰的函数)不能是虚函数,类中实现的函数一旦加上了virtual就不是内联函数了。
(5) 构造函数不能是虚函数。创建一个对象的时候立即就去调用了其构造函数,因此它能有什么多态。
(6) 析构函数一般都声明为虚函数。
若父类的析构函数为虚函数,在delete父类指针(指向子类)的时候,会调用子类的析构函数,然后再调用父类的析构函数,尽管析构函数名字不同。这是一个特例。
(7) 一般的子类重写虚函数需要函数签名和函数名字是一致的,但是返回值若为当前对象的指针或引用的时候(父类中的返回值是*Human/&Human)例外,此时这个函数也可以设置为虚函数。这是另一个特例。
(8) 重载(参数不同)函数不能设置为虚函数。重写(函数签名和名字都相同)可以设置为虚函数。
引用虚函数的目的是为了实现多态,多态就是若父类指针是指向之类的,就调用子类的方法。
==>但是实际测试父类中重载虚函数是没有问题的,子类中对父类中的虚函数进行重载也是没有问题的。
二、Demo
1. 实验1
#include <iostream> using namespace std; class Human { int a; public: virtual void eating() { cout << "use hands" << endl; } //this is an exception,return Human*/&Human* virtual Human* return_test_ptr() { return this; } virtual Human& return_test_ref() { return *this; } virtual ~Human() { cout << "~Human()" << endl; } }; class English : public Human { public: void eating() { cout << "use knifes" << endl; } English* return_test_ptr() { return this; } English& return_test_ref() { return *this; } ~English() { cout << "~English()" << endl; } }; class Chinese : public Human { public: void eating() { cout << "use chopsticks" << endl; } Chinese* return_test_ptr() { return this; } Chinese& return_test_ref() { return *this; } ~Chinese() { cout << "~Chinese()" << endl; } }; void eating_test(Human *h) { h->eating(); } void pass_value_test(Human h) { h.eating(); } void return_test_test(Human *h) { h->return_test_ptr()->eating(); h->return_test_ref().eating(); } void test_descructor(void) { Human *h = new Human(); English *e = new English(); Chinese *c = new Chinese(); Human *ha[3] = {h, e, c}; for (int i = 0; i < 3; i++) { ha[i]->eating(); delete ha[i]; } } int main() { Human h; English e; Chinese c; cout << "-----------eating_test-------------" << endl; eating_test(&h); eating_test(&e); eating_test(&c); cout << "---------pass_value_test-----------" << endl; pass_value_test(h); pass_value_test(e); pass_value_test(c); cout << "------------sizeof test------------" << endl; cout << "sizeof(h) = " << sizeof(h) << endl; cout << "sizeof(e) = " << sizeof(e) << endl; cout << "sizeof(c) = " << sizeof(c) << endl; cout << "---------return_test_test----------" << endl; return_test_test(&h); return_test_test(&e); return_test_test(&c); cout << "---------test_descructor-----------" << endl; test_descructor(); cout << "------------exit main-------------" << endl; return 0; } /* ubuntu@ubuntu:~/Android_work/cpp_object/lesson2/Polymorphism$ ./pp -----------eating_test------------- use hands use knifes use chopsticks ---------pass_value_test----------- use hands ~Human() use hands ~Human() use hands ~Human() ------------sizeof test------------ sizeof(h) = 16 sizeof(e) = 16 sizeof(c) = 16 ---------return_test_test---------- use hands use hands use knifes use knifes use chopsticks use chopsticks ---------test_descructor----------- use hands ~Human() use knifes ~English() ~Human() use chopsticks ~Chinese() ~Human() ------------exit main------------- ~Chinese() ~Human() ~English() ~Human() ~Human() */
2. 实验2:孙类实现抽象方法
#include <iostream> using namespace std; class Human { int a; public: virtual void eating() = 0; Human() { cout << "Human()" << endl; } ~Human() { cout << "~Human()" << endl; } }; class Chinese : public Human { public: //virtual void eating() = 0; void working() { cout << "Chinese working" << endl; } Chinese() { cout << "Chinese()" << endl; } ~Chinese() { cout << "~Chinese()" << endl; } }; class HeNan : public Chinese { public: void eating() { cout << "HeNan eating momo" << endl; } HeNan() { cout << "HeNan()" << endl; } ~HeNan() { cout << "~HeNan()" << endl; } }; int main() { HeNan hn; //Chinese cs; //error hn.eating(); return 0; }
三、补充
1. 只要子类不实现对象,这些虚函数可以由孙类实现,在子类中是否进行声明无所谓。
2. 虚函数和纯虚函数都不一定要在子类中实现(只要子类不定义对象即可),可以在孙子中实现。虚函数可以提供一个默认实现。
virtual void funtion1() { xxx }; //虚函数
virtual void funtion1()=0; //纯虚函数
3. 有虚函数的是抽象类,抽象类不能直接定义对象。
4. 虚函数与纯虚函数的区别
(1) 定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
(2) 定义一个函数为纯虚函数,才代表函数没有被实现。定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
posted on 2019-03-30 16:41 Hello-World3 阅读(140) 评论(0) 编辑 收藏 举报