目录
一、赋值运算符重载函数返回引用
1、返回引用好处1:提升程序效率
(1)赋值运算符重载函数返回值可以返回对象类型,也可以返回对象引用类型,都能工作。代码验证
#include <iostream>
using namespace std;
class coordinate
{
public:
int x, y;
void print(void);
//coordinate& operator=(const coordinate& other);
coordinate operator=(const coordinate& other);
coordinate();
coordinate(int x0, int y0);
coordinate(const coordinate& rhs);
};
void coordinate::print(void)
{
cout << "a=(" << this->x << "," << this->y << ")" << endl;
}
#if 0
coordinate& coordinate::operator=(const coordinate& other)
{
this->x = other.x;
this->y = other.y;
return *this;
}
#else
coordinate coordinate::operator=(const coordinate& other)
{
this->x = other.x;
this->y = other.y;
return *this;
}
#endif
coordinate::coordinate()//默认构造函数
{
this->x = 0;
this->y = 0;
}
coordinate::coordinate(int x0, int y0)//自定义构造函数
{
cout << "------constructor-------" << endl;
this->x = x0;
this->y = y0;
}
coordinate::coordinate(const coordinate& rhs)//拷贝构造函数
{
cout << "------copy_constructor-------" << endl;
this->x = rhs.x;
this->y = rhs.y;
}
int main(int argc, char *argv[])
{
coordinate a(1,5);
coordinate b(4,5);
coordinate c;
cout << "----------c = a---------" <<endl;
c = a;
cout << "----------c = a---------" <<endl;
a.print();
b.print();
c.print();
return 0;
}
图中上边的部分是返回引用的打印信息,下边的是返回对象的打印信息,返回对象时调用了拷贝构造函数。
(2)区别在于:返回引用可以避免一次返回值值传递的对象复制,这需要消耗资源的。
(3)总结:虽然C++语法并未强制要求,但是好的写法是应该返回引用
2、返回引用好处2:允许连续赋值式
(1)返回对象而不是引用时,在连续赋值(c = a = b;)时会编译可以,运行也可以,但是效率低同1中所讲。
(2)原因是先执行a=b操作,返回值再作为第2次赋值运算的右值(也就是函数参数),对象和引用是类型兼容的
(3)总结:连等在返回对象和引用时都可以,但是在返回void时就不可以了
3、思考:传参为什么要传引用
(1)如果传对象,则调用时是值传递,调用时需要复制一次,增加额外开销
(2)如果传指针,则重载后,使用时为了符合函数参数格式必须写成 a = &b;这种,不符合C语言写法习惯,也有歧义
(3)实际测试发现真的传指针时,写 c=a; c=&a;都能编译通过,且运行正确。但是确实很别扭。
(4)总结:有资料表明,其实C++早期发明引用概念时,就是为了解决此处运算符重载传参的尴尬。
二、String类的赋值运算符重载
1、标准库中String类的运算符重载
(1)https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5
(2)String类提供了 = 和 + 等多个运算符重载函数的成员
(3)标准库只给了API,没给实现,咱们可以自己实现个=运算符重载函数
2、String的=运算符重载演练
(1)自己编写一个MyString类,成员变量char *buf指向实际存储字符串的buf,new动态内存来使用。
(2)实践:先完成构造函数,成员函数print,len等基础设计,写代码测试ok
(3)编写=运算符重载函数,引出浅拷贝深拷贝问题
#include <iostream>
using namespace std;
class Mystring
{
private:
char *buf; //指针指向实际存储字符串内容的buffer空间,采用动态分配
public:
int len; //字符中存储的有效字符个数
Mystring(); //构造函数
Mystring(const char *pSrc); //拷贝构造函数
~Mystring(); //析构函数
void print(void); //普通成员函数
Mystring& operator=(const Mystring& other);//运算符重载函数
};
Mystring::Mystring()
{
//不写,空着等需要时再写,因为提供了自定义的构造函数,
//所以必须要由默认的构造函数
this->len = 32;
this->buf = new char[32];
}
Mystring::Mystring(const char *pSrc)
{
//计算有效字符的个数
const char *p = pSrc;
this->len = 0;
while(*p++ != '\0')
{
this->len++;
}
cout << "cnt of pSrc is " << this->len << endl;
//先给buf分配动态内存空间
this->buf = new char[this->len+1];
//将pSrc指向的源字符串内容,复制给当前Mystring对象来存储
p = pSrc;
char *tmp = this->buf;
while(*p != '\0')
{
*tmp++ = *p++;
}
*tmp = '\0';
}
Mystring::~Mystring()
{
delete[] this->buf;
}
void Mystring::print(void)
{
cout << "length of string = " << this->len << endl;
char *p = this->buf;
cout << "this string is : ";
while(*p != '\0')
{
cout << *p++;
}
cout << endl;
}
Mystring& Mystring::operator=(const Mystring& other)
{
delete[] this->buf;//释放掉原来的内存,重新申请内存
this->buf = new char[other.len+1];//避免字符串内存越界
this->len = other.len;
char *p1 = this->buf;
char *p2 = other.buf;
while(*p2 != '\0')
{
*p1++ = *p2++;
}
//return *this;
}
int main(int argc, char *argv[])
{
Mystring s1("123456");
Mystring s2("absjhlllbsb");
s1.print();
s2.print();
cout << "-----------------------" << endl;
s1 = s2;
s1.print();
s2.print();
return 0;
}
3、总结
(1)运算符重载技术本身很简单,就是个语法糖,前面讲的完全够了
(2)运算符重载会牵出其他技术点(譬如浅拷贝深拷贝)共同完成某个工作,这就有难点,讲究功底了
(3)C++编程中涉及到动态内存的地方一定要慎重,三思五思再慎重都不为过,运算符重载函数返回值类型不是引用而是对象可能会引发段错误,我上面提供的程序就会,调试了半小时才发现(我太菜了)。
三、++和–运算符的前置后置如何实现
1、可前置可后置的运算符
(1)int a = 5; a++; ++a;结果不同
(2)重载++和–有无意义
有意义,因为我们有时习惯了C语言的++和–的便利性。
2、如何重载++和–
C++编译器认为:
a++; 对应Type& operator++(int x); //int x用来做区分,此外没有任何其他作用。
++a对应Type& operator++(void);
示例代码:https://www.runoob.com/cplusplus/increment-decrement-operators-overloading.html
3、总结
(1)即使你觉得没必要,但是也得会,因为总有人会这样写
(2)掌握最本质的规律的人最强大,遇到越困难的问题越需要这种能力来解决
四、两种运算符重载方法
1、并非所有运算符都支持重载
(1)下面是不可重载的运算符列表
. 成员访问运算符
.*, ->* 成员指针访问运算符
:: 域运算符
sizeof 长度运算符
? : 条件运算符
# 预处理符号
2、并非只有一种方式实现重载
(1)有两种方法可以使运算符重载
第一种:使重载运算符成为该类的成员函数。这允许运算符函数访问类的私有成员。它也允许函数使用隐式的this指针形参来访问调用对象。
第二种:使重载的成员函数成为独立分开的函数。当以这种方式重载时,运算符函数必须声明为类的友元才能访问类的私有成员。
3、如何选择两种运算符重载
(1)大多数运算符(如+ =等)既可以作为成员函数也可以作为独立函数重载。
(2)更好的做法是将二元运算符重载为釆用相同类型形参的独立函数。这是因为,与独立运算符的重载不同,成员函数的重载通过使左侧形参变成隐式的,造成了两个形参之间的人为区别,这将允许转换构造函数应用右侧形参,但不应用左侧形参,从而产生了更改形参顺序的情况,导致正确的程序如果换个方式却出现了编译器错误。
示例如下:
Length a(4, 2), c(0);
c = a + 2; //编译,当于 c = a.operator+(2)
c = 2 + a; //不能编译:相当于 c = 2.operator+(a);
参考学习:https://blog.csdn.net/jacket_/article/details/89714947
注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。