目录
一、运算符重载的基本概念
我们知道C++预定义的运算符,只能用于基本数据类型的运算
如:整型,实型,字符型,逻辑型.......
运算符如:+、-、*、/、%、&、~、!、|、=、<<、>>、!=、.....
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之能作用于对象。
二、运算符重载的形式
1、运算符重载的实质是函数重载
2、可以重载为普通函数,也可以重载为成员函数
3、把含运算符重载的表达式转换成为对运算符函数的调用
4、把运算符的操作数转换成运算符函数的参数
5、运算符被多次重载时,根据实参的类型决定调用哪个运算符函数
运算符重载的形式
返回值类型 operator 运算符 (形参表)
{
..........
}
下面举一个重载的例子
定义一个复数对象重载运算符+ -
#include <iostream>
using namespace std;
class Complex {
public:
double real, imag;
Complex():real(0),imag(0) {}
Complex(double r, double i):real(r),imag(i){}
Complex operator-(const Complex& c);
};
//重载运算符+ (重载为普通函数)
Complex operator+ (const Complex& a, const Complex& b)
{
return Complex(a.real + b.real, a.imag + b.imag); //返回一个临时对象
}
//重载运算符- (重载为成员函数)
Complex Complex::operator-(const Complex& c)
{
return Complex(real - c.real, imag - c.imag); //返回一个临时对象
}
int main()
{
Complex a(9, 3), b(6, 2), c;
c = a + b;
//c = operator+(a, b);
cout << c.real << " " << c.imag << endl;//输出15 5
cout << (a - b).real << " " << (a - b).imag << endl;//输出3 1
//cout << a.operator-(b).real << " " << a.operator-(b).imag;
return 0;
}
重载为成员函数时,参数个数为运算符目数减一
重载为普通函数时,参数个数为运算符目数
运算符重载-如何决定作为成员函数还是非成员函数
对于运算符重载可通过成员函数和非成员函数实现,这二者的区别如下:
(1)成员函数运算符重载时,运算符的左值为调用对象,右值为参数对象,举个例子,a,b均是类A的对象,重载“+”实现a+b,可以将其认为是:a对象调用“+”函数,函数的参数为b对象;而在事实上,a对象和b对象都是这个“+”函数的参数,只不过a对象被隐式调用,由this指针所绑定。因此成员函数运算符重载的显式参数比实际运算参数少一个;而在非成员函数运算符重载中,必须将操作数全部显式添加在参数列表中,运算符左值为第一个参数,运算符右值为第二个参数。
(2)成员函数运算符重载时,运算符左值类型必须为所在类类型;而非成员函数运算符重载则不必。
这个时候我们就会有这样的疑问,如何决定运算符重载为成员函数还是非成员函数呢?
一般来说,对于双目运算符,应当将其重载为非成员函数(友元函数),而对于单目运算符,则应将其重载为成员函数。但这也不是绝对的,双目运算符中,“=”、“[]”、“->”和“()”是必须重载为成员函数的。而"<<"运算符由于其第一个运算符必须是ostream的,所以只能重载为友元函数
三、赋值运算符的重载
有时候希望赋值运算符两边的类型可以不匹配,比如把一个int类型的变量赋值给一个Complex对象,或把一个char * 类型的字符串赋值给一个字符串对象,此时就要重载赋值运算符“=”
赋值运算符“=”只能重载为成员函数
#include <iostream>
#include <cstring>
using namespace std;
class String {
private:
char* str;
public:
String() :str(new char[1]) { str[0] = 0; }
const char* c_str() { return str; };
String& operator = (const char* s);
~String() { delete[] str; }
};
String& String::operator=(const char* s)//重载“=”使得obj = "hello"
{
delete[] str;
str = new char[strlen(s) + 1];
strcpy(str, s);
return *this;
}
int main()
{
String s;
s = "Good Luck,";//s.operator=("Good Luck,");
cout << s.c_str() << endl; //输出Good Luck,
s = "Shenzhou 8!";//s.operator=("Shenzhou 8!");
cout << s.c_str() << endl; //输出 Shenzhou 8!
}
四、流插入运算符和流提取运算符的重载
<<运算符的重载
C++在输出内容时候 常常会是这样的方式
cout << 66 << "this";
那么这条语句为什么能成立呢?
// 实际上,cout 是在iostream头文件中定义的ostrem类的对象
//"<<"能用在cout 上是因为 ,在ostream类里面 "<<"进行了重载
cout << 66; //即 cout.operator<<(66);
cout << "this";//即 cout.operator("this");
假定我们要想把某个对象里的内容进行打印输出,那么我们可以重载 ostream 类的流插入 << 运算符
下面以 CStudent 类作为例子:
#include <iostream>
using namespace std;
class Cstudent {
private:
int age; //年龄
int sno; //学号
string sname; //姓名
public:
//构造函数
Cstudent(int age_ = 0, int sno_ = 0, string sname_ = ""):age(age_),sno(sno_),sname(sname_){}
//将该函数声明为友元函数 目的是使得函数可以访问CStudent类的私有成员变量
friend ostream& operator<<(ostream& o, const Cstudent& s);
};
ostream& operator<<(ostream& o, const Cstudent& s) {
o << s.age << " " << s.sno << " " << s.sname;
return o;
}
int main()
{
Cstudent stu1(18, 201304088, "张三");
Cstudent stu2(21, 201304066, "李四");
cout << stu1 << endl;
cout << stu2 << endl;
return 0;
}
输出结果:
18 201304088 张三
21 201304066 李四
需要注意的是:
ostream& operator<<(ostream& o, const Cstudent& s)
函数是全局的,所以函数的第一个参数必须要传入 ostream 的对象,并且 CStudent 类需要将此 函数声明成友元函数,使得函数可以访问 CStudent 类的私有成员变量
>>运算符的重载
还是以Cstudent类作为例子,假设想通过键盘的输入的内容,来初始化对象,则我们可以重载 istream 类的流提取 >> 运算符
#include <iostream>
using namespace std;
class Cstudent {
private:
int age; //年龄
int sno; //学号
string sname; //姓名
public:
//构造函数
Cstudent(int age_ = 0, int sno_ = 0, string sname_ = "") :age(age_), sno(sno_), sname(sname_) {}
//将该函数声明为友元函数 目的是使得函数可以访问CStudent类的私有成员变量
friend ostream& operator<<(ostream& o, const Cstudent& s);
friend istream& operator>>(istream& is, Cstudent& s);
};
//重载ostream对象的流插入<<运算符函数 使得打印输出Cstudent对象的信息
ostream& operator<<(ostream& o, const Cstudent& s) {
o << s.age << "," << s.sno << "," << s.sname;
return o;
}
//重载istream对象的流提取>>运算符函数 使得初始化Cstudent对象的内容
istream& operator>>(istream& is, Cstudent& s) {
string inputStr;
is >> inputStr;
int pos = inputStr.find(",", 0); //查找首次出现" "的位置
string tempStr = inputStr.substr(0, pos); //截取从0-pos位置的字符串
s.age = atoi(tempStr.c_str()); //char* 类型转int类型
int pos2 = inputStr.find(",", pos + 1); //从pos+ 1开始 查找第二次出现" "的位置
tempStr = inputStr.substr(pos + 1, pos2 - pos - 1); //截取出sno
s.sno = atoi(tempStr.c_str()); //char* 类型转int类型
tempStr = inputStr.substr(pos2 + 1, inputStr.length() - pos2 - 1);//截取出sname
s.sname = tempStr;
return is;
}
int main()
{
Cstudent stu;
cin >> stu;//输入18,201304088,张三
cout << stu;//输出18,201304088,张三
return 0;
}
下面来写一个Complex类 对流插入运算符和流提取运算符重载
这时我们便可以熟练的写出 二者的运算符重载
friend ostream& operator<<(ostream& o, const Complex& c);
friend istream& operator>>(istream& is, Complex& c);
完整程序
#include <iostream>
using namespace std;
class Complex {
private:
double real, imag;
public:
Complex() :real(0), imag(0) {}//无参构造函数
Complex(double r, double i) :real(r), imag(i) {}// 用参数初始化表对其数据成员初始化
friend ostream& operator<<(ostream& o, const Complex& c);
friend istream& operator>>(istream& is, Complex& c);
};
ostream& operator<<(ostream& o, const Complex& c)
{
o << c.real << "+" << c.imag << "i";
return o;
}
istream& operator>>(istream& is, Complex& c)
{
string s;
is >> s;
int pos = s.find("+", 0);
string temp = s.substr(0, pos);
c.real = atof(temp.c_str());
temp = s.substr(pos + 1, s.length() - pos - 1);
c.imag = atof(temp.c_str());
return is;
}
int main()
{
Complex c;
cin >> c;
cout << c;
return 0;
}
五、类型转换运算符,自增自减运算符的重载
重载类型转换运算符
可以把对象转换成为想要转换成某一种其他的类型东西
我们依旧以Complex类为例 把Complex定义的一个对象 转换为double型
完整程序
#include <iostream>
using namespace std;
class Complex {
private:
double real, imag;
public:
Complex() :real(0), imag(0) {}//无参构造函数
Complex(double r, double i) :real(r), imag(i) {}// 用参数初始化表对其数据成员初始化
operator double() { return real; }
//重载强制类型转换运算符double
};
int main()
{
Complex c(1.2, 3.4);
cout << double(c) << endl;//输出1.2
double n = 2 + c;// double n = 2 + c.operator double ()
cout << n;//输出3.2
return 0;
}
类型转换运算符重载的时候返回值类型是不写的 因为返回的就是要重载的类型本身
operator double() { return real; }//重载强制类型转换运算符double
重载自增,自减运算符
自增、自减运算符有前置/后置之分 为了区分所重载的运算符是前置运算符还是后置运算符,C++规定:
前置运算符作为一元运算符重载
重载为成员函数:
T& operator++();
T& operator--();
重载为全局函数:
T& operator++(T2);
T& operator--(T2);
后置运算符作为二元运算符重载,多写一个没有用的参数
重载为成员函数:
T& operator++(int);
T& operator--(int);
重载为全局函数:
T& operator++(T2, int );
T& operator--(T2, int );
我们以CDemo类为例
完整程序如下
#include <iostream>
using namespace std;
class CDemo {
private :
int x;
public:
CDemo(int x_ = 0) { x = x_; }
CDemo& operator++(); //前置形式
CDemo operator++( int ); //后置形式
operator int() { return x; }
friend CDemo& operator--(CDemo&);
friend CDemo operator--(CDemo& , int );
};
CDemo& CDemo::operator++() {
//前置 ++ 返回值是一个引用
++x;
return *this;
//++s 即为s.operator++()
}
CDemo CDemo::operator++( int k ) {
//后置 ++ 返回值是一个对象
CDemo temp(*this);
x++;
return temp;
//s++ 即为s.operator++(0)
}
CDemo& operator--(CDemo& d)
{
//前置 ++ 返回值是一个引用
d.x--;
return d;
//--s即为:operator--(s)
}
CDemo operator--(CDemo& d, int )
{
//后置 ++ 返回值是一个对象
CDemo temp(d);
d.x--;
return temp;
//s--即为:operator--(s, 0);
}
//前置的++ -- 没有生成对象 返回值是引用
//后置的++ -- 会生成对象 引发构造函数的调用 返回一个对象又会引发构造函数的调用
//所以用前置++ 会比后置++ 效率更高
int main()
{
CDemo d(5);
cout << (d++ ) << ","; //d.operator++(0); 输出5
cout << d << ","; //输出6
cout << (++d) << ","; //d.operator++(); 输出7
cout << d << endl; //输出7
cout << (d--) << ","; //d.operator-(d, 0); 输出7
cout << d << ","; //输出6
cout << (--d) << ","; //d.operator-(d)输出5
cout << d << endl; //输出5
return 0;
}
注意:
前置的++ -- 没有生成对象 返回值是引用
后置的++ -- 会生成对象 引发构造函数的调用 返回一个对象又会引发构造函数的调用
所以用前置 会比后置 效率更高
附:运算符重载的注意事项
1、重载后运算符的含义应该符合原有用法习惯
例如重载 + 运算符,完成的功能就应该类似于做加法,在重载的 + 运算符中做减法是不合适的。
2、运算符重载不改变运算符的优先级。重载运算符时,应该尽量保留运算符原本的特性。
3、以下运算符不能被重载:. 、 .* 、 :: 、? : 、 sizeof 。
重载运算符 () 、 [] 、 -> 、或者赋值运算符 = 时,只能将它们重载为成员函数,不能重载为全局函数。
4、必要时需要重载赋值运算符=,以避免两个对象内部的指针指向同一片存储空间。