C++初阶——类和对象(3)赋值/运算符重载

article/2024/4/20 16:15:36

 

目录

 5.赋值运算符重载

5.1 运算符重载

5.2 赋值运算符重载

5.3 前置++和后置++重载

6.日期类的实现——流插入,流提取重载

Date.h:

Date.cpp:

7.const成员

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


 5.赋值运算符重载

5.1 运算符重载

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

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

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

注意:

  1.         不能通过连接其他符号来创建新的操作符:比如operator@
  2.         重载操作符必须有一个类类型参数
  3.         用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  4.         作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5.         .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
// 全局的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(2023, 9, 26);Date d2(2023, 9, 26);cout << (d1 == d2) << endl;cout << operator==(d1, d2) << endl;
}
int main()
{Test();return 0;
}
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){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2023,12);Date d2(2023, 12);cout << (d1 == d2) << endl;return 0;
}

5.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){_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;}
private:int _year;int _month;int _day;
};

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

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 =”必须是非静态成员

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

 3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

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

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然 像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
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;
}

此时,不写赋值重载,s1 和 s2指向同一块空间,修改的也是同一块空间,析构会释放同一块空间两次,程序崩溃

5.3 前置++和后置++重载

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

6.日期类的实现——流插入,流提取重载

Date.h:

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;class Date
{//友元函数——这个函数内部可以使用Date对象访问私有保护的成员friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
public:// 全缺省的构造函数_构造函数会被频繁调用,所以直接放在类里面定义作为内联函数Date(int year = 1,int month = 1,int day = 1){_year = year;_month = month;_day = day;//判断日期合法性if (!CheckDate()){Print();cout << "Bogus date!!!" << endl;}assert(CheckDate());}//拷贝构造函数//如果不写,编译器默认生成拷贝构造函数,拷贝构造函数内置类型按照字节方式进行拷贝,而自定义类型是调用其拷贝构造函数完成拷贝的。//Date(const Date& d)//{//	_year = d._year;//	_month = d._month;//	_day = d._day;//}//析构函数//如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数//~Date()//{//}//判断是否为闰年,频繁调用,定义成内联bool IsLeapYear(int year){return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));}// 获取某年某月的天数,频繁调用,定义成内联int GetMonthDay(int year, int month){//频繁查询天数,使用静态,不用频繁开辟空间static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && IsLeapYear(year)){return days[month] + 1;}return days[month];}//判断日期合法性bool CheckDate(){if (_year >= 1 && _month > 0 && _month < 13&& _day>0 && _day <= GetMonthDay(_year, _month)){return true;}return false;}// >运算符重载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);// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)Date& operator=(const Date& d);// 日期+=天数Date& operator+=(int day);// 日期+天数Date operator+(int day);// 日期-天数Date operator-(int day);// 日期-=天数Date& operator-=(int day);// 前置++Date& operator++();// 后置++Date operator++(int);// 后置--Date operator--(int);// 前置--Date& operator--();//转换为天数int ChangeDays(int year);//日期减日期 —— 运算符重载,和函数重载,此时是函数重载int operator-(const Date& d);/*void operator<<(ostream& out);*/void Print();
private:int _year;int _month;int _day;
};//流插入重载
inline ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "/" << d._month << "/" << d._day << endl;return out;
}
//流提取重载
inline istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._day >> d._day;assert(d.CheckDate());return in;
}

Date.cpp:

#include"Date.h"//任何一个类,只需要写一个 > == 的重载,或者 <  ==,剩下的运算符复用即可// >运算符重载
bool Date::operator>(const Date& d)
{if (_year == d._year){if (_month == d._month){return _day > d._day;}return _month > d._month;}return _year > d._year;
}
// ==运算符重载
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);
}// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{//不要调用对象判断,对象判断需要调用重载运算符,代价大if (this != &d){this->_day = d._day;this->_month = d._month;this->_year = d._year;}return (*this);
}
// 日期+=天数
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_month = 1;_year++;}}return (*this);
}
// 日期+天数
Date Date::operator+(int day)
{Date newDate(*this);return newDate += day;
}
// 日期-天数
Date Date::operator-(int day)
{Date newDate(*this);return newDate -= day;
}
// 日期-=天数
Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){if (_month == 1){_month = 12;_year--;}else{_month--;}_day += GetMonthDay(_year,_month);}return (*this);
}// 前置++
Date& Date::operator++()
{return *this += 1;
}
// 后置++
Date Date::operator++(int)
{Date temp(*this);*this += 1;return temp;
}
// 后置--
Date Date::operator--(int)
{Date temp(*this);*this -= 1;return temp;
}
// 前置--
Date& Date::operator--()
{return *this -= 1;
}//转换为天数
int Date::ChangeDays(int year)
{int sumDay = _day;for (int i = 1; i < _month; i++){sumDay += GetMonthDay(_year,i);}while (_year > year){_year--;if (IsLeapYear(_year)){sumDay += 366;}elsesumDay += 365;}return sumDay;
}//日期减日期 —— 运算符重载,和函数重载,此时是函数重载
int Date::operator-(const Date& d)
{int year = 0;Date d1(*this);Date d2(d);return abs(d1.ChangeDays(d1._year) - d2.ChangeDays(d1._year));
}//日期间日期,方法二
//int Date::operator-(const Date& d)
//{
//	Date max = *this;
//	Date min = d;
//	if (*this < d)
//	{
//		max = d;
//		min = *this;
//	}
//	int n = 0;
//	while (min != max)
//	{
//		++min;
//		++n;
//	}
//	return n;
//}void Date::Print()
{cout << _year << "/" << _month << "/" << _day << endl;
}//void Date::operator<<(ostream& out)
//{
//	out << _year << "/" << _month << "/" << _day << endl;
//}

运算符重载:让自定义类型可以用运算符,转换成调用这个重载函数

函数重载:支持函数名相同的函数同时存在

虽然都用了重载这个词,但是它们之间互相独立,没有必然联系

7.const成员

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

看下面的代码:

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

请思考下面的几个问题:

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

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

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

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

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


http://www.ngui.cc/article/show-1007683.html

相关文章

建堆、堆排序、TopK问题大合集

一、如何建堆 1、向上调整建堆法O(NlogN) 原理&#xff1a; 利用向上调整的方法进行建堆&#xff0c;是通过模仿之前堆的插入操作&#xff0c;从第二个数开始&#xff0c;每次插入一个数&#xff0c;就对这个数进行向上调整&#xff0c;这样子既保证了原有数据为堆&#xff…

测试开发进阶系列课程

测试开发系列课程1.完善程序思维--------案列&#xff1a;图书管理系统的创建**&#xff08;一&#xff09;图书管理系统的创建**1.完善程序思维--------案列&#xff1a;图书管理系统的创建 &#xff08;一&#xff09;图书管理系统的创建 1.在main中写入主函数&#xff0c;…

数位DP算法学习总结

一、数位dp简述模板数位dp是一种计数时使用的动态规划算法&#xff0c;一般是要统计一个区间 [left, right] 内符合给定条件数字的个数&#xff0c;例如HDU 2089 不要62中的统计给定区间内不包含4以及62数字的个数&#xff0c;数位dp其实是暴力枚举算法的优化&#xff0c;通过过…

添加Anaconda Powershell Prompt到右键

想要使用Anaconda Powershell Prompt每次还要去开始菜单打开&#xff0c;而且还要切换到特定目录下&#xff0c;十分麻烦。通过将Anaconda Powershell Prompt添加到鼠标右键&#xff0c;可在当前目录十分方便的打开Anaconda Powershell Prompt。步骤如下&#xff1a; 1. 首先开…

Java_Spring:4. 使用 spring 的 IoC 的实现CRUD【案例】

目录 1 需求和技术要求 1.1 需求 1.2 技术要求 2 环境搭建 2.1 拷贝 jar 包 2.2 创建数据库和编写实体类 2.3 编写持久层代码 2.4 编写业务层代码 2.5 创建并编写配置文件 3 配置步骤 4 测试案例 4.1 测试类代码 4.2 分析测试了中的问题 1 需求和技术要求 1.1 需求…

Spring - Spring 注解相关面试题总结

文章目录01. Spring 配置方式有几种&#xff1f;02. Spring 如何实现基于xml的配置方式&#xff1f;03. Spring 如何实现基于注解的配置&#xff1f;04. Spring 如何基于注解配置bean的作用范围&#xff1f;05. Spring Component, Controller, Repository, Service 注解有何区别…

2023-3-25 java选择题每日一练

继承中类, 静态代码块, 实例代码块和构造方法的执行顺序其原理如下:当没有子类继承的时候顺序&#xff1a;静态代码块 → main → 构造代码块 → 构造方法public class Test {static{System.out.println("父类静态代码块开始执行&#xff01;");}{System.out.println…

【WMS学习】从悬浮窗的添加来看窗口的add和update

这里我们从一个悬浮窗应用来查看WindowManager的addView使用&#xff0c;从这里作为突破口来认识窗口的添加&#xff0c;和窗口的位置大小更新方法updateViewLayout&#xff0c;使用WindowManager的addView方法来添加窗口非常的直观&#xff0c;因为Activity的显示中&#xff0…

领域驱动设计(Domain-Driven Design, DDD)

领域驱动设计&#xff08;Domain Driven Design&#xff0c;简称DDD&#xff09;是一种面向对象软件开发方法&#xff0c;它强调将软件系统的设计和实现过程与业务领域紧密结合&#xff0c;通过深入理解和建模业务领域&#xff0c;从而达到高内聚、低耦合的目的。 领域驱动设计…

【ChatGPT】比尔·盖茨最新分享:ChatGPT的发展,不止于此

✅作者简介&#xff1a;在读博士&#xff0c;伪程序媛&#xff0c;人工智能领域学习者&#xff0c;深耕机器学习&#xff0c;交叉学科实践者&#xff0c;周更前沿文章解读&#xff0c;提供科研小工具&#xff0c;分享科研经验&#xff0c;欢迎交流&#xff01;&#x1f4cc;个人…