[译文]C++操作符重载.md


目录
  • 前言
  • 1. string 类中的操作符重载
  • 2. 自定义操作符重载
    • 2.1 operator函数
    • 2.2 示例:以成员函数方式为Point类重载+操作符
    • 2.3 操作符重载的限定规则
  • 3. 通过"友元"非成员函数重载操作符
    • 3.1 为什么我们不可以一直使用成员函数来进行操作符重载
    • 3.2 友元函数
    • 3.3 示例:使用非成员函数的友元函数重载Point类的<< 和 >>操作符
  • 4. 重载二元操作符
  • 5. 重载一元函数
    • 5.1 一元前缀操作符
    • 5.2 一元后缀操作符
    • 5.3 示例:为Counter类重载前缀和后缀++
  • 6. 示例:在Point类中综合使用
  • 7. 通过单一参数的构造函数进行的隐式转换,explicit 关键字
  • 8. 示例:Mycomplex类
  • 9. 对象的动态内存分配
    • 示例:MyDynamicArrray

原文链接:https://www3.ntu.edu.sg/home/ehchua/programming/cpp/cp7_OperatorOverloading.html

前言

操作符重载是指操作符所执行的操作依赖于提供给操作符的参数。例如:(a)位左移操作符<<会执行流插入,当左操作数为一个输出流(ostream)对象。(b)当操作数为两个内置类型时,操作符*的意义是乘;当操作数为指针时,它是间接运算符。C++让我们可以给自定义类型拓展操作符重载。

操作符重载和函数重载类似,一个函数有很多版本,通过形参列表来区分。

1. string 类中的操作符重载

例如,C++的string(在头文件中)类中重载了下述的操作符来工作在string对象上。

  • 字符串比较(, !=, >, <, >=, <=):例如,你可以通过 str1str2 来比较两个string对象的内容。
  • 流插入和提取(<<, >>):例如,你可以使用 cout << str1 和 cin >> str2来输出/输入字符串对象。
  • 字符串拼接(+, +=):例如,str1 + str2 拼接两个string对象并产生一个新的string对象;str1 +=str2 将str2附加到str1上。
  • 字符索引或下标 [ ]:例如,你可以使用 str[n] 来取得索引n处的字符;或者 str[n] = c 来修改下表n处的字符。注意 [ ] 操作符不做边界检查,也就是说你需要确保下标在合法范围。如果要进行下标的边界检查,可以使用string的 at() 成员函数。
  • 赋值(=):例如,str1 = str2 把str2赋值给str1。

示例

TestStringOverloadOperators.cpp

/* Test overloaded operators in the C++ string class
   (TestStringOverloadOperators.cpp) */
#include 
#include 
#include     // needed to use the string class
using namespace std;
 
int main() {
   string msg1("hello");
   string msg2("HELLO");
   string msg3("hello");
 
   // Relational Operators (comparing the contents)
   cout << boolalpha;
   cout << (msg1 == msg2) << endl;  // false
   cout << (msg1 == msg3) << endl;  // true
   cout << (msg1 < msg2)  << endl;  // false (uppercases before lowercases)
 
   // Assignment
   string msg4 = msg1;
   cout << msg4 << endl;  // hello
 
   // Concatenation
   cout << (msg1 + " " + msg2) << endl;  // hello HELLO
   msg3 += msg2;
   cout << msg3 << endl;  // helloHELLO
 
   // Indexing
   cout << msg1[1] << endl;     // 'e'
   cout << msg1[99] << endl;    // garbage (no index-bound check)
// cout << msg1.at(99) << endl; // out_of_range exception
}

输出

$ ./TestStringOverloadOperators 
false
true
false
hello
hello HELLO
helloHELLO
e
l
terminate called after throwing an instance of 'std::out_of_range'
  what():  basic_string::at: __n (which is 99) >= this->size() (which is 5)
Aborted (core dumped)

注意

关系运算符(==, !=, >,<, >=, <=),+<<>> 作为非成员函数进行重载(overload), 左操作数可以不是string对象(例如C风格字符串,cincout);而=, [ ], += 作为成员函数进行重载(overload), 它们的左操作数必须是string对象。下面我将会详细讲述。

2. 自定义操作符重载

2.1 operator函数

要重载一个操作符,需要使用一个特殊的函数称为操作符函数,形式为operatorΔ(), Δ表示要被重载的操作符:

return-type operatorΔ(parameter-list)

例如,operator+()重载+操作符; operator<<()重载<<操作符。注意Δ必须是C++中存在的操作符。你不可以创建自己的操作符。

2.2 示例:以成员函数方式为Point类重载+操作符

在这个例子中,我将重载Point类中的+操作符来支持两个Point对象的加法。也就是说,我们可以这样写p3 = p1 + p2, 其中p1,p2p3都是Point对象,类似于普通的算数运算。我们将构造一个新的Point示例p3作为,不对实例p1p2进行修改。

Point.h

/* The Point class Header file (Point.h) */
#ifndef POINT_H
#define POINT_H
 
class Point {
private:
   int x, y; // Private data members
 
public:
   Point(int x = 0, int y = 0); // Constructor
   int getX() const; // Getters
   int getY() const;
   void setX(int x); // Setters
   void setY(int y);
   void print() const;
   const Point operator+(const Point & rhs) const;
         // Overload '+' operator as member function of the class
};
 
#endif

注意

  • 我们通过成员函数operator+()重载了+运算符,我们应该将this实例(左操作数)和rhs这个操作数相加, 构造一个新的实例来存放,然后以值的形式返回。我们不能以引用的形式返回一个函数中创建的局部变量,因为函数退出时局部变量就会销毁。
  • 为了性能以传引用的形式传递rhs
  • 声明为const的成员函数不可以修改成员函数
  • 返回值声明为const,防止其被用作左值(lvalue)。例如,防止这样写(p1+p2) = p3, 这样没什么意义,而且可能误写为(p1+p2) == p3

Point.cpp

/* The Point class Implementation file (Point.cpp) */
#include "Point.h"
#include 
using namespace std;
 
// Constructor - The default values are specified in the declaration
Point::Point(int x, int y) : x(x), y(y) { } // Using initializer list
 
// Getters
int Point::getX() const { return x; }
int Point::getY() const { return y; }
 
// Setters
void Point::setX(int x) { this->x = x; }
void Point::setY(int y) { this->y = y; }
 
// Public Functions
void Point::print() const {
   cout << "(" << x << "," << y << ")" << endl;
}
 
// Member function overloading '+' operator
const Point Point::operator+(const Point & rhs) const {
   return Point(x + rhs.x, y + rhs.y);
}

注意

函数指定了一个新的Point对象,用xy的和来构建,以const 值返回这个对象。

#include "Point.h"
#include 
using namespace std;
 
int main() {
   Point p1(1, 2), p2(4, 5);
   // Use overloaded operator +
   Point p3 = p1 + p2;
   p1.print();  // (1,2)
   p2.print();  // (4,5)
   p3.print();  // (5,7)
 
   // Invoke via usual dot syntax, same as p1+p2
   Point p4 = p1.operator+(p2);
   p4.print();  // (5,7)
 
   // Chaining
   Point p5 = p1 + p2 + p3 + p4;
   p5.print();  // (15,21)
}

注意

  • 可以以p1+p2的形式调用重载的操作符,这种形式会翻译成.运算p1.operator+(p2).

  • +操作符支持链式操作,因为p1+p2返回的是Point对象

2.3 操作符重载的限定规则

  • 重载的操作符必须是存在且有效的操作符。你不能创造一个自己的操作符,类似于⊕.
  • 某些C++操作符不可以重载,例如sizeof, . .*作用域解析运算符(scope resolution)::, 和条件运算符 ?:.
  • 重载的操作符必须至少有一个操作数是自定义类型。不可以给为基础类型工作的操作符进行重载。也就是说,不可以重载+为两个int相减.
  • 不可以修改被重载的操作符的语法规则,例如结合顺序,优先级,参数个数。

3. 通过"友元"非成员函数重载操作符

3.1 为什么我们不可以一直使用成员函数来进行操作符重载

成员函数operatorΔ()只能被对象通过.操作符调用,比如p1.operatorΔ(p2), 等价于p1 Δ p2。明确的讲,左操作数p1应该是一个特定类的对象。假如我们想重载一个二元运算符*,把对象p1int常量相乘,p1*5会被翻译为p1.operator*(5),但是5*p1就无法使用成员函数表示.处理这个问题的唯一方法是只允许用户写p1*5,单不允许写5*p1,这样对用户不友好而且打破了交换律规则(the rule of commutativity)。另一种方式就是使用非成员函数,这种形式不是通过对象和.操作符调用,而是通过所提供的参数。例如5*p1会翻译为operator+(5, p1).

简而言之,如果左操作数是特定类的对象,则不能使用成员函数重载一个操作符。

3.2 友元函数

普通的非成员函数不可以直接访问参数中所给出的对象的私有数据成员。一种特殊类型的函数叫做友元函数,允许访问参数对象的私有数据成员。

一个类的友元函数通过关键字friend来标识, 定义在类的外面,但是它可以无限制访问类的所有成员(private, protected, public 数据成员和成员函数)。友元函数可以提高性能,因为不需要调用public成员函数来访问私有数据成员。

3.3 示例:使用非成员函数的友元函数重载Point类的<< >>操作符

在这个例子中,我们将重载<<>>操作符来支持Point对象的流插入和提取,也就是cout << aPointcin >> aPoint。因为左操作数不是Point对象(coutostream对象,cinostream对象),我们不能使用成员函数而要使用非成员函数进行操作符重载。我们将让这些函数作为Point类的friend,来允许它们直接访问私有数据成员从而提高性能。

Point.h

/* The Point class Header file (Point.h) */
#ifndef POINT_H
#define POINT_H
 
#include 
 
// Class Declaration
class Point {
private:
   int x, y;
 
public:
   Point(int x = 0, int y = 0);
   int getX() const; // Getters
   int getY() const;
   void setX(int x); // Setters
   void setY(int y);
 
   friend std::ostream & operator<<(std::ostream & out, const Point & point);
   friend std::istream & operator>>(std::istream & in, Point & point);
};
 
#endif

注意

  • 友元函数既不是public也不是private,在类内声明时可以放在任意位置.
  • coutcin要以引用的形式传递给函数,这样函数可以直接访问coutcin而不是值的拷贝.
  • coutcin返回时也以引用的形式返回,为了支持链式操作。例如,cout << p1 << endl将被诠释为(cout << p1) << endl.
  • <<中,引用形参Point声明为const.因此,函数无法修改Point对象。另一方面,在>>中,Point引用是non-const,它将会被修改来保存输入.
  • 我们使用完全描述(fully-qualified)std::istream 而不是放置一个"using namespace std;"语句在头文件。因为这个头文件可能被很多文件所包含,这些文件将也包含using语句,可能它们并不需要。

Point.cpp

/* The Point class Implementation file (Point.cpp) */
#include 
#include "Point.h"
using namespace std;
 
// Constructor - The default values are specified in the declaration
Point::Point(int x, int y) : x(x), y(y) { } // using member initializer list
 
// Getters
int Point::getX() const { return x; }
int Point::getY() const { return y; }
 
// Setters
void Point::setX(int x) { this->x = x; }
void Point::setY(int y) { this->y = y; }
 
ostream & operator<<(ostream & out, const Point & point) {
   out << "(" << point.x << "," << point.y << ")";  // access private data
   return out;
}
 
istream & operator>>(istream & in, Point & point) {
   cout << "Enter x and y coord: ";
   in >> point.x >> point.y;  // access private data
   return in;
}

注意

  • 函数定义不需要关键字friend类名::范围解析修饰符, 因为它不属于任何类.
  • 函数operator<<()被声明为类Point的友元函数,因此它可以直接访问参数Point的私有数据成员x和y。函数operator<<()不是ostream类的友元函数,因为不需要访问ostream的私有成员。
  • 除了直接访问私有数据x和y,你还可以使用public成员函数getX()getY().在这种情况中,不需要把operator<<()声明为Point的友元函数。你可以简单地直接在头文件中声明一个普通的函数原型。
// 函数原型
ostream& operator<<(ostream& out, const Point& point);

// 函数定义
ostream& operator<<(ostream& out, const Point& point) {
    out << "(" << point.getX() << "," << point.getY() << ")";
    return out;
}

推荐使用友元函数,因为它可以提高性能。此外,重载的操作符变为类的公共拓展接口的一部分,这样可以方便使用和维护。

重载的>><<操作符也可用来作文件的输入/输出,因为文件IO流 ifstream/ofstream(在头文件fstream中)是istream/ostream的子类,示例:

#include 
#include "Point.h"
using namespace std;
 
int main() {
   Point p1(1, 2);
 
   ofstream fout("out.txt");
   fout << p1 << endl;
 
   ifstream fin("in.txt"); // contains "3 4"
   fin >> p1;
   cout << p1 << endl;
}

4. 重载二元操作符

C++的操作符不是二元操作符(例如x + y)就是一元操作符(例如!x,-x),除了不可以进行重载的三元条件操作符? :

假设我们想要重载二元操作符==来比较两个Point对象。我们可以用成员函数也可以用非成员函数。

  1. 使用成员函数来重载,像下面这样进行声明
class Point {
pubic:
    bool operator==(const Point& rhs) const; // p1.operator==(p2)
};

编译器把"p1 == p2"转为"p1.operator==(p2)", 对象p1调用成员函数传入参数p2.

只有左操作数是特定的类时可以使用成员函数.

  1. 使用非成员函数来重载,经常声明为友元函数来访问私有数据从而提高性能,像下面这样进行声明。
class Point{
    friend bool operator==(const Point& lhs, const Point& rhs); // operator==(p1, p2)
};

编译器会把p1 == p2转为 operator==(p1, p2)

5. 重载一元函数

大部分的一元操作符是前缀操作符,比如 !x, -x.因此,前缀是标准的一元操作符.然而,一元自增和自减操作符有两种形式:前缀(++x, --x)和后缀(x++, x--).我们用不同的机制来区分两种不同的形式。

5.1 一元前缀操作符

一元前缀操作符的例子是!x,-x,++x,--x. 这种情况既可以用成员函数也可以用非成员函数。例如,重载前缀自增运算符++

  1. 以非成员函数友元函数的形式重载
class Point {
  friend Point& operator++(Point& point);  
};

编译器把++p转为operator++(p).

  1. 以成员函数的形式重载
class Point {
public:
    Point& operator++(); // this Point  
};

编译器把++p解释为p.operator++().

因为唯一的操作数应该是这个类对象,所以既可以用成员函数也可以用非成员函数。

5.2 一元后缀操作符

一元自增和自减操作符有两种形式:前缀(++x, --x)和后缀(x++, x--)。重载后缀操作符(比如x++, x--)是一个挑战,它应该是和前缀操作符(++x, --x)不同的。因此如下面所展示的, dummy参数被引入进来表示后缀操作符。注意++应保存旧的值,执行自增,然后以值的形式返回保存的值。

  1. 以非成员函数友元函数的形式重载:
class Point {
  friend const Point operator(Point& point, int dummy);  
};

编译器把pt++转为operator++(pt, 0).int是一个虚拟值来区分前缀和后缀操作。

  1. 以成员函数的形式重载
class Point {
  const Point operator++(int dummy); // this Point  
};

5.3 示例:为Counter类重载前缀和后缀++

/* The Counter class Header file (Counter.h) */
#ifndef COUNTER_H
#define COUNTER_H
#include 
 
class Counter {
private:
   int count;
public:
   Counter(int count = 0);   // Constructor
   int getCount() const;     // Getters
   void setCount(int count); // Setters
   Counter & operator++();              // ++prefix
   const Counter operator++(int dummy); // postfix++
 
   friend std::ostream & operator<<(std::ostream & out, const Counter & counter);
};
 
#endif

注意

  • 前缀的函数返回的是这个对象的引用来支持链式计算,例如++++c,也就是++(++c).然而,返回的引用会被用作左值来做意料之外的操作,例如++c = 8.
  • 后缀的函数返回是一个const的对象值。一个const的对象值不可以作为左值。这样做避免了链式操作,例如c++++。尽管它会被解释为(c++)++.然而(c++)不会返回这个对象,而是一个临时对象。随后的++对临时对象起作用。
  • 前缀和后缀都是non-const函数,因为都修改了成员函数count_.
/* The Counter class Implementation file (Counter.cpp) */
#include "Counter.h"
#include 
using namespace std;
 
// Constructor - The default values are specified in the declaration
Counter::Counter(int c) : count(c) { } // using member initializer list
 
// Getters
int Counter::getCount() const { return count; }
 
// Setters
void Counter::setCount(int c) { count = c; }
 
// ++prefix, return reference of this
Counter & Counter::operator++() {
   ++count;
   return *this;
}
 
// postfix++, return old value by value
const Counter Counter::operator++(int dummy) {
   Counter old(*this);
   ++count;
   return old;
}
 
// Overload stream insertion << operator
ostream & operator<<(ostream & out, const Counter & counter) {
   out << counter.count;
   return out;
}

注意

  • 前缀函数增加count,然后返回这个对象的引用.
  • 后缀函数保存旧的值(通过拷贝构造函数用这个对象构造一个新的实例),增加count,然后以值返回保存的对象。
  • 明显地,对一个对象作后缀操作比前缀操作更低效一些,因为它创建了一个临时对象。如果接下来的操作不依赖于prefix/postfix的输出,使用前缀操作。
#include "Counter.h"
#include 
using namespace std;
 
int main() {
   Counter c1;
   cout << c1 << endl;     // 0
   cout << ++c1 << endl;   // 1
   cout << c1 << endl;     // 1
   cout << c1++ << endl;   // 1
   cout << c1 << endl;     // 2
   cout << ++++c1 << endl; // 4
// cout << c1++++ << endl; // error caused by const return value
}

注意

  • 注意 cout << c1++cout << ++c1的区别.前缀和后缀形式都按预期工作;
  • ++++c1是允许的,正确地工作。c1++++不被允许,因为它会产生错误结果.

6. 示例:在Point类中综合使用

这个示例以非成员函数重载了二元操作符<<>>作为流的插入和提取。它还以成员函数重载了一元++(前缀和后缀)和二元+=,还有+操作符。

Point.h

/* The Point class Header file (Point.h) */
#ifndef POINT_H
#define POINT_H
#include 
 
class Point {
private:
   int x, y;
 
public:
   explicit Point(int x = 0, int y = 0);
   int getX() const;
   int getY() const;
   void setX(int x);
   void setY(int y);
   Point & operator++();              // ++prefix
   const Point operator++(int dummy); // postfix++
   const Point operator+(const Point & rhs) const; // Point + Point
   const Point operator+(int value) const;           // Point + int
   Point & operator+=(int value);           // Point += int
   Point & operator+=(const Point & rhs); // Point += Point
 
   friend std::ostream & operator<<(std::ostream & out, const Point & point); // out << point
   friend std::istream & operator>>(std::istream & in, Point & point);        // in >> point
   friend const Point operator+(int value, const Point & rhs); // int + Point
};
 
#endif

Point.cpp

/* The Point class Implementation file (Point.cpp) */
#include "Point.h"
#include 
using namespace std;
 
// Constructor - The default values are specified in the declaration
Point::Point(int x, int y) : x(x), y(y) { }
 
// Getters
int Point::getX() const { return x; }
int Point::getY() const { return y; }
 
// Setters
void Point::setX(int x) { this->x = x; }
void Point::setY(int y) { this->y = y; }
 
// Overload ++Prefix, increase x, y by 1
Point & Point::operator++() {
   ++x;
   ++y;
   return *this;
}
 
// Overload Postfix++, increase x, y by 1
const Point Point::operator++(int dummy) {
   Point old(*this);
   ++x;
   ++y;
   return old;
}
 
// Overload Point + int. Return a new Point by value
const Point Point::operator+(int value) const {
   return Point(x + value, y + value);
}
 
// Overload Point + Point. Return a new Point by value
const Point Point::operator+(const Point & rhs) const {
   return Point(x + rhs.x, y + rhs.y);
}
 
// Overload Point += int. Increase x, y by value
Point & Point::operator+=(int value) {
   x += value;
   y += value;
   return *this;
}
 
// Overload Point += Point. Increase x, y by rhs
Point & Point::operator+=(const Point & rhs) {
   x += rhs.x;
   y += rhs.y;
   return *this;
}
 
// Overload << stream insertion operator
ostream & operator<<(ostream & out, const Point & point) {
   out << "(" << point.x << "," << point.y << ")";
   return out;
}
 
// Overload >> stream extraction operator
istream & operator>>(istream & in, Point & point) {
   cout << "Enter x and y coord: ";
   in >> point.x >> point.y;
   return in;
}
 
// Overload int + Point. Return a new point
const Point operator+(int value, const Point & rhs) {
   return rhs + value;  // use member function defined above
}

TestPoint.cpp

#include 
#include "Point.h"
using namespace std;
 
int main() {
   Point p1(1, 2);
   cout << p1 << endl;   // (1,2)
 
   Point p2(3,4);
   cout << p1 + p2 << endl; // (4,6)
   cout << p1 + 10 << endl; // (11,12)
   cout << 20 + p1 << endl; // (21,22)
   cout << 10 + p1 + 20 + p1 << endl; // (32,34)
 
   p1 += p2;
   cout << p1 << endl; // (4,6)
   p1 += 3;
   cout << p1 << endl; // (7,9)
 
   Point p3;  // (0,0)
   cout << p3++ << endl; // (0,0)
   cout << p3 << endl;   // (1,1)
   cout << ++p3 << endl; // (2,2)
}

7. 通过单一参数的构造函数进行的隐式转换,explicit 关键字

我在中详述了explicit

在c++中,只有一个参数的构造函数可以隐式的把一个值(value)转换为一个对象(object),示例:

#include 
using namespace std;
 
class Counter {
private:
   int count;
public:
   Counter(int c = 0) : count(c) { }
         // A single-argument Constructor which takes an int
         // It can be used to implicitly convert an int to a Counter object
   int getCount() const { return count; }    // Getter
   void setCount(int c) { count = c; } // Setter
};
 
int main() {
   Counter c1; // Declare an instance and invoke default constructor
   cout << c1.getCount() << endl;  // 0
 
   c1 = 9;
     // Implicit conversion
     // Invoke single-argument constructor Counter(9) to construct a temporary object.
     // Then copy into c1 via memberwise assignment.
   cout << c1.getCount() << endl;  // 9
}

这种隐式转换很容易让人迷惑。C++引入了一个关键字explicit来禁止这种隐式转换。尽管如此,你还是可以通过类型转换操作符进行显式的转化。示例:

#include 
using namespace std;
 
class Counter {
private:
   int count;
public:
   explicit Counter(int c = 0) : count(c) { }
      // Single-argument Constructor
      // Use keyword "explicit" to disable implicit automatic conversion in assignment
   int getCount() const { return count; }    // Getter
   void setCount(int c) { count = c; } // Setter
};
 
int main() {
   Counter c1; // Declare an instance and invoke default constructor
   cout << c1.getCount() << endl;  // 0
 
// Counter c2 = 9;
     // error: conversion from 'int' to non-scalar type 'Counter' requested
 
   c1 = (Counter)9;  // Explicit conversion via type casting operator
   cout << c1.getCount() << endl;  // 9
}

8. 示例:Mycomplex

Mycomplex类是C++ STL中complex类的简化版。我强烈建议你学习一下complex的源码(在complex头文件中),你可以把GNU GCC的源码下载下来。

MyComplex.h

/*
 * The MyComplex class header (MyComplex.h)
 * Follow, modified and simplified from GNU GCC complex template class
 */
#ifndef MY_COMPLEX_H
#define MY_COMPLEX_H
 
#include 
 
class MyComplex {
private:
   double real, imag;
 
public:
   explicit MyComplex (double real = 0, double imag = 0); // Constructor
   MyComplex & operator+= (const MyComplex & rhs); // c1 += c2
   MyComplex & operator+= (double real);           // c += double
   MyComplex & operator++ ();                      // ++c
   const MyComplex operator++ (int dummy);         // c++
   bool operator== (const MyComplex & rhs) const;   // c1 == c2
   bool operator!= (const MyComplex & rhs) const;   // c1 != c2
 
   // friends
   friend std::ostream & operator<< (std::ostream & out, const MyComplex & c); // out << c
   friend std::istream & operator>> (std::istream & in, MyComplex & c);        // in >> c
   friend const MyComplex operator+ (const MyComplex & lhs, const MyComplex & rhs); // c1 + c2
   friend const MyComplex operator+ (double real, const MyComplex & rhs);  // double + c
   friend const MyComplex operator+ (const MyComplex & lhs, double real);  // c + double
};
 
#endif
 

注意(有大部分的省略)

  • 如果我们不想改变原始数据,函数的引用/指针参数应该声明为const.另一方面,对于内置类型我们省略掉const,因为作值传递时会复制,原始值不会被改变。
  • 重载++时我们把返回值声明为const,这样作是为了避免c++++,这样的操作编译器会解释为(c++)++。然而,c++返回的是一个值,是一个临时对象而不是原始对象,接下来的++会作用在临时对象上从而产生错误的结果。++++c是可以的,因为++c产生的是一个对象的引用。

MyComplex.cpp

/* The MyComplex class implementation (MyComplex.cpp) */
#include "MyComplex.h"
 
// Constructor
MyComplex::MyComplex (double r, double i) : real(r), imag(i) { }
 
// Overloading += operator for c1 += c2
MyComplex & MyComplex::operator+= (const MyComplex & rhs) {
   real += rhs.real;
   imag += rhs.imag;
   return *this;
}
 
// Overloading += operator for c1 += double (of real)
MyComplex & MyComplex::operator+= (double value) {
   real += value;
   return *this;
}
 
// Overload prefix increment operator ++c (real part)
MyComplex & MyComplex::operator++ () {
  ++real;   // increment real part only
  return *this;
}
 
// Overload postfix increment operator c++ (real part)
const MyComplex MyComplex::operator++ (int dummy) {
   MyComplex saved(*this);
   ++real;  // increment real part only
   return saved;
}
 
// Overload comparison operator c1 == c2
bool MyComplex::operator== (const MyComplex & rhs) const {
   return (real == rhs.real && imag == rhs.imag);
}
 
// Overload comparison operator c1 != c2
bool MyComplex::operator!= (const MyComplex & rhs) const {
   return !(*this == rhs);
}
 
// Overload stream insertion operator out << c (friend)
std::ostream & operator<< (std::ostream & out, const MyComplex & c) {
   out << '(' << c.real << ',' << c.imag << ')';
   return out;
}
 
// Overload stream extraction operator in >> c (friend)
std::istream & operator>> (std::istream & in, MyComplex & c) {
   double inReal, inImag;
   char inChar;
   bool validInput = false;
   // Input shall be in the format "(real,imag)"
   in >> inChar;
   if (inChar == '(') {
      in >> inReal >> inChar;
      if (inChar == ',') {
         in >> inImag >> inChar;
         if (inChar == ')') {
            c = MyComplex(inReal, inImag);
            validInput = true;
         }
      }
   }
   if (!validInput) in.setstate(std::ios_base::failbit);
   return in;
}
 
// Overloading + operator for c1 + c2
const MyComplex operator+ (const MyComplex & lhs, const MyComplex & rhs) {
   MyComplex result(lhs);
   result += rhs;  // uses overload +=
   return result;
   // OR return MyComplex(lhs.real + rhs.real, lhs.imag + rhs.imag);
}
 
// Overloading + operator for c + double
const MyComplex operator+ (const MyComplex & lhs, double value) {
   MyComplex result(lhs);
   result += value;  // uses overload +=
   return result;
}
 
// Overloading + operator for double + c
const MyComplex operator+ (double value, const MyComplex & rhs) {
   return rhs + value;   // swap and use above function
}

注意(未译)

TestMyComplex.cpp

/* Test Driver for MyComplex class (TestMyComplex.cpp) */
#include 
#include 
#include "MyComplex.h"
 
int main() {
   std::cout << std::fixed << std::setprecision(2);
 
   MyComplex c1(3.1, 4.2);
   std::cout << c1 << std::endl;  // (3.10,4.20)
   MyComplex c2(3.1);
   std::cout << c2 << std::endl;  // (3.10,0.00)
 
   MyComplex c3 = c1 + c2;
   std::cout << c3 << std::endl;  // (6.20,4.20)
   c3 = c1 + 2.1;
   std::cout << c3 << std::endl;  // (5.20,4.20)
   c3 = 2.2 + c1;
   std::cout << c3 << std::endl;  // (5.30,4.20)
 
   c3 += c1;
   std::cout << c3 << std::endl;  // (8.40,8.40)
   c3 += 2.3;
   std::cout << c3 << std::endl;  // (10.70,8.40)
 
   std::cout << ++c3 << std::endl; // (11.70,8.40)
   std::cout << c3++ << std::endl; // (11.70,8.40)
   std::cout << c3   << std::endl; // (12.70,8.40)
 
// c1+c2 = c3;  // error: c1+c2 returns a const
// c1++++;      // error: c1++ returns a const
 
// MyComplex c4 = 5.5;  // error: implicit conversion disabled
   MyComplex c4 = (MyComplex)5.5;  // explicit type casting allowed
   std::cout << c4 << std::endl; // (5.50,0.00)
 
   MyComplex c5;
   std::cout << "Enter a complex number in (real,imag): ";
   std::cin >> c5;
   if (std::cin.good()) {  // if no error
      std::cout << c5 << std::endl;
   } else {
      std::cerr << "Invalid input" << std::endl;
   }
   return 0;
}

9. 对象的动态内存分配

如果你在构造函数中动态分配了内存,你需要提供自己的析构函数,拷贝构造函数,赋值运算符来管理动态分配的内存。C++编译器默认提供的版本不对动态内存起作用。

示例:MyDynamicArrray

MyDynamicArray.h

/*
 * The MyDynamicArray class header (MyDynamicArray.h)
 * A dynamic array of double elements
 */
#ifndef MY_DYNAMIC_ARRAY_H
#define MY_DYNAMIC_ARRAY_H
 
#include 
 
class MyDynamicArray {
private:
   int size_;   // size of array
   double * ptr;  // pointer to the elements
 
public:
   explicit MyDynamicArray (int n = 8);         // Default constructor
   explicit MyDynamicArray (const MyDynamicArray & a); // Copy constructor
   MyDynamicArray (const double a[], int n);    // Construct from double[]
   ~MyDynamicArray();                           // Destructor
 
   const MyDynamicArray & operator= (const MyDynamicArray & rhs); // Assignment a1 = a2
   bool operator== (const MyDynamicArray & rhs) const;     // a1 == a2
   bool operator!= (const MyDynamicArray & rhs) const;     // a1 != a2
 
   double operator[] (int index) const;  // a[i]
   double & operator[] (int index);      // a[i] = x
 
   int size() const { return size_; }    // return size of array
 
   // friends
   friend std::ostream & operator<< (std::ostream & out, const MyDynamicArray & a); // out << a
   friend std::istream & operator>> (std::istream & in, MyDynamicArray & a);        // in >> a
};
 
#endif

注意

  • C++中成员变量和成员函数不可以使用同样的名字。我偏向于有一个叫size()public 函数,这样可以和C++ STL保持一致,我把数据成员命名为size_,在尾部增加了下划线,这样也是遵循了实际中的C++命名准则。注意开头有下划线的变量是C++编译器的内部变量,例如 _xxx是数据成员,__xxx是本地变量。
  • 我们提两个版本的索引运算符:一个用来读操作,例如a[i],另一个用来写操作,例如a[i] = x。 读操作声明为const 的成员函数,而写操作的版本会返回元素的引用,这样可以用来作为左值来进行赋值。

MyDynamicArray.cpp

/* The MyDynamicArray class implementation (MyDynamicArray.cpp) */
#include 
#include "MyDynamicArray.h"
 
// Default constructor
MyDynamicArray::MyDynamicArray (int n) {
   if (n <= 0) {
      throw std::invalid_argument("error: size must be greater then zero");
   }
 
   // Dynamic allocate memory for n elements
   size_ = n;
   ptr = new double[size_];
   for (int i = 0; i < size_; ++i) {
      ptr[i] = 0.0;  // init all elements to zero
   }
}
 
// Override the copy constructor to handle dynamic memory
MyDynamicArray::MyDynamicArray (const MyDynamicArray & a) {
   // Dynamic allocate memory for a.size_ elements and copy
   size_ = a.size_;
   ptr = new double[size_];
   for (int i = 0; i < size_; ++i) {
      ptr[i] = a.ptr[i];  // copy each element
   }
}
 
// Construct via a built-in double[]
MyDynamicArray::MyDynamicArray (const double a[], int n) {
   // Dynamic allocate memory for a.size_ elements and copy
   size_ = n;
   ptr = new double[size_];
   for (int i = 0; i < size_; ++i) {
      ptr[i] = a[i];  // copy each element
   }
}
 
// Override the default destructor to handle dynamic memory
MyDynamicArray::~MyDynamicArray() {
   delete[] ptr;  // free dynamically allocated memory
}
 
// Override the default assignment operator to handle dynamic memory
const MyDynamicArray & MyDynamicArray::operator= (const MyDynamicArray & rhs) {
   if (this != &rhs) {  // no self assignment
      if (size_ != rhs.size_) {
         // reallocate memory for the array
         delete [] ptr;
         size_ = rhs.size_;
         ptr = new double[size_];
      }
      // Copy elements
      for (int i = 0; i < size_; ++i) {
         ptr[i] = rhs.ptr[i];
      }
   }
   return *this;
}
 
// Overload comparison operator a1 == a2
bool MyDynamicArray::operator== (const MyDynamicArray & rhs) const {
   if (size_ != rhs.size_) return false;
 
   for (int i = 0; i < size_; ++i) {
      if (ptr[i] != rhs.ptr[i]) return false;
   }
   return true;
}
 
// Overload comparison operator a1 != a2
bool MyDynamicArray::operator!= (const MyDynamicArray & rhs) const {
   return !(*this == rhs);
}
 
// Indexing operator - Read
double MyDynamicArray::operator[] (int index) const {
   if (index < 0 || index >= size_) {
      throw std::out_of_range("error: index out of range");
   }
   return ptr[index];
}
 
// Indexing operator - Writable a[i] = x
double & MyDynamicArray::operator[] (int index) {
   if (index < 0 || index >= size_) {
      throw std::out_of_range("error: index out of range");
   }
   return ptr[index];
}
 
// Overload stream insertion operator out << a (as friend)
std::ostream & operator<< (std::ostream & out, const MyDynamicArray & a) {
   for (int i = 0; i < a.size_; ++i) {
      out << a.ptr[i] << ' ';
   }
   return out;
}
 
// Overload stream extraction operator in >> a (as friend)
std::istream & operator>> (std::istream & in, MyDynamicArray & a) {
   for (int i = 0; i < a.size_; ++i) {
      in >> a.ptr[i];
   }
   return in;
}

TestMyDynamicArray.cpp

/* Test Driver for MyDynamicArray class (TestMyDynamicArray.cpp) */
#include 
#include 
#include "MyDynamicArray.h"
 
int main() {
   std::cout << std::fixed << std::setprecision(1) << std::boolalpha;
 
   MyDynamicArray a1(5);
   std::cout << a1 << std::endl;  // 0.0 0.0 0.0 0.0 0.0
   std::cout << a1.size() << std::endl;  // 5
 
   double d[3] = {1.1, 2.2, 3.3};
   MyDynamicArray a2(d, 3);
   std::cout << a2 << std::endl; // 1.1 2.2 3.3
 
   MyDynamicArray a3(a2);   // Copy constructor
   std::cout << a3 << std::endl; // 1.1 2.2 3.3
 
   a1[2] = 8.8;
   std::cout << a1[2] << std::endl;  // 8.8
// std::cout << a1[22] << std::endl; // error: out_of_range
 
   a3 = a1;
   std::cout << a3 << std::endl; // 0.0 0.0 8.8 0.0 0.0
 
   std::cout << (a1 == a3) << std::endl;  // true
   std::cout << (a1 == a2) << std::endl;  // false
 
   const int SIZE = 3;
   MyDynamicArray a4(SIZE);
   std::cout << "Enter " << SIZE << " elements: ";
   std::cin >> a4;
   if (std::cin.good()) {
      std::cout << a4 << std::endl;
   } else {
      std::cerr << "Invalid input" << std::endl;
   }
   return 0;
}