与函数重载相似,运算符也存在重载问题。C++为了解决一些实际问题,允许重载现有的大多数运算符,即允许给已有的运算符赋予新的含义,从而提高C++的可扩展性,针对同样的操作,使用重载运算符比函数的显示调用更能提高程序的可读性。
C++对运算符重载进行了一下规定限制:
C++允许重载大多数已经定义的运算符。运算符重载函数既可以是类的成员函数,也可以是类的友元函数。
在类的内部定义成员函数重载运算符的格式为:class 类名{返回类型 operator 运算符(形参表)};
在类的外部定义成员函数重载运算符的格式为:返回类型 类名::operator 运算符(形参表){//函数体}
说明:返回类型是指运算符重载函数的运算结果类型;operator是定义运算符重载的关键字;运算符是要重载的运算符名称;形参表中给出了重载运算符所需要的参数和类型。
用成员函数重载运算符时,若运算符时单目的,则参数表为空,因为这是操作数访问该重载运算符对象本身的数据,该对象有this
指针指向,所以参数表为空。
单目运算符的调用有两种,即显式调用和隐式调用。
显示调用:对象名.operator 运算符()
隐式调用:重载的运算符 对象名
例如:重载“-”(负号)运算符。
#include
using namespace std;
class Point
{
public:
Point(int i = 0, int j = 0) :x(i), y(j) {};
Point operator -() {
x = -x;
y = -y;
return *this;
}
void Print();
private:
int x, y;
};
int main() {
Point ob(1, 2);
cout << "ob:";
ob.Print();
cout << endl;
cout << "-ob:";
ob = ob.operator-(); //显式调用
//ob = -ob; //隐式调用
ob.Print();
cout << endl;
return 0;
}
void Point::Print()
{
cout << x << " " << y << endl;
}
重载双目运算符时,左边操作数是访问改重载运算符对象本身的数据,有this
指针指向,右边操作数通过重载运算符的成员函数的参数指出,所以,成员函数只有一个函数。
双目运算符的调用有两种方式,即显式调用和隐式调用。
显式调用:对象名.operator 运算符(参数)
隐式调用:对象名 重载的运算符 参数
例如:重载“+”运算符,以实现连个字符串相加。
#include
#include
using namespace std;
const int MAX = 20;
class String
{
public:
String(const char *instr = NULL);
String operator+(const char *astr);
void ShowString();
private:
char buffer[MAX];
int length;
};
int main()
{
String title("C/C++");//定义对象时,调用构造函数给成员buffer赋值
title = title + " Program";//隐式调用
title = title.operator+("!");//显式调用
title.ShowString();
return 0;
}
String::String(const char* instr)
{
if (instr != NULL)
{
strcpy_s(buffer, strlen(instr) + 1, instr);
length = strlen(buffer);
}
}
String String::operator+(const char* astr)
{
String temp;
int templen;
templen = strlen(buffer) + strlen(astr);
if (templen + 1 > MAX)
{
cout << "String is too large!" << endl;
strcpy_s(temp.buffer, strlen(buffer) + 1, buffer);//相当于strcpy_s(temp.buffer, strlen(this->buffer) + 1, this->buffer);
return temp;
}
length = templen;
strcpy_s(temp.buffer, strlen(buffer) + 1, buffer);
strcat_s(temp.buffer, templen + 1, astr);
return temp;
}
void String::ShowString()
{
cout << buffer << endl;
}
例如:用成员函数重载运算符实现复数的加、减运算。
#include
using namespace std;
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0) :real(r), imag(i) {};
void Print();
Complex operator+(Complex c);
Complex operator-(Complex c);
private:
double real;
double imag;
};
int main()
{
Complex com1(1.1, 2.2), com2(3.3, 4.4), total;
total = com1 + com2;
total.Print();
total = com1 - com2;
total.Print();
return 0;
}
void Complex::Print()
{
cout << real;
if (imag > 0) cout << "+";
if (imag != 0) cout << imag << "i" << endl;
}
Complex Complex::operator+(Complex c)
{
Complex temp;
temp.real = real + c.real;
temp.imag = imag + c.imag;
return temp;
}
Complex Complex::operator-(Complex c)
{
Complex temp;
temp.real = real - c.real;
temp.imag = imag - c.imag;
return temp;
}
在C++中经常使用前缀和后缀的++、- -运算符,若将++(或- -)运算符置于变量前,则C++在引用变量前先加1(或先减1);若将++(或- -)运算符置于变量后,则C++在引用变量后使变量加1(或使变量减1)。使用格式:
类名 operator ++()
//前缀方式
类名 operator ++(int)
//后缀方式
类名 operator --()
//前缀方式
类名 operator --(int)
//后缀方式
例如:为类Point重载++运算符
#include
using namespace std;
class Point
{
public:
Point(int i = 0, int j = 0) :x(i), y(j) {};
Point operator++();
Point operator++(int);
void Print();
private:
int x, y;
};
int main()
{
Point ob1(1, 2), ob;
ob1.Print();
ob = ++ob1;
ob.Print();
ob = ob1++;
ob.Print();
ob1.Print();
return 0;
}
Point Point::operator++()
{
++x;
++y;
return *this;
}
Point Point::operator++(int)
{
Point temp = *this;//确保原对象值
x++;
y++;
return temp;
}
void Point::Print()
{
cout << "(" << x << "," << y << ")" << endl;
}
在C++中,对于任何一个类,若没有用户自定义的赋值运算符函数,系统就会自动为其生成一个默认的赋值运算符函数,以完成数据成员之间的逐位复制。通常,默认的赋值运算符函数可以完成赋值任务,但在某些特殊情况下,若类中有指针类形式,就不能进行直接的相互赋值。
例如:指针悬挂问题。
#include
#include
#include
using namespace std;
class Student
{
public:
Student(const char *na, int sco);
Student& operator=(const Student&);
~Student();
void Print();
private:
char* name;
int score;
};
Student::Student(const char* na, int sco)
{
name = new char[strlen(na) + 1];
strcpy_s(name, strlen(na) + 1, na);
score = sco;
}
Student& Student::operator=(const Student& p)//定义赋值运算符重载函数
{
if (this == &p) return *this;//避免p=p的赋值
delete[]name;//释放空间
name = new char[strlen(p.name) + 1];//分配新空间
strcpy_s(name, strlen(p.name) + 1, p.name);//字符串拷贝
score = p.score;
return *this;
}
Student::~Student()
{
delete[]name;
}
void Student::Print()
{
cout << name << setw(6) << score << endl;
}
int main()
{
Student s1("李明", 60);
Student s2("李华", 80);
s2.Print();
s2 = s1;
s2.Print();
return 0;
}
注意:1.赋值运算符不能重载为友元函数,只能重载为一个非静态成员函数;2.赋值运算符重载函数不能被继承。
当程序变得复杂时,有时需要必须重载数组下标运算符[]
。C++重载数组运算符时,认为其是双目运算符,因此重载数组下标运算符时,运算符成员函数的格式为:返回类型 类名::operator[](形参){//函数体}
例如:重载下标运算符应用,用一维数组实现一个三维向量类。
#include
#include
#include
using namespace std;
class Vector
{
public:
Vector(int a1,int a2,int a3);
int& operator[](int b);
private:
int v[3];
};
Vector::Vector(int a1, int a2, int a3)
{
v[0] = a1;
v[1] = a2;
v[2] = a3;
}
int& Vector::operator[](int b)
{
if (b < 0 || b >= 3)
{
cout << "Bad subscript!" << endl;
exit(1);
}
return v[b];
}
int main()
{
Vector v(1, 3, 5);
cout << "修改前:";
for (int i = 0; i < 3; i++)
{
cout << v[i] << setw(4);
}
cout << endl;
cout << "修改后:";
for (int i = 0; i < 3; i++)
{
v[i] = 2 * i;
}
for (int i = 0; i < 3; i++)
{
cout << v[i] << setw(4);
}
cout << endl;
return 0;
}
注意:1.重载下标运算符的优点是,可以增加C++中数组检索的安全性;2.重载下标运算符时,返回一个int的引用。可使重载的[]
运算符用在赋值语句的左边,因而在main()
中,v[i]
可以出现在赋值运算符的任何一边,使编制程序更灵活了。
重载函数调用运算符()
时,并不是创建新的调用函数的方法,而是创建了可传递任意数目参数的运算符函数。通常重载函数调用运算符时,定义了传递给重载函数的参数。重载函数调用运算符成员函数的格式为:返回类型 类名::operator()(形参){//函数体}
例如:重载函数调用运算符
#include
using namespace std;
class Matrix
{
public:
Matrix(int, int);
int& operator()(int, int);
private:
int* m;
int row, col;
};
Matrix::Matrix(int r, int c)
{
row = r;
col = c;
m = new int[row * col];
for (int i = 0; i < row * col; i++)
*(m + i) = i;
}
int& Matrix::operator()(int r, int c)
{
return (*(m + r * col + c));
}
int main()
{
Matrix m(10, 10);
cout << m(3, 4) << endl;
m(3, 4) = 35;
cout << m(3, 4) << endl;
return 0;
}
说明:程序中,m(10,10)
相当于一个10
行10
列的二维矩阵。在执行语句cout << (3,4) << endl;
时,编译器将m(3,4)
解释为m.operator()(3,4)
,从而调用运算符重载函数operator()(int r,int c)
,然后返回矩阵第3
行第4
列的元素值;语句m(3,4)=35
修改矩阵第3
行第4
列的元素值,之所以能够这样写,是因为operator()
是一个返回引用类型int&
。
在大多数情况下,用友元函数或成员函数重载运算符在功能上没有差别。用友元函数重载运算符时,因为友元函数没有this
指针,所以若运算符是单目的,则参数表中有一个操作数;若运算符是双目的,则参数表中有两个操作数。友元函数重载运算符的格式为:
friend<函数类型>operator<重载的运算符>(<形参>){}
//单目运算符重载
friend<函数类型>operator<重载的运算符>(<形参1, 形参2>){}
//双目运算符重载
例如:用友元函数重载运算符实现复数的加、减运算。
#include
using namespace std;
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0) :real(r), imag(i) {};
void Print();
friend Complex operator+(Complex a, Complex b);
friend Complex operator-(Complex a, Complex b);
private:
double real;
double imag;
};
int main()
{
Complex com1(1.1, 2.2), com2(3.3, 4.4), total;
total = com1 + com2;
total.Print();
total = com1 - com2;
total.Print();
return 0;
}
void Complex::Print()
{
cout << real;
if (imag > 0) cout << "+";
if (imag != 0) cout << imag << "i" << endl;
}
Complex operator+(Complex a, Complex b)
{
Complex temp;
temp.real = a.real + b.real;
temp.imag = a.imag + b.imag;
return temp;
}
Complex operator-(Complex a, Complex b)
{
Complex temp;
temp.real = a.real - b.real;
temp.imag = a.imag - b.imag;
return temp;
}
在进行运算符重载时,既可以用成员函数重载,也可以用友元函数重载。下面是它们的比较。
+
"运算符:Complex Complex::operator+(int c)
{
Complex temp;
temp.real = real + c;
temp.imag = imag + c;
return temp;
}
若类Complex
的对象com
要做赋值运算和加法运算,则下面的语句是正确的:com=com+10;
这是因为对象com
是“+
”运算符的左操作数,在调用重载“+
”运算符的函数时,this
指针指向com
。因此语句temp.real=real+a
相当于remp.real=this->real+a;
,而下面的语句就不正确了:com=10+com;
这是因为左操作数是一个整数,而整数是一个内部数据类型,不能产生对成员运算符函数的调用。+
”运算符,从而消除由于“+
”运算符的左操作数为内部数据类型带来的问题。=
” 、函数调用“()
”、下标“[]
”、指针成员引用“->
”等运算符,必须用成员函数重载,否则将导致编译错误。<<
”和流提取“>>
”运算符,必须用友元函数重载。-
”、指针“*
”、取地址“&
”),自增“++
”、自减“--
”等运算符,建议选择成员函数重载。+=、-=、/=、*=、&=、!=、~=、%=、>>=、<<=
,建议用成员函数重载。C++规定,当不同类型的数据进行运算时,需先将数据转换成同一类型,然后才可以进行运算。数据的类型转换可以通过两种转换形式完成:一种是隐式类型转换;另一种是显式类型转换。
V=E
时,若V
和E
的类型不一致,则将E
先转换为V
的类型后再赋值。double ->float->long->int->short/char
。用构造函数实现类型转换,类内至少定义一个只带一个参数(没有其他参数,或其他参数都是默认值)的构造函数。这样,当进行类型转换时,系统会自动调用该构造函数,创建该类的一个临时对象,该对象由被转换的值初始化,从而让实现类型转换。
例如,将一个char
型数据转换为String
类型的数据
#include
#include
using namespace std;
class String
{
public:
String(const char* ch = NULL);
~String();
void Print();
private:
char* str;
int length;
};
String::String(const char* ch)
{
if (ch != NULL)
{
length = strlen(ch);
str = new char[strlen(ch) + 1];
strcpy_s(str, strlen(ch) + 1, ch);
}
}
String::~String()
{
delete[]str;
}
void String::Print()
{
cout << str << endl;
}
int main()
{
String s = "C/C++ program!";
s.Print();
return 0;
}
说明:语句“String(const char* ch = NULL);
”声明了一个转换构造函数。该构造函数可以用来进行类型转换。主函数中,再执行语句“String s = "C/C++ program!";
”时,编译器首先调用构造函数建立包含“C/C++ program!
”的一个临时String
类的对象(通过转换构造函数将一个char*
字符串转换为String
的对象),然后再将该临时String
类的对象赋给对象s
。使用这种转换构造函数意味着不用再为字符串赋给String
类对象提供重载的赋值运算符。任何只带一个参数(或其他参数都带有默认值)的构造函数都可以认为是一种转换构造函数。
用构造函数可以实现类型转换,但是其所完成的类型转换功能具有一定的局限性。由于无法为系统预定义类型定义构造函数,因此,不能利用构造函数把自定义类型的数据转换为系统预定义类型的数据,只能实现系统预定义类型向自定义类型的类型转换。
为了解决上述问题,C++允许用户在类中定义成员函数,从而得到要转换的类型。
运算符转换函数定义格式为:
class 类名
{
operator目的类型()
{
return 目的类型的数据;
}
};
其中,目的类型为要转换成的类型,它既可以是用户自定义的类型,也可以是系统的预定义类型。
在使用运算符转换函数时,需要注意以下三个问题:
return 目的类型的数据;
”这样的语句,即必须返回目的类型数据作为函数的返回值。#include
using namespace std;
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0) :real(r), imag(i) {};
operator float();
operator int();
void Print();
private:
double real;
double imag;
};
int main()
{
Complex com1(2.2, 4.4);
cout << "com1=";
com1.Print();
cout << "Type changed to float..." << endl;
cout << "float(com1)*0.5=";
cout << float(com1) * 0.5 << endl;
Complex com2(4.7, 6);
cout << "com2=";
com2.Print();
cout << "Type changed to int..." << endl;
cout << "int(com2)*2=";
cout << int(com2) * 2 << endl;
return 0;
}
Complex::operator float()
{
return real;
}
Complex::operator int()
{
return int(real);
}
void Complex::Print()
{
cout << real;
if (imag > 0) cout << "+";
if (imag != 0) cout << imag << "i" << endl;
}