lesson03:类和对象(中)续

devtools/2024/9/23 1:08:42/

1.运算符重载

2.const成员函数

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

1.运算符重载

1.1运算符重载

c++为了增强代码的可读性,引入了运算符重载运算符重载函数是具有特殊函数名的函数

函数名:关键字operator后面接需要重载的运算符符号

函数原型:返回值类型 operator运算符(参数列表)

注意:

1.只能重载c++中已存在的操作符,不能创建新的操作符,例如operator@

2.运算符重载函数必须有一个类类型参数,可以是类类型的引用(类指针类型是不可以的,this是个特例,成员函数隐式类指针类型形参是可以的)

3.以下5种运算符不能重载

.*

::

sizeof

?:

.

4.运算符重载函数的形参必须要按顺序和运算符的操作数一一对应,所以操作数个数不能变

5.重载的操作符将继承原操作符的优先级结合性

6.注意:类的成员函数的第一个参数是this,因为这个this看不见,所以成员函数的形参看起来比所重载的操作符的操作数的数目少一,例如加操作符,它是双目操作符,有两个操作数,但是只显式写1个形参。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
// 全局的operator==
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()
{Test();return 0;
}

这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?

解答:这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 4, 21), d2(2024, 4, 21);cout << (d1 == d2);return 0;
}

7.运算符重载函数也可以以函数的形式使用。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 4, 21), d2(2024, 4, 21);cout << (d1.operator==(d2));return 0;
}

但是一般不会这么写!!!

1.2赋值运算符重载

1.为了支持连续赋值,我们要将被赋值的操作数(也就是左操作数)作为返回值,所以返回*this

2.由于拷贝对象会使效率降低,所以返回值类型写操作数所对应的类类型的引用,形参也应该写操作数所对应的类类型的引用。

3.为了预防自己给自己赋值,造成不必要的消耗,我们用if语句检查,发现是自己给自己赋值,就直接返回。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}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;}
private:int _year;int _month;int _day;
};
int main()
{Date d(2024, 4, 21);Date d1;d1 = d; //d1.operator=(d); 也可以这么写d.print();return 0;
}

4.赋值运算符只能重载成类的成员函数不能重载成全局函数

原因:如果在类内不显式定义赋值运算符重载函数,那么编译器会生成默认的赋值运算符重载函数,会产生冲突。

这种冲突实际上是可以避免的,但是重载的赋值运算符的两个操作数应该是两个来自同一个类的对象, 如果写成别的就没什么意义的,并不会出现这样的场景,编译器干脆禁止了这种行为!

5.用户没有显式定义时,编译器会生成一个默认的赋值运算符重载函数,以值的方式逐字节拷贝(类似于默认的拷贝构造函数),内置类型成员变量直接赋值,而自定义类型的成员变量需要调用对应类的赋值运算符重载完成赋值。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
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; 注意:不能这么写,这是拷贝构造!!!Date d2;d1 = d2;return 0;
}

编译器默认生成的赋值运算符重载函数可以进行值拷贝,一般情况下,只需要值拷贝的场景下,可以直接用默认的。

但是,涉及到深拷贝就不行了。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class int_arr
{
public:int* arr = (int*)malloc(sizeof(int));
};
int main()
{int_arr arr1, arr2;arr1 = arr2;arr1.arr[0] = 5;cout << arr2.arr[0];return 0;
}

这种情况下需要深拷贝,浅拷贝是不可以的,例如上面的代码,改变对象arr1中的数据也改了arr2中的数据,这不是我们想看到的。

注意:如果类中涉及资源管理,一定不能用浅拷贝。

1.3前置++和后置++

前置++和后置++都是单目运算符,而且两操作符写法一样,需要采用特殊方法区分

不做处理的就是前置++的重载

在小括号里加int的是后置++的重载

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
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;
}

2.const成员函数

我们知道,成员函数的第一个参数是看不见的this,它的类型是T*const(这里的T是该成员所对应的类的名字),那么,有没有什么办法在T前面加个const修饰,是的this的类型变为const T*const,从而使得this指向的内容无法改变(通过this访问)?

解答:在成员函数的括号的右边加const

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
private:int _year = 0;int _month = 0;int _day = 0;
public:void print()const{_year = 2024;cout << _year << ' ' << _month << ' ' << _day << endl;}
};
int main()
{Date d;d.print();return 0;
}

这里报错了,说明达到效果了。

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

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

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class Date
{
public:Date* operator&(){return this;}const Date* operator&()const{return this;}
};
int main()
{Date d;cout << &d << endl;const Date d1;cout << &d1;return 0;
}

只有特殊情况,才需要重载,比如想让别人取地址时获取其他指定地址


http://www.ppmy.cn/devtools/9425.html

相关文章

代码随想录算法训练营第四十八天| 198.打家劫舍,213.打家劫舍II,337.打家劫舍III

题目与题解 198.打家劫舍 题目链接&#xff1a;198.打家劫舍 代码随想录题解&#xff1a;​​​​​​​198.打家劫舍 视频讲解&#xff1a;动态规划&#xff0c;偷不偷这个房间呢&#xff1f;| LeetCode&#xff1a;198.打家劫舍_哔哩哔哩_bilibili 解题思路&#xff1a; 这道…

GLID: Pre-training a Generalist Encoder-Decoder Vision Model

1 研究目的 现在存在的问题是&#xff1a; 目前&#xff0c;尽管自监督预训练方法&#xff08;如Masked Autoencoder&#xff09;在迁移学习中取得了成功&#xff0c;但对于不同的下游任务&#xff0c;仍需要附加任务特定的子架构&#xff0c;这些特定于任务的子架构很复杂&am…

Lua语言 备查

开发工具 1.SciTE &#xff08;入门级&#xff09; IDEA lua &#xff08;商业级&#xff09; 基础 注释 --单行注释 a "hello" --a是一个标识符号--多行注释 --[[这是多行注释]]--连接符 -- .. str 123..456变量 变量类型可以随意改变 数字开头会报错 五种基本…

自己写的加密案例4——某东方课程头部sign加密

网址&#xff1a;aHR0cHM6Ly9kc2FwaS54ZGYuY24vcHJvZHVjdC92Mi9jbGFzcy9zZWFyY2g 进行抓包分析&#xff0c;发现请求头中由sign加密&#xff0c;简单判断是消息摘要算法。 Sign:d7c68100ca508bb7c8ae284560754303 进行xhr断点&#xff0c;一下子就发现了位置。 s c.sign&…

【MySQL面试题pro版-6】

MySQL是一个关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。MySQL是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的RDBMS (Relational Database Management System&#xff0c;关系数据…

C语言C++面试题 (包答案)

1 变量的声明和定义有什么区别 变 量 的 定 义 为 变 量 分 配 地 址 和 存 储 空 间 &#xff0c; 变 量 的 声 明 不 分 配 地 址 。 一 个 变 量 可 以 在 多 个 地 方 声 明 &#xff0c; 但 是 只 在 一 个 地 方 定 义 。 加 入 e x t e r n 修 饰 的 是 变 量 的 声 明…

vue-router学习2:路由导航方式

声明式导航 声明式导航方式在Vue Router中主要通过<router-link>组件来实现&#xff0c;它允许你直接在模板中创建导航链接&#xff0c;而无需编写额外的JavaScript代码。以下是一些常见的声明式导航方式及其示 1. 基本的导航链接 使用<router-link>组件&#x…

Unity实现关闭应用程序和关闭应用窗口

using System.Collections; using System.Collections.Generic; using UnityEngine; using System.Runtime.InteropServices; using System;public class WindowsClose : MonoBehaviour {// 声明需要使用的Windows API函数[DllImport("user32.dll", SetLastError tr…