C++十五章:继承-- 虚函数final关键字- 动态绑定-静态成员

OOP核心思想:数据抽象,继承,动态绑定

基类通常都应该定义个虚析构函数,即使该函数不执行任何实际操作也是如此

虚函数以及final关键字

1.基类希望派生类各自定义适合自身版本的某些函数,此时基类将这些函数声明成虚函数,派生类使用override 关键字进行标识,如果派生类不覆盖基类的虚函数,但此时派生类会直接继承其在基类中的版本
2.虚函数与默认实参:如果虚函数拥有默认实参,则该实参由本次调用的静态类型决定。
换句话说,如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。此时,传入派生类函数的将是基类函数定义的默认实参。
3.如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致
4.虚函数,一般派生类的返回类型必须和基类函数匹配,但存在一种特列,但类返回类型是类的指针或引用时,规则无效,如果B是基类,D是派生类,此时B中虚函数返回B或B&,则D中该虚函数返回类型可以是D或D&,因为(指针或引用情况下)是存在从D(基类)到B(派生类)的类型转换
5.如果一个虚函数指定final关键字,则继承该类的中虚函数任何尝试覆盖操作都错误
6

1.只有类的成员函数才能做虚函数
2.静态成员函数是所有同类对象所共有的,不能作为虚函数
3.全局函数不能做虚函数
4.内联函数不能做虚函数
5.构造函数不能定义为虚函数(因为在调用构造函数的时候对象还没有完全初始化)
6.析构函数可以定义为虚函数(在父类及其派生类中都动态分配内存空间时,
必须把父类的析构函数定义为虚函数,实现撤销对象时的多态性)

动态绑定:

静态类型:对象在声明时采用的类型,在编译期既已确定;
动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;
静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于
对象的动态类型
,发生在运行期;

从上面的定义也可以看出,非虚函数一般都是静态绑定,而虚函数都是动态绑定(如此才可实现多态性),引用或指针的静态类型和动态类型不同,这是多态的根本所在

动态绑定示例

存在着派生类向基类的类型的隐式转换,但也只能指针绑定或引用指向到派生类的基类部分,基类向派生类不存在隐式类型转换

//Quote 是基类,Bulk_quote是派生类
double print_total(ostream &os, const Quote &item, size_t n)
{
	double ret = item.net_price(n);
	os << "ISBN: " << item.isbn() << "# sold: " << n << " total due: " << ret << endl;
	return ret;
}
//执行下面调用
	Quote Q("math" ,20);
	Bulk_quote Bq("math", 20, 10, 0.2);
	print_total(cout, Q, 20); //动态绑定
	print_total(cout, Bq, 20); //因为Bq是可以向Q的类型转换的,则形参可以是Bq,
	                           //则调用item.net_price(n)是根据实际对象所在的函数执行,动态绑定
	1.也就是说在使用基类的引用或指针调用虚函数的时,会发生动态绑定,
	
	2.另一方面使用基类的引用或指针对非虚函数调用在编译时进行绑定
	
	3.类似,通过对象进行的函数(虚函数或非虚函数)调用也在编译时绑定,
	对象的类型是确定不变的,通过对象进行的函数调用将在编译时绑定到该对象所属类中的函数版本
	
//name是普通成员函数,print是虚函数	 derived 是base 的派生类
base bobj;          base *bp1 = &bobj;       base &br1 = bobj;
derived dobj;       base *bp2 = &dobj;       base &br2 = dobj;
(a) bobj.print();   // base::print()对象进行的函数(虚函数或非虚函数)调用也在编译时绑定
                   //对象进行的函数调用将在编译时绑定到该对象所属类中的函数版本
                   
(b) dobj.print();   // derived::print()对象进行的函数(虚函数或非虚函数)调用也在编译时绑定
                   //对象进行的函数调用将在编译时绑定到该对象所属类中的函数版本
                   
(c) bp1->name();  // base::name()使用基类的引用或指针对非虚函数调用在编译时进行绑定 bp1类型是基类的指针对象
(d) bp2->name();  // base::name()使用基类的引用或指针对非虚函数调用在编译时进行绑定 bp2类型是基类的指针对象
(e) br1.print();    // base::print()使用基类的引用或指针调用虚函数的时,会发生动态绑定
(f) br2.print();    // derived::print()使用基类的引用或指针调用虚函数的时,会发生动态绑定

那如何回避虚函数的机制
通常情况下,只有成员函数或友员中的代码才需要使用作用域运算符来回避这种机制
 base *br2 = &dobj;
 br2->.print()//本应是动态绑定,调用的是derived::print()
 br2->.base::print()//加上基类作用域运算符强行调用base::print(),
                    //而不管br2实际指向的对象类型是什么,该调用在编译完成解析


        
	              

       

继承与静态成员:

如果使用派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝,移动或赋值,它的派生类部分将被忽略掉,也就是说不存在类对象之间的类型转换‘

继承的派生类对象的声明不应该包括派生列表。某个类作为基类的话,必须定义,不应该只是声明

继承的派生类自身拥有基类的部分以及自身的部分
静态成员的访问:

class
{
public:
  static void statmen();
  int a = 0;
};
class Derived:public Base
{
  void f(const Derived&)
};
 void Derived::f(const Derived& deri)
 {
    Base::statmem();//因为类中静态成员不依赖于对象存在,所以可以通过类作用域调用 
                    //但非静态成员依赖于对象存在,所以需要对象调用(自己的理解)
    cout << Base::a; //错误的调用      
    Derived::statmem();//因为类中静态成员不依赖于对象存在,所以可以通过类作用域调用
    deri.statmem();//也可以通过Derived对象调用
    statmem();//通过this对象调用
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值