運算符重載
函式
自定義類的賦值運算符重載函式的作用與內置賦值運算符的作用類似,但是要注意的是,它與拷貝構造函式與析構函式一樣,要注意深拷貝淺拷貝的問題,在沒有深拷貝淺拷貝的情況下,如果沒有指定默認的賦值運算符重載函式,那么系統將會自動提供一個賦值運算符重載函式。
示例
下面是Vector的定義—— 包含成員欄位、構造函式和一個ToString()重寫方法,以便查看Vector的內容,最後是運算符重載:
namespace Wrox.ProCSharp.OOCSharp
{
struct Vector
{
public double x,y,z;
public Vector(double x,double y,double z)
{
this.x = x;
this.y = y;
this.z = z;
}
public Vector(Vector rhs)
{
x = rhs.x;
y = rhs.y;
z = rhs.z;
}
public override string ToString()
{
return "( " + x + "," + y + "," + z + " )";
}
這裡提供了兩個構造函式,通過傳遞每個元素的值,或者提供另一個複製其值的Vector,來指定矢量的初始值。第二個構造函式帶一個Vector參數,通常稱為複製構造函式,因為它們允許通過複製另一個實例來初始化一個類或結構實例。注意,為了簡單起見,把欄位設定為public。也可以把它們設定為private,編寫相應的屬性來訪問它們,這樣做不會改變這個程式的功能,只是代碼會複雜一些。
下面是Vector結構的有趣部分—— 為+運算符提供支持的運算符重載:
public static Vector operator + (Vector lhs,Vector rhs)
{
Vector result = new Vector(lhs);
result.x += rhs.x;
result.y += rhs.y;
result.z += rhs.z;
return result;
}
}
}
運算符重載的聲明方式與方法的聲明方式相同,但operator關鍵字告訴編譯器,它實際上是一個運算符重載,後面是相關運算符的符號,在本例中就是+。返回類型是在使用這個運算符時獲得的類型。在本例中,把兩個矢量加起來會得到另一個矢量,所以返回類型就是Vector。對於這個+運算符重載,返回類型與包含類一樣,但這種情況並不是必需的。兩個參數就是要操作的對象。對於二元運算符(帶兩個參數),如+和-運算符,第一個參數是放在運算符左邊的值,第二個參數是放在運算符右邊的值。
C#要求所有的運算符重載都聲明為public和static,這表示它們與它們的類或結構相關聯,而不是與實例相關聯,所以運算符重載的代碼體不能訪問非靜態類成員,也不能訪問this標識符;這是可以的,因為參數提供了運算符執行任務所需要知道的所有數據。
前面介紹了聲明運算符+的語法,下面看看運算符內部的情況:
{
Vector result = new Vector(lhs);
result.x += rhs.x;
result.y += rhs.y;
result.z += rhs.z;
return result;
}
這部分代碼與聲明方法的代碼是完全相同的,顯然,它返回一個矢量,其中包含前面定義的lhs和rhs的和,即把x、y和z分別相加。
簡單代碼
下面需要編寫一些簡單的代碼,測試Vector結構:
static void Main()
{
Vector vect1,vect2,vect3;
vect1 = new Vector(3.0,3.0,1.0);
vect2 = new Vector(2.0,;–4.0,–4.0);
vect3 = vect1 + vect2;
Console.WriteLine("vect1 = " + vect1.ToString());
Console.WriteLine("vect2 = " + vect2.ToString());
Console.WriteLine("vect3 = " + vect3.ToString());
}
把這些代碼保存為Vectors.cs,編譯並運行它,結果如下:
Vectors
vect1 = ( 3,3,1 )
vect2 = ( 2,–4,–4 )
vect3 = ( 5,–1,–3 )
運算符重載不能用於Java
下面舉一個TICPP中的例子……
一元的:
//: C12:OverloadingUnaryOperators.cpp
// From Thinking in C++,2nd Edition
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
#include <iostream>
using namespace std;
// Non-member functions:
class Integer {
long i;
Integer* This() { return this; }
public:
Integer(long ll = 0) : i(ll) {}
// No side effects takes const& argument:
friend const Integer&
operator+(const Integer& a);
friend const Integer
operator-(const Integer& a);
friend const Integer
operator~(const Integer& a);
friend Integer*
operator&(Integer& a);
friend int
operator!(const Integer& a);
// Side effects have non-const& argument:
// Prefix:
friend const Integer&
operator++(Integer& a);
// Postfix:
friend const Integer
operator++(Integer& a,int);
// Prefix:
friend const Integer&
operator--(Integer& a);
// Postfix:
friend const Integer
operator--(Integer& a,int);
};
// Global operators:
const Integer& operator+(const Integer& a) {
cout << "+Integer\n";
return a; // Unary + has no effect
}
const Integer operator-(const Integer& a) {
cout << "-Integer\n";
return Integer(-a.i);
}
const Integer operator~(const Integer& a) {
cout << "~Integer\n";
return Integer(~a.i);
}
Integer* operator&(Integer& a) {
cout << "∬eger\n";
return a.This(); // &a is recursive!
}
int operator!(const Integer& a) {
cout << "!Integer\n";
return !a.i;
}
// Prefix; return incremented value
const Integer& operator++(Integer& a) {
cout << "++Integer\n";
a.i++;
return a;
}
// Postfix; return the value before increment:
const Integer operator++(Integer& a,int) {
cout << "Integer++\n";
Integer before(a.i);
a.i++;
return before;
}
// Prefix; return decremented value
const Integer& operator--(Integer& a) {
cout << "--Integer\n";
a.i--;
return a;
}
// Postfix; return the value before decrement:
const Integer operator--(Integer& a,int) {
cout << "Integer--\n";
Integer before(a.i);
a.i--;
return before;
}
// Show that the overloaded operators work:
void f(Integer a) {
+a;
-a;
~a;
Integer* ip = &a;
!a;
++a;
a++;
--a;
a--;
}
// Member functions (implicit "this"):
class Byte {
unsigned char b;
public:
Byte(unsigned char bb = 0) : b(bb) {}
// No side effects: const member function:
const Byte& operator+() const {
cout << "+Byte\n";
return *this;
}
const Byte operator-() const {
cout << "-Byte\n";
return Byte(-b);
}
const Byte operator~() const {
cout << "~Byte\n";
return Byte(~b);
}
Byte operator!() const {
cout << "!Byte\n";
return Byte(!b);
}
Byte* operator&() {
cout << "&Byte\n";
return this;
}
// Side effects: non-const member function:
const Byte& operator++() { // Prefix
cout << "++Byte\n";
b++;
return *this;
}
const Byte operator++(int) { // Postfix
cout << "Byte++\n";
Byte before(b);
b++;
return before;
}
const Byte& operator--() { // Prefix
cout << "--Byte\n";
--b;
return *this;
}
const Byte operator--(int) { // Postfix
cout << "Byte--\n";
Byte before(b);
--b;
return before;
}
};
void g(Byte b) {
+b;
-b;
~b;
Byte* bp = &b;
!b;
++b;
b++;
--b;
b--;
}
int main() {
Integer a;
f(a);
Byte b;
g(b);
} ///:~
二元的:
//: C12:Integer.h
// From Thinking in C++,2nd Edition
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Non-member overloaded operators
#ifndef INTEGER_H
#define INTEGER_H
#include <iostream>
// Non-member functions:
class Integer {
long i;
public:
Integer(long ll = 0) : i(ll) {}
// Operators that create new,modified value:
friend const Integer
operator+(const Integer& left,
const Integer& right);
friend const Integer
operator-(const Integer& left,
const Integer& right);
friend const Integer
operator*(const Integer& left,
const Integer& right);
friend const Integer
operator/(const Integer& left,
const Integer& right);
friend const Integer
operator%(const Integer& left,
const Integer& right);
friend const Integer
operator^(const Integer& left,
const Integer& right);
friend const Integer
operator&(const Integer& left,
const Integer& right);
friend const Integer
operator|(const Integer& left,
const Integer& right);
friend const Integer
operator<<(const Integer& left,
const Integer& right);
friend const Integer
operator>>(const Integer& left,
const Integer& right);
// Assignments modify & return lvalue:
friend Integer&
operator+=(Integer& left,
const Integer& right);
friend Integer&
operator-=(Integer& left,
const Integer& right);
friend Integer&
operator*=(Integer& left,
const Integer& right);
friend Integer&
operator/=(Integer& left,
const Integer& right);
friend Integer&
operator%=(Integer& left,
const Integer& right);
friend Integer&
operator^=(Integer& left,
const Integer& right);
friend Integer&
operator&=(Integer& left,
const Integer& right);
friend Integer&
operator|=(Integer& left,
const Integer& right);
friend Integer&
operator>>=(Integer& left,
const Integer& right);
friend Integer&
operator<<=(Integer& left,
const Integer& right);
// Conditional operators return true/false:
friend int
operator==(const Integer& left,
const Integer& right);
friend int
operator!=(const Integer& left,
const Integer& right);
friend int
operator<(const Integer& left,
const Integer& right);
friend int
operator>(const Integer& left,
const Integer& right);
friend int
operator<=(const Integer& left,
const Integer& right);
friend int
operator>=(const Integer& left,
const Integer& right);
friend int
operator&&(const Integer& left,
const Integer& right);
friend int
operator||(const Integer& left,
const Integer& right);
// Write the contents to an ostream:
void print(std::ostream& os) const { os << i; }
};
#endif // INTEGER_H ///:~
//: C12:Integer.cpp {O}
// From Thinking in C++,2nd Edition
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Implementation of overloaded operators
#include "Integer.h"
#include "../require.h"
const Integer
operator+(const Integer& left,
const Integer& right) {
return Integer(left.i + right.i);
}
const Integer
operator-(const Integer& left,
const Integer& right) {
return Integer(left.i - right.i);
}
const Integer
operator*(const Integer& left,
const Integer& right) {
return Integer(left.i * right.i);
}
const Integer
operator/(const Integer& left,
const Integer& right) {
require(right.i != 0,"divide by zero");
return Integer(left.i / right.i);
}
const Integer
operator%(const Integer& left,
const Integer& right) {
require(right.i != 0,"modulo by zero");
return Integer(left.i % right.i);
}
const Integer
operator^(const Integer& left,
const Integer& right) {
return Integer(left.i ^ right.i);
}
const Integer
operator&(const Integer& left,
const Integer& right) {
return Integer(left.i & right.i);
}
const Integer
operator|(const Integer& left,
const Integer& right) {
return Integer(left.i | right.i);
}
const Integer
operator<<(const Integer& left,
const Integer& right) {
return Integer(left.i << right.i);
}
const Integer
operator>>(const Integer& left,
const Integer& right) {
return Integer(left.i >> right.i);
}
// Assignments modify & return lvalue:
Integer& operator+=(Integer& left,
const Integer& right) {
if(≤ft == &right) {/* self-assignment */}
left.i += right.i;
return left;
}
Integer& operator-=(Integer& left,
const Integer& right) {
if(≤ft == &right) {/* self-assignment */}
left.i -= right.i;
return left;
}
Integer& operator*=(Integer& left,
const Integer& right) {
if(≤ft == &right) {/* self-assignment */}
left.i *= right.i;
return left;
}
Integer& operator/=(Integer& left,
const Integer& right) {
require(right.i != 0,"divide by zero");
if(≤ft == &right) {/* self-assignment */}
left.i /= right.i;
return left;
}
Integer& operator%=(Integer& left,
const Integer& right) {
require(right.i != 0,"modulo by zero");
if(≤ft == &right) {/* self-assignment */}
left.i %= right.i;
return left;
}
Integer& operator^=(Integer& left,
const Integer& right) {
if(≤ft == &right) {/* self-assignment */}
left.i ^= right.i;
return left;
}
Integer& operator&=(Integer& left,
const Integer& right) {
if(≤ft == &right) {/* self-assignment */}
left.i &= right.i;
return left;
}
Integer& operator|=(Integer& left,
const Integer& right) {
if(≤ft == &right) {/* self-assignment */}
left.i |= right.i;
return left;
}
Integer& operator>>=(Integer& left,
const Integer& right) {
if(≤ft == &right) {/* self-assignment */}
left.i >>= right.i;
return left;
}
Integer& operator<<=(Integer& left,
const Integer& right) {
if(≤ft == &right) {/* self-assignment */}
left.i <<= right.i;
return left;
}
// Conditional operators return true/false:
int operator==(const Integer& left,
const Integer& right) {
return left.i == right.i;
}
int operator!=(const Integer& left,
const Integer& right) {
return left.i != right.i;
}
int operator<(const Integer& left,
const Integer& right) {
return left.i < right.i;
}
int operator>(const Integer& left,
const Integer& right) {
return left.i > right.i;
}
int operator<=(const Integer& left,
const Integer& right) {
return left.i <= right.i;
}
int operator>=(const Integer& left,
const Integer& right) {
return left.i >= right.i;
}
int operator&&(const Integer& left,
const Integer& right) {
return left.i && right.i;
}
int operator||(const Integer& left,
const Integer& right) {
return left.i || right.i;
} ///:~
//: C12:IntegerTest.cpp
// From Thinking in C++,2nd Edition
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
//{L} Integer
#include "Integer.h"
#include <fstream>
using namespace std;
ofstream out("IntegerTest.out");
void h(Integer& c1,Integer& c2) {
// A complex expression:
c1 += c1 * c2 + c2 % c1;
#define TRY(OP) \
out << "c1 = "; c1.print(out); \
out << ",c2 = "; c2.print(out); \
out << "; c1 " #OP " c2 produces "; \
(c1 OP c2).print(out); \
out << endl;
TRY(+) TRY(-) TRY(*) TRY(/)
TRY(%) TRY(^) TRY(&) TRY(|)
TRY(<<) TRY(>>) TRY(+=) TRY(-=)
TRY(*=) TRY(/=) TRY(%=) TRY(^=)
TRY(&=) TRY(|=) TRY(>>=) TRY(<<=)
// Conditionals:
#define TRYC(OP) \
out << "c1 = "; c1.print(out); \
out << ",c2 = "; c2.print(out); \
out << "; c1 " #OP " c2 produces "; \
out << (c1 OP c2); \
out << endl;
TRYC(<) TRYC(>) TRYC(==) TRYC(!=) TRYC(<=)
TRYC(>=) TRYC(&&) TRYC(||)
}
int main() {
cout << "friend functions" << endl;
Integer c1(47),c2(9);
h(c1,c2);
} ///:~
基本模型
終止模型
一種稱為"終止模型"(它是Java與C++所支持的模型).在這種模型中,將假設錯誤非常關鍵,將以致於程式無法返回到異常發生的地方繼續執行.一旦異常被拋出,就表明錯誤已無法挽回,也不能回來繼續執行.
恢復模型
另一種稱為"恢復模型".意思是異常處理程式的工作是修正錯誤,然後重新嘗試調動出問題的方法,並認為的二次能成功. 對於恢復模型,通常希望異常被處理之後能繼續執行程式.在這種情況下,拋出異常更像是對方法的調用--可以在Java里用這種方法進行配置,以得到類似恢復的行為.(也就是說,不是拋出異常,而是調用方法修正錯誤.)或者,把try塊放在while循環里,這樣就可以不斷的進入try塊,直到得到滿意的結果.
各有千秋
雖然恢復模型開始顯得很吸引人,並且人們使用的作業系統也支持恢復模型的異常處理,但程式設計師們最終還是轉向了使用類似"終止模型"的代碼.因為:處理程式必須關注異常拋出的地點,這勢必要包含依賴於拋出位置的非通用性代碼.這增加了代碼編寫和維護的困難,對於異常可能會從許多地方拋出的大型程式來說,更是如此. 下面我寫的一個簡單的例子 VC++6.0下通過
#include <iostream>
using namespace std;
class Error
{
public:
virtual void show()=0;
};
class DenoError:public Error
{
public:
void show() { cout<<"分母不可以為0!"<<endl; }
};
void main()
{
int a,b;
cin>>a>>b;
try {
DenoError e;
if(b==0) throw e;
int c=a/b;
cout<<c<<endl;
}
catch(DenoError & e)
{ e.show(); }
}
分類
支持運算符重載和定義新運算符的語言:
•PostgreSQL的SQL方言
•Ruby
•Haskell
支持運算符重載的語言:
•Ada
•C++
•C#
•D
•Perl
•Python
•Pico(某種程度上)
•Pascal(僅Free Pascal Dialect)
•FreeBASIC
•Visual Basic(需要 Visual Basic .NET 2008 或更高版本)
不支持運算符重載的語言:
•C
•Delphi
•Java
•Objective-C