本节知识点:
1.函数重写:
a.通过上篇文章#includec.父类中被重写的函数依然会继承给子类,仅仅是被子类中的函数隐藏了。但是可以通过作用域分辨符:: 来访问父类中被隐藏的函数using namespace std; class parent { public: void fun(int a) //这两个函数发生了重载 { cout << "parent fun() " << a << endl; } void fun() { cout << "parent fun() " << endl; } }; class child : public parent { public: void fun() //与上面父类中的函数发生了重写 { cout << "child fun() " << endl; } }; int main() { child c; c.parent::fun(4); //这里发生了函数的重载 c.parent::fun(); c.fun(); //发生了函数的重写 隐藏了父类中的同名函数 //c.fun(4); //这里会报错 因为没有发生函数重载 被子类的函数隐藏了 return 0; }
2.函数重写与赋值兼容性:
a.对于类的赋值兼容性,父类的指针或引用,指向子类的对象,如:parent *p = &c 或 parent &p = c 此时的p它不能访问子类中独特的成员,它只能访问父类继承给子类的那些成员。其实在赋值的过程中,应该是 隐藏了一个类似强制类型转换的过程, 就像把一个int的变量赋值给一个char的变量一样, 要进行切割的。 如果父类和子类之间存在同名成员,能访问的应该都是父类的同名成员(前提是没有虚函数设置)。 当p进行访问的时候,先去判断访问成员,是不是父类继承给子类的,不是,直接就会报错,说成员不存在(因为p的类型是parent类型) 。然后再去判断父类子类直接有没有同名的,不同名的,子类会赋值给父类。同名的,会直接调用父类的同名成员,忽略子类的成员。 示例代码:#include注意: 如果子类与父类存在同名成员,child c子类对象去访问同名成员,访问到的一定是子类中的同名成员,把父类继承过来的同名成员隐藏(此时是子类的类型)。如果父类的指针或引用指向子类对象的时候,存在同名成员,通过指针或引用访问到的一定是父类的同名成员,子类的同名成员不是被隐藏了,而是根本就没有赋值过来(此时是父类的类型)。这里就说明一个问题,变量的类型决定变量的行为!!!(但是前提一定是没有设置虚函数)using namespace std; class parent { public: int a; int b; void fun() { cout << "parent fun " << endl; } }; class child : public parent { public: int a; void fun() { cout << "child fun " << endl; } }; int main() { child c; c.a = 10; c.b = 100; parent *p = &c; parent &p1 = c; cout << p->a << endl; cout << p1.a << endl; cout << p1.b << endl; cout << p->b << endl; p->fun(); p1.fun(); return 0; }
b. c语言和c++语言都是 静态编译型语言, 所谓静态编译型语言就是在编译前清楚变量函数的类型,根据确定下来的变量函数的类型进行编译。所以说,在默认的情况下,由于程序没有运行,不可能知道父类指针指向的具体是父类对象还是子类对象,一律认为父类指针指向的是父类对象,因此编译的结果为调用父类的成员函数。
3.多态的本质:
a. 如何根据不同对象的类型来判断重写函数的调用,这是面向对象中多态的概念。如果父类指针指向的是父类对象,则调用父类中定义的函数。如果父类指针指向的是子类对象,则调用子类中定义的重写函数。 b.多态, 根据实际的对象类型决定函数调用语句的具体调用目标,打破了c++静态编译的弊端。 使得同样的调用语句有多种不同的表现形式。
#include注意:对于虚函数的声明,其实仅仅在父类中声明就可以了,不用再在子类中进行声明了。也可以即在父类中声明,也在子类中声明。using namespace std; class parent { public: virtual void fun() { cout << "parent fun() " << endl; } }; class child : public parent { public: virtual void fun() { cout << "child fun() " << endl; } }; int main() { child c; parent *p = &c; p->fun(); //同样的语句 有不同的表现形式 parent d; p = &d; p->fun();//次条语句 具有多态的特性 return 0; }
4.重写与重载:
a.在阐述重写与重载之前,要先说明一些概念。 在一个类里面,肯定是不允许存在同名函数或变量的。但是父类和子类对象之间,虽然说子类是特殊的父类,但是毕竟他俩是相对独立的, 这样就允许子类与父类之间存在同名成员了, 同时子类还会默认隐藏父类的同名成员。 子类中依然继承了父类的同名成员, 可以通过作用域分别符进行访问。 对于同名成员函数,就存在两种情况,参数相同的就发生了函数的重写。对于参数不相同的,切记,这里没有发生重载(因为子类和父类之间是不可以发生重载的),虽然说参数不相同就是两个完全不相同的函数,但是函数名字相同就发生了隐藏。两者的区别就在,函数的重写是允许父类和子类之间存在完全相同的函数结构。总之,c++允许子类与父类之间,存在完全相同的同名成员函数(即函数的重写)和同名成员变量,也允许存在参数不同的同名成员函数(其实允许是合理的),但不发生函数重载,对于同名成员一律进行对父类的隐藏,并可以通过作用域分别符进行访问。 b. 比较重载与重写: 函数重载: 必须在同一个作用域。子类无法重载父类函数,父类同名函数将被隐藏。重载是在编译期间静态的根据参数类型和个数决定调用哪个函数的。 函数重写: 必须发生在父类与子类之间。并且父类与子类之间的函数必须有完全相同的函数原型。使用virtual关键字声明后能够产生多态。多态是在运行期间动态的根据具体对象的类型决定调用函数的。5.深入理解虚函数:
a. c++中多态的实现原理:当类中声明虚函数时,编译器会在类中生成一个虚函数表。虚函数表是一个存储类成员虚函数的 函数指针的数据结构(实际上是一个 链表)。 虚函数表是由编译器自动生成与维护的。virtual成员函数会被编译器放入虚函数表中。存在虚函数时,每个对象都有一个指向虚函数表的指针(即VPTR指针)b. 多态实现的过程:首先, 得有两个以上具有继承关系的类(一个父类与多个子类是个经典的例子)。 然后, 类之间存在着函数的重写。再把重写的函数定义为虚函数。因为定义了virtual成员函数,编译器就会在这些类创建对象时分别产生各自的虚函数表。当然,各自的虚函数表需要各自的虚函数表的指针(VPTR指针是类的一个成员,且还是第一个成员)。最重要的是,在调用多态的这些函数时,编译器首先判断函数是否为虚函数。如果是,就利用此时的对象的VPTR指针所指向的虚函数表中查找这个函数,并调用,查找和调用是在程序运行时完成的(注意对象是如何找到VPTR指针的,这个指针首先是不可以外界调用的,当类中有函数声明为virtual属性时,类中就会多一个成员变量,即VPTR指针,且这个指针放在了类的第一个成