《C++大学教程》 第10章 运算符重载:string类 笔记(上)

0. 前言

《C++大学教程》 第10章 前10节 笔记更一下。
第10章后续内容请见下一篇。

10.1 简介

运算符重载将C++中的运算符与类对象结合在一起使用

C++允许程序员重载大部分由类使用的运算符——编译器基于操作数的类型产生合适的代码。

例如,C++语言重载了加法运算符(+)和减法运算符(-),在基础数据类型如整数算术运算、浮点数算术运算和指针算术运算中,这两个运算符会根据上下文执行不同的运算

10.2 使用标准库中string类的重载运算符

转义字符\"代表一个双引号字符".

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引用)返回。

10.3 运算符重载的基础知识

非static成员函数定义或者非成员函数定义可以实现运算符重载

格式:operator后接用于重载某些类的运算符

如果以成员函数方式重载运算符,那么该成员函数必须是非static的。因为他们必须由该类的对象调用,并作用在这个对象上。

如果要对类的对象使用运算符,那么运算符必须重载。但是,有三个运算符除外:赋值运算符(=)、取址运算符(&)和逗号运算符(,)

不能被重载的运算符:..*::?:

运算符重载的规则和限制:

  • 运算符的优先级不能被重载改变。
  • 运算符的结合性不能被重载改变。
  • 运算符所需要的操作数的数目不能改变。
  • 不能创造新的运算符。
  • 运算符作用在基本类型上的方式不能被运算符重载改变。例如,不能将+运算符重载为两个int变量相减。运算符重载仅仅适用于用户定义类型或者用户定义类型和基本类型的混合。
  • 关系运算符,如++=,必须被单独重载。
  • 当重载()、[]、->或任何赋值运算符时,运算符重载函数必须被声明为类成员。对所有其他可重载的运算符来说,运算符重载函数可以时成员函数或者非成员函数。

10.4 重载二元运算符

二元运算符可以重载为带有一个参数的非static成员函数,或者两个参数(其中一个必须是类的对象或者是类对象的引用)的非成员函数。一个非成员函数运算符函数因为性能原因经常被声明为类的友元

作为成员函数的二元重载运算符

如果yzString类的对象,那么y<z就会被处理成y.operator<(z),调用声明如下的operator<成员函数:

	class String
	{
	public:
		bool operator<(const String &) const;
		...
	};

仅当左操作数是该类的对象且重载函数是一个成员时,二元运算符的重载函数才能作为成员函数。

作为非成员函数的二元重载运算符

如果yzString类对象或String类对象的引用,那么y<z就会被处理成operator<(y,z),调用声明如下的函数operator<

	bool operator<(const String &, const String &);

10.5 重载二元流插入运算符和流提取运算符

// Fig. 10.3: PhoneNumber.h
// PhoneNumber class definition
#ifndef PHONENUMBER_H
#define PHONENUMBER_H

#include <iostream>
#include <string>

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 <iomanip>
#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 <iostream>
#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对象的成员变量areaCodeexchangeline中。

当编译器遇到表达式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类成员,或者是这个类无法提供合适的获取函数,那么这些运算符应该声明为友元。

10.6 重载一元运算符

类的一元运算符可以重载为不带参数的非static成员函数(可以访问该类每个对象中的非static数据)或者带有一个参数的非成员函数,且参数必须是该类的对象或者是该类对象的引用

作为成员函数的一元重载运算符

如果sString类的对象,当一元运算符!重载成不带参数的成员函数且编译器遇到表达式!s时,编译器就会生成函数调用s.operator!()。操作数s就是调用String类成员函数operator!的类对象。该函数声明如下:

	class String
	{
	public:
		bool operator() const;
		...
	};

作为非成员函数的一元重载运算符

如果sString类的一个对象(或者是String类对象的一个引用),那么!s就会处理为operator!(s)。调用声明如下的非成员函数operator!

	bool operator!(const String &);

10.7 重载一元前置与后置运算符:++和- -

重载前置的自增运算符

假设我们想把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对象。这是因为在进行自增前,后置的自增运算符通常先返回一个包含对象原始值的临时对象。

10.8 实例研究: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 &,就会返回一个不再存在对象的引用

10.9 动态内存管理

动态内存管理:在程序中你可以通过内存的分配和释放来控制对象,或者由内置类型或用户自定义类型构成的数组。通过运算符newdelete实现。

使用new动态获取内存

	Timer *timePtr = new Time();

new运算符为一个Timer类型的对象分配大小合适的内存空间,调用默认的构造函数来初始化这个对象并返回一个指向new运算符右边类型的指针*Timer *。如果new无法在内存中为对象找到足够的空间,会通过抛出异常,指出发生了错误。

使用delete动态释放内存

	delete timePtr;

首先调用timePtr所指对象的析构函数,然后回收对象占用的内存空间,把内存返还给自由存储区。

内存泄漏:当动态分配的内存空间不再使用时若不释放,将导致系统过早地用完内存。

delete一个动态分配的内存块之后确保不要对同一块内存再次delete。防止方法:将delete过的指针的值设为nullptrdelete一个nullptr是没有影响的。

动态内存的初始化

	double *ptr = new double(3.14159);

	Timer *timePtr = new Time(12, 45, 0);

使用new[]动态分配内置数组

分配一个10个元素的整型数组并把这个数组指派给gradesArray:

	int *gradesArray = new int[10]();

声明了指针gradesArray,并且将指向一个动态分配的10元素整型数组第一个元素的指针赋值给gradesArray。在new int[10]后边的()初始化数组元素。

在编译时,创建的数组的大小必须用常量整数表达式来指定。但是,动态分配数组的大小可以用在执行期间求值的任何非负整数表达式指定

使用delete[]动态释放内置数组

要删除由gradesArray指向的动态分配的数组的内存,必须使用:

	delete [] gradesArray;

delete []:如果gradesArray指向一个对象数组,那么语句首先调用数组中每个对象的析构函数,然后再收回空间。
类似地,总是使用delete运算符将分配给单个元素的内存空间删除。

10.10 实例研究:Array类

基于指针的数组存在大量的问题,包括:

  • 程序能轻易“跨过”数组任意一端,因为C++并不检查下标是否超出数组范围。
  • 大小为n的数组其元素的下标必须是0,…,n-1,下标范围不允许有其他的选择。
  • 不能一次输入或者输出整个数组。每个数组元素必须单独读取或者写入。
  • 对于两个数组而言,不能用相等运算符或者关系运算符进行有意义的比较。
  • 如果要把数组传递给一个函数,且函数本身在设计时就满足能够处理任意大小的数组,那么这个数组的大小就必须作为另外的实参而传入。
  • 不能用赋值运算符将一个数组赋值给另一个数组。

10.10.1 使用Array类

// Fig. 10.9: fig10_09.cpp
// Array class test program.
#include <iostream>
#include <stdexcept>
#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

10.10.2 Array类定义

// Fig. 10.10: Array.h
// Array class definition with overloaded operators.
#ifndef ARRAY_H
#define ARRAY_H

#include <iostream>

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 <iostream>
#include <iomanip>
#include <stdexcept> 

#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类的友元。因此可以访问Arrayprivate数据。

基于范围的for语句不能用于动态分配的内置数组。

Array默认构造函数

这此默认构造函数首先确认实参的有效性,并把它赋值给数据成员size;然后利用new为基于指针的内部表示的数组分配内存,并把new返回的指针赋给数据成员ptr;接着构造函数使用for语句将数组的所有元素值设置为零。

Array拷贝构造函数

拷贝构造函数通过建立现有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析构函数

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节写于本篇。

每天回来看一节,第二天就忘了。
等着看完后再每天回来写一节,进度更是缓慢。
今天强行克制着自己把上半部分更完。
越拖越烦。

个人水平有限,有问题欢迎各位大神批评指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值