C++:运算符重载和“const”成员

news/2024/9/22 22:09:56/

hello,各位小伙伴,本篇文章跟大家一起学习《C++:运算符重载》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !

文章目录

    • 赋值运算符重载
      • 1. 运算符重载
      • 2.赋值运算符重载
        • 第一个点
        • 第二个点:
        • 第三个点:
    • 前置++和后置++重载
    • const成员

赋值运算符重载

1. 运算符重载

在《C++:函数重载和引用》里有讲到函数中在,那么和运算符重载有什么不一样呢?

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字 operator 后面接需要重载的运算符符号。
函数原型:返回值类型 operator 操作符(参数列表)

但是并不是所有符号都能重载,要注意:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .*::sizeof?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

举个例子:

class Date
{
public:Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;}// 加const修饰是为了不让传参被改变bool operator==(const Date& d){return _year == d._year && _month == d._month&& _day == d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 5, 1);Date d2(d1);//拷贝构造cout << (d1 == d2) << endl;return 0;
}

输出结果是:1,也就是说d1和d2是相等的

// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d){return _year == d._year && _month == d._month&& _day == d._day;}

2.赋值运算符重载

第一个点

先来看格式,赋值运算符重载格式:

  • 参数类型:const T&,传递引用可以提高传参效率 //T是类名
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
Date& operator=(const Date& d){if(this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}

1.先讲讲参数形式为什么是**const T&**和返回值类型:T&,在《C++:构造函数、析构函数、拷贝构造函数》讲到过在对象的传值传参时,会发生拷贝构造,返回临时对象时,也会发生拷贝构造,会降低效率。所以选择传引用传参和用引用做返回值

用引用做返回值还有一个原因是为了支持连续赋值,举个例子:

int main()
{Date d1(2024,5,1);Date d2(1,1,1);Date d3(1,1,1);d3 = d2 = d1;return 0;
}

d2 = d1时调用了函数Date& operator=(const Date& d),会返回一个值,这个值属于什么类型会影响d3,所以返回*this是为了将d2的值赋值给d3,从而实现了连续赋值

2.检测是否自己给自己赋值,自己给自己复制就没意思了,所以直接return *this

第二个点:

赋值运算符只能重载成类的成员函数不能重载成全局函数
原因:

赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

第三个点:

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}

在这里会调用Time类的赋值运算符重载完成赋值

这里有小伙伴会说:既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。但是存在即合理,看以下例子:

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}

这个是用类来实现栈,为什么程序会崩溃呢?
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

逐字节拷贝:

  1. 也就是说s2的_arry的地址指向了s1的_arry的地址
  2. 那么编译器为s2的_arry开辟的空间消失了
  3. 导致内存泄漏
  4. 在最后销毁时,将同一块空间释放了两次,程序崩溃

图解:
在这里插入图片描述

前置++和后置++重载

根据前面所讲,
函数名字为:关键字 operator 后面接需要重载的运算符符号。
函数原型:返回值类型 operator 操作符(参数列表)

那么怎么区分前置++和后置++重载的函数,来看:
在C++中,前置++和后置++运算符可以被重载为成员函数或友元函数。要区分重载的前置++和后置++函数,你需要注意两点:

  1. 函数参数列表:前置++和后置++函数的参数列表应该是不同的,以便编译器可以区分它们

  2. 函数的返回类型:前置++函数应该返回引用,而后置++函数应该返回值

下面是一个示例,展示了如何重载前置++和后置++运算符:

#include <iostream>class Counter {
public:Counter() : count(0) {}// 前置++运算符重载Counter& operator++() {++count;return *this;}// 后置++运算符重载Counter operator++(int) {Counter temp = *this;++(*this);return temp;}void display() {std::cout << "Count: " << count << std::endl;}
private:int count;
};int main() {Counter c1, c2;// 前置++++c1;c1.display();  // 输出: Count: 1// 后置++c2++;c2.display();  // 输出: Count: 1return 0;
}

在这个示例中,前置++运算符被重载为成员函数operator++(),后置++运算符被重载为成员函数operator++(int)。前置++函数返回引用,而后置++函数返回值。

注意:C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

只不过这个const放的位置有点奇怪,因为在类的成员函数参数列表中this指针已经是固定在第一位的,只是看不到,所以要这么做:

class Date
{
public:void Print() const{cout<<_year<<"-"<<_month<<"-"<<_day<<endl;}
private:int _year;int _month;int _day;
};

那么const修饰后的对象调用函数,又与普通的对象调用函数有什么不一样呢?
来看下列代码:

#include<iostream>
using namespace std;class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022, 1, 13);d1.Print();const Date d2(2022, 1, 13);d2.Print();
}int main()
{Test();return 0;
}

运行结果:在这里插入图片描述
很明显,d1不能调用void Print() const,d2不能调用void Print()

问题又来了:

  1. const对象内可以调用其它的非const成员函数吗?
  2. 非const对象内可以调用其它的const成员函数吗?

总结:

  1. const 对象可以调用非 const 成员函数吗?

    • 是的,const 对象可以调用非 const 成员函数。因为 const 对象的调用不会改变对象的状态,只是限制了对成员变量的修改,所以它可以安全地调用非 const 成员函数。
  2. 非 const 对象可以调用 const 成员函数吗?

    • 是的,非 const 对象可以调用 const 成员函数。因为非 const 对象的调用不会限制对成员变量的修改,所以它可以安全地调用 const 成员函数。
  3. const 成员函数内可以调用其他的非 const 成员函数吗?

    • 是的,const 成员函数内可以调用其他的非 const 成员函数。因为 const 成员函数承诺不会修改对象的状态,但它可以通过调用其他非 const 成员函数来实现某些功能。
  4. 非 const 成员函数内可以调用其他的 const 成员函数吗?

    • 是的,非 const 成员函数内可以调用其他的 const 成员函数。因为非 const 成员函数没有限制对成员变量的修改,所以它可以安全地调用 const 成员函数。

总的来说,const 成员函数提供了对象只读的访问权限,并不限制调用其他成员函数的类型;而非 const 成员函数可以调用任何类型的成员函数。

在这个示例中,callNonConstFromConst()函数是一个const成员函数,它调用了非const成员函数nonConstFunc(),这是允许的。但是,如果你尝试在非const成员函数中调用const成员函数(例如callConstFromNonConst()),编译器将会报错。

简单理解就是:权限问题,const非const,权限放大,是肯定不行的;反过来权限缩小,是可以的。

好啦,这篇文章就到此结束了
所以你学会了吗?

好啦,本章对于《C++:运算符重载和“const”成员》的学习就先到这里,如果有什么问题,还请指教指教,希望本篇文章能够对你有所帮助,我们下一篇见!!!

如你喜欢,点点赞就是对我的支持,感谢感谢!!!

请添加图片描述


http://www.ppmy.cn/news/1433187.html

相关文章

分类预测 | Matlab实现RIME-BP霜冰优化BP神经网络多特征分类预测

分类预测 | Matlab实现RIME-BP霜冰优化BP神经网络多特征分类预测 目录 分类预测 | Matlab实现RIME-BP霜冰优化BP神经网络多特征分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.RIME-BP霜冰优化BP神经网络多特征分类预测&#xff08;Matlab实现完整源码和数据&a…

python模式设计之责任链模式

责任链模式是一种行为设计模式&#xff0c;它允许对象在链中依次处理请求&#xff0c;直到有一个对象能够处理它为止。 在责任链模式中&#xff0c;一系列对象按照其顺序组成一个链。当请求进入该链时&#xff0c;每个对象都会依次尝试处理请求。如果某个对象可以处理请求&…

309. 买卖股票的最佳时机含冷冻期

309. 买卖股票的最佳时机含冷冻期 题目链接&#xff1a;309. 买卖股票的最佳时机含冷冻期 代码如下&#xff1a; /* 转移方程&#xff1a;dp[i][0] max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i]);dp[i][1] max(dp[i - 1][1], dp[i - 1][3]);dp[i][2] d…

【机器学习入门:理解Scikit-learn与Python的关系】

文章目录 前言Python与机器学习Scikit-learn简介Scikit-learn与Python的关系使用Scikit-learn进行机器学习结语 前言 在当今的数据科学和人工智能领域&#xff0c;机器学习已经成为了一个不可或缺的组成部分。而对于那些刚刚踏入这一领域的新手来说&#xff0c;理解机器学习的基…

Git和Github绑定

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

SpringCloud系列(11)--将微服务注册进Eureka集群

前言&#xff1a;在上一章节中我们介绍并成功搭建了Eureka集群&#xff0c;本章节则介绍如何把微服务注册进Eureka集群&#xff0c;使服务达到高可用的目的 Eureka架构原理图 1、分别修改consumer-order80模块和provider-payment8001模块的application.yml文件&#xff0c;使这…

linq select 和selectMany的区别

Select 和 SelectMany 都是 LINQ 查询方法&#xff0c;但它们之间有一些区别。 Select 方法用于从集合中选择特定的属性或对集合中的元素进行转换&#xff0c;并返回一个新的集合。例如&#xff1a; var numbers new List<int> { 1, 2, 3, 4, 5 }; var squaredNumbers…

在 Windows 系统上彻底卸载 TeamViewer 软件

在 Windows 系统上彻底卸载 TeamViewer 软件 References 免费版仅供个人使用 您的会话将在 5 分钟后终止 Close TeamViewer by locating the TeamViewer icon in the system tray, right click and “Exit TeamViewer”. Right click Windows start menu then Control Panel -…