实验总结与心得
①重定义继承的函数(homework没有用到,但是看书学的)
在派生类中重定义继承的函数,可以覆盖基类的原函数,从而实现函数重写
②函数重定义和函数重载的区别:
先说一下一个定义,函数签名(function signature),函数签名是 指 “函数名+ 参数列表中的类型序列”,(暂时不包括关键字const和&)。因此,重载函数必须有不同的函数签名,而重定义函数可以有相同的函数签名。在重定义函数签名后,事实上,派生类对象还是可以调用被重定义的基类中的函数,假设基类为father,派生类为son,被重定义的函数为f(),派生类对象名为object,则可以通过作用域符来调用基类被重定义的函数,格式如下:
object.father::f();
③一些不继承的函数:(上一次实验报告略微提到,这次详细总结)
1. 构造函数
2. 析构函数
3. 拷贝构造函数或赋值操作符
这里详细说一下拷贝构造函数(赋值操作符同理),派生类是不会继承基类的拷贝构造函数的,主要原因是基类不能提供足够的信息去创建一个派生类的对象(派生类成员独有的变量,基类没有,所以无法初始化),但是相反,一个派生类对象却可以赋值给一个基类对象(信息足够,但是会发生片段丢失,即派生类独有的成员变量不会复制,显然嘛)。事实上,即便没有定义拷贝构造函数和赋值操作符,编译器会默认生成一个默认的无参拷贝构造函数,但是这个默认的函数只能拷贝成员变量的内容,假如类里面有指针这样的动态数据,就会产生错误。
因此,无论是在基类还是派生类,都应该自己定义一个拷贝构造函数和赋值操作符。特别地,因为派生类可以给基类赋值,因此,在派生类的拷贝构造函数中,可以这么写:
Son(const &object):father(object)
{……}
④最好将析构函数声明为virtual,书里面讲得非常详细,我总结一下,因为在很多情况下,基类都会提供virtual函数作为接口,这样的情况下,为了实现多态,我们都会通过用基类指针指向派生类的对象(这次的题就是这样),
如 father *p = new son; 这个时候假如我们通过delete p的方式析构,就有可能造成内存泄漏,因为p 是 基类father类型的指针,所以delete p只会调用基类的析构函数,而当将father类的析构函数定义为virtual后,自然会调用派生类的析构函数,在这个过程中就会调用基类析构函数,因此不会存在内存泄漏的问题。另外,当基类的析构函数定义为虚函数(virtual)后,所有派生类的析构函数都会自动变为virtual。
⑤多态下的指针调用,这是个比较难搞的知识点,下面粗略讲一下。
1. 基类指针指向派生类,则只可以访问基类函数和基类的成员变量,因此一般会通过虚函数提供接口访问派生类成员变量。
2. 派生类指针指向基类,这样做有一定的风险,必须通过合法转换,因为派生类指针可以访问派生类的成员函数,而基类对象不一定有派生类的成员函数或成员变量。
3. 如果基类和派生类有重定义函数,即派生类重定义了继承的函数,则通过指针调用成员函数,取决于指针的初始类型(原因下面解释),如果指针声明时为基类指针,则会调用基类成员函数,反之调用派生类函数。
4. 如果基类和派生类有虚函数,则通过指针调用virtual函数,调用基类还是派生类的virtual取决于指针指向的对象类型。
⑥函数重定义和虚函数的区别(解释上面)
最大的区别就是,重定义函数是静态绑定的,而virtual函数是动态绑定的,也就是说重定义函数在编译的时候根据声明的类型就已经确定下来了,而virtual会在程序运行的时候根据指针指向的对象类型而改变。好奇之下,
查了一下实现原理,虚函数表,编译原理层面,~卒,先mark down了。
⑦复习了一下函数指针,才知道原来平时我们写的函数都是一个简写,假如声明一个函数fun(int a),事实上fun是一个指针,所以最原始的方法是这样调用(*fun)(a),fun(a)只是一个ANSC下的简写,另外要给一个函数指针赋值也有两种方式fp = fun 或者 fp = &fun(等价于fp = &fun() )。