📄 08章 运算符重载.txt
字号:
168 int String::getLength() const { return length; }
169
170 // Utility function to be called by constructors and
171 // assignment operator.
172 void String::setString( const char *string2 )
173 {
174 sPtr = new char[ length + 1 ]; // allocate storage
175 assert( sPtr != 0 ); // terminate if memory not allocated
176 strcpy( sptr, string2 ); // copy literal to object
177 }
178
179 // Overloaded output operator
180 ostream &operator<<( ostream &output, const String &s )
181 {
182 output << s.sPtr;
183 return output; // enables cascading
184 }
185
186 // Overloaded input operator
187 istream &operator>>( istream &input,String &s )
188 {
189 char temp[ 100 ]; // buffer to store input
190
191 input >> setw( 100 ) >> temp;
192 s = temp; // use String class assignment operator
193 return input; // enables cascading
194 }
195 // Fig. 8.5:fig08 05.cpp
196 // Driver for class String
197 #include <iostream.h>
198 #include "string1.h"
199
200 int main()
201 {
202 String s1( "happy" ), s2( "birthday" ), s3;
203
204 // test overloaded equality and relational operators
205 cout << "s1 is \"" << s1 << "\"; s2 is \"" << s2
206 << "\"; s3 is \"" << s3 << '\"'
207 << "\nThe results of comparing s2 and s1:"
208 << "\ns2 == s1 yields"
209 << ( s2 == s1 ? "true" : "false" )
210 << "\ns2 != s1 yields"
211 << ( s2 != s1 ? "true" : "false" }
212 << "\ns2 > s1 yields"
213 << ( s2 > s1 ? "true" : "false" )
214 << "\ns2 < s1 yields "
215 << ( s2 < s1 ? "true" : "false" )
216 << "\ns2 >= s1 yields
217 << ( s2 >= s1 ? "true" : "false" )
218 << "\ns2 <= s1 yields"
219 << ( s2 <= s1 ? "true" : "false" );
220
221 // test overloaded String empty (!) operator
222 cout << "\n\nTesting !s3:\n";
223 if ( !s3 ) {
224 cout << "s3 is empty; assigning s1 to s3;\n";
225 s3 = s1; // test overloaded assignment
226 cout << "s3 is \"" << s3 << "\"";
227 }
228
229 // test overloaded String concatenation operator
230 cout << "\n\ns1 += s2 yields s1 = ";
231 s1 += s2; // test overloaded concatenation
232 cout << s1;
233
234 // test conversion constructor
235 cout << "\n\ns1 += \" to you\" yields\n";
236 s1 +=" to you"; // test conversion constructor
237 cout << "s1 = "<< s1 << "\n\n";
238
239 // test overloaded function call operator () for substring
240 cout << "The substring of s1 starting at\n"
241 << "location 0 for 14 characters, s1(0, 14), is:\n"
242 << s1( 0, 14 ) << "\n\n";
243
244 // test substring "to-end-of-String" option
245 cout << "The substring of s1 starting at\n"
246 << "location 15, s1(15, 0), is:"
247 << s1( 15, 0 ) << "\n\n"; // 0 is "to end of string"
248
249 // test copy constructor
250 String *s4Ptr = new String(s1);
251 cout << "*s4Ptr = "<< *s4Ptr << "\n\n";
252
253 // test assignment (=) operator with self-assignment
254 cout << "assigning *s4Ptr to *s4Ptr\n";
255 *s4Ptr = *s4Ptr; // test overloaded assignment
256 cout << "*s4Ptr = "<< *s4Ptr << '\n';
257
258 // test destructor
259 delete s4Ptr;
260
261 // test using subscript operator to create lvalue
262 s1[ 0 ] = 'H';
263 s1[ 6 ] = 'B';
264 cout << "\nsl after s1[ 0 ] = 'H' and s1[ 6 ] = 'B' is:"
265 << s1 << "\n\n";
266
267 // test subscript out of range
268 cout << "Attempt to assign 'd' to s1[ 30 ] yields:" << endl;
269 s1[ 30 ] = 'd'; // ERROR: subscript out of range
270
271 return 0;
272 }
输出结果:
Conversion constructor: happy
Conversion constructor: birthday
Conversion constructor:
sl is "happy"; s2 is "birthday"; s3 is ""
The results of comparing s2 and s1:
s2 == s1 yields false
s2 != s1 yields true
s2 > sl yields false
s2 < sl yields true
s2 >= s1 yields false
s2 <= s1 yields true
Testing !s3:
s3 is empty; assigning s1 to s3;
operator = called
s3 is "happy"
s1 += s2 yields s1 = happy birthday
s1 +=" to you" yields
Conversion constructor: to you
Destructor: to you
s1 = happy birthday to you
Conversion constructor:
The substring of sl starting at
location 0 for 14 characters, sl(0, 14), is:
happy birthday
Conversion constructor:
The substring of sl starting at
location 15, s1(15,0}, is: to you
copy constructor: happy birthday to you
*s4Ptr = happy birthday to you
assigning *s4Ptr to *s4Ptr
operator = called
Attempted assignment of a string to itself
*s4Ptr = happy birthday to you
destructor: happy birthday to you
s1 after s1[ 0] = 'H' and si[ 6] = 'B' is: Happy Birthday to you
Attempt to assign 'd' to s1[30] yields:
Assertion failed: subscript >= 0 && subscript < length,
file String1.cpp,line 76
abnormal program termination
图 8.5 定义基本的String类
我们从String的内部表示开始讨论。第44行到第45行:
int length; // Strzng length
char*sPtr; // pointer to start of string
声明了类的private数据成员。String的对象有一个length字段(表示字符串中除字符串终止符以外的字符个数)和一个指向动态分配内存(表示字符串)的指针sPtr。
现在分析一下图8.5中定义String类的头文件。下面的两行代码(第9行到第10行):
friend ostream &operator<<( ostream &,const String &);
friend istream &operator>>( istream &, String & );
把重载的流插入运算符函数operator<<(第180行定义)和流读取运算符函数operator>>(第187行定义)声明为类的友元。这两个函数的实现是显而易见的。
第13行:
String(const char * = "");// conversion/default ctor
声明了一个转换构造函数,该构造函数(第60行定义)有一个const char*类型的参数(默认值是空字符串)。该函数实例化了String的一个对象,该对象包含了与参数相同的字符串。任何只带一个参数的构造函数都可以认为是一种转换构造函数。稍后就会看到,当使用char*参数对String类做任何操作时,转换构造函数是很有用的。转换构造函数把一个char*字符串转换为String的对象(然后该对象要赋给目标String对象)。使用这种转换构造函数意味着不必再为将字符串赋给String的对象提供重载的赋值运算符,编译器先自动地调用该函数建立一个包含该字符串的临时String对象,
然后再调用重载的赋值运算符将临时String对象赋给另一个String对象。
软件工程视点8. 7
当使用转换构造函数实现隐式转换时,C++只会使用一个隐式的构造函数调用来试图满足重载赋值运算符的需要。通过执行一系列隐式的、用户自定义的类型转换来满足重载运算符的需要是不可能的。
在做出像String s1("happy")这样的声明时,调用String的转换构造函数。转换构造函数计算了字符串的长度并将该长度赋给private数据成员length,然后调用private工具函数setString。函数setString(第172行定义)使用new为private数据成员sPtr分配足够的空间,并用assert来测试内存分配操作是否成功。如果成功,则用函数strcpy把字符串复制到对象中。
第14行:
String(const String &); // copy constructor
是一个复制构造函数(第67行定义),它通过复制已存在的String对象来初始化一个String对象。必须要小心对待这种复制操作,避免使两个String对象指向同一块动态分配的内存区,默认的成员复制更容易发生这种问题。复制构造函数除了将源String对象的length成员复制到目标String对象外,其余操作和转换构造函数类似。注意,复制构造函数为目标对象的内部字符串分配了新的存储空间,如果它只是简单地将源对象中的sPtr复制到目标对象的sptr,则这两个对象将指向同一块动态分配的内存块。执行一个对象的析构函数将释放该内存块,从而使另一个对象的sPtr没有定义,这种情况可能会引起严重的运行时错误。
第15行:
~String(); // destructor
声明了类String的析构函数(第74行定义)。该析构函数用delelte回收构造函数中用new为字符串分配的动态内存。
第16行:
const String &operator=(const String &); // assignment
声明了重载的赋值运算符函数operator=(第81行定义)。当编译器遇到像string1=string2这样的表达式时,就会生成函数调用:
string1.operator=(string2);
重载的赋值运算符函数operator测试了这种赋值是否为自我赋值(正如在复制构造函数中所做的那样)。如果是自我赋值运算,由于该对象已存在,函数就简单地返回。如果忽略自我赋值测试,那么函数就会立即释放目标对象所占用的空间,这样会丢失字符串。假如不是自我赋值,那么函数就释放目标对象所占用的内存空间,将源对象中的length字段复制到目标对象并调用setString(第172行)为目标对象建立新空间,用assert测试new操作是否成功,最后用函数strcpy将源对象的字符串复制到目标对象中。不管上述赋值是否为自我赋值,函数都返回*this以确保可以连续赋值。
第17行:
const String &operator+=( const String & ); // concatenation
声明了重载的字符串连接运算符(第98行定义)。当编译器遇到main函数中的表达式s1+=s2时,生成函数调用s1.operator+=(s2)。函数operator+=建立一个临时指针,该指针用来存放当前对象的字符串指针,直到可以撤消该字符串的内存为止,该函数还计算了连接后的字符串长度,用new为字符串分配空间,用assert测试new操作是否成功。如果成功,则用函数strcpy将原先的字符串复制到分配的空间中,然后用函数strcat将源对象的字符串连接到所分配的空间中,最后再用delele释放该对象原来的字符串占据的空间,返回*this作为String&以确保运算符+=可以连续执行。
连接String类型的对象和char*类型的对象不需要再重载一个连接运算符,const char*转换构造函数将传统的字符串转换为临时的String类型的对象,然后由该对象匹配现有的重载连接运算符。C++为实现匹配只能在一层之内执行这样的转换。在执行内部类型和类之间的转换前,C++还能在内部类型之间执行编译器隐式定义的类型转换。注意,生成临时String对象时,调用转换构造函数和析构函数(见图8.5中s1 += "to you" 产生的输出)。这是隐式转换期间生成和删除临时类对象时向类客户隐藏的函数调用开销的一个例子。复制构造函数按值调用传递参数和按值返回类对象时也
产生类似开销。
性能提示8.2
与先执行隐式类型转换然后再执行连接操作相比,使重载的连接运算符+=只有一个const char*类型参数的执行效率更高。隐式类型转换需要较少的代码,出错也较少。
第18行:
bool operator!()const; // is String empty?
声明了重载的取非运算符(第111行定义)。该运算符通常与字符串类一起使用,测试字符串是否为空。例如,当编译器遇到表达式!string1时,就会生成函数调用:
strlng1.operator!()
该函数仅仅返回length是否等于0的测试结果
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -