C++的运算符重载(2)

目录

一、赋值运算符重载函数返回引用

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;
}

C++的运算符重载(2)
  图中上边的部分是返回引用的打印信息,下边的是返回对象的打印信息,返回对象时调用了拷贝构造函数。

(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

注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。

上一篇:9.时间日期日历及错误异常处理


下一篇:C++智能指针前置知识1-浅拷贝与深拷贝