应该说昨天晚上看了运算符重载,发现这块上了好强的难度,需要好好消化一下了
运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载
实现两个自定义数据类型相加的运算,如何实现用+号也能让两个类中的属性相加
void test01()
{
Person p1;
p1.m_a = 10;
p1.m_b = 10;
Person p2;
p2.m_a = 10;
p2.m_b = 10;
Person p3 = p1 + p2;//如何实现
class Person
{
public:
int m_a;
int m_b;
public:
//通过成员函数重载
Person PersonaddPerson(Person& p)
{
Person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}
};
通过成员函数重载这个+,实现我们想要做的加法,但是每个人的编程习惯不同,这样会导致很多函数名,因此编辑器自己起了一个名字,称为operator,上述代码就变为
Person operator+ (Person& p)
{
Person temp;
temp.m_a = this->m_a + p.m_a;
temp.m_b = this->m_b + p.m_b;
return temp;
}
接下来就调用这个成员函数
p3 = p1.operator+(p2)//本质是这个,可以简化为Person p3 = p1 + p2
上述便是通过成员函数重载+号的方法,下面是用全局函数重载+号
Person& operator+ (Person &p1 , Person &p2)
{
Person temp ;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
上述代码调用的本质是
Person p3 = operator+(p1,p2);//也可以简写成Person p3 = p1 + p2
如果我想实现Person p4 = p1 + 100这个功能呢,一样也可以通过全局函数重载实现这个功能
Person& operator+ (Person &p1 , int num)
{
Person temp ;
temp.m_a = p1.m_a + num;
temp.m_b = p1.m_b + num;
return temp;
}
如果一开始将int m_a; int m_b;设置成private,可以使用之前学的友元 即全局函数做友元,或者成员函数做友元均可实现
总结:1:对于内置的数据类型的表达式的运算符时不可能改变的 2:不要滥用运算符重载
左移运算符重载
可以输出自定义数据类型
class Person
{
public:
Person(int a , int b )
{
m_a = a;
m_b = b;
}
private:
int m_a;
int m_b;
};
写到这里的时候如果想是用成员函数重载左移运算符会发现
void operator<<( )
{
}
//按照之前的原理,调用这个成员函数的本质应该是
// p.operator<<() 简化版本p << cout
会发现无法实现cout在左侧,所以不会利用成员函数重载<<运算符 。
这时只能使用全局函数重载左移运算符
ostream &operator<<( ostream &cout , Person &p)
{
cout << "m_a = " << p.m_a << "m_b" << p.m_b;
return cout ; // cout 重载的重点 , 为了支持链式调用和保持与标准库的一致性
}
这里可能想不到return cout。如果这里没有return cout 会发现 cout << p << endl; 在第二个<<的时候会报错,要使用链式编程思想,上述函数返回的是void,所以不能进行函数追加。
之后会发现m_a,m_b的属性是私有的,所以我们要让全局函数做友元添加到person类中,便实现了重载左移运算符。
friend ostream &operator<<( ostream &cout , Person &p);
总结:重载左移运算符配合友元可以实现输出自定义类型的数据
递增运算符重载
这玩意是真的难呀,感觉很多点想不到
class MyInteger
{
friend ostream& operator<< (ostream& cout , MyInteger & myint);
public:
MyInteger()
{
m_Num = 0;
}
private:
int m_Num;
};
使用成员函数重载前置递增运算符
MyInteger& operator++()
{
m_Num ++ ;
return *this;// return *this;//返回引用是为了一直对一个数据进行递增
}
//测试代码
void test01()
{
MyInteger myint;
cout << ++myint << endl; //cout << ++myint << endl;
}
这边需要使用上述的<<左移运算符的重载,我们再来写一遍
ostream& operator<< (ostream& cout , MyInteger & myint)
{
cout << myint.m_Num;
return cout;
}
上述便是使用成员函数重载前置递增运算符
接下来使用成员函数实现后置递增运算符
和前置递增一样的思想,但是在传入参数的时候 有个int,这是函数重载的区分条件。int代表占位参数,可以用于区分前置和后置递增。
//后置递增 a++ 先打印 后递增
void operator++(int)
{
MyInterger temp = *this ;
m_Num++;
return temp; // 后置递增会先打印然后再递增,所以这边需要有一个临时变量来接收
}
最后一行代码的时候如果返回引用的话,这边是返回局部变量的引用,局部变量在执行完之后会被释放
但是在后置递增这边,直接运行是不行的,比如下面的test
void test02()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
在myint++后面的<<左移运算符会报错,显示没有可以用的运算符,网上deepseek查了才知道,要将左移运算符重载的代码稍微改一下才可以,需要将 operator<< 的参数改为 const MyInteger& ,以支持常对象的输出。(反正我是不懂的哦,要记住这玩意),使用这个const修饰的引用在前置递增和后置递增都不会报错。
ostream& operator<< (ostream& cout, const MyInteger& myint)
{
cout << myint.m_Num;
return cout;
}
赋值运算符重载
C++编译器至少给一个类添加4个函数
无参构造函数 无参析构函数 拷贝构造函数 赋值运算符operator =
如果类中有属性指向堆区,做赋值操作的时候会出现深浅拷贝问题 这是话就需要自建深拷贝构造函数
我们先来确定赋值运算符是需要做什么的
void test01()
{
Person p1(18);
Person p2(20);
p2 = p1; // 赋值运算操作 类似 a = 1 , b = 2 , b = a 的操作
}
class Person
{
public:
Person(int age )
{
m_age = new int(age);
}
~Person()
{
if(m_age != NULL)//先判断是否为空,如果不为空先进行清空
{
delete m_age;
m_age = NULL;
}
}
Person& operator=(Person &p)
{
if(m_age != NULL)//先判断是否为空,如果不为空先进行清空
{
delete m_age;
m_age = NULL;
}
m_age = new int(*p.m_age);//在堆区开辟一块新内存接收数据
return *this;//返回对象本身
}
int *m_age;
};
赋值运算操作这里有一个坑就在析构的时候浅拷贝的问题 堆区内存重复释放
关系重载运算符
重载关系运算符,可以让两个自定义类型的对象进行对比操作 (主要符号是 == 和 !=)
void test01()
{
Person p1("Tom", 18);
Person p2("Jerry", 18);
if (p1 == p2)
{
cout << "p1和p2 是相等的" << endl;
}
else
{
cout << "p1和p2 是不相等的" << endl;
}
}
先来看一下上面的测试代码,这样我们才能明白关系重载运算符需要做什么。上述代码中想实现使用 == 关系运算符 比较p1 和 p2 这两个的关系,知道了需求我们来写成员函数重载
class Person
{
public:
Person(string name , int age )
{
m_name = name ;
m_age = age ;
}
bool operator==(Person &p)
{
if (*this->m_name == p.m_name && *this->m_age == p.m_age)
{
return true;
}
return false;
}
string m_name ;
int m_age;
};
重载=!和上述一样的,就不再继续写相似的了
函数调用运算符重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数调用,因此称为仿函数
仿函数没有固定的写法,非常灵活 STL
函数调用运算符重载有什么用呢?接下来看测试代码
void test01()
{
Myprint myprint;
myprint("hello world");//由于使用起来类似函数调用,因此称为仿函数
}
class Myprint // 打印
{
public:
void operator()(string text)
{
cout << text << endl;
}
};
这样就可可以正确调用打印输出了,由于使用起来类似函数调用,因此称为仿函数
上述代码实现了打印输出类,下面再实现一个加法类
void test02()
{
Myadd add;
int ret = add(1, 2);
cout << "ret = " << ret << endl;
}
class Myadd
{
public:
int operator()(int a , int b )
{
return a + b ;
}
};
//匿名函数对象 Myadd()通过一个类名加()创建一个匿名对象
cout << Myadd()(100, 100) << endl;
上述就结束啦,前面三个的难度还是比较大的,到了后面三个重载就简单多了!