第18课 - 多态与继承 - 下

第18课 - 多态与继承 - 下

    一. 重载和重写

      
 Source Example 1:
            #include <iostream>

            /* run this program using the console pauser or add your own getch, system("pause") or input loop */

            class Parent {
            public:
                virtual void func()
                {
                    printf("Parent->");
                    printf("func()!\n");
                }
                
                virtual void func(int i)
                {
                    printf("Parent->");
                    printf("func(int i)!\n");
                }
                
                virtual void func(int i, int j)
                {
                    printf("Parent->");
                    printf("func(int i, int j)!\n");
                }
            };

            class Child : public Parent
            {
            public:
                virtual void func(int i)
                {
                    printf("Child->");
                    printf("func(int i)!\n");
                }
                
                virtual void func(int i, int j)
                {
                    printf("Child->");
                    printf("func(int i, int j)!\n");
                }
                
                void func(int i, int j, int k)
                {
                    printf("Child->");
                    printf("func(int i, int j, int k)!\n");
                }
            };

            void run(Parent* p)
            {
                p->func(1,2);
            }

            int main(int argc, char** argv) {
                
                Parent p;
                Child c;
                
                p.func();
                p.func(1);
                p.func(1,2);
                
                /* 编译报错,因为void func(int i, int j, int k)函数将继承的func函数名称覆盖了 */
                /* 并没有发生函数重载,函数重载发生在同一作用域之间 */
                //c.func();
                
                /* 这样才能调用父类中的func函数 */
                c.Parent::func();
                
                /* Child里面定义的两个func函数处于同一作用域,可以发生重载 */
                c.func(1,2);
                
                printf("\n");
                /* 会发生多态 */
                run(&p);
                run(&c);    
                
                
                return 0;
            }

            输出结果如下:


            

        1.1 重载与重写区别

            函数重载:

                a.必须在同一个类(同一个作用域)中进行

                b.子类无法重载父类的函数,父类同名函数将被覆盖

                c.重载是函数在编译期间根据参数类型和个数决定调用的函数

            函数重写:

                a.必须发生于子类和父类之间

                b.并且子类和父类中的函数必须有完全相同的原型

                c.使用virtual声明之后能够产生多态

                d.多态是在运行期间根据具体对象的类型决定调用函数(编译时不知道)

                

    二.虚函数深入理解

        2.1 问题1: 是否可以将类的每个成员都声明为虚函数?

            

        2.2 C++中多态的实现原理

            2.2.1 当类中声明为虚函数时,编译器会在类中产生一个虚函数表(存储虚函数的地址)

            2.2.2 虚函数表是一个存储类成员函数指针的数据结构

            2.2.3 虚函数表是由编译器自动生成与维护的

            2.2.4 virtual成员函数会被编译器放入虚函数表中

            2.2.5 存在虚函数时,每个对象都有一个指向虚函数表的指针(VPTR指针,一般作为对象的第一个成员)

            
            void run(Parent* p)                编译器确定func()是否为虚函数?
            {                                        1.Yes, 编译器在对象VPTR所指向的虚函数表中查找func(),并调用,查找,在运行时完成
                p->func();                     2.No, 编译器直接可以确定并找到被调用成员的函数
            }
            

            问题一答案:通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要寻址操作才能真正的确定应该调用的函数。

                        而普通成员函数在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。

                        出于效率的考虑,没必要将所有的成员函数都声明为虚函数。

                        

        2.2 问题2:    对象中VPTR指针什么时候被初始化?

            2.2.1 对象在创建的时候由编译器对VPTR指针进行初始化

            2.2.2 只有当对象的构造完全结束后VPTR的指向才最终确定

                    完全结束->(父类和子类的构造都结束,每个构造函数执行之前VPTR指针都会变化)

            2.2.3 父类对象的VPTR指向父类虚函数表

            2.2.4 子类对象的VPTR指向子类虚函数表

            

            结论:构造函数中调用虚函数无法实现多态。

            
          
 Source Example2.2(构造函数中的"多态")
                #include <iostream>

                /* run this program using the console pauser or add your own getch, system("pause") or input loop */

                class Parent {
                public:
                    Parent()
                    {
                        this->func();
                    }
                    virtual void func()
                    {
                        printf("Parent->");
                        printf("func()!\n");
                    }
                };

                class Child : public Parent
                {
                public:
                    virtual void func(int i)
                    {
                        printf("Child->");
                        printf("func(int i)!\n");
                    }
                };

                
                int main(int argc, char** argv) {
                    
                    /* 会调用父类的func函数 */
                    Parent p;
                    /*  
                     * 1. 首先会执行父类函数的构造函数,此时VPTR指针指向父类的虚函数表
                     * 2. 调用虚函数表的func函数,即父类中的func函数
                     * 3. 其次会执行子类函数的构造函数,此时VPTR指针指向子类的虚函数表
                     * 4. 因此会调用父类的func函数,多态并没有发生
                     * 5. 结论,构造函数中无法实现多态
                     */
                    Child c;
                                
                    return 0;
                }
                

                输出结果如下:   


                

    三.纯虚函数

        3.1 面向对象中的抽象概念

                在进行面向对象分析时,会有一些抽象的概念

                             ->矩形

                        图像 ->三角形        考虑如何给图形求面积?

                             ->圆形  

                

                在现实中需要知道图像的具体类型才能球面积,所以对概念上的"图形"求面积是没有意义的!

                    class Shape

                    {

                        public:

                            double area()

                            {

                                return 0

                            }

                    }

                这个类的设计完全脱离实际,没有任何意义。

3.2 用Shape作为基类进行继承

        
Source Example 3.2:
             #include <iostream>

            /* run this program using the console pauser or add your own getch, system("pause") or input loop */

            class Shape{
            public:
                virtual double area()
                {
                    return 0;
                }
            };

            class Rectangle : public Shape{
            protected:
                int a;
                int b;
            public:
                Rectangle(int a, int b)
                {
                    this->a = a;
                    this->b = b;
                }
                virtual double area()
                {
                    return a * b;
                }
            };

            class Circle : public Shape{
            protected:
                int r;
            public:
                Circle(int r)
                {
                    this->r = r;
                }
                virtual double area()
                {
                    return 3.14 * r * r;
                }
            };

            void area(Shape* s)
            {
                printf("area = %lf\n", s->area());
            }
            
            int main(int argc, char** argv) {
                
                Rectangle r(1,2);
                Circle c(3);
                Shape s;
                
                area(&r);
                area(&c);
                area(&s);        
                    
                return 0;
            }
            输出结果如下:
                

        3.3 面向对象中的抽象类

            3.3.1 抽象类可用于表示现实世界中的抽象概念

            3.3.2 抽象类是一种只能定义类型,而不能产生对象的类

            3.3.3 抽象类只能被继承并且重写相关函数

            3.3.4 抽象类的直接特征是纯虚函数

            
            注意:纯虚函数是只声明函数原型,而故意不定义函数体的虚函数
            

        3.4 抽象类与纯虚函数

            3.4.1 抽象类不能用于定义纯虚对象

            3.4.2 抽象类只能用于定义指针和引用

            3.4.3 抽象类中的纯虚函数必须被子类重写

            

            class Shape

            {

            public:

                virtual double area() = 0;

            };

            area是纯虚函数,=0 告诉编译器,这个函数故意只声明不定义

            
        
Source Example3.4:
        #include <iostream>

        /* run this program using the console pauser or add your own getch, system("pause") or input loop */

        class Shape{
        public:
            virtual double area() = 0;
        };

        class Rectangle : public Shape{
        protected:
            int a;
            int b;
        public:
            Rectangle(int a, int b)
            {
                this->a = a;
                this->b = b;
            }
            /* 子类中必须要有重写Shape类中的area函数,不然会报错 */
            virtual double area()
            {
                return a * b;
            }
        };

        class Circle : public Shape{
        protected:
            int r;
        public:
            Circle(int r)
            {
                this->r = r;
            }
            /* 子类中必须要有重写Shape类中的area函数,不然会报错 */
            virtual double area()
            {
                return 3.14 * r * r;
            }
        };

        void area(Shape* s)
        {
            printf("area = %lf\n", s->area());
        }
        
        int main(int argc, char** argv) {
            
            Rectangle r(1,2);
            Circle c(3);
            /* 编译会报错,因为不能定义抽象类的对象 */
            Shape s;
            
            area(&r);
            area(&c);        
                
            return 0;
        }

    四.小结

        4.1 函数重载与函数重写不同

        4.2 多态是通过虚函数表实现的

        4.3 虚函数在效率上会受到影响

        4.4 抽象类可用于表示现实世界中的抽象概念

        4.5 抽象类是通过纯虚函数实现的

猜你喜欢

转载自blog.csdn.net/qq_36521904/article/details/80485828