成长记录

欢迎喜欢计算机技术的朋友进来交流
 
Jason @ 2008-11-01 14:40

http://blog.csdn.net/thomashuang/archive/2004/06/07/22393.aspx



 
Jason @ 2008-10-26 14:05

在过去的学习中,我们始终接触的单个类的继承,但是在现实生活中,一些新事物往往会拥有两个或者两个以上事物的属性,为了解决这个问题,C++引入了多重继承的概念,C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承

举个例子,交通工具类可以派生出汽车和船连个子类,但拥有汽车和船共同特性水陆两用汽车就必须继承来自汽车类与船类的共同属性。

  由此我们不难想出如下的图例与代码:

  当一个派生类要使用多重继承的时候,必须在派生类名和冒号之后列出所有基类的类名,并用逗好分隔。

//程序作者:管宁   
//站点:www.cndev-lab.com   
//所有稿件均有版权,如要转载,请务必著名出处和作者   
 
#include <iostream
using namespace std; 
 
class Vehicle 

    public
        Vehicle(int weight = 0) 
        { 
            Vehicle::weight = weight; 
        } 
        void SetWeight(int weight) 
        { 
            cout<<"重新设置重量"<<endl; 
            Vehicle::weight = weight; 
        } 
        virtual void ShowMe() = 0; 
    protected
        int weight; 
}; 
class Car:public Vehicle//汽车 

    public
        Car(int weight=0,int aird=0):Vehicle(weight) 
        { 
            Car::aird = aird; 
        } 
        void ShowMe() 
        { 
            cout<<"我是汽车!"<<endl; 
        } 
    protected
        int aird; 
}; 
 
class Boat:public Vehicle//船 

    public
        Boat(int weight=0,float tonnage=0):Vehicle(weight) 
        { 
            Boat::tonnage = tonnage; 
        } 
        void ShowMe() 
        { 
            cout<<"我是船!"<<endl; 
        } 
    protected
        float tonnage; 
}; 
 
class AmphibianCar:public Car,public Boat//水陆两用汽车,多重继承的体现 

    public
        AmphibianCar(int weight,int aird,float tonnage) 
        :Vehicle(weight),Car(weight,aird),Boat(weight,tonnage) 
        //多重继承要注意调用基类构造函数 
        { 
         
        } 
        void ShowMe() 
        { 
            cout<<"我是水陆两用汽车!"<<endl; 
        } 
}; 
int main() 

    AmphibianCar a(4,200,1.35f);//错误 
    a.SetWeight(3);//错误 
    system("pause");  
}

  上面的代码从表面看,看不出有明显的语发错误,但是它是不能够通过编译的。这有是为什么呢?
  这是由于多重继承带来的继承的模糊性带来的问题。 
先看如下的图示:

 

  在图中深红色标记出来的地方正是主要问题所在,水陆两用汽车类继承了来自Car类与Boat类的属性与方法,Car类与Boat类同为AmphibianCar类的基类,在内存分配上AmphibianCar获得了来自两个类的SetWeight()成员函数,当我们调用a.SetWeight(3)的时候计算机不知道如何选择分别属于两个基类的被重复拥有了的类成员函数SetWeight()。

  由于这种模糊问题的存在同样也导致了AmphibianCar a(4,200,1.35f);执行失败,系统会产生Vehicle”不是基或成员的错误。

  以上面的代码为例,我们要想让AmphibianCar类既获得一个Vehicle的拷贝,而且又同时共享用Car类与Boat类的数据成员与成员函数就必须通过C++所提供的虚拟继承技术来实现。

  我们在Car类和Boat类继承Vehicle类出,在前面加上virtual关键字就可以实现虚拟继承,使用虚拟继承后,当系统碰到多重继承的时候就会自动先加入一个Vehicle的拷贝,当再次请求一个Vehicle的拷贝的时候就会被忽略,保证继承类成员函数的唯一性

  修改后的代码如下,注意观察变化:

//程序作者:管宁   
//站点:www.cndev-lab.com   
//所有稿件均有版权,如要转载,请务必著名出处和作者   
 
#include <iostream
using namespace std; 
 
class Vehicle 

    public
        Vehicle(int weight = 0) 
        { 
            Vehicle::weight = weight; 
            cout<<"载入Vehicle类构造函数"<<endl; 
        } 
        void SetWeight(int weight) 
        { 
            cout<<"重新设置重量"<<endl; 
            Vehicle::weight = weight; 
        } 
        virtual void ShowMe() = 0; 
    protected
        int weight; 
}; 
class Car:virtual public Vehicle//汽车,这里是虚拟继承 

    public
        Car(int weight=0,int aird=0):Vehicle(weight) 
        { 
            Car::aird = aird; 
            cout<<"载入Car类构造函数"<<endl; 
        } 
        void ShowMe() 
        { 
            cout<<"我是汽车!"<<endl; 
        } 
    protected
        int aird; 
}; 
 
class Boat:virtual public Vehicle//船,这里是虚拟继承 

    public
        Boat(int weight=0,float tonnage=0):Vehicle(weight) 
        { 
            Boat::tonnage = tonnage; 
            cout<<"载入Boat类构造函数"<<endl; 
        } 
        void ShowMe() 
        { 
            cout<<"我是船!"<<endl; 
        } 
    protected
        float tonnage; 
}; 
 
class AmphibianCar:public Car,public Boat//水陆两用汽车,多重继承的体现 

    public
        AmphibianCar(int weight,int aird,float tonnage) 
        :Vehicle(weight),Car(weight,aird),Boat(weight,tonnage) 
        //多重继承要注意调用基类构造函数 
        { 
            cout<<"载入AmphibianCar类构造函数"<<endl; 
        } 
        void ShowMe() 
        { 
            cout<<"我是水陆两用汽车!"<<endl; 
        } 
        void ShowMembers() 
        { 
            cout<<"重量:"<<weight<<"顿,"<<"空气排量:"<<aird<<"CC,"<<"排水量:"<<tonnage<<"顿"<<endl; 
        } 
}; 
int main() 

    AmphibianCar a(4,200,1.35f); 
    a.ShowMe(); 
    a.ShowMembers(); 
    a.SetWeight(3); 
    a.ShowMembers(); 
    system("pause");  
}

  注意观察类构造函数的构造顺序。

  虽然说虚拟继承与虚函数有一定相似的地方,但读者务必要记住,他们之间是绝对没有任何联系的!



 
Jason @ 2008-10-24 21:27

C++之父Bjarn Stroustrup对C++语言概括的第一条就是"a better C"。struct这个关键字就来源于C。而在C++中,struct的含义已经
和C中的struct不尽相同了。在C++中,用户定义类型,也就是class,拥有和内建类型一样的地位。这可以从C++中struct定义的类
型在声明变量时不必再写出struct关键字看出。如

  

  struct Foo {

  // ...

  };

  

  Foo f;

  在C++程序中,人们似乎更热衷于使用class,而几乎忽略了struct的存在。实际上,struct就是成员默认为public的class(在
class中,成员默认为private)。事实上以下两端代码完全等价:

  代码一:

  

  struct Foo {

  

  // ...

  

  };

  代码二:

  

  class Foo {

  

  public:

  

  // ...

  

  };

  那么为什么要有struct的存在呢?首先的原因自然是保持对C的兼容。原先的C代码可以不必修改就成为合法的C++代码。第二
个原因(个人愚见),是为了让struct来表示抽象的数据类型以及抽象接口,而与class所表示的类的概念相区别。

  struct在C中的使用方式转移到C++以后仍然是非常重要,作为一组相关的数据而存在于一个struct,说明了他们在逻辑上是相
互关联的数据,同时他们被保存在struct里而不是全局变量,也是对数据的一种管理。个人认为一种很朴素的用法要取代当前很热
门的get-set用法。比如

  

  class Foo {

   int bar;

  public:

   int get_bar() { return bar; }

   void set_bar(const int& b) { bar = b; }

  };
  这里Foo::bar是一个需要频繁存取的数据对象,它作为类Foo的私有成员存在,而通过公共接口存取。这是面向对象思想中数
据封装的体现。而考虑一下这个Foo::bar是否有必要成为私有成员?没有,因为它就是一个数据,没有必要用私有类成员的思想来
封装它,似乎可以看成是面向对象思想的过度滥用。等效的可以写成:

  

  struct Foo {

   int bar;

  };

  然后通过普通的赋值操作来完成。这样做似乎是回到了原始时代,但没有必要用的就不要用,否则还会影响效率。 其次一个
struct的应用就是来描述纯虚类,也就是后来Java语言中类似接口的东西:

  

  struct Foo {

   virtual void Bar1() = 0;

   virtual void Bar2() = 0;

   // ...

  };

  使用struct可以些许节省编译器的语法分析时间:),而且能在语义上表达的更为清楚。

  一般来讲,当一个类中有必要进行数据隐藏时,请用class声明,并将私有数据标记为private,公共接口标记为public;而当所
有成员都有必要成为公有成员的时候,请用struct来声明它。



 
Jason @ 2008-10-24 08:59

引言

  指针是C/C++语言的特色,而数组名与指针有太多的相似,甚至很多时候,数组名可以作为指针使用。于是乎,很多程序设计者就被搞糊涂了。而许多的大学老师,他们在C语言的教学过程中也错误得给学生讲解:"数组名就是指针"。很幸运,我的大学老师就是其中之一。时至今日,我日复一日地进行着C/C++项目的开发,而身边还一直充满这样的程序员,他们保留着"数组名就是指针"的误解。

  想必这种误解的根源在于国内某著名的C程序设计教程。如果这篇文章能够纠正许多中国程序员对数组名和指针的误解,笔者就不甚欣慰了。借此文,笔者站在无数对知识如饥似渴的中国程序员之中,深深寄希望于国内的计算机图书编写者们,能以"深入探索"的思维方式和精益求精的认真态度来对待图书编写工作,但愿市面上多一些融入作者思考结晶的心血之作!

  魔幻数组名

  请看程序(本文程序在WIN32平台下编译):

1. #include <iostream.h>
2. int main(int argc, char* argv[])
3. {
4.  char str[10];
5.  char *pStr = str;
6.  cout << sizeof(str) << endl;
7.  cout << sizeof(pStr) << endl;
8.  return 0;
9. }

  1、数组名不是指针

  我们先来推翻"数组名就是指针"的说法,用反证法。

  证明 数组名不是指针

  假设:数组名是指针;

  则:pStr和str都是指针;

  因为:在WIN32平台下,指针长度为4;

  所以:第6行和第7行的输出都应该为4;

  实际情况是:第6行输出10,第7行输出4;

  所以:假设不成立,数组名不是指针

  2、数组名神似指针

  上面我们已经证明了数组名的确不是指针,但是我们再看看程序的第5行。该行程序将数组名直接赋值给指针,这显得数组名又的确是个指针!

  我们还可以发现数组名显得像指针的例子:

1. #include <string.h>
2. #include <iostream.h>
3. int main(int argc, char* argv[])
4. {
5.  char str1[10] = "I Love U";
6.  char str2[10];
7.  strcpy(str2,str1);
8.  cout << "string array 1: " << str1 << endl;
9.  cout << "string array 2: " << str2 << endl;
10.  return 0;
11. }

  标准C库函数strcpy的函数原形中能接纳的两个参数都为char型指针,而我们在调用中传给它的却是两个数组名!函数输出:

string array 1: I Love U
string array 2: I Love U

  数组名再一次显得像指针!

  既然数组名不是指针,而为什么到处都把数组名当指针用?于是乎,许多程序员得出这样的结论:数组名(主)是(谓)不是指针的指针(宾)。

  整个一魔鬼。

  揭密数组名

  现在到揭露数组名本质的时候了,先给出三个结论:

  (1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组;

  (2)数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量;

  (3)指向数组的指针则是另外一种变量类型(在WIN32平台下,长度为4),仅仅意味着数组的存放地址!

  1、数组名指代一种数据结构:数组

  现在可以解释为什么第1个程序第6行的输出为10的问题,根据结论1,数组名str的内涵为一种数据结构,即一个长度为10的char型数组,所以sizeof(str)的结果为这个数据结构占据的内存大小:10字节。

  再看:

1. int intArray[10];
2. cout << sizeof(intArray) ;

  第2行的输出结果为40(整型数组占据的内存空间大小)。

  如果C/C++程序可以这样写:

1. int[10] intArray;
2. cout << sizeof(intArray) ;

  我们就都明白了,intArray定义为int[10]这种数据结构的一个实例,可惜啊,C/C++目前并不支持这种定义方式。

  2、数组名可作为指针常量

  根据结论2,数组名可以转换为指向其指代实体的指针,所以程序1中的第5行数组名直接赋值给指针,程序2第7行直接将数组名作为指针形参都可成立。

  下面的程序成立吗?

1. int intArray[10];
2. intArray++;

  读者可以编译之,发现编译出错。原因在于,虽然数组名可以转换为指向其指代实体的指针,但是它只能被看作一个指针常量,不能被修改。

  而指针,不管是指向结构体、数组还是基本数据类型的指针,都不包含原始数据结构的内涵,在WIN32平台下,sizeof操作的结果都是4。
顺便纠正一下许多程序员的另一个误解。许多程序员以为sizeof是一个函数,而实际上,它是一个操作符,不过其使用方式看起来的确太像一个函数了。语句sizeof(int)就可以说明sizeof的确不是一个函数,因为函数接纳形参(一个变量),世界上没有一个C/C++函数接纳一个数据类型(如int)为"形参"。

  3、数据名可能失去其数据结构内涵

  到这里似乎数组名魔幻问题已经宣告圆满解决,但是平静的湖面上却再次掀起波浪。请看下面一段程序:

1. #include <iostream.h>
2. void arrayTest(char str[])
3. {
4.  cout << sizeof(str) << endl;
5. }
6. int main(int argc, char* argv[])
7. {
8.  char str1[10] = "I Love U";
9.  arrayTest(str1);
10.  return 0;
11. }

  程序的输出结果为4。不可能吧?

  一个可怕的数字,前面已经提到其为指针的长度!

  结论1指出,数据名内涵为数组这种数据结构,在arrayTest函数体内,str是数组名,那为什么sizeof的结果却是指针的长度?这是因为:

  (1)数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;

  (2)很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

  所以,数据名作为函数形参时,其全面沦落为一个普通指针!它的贵族身份被剥夺,成了一个地地道道的只拥有4个字节的平民。

  以上就是结论4。

  结束语

  最后,笔者再次表达深深的希望,愿我和我的同道中人能够真正以谨慎的研究态度来认真思考开发中的问题,这样才能在我们中间产生大师级的程序员,顶级的开发书籍。每次拿着美国鬼子的开发书籍,我们不免发出这样的感慨:我们落后太远了。


 
Jason @ 2008-10-16 14:13

C中  
  #define   NULL   ((void*)0)  
  C++中  
  #define   NULL   0  
   
  不管在C还是C++中,'{post.abstract}'(类型为char)可以隐式的转型为int(0的类型),但是不能隐式的和void*只见转型。因此  
  在C++中,NULL,0,'{post.abstract}'可以混用,在C中,空指针测试和赋空指针时使用'{post.abstract}'则会导致编译错误。



 
Jason @ 2008-10-12 23:08

RTTI 是“Runtime Type Information”的缩写,意思是:运行时类型信息。它提供了运行时确定对象类型的方法。本文将简略介绍 RTTI 的一些背景知识、描述 RTTI 的概念,并通过具体例子和代码介绍什么时候使用以及如何使用 RTTI;本文还将详细描述两个重要的 RTTI 运算符的使用方法,它们是 typeid 和 dynamic_cast。
其实,RTTI 在C++中并不是什么新的东西,它早在十多年以前就已经出现了。但是大多数开发人员,包括许多高层次的C++程序员对它并不怎么熟悉,更不用说使用 RTTI 来设计和编写应用程序了。
一些面向对象专家在传播自己的设计理念时,大多都主张在设计和开发中明智地使用虚拟成员函数,而不用 RTTI 机制。但是,在很多情况下,虚拟函数无法克服本身的局限。每每涉及到处理异类容器和根基类层次(如 MFC)时,不可避免要对对象类型进行动态判断,也就是动态类型的侦测。如何确定对象的动态类型呢?答案是使用内建的 RTTI 中的运算符:typeid 和 dynamic_cast。
首先让我们来设计一个类层次,假设我们创建了某个处理文件的抽象基类。它声明下列纯虚拟函数:open()、close()、read()和 write():

class File
{
public:
virtual int open(const string & filename)=0;
virtual int close(const string & filename)=0;
//
virtual ~File()=0; // 记住添加纯虚拟析构函数(dtor)
};
现在从 File 类派生的类要实现基类的纯虚拟函数,同时还要提供一些其他的操作。假设派生类为 DiskFile,除了实现基类的纯虚拟函数外,还要实现自己的flush()和defragment()操作: class DiskFile: public File
{
public:
int open(const string & filename);

// 实现其他的纯虚拟函数
......

// 自己的专有操作
virtual int flush();
virtual int defragment();
};
接着,又从 DiskFile 类派生两个类,假设为 TextFile 和 MediaFile。前者针对文本文件,后者针对音频和视频文件: class TextFile: public DiskFile
{
// ......
int sort_by_words();
};

class MediaFile: public DiskFile
{
//......
};
我们之所以要创建这样的类层次,是因为这样做以后可以创建多态对象,如:File *pfile; // *pfile的静态类型是 File
if(some_condition)
pfile = new TextFile; // 动态类型是 TextFile
else
pfile = new DiskFile; // 动态类型是 DiskFile
假设你正在开发一个基于图形用户界面(GUI)的文件管理器,每个文件都可以以图标方式显示。当鼠标移到图标上并单击右键时,文件管理器打开一个菜单,每个文件除了共同的菜单项,不同的文件类型还有不同的菜单项。如:共同的菜单项有“打开”“拷贝”、和“粘贴”,此外,还有一些针对特殊文件的专门操作。比如,文本文件会有“编辑”操作,而多媒体文件则会有“播放”菜单。为了使用 RTTI 来动态定制菜单,文件管理器必须侦测每个文件的动态类型。利用 运算符 typeid 可以获取与某个对象关联的运行时类型信息。typeid 有一个参数,传递对象或类型名。因此,为了确定 x 的动态类型是不是Y,可以用表达式:typeid(x) == typeid(Y)实现:#include <typeinfo> // typeid 需要的头文件
void menu::build(const File * pfile)
{
if (typeid(*pfile)==typeid(TextFile))
{
add_option("edit");
}
else if (typeid(*pfile)==typeid(MediaFile))
{
add_option("play");
}
}
使用 typeid 要注意一个问题,那就是某些编译器(如 Visual C++)默认状态是禁用 RTTI 的,目的是消除性能上的开销。如果你的程序确实使用了 RTTI,一定要记住在编译前启用 RTTI。使用 typeid 可能产生一些将来的维护问题。假设你决定扩展上述的类层次,从MediaFile 派生另一个叫 LocalizeMedia 的类,用这个类表示带有不同语言说明文字的媒体文件。但 LocalizeMedia 本质上还是个 MediaFile 类型的文件。因此,当用户在该类文件图标上单击右键时,文件管理器必须提供一个“播放”菜单。可惜 build()成员函数会调用失败,原因是你没有检查这种特定的文件类型。为了解决这个问题,你必须象下面这样对 build() 打补丁: void menu::build(const File * pfile)
{

//......

else if (typeid(*pfile)==typeid(LocalizedMedia))
{
add_option("play");
}
}
唉,这种做法真是显得太业余了,以后每次添加新的类,毫无疑问都必须打类似的补丁。显然,这不是一个理想的解决方案。这个时候我们就要用到 dynamic_cast,这个运算符用于多态编程中保证在运行时发生正确的转换(即编译器无法验证是否发生正确的转换)。用它来确定某个对象是 MediaFile 对象还是它的派生类对象。dynamic_cast 常用于从多态编程基类指针向派生类指针的向下类型转换。它有两个参数:一个是类型名;另一个是多态对象的指针或引用。其功能是在运行时将对象强制转换为目标类型并返回布尔型结果。也就是说,如果该函数成功地并且是动态的将 *pfile 强制转换为 MediaFile,那么 pfile的动态类型是 MediaFile 或者是它的派生类。否则,pfile 则为其它的类型:void menu::build(const File * pfile)
{
if (dynamic_cast <MediaFile *> (pfile))
{
// pfile 是 MediaFile 或者是MediaFile的派生类 LocalizedMedia
add_option("play");
}
else if (dynamic_cast <TextFile*> (pfile))
{
// pfile 是 TextFile 是TextFile的派生类
add_option("edit");
}
}
细细想一下,虽然使用 dynamic_cast 确实很好地解决了我们的问题,但也需要我们付出代价,那就是与 typeid 相比,dynamic_cast 不是一个常量时间的操作。为了确定是否能完成强制类型转换,dynamic_cast`必须在运行时进行一些转换细节操作。因此在使用 dynamic_cast 操作时,应该权衡对性能的影响。



 
Jason @ 2008-10-12 13:21

陈皓

http://blog.csdn.net/haoel

 

 

前言

 

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

 

 

关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。

 

当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。

 

言归正传,让我们一起进入虚函数的世界。

 

 

虚函数表

 

C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、重载的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

 

这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

 

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。

 

假设我们有这样的一个类:

 

class Base {

     public:

            virtual void f() { cout << "Base::f" << endl; }

            virtual void g() { cout << "Base::g" << endl; }

            virtual void h() { cout << "Base::h" << endl; }

 

};

 

按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

 

          typedef void(*Fun)(void);

 

            Base b;

 

            Fun pFun = NULL;

 

            cout << "虚函数表地址:" << (int*)(&b) << endl;

            cout << "虚函数表第一个函数地址:" << (int*)*(int*)(&b) << endl;

 

            // Invoke the first virtual function 

            pFun = (Fun)*((int*)*(int*)(&b));

            pFun();

 

实际运行经果如下:(Windows XP+VS2003,  Linux 2.6.22 + GCC 4.1.3)

 

虚函数表地址:0012FED4

虚函数表第一个函数地址:0044F148

Base::f

 

 

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()Base::h(),其代码如下:

 

            (Fun)*((int*)*(int*)(&b)+0);  // Base::f()

            (Fun)*((int*)*(int*)(&b)+1);  // Base::g()

            (Fun)*((int*)*(int*)(&b)+2);  // Base::h()

 

这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“{post.abstract}”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

 

 

下面,我将分别说明“无重载”和“有重载”时的虚函数表的样子。没有重载父类的虚函数是毫无意义的。我之所以要讲述没有重载的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

 

一般继承(无虚函数重载)

 

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

 

 

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

 

对于实例:Derive d; 的虚函数表如下:

 

我们可以看到下面几点:

1)虚函数按照其声明顺序放于表中。

2)父类的虚函数在子类的虚函数前面。

 

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

 

 

 

一般继承(有虚函数重载)

 

重载父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

 

 

 

为了让大家看到被继承过后的效果,在这个类的设计中,我只重载了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

 

 

我们从表中可以看到下面几点,

1)重载的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被重载的函数依旧。

 

这样,我们就可以看到对于下面这样的程序,

 

            Base *b = new Derive();

 

            b->f();

 

b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

 

 

 

多重继承(无虚函数重载)

 

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有重载复类的函数。

 

 

 

对于子类实例中的虚函数表,是下面这个样子:

 

我们可以看到:

1)  每个父类都有自己的虚表。

2)  子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

 

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

 

 

 

 

多重继承(有虚函数重载)

 

下面我们再来看看,如果发生虚函数重载的情况。

 

下图中,我们重载了父类的f()函数。

 

 

 

下面是对于子类实例中的虚函数表的图:

 

 

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

 

            Derive d;

            Base1 *b1 = &d;

            Base2 *b2 = &d;

            Base3 *b3 = &d;

            b1->f(); //Derive::f()

            b2->f(); //Derive::f()

            b3->f(); //Derive::f()

 

            b1->g(); //Base1::g()

            b2->g(); //Base2::g()

            b3->g(); //Base3::g()

 

 

安全性

 

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

 

一、通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

 

          Base1 *b1 = new Derive();

            b1->f1();  //编译出错

 

任何妄图使用父类指针想调用子类中的未重载父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

 

 

二、访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

 

如:

 

class Base {

    private:

            virtual void f() { cout << "Base::f" << endl; }

 

};

 

class Derive : public Base{

 

};

 

typedef void(*Fun)(void);

 

void main() {

    Derive d;

    Fun  pFun = (Fun)*((int*)*(int*)(&d)+0);

    pFun();

}

 

 

结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

 

在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了,目前是软件开发技术主管,技术方面,主攻Unix/C/C++,比较喜欢网络上的技术,比如分布式计算,网格计算,P2PAjax等一切和互联网相关的东西。管理方面比较擅长于团队建设,技术趋势分析,项目管理。欢迎大家和我交流,我的MSNEmail是:haoel@hotmail.com 

 

附录一:VC中查看虚函数表

 

我们可以在VCIDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的)

附录 二:例程

下面是一个关于多重继承的虚函数表访问的例程:

 

#include <iostream>

using namespace std;

 

class Base1 {

public:

            virtual void f() { cout << "Base1::f" << endl; }

            virtual void g() { cout << "Base1::g" << endl; }

            virtual void h() { cout << "Base1::h" << endl; }

 

};

 

class Base2 {

public:

            virtual void f() { cout << "Base2::f" << endl; }

            virtual void g() { cout << "Base2::g" << endl; }

            virtual void h() { cout << "Base2::h" << endl; }

};

 

class Base3 {

public:

            virtual void f() { cout << "Base3::f" << endl; }

            virtual void g() { cout << "Base3::g" << endl; }

            virtual void h() { cout << "Base3::h" << endl; }

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

            virtual void f() { cout << "Driver::f" << endl; }

            virtual void g1() { cout << "Driver::g1" << endl; }

};

 

 

typedef void(*Fun)(void);

 

int main()

{

            Fun pFun = NULL;

 

            Derive d;

            int** pVtab = (int**)&d;

 

            //Base1's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

            pFun = (Fun)pVtab[0][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

            pFun = (Fun)pVtab[0][1];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

            pFun = (Fun)pVtab[0][2];

            pFun();

 

            //Derive's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

            pFun = (Fun)pVtab[0][3];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[0][4];

            cout<<pFun<<endl;

 

 

            //Base2's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[1][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[1][1];

            pFun();

 

            pFun = (Fun)pVtab[1][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[1][3];

            cout<<pFun<<endl;

 

 

 

            //Base3's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[2][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[2][1];

            pFun();

 

            pFun = (Fun)pVtab[2][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[2][3];

            cout<<pFun<<endl;

 

            return 0;

}

 

(转载时请注明作者和出处。未经许可,请勿用于商业用途)



 
Jason @ 2008-10-11 19:59

股票价格(Stock Price)

目录

[隐藏]
type=text/javascript> if (window.showTocToggle) { var tocShowText = "显示"; var tocHideText = "隐藏"; showTocToggle(); }

什么是股票价格

  股票本身没有价值,但它可以当做商品出卖,并且有一定的价格。股票价格又叫股票行市,是指股票在证券市场上买卖的价格。

股票价格的分类

  股票有市场价格和理论价格之分。

  1.股票的市场价格

  股票的市场价格即股票在股票市场上买卖的价格。股票市场可分为发行市场流通市场,因而,股票的市场价格也就有发行价格和流通价格的区分。股票的发行价格就是发行公司与证券承销商议定的价格。股票发行价格的确定有三种情况:

  (1)股票的发行价格就是股票票面价值

  (2)股票的发行价格以股票在流通市场上的价格为基准来确定。

  (3)股票的发行价格在股票面值与市场流通价格之间,通常是对原有股东有偿配股时采用这种价格。国际市场上确定股票发行价格的参考公式是:

  股票发行价格=市盈率还原值×40%+股息还原率×20%+每股净值×20%+预计当年股息与一年期存款利率还原值×20%

  这个公式全面地考虑了影响股票发行价格的若干因素,如利率股息、流通市场的股票价格等,值得借鉴。

  股票在流通市场上的价格,才是完全意义上的股票的市场价格,一般称为股票市价或股票行市。股票市价表现为开盘价收盘价最高价最低价等形式。

  其中收盘价最重要,是分析股市行情时采用的基本数据。

  2.股票的理论价格

  股票代表的是持有者的股东权。这种股东权的直接经济利益,表现为股息、红利收入。股票的理论价格,就是为获得这种股息、红利收入的请求权而付出的代价,是股息资本化的表现。

  静态地看,股息收入与利息收入具有同样的意义。投资者是把资金投资于股票还是存于银行,这首先取决于哪一种投资的收益率高。按照等量资本获得等量收入的理论,如果股息率高于利息率,人们对股票的需求就会增加,股票价格就会上涨,从而股息率就会下降,一直降到股息率市场利率大体一致为止。按照这种分析,可以得出股票的理论价格公式为:

  股票理论价格=股息红利收益/市场利率

股票价格的偏向性特征

  1.定义

  股票价格的偏向性特征指股票价格总体上具有不断向上增长的长期历史趋势,这是基金长期投资能够赢利的重要理论依据。

  2.表现形式

  偏向性特征主要体现在:

  • 它是一种总体表现上的特征,而非个体表现上的特征。
  • 偏向性特征是一种长期历史趋势,而非短期市场现象。这是所说的“长期”,是指长达30年、50年、100年、200年这样的历史跨度。

  3.影响因素

  股市价格波动的偏向型特征,主要受以下两个因素影响:

  • 上市公司整体中最活跃的处于生命周期上升阶段公司的股价波动,是偏向性的主要推动力。
  • 上述推动过程中以“接力”方式形成的不断接替的动力。

  4.几点启示

  股票价格波动的偏向性是以大量企业在激烈的市场竞争中被淘汰为代价而获得的。不理解这一点,投资者就很容易简单地从股价波动的偏向性特征中直接推导出简单地长期持有的股票投资战略。如果没有相应的投资风险分散化的资金管理措施,股价波动的偏向性并不能保证投资者获得满意的投资回报。

股票的理论价格与市场价格的关系

  股票的理论价格不等于股票的市场价格,两者甚至有相当大的差距。但是,股票的理论价格为预测股票市场价格的变动趋势提供了重要的依据,也是股票市场价格形成的一个基础性因素。

股票价格和企业业绩的关系

  股票带给持有者的现金流入包括两部分: 股利收入和出售时的资本利得。股票的内在价值由一系列的股利和将来出售股票时售价的现值所构成。如果股东永远持有股票,他只获得股利,是一个永续的现金流入。这个现金流入的现值就是股票的价值。如果投资者不打算永久地持有该股票,而在一段时间后出售,它的未来现金流入是几次股利和出售时的股价。这种情况在实际应用中,面临的主要问题是如何预计未来每年的股利,以及如何确定贴现率

  股利的多少,取决于每股盈利股利支付率两个因素。对其估计的方法是历史资料的统计分析,例如回归分析时间序列分析法趋势分析法等。股票评价的基本模型要求无限期的预计历年的股利,实际上是不可能做到的。因此,应用的模型都是各种简化的办法,如假设每年股利相同或固定比率增长等。贴现率的主要作用是把所有未来不同时间的现金流入折算为现在的价值。折算现值的比率应当使投资者所要求的收益率。那么,投资者要求的收益率应当是多少呢?一种办法是股票历史上长期的收益率来确定。据计算,美国普通股在历史上长期的收益率为8%-9%。这种方法的缺点是:过去的情况未必是和将来的发展;历史上不同时期的收益率高低不同,不好判断那一个更适用。

  另一种方法是参照债券的收益率,加上一定的风险报酬率来确定。还有一种更常见的方法是直接使用市场利率。因为投资者要求的收益率一般不低于市场利率,市场利率是投资与股票的机会成本,所以市场利率可以作为贴现率。假设未来股利不变,其支付过程是一个永续年金,则股票的价值可用公式计算出来;但是企业的股利不是固定不变的,假设股利每年按照固定的比率增长,其股票的价值也可以用公式计算出来。

  理论上的股票价值评价方法是完美的、无懈可击的。但是,在实践应用时它有一个致命的缺陷:它的结论是建立在严格的假设之上的,即我们假定所有年度的股利都是能够准确预测的,然后通过对股利的贴现计算出股票的价值。存现实经济生活中,这是完全不可能做到的,我们不可能准确预计五年后一个公司能获得多少利润,更可能预计遥远未来的某一年那个公司的年度利润,甚至我们都无法确定到那时这个公司还是否存在。

  既然事实上股票价值难以用不可把握的未来预期股利来计算,那么究竟股票的价值是以什么为根据的?广大投资者是怎样确定股票的价值的?根据有效市场理论,在证券市场上,广大理性的投资者是根据对股票价值的无偏估计来确定股票价格的。投资者估计股票价值的依据就是基干对企业目前业绩的全面评价,有关信息的来源主要是上市公司公开发布的财务报表及与该公司有关的其它资料。也就是说,投资者对股票价值的判断即使是以将来预期的收益为理论根据的,这个预期的收益也是以企业目前已经公布的财务资料为基础的。

  基于这样一个判断,我们可以试图找出股票价值同财务数据之间的数量关系。如果能够把股票价值同公开的财务数据之间的关系定量地表示出来,那么广大的投资者就有了一个简便易行的方法来确定股票的价值,进而也有了一个科学的评价上市公司企业业绩的方法,同时,推而广之,这个定量方法也可以用来评价非上市公司业的业绩。这样,我们就有了一个简便易行、科学有效的可适用于所有企业的业绩评价方法。

影响股票价格的因素

  股票的市场价格由股票的价值所决定,但同时受许多其他因素的影响。一般地看,影响股票市场价格的因素主要有以下几个方面:

  1.宏观因素

  包括对股票市场价格可能产生影响的社会、政治、经济、文化等方面。

  A.宏观经济因素。即宏观经济环境状况及其变动对股票市场价格的影响,包括宏观经济运行的周期性波动等规律性因素和政府实施的经济政策等政策性因素。股票市场是整个金融市场体系的重要组成部分,上市公司是宏观经济运行微观基础中的重要主体,因此股票市场的股票价格理所当然地会随宏观经济运行状况的变动而变动,会因宏观经济政策的调整而调整。例如,一般地讲,股票价格会随国民生产总值的升降而涨落。

  B.政治因素。即影响股票市场价格变动的政治事件。一国的政局是否稳定对股票市场有着直接的影响。一般而言,政局稳定则股票市场稳定运行;相反,政局不稳则常常引起股票市场价格下跌。除此之外,国家的首脑更换、罢工、主要产油国的动乱等也对股票市场有重大影响。

  C.法律因素。即一国的法律特别是股票市场的法律规范状况。一般来说,法律不健全的股票市场更具有投机性,震荡剧烈,涨跌无序,人为操纵成分大,不正当交易较多;反之,法律法规体系比较完善,制度和监管机制比较健全的股票市场,证券从业人员营私舞弊的机会较少,股票价格受人为操纵的情况也较少,因而表现得相对稳定和正常。总体上说,新兴的股票市场往往不够规范,而成熟的股票市场法律法规体系则比较健全。

  D.军事因素。主要是指军事冲突。军事冲突是一国国内或国与国之间、国际利益集团与国际利益集团之间的矛盾发展到不可以采取政治手段来解决的程度的结果。军事冲突小则造成一个国家内部或一个地区的社会经济生活的动荡,大则打破正常的国际秩序。它使股票市场的正常交易遭到破坏,因而必然导致相关的股票价格的剧烈动荡。例如,海湾战争之初,世界主要股市均呈下跌之势,而且随着战局的不断变化,股市均大幅振荡。

  E.文化、自然因素。就文化因素而言,一个国家的文化传统往往在很大程度上决定着人们的储蓄和投资心理,从而影响股票市场资金流入流出的格局,进而影响股票市场价格;证券投资者的文化素质状况则从投资决策的角度影响着股票市场。一般地,文化素质较高的证券投资者在投资时相对较为理性,如果证券投资者的整体文化素质较高,则股票市场价格相对比较稳定;相反,如果证券投资者的整体文化素质偏低,则股票市场价格容易出现暴涨暴跌。在自然方面,如发生自然灾害,生产经营就会受到影响,从而导致有关股票价格下跌;反之,如进入恢复重建阶段,由于投入大量增加,对相关物品的需求也大量增加,从而导致相关股票价格的上升。

  2.产业和区域因素

  主要是指产业发展前景和区域经济发展状况对股票市场价格的影响。它是介于宏观和微观之间的一种中观影响因素,因而它对股票市场价格的影响主要是结构性的。

  A.在产业方面,每一种产业都会经历一个由成长到衰退的发展过程,这个过程称为产业的生命周期。产业的生命周期通常分为四个阶段,即初创期、成长期、稳定期、衰退期。处于不同发展阶段的产业在经营状况及发展前景方面有较大差异,这必然会反映在股票价格上。蒸蒸日上的产业股票价格呈上升趋势,日见衰落的产业股票价格则逐渐下落。

  B.在区域方面,由于区域经济发展状况、区域对外交通与信息沟通的便利程度、区域内的投资活跃程度等的不同,分属于各区域的股票价格自然也会存在差异,即便是相同产业的股票也是如此。经济发展较快、交通便利、信息化程度高的地区,投资活跃,股票投资有较好的预期;相反,经济发展迟缓、交通不便、信息闭塞的地区,其股票价格总体上呈平淡下跌趋势。

  3.公司因素

  即上市公司的运营对股票价格的影响。上市公司是发行股票筹集资金的运用者,也是资金使用的投资收益的实现者,因而其经营状况的好坏对股票价格的影响极大。而其经营管理水平、科技开发能力、产业内的竞争实力与竞争地位、财务状况等无不关系着其运营状况,因而从各个不同的方面影响着股票的市场价格。由于产权边界明确,公司因素一般只对本公司的股票市场价格产生深刻的影响,是一种典型的微观影响因素。

  4.市场因素

  即影响股票市场价格的各种股票市场操作。例如,看涨看跌买空卖空、追涨与杀跌、获利平仓与解套或割肉等行为,不规范的股票市场中还存在诸如分仓、串谋、轮炒等违法违规操纵股票市场的操作行为。一般而言,如果股票市场的做多行为多于做空行为,则股票价格上涨;反之,如果做空行为占上风,则股票价格趋于下跌。由于各种股票市场操作行为主要是短期行为,因而市场因素对股票市场价格的影响具有明显的短期性质。

  在以上影响股票市场价格的诸多因素中,宏观因素、产业和区域因素及公司因素主要是通过影响股票发行主体即公司的经营状况和发展前景来影响股票市场价格,它们在股票市场之外,因而被称为基本因素。基本因素的变动形成了股票市场价格变动的主要利多题材和利空依据。市场因素则主要是通过投资者的买卖操作来影响股票市场价格,它存在于股票市场内部,与基本因素没有直接的关联,因而被称为技术因素。技术因素是技术分析的对象。

股票价格分析方法

  股票价格分析的方法很多,其中主要的是基本分析法,也称为基本面分析方法。基本分析法就是利用丰富的统计资料,运用多种多样的经济指标,采用比例、动态的分析方法从研究宏观的经济大气候开始,逐步开始中观的行业兴衰分析,进而根据微观的企业经营、盈利的现状和前景,从中对企业所发行的股票作出接近显示现实的客观的评价,并尽可能预测其未来的变化,作为投资者选购的依据。由于它具有比较系统的理论,受到学者们的鼓吹,成为股价分析的主流。

  基本分析法是准备做长线交易的股民以及“业余”股民所应采取的最主要,也是最重要的分析方法。因为这种分析方法是从分析股票的内在价值来入手的,而把对股票市场的大环境的分析结果摆在次位,看好一支股票时,看中的是它的内在潜力与长期发展的良好前景,所以当我们采用这种分析法来进行完预测分析并在适当的时机购入了具体的股票后,就可不必耗费太多的时间与精力去关心股票价格的实时走势了。



 
日历
网志分类
『所有网志』 (322)
生活 (41)
工作 (11)
运动 (3)
情感 (1)
java (65)
c/c++ (16)
.net (23)
linux (6)
网络 (73)
数据结构与算法 (6)
数据库 (4)
体系结构与编译技术 (2)
Flash (0)
图形图像 (0)
黑客与系统安全 (8)
病毒 (0)
XML (1)
windows视窗 (2)
操作系统 (13)
wimax (12)
手机 (14)
数学 (4)
日语 (0)
法语 (0)
美剧 (0)
计算机硬件 (6)
最新留言
06/16 好 我们考试就...
站内搜索
友情链接
我的歪酷 非非共享界
订阅 RSS
0047905
歪酷博客