当前位置:嗨网首页>书籍在线阅读

24-其他的类方法

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

13.8.2 其他的类方法

定义类时,还需要注意其他几点。下面的几小节将分别介绍。

1.构造函数

构造函数不同于其他类方法,因为它创建新的对象,而其他类方法只是被现有的对象调用。这是构造函数不被继承的原因之一。继承意味着派生类对象可以使用基类的方法,然而,构造函数在完成其工作之前,对象并不存在。

2.析构函数

一定要定义显式析构函数来释放类构造函数使用new分配的所有内存,并完成类对象所需的任何特殊的清理工作。对于基类,即使它不需要析构函数,也应提供一个虚析构函数。

3.转换

使用一个参数就可以调用的构造函数定义了从参数类型到类类型的转换。例如,下述Star类的构造函数原型:

Star(const char *);                      // converts char * to Star
Star(const Spectral &, int members = 1); // converts Spectral to Star

将可转换的类型传递给以类为参数的函数时,将调用转换构造函数。例如,在如下代码中:

Star north;
north = "polaris";

第二条语句将调用Star::operator = (const Star &)函数,使用Star::star(const char )生成一个Star对象,该对象将被用作上述赋值运算符函数的参数。这里假设没有定义将char 赋给Star的赋值运算符。

在带一个参数的构造函数原型中使用explicit将禁止进行隐式转换,但仍允许显式转换:

class Star
{
...
public:
    explicit Star(const char *);
...
};
...
Star north;
north = "polaris";       // not allowed
north = Star("polaris"); // allowed

要将类对象转换为其他类型,应定义转换函数(参见第11章)。转换函数可以是没有参数的类成员函数,也可以是返回类型被声明为目标类型的类成员函数。即使没有声明返回类型,函数也应返回所需的转换值。下面是一些示例:

Star::operator double() {...}        // converts star to double
Star::operator const char * () {...} // converts to const char

应理智地使用这样的函数,仅当它们有帮助时才使用。另外,对于某些类,包含转换函数将增加代码的二义性。例如,假设已经为第11章的Vector类型定义了double转换,并编写了下面的代码:

Vector ius(6.0, 0.0);
Vector lux = ius + 20.2;     // ambiguous

编译器可以将ius转换成double并使用double加法,或将20.2转换成veotor(使用构造函数之一)并使用vector加法。但除了指出二义性外,它什么也不做。

C++11支持将关键字explicit用于转换函数。与构造函数一样,explicit允许使用强制类型转换进行显式转换,但不允许隐式转换。

4.按值传递对象与传递引用

通常,编写使用对象作为参数的函数时,应按引用而不是按值来传递对象。这样做的原因之一是为了提高效率。按值传递对象涉及到生成临时拷贝,即调用复制构造函数,然后调用析构函数。调用这些函数需要时间,复制大型对象比传递引用花费的时间要多得多。如果函数不修改对象,应将参数声明为const引用。

按引用传递对象的另外一个原因是,在继承使用虚函数时,被定义为接受基类引用参数的函数可以接受派生类。这在本章前面介绍过(同时请参见本章后面的“虚方法”一节)。

5.返回对象和返回引用

有些类方法返回对象。您可能注意到了,有些成员函数直接返回对象,而另一些则返回引用。有时方法必须返回对象,但如果可以不返回对象,则应返回引用。来具体看一下。

首先,在编码方面,直接返回对象与返回引用之间唯一的区别在于函数原型和函数头:

Star nova1(const Star &);    // returns a Star object
Star & nova2(const Star &); // returns a reference to a Star

其次,应返回引用而不是返回对象的的原因在于,返回对象涉及生成返回对象的临时副本,这是调用函数的程序可以使用的副本。因此,返回对象的时间成本包括调用复制构造函数来生成副本所需的时间和调用析构函数删除副本所需的时间。返回引用可节省时间和内存。直接返回对象与按值传递对象相似:它们都生成临时副本。同样,返回引用与按引用传递对象相似:调用和被调用的函数对同一个对象进行操作。

然而,并不总是可以返回引用。函数不能返回在函数中创建的临时对象的引用,因为当函数结束时,临时对象将消失,因此这种引用将是非法的。在这种情况下,应返回对象,以生成一个调用程序可以使用的副本。

通用的规则是,如果函数返回在函数中创建的临时对象,则不要使用引用。例如,下面的方法使用构造函数来创建一个新对象,然后返回该对象的副本:

Vector Vector::operator+(const Vector & b) const
{
    return Vector(x + b.x, y + b.y);
}

如果函数返回的是通过引用或指针传递给它的对象,则应按引用返回对象。例如,下面的代码按引用返回调用函数的对象或作为参数传递给函数的对象:

const Stock & Stock::topval(const Stock & s) const
{
    if (s.total_val > total_val)
        return s;       // argument object
    else
        return *this;   // invoking object
}

6.使用const

使用const时应特别注意。可以用它来确保方法不修改参数:

Star::Star(const char * s) {...} // won't change the string to which s points

可以使用const来确保方法不修改调用它的对象:

void Star::show() const {...} // won't change invoking object

这里const表示const Star * this,而this指向调用的对象。

通常,可以将返回引用的函数放在赋值语句的左侧,这实际上意味着可以将值赋给引用的对象。但可以使用const来确保引用或指针返回的值不能用于修改对象中的数据:

const Stock & Stock::topval(const Stock & s) const
{
    if (s.total_val > total_val)
        return s;     // argument object
    else
        return *this; // invoking object
}

该方法返回对this或s的引用。因为this和s都被声明为const,所以函数不能对它们进行修改,这意味着返回的引用也必须被声明为const。

注意,如果函数将参数声明为指向const的引用或指针,则不能将该参数传递给另一个函数,除非后者也确保了参数不会被修改。