6.类和对象(4)
文章目录
回顾
前面讲解了C++中类的三个关键成员函数:析构函数 用于对象销毁时释放资源(如动态内存),名称格式为~类名
;拷贝构造函数 通过同类对象初始化新对象,参数必须为引用以避免无限递归,默认浅拷贝需注意指针问题;赋值运算符重载 (operator=
)实现对象间赋值,需返回引用以支持连续赋值,并处理自赋值和深拷贝。核心思想是:涉及资源管理(如指针)的类必须自定义这三个函数,而简单类可依赖编译器默认实现,否则可能导致内存泄漏或重复释放。
主要介绍了C++中的默认构造函数及其特性。文章指出空类实际上包含6个默认成员函数,其中构造函数在对象实例化时自动调用,用于初始化对象而非创建对象。构造函数具有类同名、无返回值、支持重载等特点,且不能被设为私有。编译器生成的默认构造函数对内置类型无效,但会调用自定义类型的默认构造函数。C++11允许为内置类型成员提供默认值。重点阐释了默认构造函数的概念,包括无参、全缺省和编译器生成的构造函数都属于默认构造函数,且一个类只能有一个默认构造函数。文章还强调了默认构造函数与普通构造函数的区别,并提供了实践建议和使用注意事项。
本文围绕C++类和对象展开。介绍对象概念,对比C与C++编程范式;讲解类定义、访问限定、作用域、实例化;说明类存储大小含内存对齐,即便无成员变量也占1字节;还阐述this指针特性,它是成员函数隐式形参。
简单日期类的实现
日期类通过规范化调整(Adjust
函数)处理跨月/跨年边界,复用运算符实现高效日期计算(如+
复用+=
),支持完整的比较、算术和自增/自减操作,核心是闰年判断和月份天数计算。这份代码中还有一些没有提到的内容,后面会有笔记。
代码
头文件:
cpp
#pragma once
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, Date d);
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day) const;
// 日期-天数
Date operator-(int day) const;
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++(int);
// 后置++
Date operator++();
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
bool operator>(const Date& d) const;
// ==运算符重载
bool operator==(const Date& d) const;
// >=运算符重载
bool operator >= (const Date& d) const;
// <运算符重载
bool operator < (const Date& d) const;
// <=运算符重载
bool operator <= (const Date& d) const;
// !=运算符重载
bool operator != (const Date& d) const;
// 日期-日期 返回天数
int operator-(const Date& d) const;
//调整
void Adjust(Date& d);
//打印
void Print() const;
流插入
//void operator<<(ostream& out);
private:
int _year;
int _month;
int _day;
};
源文件:
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"
//判断闰年
bool IsLeapYear(int year)
{
if (((year % 4 == 0) &&
(year % 100 != 0)) || (year % 400 == 0))
{
return true;
}
return false;
}
//调整
void Date::Adjust(Date& d)
{
while (d._day > GetMonthDay(d._year, d._month))
{
d._day -= GetMonthDay(d._year, d._month);
d._month++;
if (d._month > 12)
{
d._month -= 12;
d._year++;
}
}
while (d._day <= 0)
{
//上一个月的天数
d._day += GetMonthDay(d._year, d._month - 1);
d._month--;
if (d._month <= 0)
{
d._month += 12;
d._year--;
}
}
}
// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
if (month == 2)
{
if (IsLeapYear(year))
{
return 29;
}
else
{
return 28;
}
}
else
{
if (month == 1 || month == 3 || month == 5 ||
month == 7 || month == 8 || month == 10 ||
month == 12)
{
return 31;
}
else
{
return 30;
}
}
}
// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// 析构函数
Date::~Date()
{
//cout << "~Date()" << endl;
}
// 日期+=天数
Date& Date::operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
Adjust(*this);
return *this;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
Adjust(*this);
return *this;
}
// 日期+天数
Date Date::operator+(int day)
{
Date tmp = *this;
//方法一
//tmp._day += day;
//Adjust(tmp);
//方法二
tmp += day;
return tmp;
}
// 日期-天数
Date Date::operator-(int day)
{
Date tmp = *this;
//方法一
//tmp._day -= day;
//Adjust(tmp);
//方法二
tmp -= day;
return tmp;
}
// 前置++
Date& Date::operator++(int)
{
*this += 1;
return *this;
}
// 后置++
Date Date::operator++()
{
Date tmp = *this;
*this += 1;
return tmp;
}
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// 后置--
Date Date::operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
// >运算符重载
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)
{
if (_day == d._day)
{
return true;
}
}
}
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);
}
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = d;
int flag = 1;
if (min > max)
{
min = *this;
max = d;
flag = -1;
}
int day = 0;
while (min != max)
{
++min;
++day;
}
return day * flag;
}
//打印
void Date::Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
流插入
//void Date::operator<<(ostream& out)
//{
// out << _year << "-" << _month << "-" << _day << endl;
//}
补充:前置++与后置++的重载区别
1>我们需要清楚前置和后置的区别:
前置是先运算在使用,后置是先使用再运算,所以运算的本质是对变量自身的值的改变,而使用是这个表达式的输出结果。因此前置运算可以直接返回运算后的值,此时用引用可以节约资源;后置需要先创建临时变量,临时变量的值不变,但形参要进行运算,最后返回这个临时变量。我们会发现前置比后置消耗的资源要少很多,自定义类型对象一般情况下最好用前置运算。
2>在C++中我们为了能把前置++/--和后置++/--进行区分,规定后置运算要在形参上加上一个int与前置构成运算符重载;
需要注意:这条规定是死的,不能定义前置运算加上int,要不然运算会出错但编译器不会报错

在 C++ 中,区分前置和后置自增/自减运算符重载的关键是函数签名中的占位参数 int
,在 C++ 中,后置自增/自减运算符需要 int
占位参数的原因是为了解决函数重载的歧义问题。这是 C++ 语言设计者为区分前置和后置版本而制定的特殊语法规则。具体实现需要注意以下要点:
📌 区分方式
- 前置版本 :无参数
Date& operator++();
Date& operator--();
- 后置版本 :带
int
占位参数
Date operator++(int);
Date operator--(int);
(调用时编译器自动传入 0 作为参数)
⚠️ 实现注意事项
特性 | 前置版本 | 后置版本 |
---|---|---|
返回值类型 | 返回当前对象引用 (Date& ) |
返回操作前的临时副本 (Date ) |
操作逻辑 | 先自增/自减,后返回新值 | 先保存旧值,再自增/自减,返回旧值 |
效率 | 高效(无拷贝开销) | 较低(需构造临时对象) |
实现复用 | 通常直接修改成员变量 | 通常调用前置版本实现核心逻辑 |
链式操作支持 | 支持(如 ++++d ) |
不支持(返回右值) |
补充:关于流插入运算符(<<)的解释
现在我们可以解释为什么在C++中使用cout<<
可以自己判断参数类型并进行输出了,因为在库函数源文件中就已经定义了<<
多种重载形式,所以对于C++中的内置类型对象的输出可以自己识别但对于自定义类型就做不到了

拓展:仿照流插入操作符(<<)的作用创建一个可以直接实现日期的流插入重载

const成员
内置变量类型可以用const修饰,自然自定义类型也可以用const进行修饰,但是会有一些区别。
如果定义一个被const修饰的类类型的对象,在调用其成员函数的时候可能会出现权限被放大的情况

因为d2是被const修饰的对象,d2的值不能被修改,当我们调用d2.Printf()
时,我们知道会有一个this
指针变量作为隐含的参数,但这个this
指针变量默
认情况下的类型是Date*const this
,this
指针指向的对象(d2)的值是可以通过this
指针修改的,因此编译不通过,权限被放大。
解决方法:在成员函数后面加上一个const修饰,如图,这样this指针的类型就变成了const Date*const this
,不会发生权限的放大了。

对于前面实现的日期类或其他类,如果某成员函数的功能只是读取数据而不需要修改数据,最好在函数后面加上const修饰,这样被const修饰的对象也可以
正常使用该函数。
通过以上的学习可以回答一下这几个问题:
-
const对象可以调用非const成员函数吗? 否
-
非const对象可以调用const成员函数吗? 可以
-
const成员函数内可以调用其它的非const成员函数吗? 否
-
非const成员函数内可以调用其它的const成员函数吗? 可以
总结
本文实现了一个完整的C++日期类,重点包括:
核心功能:通过Adjust函数处理日期跨月/跨年边界,支持日期的加减运算(+=/-=复用实现+/-);
运算符重载:完整实现了比较运算符(>,==等)、算术运算符(±)和自增/自减(前置/后置);
关键算法:包含闰年判断和月份天数计算;
类设计:包含构造函数、拷贝控制成员、流输出友元等;
代码复用:通过+=实现+、-=实现-等技巧减少重复代码。
该日期类实现了规范的日期计算功能,通过运算符重载提供了直观的日期操作接口,是C++类和对象特性的典型应用案例。