当前位置: 首页 > news >正文

C++第五弹 -- 类与对象(中下) (赋值运算符重载函数 const成员函数 取地址操作符重载函数)

目录

  • 前言
  • 一. 赋值运算符重载
    • 1. 运算符重载
    • 2. 赋值运算符的重载
    • 3. 前置++ 和 后置++ 重载
  • 二. 日期类的实现
  • 三. const成员函数
  • 四. 取地址及const取地址操作符重载
  • 总结

前言

本文将深入探讨C++中的运算符重载,重点讲解赋值运算符、前置/后置++运算符、取地址运算符的重载方法,以及const成员函数的定义和使用方法。通过日期类的实现示例,展示运算符重载和const成员函数在实际应用中的具体代码实现,帮助读者更好地理解和运用这些C++特性。

博客主页: 酷酷学!!!

感谢关注~


正文开始

一. 赋值运算符重载

1. 运算符重载

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

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

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义(这样只是更加规范)
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
    现。 (. *运算符表示对象成员解引用操作,一般用作成员函数指针 )

例如:成员函数需要加&才能取到函数指针, 而普通函数函数名就是函数首地址

class OB
{
public:void func() {cout << "void func()" << endl;}
};typedef void(OB::*PtrFunc)() ;//成员函数指针类型//int main()
//{
//	// 函数指针
//	// void (*ptr)();
//
//	// 成员函数要加&才能取到函数指针
//	PtrFunc fp = &OB::func;//定义成员函数指针p指向函数func
//
//	OB temp;//定义ob类对象temp
//
//	(temp.*fp)();
//	
//
//	return 0;
//}

在这里插入图片描述

全局的运算符重载operator==


 重载成全局,无法访问私有成员
 1、提供这些成员get和set
 2、友元  后面会讲
 3、重载为成员函数(一般使用这种)
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
void Test()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;
}int main()
{Date d3(2024, 4, 14);Date d4(2024, 4, 15);// 显式调用operator==(d3, d4);// 直接写,装换调用,编译会转换成operator==(d3, d4);d3 == d4;//一般习惯上会用这种return 0;
}

这里会发现运算符重载成全局的就需要成员变量是公有的, 那么就违背了类的封装性, 这里其实我们可以使用友元, get/set函数, 但是最用的还是将它重载成成员函数

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* const this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date & d2)//const修饰d2的内容d2,其内容不可被修改, 传引用用于提高效率){return _year == d2._year;&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};int main()
{Date d3(2024, 4, 14);Date d4(2024, 4, 15);// 显式调用d3.operator==(d4);// 转换调用 等价于d3.operator==(d4);d3 == d4;int i = 0, j = 1;bool ret = i == j;return 0;
}

将它重载成成员函数时需要注意, 这里这里第一个参数为隐藏的this指针, 所以实际上我们只需要写一个参数位置就可以了.

2. 赋值运算符的重载

  1. 赋值运算符重载格式
  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义

代码如下:

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){cout << "Date(const Date& d)" << endl;_year = d._year;_month = d._month;_day = d._day;}// d1 = d4;// d1 = d2 = d4;// d1 = d1// Date operator=(const Date& d)/*Date& operator=(const Date& d)//我们可以看到这里我们需要将赋值后的对象进行返回才能进行连续赋值,那么是传引用还是传值呢?看下面分析{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}~Date(){cout << "~Date()" << endl;_year = -1;_month = -1;_day = -1;}

左: 如下面这两个函数, Date出了作用域就被销毁了 只能传值返回, 返回的是d的一份临时拷贝
右: 如果传引用返回,但是d已经被销毁了, 所以ref是一个随机值

在这里插入图片描述
传引用返回可以减少拷贝次数, 提高程序运行效率

在这里插入图片描述
在这里插入图片描述
这里Date在main函数中, 显然生命周期是整个程序, 所以传引用返回提高效率

  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

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

在这里插入图片描述

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

跟拷贝构造类似, Date或者MyQueue默认生成的赋值就够用了, 但是类似Stack/List等都需要我们自己实现赋值重载

3. 前置++ 和 后置++ 重载

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的结果// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率Date& operator++(){_day += 1;return *this;}// 后置++:// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1//       而temp是临时对象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);_day += 1;return temp;}
private:int _year;int _month;int _day;
};
int main()
{Date d;Date d1(2022, 1, 13);d = d1++;    // d: 2022,1,13   d1:2022,1,14d = ++d1;    // d: 2022,1,15   d1:2022,1,15return 0;
}

二. 日期类的实现

  • Date.h
#pragma once#include<iostream>
#include<assert.h>
using namespace std;class Date
{//友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);public://声明//构造函数需要写(析构函数,拷贝构造函数不需要写)Date(int year = 1900, int month = 1, int day = 1);void Print();//因为要重复使用,所以定义在类中,相当于内联函数//获取某年某月有多少天int GetMonthDay(int year, int month){assert(month > 0 && month < 13);static int Month[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };//静态变量声明周期为整个程序,不需要频繁创建销毁if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}else{return Month[month];}}bool operator<(const Date& d);bool operator<=(const Date& d);bool operator>(const Date& d);bool operator>=(const Date& d);bool operator==(const Date& d);bool operator!=(const Date& d);Date& operator+=(int day);Date operator+(int day);Date& operator-=(int day);Date operator-(int day);Date& operator=(const Date& d);Date& operator++();//前置++//返回+1之后的结果//注意:this指向的对象函数结束后不会被销毁,故以引用的方式返回提高效率Date operator++(int);//后置++//前置++和后置++都是一元运算符, 为了让前置++与后置++形参正确重载//C++规定:后置++重载时多增加一个int类型的参数, 但调用函数时该参数不用//传递,编译器自动传递//注意:后置++是先使用后+1,因此需要返回+1之后的旧值, 故需要在实现时//先将this保存一份,然后再给this+1,而tmp是临时对象,因此只能以值的方式//返回,不能返回引用bool CheckDate();Date& operator--();Date operator--(int);//d1 - d2int operator-(const Date& d);//满足连续调用的条件d1<<d2<<d3,故需要返回值,不会析构,引用返回ostream& operator<<(ostream& out);
private:int _year;int _month;int _day;
};//ostream& operator<<(ostream& out, const Date& d);

内联函数和static修饰的成员函数不具备外部链接属性,没有函数名修饰, 只能在内部使用, 而普通函数在.h文件中写之后, 会被展开到, test.cpp和Date.cpp会重复定义, 导致报错

  • Date.cpp

这里计算两个日期相加比较难算, 可以对比数的进位
在这里插入图片描述

#include"Date.h"bool Date::CheckDate()
{if (_month < 1 || _month>12 || _day<1|| _day > GetMonthDay(_year, _month))return false;else return true;
}Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;if (!CheckDate()){cout << "日期非法" << endl;}
}void Date::Print()
{cout << _year << "-" << _month << "-" << _day << endl;
}bool Date::operator<(const Date& d)
{if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month){return true;}else if (_month == d._month){return _day < d._day;}}return false;
}bool Date::operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}
//直接进行复用
bool Date::operator<=(const Date& d)
{return *this < d || *this == d;
}bool Date::operator>(const Date& d)
{return !(*this <= d);
}bool Date::operator>=(const Date& d)
{return !(*this < d);
}bool Date::operator!=(const Date& d)
{return !(*this == d);
}Date& Date::operator+=(int day)
{//进位实现_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){++_year;_month = 1;}}return *this;
}Date Date::operator+(int day)
{Date tmp = *this;tmp += day;return tmp;
}Date& Date::operator-=(int day)
{//比对加法实现_day -= day;while (_day < 1){_month--;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}Date Date::operator-(int day)
{Date tmp = *this;tmp -= day;return tmp;
}Date& Date::operator=(const Date& d)
{if (this != &d)//自己给自己赋值无意义{_year = d._year;_month = d._month;_day = d._day;}return *this;//将自己返回
}//++d1
Date& Date::operator++()
{*this += 1;return *this;
}//d1++
Date Date::operator++(int)
{Date tmp = *this;tmp += 1;return tmp;
}Date& Date::operator--()
{*this -= 1;return *this;
}Date Date::operator--(int)
{Date tmp = *this;tmp -= 1;return tmp;
}int Date::operator-(const Date& d)
{Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n * flag;
}//d1<<d2<<d3
//ostream& Date::operator<<(ostream& out)
//{
//	out << _year << "年" << _month << "月" << _day << "日" << endl;
//	return out;
//}ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}//但是这样写无法访问成员, 怎么办? 友元 或者 将成员置为public
//返回out的引用是将多个输出连接在一起,形成链式调用istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;if (!d.CheckDate()){cout << "日期非法" << endl;}return in;
}

在这里插入图片描述

  • test.cpp
#include"Date.h"int main()
{Date d1;Date d2(2024, 7, 11);int ret = d1 == d2;int ret1 = d1 < d2;int ret2 = d1 <= d2;cout << ret << ret1 << ret2 << endl;cout << d1;//这样写会报错,因为参数顺序不匹配d1 << cout;//为了解决这个问题,将cout重载写在类外return 0;
}

在这里插入图片描述

三. const成员函数

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

但是成员函数中, this指针变量已经被隐藏了, 怎么实现呢?

void Print() const //在函数旁边写const

注意 这里修饰的是this里面所指向的内容, 而编译器的this指针是修饰this指针本身

const Date* this 修饰内容
Date* const this 修饰this
而此处相当于双const
const Date* const this
在这里插入图片描述

如果对象是常量对象,则会调用带有const关键字的Print函数;
如果对象是非常量对象,则会调用不带const关键字的Print函数。
这种行为被称为"常量重载"。

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}/*void Print()//Date* const this{cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}*/void Print() const //const Date* const this{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()
{const Date d1(2022, 1, 13);//常量对象d1.Print();//调用没有const修饰的print涉及权限放大,报错, 调用有const修饰的print可以Date d2(2022, 1, 13);//非常量对象d2.Print();//调用没有const修饰的成员函数权限平移,可以,调用有const修饰的权限缩小,可以
}

四. 取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date
{
public:Date* operator&(){return this;}const Date* operator&()const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!

class A
{
public:// 我们不实现,编译器会自己实现,我们实现了编译器就不会自己实现了// 一般不需要我们自己实现// 除非不想让别人取到这个类型对象的真实地址A* operator&(){cout << "A* operator&()" << endl;return nullptr;}const A* operator&() const{cout << "const A* operator&() const" << endl;return (const A*)0xffffffff;}
private:int _a1 = 1;int _a2 = 2;int _a3 = 3;
};int main()
{A aa1;const A aa2;cout << &aa1 << endl;cout << &aa2 << endl;return 0;
}

总结

C++中的运算符重载可以增强代码可读性,提高代码效率。
赋值运算符重载只能是类成员函数,不能是全局函数。
前置/后置++运算符重载需要分别定义两个函数,前置++返回引用,后置++返回对象拷贝。
取地址运算符一般不需要重载,使用编译器生成的默认重载即可。
const成员函数修饰的是this指针,表示该函数不能修改类的成员变量。
const成员函数可以被常量对象调用,也可以被非常量对象调用。


完, 如有其他问题, 感谢各位楷模指出

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【LeetCode】633. 平方数之和
  • 设计模式Base
  • vue-路由自动化
  • Nginx-http_limit_req_module模块
  • docker基础知识以及windows上的docker desktop 安装
  • Unity实现安卓App预览图片、Pdf文件和视频的一种解决方案
  • SpringBootWeb 篇-入门了解 Swagger 的具体使用
  • 详细谈谈负载均衡的startupProbe探针、livenessProbe探针、readnessProbe探针如何使用以及使用差异化
  • 【深度学习】图形模型基础(5):线性回归模型第三部分:线性回归模型拟合
  • sqlmap使用之-post注入、head注入(ua、cookie、referer)
  • 【HarmonyOS】获取通讯录信息
  • hudi数据湖万字全方位教程+应用示例
  • 14-47 剑和诗人21 - 2024年如何打造AI创业公司
  • google Guava组件实现原理和Java使用场景【主要是本地缓存Cache】
  • 如何理解Node.js?NPM?Yarn?Vue?React?
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • interface和setter,getter
  • JAVA 学习IO流
  • JavaScript设计模式与开发实践系列之策略模式
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • php的插入排序,通过双层for循环
  • Python学习之路13-记分
  • Rancher如何对接Ceph-RBD块存储
  • REST架构的思考
  • Spring-boot 启动时碰到的错误
  • spring学习第二天
  • WebSocket使用
  • 编写高质量JavaScript代码之并发
  • 关于extract.autodesk.io的一些说明
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 如何在GitHub上创建个人博客
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • #if 1...#endif
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (Java入门)学生管理系统
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (附源码)php新闻发布平台 毕业设计 141646
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (南京观海微电子)——示波器使用介绍
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (原)Matlab的svmtrain和svmclassify
  • (杂交版)植物大战僵尸
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • .DFS.
  • .gitignore
  • .net core webapi 大文件上传到wwwroot文件夹
  • .net6Api后台+uniapp导出Excel
  • .net6使用Sejil可视化日志
  • .NET成年了,然后呢?
  • .Net调用Java编写的WebServices返回值为Null的解决方法(SoapUI工具测试有返回值)
  • .Net环境下的缓存技术介绍