注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

刘邓

每天收获一点点-目标:富足

 
 
 

日志

 
 

C++高级细节(3)  

2012-05-12 10:31:49|  分类: C++编程思想(卷 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
1.重新定义一个基类中的重载函数将会隐藏所有该函数的基类中的实现版本。也就是说以后此派生类的对象将只能调用派生类的函数实现。

#include<iostream>
#include<fstream>
using namespace sdt;

class Base
{
public:
void print(){cout<<"BASE"<<endl;}
void print(int){cout<<"BASE"<<endl;}
};

class Son:public Base
{
//public:
//int print(int,float) {cout<<"Son"<<endl;}

//这里如果重载了print那么下面的son.print()将无法通过编译,因为父类的print被隐藏

//而子类没有对应的实现

};
int main()
{
Son son;
son.print();
system("pause");
return 0;
}

 

2.虚函数重载问题

对于虚函数的重新定义可以修改其参数表,但是却不能修改返回值类型。为什么是这样呢?

就如之前文章所描述的虚函数实现一样,虚函数是通过虚函数表VTABLE实现的,每个虚函数在该类的VTABLE中有唯一的地址

且和父类或者子类的虚函数有对应关系,如果修改了函数的返回值类型那么将无法多态的确定到底调用哪个虚函数版本,造成错误。

ps:有个特例就是如果虚函数的返回值是类类型的引用或者指针且是父类的,那么子类在重载这个函数是可以将返回值重定义为子类的引用或者指针,之所以这么做一方面是可以保证返回确切的返回值类型,同时另一方面有自动向上类型转换的存在。不过一般情况下返回其基类的类型同样可以解决问题,所以是个特例。

 

3.效率漏掉

当我寻找效率漏掉的时候,记住:

1.编译器要为虚基类及其子类插入VPTR并初始化

2.编译器要为虚基类及其子类构建VTABLE

3.编译器要检查this的值,以保证new可以产生正确的结果

4.调用基类的构造函数

 

4.构造函数和析构函数的调用次序

构造函数的调用次序是从基类到最晚派生类

原因:

构造函数会初始化VPTR使之对于与VTABLE,如果派生类的构造函数要操作基类继承的成员,而基类还没有被初始化就可能出现灾难,所以从上往下的初始化保证了系统能在保证上层得到合理初始化后初始化下层

 

析构函数的调用次序是从最晚派生类

原因:如果先调用基类的析构函数把基类的所有成员全部销毁了,然后调用子类的析构函数,而子类的析构函数需要用到基类的成员,而成员已经被销毁就可能出现灾难

 

5.虚析构函数

析构函数可以使虚函数,用法和普通的虚函数同等(构造函数不能使虚函数,因为在没有调用构造函数之前还无法确定对象的确切类型,VPTR和VTABLE都要靠构造函数来初始化,这就谈不上多态,而调用析构函数的时候已经可以明确知道调用对象的类型)

另:如果析构函数以纯虚函数的形式出现则必须要有定义!因为析构函数和构造函数是一个类的必须部分(即使没定义,系统也会为我们生成默认的构造函数和析构函数)而如果没有定义在VTABLE中纯虚析构函数的地址为0,这就无法准确析构类对象,这是无法容忍的。所以...

C++编程思想:作为一个准则,任何时候我们类中都要有一个虚函数,我们应该立即增加一个虚析构函数(即使什么都不做)

 

6.析构函数内部调用

析构函数内部的函数调用将只调用本地版本以保证,此函数或者变量还没有被摧毁,如果调用远程的比如子类的(::通过作用域解析符)将无效,系统依旧默认使用本地的函数。

7.虚运算符重载

运算符的虚重载是很复杂的,有个基本准则:定义一个纯虚基类作为公共接口(运算符),每个运算符的两端必须是公共基类的派生类,这样才能促进多态的实现

下面抄录一部分基类实现代码:

详见C++编程思想第一卷 15.12节 (电子版可以从www.bruceeckel.com下载)

class Matrix;//矩阵
class Scalar;//标量
class Vecotr;//向量
//上面的单个类都是Math的子类
class Math
{
        public:
        virtual Math& operator*(Math& rv) = 0;
        virtual Math& multiply(Matrix*) = 0;//实现Matrix,Scalar和Vector乘于Matrix的情形
        virtual Math& multiply(Scalar*) = 0;
        virtual Math& multiply(Vector *) = 0;;
        };


8.dynamic_cast显式类型转换(动态类型转换)

1.使用显式类型转换必须作用域一个真正的多态层次上——含有虚函数——这是因为dynamic_cast通过检查VTABLE的信息来确定是否可以完成转换,如果可以完成转换则返回需要转换类型的指针,如果不能转换则返回0

2.由于dynamic_cast存在检查是否可以转换的步骤,所以一定程度上增加系统负担,降低了效率。dynamic_cast用在无法确定是否可以完成转换的情况,而当我们能够确定转换双方的类型,并知道可以转换的时候可以采用static_cast来完成转换,此不需要检查操作。

9.头文件和template

头文件规则:“不要放置分配存储空间的任何东西”在template后面的所有东西都在编译器不分配任何人东西,而是到运行时态模板被实例化后才会分配。

这样为了方便阅读和使用最好将所有的模板声明和实现放在头文件中,但是如果在编写库的时候这种做法可能会泄露源代码

 

10.模板类

class Schematic :public vector<Shape*>;

vector<Shape*>是对模板容器vector的实例化,即一个存储指向Shape类型的指针向量。

Schematic继承于vector<Shape*>则拥有了该类型的成员变量和方法

又vector存放的是Shape*则所有指向Shape及其派生类的指针都可以存储在Schematic中,将实现自动类型转换,

遍历每个元素调用Shape的公共接口将多态的选择对应的方法!Amazing!!Right??!


11.void*

void*最大的作用:int *ip;void *vp;

vp = ip;

这在函数返回值绑定是很有用


12.系统堆和栈(非数据结构)

系统堆内存分配由new和delete进行(可以借助智能指针,防止忘记删除delete)

系统栈由编译器为变量分配,根据变量的生命周期来分配和释放。

 

到此多态和虚函数的基础细节就结束了,下面将进入一些高级议题的学习。加油!

  评论这张
 
阅读(66)| 评论(1)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017