Coding之路——重新学习C++(7):用继承写出一个好类

1.继承类时需要注意的地方

  (1)当一个类作为基类的时候,这个类就必须有定义。

  (2)在派生类中,基类的默认构造函数可以被隐式调用,但是如果基类的构造函数都有参数,派生类需要直接调用一个。派生类的构造函数只能描述派生类自己的成员变量和自己的基类的直接初始式,它不能直接初始化基类的成员。

Manager::Manager(const string &n, int d, int lvl)
    :family_name(n),           //错误:在Manager里没有family_name声明
    department(d),             //错我:在Mangager里没有department声明
    level(lvl){
    //...
}
//错误:没有调用Employee的构造函数

  (3)类对象的构造是自下而上的:首先是基类,再是成员,最后是派生类本身。类对象的销毁是自上而下的:首先是派生类本身,而后是成员,最后是基类。

  (4)当某个virtual函数是inline的,那么就可以用“::”特殊说明的调用使用在线替换。使我们能处理一些情况,例如某个虚函数针对同一个对象调用了另一个虚函数。

  (5)抽象类。最重要的用途是提供一个界面,而不暴露任何实现细节。不过,一个未在派生类里定义的纯虚函数仍然是纯虚函数,这样派生类仍然是一个抽象类。

2.从实例中体会类的继承。

  (1)我们先看一看我们传统的继承类的方式:

//Ival_box能从某个用户界面取得一个整数
class Ival_box{
protected:
    int val;
    int low,high;
    bool changed;
public:
    Ival_box(int ll, int hh){
        changed = false;
        val = low = ll;
        high = hh;
    }

    virtual int get_value(){
        changed = false;
        return val;
    }

    virtual void set_value(int i){
        changed = true;
        val = i;
    }

    virtual void reset_value(int i){
        changed = false;
        val = i;
    }

    virtual void prompt{}

    virtual bool was_changed()const{ return changed;}
};
class Ival_slider:public Ival_box{
    //定义滑块的视觉图形要素等  //...public:
    Ival_slider(int, int);

    int get_value();
    void prompt();
};

  (2)假设我们实现图形要素需要一个叫“BBWindow”的类,我们就得重写Ival_box类,让Ival_box类继承BBWindow类。但是假如我们还需要BJWindow类,ZXWindow类等多个类呢,我们不可能让Ival_box类全部继承一遍。所以,C++之父给出了一个对我比较有启示的继承方式,类层次结构相当清晰:

//用户界面应该是一个实现细节,对于希望了解它的用户应该是隐蔽的
//类Ival_box应该不包含数据
//在用户界面修改后,无需重新编译使用Ival_box的一族类的代码
//针对不同界面的Ival_box能在我们的程序中共存。
class Ival_box{
public:
    virtual int get_value() = 0;
    virtual void set_value(int i) = 0;
    virtual void reset_value(int i) = 0;
    virtual void prompt() = 0;
    virtual bool was_changed() const = 0;
    virtual ~Ival_box();
};

完整的层次结构是将我们面向应用的概念层次结构当做界面组成,用派生类表示:

class Ival_box{/*...*/}
class Ival_slider: public Ival_box{/*...*/}
class Ival_dial: public Ival_box{/*...*/}
class Flashing_ival_slider: public Ival_slider{/*...*/}
class Popup_ival_dial: public Ival_dial{/*...*/}

接下来的这一层次结构针对的是各种图形用户界面的实现,也表示为派生类:

class BBslider:public BBWindow{/*...*/};
class CWslider:public CWWindow{/*...*/};
class BB_ival_slider: public Ival_slider, protected BBsilder{/*...*/};
class BB_flashing_ival_slider:public Flashing_ival_slider,
        protected BBWindow_with_bells_and_whistles{/*...*/};
class BB_popup_ival_slider:public Popup_ival_slider,
        protected BBslider{/*...*/};
class CW_ival_slider:public Ival_slider, protected CWslider {/*...*/};

这个类的层次结构是这样的:

最后在继承类的时候,我们可以运用到以下的建议:数据成员最好是私用的,这可以使派生类无法来干扰它们。更进一步的说,数据应该放在派生类中,在派生类中,我们可以根据具体需要确切的定义数据,又不会把无关的派生类弄乱,对于几乎所有情况,保护界面只应该包含函数,类型和常量。

时间: 2024-10-09 19:10:41

Coding之路——重新学习C++(7):用继承写出一个好类的相关文章

Java学习笔记——面试常客:写出一个死锁的例子

现在的面试挺蛋疼,为了考察大家的语言掌握水平,类似这样的题特别多,不过在某个角度来说确实能看出一个人对某个知识点的理解,就比如今天这个死锁的小例子,主要考察大家对线程死锁概念的理解程度,也考察大家对java语言的敲代码水平,下面是一个死锁的简单例子: <span style="font-size:18px;">class Test implements Runnable { private boolean flag; //flag标签,让t1和t2线程执行不同代码 publ

Coding之路——重新学习C++(3):对于编译和链接的重新认识

1.C++的源代码是怎么变成程序的. (1)我们在编写完源代码后,首先需要把源代码交给编译器,编译器首先进行预处理,也就是处理宏,把#include指令引进的头文件全部引进,产生编译单元.编译单元是编译器的真正工作对象,是真正意义上的C++对象. (2)一般的编译模式会采用分别编译,这时我们必须保证所有的声明具有一致性,连接器程序帮助我们把所有编译的部分都约束在一起,让所有的对象.函数没有二义性,这些都在程序运行前结束.当然,也有可以再程序运行后加入新代码(动态连接). 2.连接时的二三事(必须

Coding之路——重新学习C++(4):定义一个正确的类

我们都能定义一个类,可是如何定义一个正确的类,这是一个需要我们深入理解的问题.C++之父曾经说过定义新类型的基本思想就是将实现一个类的时候并非必要的细节(存储该类型的对象采用的布局细节)和对于这个类的正确使用至关重要的性质(访问数据的成员函数)分开设计.这种区分的最好实现方式是提供一个特定的表层接口,所有对于类内部数据结构和内部维护的调用都通过这个表层接口. 1.类该怎么定义 (1)首先我们要明白,建立一个对象,构造函数把成员变量都放在了堆之中(除了static变量之外,static变量放在全局

Coding之路——重新学习C++(2):static的详细理解

一.C中的static关键字 1. static 局部变量 静态局部变量属于静态存储方式,它具有以下特点: (1)静态局部变量 在函数内定义它的生存期为 整个程序生命周期,但是其 作用域仍与 自动变量相同 ,只能在定义该变量的函数内使用该变量.退出该函数后,尽管该变量还继续存在,但不能使用它.(2)对基本类型的静态局部变量若在声明时未赋以初值,则系统自动赋予0值 .而对自动变量不赋初值,则其值是不定的. 根据静态局部变量的特点,可以看出它是一种生存期为整个程序生命周期.虽然离开定义它的函数后不能

Coding之路——重新学习C++(8):神奇的模板

1.解析一个正确的模板类 (1)首先,我们想创造一个模板,可以先针对一个特定的类型参数设计它的行为方式,然后在对抽象的一般类型进行推广.例如我们可以先设计String<char>类的具体实现,然后再推广到String<C>类模板. (2)类模板的名字是不能重载的.所以,如果在某个作用域内声明了一个类模板,就不能有其他同样名字的实体了. template<class T> class String{/*...*/}; class String {/*...*/}; //错

Coding之路——重新学习C++(10):类的层次结构

1.多重继承 (1)多重继承一直是C++中让许多人诟病的机制,不过它大大增加了类的层次结构的灵活性,先看一个简单的例子: class Task{ public: virtual void pending() = 0; //... }; class Displayed{ public: //... virtual void draw() = 0; }; class Satelite:public Task, public Displayed{ public: //... void pending(

Coding之路——重新学习C++(9):解决异常

1.什么是异常 (1)异常的基本思路是让一个函数发现自己无法解决的错误时抛出异常,让调用者来解决.异常处理机制类似于编译时的类型检查和歧义性控制在运行时的对应物,它是一种非局部的控制结构,在抛出异常时,我们用堆栈回退来找到能处理异常的上层函数.有人把异常想象成程序中那些无法挽回的重大错误,但是异常通常代表的是“系统的某些部分不能完成要它做的事”. (2)在我们用catch捕捉异常的时候,我们用实际参数的值对形参初始化,这可能造成我们抛出的异常因为捕捉而被切割,所以我们经常用指针和引用捕捉异常来保

Coding之路——重新学习C++(1):C++基础知识盲点总结

最近为了找工作参加了许多公司的笔试和面试,发现了以前的知识虽然学了很多,但是并不深入和系统.所以准备把一些书重新读一读,并且打算做一些总结,毕竟老祖宗教导我们“学而时习之,不亦说乎”. 1.把程序分成模块 当我们做程序一般都是分成许多模块去做,因为这样可以保证模块之间的独立性,不会因为一个模块的改动影响整个程序.所以我们在分模块的时候最重要的就是在一系列有关的过程(函数)和它们用到的数据组你开织在一起,在C++中一般放入一个命名空间中或者一个类中.每个模块应该提供接口隐藏数据和功能函数的具体实现

Coding之路——重新学习C++(6):一个String类

这个String类是对运算符重载和以前知识的一个综合应用,提供了值语义.字符串读写.检查和不检查的访问.流I/O和字符串拼接等功能. 1.String类的定义 class String{ //类型的定义 struct Srep; //表示能被几个同样值的String共享 Srep *rep; public: class Cref; //实现下标运算,区别对待读操作和写操作 class Range(); //范围错误时抛出的异常 //构造.赋值和析构函数 String(); //x = "&quo