《C++大学教程》 第10章 前10节 笔记更一下。
第10章后续内容请见下一篇。
运算符重载:将C++中的运算符与类对象结合在一起使用
C++允许程序员重载大部分由类使用的运算符——编译器基于操作数的类型产生合适的代码。
例如,C++语言重载了加法运算符(+)和减法运算符(-),在基础数据类型如整数算术运算、浮点数算术运算和指针算术运算中,这两个运算符会根据上下文执行不同的运算。
转义字符\"
代表一个双引号字符"
.
string类中重载的相等和关系运算符:两个字符串自左向右,依次比较对应字符的ASCII值的大小。
string类提供了成员函数empty
判定string是否为空。如果string对象为空,那么成员函数empty
返回true
;否则,返回false
。
string类提供了成员函数substr
获得一个子字符串。第一个参数指定起始位置,第二个参数指定长度。当第二个参数不确定时,substr
返回字符串的剩余部分。
// test copy constructor
string s4( s1 );
分配了一个string对象,并用s1
的副本对其进行初始化,这引起string类拷贝构造函数的调用。
string类的重载运算符[ ]
不会进行任何边界检查。
// test subscript out of range with string member function "at"
try
{
cout << "Attempt to assign 'd' to s1.at( 30 ) yields:" << endl;
s1.at( 30 ) = 'd'; // ERROR: subscript out of range
} // end try
catch ( out_of_range &ex )
{
cout << "An exception occurred: " << ex.what() << endl;
} // end catch
string类的成员函数at
提供了检查范围。如果参数是一个无效的下标,会抛出一个异常out_of_range
;如果下标有效,则将指定位置的字符作为可修改的左值或者不可修改的右值(即一个const
引用)返回。
非static成员函数定义或者非成员函数定义可以实现运算符重载
格式:operator后接用于重载某些类的运算符
如果以成员函数方式重载运算符,那么该成员函数必须是非static的。因为他们必须由该类的对象调用,并作用在这个对象上。
如果要对类的对象使用运算符,那么运算符必须重载。但是,有三个运算符除外:赋值运算符(=)、取址运算符(&)和逗号运算符(,)。
不能被重载的运算符:.
、.*
、::
、?:
。
+
运算符重载为两个int
变量相减。运算符重载仅仅适用于用户定义类型或者用户定义类型和基本类型的混合。+
和+=
,必须被单独重载。二元运算符可以重载为带有一个参数的非static成员函数,或者两个参数(其中一个必须是类的对象或者是类对象的引用)的非成员函数。一个非成员函数运算符函数因为性能原因经常被声明为类的友元。
如果y
和z
是String
类的对象,那么y
y.operator<(z)
,调用声明如下的operator<
成员函数:
class String
{
public:
bool operator<(const String &) const;
...
};
仅当左操作数是该类的对象且重载函数是一个成员时,二元运算符的重载函数才能作为成员函数。
如果y
和z
是String
类对象或String
类对象的引用,那么y
operator<(y,z)
,调用声明如下的函数operator<
:
bool operator<(const String &, const String &);
// Fig. 10.3: PhoneNumber.h
// PhoneNumber class definition
#ifndef PHONENUMBER_H
#define PHONENUMBER_H
#include
#include
class PhoneNumber
{
friend std::ostream &operator<<( std::ostream &, const PhoneNumber & );
friend std::istream &operator>>( std::istream &, PhoneNumber & );
private:
std::string areaCode; // 3-digit area code
std::string exchange; // 3-digit exchange
std::string line; // 4-digit line
}; // end class PhoneNumber
#endif
// Fig. 10.4: PhoneNumber.cpp
// Overloaded stream insertion and stream extraction operators
// for class PhoneNumber.
#include
#include "PhoneNumber.h"
using namespace std;
// overloaded stream insertion operator; cannot be
// a member function if we would like to invoke it with
// cout << somePhoneNumber;
ostream &operator<<( ostream &output, const PhoneNumber &number )
{
output << "(" << number.areaCode << ") "
<< number.exchange << "-" << number.line;
return output; // enables cout << a << b << c;
} // end function operator<<
// overloaded stream extraction operator; cannot be
// a member function if we would like to invoke it with
// cin >> somePhoneNumber;
istream &operator>>( istream &input, PhoneNumber &number )
{
input.ignore(); // skip (
input >> setw( 3 ) >> number.areaCode; // input area code
input.ignore( 2 ); // skip ) and space
input >> setw( 3 ) >> number.exchange; // input exchange
input.ignore(); // skip dash (-)
input >> setw( 4 ) >> number.line; // input line
return input; // enables cin >> a >> b >> c;
} // end function operator>>
// Fig. 10.5: fig10_05.cpp
// Demonstrating class PhoneNumber's overloaded stream insertion
// and stream extraction operators.
#include
#include "PhoneNumber.h"
using namespace std;
int main()
{
PhoneNumber phone; // create object phone
cout << "Enter phone number in the form (123) 456-7890:" << endl;
// cin >> phone invokes operator>> by implicitly issuing
// the global function call operator>>( cin, phone )
cin >> phone;
cout << "The phone number entered was: ";
// cout << phone invokes operator<< by implicitly issuing
// the global function call operator<<( cout, phone )
cout << phone << endl;
} // end main
流提取运算符函数operator>>
以一个istream
引用(input
)和一个PhoneNumber
引用(number
)作为其参数,并返回一个istream
引用。
运算符函数把电话号码的3个部分作为字符串分别读到由形参number
引用的PhoneNumber
对象的成员变量areaCode
、exchange
和line
中。
当编译器遇到表达式cin >> phone;
时,会产生如下的非成员函数调用:operator>>(cin, phone);
。当这个函数调用执行时,引用形参input
成为cin
的别名,而引用形参number
成为phone
的别名。
流操纵符setw
限定了读到每个字符数组的字符个数。
istream
的成员函数ignore
丢弃输入流中指定个数的字符(默认为一个字符)。
流插入运算符函数以一个ostream
引用(output
)和一个const PhoneNumber
引用(number
)作为其参数,并返回一个ostream
引用。
【const
表示该PhoneNumber
仅用于输出,不做修改】
当编译器遇到表达式cout << phone
时,会产生如下的非成员函数调用:operator<<(cout, phone);
。
二元运算符的重载运算符函数可以作为成员函数来实现的前提条件是仅当左操作数是该函数所在类的对象。
如重载的输入和输出运算符需要直接访问非public
类成员,或者是这个类无法提供合适的获取函数,那么这些运算符应该声明为友元。
类的一元运算符可以重载为不带参数的非static成员函数(可以访问该类每个对象中的非static
数据)或者带有一个参数的非成员函数,且参数必须是该类的对象或者是该类对象的引用。
如果s
是String
类的对象,当一元运算符!
重载成不带参数的成员函数且编译器遇到表达式!s
时,编译器就会生成函数调用s.operator!()
。操作数s
就是调用String
类成员函数operator!
的类对象。该函数声明如下:
class String
{
public:
bool operator!() const;
...
};
如果s
是String
类的一个对象(或者是String
类对象的一个引用),那么!s
就会处理为operator!(s)
。调用声明如下的非成员函数operator!
:
bool operator!(const String &);
假设我们想把Date
对象d1
的天数加1。当编译器遇到前置自增运算的表达式++d1
时,它会产生下列成员函数调用:
d1.operator++()
这个运算符函数的原型是:
Date &operator++();
如果以非成员函数实现前置的自增运算符,那么当编译器遇到表达式++d1
时,将产生如下的函数调用:
operator++(d1)
这个非成员运算符函数的原型将在Date
类中声明为:
Date &operator++(Date &);
为了使编译器能够识别出重载的前置和后置自增运算符函数各自的特征,C++中采用的约定是:当编译器遇到后置自增运算的表达式d1++
时,它会产生如下的成员函数调用:
d1.operator++(0)
这个运算符函数的原型是:
Date &operator++(int);
实参0纯粹是个“哑值”,它使编译器能够区分前置和后置的自增运算符函数。
如果以非成员函数实现后置的自增运算,那么当编译器遇到表达式d1++
时,将产生如下的函数调用:
operator++(d1, 0)
这个非成员运算符函数的原型将在Date
类中声明为:
Date operator++(Date &, int);
注意:后置的自增运算符按值返回Date
对象,而前置的自增运算符按引用返回Date
对象。这是因为在进行自增前,后置的自增运算符通常先返回一个包含对象原始值的临时对象。
// overloaded postfix increment operator; note that the
// dummy integer parameter does not have a parameter name
Date Date::operator++( int )
{
Date temp = *this; // hold current state of object
helpIncrement();
// return unincremented, saved, temporary object
return temp; // value return; not a reference return
} // end function operator++
一进入函数operator++
,把当前对象(*This
)保存到temp
。然后,再调用helpIncrement
递增当前的Date
对象。最后,返回前面存储在temp
中的对象自增前的副本。
请注意,该函数不能返回局部Date
对象temp
的引用,因为局部变量在其声明所在的函数退出时便销毁了。这样一来,该函数的返回类型若声明为Date &
,就会返回一个不再存在对象的引用。
动态内存管理:在程序中你可以通过内存的分配和释放来控制对象,或者由内置类型或用户自定义类型构成的数组。通过运算符new
和delete
实现。
Timer *timePtr = new Time();
new
运算符为一个Timer
类型的对象分配大小合适的内存空间,调用默认的构造函数来初始化这个对象并返回一个指向new
运算符右边类型的指针*Timer *
。如果new
无法在内存中为对象找到足够的空间,会通过抛出异常,指出发生了错误。
delete timePtr;
首先调用timePtr
所指对象的析构函数,然后回收对象占用的内存空间,把内存返还给自由存储区。
内存泄漏:当动态分配的内存空间不再使用时若不释放,将导致系统过早地用完内存。
在delete
一个动态分配的内存块之后确保不要对同一块内存再次delete
。防止方法:将delete
过的指针的值设为nullptr
。delete
一个nullptr
是没有影响的。
double *ptr = new double(3.14159);
Timer *timePtr = new Time(12, 45, 0);
分配一个10个元素的整型数组并把这个数组指派给gradesArray
:
int *gradesArray = new int[10]();
声明了指针gradesArray
,并且将指向一个动态分配的10元素整型数组第一个元素的指针赋值给gradesArray
。在new int[10]
后边的()
初始化数组元素。
在编译时,创建的数组的大小必须用常量整数表达式来指定。但是,动态分配数组的大小可以用在执行期间求值的任何非负整数表达式指定。
要删除由gradesArray
指向的动态分配的数组的内存,必须使用:
delete [] gradesArray;
delete []
:如果gradesArray
指向一个对象数组,那么语句首先调用数组中每个对象的析构函数,然后再收回空间。
类似地,总是使用delete
运算符将分配给单个元素的内存空间删除。
基于指针的数组存在大量的问题,包括:
n
的数组其元素的下标必须是0,…,n-1
,下标范围不允许有其他的选择。// Fig. 10.9: fig10_09.cpp
// Array class test program.
#include
#include
#include "Array.h"
using namespace std;
int main()
{
Array integers1( 7 ); // seven-element Array
Array integers2; // 10-element Array by default
// print integers1 size and contents
cout << "Size of Array integers1 is "
<< integers1.getSize()
<< "\nArray after initialization:\n" << integers1;
// print integers2 size and contents
cout << "\nSize of Array integers2 is "
<< integers2.getSize()
<< "\nArray after initialization:\n" << integers2;
// input and print integers1 and integers2
cout << "\nEnter 17 integers:" << endl;
cin >> integers1 >> integers2;
cout << "\nAfter input, the Arrays contain:\n"
<< "integers1:\n" << integers1
<< "integers2:\n" << integers2;
// use overloaded inequality (!=) operator
cout << "\nEvaluating: integers1 != integers2" << endl;
if ( integers1 != integers2 )
cout << "integers1 and integers2 are not equal" << endl;
// create Array integers3 using integers1 as an
// initializer; print size and contents
Array integers3( integers1 ); // invokes copy constructor
cout << "\nSize of Array integers3 is "
<< integers3.getSize()
<< "\nArray after initialization:\n" << integers3;
// use overloaded assignment (=) operator
cout << "\nAssigning integers2 to integers1:" << endl;
integers1 = integers2; // note target Array is smaller
cout << "integers1:\n" << integers1
<< "integers2:\n" << integers2;
// use overloaded equality (==) operator
cout << "\nEvaluating: integers1 == integers2" << endl;
if ( integers1 == integers2 )
cout << "integers1 and integers2 are equal" << endl;
// use overloaded subscript operator to create rvalue
cout << "\nintegers1[5] is " << integers1[ 5 ];
// use overloaded subscript operator to create lvalue
cout << "\n\nAssigning 1000 to integers1[5]" << endl;
integers1[ 5 ] = 1000;
cout << "integers1:\n" << integers1;
// attempt to use out-of-range subscript
try
{
cout << "\nAttempt to assign 1000 to integers1[15]" << endl;
integers1[ 15 ] = 1000; // ERROR: subscript out of range
} // end try
catch ( out_of_range &ex )
{
cout << "An exception occurred: " << ex.what() << endl;
} // end catch
} // end main
// Fig. 10.10: Array.h
// Array class definition with overloaded operators.
#ifndef ARRAY_H
#define ARRAY_H
#include
class Array
{
friend std::ostream &operator<<( std::ostream &, const Array & );
friend std::istream &operator>>( std::istream &, Array & );
public:
explicit Array( int = 10 ); // default constructor
Array( const Array & ); // copy constructor
~Array(); // destructor
size_t 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:
size_t size; // pointer-based array size
int *ptr; // pointer to first element of pointer-based array
}; // end class Array
#endif
// Fig. 10.11: Array.cpp
// Array class member- and friend-function definitions.
#include
#include
#include
#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 :
throw invalid_argument( "Array size must be greater than 0" ) ),
ptr( new int[ size ] )
{
for ( size_t 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 an Array
Array::Array( const Array &arrayToCopy )
: size( arrayToCopy.size ),
ptr( new int[ size ] )
{
for ( size_t 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
size_t 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 ( size_t 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 ( size_t 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 )
throw out_of_range( "Subscript out of range" );
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 )
throw out_of_range( "Subscript out of range" );
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 ( size_t 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 )
{
// output private ptr-based array
for ( size_t 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 ( a.size % 4 != 0 ) // end last line of output
output << endl;
return output; // enables cout << x << y;
} // end function operator<<
每个Array
对象都包含两个数据成员:一个是size
成员,用于表示数组元素的个数;另一个是int
指针ptr
,指向了由该Array
对象管理的动态分配的基于指针的整型数组。
将重载的流插入运算符和重载的流提取运算符声明为Array
类的友元。因此可以访问Array
的private
数据。
基于范围的for
语句不能用于动态分配的内置数组。
这此默认构造函数首先确认实参的有效性,并把它赋值给数据成员size
;然后利用new
为基于指针的内部表示的数组分配内存,并把new
返回的指针赋给数据成员ptr
;接着构造函数使用for语句将数组的所有元素值设置为零。
拷贝构造函数通过建立现有Array
对象的副本来初始化一个Array
对象。
在需要对象的副本时,例如在向函数按值传递对象时,从函数按值返回对象时,或者用同一个类的另一对象的副本初始化一个对象时,都会用到拷贝构造函数。
拷贝构造函数的参数应当是一个const
引用,以允许复制const
对象。
// create Array integers3 using integers1 as an
// initializer; print size and contents
Array integers3( integers1 ); // invokes copy constructor
实例化Array
对象integers3
,并用Array
对象integers1
对其进行初始化。
调用了Array
拷贝构造函数把integers1
的元素复制到integers3
。
请注意,Array integers3 = integers1;
同样可以调用拷贝构造函数。当等号出现在对象声明中,它调用该对象的构造函数。可用来向构造函数传递单个参数。
当Array
类的对象离开其作用域时,调用析构函数。该析构函数使用delete []
释放在构造函数中由new
动态分配的内存。
当编译器遇到表达式integers1 = integers2;
,会通过调用integers1.operator = (integers2);
来调用成员函数operator =
。
成员函数operator =
进行自我赋值测试。如果this
等于右操作数的地址,那么说明正在试图自我赋值,因此跳过赋值操作;
如果不是自我赋值,那么该成员函数会确认两个数组的大小是否相同,若是相同的情况,则不会为左侧Array
对象中原来的整数数组重新分配空间。否则operator =
首先使用delete []
释放原来分配给目标数组的内存,将源数组的size
复制给目标数组的size
,使用new
为目标数组分配内存并把new返回的指针赋给这个数组的ptr
成员。
随后for
语句将数组元素从源数组复制到目标数组中。
不管是否为自我赋值,该成员函数都返回当前对象*this
的常量引用。允许执行诸如x = y = z
之类的串联Array
赋值操作[从右到左],但也能防止(x=y) = z
这种形式,因为x = y
返回的常量引用不能被z
赋值。
通常需为任何一个使用动态分配内存的类同时提供一组函数:拷贝构造函数、析构函数和重载的赋值运算符函数。
// inequality operator; returns opposite of == operator
bool operator!=( const Array &right ) const
{
return ! ( *this == right ); // invokes Array::operator==
} // end function operator!=
成员函数operator !=
使用重载的operator ==
确定两个Array
是否相等,然后返回这一结果的相反值。
重用可以减少类中所必须书写的代码量。
此外,请注意:operator !=
完整的函数定义位于Array
头文件中。这样使编译器可以内联operator !=
的定义,消除额外的函数调用开销。
// subscript operator for non-const objects returns modifiable lvalue
int &operator[]( int );
// subscript operator for const objects returns rvalue
int operator[]( int ) const;
当编译器遇到表达式integers1[ 5 ]
时,通过生成函数调用integers1.operator[](5)
,调用合适的重载的operator[]
成员函数。
程序只能调用const
对象的const
成员函数。
每一个operator[]
定义都确定作为参数接收的下标是否在有效范围内。如果越界,则会抛出out_of_range
异常;
如果属于有效范围,非const
版本的operator[]
返回作为引用的对应数组元素,可作为可修改的左值而使用;const
版本的operator[]
则返回对应数组元素的副本,只能作为右值。
将本章前10节写于本篇。
每天回来看一节,第二天就忘了。
等着看完后再每天回来写一节,进度更是缓慢。
今天强行克制着自己把上半部分更完。
越拖越烦。
个人水平有限,有问题欢迎各位大神批评指正!