简介
C++预定义的运算符,只能用于基本数据结构类型的运算:整型、实型、字符型、逻辑型。
定义
运算符重载,就是对已有的运算符(C++中预定义的运算符)赋予多重含义,使同一运算符作用于不同类型的数据时导致不同的行为
目的
扩展C++中提供的运算符的适用范围,使之能作用于对象
形式
运算符重载的实质是函数重载
可以重载为普通函数,也可以重载为成员函数
把含运算符的表达式转换成对运算符函数的调用
吧运算符的操作数转换为函数的参数
运算符被多次重载时,根据实参的类型决定调用哪个运算符函数
返回值类型 operator 运算符(形参表) {
}
重载为普通函数时,参数个数为运算符目数减一
重载为成员函数时,参数个数为运算符目数
记住是目数不是数目啦
class Complex {
public:
double real, imag;
Complex(double r = 0.0, double i = 0.0):real(r), imag(i) {}
Complex operator-(const Complex &c);
};
Complex operator+(const Complex &a, const Complex &b) {
return Complex(a.real+b.real, b.imag);
}
Complex Complex::operator-(const Complex &c) {
return Complex(real-c.real, imag-c.imag);
}
int main() {
Complex a(4, 4), b(1, 1), c;
c = a+b; // 等价于c=operator+(a,b)
cout << c.real << "," << c.imag << endl;
cout << (a-b).real << "," << (a-b).imag << endl;
// a-b等价于a.operator-(b)
return 0;
}
赋值运算符重载(‘=’)
重载为成员函数
赋值运算符只能重载为成员函数
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) {
delete [] str;
str = new char[strlen(s)+1];
strcpy(str, s);
return *this;
}
~String() { delete[] str; }
};
int main() {
String s1, s2;
s1 = "s1";
s2 = "s2";
s1 = s2;
cout << s1.c_str() << "," << s2.c_str() << endl;
return 0;
}
如果不定义自己的赋值运算符,那么s1=s2实际上导致s1.str和s2.str指向同一片内存空间
如果s1对象消亡,析构函数将释放s1.str指向的空间,则s2消亡时还要释放一次
如果执行s1=“s_”,会导致s2.str指向的地方被delete
因此要在类String里添加成员函数
String& operator=(const String &s) {
delete []str;
str = new char[strlen(s.str)+1];
strcpy(str, s.str);
return *this;
}
考虑 s=s; 这条语句
String &operator=(const String &s) {
if (this == &s) return *this;
delete []str;
str = new char[strlen(s.str)+1];
strcpy(str, s.str);
return *this;
}
对运算符进行重载的时候,好的风格是应该尽量保留运算符原本的性质
返回值类型
考虑:
a = b = c; ——> a.operator=(b.operator=( c ));
(a = b) = c; ——> (a.operator=(b)).operator=( c )
but!!! 考虑String类的复制构造函数,同样会面临和 = 同样的问题,用同样的方法处理
String (String &s) {
str = new char[strlen(s.str)+1];
strcpy(str, s.str);
}
运算符重载为友元函数
一般情况下,将运算符重载为类的成员函数,是较好的选择
有时,重载为成员函数不能满足使用要求;重载为普通函数,不能访问类的私有成员——>就需要将运算符重载为友元
class Complex {
double real, imag;
public:
Complex(double r, double i):real(r), imag(i) {}
Complex operator+(double r){
return Complex(real+r, imag);
}
};
经上述重载后
Complex c;
c = c+5; // 有定义,相当于c=c.operator+(5)
c = 5+c; // 编译出错
为了使上述表达式能成立,需要将+重载为普通函数
改为:
Complex operator+(double r, const Complex &c) {
return Complex(c.real+r, c.imag);
}
就能解释嘚通5+c啦
但是普通函数又不能访问私有成员,需要将运算符重载为友元
class Complex {
double real, imag;
public:
Complex(double r, double i):real(r), imag(i) {}
Complex operator+(double r){
return Complex(real+r, imag);
}
friend Complex operator+(double r, const Complex &c) {
return Complex(c.real+r, c.imag);
}
};
实例
可变长整型数组
#include <iostream>
#include <cstring>
using namespace std;
class CArray {
int size;
int *ptr;
public:
CArray(int len = 0); // 参数为数组元素个数
CArray(CArray &a);
~CArray();
void push_back(int v);
CArray& operator=(const CArray &a); // 对象间赋值
int length() { return size; }
// 用以支持根据下标访问数组元素个数,如n=a[1];a[2]=5;
int& operator[](int i) { return ptr[i]; }
};
int main() {
CArray a;
// 要用动态分配的内存来存放数组元素,需要一个指针成员变量
for (int i = 0; i < 5; ++ i) a.push_back(i);
CArray a2, a3;
a2 = a; // 要重载'=',"[]"
for (int i = 0; i < a.length(); ++ i) cout << a2[i] << " ";
cout << endl;
a2 = a3; // a2,a3都是空的,a2.length()返回0
for (int i = 0; i < a2.length(); ++ i) cout << a2[i] << " ";
a[3] = 100;
CArray a4(a); // 要自己写复制构造函数
for (int i = 0; i < a4.length(); ++ i) cout << a4[i] << " ";
return 0;
}
CArray::CArray(int len):size(len) {
ptr = len == 0 ? nullptr : new int[len];
}
CArray::CArray(CArray &a) {
if (a.ptr == nullptr) {
ptr = nullptr;
size = 0;
return ;
}
ptr = new int[a.size];
// 记住要指向不同的内存空间
memcpy(ptr, a.ptr, sizeof(int)*a.size);
size = a.size;
}
CArray::~CArray() {
if (ptr) delete [] ptr;
}
// 是赋值号左边对象存放的数组大小内容都和右边对象一样
CArray& CArray::operator=(const CArray &a) {
// 防止a=a这样的赋值出错
if (ptr == a.ptr) return *this;
// 如果a里面的数组是空的
if (a.ptr == nullptr) {
if (ptr) delete [] ptr;
ptr = nullptr;
size = 0;
return *this;
}
// 如果原有空间足够大,就不用分配新的空间
if (size < a.size) {
if (ptr) delete []ptr;
ptr = new int[a.size];
}
memcpy(ptr, a.ptr, sizeof(int)*a.size);
size = a.size;
return *this;
}
// 在尾部添加数组
void CArray::push_back(int v) {
if (ptr) {
int *tmp = new int[size+1];
memcpy(tmp, ptr, sizeof(int)*size);
delete []ptr;
ptr = tmp;
}
else ptr = new int[1];
ptr[size++] = v;
}
流插入运算符重载
cout/cin是在iotream中定义的:
cout是ostream的对象
cin是istream的对象
<</>> 这俩运算符能用在 cout/cin 上是因为在iostream里对两个运算符进行了重载
流插入流提取运算符本质上是左移右移运算符,只不过是被重载了
流插入运算符的重载
ostream& ostream::operator<<(int n) {
...
return *this;
}
ostream& ostream::operator<<(const char *s) {
...
return *this;
}
因为istream和ostream都已经写好了,所以不能>>和<<重载成成员函数,只能重载成全局函数,要声明为友元函数
#include <bits/stdc++.h>
using namespace std;
class Complex {
double real, imag;
public:
Complex(double r = 0, double i = 0):real(r), imag(i) {}
friend ostream& operator<<(ostream& os, const Complex &c);
friend istream& operator>>(istream& is, Complex &c);
};
ostream& operator<<(ostream& os, const Complex &c) {
os << c.real << "+" << c.imag << "i";
return os;
}
istream& operator>>(istream& is, Complex &c) {
string s;
is >> s;
int pos = s.find("+", 0);
string tmp = s.substr(0, pos);
c.real = atof(tmp.c_str());
tmp = s.substr(pos+1, s.length()-pos-2);
c.imag = atof(tmp.c_str());
return is;
}
int main() {
Complex c;
int n;
cin >> c >> n;
cout << c << ' ' << n;
return 0;
}
类型转换运算符
类型强制转换运算符被重载时不能写返回值类型,实际上其返回值类型就是该类型强制转换运算符代表的类型
重载的类型转换运算符可以显示的转换也可以自动转换,如:
#include <bits/stdc++.h>
using namespace std;
class Complex {
double real, imag;
public:
Complex(double r = 0, double i = 0):real(r), imag(i) { }
operator double() { return real; }
};
int main() {
Complex c(1.2, 3.4);
cout << (double)c << endl;
double n = c;
cout << n;
return 0;
}
上述两行是输出一样嗒
自增,自减运算符的重载
由于自增自减有前置后置之分,为了区分所重载的是前置运算符还是后置运算符,C++规定:
前置运算符作为一元运算符重载
重载为成员函数:
T & operator++();
T & operator–();
重载为全局函数:
T1 & operator++(T2);
T1 & operator–(T2);
后置运算符作为二元函数重载,多写一个没用的参数
重载为成员函数:
T operator++(int);
T operator–(int);
重载为全局函数:
T1 operator++(T2, int);
T1 operator–(T2, int);
在C++里面后置运算符并没有返回对操作数的引用
前置++和前置–的效率优于后置(整型变量没啥差别)
但是在没有后置运算符重载而有前置重载的情况下:在VS下,obj++也调用前置重载,而dev则令obj++编译出错
#include <bits/stdc++.h>
using namespace std;
class CDemo {
private:
int n;
public:
CDemo(int i = 0):n(i) {}
CDemo& operator++(){ // 前置++
n ++;
return *this;
} // ++tmp即为tmp.operator++();
CDemo operator++(int k) { // 后置++
CDemo tmp(*this);
n ++;
return tmp; // 返回修改前的对象
} // tmp++即为tmp.operator++(0);
// int作为一个类型强制转换运算符被重载
operator int () { return n; }
friend CDemo& operator--(CDemo& d) { // 前置--
d.n--;
return d;
} // --tmp即为operator--(tmp);
friend CDemo operator--(CDemo& d, int) { // 后置--
CDemo tmp(d);
d.n--;
return tmp;
} // tmp--即为operator--(tmp, 0);
};
int main() {
CDemo d(5);
cout << (d++) << ",";
cout << d << ",";
cout << (++d) << ",";
cout << d << ",";
cout << endl;
cout << (d--) << ",";
cout << d << ",";
cout << (--d) << ",";
cout << d;
return 0;
}
注意事项
1)C++不允许定义新的运算符;
2)重载后运算符的含义应该符合日常习惯;
3)运算符重载不改变运算符的优先级;
4)以下运算符不能被重载:
. | .* | :: | ?: | sizeof |
---|
5)重载运算符 () / [] / -> / = 时,运算符重载函数必须声明为类的成员函数
new和delete能被重载||ヽ( ̄▽ ̄)ノミ|Ю