书:C++大学教程(第七版)
1. 运算符重载
(1) 重载不能改变运算符的优先级;
(2) 重载不能改变运算符的结合律;
(3) 重载不能改变元素运算符的“元数”;
(4) 不能创建新的运算符,只有现有的运算符才可以重载;
(5) 运算符重载不能改变运算符对于基本类型对象操作的含义;
(6) 运算符重载只能对用户自定义类型的对象,或者用户自定义类型对象和基本类型对象的混合使用起作用;
class Complex
{
friend ostream &operator<<( ostream &, const Complex & );
friend istream &operator>>( istream &, Complex & );
public:
Complex( double = 0.0, double = 0.0 ); // constructor
Complex operator+( const Complex& ) const; // addition
Complex operator-( const Complex& ) const; // subtraction
Complex operator*( const Complex& ) const; // multiplication
Complex& operator=( const Complex& ); // assignment
bool operator==( const Complex& ) const;
bool operator!=( const Complex& ) const;
private:
double real; // real part
double imaginary; // imaginary part
}; // end class Complex
- 类成员函数和全局函数的运算符函数的比较
(1) 运算符函数可以是成员函数或全局函数。出于性能方面的考虑,全局函数通常指定为友元函数。成员函数用this指针隐式获得类对象的某个参数(对二元运算符而言即为左操作数),而二元运算符的两个操作数参数在全局函数调用中必须显示列出。
(2) 必须作为类的成员函数进行重载的运算符:()、[]、->、任何赋值运算符;
(3) 当运算符函数作为成员函数实现时,最左边(或者只有最左边)的操作数必须是运算符的一个类对象(或者是该类对象的一个引用)。如果左操作数必须是一个不同类的对象或者是一个基本类型对象,那么该运算符函数必须作为全局函数来实现。如果全局运算符函数必须直接访问类的private或者protected成员,那么该函数可以指定成该类的友元函数。
(4) 一个特定类的运算符成员函数仅在下面两种情形下(由编译器隐式)调用:当二元运算符的左操作数的确是该类的对象时,或者当一元运算符唯一的操作数是该类的对象时。 - 流插入运算符(<<)和流提取运算符(>>)为何作为全局函数来重载?
重载的流插入运算符(<<)用在其左操作数的类型为ostream&之类的表达式中,如cout<<classObject。
如果要在右操作数是用户自定义类的对象的情况下使用该运算符,那么该运算符必须重载成全局函数。要以成员函数重载,运算符<<就必须成为ostream类的成员。用户自定义的类不可能做到这一点,因为我们不能修改C++标准库中的类。重载流提取运算符同理。
这两个重载运算符函数都可能需要访问要输出或输入的类对象的private数据成员,因此出于性能原因,可以把这些重载运算符函数制定为类的友元函数。 - 返回对自动变量或者其他临时队形的引用时很危险的额,相当于对不存在的对象创建了“虚悬引用”。
- 重载一元运算符
类的一元运算符可以重载为不带参数的非static成员函数或者带有一个参数的全局函数。全局函数的参数必须是该类的对象或者是该类对象的引用。实现重载运算符的成员函数必须是非static的,这样它们可以访问该类每个对象中的非static数据。 - 重载二元运算符
二元运算符可以重载为带有一个参数的非static成员函数,或者两个参数(其中一个必须是类的对象或者是类对象的引用)的全局函数。 - 复制构造函数
(1) 无论何时需要对象的副本,例如在向函数按值传递对象时,从函数按值返回对象时,或者用同一个类的另一对象的副本初始化一个对象时,都会调用复制构造函数。
(2) 复制构造函数必须按引用接收参数,而非按值。否则,复制构造函数的调用会导致无穷递归。因为按值接收对象时,需要复制构造函数生成实参对象的副本。回想一下,无论何时需要对象的副本,都会调用类的复制构造函数。所以如果复制构造函数按值接收参数,那么为了生成其参数的副本,它会递归地调用自己。 - 通常会为任何一个使用动态分配内存的类同时提供一组函数:复制构造函数、析构函数和重载的复制运算符函数。
- 阻止类的一个对象赋给另一个对象,只要把复制运算符声明为这个类的private成员即可。
- 阻止类对象的复制
只需把这个类重载的赋值运算符和复制构造函数声明成private。 - 转换运算符
转换运算符也称为强制类型转换运算符,可用于将某一类的对象转换成另一类的对象,或者转换成基本类型的对象。这种转换运算符必须是非static成员函数。
A::operator char *() const;
声明了一个重载的强制类型转换运算符函数,可以把用户自定义类型A的对象转换成一个临时的char*对象。这个运算符函数声明为const,因为它并不修改原始的对象。重载的强制类型转换运算符函数不指定返回类型,因为返回类型其实就是对象正要转换成的目标类型。如果s是某个类的对象,当编译器遇到表达式static_cast<char*>(s)时,它会产生函数调用:
s.operator char *()
操作数s是正在调用成员函数operator char* 的类对象s。
强制类型转换运算符和转换构造函数的优点之一是:必要时,编译器可以隐式地调用这些函数来创建临时的对象。
12. 重载前置的自增运算符
遇到++d1,会产生d1.operator++()调用;
函数原型:Date &operator++();
如果以全局函数实现前置的自增运算符,那么当编译器遇到表达式++d1,将产生函数调用operator++(d1);这个运算符函数的原型将在Date类中声明为:Date &operator(Date &);
13. 重载后置的自增运算符
遇到d1++,会产生d1.operator++(0)调用;
函数原型:Date &operator++(int );
实参0纯粹是个“哑值”,它使编译器能够区分前置的后置的自增运算符函数。
如果以全局函数实现前置的自增运算符,那么当编译器遇到表达式++d1,将产生函数调用operator++(d1, 0);这个运算符函数的原型将在Date类中声明为:Date &operator(Date &, int );
后置的自增运算符按值返回Date对象,而前置的自增运算符按引用返回Date对象。这是因为在进行自增前,后置的自增运算符通常先返回一个包含对象原始值的临时对象。C++将这样的对象作为右值处理,使其不能用在赋值运算符的左侧。前置的自增运算符返回实际自增后的具有新值的对象,这种对象在连续的表达式中可以作为左值使用。
由后置的自增(或自减)运算符创建的临时对象会对性能造成很大的影响,尤其是在循环中使用这个运算符时。出于这个原因,仅当程序的逻辑要求后置自增(或后置自减)操作时,才应该使用后置自增(或自减)运算符。
14. explicit构造函数
outputArray( 3 ); // convert 3 to an Array and output Array’s contents
main函数中将3隐式调用含有一个int参数的构造函数,产生Array对象,再打印Array对象
使用explicit,禁止不应该允许的由转换构造函数完成的隐式转换。
#ifndef ARRAY_H
#define ARRAY_H
#include <iostream>
using namespace std;
class Array
{
friend ostream &operator<<( ostream &, const Array & );
friend istream &operator>>( istream &, Array & );
public:
Array( int = 10 ); // default constructor
Array( const Array & ); // copy constructor
~Array(); // destructor
int getSize() const; // return size
const Array &operator=( const Array & ); // assignment operator
bool operator==( const Array & ) const; // equality operator
// inequality operator; returns opposite of == operator
bool operator!=( const Array &right ) const
{
return ! ( *this == right ); // invokes Array::operator==
} // end function operator!=
// subscript operator for non-const objects returns modifiable lvalue
int &operator[]( int );
// subscript operator for const objects returns rvalue
int operator[]( int ) const;
private:
int size; // pointer-based array size
int *ptr; // pointer to first element of pointer-based array
}; // end class Array
#endif
// Array class member- and friend-function definitions.
#include <iostream>
#include <iomanip>
#include <cstdlib> // exit function prototype
#include "Array.h" // Array class definition
using namespace std;
// default constructor for class Array (default size 10)
Array::Array( int arraySize )
{
size = ( arraySize > 0 ? arraySize : 10 ); // validate arraySize
ptr = new int[ size ]; // create space for pointer-based array
for ( int i = 0; i < size; i++ )
ptr[ i ] = 0; // set pointer-based array element
} // end Array default constructor
// copy constructor for class Array;
// must receive a reference to prevent infinite recursion
Array::Array( const Array &arrayToCopy )
: size( arrayToCopy.size )
{
ptr = new int[ size ]; // create space for pointer-based array
for ( int i = 0; i < size; i++ )
ptr[ i ] = arrayToCopy.ptr[ i ]; // copy into object
} // end Array copy constructor
// destructor for class Array
Array::~Array()
{
delete [] ptr; // release pointer-based array space
} // end destructor
// return number of elements of Array
int Array::getSize() const
{
return size; // number of elements in Array
} // end function getSize
// overloaded assignment operator;
// const return avoids: ( a1 = a2 ) = a3
const Array &Array::operator=( const Array &right )
{
if ( &right != this ) // avoid self-assignment
{
// for Arrays of different sizes, deallocate original
// left-side array, then allocate new left-side array
if ( size != right.size )
{
delete [] ptr; // release space
size = right.size; // resize this object
ptr = new int[ size ]; // create space for array copy
} // end inner if
for ( int i = 0; i < size; i++ )
ptr[ i ] = right.ptr[ i ]; // copy array into object
} // end outer if
return *this; // enables x = y = z, for example
} // end function operator=
// determine if two Arrays are equal and
// return true, otherwise return false
bool Array::operator==( const Array &right ) const
{
if ( size != right.size )
return false; // arrays of different number of elements
for ( int i = 0; i < size; i++ )
if ( ptr[ i ] != right.ptr[ i ] )
return false; // Array contents are not equal
return true; // Arrays are equal
} // end function operator==
// overloaded subscript operator for non-const Arrays;
// reference return creates a modifiable lvalue
int &Array::operator[]( int subscript )
{
// check for subscript out-of-range error
if ( subscript < 0 || subscript >= size )
{
cerr << "\nError: Subscript " << subscript
<< " out of range" << endl;
exit( 1 ); // terminate program; subscript out of range
} // end if
return ptr[ subscript ]; // reference return
} // end function operator[]
// overloaded subscript operator for const Arrays
// const reference return creates an rvalue
int Array::operator[]( int subscript ) const
{
// check for subscript out-of-range error
if ( subscript < 0 || subscript >= size )
{
cerr << "\nError: Subscript " << subscript
<< " out of range" << endl;
exit( 1 ); // terminate program; subscript out of range
} // end if
return ptr[ subscript ]; // returns copy of this element
} // end function operator[]
// overloaded input operator for class Array;
// inputs values for entire Array
istream &operator>>( istream &input, Array &a )
{
for ( int i = 0; i < a.size; i++ )
input >> a.ptr[ i ];
return input; // enables cin >> x >> y;
} // end function
// overloaded output operator for class Array
ostream &operator<<( ostream &output, const Array &a )
{
int i;
// output private ptr-based array
for ( i = 0; i < a.size; i++ )
{
output << setw( 12 ) << a.ptr[ i ];
if ( ( i + 1 ) % 4 == 0 ) // 4 numbers per row of output
output << endl;
} // end for
if ( i % 4 != 0 ) // end last line of output
output << endl;
return output; // enables cout << x << y;
} // end function operator<<
// Driver for simple class Array.
#include <iostream>
#include "Array.h"
using namespace std;
void outputArray( const Array & ); // prototype
int main()
{
Array integers1( 7 ); // 7-element array
outputArray( integers1 ); // output Array integers1
outputArray( 3 ); // convert 3 to an Array and output Array’s contents
} // end main
// print Array contents
void outputArray( const Array &arrayToOutput )
{
cout << "The Array received has " << arrayToOutput.getSize()
<< " elements. The contents are:\n" << arrayToOutput << endl;
} // end outputArray