打印

翻了一下前几年的C++教案,觉得还有点价值,将摘选帖出

[复制链接]
楼主: HWM
手机看帖
扫描二维码
随时随地手机跟帖
61
HWM|  楼主 | 2008-4-24 21:13 | 只看该作者 回帖奖励 |倒序浏览

C++ 随笔-25,虚函数的返祖问题


在论述对象的返祖时曾经提到,对于虚函数而言,用作用域和函数返祖效果是不一样的。现在具体阐述一下它们的差异。先给出下面程序,其中成员函数Display改成虚函数。此外加了一个顶层成员函数Show_it,此函数只是表明Display定义为虚函数的必要性,在这里不起作用。

class point
{
    protected:
    int x, y;

    public:
    point( int x0, int y0 ) { x = x0; y = y0; }
    virtual void Display( void );
    void Show_it( void ) { Display(); }
};

void point::Display( void )
{
// display a point
}

class line : public point
{
    protected:
    int r, d;

    public:
    line( int x0, int y0, int r0, int d0 ) : point( x0, y0 ) { r = r0; d = d0; }
    virtual void Display( void );
};

void line::Display( void )
{
// display a line
}

class circle : public line
{
    public:
    circle( int x0, int y0, int r0 ) : line( x0, y0, r0, 0 ) {}
    virtual void Display( void );
};

void circle::Display( void )
{
// display a circle
}

void Display_line( line& L )
{
    L.Display();
}

circle C(0, 0, 10);

int main( void )
{
    Display_line( C ); // 试图用函数调用返祖,但最终未果

    C.line::Display(); // 采用作用域指定返祖
    
    while ( 1 )
    {
    }
}

先看作用域指定返祖:

    C.line::Display();

由于在调用虚函数时采用了作用域指定,这样一来编译器在此就取消了多态调用机制,采用直接调用类line的成员函数Display。因此对于虚函数而言,采用作用域指定返祖确实可以得到预期的效果。

再看用函数调用返祖:

    Display_line( C );

在函数Display_line内,有一个虚函数Display的调用,而且这个调用采用了多态调用机制(未加作用域指定)。虽然C进入函数Display_line后以类line的身份出现,但对象自身毕竟还是属于类circle。所以根据多态调用机制,可以得到下面的调用路线图:

    this --> x
             y
             VPTR --> VTAB_circle: Display_circle
             r
             d

其中this是指向对象C的指针,VTAB_circle是类circle的虚函数表首地址,Display_circle是在类circle中定义的虚函数Display的入口。由此可见,L.Display调用的是类circle的成员函数Display,而不是类line的成员函数Display。

通过上例,可以看到两点:

    1) 作用域指定使多态性失效

    2) 多态性使返祖失效

使用特权

评论回复
62
hzz137| | 2008-4-24 21:59 | 只看该作者

顶一个!好!

使用特权

评论回复
63
wh111wh| | 2008-4-25 00:36 | 只看该作者

orz

没搞过C++,一直以为面向对象无非就可视化编程,原来还是大有学问啊。
拜读ing。想问楼主这是哪本教材?

使用特权

评论回复
64
xushouxue| | 2008-4-25 12:59 | 只看该作者

教材是买不到的

那是楼主自己编写的,属于非卖品!

使用特权

评论回复
65
yysmcu| | 2008-4-26 13:41 | 只看该作者

楼主的随堂笔记值得收藏起来慢慢子爵

使用特权

评论回复
66
tepow| | 2008-4-26 21:25 | 只看该作者

非常感谢, LZ请继续.

使用特权

评论回复
67
hebeijiang| | 2008-4-27 14:52 | 只看该作者

谢谢!慢慢学习中。

使用特权

评论回复
68
HWM|  楼主 | 2008-4-27 17:07 | 只看该作者

C++ 随笔-26,有关多态性的两个有争议的限制


在C++标准中,规定两种成员函数不能定义为虚函数,它们分别是构造函数和静态成员函数。

先看构造函数

一般认为,由于在调用基类构造函数时当前类对象的虚表指针VPTR还未定义,所以在当前类对象还未完全成熟之前不能对基类的构造函数进行置换。其实这个理由并不充分,因为从逻辑上看,VPTR没有必要必须在调用基类构造函数后再确定。这个限制实际上更多的是一种伦理上的体现,因为儿子篡改父亲的洗礼程序实在是难以理解。反观析构函数,由子类修改父类的后事程序似乎合乎常理,虽然子类先于父类消亡。所以在C++标准中,允许将析构函数定义成虚函数。

再看静态成员函数

静态成员函数不能被定义成虚函数的“理由”一般可归为:在多态调用机制里需要有一个指针this,而静态成员函数调用没有相关的this指针。这个理由更是站不住脚,因为虽然多态调用机制需要一个指针this,但没有必要作为参数传给被调用的成员函数。反过来,如果产生多态调用的那个函数是静态成员函数的话,这个this指针就真的得不到了。静态成员函数不能被定义为虚函数的真正理由是因为“多态”和“静态”的通常含义相差太远,把它们硬捆在一起太不协调。另外静态成员函数可以被另一静态成员函数调用,但若把静态成员函数定义成虚函数,就不可能再被另一静态成员函数调用(因为多态性需要this指针)。

不管是何种理由,总之构造函数和静态成员函数在C++标准中被禁止定义为虚成员函数,作为使用者遵守它就是了。就象自然语言一样,有些规则是自然形成的,没有绝对的理由。

使用特权

评论回复
69
著安| | 2008-4-27 20:48 | 只看该作者

你好

谢谢了! 不过觉得就是 有点多了 !heihei

使用特权

评论回复
70
xushouxue| | 2008-4-28 08:52 | 只看该作者

多多益善

使用特权

评论回复
71
丹凤桥| | 2008-4-29 21:18 | 只看该作者

收藏

使用特权

评论回复
72
HWM|  楼主 | 2008-5-2 17:05 | 只看该作者

C++ 随笔-27,抽象类


前面曾经提到过“种子类”,其主要用处在于代码共享,一般是通过其派生类再去实例化形成具体的对象。

现在再看一个更基础的类(主要用于多态环境)——抽象类。在具体论述抽象类之前,先看一下两个特殊的虚函数:

    1,空虚函数

    空虚函数是函数体为空的虚成员函数,一般形式为:

    virtual void v_fun( <parameters-list> ) {}
或    virtual <return-type> v_fun( <parameters-list> ) { return <default-result> }

一个类中若含有空虚函数,则一般必须由派生类来定义一个具体的非空虚函数去置换相应的空虚函数,最后才能实例化成一个有意义的对象。由此可见空虚函数只是为派生类的多态性奠定一个基础框架,具体的多态性实现必须由各相应的派生类来加以落实。仔细分析可以看到,利用空虚函数实现上述多态性框架有一些不妥之处,其中之一就是多余代码。空虚函数本身没有意义,但却要占用代码空间。另外若用含空虚函数的类去实例化一个对象的话,此对象一般也没啥意义。下面给出含有空虚函数的状态机包装类:

// 有关"状态机"的封装

enum status_type { S0, S1, S2, S3, S4,…,Sn };
    
class class_SM
{
    protected:
    
    status_type status;
    
    virtual int Fy( int x );
    virtual void Fs( int x );
    
    public:
    
    class_SM( void ) { status = S0; }
    class_SM( status_type S ) { status = S; }
    
    int Do_it( int x );
    
    ~class_SM() {}
};

int class_SM :: Fy( int x )
{
// return Fy( x, status )

    return x;
}

void class_SM :: Fs( int x )
{
// status <- Fs( x, status )
}

int class_SM :: Do_it( int x )
{
    int result;
    
    result = Fy( x ); Fs( x );
    
    return result;
}

其中Fy和Fs都是空虚函数。


    2,纯虚函数

    纯虚函数是一种纯粹虚设的函数,一般形式为:

    virtual void v_fun( <parameters-list> ) = 0;
或    virtual <return-type> v_fun( <parameters-list> ) = 0;

可以看出,纯虚函数没有函数体,那具体是何物呢?其实纯虚函数只是在VTAB中预留了一个值为0的单元,此单元将被其相关衍生类置换成具体的虚函数入口地址。由于纯虚函数没有函数体,所以并不占有代码空间,而只是在VTAB中预留了一个单元而已。但正是因为没有函数体,而且VTAB中的预留值是0,所以纯虚函数是不可调用的。为了限制对纯虚函数的调用,编译系统一般是不允许对含有纯虚函数的类进行实例化的。


为了区别于一般的类,含有纯虚成员函数的类被称为抽象类。由上可见,抽象类不能被实例化成对象。这样也就在语法层面上限制了无意义对象的出现。下面给出状态机的包装抽象类:

// 有关"状态机"的封装(抽象类)

enum status_type { S0, S1, S2, S3, S4,…,Sn };
    
class class_SM
{
    protected:
    
    status_type status;
    
    virtual int Fy( int x ) = 0;  // return Fy( x, status )
    virtual void Fs( int x ) = 0; // status <- Fs( x, status )
    
    public:
    
    class_SM( void ) { status = S0; }
    class_SM( status_type S ) { status = S; }
    
    int Do_it( int x );
    
    ~class_SM() {}
};

int class_SM :: Do_it( int x )
{
    int result;
    
    result = Fy( x ); Fs( x );
    
    return result;
}

使用特权

评论回复
73
唐龙80| | 2008-5-2 18:48 | 只看该作者

历害

看到哪些代码头都大

使用特权

评论回复
74
hotpower| | 2008-5-2 19:35 | 只看该作者

学习了~~~

使用特权

评论回复
75
feiyuanxia| | 2008-5-3 14:22 | 只看该作者

认真学习~

认真学习中~

使用特权

评论回复
76
HWM|  楼主 | 2008-5-5 21:43 | 只看该作者

C++ 随笔-28,虚成员函数的一个综合例题


有关成员函数,在前面已经谈到过两个概念,它们分别是“覆盖”和“重载”。如果两个成员函数其名相同,但它们处于两个不同层级继承类中,则派生类中的成员函数将覆盖基类的同名函数。如果两个成员函数其名相同,而且它们处于同一个类中(在同一个类作用域内),则这些同名成员函数就构成了重载关系。

对于虚成员函数而言,除了上述的“覆盖”和“重载”概念外(因为虚函数也是成员函数的一种),还多了一种“置换”(即多态)概念。具体来说就是,若有两个虚成员函数其接口相同(函数名和参数完全相同),且分别处在两个不同层级的继承类中,则派生类中的虚成员函数将置换基类中的同接口虚成员函数。

在阐述新的内容前,给出一个包含上述三个概念的综合例题,算是关于继承,重载和多态性的一个回顾。


class A
{
    public:
    virtual int v_fun( char x ) { return x+1; }
    int foo_A( int x ) { return v_fun( x ); }
};

class B : public A
{
    public:
    virtual int v_fun( int x ) { return x+2; }
    int foo_B( char x ) { return v_fun( x ); }
};

class C : public B
{
    public:
    virtual int v_fun( char x ) { return x+3; }
    virtual int v_fun( int x ) { return x+4; }
    virtual int v_fun( long x ) { return x+5; }
    int foo_C( int x ) { return v_fun( x ); }
    int foo_C( long x ) { return v_fun( x ); }
};

class D : public C
{
    public:
    virtual    int v_fun( char x ) { return x+6; }
    virtual int v_fun( long x ) { return x+7; }
    virtual int v_fun( double x ) { return x+8; }
    int foo_D( double x ) { return v_fun( x ); }
};

int A_foo( A& a )
{
    char x;
    
    x =10;
    return a.v_fun( x );
}

int B_foo( B& a )
{
    char x;
    
    x =10;
    return a.v_fun( x );
}

int C1_foo( C& a )
{
    char x;
    
    x =10;
    return a.v_fun( x );
}

int C2_foo( C& a )
{
    int x;
    
    x =10;
    return a.v_fun( x );
}

int C3_foo( C& a )
{
    long x;
    
    x =10;
    return a.v_fun( x );
}

int D_foo( D& a )
{
    double x;
    
    x =10;
    return a.v_fun( x );
}

C Object_C;
D Object_D;

long X;
double Y;
int Z;
    
int main( void )
{
    X = 100;
    Y  = 200;
    
    Z = Object_C.v_fun( X );       // Z <- ?
    Z = Object_D.v_fun( Y );       // Z <- ?
    
    Z = Object_D.foo_A( X );       // Z <- ?
    Z = A_foo( Object_D );         // Z <- ?
    
    Z = Object_D.foo_B( X );       // Z <- ?
    Z = B_foo( Object_D );         // Z <- ?
    
    Z = Object_D.foo_C( X );       // Z <- ?
    Z = Object_D.foo_C( (int)X );  // Z <- ?
    Z = C1_foo( Object_D );        // Z <- ?
    Z = C2_foo( Object_D );        // Z <- ?
    Z = C3_foo( Object_D );        // Z <- ?
    
    Z = Object_D.foo_D( X );       // Z <- ?
    Z = D_foo( Object_D );         // Z <- ?
    
    while ( 1 )
    {
    }
}

使用特权

评论回复
77
hotpower| | 2008-5-5 21:50 | 只看该作者

再学习~~~

使用特权

评论回复
78
o_oaao_o| | 2008-5-9 14:20 | 只看该作者

我是菜鸟,我要好好学习

这么都,都是财富吗?
我会好好学习的,希望大家多多帮助!

使用特权

评论回复
79
HWM|  楼主 | 2008-5-12 11:26 | 只看该作者

C++ 随笔-29,C中的“流”是何物

C是一种没有“输入/输出”的语言,初听起来好象有点耸人听闻,但事实确实如此。在C/C++中没有一条专用于I/O的语法,C/C++的输入和输出是通过外挂的标准库来实现的。AT&T的语言实现者们决定将这些I/O操作移入运行库中,这样使得其编译器比大多数同时代的其它编译器规模小,而且移植性更好。

流(stream)是替代stdio库函数的C/C++中新一代输入输出运行库体系,由于其借用了两个形象化的操作符“<<”和“>>”所以将其称之为“流”。下面给出一个广义流的描述。

流组成了保护性的外壳,使I/O操作具有了多态性和其他面向对象的特征。尽管流是常常与I/O操作联系在一起的,它实际上是通过缓冲机制,将一个对象的数据传输到另一对象这一通用传输过程的抽象。也就是说,任何一个对象进行修改或不修改,然后将其从一个内存地址移入另一内存的函数,都可以看成“流”操作。

流向I/O操作中加进了许多操作符重载和其它面向对象的特性。其目的是在毫无关系的输入输出对象类型之间建立起统一的表示方法,由编译器去处理细节。所谓“流语句”使用了下面的表示方法:

    input_stream >> typed_variable;
    output_stream << typed_variable;

上面的typed_variable应理解成广义的变量,即可以是一般的普通变量或结构变量或对象变量。input_stream和output_stream一般是标准的已定义且事例化了的I/O对象。操作符<<和>>表示从一个对象到另一个对象的数据流,C++的任何内部数据类型都可以用于流I/O,而自定义的类对象则可以通过对操作符<<和>>重载实现自定义的流I/O操作。

语法上要强调的是,操作符<<和>>只服从左至右的结合关系,具体为:

    cin >> a >> b >> c;
    cout << x << y << z;

应理解为:

    (((cin >> a) >> b) >> c);
    (((cout << x) << y) << z);

使用C++的标准流I/O类库需包含头文件iostream.h,其中被重载的操作符“<<”称插入操作符,操作符“>>”称抽取操作符。插入或抽取都是相对于缓冲区而言的。如上语句的操作过程可以表示成:

    cin >> a; // 从输入缓冲区中抽取一个数据放入到a中
    cin >> b; // 从输入缓冲区中抽取一个数据放入到b中
    cin >> c; // 从输入缓冲区中抽取一个数据放入到c中



    cout << x; // 将x中的数据插入到输出缓冲区中
    cout << y; // 将y中的数据插入到输出缓冲区中
    cout << z; // 将z中的数据插入到输出缓冲区中

为了更直观起见,下面给出一个使用I/O流输出复数的一个例子,输出形式为x + iy:


#include "stdafx.h" // for VC
#include "iostream.h"

class class_complex
{
    protected:
    
    float r, i;
    
    public:
    
    class_complex( void ) {};
    class_complex( float r0, float i0 ) { r = r0; i = i0; }
    
    float get_r( void ) { return r; }
    float get_i( void ) { return i; }
    
    class_complex operator + ( class_complex& y );
    class_complex operator - ( class_complex& y );
    class_complex operator * ( class_complex& y );
    class_complex operator / ( class_complex& y );

    friend ostream& operator << ( ostream& os, class_complex c );
};

class_complex class_complex::operator + ( class_complex& y )
{
    class_complex z;
    
    z.r = r + y.r;
    z.i = i + y.i;
    
    return z;
}

class_complex class_complex::operator - ( class_complex& y )
{
    class_complex z;
    
    z.r = r - y.r;
    z.i = i - y.r;
    
    return z;
}

class_complex class_complex::operator * ( class_complex& y )
{
    class_complex z;
    
    z.r = r * y.r - i * y.i;
    z.i = r * y.i + i * y.r;
    
    return z;
}

class_complex class_complex::operator / ( class_complex& y )
{
    class_complex z;
    float a;
    
    a = y.r * y.r + y.i * y.i;
    z.r = ( r * y.r + i * y.i ) / a;
    z.i = ( i * y.r - r * y.i ) / a;
    
    return z;
}

ostream& operator << ( ostream& os, class_complex c )
{
    return os << c.r << " + i" << c.i;
}

class_complex C1(1, 1), C2(2, 2), C3, C4;

int main(int argc, char* argv[])
{
    C3 = C1 * C2;
    C4 = C3 / C1;

    cout << "C3 = " << C3 << endl
         << "C4 = " << C4 << endl;
    
    return 0;
}

使用特权

评论回复
80
iC921| | 2008-5-19 00:24 | 只看该作者

多谢楼主!

现在是19号了,顶一下放下置顶。请理解。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则