C++学习笔记 运算符的重载
目录
运算符重载
运算符重载的基本概念
运算符重载的需求
- C++预定义的运算符,只能用于基本的数据类型的运算:整形、实型、字符型、逻辑型...
- 在数学上,两个负数可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的
- 有时会希望,让对象也能通过运算符进行运算, 这样代码更简洁,容易理解
- 例如:
- complex_a和complex_b是两个复数对象;
求两个复数的和,希望能直接写 complex_a + complex_b
- complex_a和complex_b是两个复数对象;
运算符重载
- 运算符重载,就是对已有的运算符(C++中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为
- 运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之能作用于对象
- 同一个运算符, 对不同的类型的操作数,所发生的行为不同
运算符重载的形式
- 运算符重载的是指是函数重载
- 可以重载为普通函数, 也可以重载为成员函数
- 把含运算符的表达式转换成对运算符函数的调用
- 把运算符的操作数转换成运算符函数的参数
- 运算符被多次重载时, 根据实参的类型决定调用哪个运算符函数
返回值参数 operator 运算符(形参表){
....
}
class Complex {
public:
double real, imag;
Complex(double r = 0, double i = 0): real(r), imag(i){}
Complex operator-(const Complex &c);
};
Complex Complex::operator-(const Complex &c) {
return Complex(real - c.real, imag - c.imag); //返回一个临时对象
}
Complex operator+(const Complex &a, const Complex &b) {
return Complex(a.real + b.real, a.imag + b.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;
}
赋值运算符的重载
有时候希望赋值运算符两边的类型可以不匹配,比如, 把一个int类型变量赋值给一个Complex对象,或把一个char*类型的字符串赋值给一个字符串对象,此时就需要重载运算符“=”
赋值运算符 “=” 只能重载为成员函数
class String {
private:
char *str;
public:
String():str(new char[1]){str[0] = 0;}
const char *get_str(){return str;};
String &operator = (const char *s);
~String(){delete []str;}
};
String &String::operator=(const char *s) {
delete []str;
str = new char[strlen(s) + 1];
strcpy(str, s);
return *this;
}
int main() {
String s;
s = "Hello";
cout << s.get_str() << endl;
// String s2 = "world"; //这句话要是不注释掉会报错,初始化句会调用构造函数而不是赋值重载
s = "world";
cout << s.get_str() << endl;
return 0;
}
浅拷贝和深拷贝
若有这样的一种情况
String s1, s2;
s1 = "this";
s2 = "that";
s1 = s2;
- 如不定义自己的赋值运算符, 那么s1, s2实际上导致s1.str和s2.str指向同一地方
- 如果s1对象消亡, 析构函数将释放s1.str指向的空间, 然而s2消亡还要再释放一次, 不妥(程序崩溃)
- 若s1 = “other”,则s2指向的空间也会消亡
解决方法:新添加一个赋值运算符的重载
String & operator = (const string & s){
if(this == &s) return *this; //防止s = s这种情况的出现导致程序崩溃
delete [] str;
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
return *this;
}
对 operator = 返回值类型的讨论
- 为什么是String &?
运算符进行重载的时候, 好的风格应该是尽量保留运算符原本的特性
(考虑 a = b = c 和 (a = b) = c) - 上述的String类是否就没有问题了?
为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);
};
Complex Complex::operator+ (double r) {
return Complex(real + r, imag); //能解释c + 5
}
经过上述重载后
Complex c;
c = c + 5; //有定义, 相当于c = c.operator + (5);
但是
c = 5 + c; //编译出错
所以, 为了使得上述的表达式能成立, 需要将 + 重载为普通函数
class Complex{
double real, imag;
public:
Complex(double r, double i): real(r), imag(i){};
Complex operator+ (double r);
friend Complex operator+ (double r, const Complex &c);
};
Complex operator+ (double r, const Complex &c){
return Complex(c.real, c.imag); //能解释 5 + c
}
类型转换运算符重载
#include <iostream>
using namespace std;
class Complex{
doubel real, imag;
public;
Complex(double r = 0, double i = 0):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
}
- 必须重载后才可使用
自增, 自减运算符的重载
- 自增运算符、自减运算符--有前置/后置之分, 为了区分所重载的是前置运算符还是后置运算符, 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);
- 前置运算符作为一元运算符重载
- 但是在没有后置运算符重载而有前置运算符重载的情况下, 在vs中, obj++ 也调用前置重载, 而dev则令 obj++ 编译出错
未完待续...
默默地一点点变强,细节决定成败