本篇要学习的内容和知识结构概览
运算符重载使用场景
常规赋值操作
// = 赋值运算符 普通的赋值操作
int a, b;
a = 5;
b = a;复制代码
我们现在有一个类
class str {
char * st;
public:
// 使用指针的构造函数
str(char * s) {
st = new char[strlen(s) + 1];
strcpy(st, s);
}
};复制代码
想要实现这种赋值操作
str s1("abc");
str s2("def");
s1 = s2;复制代码
就需要我们通过重载"="赋值运算符来实现了
具体实现如下
#include <iostream>
using namespace std;
class str {
char * st;
public:
// 使用指针的构造函数
str(char * s) {
st = new char[strlen(s) + 1];
strcpy(st, s);
}
// 使用引用的构造函数
str(str & a) {
st = new char[strlen(a.st) + 1];
strcpy(st, a.st);
}
// 重载使用对象引用的赋值运算符
str & operator=(str & a) {
if (this == &a) {
return *this;
}
delete st;
st = new char(strlen(a.st) + 1);
strcpy(st, a.st);
return *this;
}
// 输出字符串
void print() {
cout << st << endl;
}
// 析构函数
~str() {
delete st;
}
};
int main() {
// 两个字符数组
char c1[] = "hello";
char c2[] = "world";
// 三个对象
str s1(c1);
str s2(c2);
str s3(s1);
// 现在我们可以实现表象上的 = 赋值操作
s3 = s2 = s1;
// 等价于下面这句话
s3.operator=(s2.operator=(s1));
// 等价于下面这两句话
s2.operator=(s1);
s3.operator=(s2);
// 打印
s1.print();
s2.print();
s3.print();
}复制代码
所以说呢:
我们在使用运算符进行运算的时候, 实际上也是通过函数来实现运算的.
任何运算都是通过函数来实现的, 所以通过运算符来进行计算, 实际也是通过函数来完成
运算符重载的实质
表达式 7 / 2 = 3, 7.0 / 2.0 = 3.5, 同一个运算符 / , 具有不同的意义, 称之为”运算符重载”, 实际上就是"函数重载".
每个运算符都有自己的函数形式, 像下面这些
7 + 2 的函数形式就是 operator + (7, 2)
7 - 2 的函数形式就是 operator - (7, 2)
7 * 2 的函数形式就是 operator * (7, 2)
7 / 2 的函数形式就是 operator / (7, 2)
所以, 要重载某个运算符, 只要重载相应的函数就可以了
比如:
// string对象进行 + 运算
string a = "abc";
string b = "def";
cout << a + b << endl;复制代码
定义的重载运算符都要求能够访问这个类型的私有成员, 在这个前提下:
要么将运算符重载为这个类型的成员函数
要么将运算符重载为这个类型的友元
将作为类的成员函数的重载运算符称为类运算符
将作为类的友元重载运算符称为友元运算符
可重载运算符和不可重载运算符的列表
下面是: 可重载运算符与只能用类运算符重载和只能用友元运算符重载和不是运算符的区分
插入符<< 和 提取符 >> 的重载
注意:
<< 或者 >> 输入流和输出流都是标准类库, 不可修改, 所以不能在它们自己的类里重载为类运算符
操作符的左边是流对象, 而不是被操作的对象,而且我们还要访问被操作对象的私有数据, 所以我们只能将它们作为被操作类对象的友元重载
#include <iostream>
using namespace std;
// 一个新的类
class Test {
// 三个成员属性
int i;
float f;
char c;
public:
// 构造函数
Test(int a = 0, float b = 0, char ch = '\0') {
i = a;
f = b;
c = ch;
}
// 友元重构
friend ostream & operator << (ostream &, Test &);
// 友元重构
friend istream & operator >> (istream &, Test &);
};
// 重载插入符
ostream & operator << (ostream & output, Test & test) {
output << test.i << ", ";
output << test.f << ", ";
output << test.c << endl;
return output;
}
// 重载提取符
istream & operator >> (istream & input, Test & test) {
input >> test.i;
input >> test.f;
input >> test.c;
return input;
}
int main() {
// 通过重载提取符实例一个对象
Test a, b;
// 可以这么写 函数调用形式
// operator >> (cin, a);
// operator >> (cin, b);
// operator << (cout, a);
// operator << (cout, b);
// 也可以这么写 运算符形式
cin >> a >> b;
// << 插入符之所以可以连续使用是因为 cout << a 这个表达式返回cout对象, 继续输出b对象 cout << b
// cout << a << b << endl;
// operator <<(operator << (cout, a), b);
}复制代码
注意: 不能自己定义新的运算符, 只能是把原有的运算符用到自己设计的类上去
++ 运算符的重载:
作为类运算符的重载
#include <iostream>
using namespace std;
class Number {
int num;
public:
Number(int i) {
num = i;
}
// ++在前 编译器默认: 重写++时, 没有参数, 为++在前
int operator ++ () {
num++;
return num;
}
// ++ 在后: 编译器默认: 重写++时, 有一个参数, 为++在后
int operator ++ (int) {
int i = num;
num ++;
return i;
}
void print() {
cout << num << endl;
}
};
int main() {
Number n(10);
// int a = ++n;
int a = n.operator++();
cout << a << endl;
n.print();
// a = n++;
a = n.operator++(0);
cout << a << endl;
n.print();
}复制代码
作为友元运算符的重载
#include <iostream>
using namespace std;
class Number {
int num;
public:
Number(int i) {
num = i;
}
// ++ 在前: 一个参数 ++ 在前
friend int operator ++ (Number &);
// ++ 在后: 两个参数 ++ 在后
friend int operator ++ (Number &, int);
void print() {
cout << num << endl;
}
};
int operator ++ (Number & a) {
a.num++;
return a.num;
}
int operator ++ (Number & a, int) {
int i = a.num;
a.num ++;
return i;
}
int main() {
Number n(10);
// int i = ++n; 这个句子会调用operator++(Number)这个函数, 也就是++在前
int i = operator++(n);
cout << i << endl;
n.print();
// i = n++; 这个句子会调用operator++(Number, int)这个函数, 也就是++在后
i = operator++(n, 0);
cout << i << endl;
n.print();
}复制代码
注意:
经过重载, 运算符并不改变原有的优先级, 也不改变所需操作数目
当不涉及到定义的类对象时, 它仍然执行系统预定义的运算, 只有用到自己定义的对象止, 才执行新定义的操作
类运算符和友元运算符的区别
如果运算符所需的操作数希望进行隐式类型转换, 则运算符应通过友元来重载
如果一个运算符的操作需要修改类对象的状态, 则应当使用类运算符
运算符 + 作为友元运算符
#include <iostream>
using namespace std;
class complex {
double real, imag;
public:
complex(double r = 0, double i = 0) {
real = r;
imag = i;
}
// 友元重载
friend complex operator + (complex, complex);
void show() {
cout << real << "+" << imag << "i" << endl;
}
};
// 重载运算符
complex operator + (complex a, complex b) {
double r = a.real + b.real;
double i = a.imag + b.imag;
return complex(r, i);
}
int main() {
complex x(3, 2), y;
/**
friend complex operator + (complex &, complex &);
如果将友元运算符声明成这样: 则下面两个语句都编译错误
*/
// 使用友元运算符我们就可以将 7 隐式类型转成 7 + 0i 然后再进行 + 运算
y = x + 7; // 等价于 y = operator(x, 7)
y = 7 + y; // 等价于 y = operator(7, y);
y.show();
}复制代码
运算符 + 作为类运算符 (会出现编译错误)
#include <iostream>
using namespace std;
class complex {
double real, imag;
public:
complex(double r = 0, double i = 0) {
real = r;
imag = i;
}
// 类重载
complex operator + (complex a) {
double r = a.real + real;
double i = a.imag + imag;
return complex(r, i);
}
void show() {
cout << real << "+" << imag << "i" << endl;
}
};
int main() {
complex x(3, 2), y;
/**
complex operator + (complex, complex);
如果将类运算符声明成这样: y = 7 + y; 语句编译错误
*/
y = x + 7; // 等价于 y = x.operator(7)
// y = 7 + y; // 等价于 y = 7.operator(y);
y.show();
}复制代码
注意:
在上面的main函数代码中, 如果对象作为重载运算符函数的参数, 则可以使用构造函数将常量转换成该类型的对象. 如果使用引用作为参数, 这些常量不能作为对象名使用, 所以编译错误
总结
在学习C++这门语言的时候明显的感觉到她的包罗万象, 丰富多彩. 她有自己的很多特性, 表现在使用上就是更加的灵活, 总得来说就是: 没有她没有的, 只有你想不到的! 我会继续深入学习C++, 继续挖掘语言的本质!