08-将引用用于类对象
8.2.5 将引用用于类对象
将类对象传递给函数时,C++通常的做法是使用引用。例如,可以通过使用引用,让函数将类string、ostream、istream、ofstream和ifstream等类的对象作为参数。
下面来看一个例子,它使用了string类,并演示了一些不同的设计方案,其中的一些是糟糕的。这个例子的基本思想是,创建一个函数,它将指定的字符串加入到另一个字符串的前面和后面。程序清单8.7提供了三个这样的函数,然而其中的一个存在非常大的缺陷,可能导致程序崩溃甚至不能通过编译。
程序清单8.7 strquote.cpp
// strquote.cpp -- different designs
#include <iostream>
#include <string>
using namespace std;
string version1(const string & s1, const string & s2);
const string & version2(string & s1, const string & s2); // has side effect
const string & version3(string & s1, const string & s2); // bad design
int main()
{
string input;
string copy;
string result;
cout << "Enter a string: ";
getline(cin, input);
copy = input;
cout << "Your string as entered: " << input << endl;
result = version1(input, "***");
cout << "Your string enhanced: " << result << endl;
cout << "Your original string: " << input << endl;
result = version2(input, "###");
cout << "Your string enhanced: " << result << endl;
cout << "Your original string: " << input << endl;
cout << "Resetting original string.\n";
input = copy;
result = version3(input, "@@@");
cout << "Your string enhanced: " << result << endl;
cout << "Your original string: " << input << endl;
return 0;
}
string version1(const string & s1, const string & s2)
{
string temp;
temp = s2 + s1 + s2;
return temp;
}
const string & version2(string & s1, const string & s2) // has side effect
{
s1 = s2 + s1 + s2;
// safe to return reference passed to function
return s1;
}
const string & version3(string & s1, const string & s2) // bad design
{
string temp;
temp = s2 + s1 + s2;
// unsafe to return reference to local variable
return temp;
}
下面是该程序的运行情况:
Enter a string: It’s not my fault.
Your string as entered: It's not my fault.
Your string enhanced: ***It's not my fault.***
Your original string: It's not my fault.
Your string enhanced: ###It's not my fault.###
Your original string: ###It's not my fault.###
Resetting original string.
此时,该程序已经崩溃。
程序说明
在程序清单8.7的三个函数中,version1最简单:
string version1(const string & s1, const string & s2)
{
string temp;
temp = s2 + s1 + s2;
return temp;
}
它接受两个string参数,并使用string类的相加功能来创建一个满足要求的新字符串。这两个函数参数都是const引用。如果使用string对象作为参数,最终结果将不变:
string version4(string s1, string s2) // would work the same
在这种情况下,s1和s2将为string对象。使用引用的效率更高,因为函数不需要创建新的string对象,并将原来对象中的数据复制到新对象中。限定符const指出,该函数将使用原来的string对象,但不会修改它。
temp是一个新的string对象,只在函数version1()中有效,该函数执行完毕后,它将不再存在。因此,返回指向temp的引用不可行,因此该函数的返回类型为string,这意味着temp的内容将被复制到一个临时存储单元中,然后在main()中,该存储单元的内容被复制到一个名为result的string中:
result = version1(input, "***");
将C-风格字符串用作string对象引用参数
对于函数version1(),您可能注意到了很有趣的一点:该函数的两个形参(s1和s2)的类型都是const string &,但实参(input和“**”)的类型分别是string和const char 。由于input的类型为string,因此让s1指向它没有任何问题。然而,程序怎么能够接受将char指针赋给string引用呢?
这里有两点需要说明。首先,string类定义了一种char 到string的转换功能,这使得可以使用C-风格字符串来初始化string对象。其次,本章前面讨论过的类型为const引用的形参的一个属性。假设实参的类型与引用参数类型不匹配,但可被转换为引用类型,程序将创建一个正确类型的临时变量,使用转换后的实参值来初始化它,然后传递一个指向该临时变量的引用。例如,在本章前面,将int实参传递给const double &形参时,就是以这种方式进行处理的。同样,也可以将实参char 或const char *传递给形参const string &。
这种属性的结果是,如果形参类型为const string &,在调用函数时,使用的实参可以是string对象或C-风格字符串,如用引号括起的字符串字面量、以空字符结尾的char数组或指向char的指针变量。因此,下面的代码是可行的:
result = version1(input, "***");
函数version2()不创建临时string对象,而是直接修改原来的string对象:
const string & version2(string & s1, const string & s2) // has side effect
{
s1 = s2 + s1 + s2;
// safe to return reference passed to function
return s1;
}
该函数可以修改s1,因为不同于s2,s1没有被声明为const。
由于s1是指向main()中一个对象(input)的引用,因此将s1作为引用返回是安全的。由于s1是指向input的引用,因此,下面一行代码:
result = version2(input, "###");
与下面的代码等价:
version2(input, "###"); // input altered directly by version2()
result = input; // reference to s1 is reference to input
然而,由于s1是指向input的引用,调用该函数将带来修改input的副作用:
Your original string: It's not my fault.
Your string enhanced: ###It's not my fault.###
Your original string: ###It's not my fault.###
因此,如果要保留原来的字符串不变,这将是一种错误设计。
程序清单8.7中的第三个函数版本指出了什么不能做:
const string & version3(string & s1, const string & s2) // bad design
{
string temp;
temp = s2 + s1 + s2;
// unsafe to return reference to local variable
return temp;
}
它存在一个致命的缺陷:返回一个指向version3()中声明的变量的引用。这个函数能够通过编译(但编译器会发出警告),但当程序试图执行该函数时将崩溃。具体地说,问题是由下面的赋值语句引发的:
result = version3(input, "@@@");
程序试图引用已经释放的内存。