Chinaunix首页 | 论坛 | 博客
  • 博客访问: 300282
  • 博文数量: 148
  • 博客积分: 4365
  • 博客等级: 上校
  • 技术积分: 1566
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-05 21:38
文章分类
文章存档

2014年(2)

2013年(45)

2012年(18)

2011年(1)

2009年(54)

2008年(28)

我的朋友

分类: C/C++

2008-10-06 14:54:14

终于进入第四部分,面向对象(OOP)与泛型程序设计(GP)

面向对象程序设计三个重要组成部分,数据抽象,类的派生,动态绑定。
前面通过类学习了数据抽象,这一章面向后两个内容。  

OOP思想中的关键是多态(polymorphism), [],生物遗传学上的一个词,在C++里就是指通过基类型的引用或指针的某些应用,见后文。

基类(base class)何以派生出派生类(derived class),派生类可以修改继承自基类的某些成员函数来满足自己的要求,还可以增加一些基类中没有的数据成员。
派生类同样可以作为新的基类派生出新的派生类。

基类中的函数,在其声明前有virtual关键字的,称之为虚函数(virtual不能在类外函数实现时出现).虚函数在派生类中是可以修改(redefine)其内容的,没有这个关键字的函数,派生类不能修改其内容。
root的基类声明为virtual的函数,后面层层派生的子类中,都不用加virtual,该函数都为虚函数。

动 态绑定(dynamic bind):假设有一个函数f,它的某个参数是基类型的引用br,在f中通过br调用了属于基类的虚函数vf,这时就发生了动态绑定。对函数vf的解析是 在运行时刻(run time),而不是编译时(compile time)。f的参数br虽然是基类型的,但是调用时传进的实参却可以是基类的某派生类的对象,这一点问题都没有,也就是说f中调用的vf有可能是派生类 中的版本,这样程序当然不可能在编译时确定了

class base{
public:
    void func(){
        cout<<"this is in base\n";
    }
    virtual void func2(){
        cout<<"this is in base 2 \n";
    }
};

class derived :public base{
public:
    void func(){
            cout<<"this is in derived\n";
        }
    void func2(){
            cout<<"this is in derived 2 \n";
        }
};
void func3(base & ref_b){
    ref_b.func();
    ref_b.func2();
}
int main(void) {
    derived obj_d;
    obj_d.func();
    obj_d.func2();
    func3(obj_d);
    cout<<"end in main\n";
    return EXIT_SUCCESS;
}

运行结果为

this is in derived
this is in derived 2
this is in base
this is in derived 2
end in main

main函数里创建了一个派生类的对象,通过其调用func函数,基类和派生类都有自己的func函数,并且不是虚函数,派生类自己的虚函数就把基类的给“冲掉了”,所以运行的是派生类的版本。
func2函数虽然是虚函数,但我们是通过派生类对象来进行调用的,所以也是派生类版本。
func3函数,参数为基类型引用,传进的实参为派生类对象,func函数为基类版本的,func2为派生类版本的,因为func2是虚函数,只有func3中obj_d.func2();只有这里是动态绑定
c++中动态绑定只发生在通过指向基类的引用或指针调用虚函数。
当继承类型不是public时,基类型指针或引用无法指向派生类对象,mingw会提示:
xx(base) is inaccessble in yy(derived)


基类中的公有数据成员在派生类中是仍然存在并可以访问的,私有数据成员就不行了,但是公有成员也可以被类外的代码访问,为了解决此问题,可以将那些不希望能在类外访问,但又能让派生类访问的数据成员声明为保护的(protected)。
派生类能在派生后访问属于其自己的从基类派生来的那些保护成员,而不能访问属于基类的保护成员。

class base{
public:
    int pub;
protected:
    int pro;
private:
    int pri;
};

class derived :public base{
public:
//    derived(int a=1,int b=2):pub(a),pro(b){}
    derived(int ,int,int);
    void func(derived & ref_d){
        cout<<ref_d.pub<<" "<<ref_d.pro<<" "<<ref_d.ppp<<endl;
    }
private:
    int ppp;
};
derived::derived(int a=2,int b=3,int c=4){
    pub=a;pro=b;ppp=c;
}

int main(void) {
    derived obj1,obj2(4,5,6);
    obj1.func(obj2);
    cout<<"end in main\n";
    return EXIT_SUCCESS;
}

很 有意思,程序没有错误,在派生类的func函数中,接收一个自身类型的引用,访问输出其公有,保护和私有成员,在main函数中创建两个派生类对象 obj1和obj2,调用obj1.func,传进的参数是obj2,居然直接能访问obj2的私有和保护成员,看来前面classes那章看书不够仔 细,拿基类做实验也一样。
还有一点是派生类中关于构造函数的注释不能去掉,去掉会有错误,看来要看到后面的构造函数等地方的继承可以搞定这个问题。其原因为构造函数初始化列表只能写在本类中定义的成员,不能是基类中成员,但在构造函数体内可以使用基类成员。

派生类也可以不重写继承来自基类的虚函数,那么派生类的该函数与基类的一模一样。派生类如果还要作为基类派生其它的类,则可以在其需要的函数前再加virtual。
派生类重写基类中的虚函数,其重写后的版本需与基类中的函数原型相同(返回值,参数个数及类型)。但有一种例外,当基类中的虚函数原型返回值是指向基类的引用或指针时,派生类重写后的返回值可以是该基类的派生类类型的引用或指针。

每个派生类对象都含有其基类的部分,虽然编译器不一定将这两部分连续存储,这也是派生类的实参能传进基类引用形参的原因。
派生类的组成是:其内部定义的非静态的成员和基类中定义的非静态成员。

类A若是想作为基类派生其它类,在定义派生类前,类A必须完全定义,只有声明不行。

class b;
class d :public b{};

编译报错base class `b' has incomplete type
这隐含地说明了一个类不能作为其自己的基类。
类似地如果只要声明一个派生类,不能写成

class d :public b;

应该写成

class d ;
class b;

阅读(668) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~