首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

2.2  构造函数

在完成throttle类之后,可以在程序中直接使用这个类,图2-2中的示例就是这样一个程序。该程序以throttle类的定义开始,接着是使用这个类的主程序,最后则是4个成员函数的实现。这个程序能够正常工作,而且同其他程序一样,其中的代码可以被放到一个单独的文件中进行编译和运行。但是在完成讲述节流阀的示例之前,还需要对它作一些改进。

第一个改进是解决节流阀初始化的问题。在前面的程序中,3个成员函数都有一个标明为“shut_off至少已经被调用过一次,以便初始化节流阀”的前置条件。如果没有这个前置条件,成员变量position将会包含无用的值,而且程序将会发生意想不到的事情。不幸的是,对于确保节流阀已经被初始化,目前还没有用来测试前置条件的办法。

构造函数是一个保证会被调用的初始化函数,这为解决上述问题提供了一种方法。构造函数(constructor)是一个具有以下特性的成员函数:

●       如果类具有一个构造函数,那么在声明类的变量时,构造函数将会被自动调用。如果构造函数带有参数,那么调用构造函数所使用的参数必须在类变量的名称之后(变量被声明的位置)给出。

●       构造函数的名称必须与类的名称相同。在我们的示例中,构造函数的名称是throttle。这一特性好像很奇怪:我们通常会尽量避免对两个不同事物使用相同的名称。但是,C++的确要求构造函数和类使用相同的名称。

●       构造函数没有返回值。正因为如此,在构造函数头的前面禁止带有void(或其他返回类型)。编译器知道每个构造函数都没有返回值,因此如果在构造函数头的前面写有void,将会引发编译错误。

2.2.1  节流阀类的构造函数

下面将实现一个节流阀的构造函数,以便更进一步了解上述特性。我们设计的构造函数应当会使节流阀变得更为灵活,它允许节流阀的位置总数从1变到其他值,也就是说不再限制节流阀只具有6个位置。例如,割草机的节流阀可能只需要4个位置,而用于火箭(需要精确控制)的节流阀则可能需要40个位置。

节流阀构造函数带有一个参数,用于指明该节流阀拥有的位置总数。由于构造函数总是会将节流阀的当前位置初始化为零,因此并不需要为“节流阀的当前位置”使用第二个参数。以下是包含前置条件/后置条件的构造函数的原型:

throttle(int size);

// Precondition: 0 < size.

// Postcondition: The throttle has size positions above the shutoff position,

// and its current position is off.

注意,其中使用了单词throttle作为构造函数的名称,这看起来确实是很奇怪,但我们别无选择:构造函数的名称必须与类的名称相同。

也应注意到,原型的前面并没有出现单词void,也没有出现任何其他的函数返回类型。构造函数的原型随同其他成员函数的原型一起放置在throttle的类定义中,如下所示:

class throttle

{

public:

    // CONSTRUCTOR

    throttle(int size);

    //这是节流阀构造函数的原型。

    // MODIFICATION MEMBER FUNCTIONS

    void shut_off( );

   

    //其他成员函数通常出现在这里。

稍后将会继续讲述构造函数的实现,但是在这之前,首先介绍一些声明节流阀对象时使用构造函数的示例。例如,下面是两个节流阀的声明:

throttle mower_control(4);

throttle apollo(40);

在这些声明之后,每个节流阀都会被关闭。其中mower_control具有4个位置,而节流阀apollo则具有40个位置。

通常来说,提供多个不同的构造函数十分有帮助,其中每个构造函数都可以进行不同类型的初始化。举例来说,假如有的节流阀只需要一个开启位置—— 这是一种开关节流阀,那么就可以提供另一个没有参数的构造函数。第二个构造函数只赋予节流阀一个开启位置,并且设置当前位置为零。这个构造函数的原型如下所示:

throttle( );

// Precondition: None.

// Postcondition: The throttle has one position above the shutoff position,

// and its current position is off.

不带任何参数的构造函数称为默认构造函数(default constructor)。下面是两个节流阀的声明,其中第一个节流阀使用了默认构造函数,而第二个节流阀则使用另一个构造函数。

throttle toggle;

throttle complicated(100);

当toggle使用默认构造函数时,并没有参数列表—— 甚至没有一对圆括号。换句话说,如果要使用默认构造函数,只需在没有参数列表的情况下声明一个对象,随后默认构造函数将会被调用。

针对对象的不同初始化方法,可以声明任意多个构造函数。每个构造函数必须具有一个明确的参数列表,以便编译器能够识别。但是只允许有一个默认构造函数。

为了实现新的构造函数,还需要一个新的私有成员变量top_position,它用于表明节流阀的最大位置。默认构造函数设置top_position为1,而另一个构造函数将会根据它的size参数设置top_position。新类的完整定义在图2-3中给出,其中也包括两个新构造函数的实现。

类定义

class throttle

{

public:

    // CONSTRUCTORS

    throttle( );

    //默认构造函数的原型。

    throttle(int size);

    //另一个构造函数的原型。

    // MODIFICATION MEMBER FUNCTIONS

    void shut_off( );

    void shift(int amount);

    // CONSTANT MEMBER FUNCTIONS

    double flow( ) const;

    bool is_on( ) const;

private:

    int top_position;

    //新的私有成员变量跟踪节流阀具有多少个位置。

    int position;

图2-3  节流阀类的构造函数

};

构造函数的实现

throttle::throttle( )

{

    top_position = 1;

    position n = 0;

}

throttle::throttle(int size)

// Library facilities used: cassert

{

    assert(size > 0);

    top_position = size;

    position = 0;

}

图2-3  (续)

如果类没有构造函数时将会发生什么

如果我们编写了一个没有构造函数的类,那么编译器将会自动创建一个简单的默认构造函数。这个自动默认构造函数(automatic default constructor)并没有做过多的处理,它只是调用属于其他类对象的成员变量的默认构造函数。我们通常应当编写自己的构造函数(包括默认构造函数),而不是依赖于自动默认构造函数。

编程提示

总是提供构造函数

在编写类并定义构造函数的时候,类中的每个变量在声明时都应该保证会调用一个构造函数。这种方法减少了使用未被初始化变量的机会,从而增加了程序的可靠性。我们也建议为每个类都定义一个默认构造函数,这便于程序员利用不带任何参数的构造函数来声明一个类变量。

2.2.2  修改节流阈类的成员函数

由于增加了一个新的成员变量top_position,因此必须修改各个成员函数,以便使用top_position而不是使用6作为节流阀的最高位置。举例来说,下面是修改后的成员函数shift的实现:

void throttle::shift(int amount)

// Postcondition: The throttle's position has been moved by amount (but

// not below 0 or above the top position).

{

    position += amount;

    if (position < 0)

        position = 0;

    else if (position > top_position)

        position = top_position;

        //使用成员变量top_position替代数字6。

}

应当注意,这里不再需要前置条件,因为对象在声明时就已经保证了会调用一个构造函数。当函数没有前置条件时,可以省略之(就像这里所做的那样),或者也可以将前置条件写作“None”。

2.2.3  内联成员函数

下面介绍一种新的技术来改写另外三个成员函数,这种新技术将会替换类定义中的shut_off、flow和is_on这3个函数的全部定义,如图2-4中的3个高亮行所示。

在flow的定义中,利用double把top_position由整数转变为double型数字(否则,除法将进行整数除法,并舍弃计算结果的余数)。这种数据类型的转变称为类型转换(type cast)。当对两个整数做除法操作时(而且希望除法的计算结果包含小数部分),这种类型转换将会很有用处。

函数is_on的实现也作了一个改变,即is_on仅仅检查position。这种方法看起来比最初的实现(通过激活函数flow)更为简单。

如果函数的定义被置于类定义中,则称这种函数为内联成员函数(inline member function)。它具有以下两点效果:

●       后面将不需要再次编写函数的实现。

●       程序每次使用内联函数时,编译器将会重新编译这个简短的函数定义,并将编译后的函数定义的一个副本放置到代码中。这种方法能够缩短执行时间(没有了实际的函数调用和函数返回),但在空间上并非高效(最终会得到多个相同的已编译代码的副本)。

需要注意的是,当声明一个内联成员函数时,在开花括号之前和闭花括号之后都不再需要使用分号。

编程提示

何时使用内联成员函数

内联成员函数会导致程序的低效—— 编译后的代码可能比原来的代码更长一些。内联函数还会导致类的定义更加混乱,使之难于阅读和难于调试。鉴于这些问题,我们建议只在简单的情况下使用内联成员函数,例如当函数的定义只包含一条单独的短语句时。

类定义

class throttle

{

    //高亮的代码显示了三个内联成员函数。

public:

    // CONSTRUCTORS

图2-4  内联成员函数

    throttle( );

    throttle(int size);

    // MODIFICATION MEMBER FUNCTIONS

    void shut_off( ) { position = 0; }

    void shift(int amount);

    // CONSTANT MEMBER FUNCTIONS

    double flow( ) const { return position / double(top_position); }

    //类型名称double将top_position由整数转变为double型数字。这种转变称为“类型转换”,它能够防止意外的整数除法。

    bool is_on( ) const { return (position > 0); }

private:

    int top_position;

    int position;

};

图2-4  (续)

2.2.4  自测习题

9. 使用内联函数重新编写自测习题8中的函数。

10. 当声明一个对象变量时,如果程序员不为这个类编写构造函数,将会发生什么情况?

11. 找出下列构造函数原型中的错误:

void throttle(int size);

12. 编写一个新的具有两个参数的节流阀构造函数,两个参数分别是:节流阀的位置总数和它的初始位置。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论