六、运算符重载
自定义的类中,一般是不支持运算符的,如果对于该类的对象,非要使用相关运算符,需要进行将运算符进行重载
运算符重载的好处:可以使得程序代码更加优雅、好看、简洁、高效
6.1 运算符种类
单、算、移、关、逻辑、条件表达式、赋值、逗号
6.2 运算符重载的定义
运算符重载,也是实现泛型编程的一种,能够实现“一符多用”,可以使用运算符重载来完成对象与对象之间的符号运算
6.3 运算符重载函数
1> 函数名:统一为 operator# //这里的#表示要被重载的运算符
2> 参数:根据运算符的功能和位置而定,全局函数版双目运算符参数有两个,成员函数版双目运算符参数一个
全局函数版,单目运算符参数有一个,成员函数版单目运算符参数无
3> 返回值:根据运算符的特征而定
6.4 调用时机和调用机制
调用时机:当对象使用该运算符时,系统自动调用运算符重载函数
调用机制:左调右参原则,运算符左侧是函数的调用者,运算符右侧是函数的参数
例如:a = b; //a.operator=(b);
s1 + s2; //s1.operator+(s2);
6.5 运算符重载函数的格式
1> 任意一个运算符重载都有两个版本:成员函数版和全局函数版
2> 实际使用过程中,只实现一种重载的情况,常用成员函数版
6.6 算术运算符
1> 种类:+、-、*、/、%
2> 算术运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数
3> 左操作(L):既可以是左值也可以是右值
右操作数(R):既可以是左值也可以是右值
运算结果:只能是右值
4> 格式:
成员函数版:const 类名 operator#(const 类名 &R) const
解析:第一个const保护运算结果不被修改
第二个const保护右操作数在运算过程中不被修改
第三个const保护左操作数自身在运算过程中不被修改
全局函数版:const 类名 operator#(const 类名 &L,const 类名 &R)
6.7 赋值类运算符重载
1> 种类:=、+=、-=、*=、/=、%=
2> 赋值运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数
3> 左操作(L):只能是左值
右操作数(R):既可以是左值也可以是右值
运算结果:左操作数自身的引用
4> 格式:
成员函数版:类名 & operator#(const 类名 &R)
全局函数版:类名 & operator#( 类名 &L,const 类名 &R)
6.8 关系运算符重载函数
1> 种类:>、<、==、!=、>=、<=
2> 关系运算符属于双目运算符,格式:L # R; L表示左操作数,#表示运算符号,R表示右操作数
3> 左操作(L):既可以是左值也可以是右值
右操作数(R):既可以是左值也可以是右值
运算结果:bool类型的右值
4> 格式:
成员函数版:const bool operator#(const 类名 &R) const
全局函数版:const bool operator#(const 类名 &L,const 类名 &R)
6.9 单目运算符
1> 种类:-(负号) 、!
2> 格式:# R; #表示运算符号,R表示右操作数
3> 操作数(R):既可以是左值也可以是右值
运算结果:同类的一个右值
4> 格式:
成员函数版:const 类名 operator#() const
全局函数版:const bool operator#(const 类名 &R)
6.10 自增自减运算
1> 种类:++ 、 --
2> 格式:# R; #表示运算符号,R表示右操作数
3> 操作数(R):只能是左值
运算结果:
前置:是自身的引用
成员格式:类名 &operator#()
全局格式:类名 &operator#(类名 &R)
后置:临时值
成员格式:类名 operator#(int)
全局格式:类名 operator#(类名 &R, int)
6.11 插入和提取运算符重载
1> 插入和提取运算符的来源
namespace std
{
ostream cout;
istream cin;
}
2> 由于运算符重载函数的调用遵循左调有参原则,所以,自定义类的重载函数,需要流对象和自定义类的对象
例如:cout << c1; //cout.operator<<(c1)
3> 此时,原则上来说,需要在流所在的类对象中,写成员函数,难度较大,所以,对于该类的重载函数,我们只能实现全局函数版,将该函数作为类的友元函数
4> 由于不需要使用istream和ostream类中的私有成员,所以,全局函数版的重载函数不需要在流对应的类中,声明友元函数
如果,重载函数中,需要使用自定义的类中的私有成员,则需要在自定义类中,声明该全局函数为友元函数
5> 格式:
ostream& operator<<(ostream& L, const 类名& R)
istream& operator>>(istream &L, 类名 &R)
6.12 类型转换运算符
1> 有时,需要将类对象进行强制类型转换为其他数据类型,此时就需要在类体内提供类型转换运算符重载函数
2> 定义格式: operator 要转换的类型() { 返回对应类型的数据 }
6.13 函数对象(仿函数)
1> 本质上就是重载 () 运算符,由于调用时,跟函数调用一样,所以称为仿函数
2> 定义格式: operator() (参数列表) {函数体内容}
3> 调用格式:对象名(实参名); //对象 . operator() (实参名)
6.14 运算符重载的限制
不能重载运算符 ::(作用域解析)、.(成员访问)、.*(通过成员指针的成员访问)及 ?:(三元条件)。
不能创建新运算符,例如 **、<> 或 &|。
运算符 && 与 || 的重载失去短路求值。
重载的运算符 -> 必须要么返回裸指针,要么(按引用或值)返回同样重载了运算符 -> 的对象。
不可能更改运算符的优先级、结合方向或操作数的数量。
&&、|| 和 ,(逗号)在被重载时失去其特殊的定序性质,并且即使不使用函数调用记法,也表现为与常规的函数调用相似。
(C++17 前)
作业:
将myString类中能够实现的操作都实现一遍
#include <iostream>
#include <cstring>
#include <string>
class myString {
private:
char* str;
int length;
public:
// 构造函数
myString(const char* s = "") { // 定义一个名为myString的构造函数,接受一个指向字符数组的指针作为参数,默认值为空字符串
length = strlen(s); // 计算传入字符串的长度
str = new char[length + 1]; // 为新的字符数组分配内存空间,长度为原字符串长度加1(用于存储空字符'\0')
strcpy(str, s); // 将传入的字符串复制到新分配的字符数组中
}
// 析构函数
~myString() {
delete[] str;
}
// operator=为字符串赋值
myString& operator=(const myString& other) {
if (this != &other) {
delete[] str;
length = other.length;
str = new char[length + 1];
strcpy(str, other.str);
}
return *this;
}
// operator+=为字符串赋值
myString& operator+=(const myString& other) {
if (this != &other) {
char* new_str = new char[length + other.length + 1];
strcpy(new_str, str);
strcat(new_str, other.str);
delete[] str;
str = new_str;
length += other.length;
}
return *this;
}
// 输出运算符重载
friend std::ostream& operator<<(std::ostream& cout, const myString& obj) {
cout << obj.str;
return cout;
}
// 输入运算符重载
friend std::istream& operator<<(std::istream& cin, const myString& obj) {
cin >> obj.str;
return cin;
}
// at访问指定字符,有边界检查
char at(int index) const {
if (index >= 0 && index < length) {
return str[index-1];
} else {
std::cerr << "Index out of bounds" << std::endl;
return '\0'; // 返回空字符作为错误标志
}
}
// 重载 operator[]
char operator[](int index) {
return str[index-1];
}
//返回指向字符串首字符的指针
const char* data( )const {
return str;
}
//返回字符串的不可修改的C字符数组版本
const char* c_str() const {
return str;
}
//检查字符串是否为空
bool empty() const {
return length == 0;
}
size_t size() const {
return length;
}
int capacity() const {
return length + 1;
}
void clear() {
delete[] str;
str = nullptr;
length = 0;
}
//后附字符到结尾
void push_back(char c) {
// 创建一个新数组,长度比原字符串多一个字符的空间,用于存放新添加的字符
char* newStr = new char[length + 2];
// 将原字符串复制到新数组中
strcpy(newStr, str);
// 在新数组的末尾添加新字符
newStr[length] = c;
newStr[length + 1] = '\0'; // 在新字符串末尾添加空字符,表示字符串结束
// 更新成员变量
delete[] str; // 删除原字符串
str = newStr; // 将新字符串赋值给成员变量
length++; // 增加字符串长度
}
//移除末尾字符
void pop_back() {
if (length > 0) {
length--;
str[length] = '\0';
}
}
//比较俩个字符串
bool operator==(const myString& other) const {
if (length != other.length) {
return false;
}
for (int i = 0; i < length; ++i) {
if (str[i] != other.str[i]) {
return false;
}
}
return true;
}
};
int main() {
myString str1("Hello");
myString str2("World");
myString str3("");
std::cout<<str1<<std::endl;
str1=str2;
std::cout<<str1<<std::endl;
str1="good";
std::cout<<str1<<std::endl;
std::cout << "str1的第1位字母: " << str1.at(1) << std::endl;
std::cout << "str1的第2位字母: " << str1.operator[](2) << std::endl;
myString file1=str2.data();
std::cout << file1<< std::endl;
myString file2=str2.c_str();
std::cout << file2<< std::endl;
if(str1.empty()){
std::cout << "str1为空"<< std::endl;
}else{
std::cout << "str1不为空"<< std::endl;
}
if(str3.empty()){
std::cout << "str3为空"<< std::endl;
}else{
std::cout << "str3不为空"<< std::endl;
}
std::cout << "str1中字符数为:"<<str1.size()<< std::endl;
std::cout << "str1中容量为为:"<<str1.capacity()<< std::endl;
str1.clear();
if(str1.empty()){
std::cout << "str1为空"<< std::endl;
}else{
std::cout << "str1不为空"<< std::endl;
}
str1="good";
str1+=str2;
std::cout<<"str1为"<<str1<<std::endl;
std::cout<<"str2为"<<str2<<std::endl;
char ch;
std::cin >> ch;
str1.push_back(ch); // 将字符ch添加到字符串末尾
std::cout << str1 << std::endl;
str1.pop_back();
std::cout << str1 << std::endl;
std::cout<<"str1为"<<str1<<std::endl;
std::cout<<"str2为"<<str2<<std::endl;
if (str1 == str2) {
std::cout << "str1和str2相等" << std::endl;
} else {
std::cout << "str1和str2不相等" << std::endl;
}
return 0;
}