CD19.【C++ Dev】类和对象(10) 日期类对象的成员函数(日期+天数)

目录

日期+天数

需要考虑的几个问题

1.天数加在日上,有可能会溢出,需要进位

2.对月进位,也有可能导致月会溢出,需要进位

3.对年进位,需要考虑是否为闰年

代码设计

取得指定月的天数GetMonthDay函数

方法1:if判断或switch/case

方法2:查表

版本1

版本2

operator+

初始化临时对象

进位的处理

完整代码

测试代码

提问

operator+=

完整代码

测试代码

代码复用

1.+复用+=

2.+=复用+

提问

+复用+=调用拷贝构造函数次数如下:

+=复用+调用拷贝构造函数次数如下:

练习


日期+天数

日期+天数:显然需要重载"+",操作:?年?月?日+天数==?年?月?日

需要考虑的几个问题

1.天数加在日上,有可能会溢出,需要进位

2.对月进位,也有可能导致月会溢出,需要进位

3.对年进位,需要考虑是否为闰年

闰年判断参见15.【C语言】初识操作符 下文章

口诀:四年一闰,百年不闰,四百年又闰

if ((year%4==0 && year%100!=0) || (year%400==0))
{//do_something
}

代码设计

取得指定月的天数GetMonthDay函数

天数加在日上,有可能会溢出,需要进位则需要考虑每一个月最多多少天,而且二月份比较特殊(闰年2月有29天,非闰年2月有28天),可以写一个函数来取得每个月的天数

方法1:if判断或switch/case
if (month==1)
{//......
}
else if (month==2)
{//......
}
else if (month==3)
{//......
}
//.......
else
{//......
}

或者使用switch/case

switch (month)
{case 1:case 2://......case 3:
}

无论使用if还是switch/case,代码冗长,可读性差

方法2:查表

这个思想其实是哈希表思想

int day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//默认2月是28天,之后单独判断

(注:day[0]可以为任何数,仅起到占位的作用)

使用下标访问的方法:day[month]来取得指定月的天数

版本1
int GetMonthDay(int year,int month)
{int day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//默认2月是28天,之后单独判断if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && month == 2){return 29;}return day[month];
}

提问:版本1代码有没有可以优化的地方?

答:

优化1:day数组为局部数组,每次调用该函数,程序都会创建day数组,如果多次调用,该步骤会消耗大量时间,可以尝试使用全局数组或者将数组放到静态区上

优化2:if判断的改进

可以利用短路运算的特点,将第二个条件month==2前置,月份不为2就不判断是否为闰年,节省时间

版本2
int GetMonthDay(int year,int month)//可以不使用引用返回
{//数组放到静态区上,只创建一次static int day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//默认2月是28天,之后单独判断if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){return 29;}return day[month];
}

operator+

算法:天满了向月进位,月满了向年进位.显然需要使用循环

要点:

1.先加到天上,再考虑进位的问题

2.operator+是不能改变原对象的,需要使用临时对象tmp,一旦使用临时对象tmp就不能引用返回,只能传值返回(避免传引用返回错误,牺牲一点时间)

3.区分operator+和operator+=

初始化临时对象

可以使用构造函数来初始化tmp,如下:

Date Date::operator+(int day)
{Date tmp(*this);tmp._day += day;//......
}
进位的处理
while (tmp._day > GetMonthDay(tmp._year,tmp._month))
{tmp._day -= GetMonthDay(tmp._year,tmp._month);tmp._month++;if (_month == 13){tmp._year++;tmp._month = 1;}
}
完整代码
Date Date::operator+(int day)
{Date tmp(*this);tmp._day += day;while (tmp._day > GetMonthDay(tmp._year,tmp._month)){tmp._day -= GetMonthDay(tmp._year,tmp._month);tmp._month++;if (_month == 13){tmp._year++;tmp._month = 1;}}return tmp;//注意返回的不是*this!
}
测试代码
#include "Date.h"
int main()
{Date d1(2025, 3, 25);Date d2=d1 + 100;d2.Print();return 0;
}

运行结果:

提问

Date d2=d1 + 100;是初始化还是赋值?

回顾CD17.【C++ Dev】类和对象(8):赋值运算符文章的知识点:

1.赋值运算符重载:已经存在的两个对象之间的拷贝(如d3 = d1)

2.构造函数:用一个已经存在的对象去初始化另一个对象

显然Date d2=d1 + 100是构造函数执行的,因此为初始化

也可以手动打印测试信息:

修改构造函数和拷贝构造函数:

Date::Date(int year , int month, int day)
{cout << this <<" Date::Date(int year , int month, int day)" << endl;_year = year;_month = month;_day = day;
}Date::Date(const Date& x)
{cout << this<< " Date::Date(const Date& x)" << endl;_year = x._year;_month = x._month;_day = x._day;
}

调用哪个函数就打印哪个函数的信息

测试以下代码:

#include "Date.h"
int main()
{Date d1(2025, 3, 25);cout << "d1 address:"<< & d1 << endl;Date d2 = d1 + 100;cout <<"d2 address:"<< &d2 << endl;return 0;
}

运行结果:

初始化d2时调用的是拷贝构造函数,因为拷贝构造也是构造!

operator+=

可以在operator+的基础上进行代码复用(代码复用的思想参见CD18.【C++ Dev】类和对象(9)(声明和定义分离的写法以及代码复用)文章)

完整代码

按照之前在CD17.【C++ Dev】类和对象(8):赋值运算符文章中讲过复制运算符的格式,注意要返回*this,返回类型为Date&

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;
}
测试代码
#include "Date.h"
int main()
{Date d1(2025, 3, 25);d1 += 100;d1.Print();return 0;
}

运行结果是正确的:

提问

operator+=的代码真的没有问题吗?

答:其实仍然有问题.

使用下面这个测试代码:

#include "Date.h"
int main()
{Date d1(2025, 3, 25);d1 += -100;d1.Print();return 0;
}

 运行结果:日是负数,错误

修复

需要添加判断:加一个负数等于减这个数的相反数,需要调用operator-=,会在CD20.【C++ Dev】类和对象(11) 日期类对象的成员函数(++、--、日期-日期)文章实现

Date& Date::operator+=(int day)
{if (day < 0){*this -= -day;return *this;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;
}

代码复用

+和+=的代码有些重复,代码如下:

1.+复用+=

Date Date::operator+(int day)
{Date tmp(*this);tmp += day;return tmp;
}

2.+=复用+

Date& Date::operator+=(int day)
{*this = *this + day;return *this;
}

提问

对比以上两段代码,哪个写法更高效?

修改函数,打印测试信息:

Date::Date(int year , int month, int day)
{cout <<"构造函数:Date::Date(int year , int month, int day)" << endl;_year = year;_month = month;_day = day;
}Date::Date(const Date& x)
{cout << "拷贝构造函数:Date::Date(const Date& x)" << endl;_year = x._year;_month = x._month;_day = x._day;
}

测试代码(在没有优化的情况下,问一共拷贝对象几次?)

#include "Date.h"
int main()
{cout << "d1和d2初始化:" << endl;Date d1(2025, 3, 25);Date d2(1, 1, 1);cout << endl;cout << "d2=d1 + 100如下:" << endl;d2=d1 + 100;cout << endl;cout << "d1 += 100如下:" << endl;d1 += 100;return 0;
}
+复用+=调用拷贝构造函数次数如下:

可画过程图分析:

(注:Date tmp(*this)传的是*this,为Date::Date(const Date& x)需要的参数) 

结果:拷贝对象一共两次 

测试代码执行结果:

(VS显示只调用拷贝对象一次的原因:编译器做了优化)

 如果在MSDOS下的老编译器Tubro C++上测试:

#include <iostream.h>
#include <conio.h>
class Date
{
public:int GetMonthDay(int year,int month){static int day[] = { 0,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;}return day[month];}Date (int year , int month, int day){cout << this <<"Date (int year , int month, int day)" << endl;_year = year;_month = month;_day = day;}Date (const Date& x){cout << "Date(const Date& x)" << endl;_year = x._year;_month = x._month;_day = x._day;}Date operator+(int day){Date tmp(*this);tmp += day;return tmp;}Date& operator+=(int day){_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;}int _year;int _month;int _day;
};int main()
{cout << "Initializing d1 and d2......" << endl;Date d1(2025, 3, 25);Date d2(1, 1, 1);cout << endl;cout << "d2=d1 + 100:" << endl;d2=d1 + 100;cout << endl;cout << "d1 += 100:" << endl;d1 += 100;getch();return 0;
}

(旧编译器下头文件是.h结尾的,最后的getch()是为了让运行临时暂停,好查看结果)  

运行结果:

(没有优化,一共拷贝对象2次)  

+=复用+调用拷贝构造函数次数如下:

可画过程图分析:

结果:拷贝对象一共四次

测试代码执行结果:

(VS显示只调用拷贝对象两次的原因:编译器做了优化)  

如果在MSDOS下的老编译器Tubro C++上测试:

#include <iostream.h>
#include <conio.h>
class Date
{
public:int GetMonthDay(int year,int month){static int day[] = { 0,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;}return day[month];}Date (int year , int month, int day){cout << this <<"Date (int year , int month, int day)" << endl;_year = year;_month = month;_day = day;}Date (const Date& x){cout << "Date(const Date& x)" << endl;_year = x._year;_month = x._month;_day = x._day;}Date operator+(int day){Date tmp(*this);tmp._day += day;while (tmp._day > GetMonthDay(tmp._year,tmp._month)){tmp._day -= GetMonthDay(tmp._year,tmp._month);tmp._month++;if (tmp._month == 13){tmp._year++;tmp._month = 1;}}return tmp;}Date& operator+=(int day){*this = *this + day;return *this;}int _year;int _month;int _day;
};int main()
{cout << "Initializing d1 and d2......" << endl;Date d1(2025, 3, 25);Date d2(1, 1, 1);cout << endl;cout << "d2=d1 + 100:" << endl;d2=d1 + 100;cout << endl;cout << "d1 += 100:" << endl;d1 += 100;getch();return 0;
}

运行结果是这样的: 

(没有优化,一共拷贝对象4次)

练习

日期-日期问题:

https://leetcode.cn/problems/number-of-days-between-two-dates/

下篇博客将分析此题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/42913.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零构建大语言模型全栈开发指南:第二部分:模型架构设计与实现-2.2.3实战案例:在笔记本电脑上运行轻量级LLM

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 实战案例:在笔记本电脑上运行轻量级LLM2.2.3 模型架构设计与实现1. 环境与工具准备1.1 硬件要求1.2 软件栈选择2. 轻量级模型架构设计2.1 模型参数配置2.2 关键技术优化3. 实战流程3.1 数据准备流程3.2…

新手村:逻辑回归-理解04:熵是什么?

新手村&#xff1a;逻辑回归04&#xff1a;熵是什么? 熵是什么? 前置条件 在开始学习逻辑回归中的熵理论之前&#xff0c;需要掌握以下基础知识&#xff1a; 概率论与统计学&#xff1a; 概率分布&#xff08;如伯努利分布、正态分布&#xff09;。条件概率和贝叶斯定理。期…

linux》》docker 、containerd 保存镜像、打包tar、加载tar镜像

Linux》》docker: 默认情况下&#xff0c;Docker镜像保存在/var/lib/docker/目录下。 当您使用docker pull命令从Docker Hub或私有镜像仓库中拉取镜像时&#xff0c;Docker会自动将镜像文件保存在/var/lib/docker/image/目录下。 每个镜像都由一个或多个层组成&#xff0c;这些…

Processor System Reset IP 核 v5.0(vivado)

这个IP的作用&#xff0c;我的理解是&#xff0c;比普通按键复位更加高效灵活&#xff0c;可以配置多个复位输出&#xff0c;可以配置复位周期。 1、输入信号&#xff1a; 重要的信号有时钟clk信号&#xff0c;一般连接到系统时钟&#xff1b;输入复位信号&#xff0c;一般是外…

SQL中累计求和与滑动求和函数sum() over()的用法

[TOC](SQL中累计求和与滑动求和函数sum() over()的用法) 一、窗口函数功能简介 sum(c) over(partition by a order by b) 按照一定规则汇总c的值&#xff0c;具体规则为以a分组&#xff0c;每组内按照b进行排序&#xff0c;汇总第一行至当前行的c的加和值。 sum()&#xff1a…

优雅的开始一个Python项目

优雅的开始一个Python项目 这是我在初始化一个Python项目时&#xff0c;一键生成的项目文件。它自动完成了git初始化、环境管理、日志模块这三件事情&#xff0c;并在最后进入了虚拟环境。 uv安装 uv是一个现代的Python包管理和项目管理工具。uv中文文档 安装uv: # unix: …

【Django】教程-2-前端-目录结构介绍

【Django】教程-1-安装创建项目目录结构介绍 3. 前端文件配置 3.1 目录介绍 在app下创建static文件夹, 是根据setting中的配置来的 STATIC_URL ‘static/’ templates目录&#xff0c;编写HTML模板&#xff08;含有模板语法&#xff0c;继承&#xff0c;{% static ‘xx’ …

Vue 2 探秘:visible 和 append-to-body 是谁的小秘密?

&#x1f680; Vue 2 探秘&#xff1a;visible 和 append-to-body 是谁的小秘密&#xff1f;&#x1f914; 父组件&#xff1a;identify-list.vue子组件&#xff1a;fake-clue-list.vue 嘿&#xff0c;各位前端探险家&#xff01;&#x1f44b; 今天我们要在 Vue 2 的代码丛林…

docker-compese 启动mysql8.0.36与phpmyadmin,并使用web连接数据库

1、找一个文件夹&#xff0c;比如 E:\zqy\file\mysql&#xff0c;cd到这个目录下创建文件docker-compose.yml 2、将下面的代码块复制到docker-compose.yml文件中 version: 3.3 services:mysql:image: mysql:8.0.36container_name: mysqlrestart: alwaysports:- 3306:3306netw…

【Java SE】包装类 Byte、Short、Integer、Long、Character、Float、Double、Boolean

参考笔记&#xff1a;java 包装类 万字详解&#xff08;通俗易懂)_java包装类-CSDN博客 目录 1.简介 2.包装类的继承关系图 3.装箱和拆箱 3.1 介绍 3.2 手动拆装箱 3.3. 自动拆装箱 ​4.关于String类型的转化问题 4.1 String类型和基本类型的相互转化 4.1.1 String —…

《网络安全等级测评报告模版(2025版)》

网络安全等级保护&#xff08;以下简称“等保测评”&#xff09;制度是我国网络安全领域现行的基本制度。所谓等保测评&#xff0c;即具有资质的测评机构&#xff0c;依据国家网络安全等级保护规范规定&#xff0c;按照有关管理规范和技术标准&#xff0c;对等保对象&#xff0…

【零基础入门unity游戏开发——2D篇】2D物理系统 —— 2DEffector效应器

考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、流程控制、面向对象等,适合没有编程基础的…

从零开始:使用Luatools工具高效烧录Air780EPM核心板项目的完整指南

本文将深入讲解如何使用Luatools工具烧录一个具体的项目到Air780EPM开发板中。如何使用官方推荐的Luatools工具&#xff08;一款跨平台、命令行驱动的烧录利器&#xff09;&#xff0c;通过“环境配置→硬件连接→参数设置→一键烧录”四大步骤&#xff0c;帮助用户实现Air780E…

SpringMVC的请求与响应

SpringMVC的请求与响应 SpringMVC请求处理流程请求映射和参数绑定RequestMapping注解RequestMapping的属性RequestMapping的请求参数绑定HTML代码JavaBean代码controller代码在控制器中使用原生的ServletAPI对象 SpringMVC响应数据处理及跳转结果跳转方式ModelAndViewServletAP…

【QT继承QLabel实现绘制矩形、椭圆、直线、多边形功能,并且支持修改大小,移动位置,复制,粘贴,删除功能】

文章目录 介绍绘制一个矩形&#xff08;椭圆&#xff09;roi绘制一个多边形roi对矩形roi的缩放&#xff1a;对多边形rio的缩放&#xff08;移动点的位置&#xff09; 介绍 绘制矩形&#xff0c;椭圆&#xff0c;直线实际用的都是是同一个思路&#xff1a;鼠标第一次点击就确定…

3.0 Disruptor的使用介绍(一)

Disruptor: 其官网定义为&#xff1a;“A High Performance Inter-Thread Messaging Library”&#xff0c;即&#xff1a;线程间的高性能消息框架&#xff0c;与Labview的生产者、消费者模型很相似。 其组成部分比较多&#xff0c;先介绍几个常用的概念&#xff1a; …

ubuntu 2204键盘按键映射修改

键盘的按键和实际输出不一致&#xff0c;可以通过以下方法重新修改按键keycode. 1.在终端执行如下命令&#xff1a; xev -event keyboard 上边64是alt_l&#xff0c;但是键盘上对应的super(windows)。 2.vim /usr/share/X11/xkb/keycodes/evdev //<LALT> 64; 注释&l…

【Linux】System V共享内存:零拷贝加速进程通信!

引言 本文深入探讨System V IPC中的共享内存技术&#xff0c;涵盖其原理、操作步骤、实现细节及与其他IPC机制的关系&#xff0c;助力读者全面掌握这一高效进程间通信方式。 &#x1f4dd; 文章总结&#xff1a; 共享内存原理 System V共享内存通过让多个进程共享同一物理内存区…

UE4学习笔记 FPS游戏制作31 显示计分板

一 制作计分板 创建一个RankPanel的UI蓝图 在蓝图里拖入如下物体 覆层&#xff08;layout&#xff09;&#xff1a;让子物体跟随自己缩放&#xff0c;子物体需要设置为拉伸模式&#xff0c;有点类似于的panel&#xff0c;本身只是一个容器 调整各个物体 覆层&#xff1a; 锚…

如何在Linux CentOS上安装和配置Redis

如何在Linux CentOS上安装和配置Redis 大家好&#xff0c;我是曾续缘。欢迎来到本教程&#xff01;今天我将向您介绍在Linux CentOS上安装和配置Redis的详细步骤。Redis是一个高性能的键值存储系统&#xff0c;常用于缓存、消息队列和数据持久化等应用场景。让我们一起开始吧&…