欢迎光临
我们一直在努力

C++类与对象完整学习路径(中):如何理解默认成员函数2

目录

前序        

1.运算符重载

1.1运算符重载

1.1.1 为什么要重载?

1.1.2 能否通过运算符重载改变原运算符的含义?

3.流插入(<<)和流提取(>>)重载

4.前置++和后置++重载的区分

5.赋值运算符重载

2.const成员函数


前序        

        大家好,我是小沐。上一篇我们探讨了如何理解构造函数、析构函数还有拷贝构造函数,可以说类与对象这一篇算是轻舟已过百重山~ (没打错哈,就是百重山)。

本篇内容:

                1.运算符重载。

                2.赋值运算符重载。

                3.const函数。

1.运算符重载

先问:1.为什么要运算符重载?是以前的功能不够用?

           2.运算符重载后的操作数是什么?

(如果不明确这两个问题,就算这部分内容学会了,也不能完全消化,就像人穿鞋为什么要穿袜子一样)

        在C语言阶段,我们使用各种运算符进行运算时,操作数都是系统的内置类型,但是在C++里面,我们引入了类和对象的概念,它们是一种自定义类型,在过去我们都用点运算符进行访问,想操作对象就得慢慢的点访问都是比较麻烦的。祖师爷本贾尼为了让类这种自定义数据类型也能像内置类型那样直接操作,便设计出了运算符重载。

1.1运算符重载

1.1.1 为什么要重载?

        明确了运算符重载的目标之后,那我们学习就十分得心应手了。

先给基本知识:

        1.运算符的操作对象时类类型时,C++允许我们通过运算符重载的形式给运算符指定新的含义。

        2.运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。

        3.运算符原来的操作数数量是多少,重载后操作数依然只能是多少,不能变多或变少。

        我们来看一个日期类型的运算符重载。

#include <iostream>
using namespace std;
class Date
{
public:
Date(int n = 1)
{
_a = n;
}
Date operator+(Date& d);

void Print()
{
cout << _a << endl;
}
private:
int _a;

};
Date Date::operator+(Date& d)
{
Date temp ;
temp._a = this->_a + d._a;
return temp;
}

int main()
{
Date d1(3);
Date d2(4);
Date d3 = d1 + d2;
d3.Print();
return 0;
}

        可以看到,运算符重载之后,我们就可以让类类型的对象如内置类型一样,可以直接名字进行运算。如果你还是感受不到,我们来试试,不用运算符重载让d1和d2完成相加。

        很明显,在没有给+号在类类型上的新含义时,原始相加方式是不合法的,C++上不允许这么做。如果不用运算符重载,来实现相加,就要这么操作(方法不唯一)

#include <iostream>
using namespace std;
class Date
{
public:
Date(int n = 1)
{
_a = n;
}
//Date operator+(Date& d);
//需要一个间接访问private部分的函数
int GetVal()
{
return _a;
}
void Add(int n)
{
_a = n;
}

void Print()
{
cout << _a << endl;
}
private:
int _a;

};
//Date Date::operator+(Date& d)
//{
//Date temp ;
//temp._a = this->_a + d._a;
//return temp;
//}

int main()
{
Date d1(3);
Date d2(4);
Date d3;
int temp = d1.GetVal() + d2.GetVal();
d3.Add(temp);
d3.Print();
return 0;
}

        需要加上很多的间接访问方式才能够,完成一个相加后赋值给另一个对象的操作。这只是一次加法操作,要知道在一个程序中是要经常使用运算操作符的,使用运算符重载后就能像内置类型简单操作了。

1.1.2 能否通过运算符重载改变原运算符的含义?

        从语法层面来说是可以的,但是仅仅对类类型有用,以上面的代码为例子

Date Date::operator+(Date& d)
{
Date temp ;
temp._a = this->_a – d._a; //正确的逻辑应该是 temp._a = this->_a + d._a;
return temp;
}

        这种情况下是能被改变的。那上面为什么说仅仅类类型有用呢?

C++规定:重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义。如

operator+(int x, int y)

        这种不仅是为了适配语法环境,还有为了避免,有人乱来,如把原来的1+1 = 2改为

1 + 1等于别的值,那就比较扯了。像上面这种写法语法上是不允许的。

注: .*   ::  sizeof   ?:   .    这五个运算符不能重载。

        其中.* (成员函数指针调用运算符)很多同学可能没见过,一般用于对象调用成员函数指针时用

class A
{
public:
void func()
{
cout << "A::func()" << endl;
}
};

typedef void(A::* PF)();
int main()
{
//C++规定成员函数要加&才能提取到函数指针
PF pf = &A::func;
A obj;
//调用func
(obj.*pf)();
return 0;
}

        等价逻辑:

  • 拿对象obj,拿成员函数指针pf
  • 通过.*,把obj作为 this 指针传给这个成员函数
  • 执行函数调用
  • 3.流插入(<<)和流提取(>>)重载

            我们在习惯上调用流插入和流提取时,左边是cout和cin 。当将这两个运算符重载定义在类里面时,就会出现一个比较反习惯的写法

    思考下面这种写法是否能够正常输出

    class Date
    {
    public:
    Date(int year = 2023, int month = 1, int day = 1)
    {
    _year = year;
    _month = month;
    _day = day;
    }
    ostream& operator<<( ostream& out)
    {
    out << this->_year << "年" << _month << "月" << _day << "日" << endl;
    return out;
    }

    private:
    int _year;
    int _month;
    int _day;
    };

    int main()
    {
    Date d(2024, 5, 9);
    cout << d;
    return 0;
    }

            答案是不能,代码会编译报错。

    成员函数默认第一个参数是const Date* this,所以你传参肯定是要按照顺序传的,现在这种写法等价于operator<<(cout,d)明显传参位置不对。

    所以正确的调用方式应该是

    int main()
    {
    Date d(2024, 5, 9);
    d << cout;
    return 0;
    }

            这就特别反习惯了,所以重载流插入和流提取我们一般定义在全局函数中,这样就不存在隐藏的参数卡在第一个位置了。为了方便解释此处,我们引用一个友元函数

    class Date
    {
    public:
    friend ostream& operator<<(ostream& out, const Date& d);
    Date(int year = 2023, int month = 1, int day = 1)
    {
    _year = year;
    _month = month;
    _day = day;
    }
    private:
    int _year;
    int _month;
    int _day;
    };
    ostream& operator<<(ostream& out, const Date& d)
    {
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
    }

    int main()
    {
    Date d(2024, 5, 9);
    cout << d;
    return 0;
    }

    这样就符合我们的流插入习惯了。那么流提取的逻辑也是一样的,这里就不再讨论。

    4.前置++和后置++重载的区分

            我们知道,无论是前置++还是后置++都能对操作数自身起到加1的效果,但是在给别的变量赋值的时候,前置是先自身加1再赋值给别人,而后置++是先赋值给别人再自身加1,但是在重载中,它们的名字都一样

    operator++() //前置++

    operator++() //后置++

            我们该如何区分它呢?我们先看实现

    class Date
    {
    public:
    friend ostream& operator<<(ostream& out, const Date& d);
    Date(int year = 2023, int month = 1, int day = 1)
    {
    _year = year;
    _month = month;
    _day = day;
    }
    //前置
    Date operator++()
    {
    this->_year++;
    this->_month++;
    this->_day++;
    return *this;
    }

    //后置
    Date operator++()
    {
    Date temp = *this;
    this->_year++;
    this->_month++;
    this->_day++;
    return temp;
    }
    private:
    int _year;
    int _month;
    int _day;
    };

    你会发现,除了实现,其它地方一模一样,那么为了区分前置++和后置++,C++做了如下规定

    C++规定:后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分

    //后置
    Date operator++(int)
    {
    Date temp = *this;
    this->_year++;
    this->_month++;
    this->_day++;
    return temp;
    }

    我们来看运行结果

            (这里不要纠结实现的是什么,关注语法)

    5.赋值运算符重载

            赋值运算符重载也是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,不同于拷贝构造,拷贝构造用于拷贝初始化给另一个要创建的对象。

    赋值运算符重载的特点:

    1.赋值运算符重载时一个运算符重载,规定必须重载为成员函数

    2.赋值运算符重载后传参不会造成无穷递归。

    3.有返回值,使用引用返回提高效率。

    4.没有显示实现时,编译器会自动生成一个默认赋值运算符重载,行为默认跟拷贝构造类似。

            先看基本实现操作

    Date& operator=( Date& d)
    {
    d._day = this->_day;
    d._month =this->_month ;
    d._year = this->_year ;
    return d;
    }

    特别注意的一个点是,尽量带引号做返回值

    好处:

            1.可以不用调用拷贝构造,能提高效率。

            2.能够实现连续赋值               

    赋值运算符的赋值顺序是不同于C语言阶段的连续赋值顺序的。

            这是C语言的赋值顺序,而对于C++的赋值重载来说,刚好相反,原因就在于this指针,成员函数的第一个参数总是this指针,也就导致左边的参数永远是左操作数,C++又规定赋值重载只能做为成员函数,所以无法像流插入和流提取那样定义成全局函数来进行调整。

    2.const成员函数

            我们把用const修饰的成员函数叫做const成员函数,用法是把const放到成员函数参数列表的后面。

    const会修饰对应的成员函数的this指针,,表明在该成员函数中,不能对类的任何成员进行修改。

            如果加上后强行修改,如

            编译器会报错,制止语法错误。一般不打算修改成员变量的成员函数都建议加上const进行修饰,也没有什么特别的规定,就是为了避免写代码时不小心写错时,可以及时报错提醒整改。


            本篇结束!!你在学习这部分的时候有没有踩过什么坑?评论区聊聊,下一篇我们将从赋值运算符重载继续分享~

    赞(0)
    未经允许不得转载:171主机测评 » C++类与对象完整学习路径(中):如何理解默认成员函数2
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址