运算符重载的基本概念
与函数重载相似,运算符也存在重载问题。C++为了解决一些实际问题,允许重载现有的大多数运算符,即允许给已有的运算符赋予新的含义,从而提高C++的可扩展性,针对同样的操作,使用重载运算符比函数的显示调用更能提高程序的可读性。
C++对运算符重载进行了一下规定限制:
- 只能重载C++中原先已定义的运算符。不能自己“创造”新的运算符进行重载。
- 并不是所有的运算符都可以进行重载。不能进行重载的运算符有“·”“.*”“::”“?:”。
- 不能改变运算符原有的优先级和结合性。C++已预先规定了每个运算符的优先级和结合性,已决定运算次序。不能改变其运算次序,若确实需要改变,只能采用加“()”的方法。
- 不能改变运算符对预定义类型数据的操作方式,但是可以根据实际需要,对原有运算符进行是当地改造与扩充。
- 运算符重载有两种方式,即重载为类的成员函数和重载为类的友元函数。
成员函数重载运算符
C++允许重载大多数已经定义的运算符。运算符重载函数既可以是类的成员函数,也可以是类的友元函数。
在类的内部定义成员函数重载运算符的格式为:class 类名{返回类型 operator 运算符(形参表)};
在类的外部定义成员函数重载运算符的格式为:返回类型 类名::operator 运算符(形参表){//函数体}
说明:返回类型是指运算符重载函数的运算结果类型;operator是定义运算符重载的关键字;运算符是要重载的运算符名称;形参表中给出了重载运算符所需要的参数和类型。
重载单目运算符
用成员函数重载运算符时,若运算符时单目的,则参数表为空,因为这是操作数访问该重载运算符对象本身的数据,该对象有this
指针指向,所以参数表为空。
单目运算符的调用有两种,即显式调用和隐式调用。
显示调用:对象名.operator 运算符()
隐式调用:重载的运算符 对象名
例如:重载“-”(负号)运算符。
#include <iostream>
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 <iostream>
#include <string.h>
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 <iostream>
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 <iostream>
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 <iostream>
#include <string.h>
#include <iomanip>
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 <iostream>
#include <string.h>
#include <iomanip>
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 <iostream>
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 <iostream>
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++中的不部分运算符既可以用成员函数重载,也可以用友元函数重载。在选择时主要取决于实际情况和使用者的习惯。运算符重载规则如下:
1.对于双目运算符,用友元函数重载比用成员函数重载更便于使用。若一个运算符的操作需要修改类对象的状态,建议使用成员函数重载;若运算符所需的操作数(尤其是第一个操作数)虚妄有隐式类型转换,则运算符必须用友元函数重载。
2.对于赋值“=
” 、函数调用“()
”、下标“[]
”、指针成员引用“->
”等运算符,必须用成员函数重载,否则将导致编译错误。
3.对于流插入“<<
”和流提取“>>
”运算符,必须用友元函数重载。
4.对于单目运算符(如取负“-
”、指针“*
”、取地址“&
”),自增“++
”、自减“--
”等运算符,建议选择成员函数重载。
5.对于复合赋值运算符+=、-=、/=、*=、&=、!=、~=、%=、>>=、<<=
,建议用成员函数重载。
6.对于算术运算符、关系运算符、逻辑运算符、位运算符等,建议用友元函数重载。
类型转换
系统预定义类型之间的转换
C++规定,当不同类型的数据进行运算时,需先将数据转换成同一类型,然后才可以进行运算。数据的类型转换可以通过两种转换形式完成:一种是隐式类型转换;另一种是显式类型转换。
- 隐式类型转换
当执行赋值表达式V=E
时,若V
和E
的类型不一致,则将E
先转换为V
的类型后再赋值。
与C语言一样,C++中规定数据类型级别从高到低的次序是:double ->float->long->int->short/char
。
当两个操作数类型不一致时,运算之前将级别低的自动转换为级别高的,然后再进行运算。 - 显式类型转换
显示类型转换有以下两种方式。1. 强制转换法:(类型名)表达式;2.函数法:类型名(表达式)。
以上的种种是一般数据类型之间的转换,那该如何实现用户自定义类型与其他数据类型的转换呢?通常采用两种方法:用构造函数实现类型转换和用运算符转换函数实现类型转换。
用构造函数实现类型转换
用构造函数实现类型转换,类内至少定义一个只带一个参数(没有其他参数,或其他参数都是默认值)的构造函数。这样,当进行类型转换时,系统会自动调用该构造函数,创建该类的一个临时对象,该对象由被转换的值初始化,从而让实现类型转换。
例如,将一个char
型数据转换为String
类型的数据
#include <iostream>
#include <string.h>
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 <iostream>
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;
}