【C++】C++类与对象2:C++构造函数、运算符重载与流输入输出全面解析

📌 相关专栏

很高兴你点开这篇文章✨

这里会持续更新我喜欢的内容,关注我,一起慢慢变好呀

👍 点赞 ⭐ 收藏 💬 评论


文章目录

  • 前言
  • 一、构造函数
    • [1.1 什么是构造函数](#1.1 什么是构造函数)
    • [1.2 声明与定义分离时缺省参数的位置](#1.2 声明与定义分离时缺省参数的位置)
  • 二、辅助函数:获取月份天数
  • 三、运算符重载
    • [3.1 什么是运算符重载](#3.1 什么是运算符重载)
    • [3.2 日期 += 天数](#3.2 日期 += 天数)
    • [3.3 日期 + 天数 vs 日期 += 天数](#3.3 日期 + 天数 vs 日期 += 天数)
    • [复用原则:用 += 实现 +](#复用原则:用 += 实现 +)
    • [3.4 前置++ vs 后置++](#3.4 前置++ vs 后置++)
    • [3.5 比较运算符](#3.5 比较运算符)
    • [3.6 日期 - 日期(计算天数差)](#3.6 日期 - 日期(计算天数差))
  • 四、流插入(cout)与流提取(cin)重载
    • [4.1 问题:为什么不能写在类里面?](#4.1 问题:为什么不能写在类里面?)
    • [4.2 友元函数](#4.2 友元函数)
    • [4.3 实现流插入和流提取](#4.3 实现流插入和流提取)
  • 五、知识点汇总
  • 六、本文的所有代码
    • [🐾 </h3>Date.cpp](#🐾 Date.cpp)
    • [🐾 </h3>Date.h](#🐾 Date.h)
    • [🐾 </h3>Test.cpp](#🐾 Test.cpp)

前言

在前一篇文章中,我们学习了类的基础知识:访问限定符、类域、this指针等。这一篇,我们要真正动手写一个完整的类------Date类。

💡接下来的这篇文章,我们将一起学到:

  • 构造函数如何初始化对象
  • 如何让日期支持 +、-、++、-- 等运算符
  • 前置++和后置++有什么区别,如何重载
  • 如何让cout << d1 直接打印日期
  • 如何复用写好的运算符,避免重复代码

让我们从一个简单的日期类开始。

🐶 🐾 ✨ 🐾 🐶


一、构造函数

1.1 什么是构造函数

构造函数是对象实例化时自动调用的函数,用于初始化对象。

🐾 特点:

  • 函数名与类名相同
  • 没有返回值
  • 可以带参数(支持重载和缺省参数)
  • 对象创建时自动执行
cpp 复制代码
class Date
{
public:
    // 构造函数(全缺省)
    Date(int year = 2005, int month = 2, int day = 8)
    {
        _year = year;
        _month = month;
        _day = day;
        
        // 检查日期是否合法
        if (!CheckDate())
        {
            cout << "日期非法:";
            Print();
        }
    }
    
private:
    int _year;
    int _month;
    int _day;
};

// 使用方式
Date d1;              // 使用缺省值 2005/2/8
Date d2(2026, 3, 23); // 使用传入的值

1.2 声明与定义分离时缺省参数的位置

重要规则:缺省参数只能写在声明中,不能出现在定义中。

cpp 复制代码
// Date.h
class Date 
{
public:
    Date(int year = 2005, int month = 2, int day = 8);  // ✅ 声明写缺省
};

// Date.cpp
Date::Date(int year, int month, int day)  // ❌ 定义不能再写缺省
{
    _year = year;
    _month = month;
    _day = day;
}

🐶 🐾 ✨ 🐾 🐶


二、辅助函数:获取月份天数

cpp 复制代码
//默认是内联inline函数,则不需要为了调用函数而建立栈帧
int GetMonthDay(int year, int month)
{
    assert(month > 0 && month < 13);
    
    // static修饰,数组只初始化一次,避免重复创建,创建一个月份数组来储存每月对应的天数
    static int monthDayArr[13] = { -1, 31, 28, 31, 30, 31, 31, 30, 31, 30, 31, 30, 31 };
    
    // 闰年2月特殊处理
    if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
    {
        return 29;			//对应闰年二月的29天
    }
    return monthDayArr[month];
}

注意:

  • static 数组只初始化一次,提高效率
  • 下标从1开始对应1月,0位置用-1占位
  • 闰年判断:能被4整除且不能被100整除,或能被400整除
cpp 复制代码
static int monthDayArr[13] = { -1,31,28,31,30,31,31,30,31,30,31,30,31 };
		//第一个-1是为了补上0这个位置,保证每一个月对应相应的天数
		//月份数组元素:-1 31  28  31  30  31  31  30  31  30  31  30  31
		//月份数组下标:0	 1	2	3	4	5	6	7	8	9	10	11	12

🐶 🐾 ✨ 🐾 🐶


三、运算符重载

3.1 什么是运算符重载

C++允许我们重新定义运算符的行为,让自定义类型也能使用 +、-、++ 等运算符。

  • 🐾 语法: 返回值 operator运算符(参数列表)

3.2 日期 += 天数

cpp 复制代码
//日期+=天数
Date& Date::operator+=(int day)
{
    _day += day;
    while (_day > GetMonthDay(_year, _month))
    {
        _day -= GetMonthDay(_year, _month);
        _month++;
        if (_month > 12)
        {
            _year++;
            _month = 1;
        }
    }
    return *this;   //首先引用返回目的是避免传参而减少拷贝构造的次数提高效率
					//其次this指针是类的地址,我们要返回类,所以对this的引用
}

3.3 日期 + 天数 vs 日期 += 天数

运算符 是否修改原对象 返回值类型 典型用法
+= 引用 Date& d1 += 100
+ Date 值 Date d2 = d1 + 100

复用原则:用 += 实现 +

cpp 复制代码
Date Date::operator+(int day)
{
    Date tmp = *this;  // 拷贝一份
    tmp += day;        // 复用 +=
    return tmp;        // 返回拷贝(tmp是局部变量)
}

3.4 前置++ vs 后置++

类型 写法 返回值 实现
前置++ ++d1 引用 Date& 先自增,再返回自己
后置++ d1++ Date 值 先保存副本,再自增,返回副本
cpp 复制代码
// 前置++(返回引用)
Date& Date::operator++()
{
    *this += 1;
    return *this;
}

// 后置++(返回副本,int参数是占位符)
Date Date::operator++(int)
{
    Date tmp = *this;
    *this += 1;
    return tmp;
}

3.5 比较运算符

cpp 复制代码
bool Date::operator<(const Date& d)
{
    if (_year < d._year) return true;
    if (_year == d._year && _month < d._month) return true;
    if (_year == d._year && _month == d._month && _day < d._day) return true;
    return false;
}
		/*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;		//包括_year>d._year和_month>d._month
		}*/

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);  // 复用 ==
}

3.6 日期 - 日期(计算天数差)

cpp 复制代码
int Date::operator-(const Date& d)
{
    int flag = 1;
    Date max = *this;
    Date min = d;
    
    // 如果当前日期更小,说明结果是负数
    if (*this < d)
    {
        min = *this;
        max = d;
        flag = -1;
    }
    
    int n = 0;
    while (min != max)
    {
        ++min;   // 复用前置++
        n++;
    }
    return n * flag;
}

🐶 🐾 ✨ 🐾 🐶


四、流插入(cout)与流提取(cin)重载

4.1 问题:为什么不能写在类里面?

cpp 复制代码
class Date 
{
public:
    // ❌ 错误!第一个参数会变成 this 指针
    void operator<<(ostream& out, Date& d);
};

成员函数的第一个参数是隐式的 this,但 cout << d1 需要 ostream 作为第一个参数。

  • 解决办法: 写在全局,并用友元访问私有成员。

4.2 友元函数

友元函数可以在类外访问类的私有成员。

cpp 复制代码
class Date
{
public:
    // 声明友元函数
    friend ostream& operator<<(ostream& out, Date& d);
    friend istream& operator>>(istream& in, Date& d);
    
private:
    int _year;
    int _month;
    int _day;
};

4.3 实现流插入和流提取

🐾 流插入 cout 的重载

cpp 复制代码
// 流插入 cout << d1
ostream& operator<<(ostream& out, Date& d)
{
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;  // 返回out支持连续输出,out其实就是cout的别名
}

🐾 流提取 cin 的重载

复制代码
// 流提取 cin >> d1
istream& operator>>(istream& in, Date& d)
{
    cout << "请依次输入年月日:";
    in >> d._year >> d._month >> d._day;
    
    while (!d.CheckDate())  // 检查合法性
    {
        cout << "输入的日期非法,请重新输入!" << endl;
        in >> d._year >> d._month >> d._day;
    }
    return in;  // 返回in支持连续输入
}

🐾 使用示例:

cpp 复制代码
Date d1, d2;
cin >> d1 >> d2;
cout << d1 << d2 << endl;

🐶 🐾 ✨ 🐾 🐶


五、知识点汇总

知识点 核心要点
构造函数 与类同名、无返回值、对象创建时自动调用
缺省参数位置 只能写在声明中,不能写在定义中
运算符重载 返回值 operator符号(参数)
前置++ vs 后置++ 前置返回引用,后置返回副本(int占位符区分)
复用原则 用 += 实现 +,用 == 实现 !=
友元函数 声明加 friend,可访问私有成员
流插入/提取 必须写在全局,返回引用支持链式操作

🐶 🐾 ✨ 🐾 🐶


六、本文的所有代码

🐾 Date.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"

//声明与定义分离时,缺省参数只能写在声明中,不能出现在声明和定义
//构造函数初始化对象

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	if (!CheckDate())
	{
		cout << "日期非法:";
		Print();
	}
}


//检查日期是否合法
bool Date::CheckDate()
{
	if (_month > 0 && _month < 13 && _day>0 && _day<= GetMonthDay(_year, _month))
	{
		return true;
	}
	else
		return false;

}

void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

//日期+=天数
Date& Date::operator+=(int day)
{
	_day = day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;	//首先引用返回目的是避免传参而减少拷贝构造的次数提高效率
					//其次this指针是类的地址,我们要返回类,所以对this的引用
}


//复用+的逻辑来实现+=
Date& Date::operator+=(int day)
{
	*this = *this + day;
	return *this;
}


//日期+天数
Date Date :: operator+(int day)
{
	Date tmp = *this;	//通过创建新的局部对象来存放日期,避免对原日期进行修改

	/*tmp._day += day;
	while (tmp._day > GeyMonthDay(tmp._year, tmp._month))
	{
		tmp._day-- = GetMonthDay(tmp._day, tmp._month);
		tmp._month++;
		if (tmp._month > 12)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}*/

	tmp += day;		//直接复用上面的+=运算符重载的逻辑,则不需要重复写
	return tmp;
	//由于tmp是在函数内部创建的局部对象,出了函数责则被自动销毁,所以只能传值返回而不能用引用返回
}

//日期-=天数
Date& Date:: operator-=(int day)
{
	_day = day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}	
	return *this;		
}

//复用-的逻辑来实现-=
Date& Date::operator -= (int day)
{
	*this = *this - day;
	return *this;
}


//日期-天数
Date Date::operator-(int day)
{
	Date tmp = *this;

	/*tmp._day -= aday;
	while (tmp._day = 0)
	{
		tmp._month--;
		if (tmp._month == 0)
		{
			tmp._year--;
			tmp._month = 12;
		}
		tmp_day += GetMonthDay(tnp._year, tmp._month);
	}*/
	tmp -= day;
	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)
		{
			return _day < d._day;
		}
	}
	return false;		//包括_year>d._year和_month>d._month
}

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);//实现了上面的的=两个比较的运算符重载就可以直接进行复用
}

//后置++(d1++)
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

//前置++(++d1)
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

//后置--(d1++)
Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

//前置--(++d1)
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}


//日期-日期
int Date::operator-(const Date& d)
{
	int flag = 1;	//flag为1表示假设大得分日期减去小的日期
	Date max = *this;
	Date min = d;
	if (*this < d)	//假设不成立,则说明是小的日期减去大的日期,则得到的结果为负值
	{
		min = *this;
		max = d;
		flag = -1;
	}
	int n = 0;
	while (min != max)		//通过循环复用++运算符重载来得到两个日期之间的天数
	{
		++min;
		++ n;
	}
	return n * flag;		//如果flag为-1则说明是小的日期减去大的日期,n为正数则需要加负号
}

//流插入cout的重载
ostream& operator<<(ostream& out, Date& d)
{
	out << d._day << "年" << d._month << "月" << d._day << "日" << endl;
	return out;//返回的 out 其实就是 cout 的别名
}

//流提取 cin 的重载
istream& operator<<(istream& in, Date& d)
{
	cout << "请依次输入年月日:";
	in >> d._year >> d._month >> d._day;
	while (1) 
	{
		if ((!d.CheckDate()))	//如果输入的日期非法则需要重新输入
		{
			cout << "输入的日期非法:";
			d.Print();
				cout << "请重新输入!" <<endl;
		}
		else
		{
			break;
		}
	}
	return in;	//返回的 in 其实就是 in 的别名
}

🐾 Date.h

cpp 复制代码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

class Date
{
public:
	//友元函数的声明:能类外的函数也能直接访问类中的成员
	friend ostream& operator<<(ostream& out, Date& d);
	friend istream& operator>>(istream& in, Date& d);

	//全缺省函数
	Date(int year = 2005,  int month = 2, int day = 8);
	void Print();

	//默认是内联inline函数,则不需要为了调用函数而建立栈帧
	int GetMonthDay(int year, int month)
	{
		assert(month > 0 && month < 13);
		//创建一个月份数组来储存每月对应的天数
		static int monthDayArr[13] = { -1,31,28,31,30,31,31,30,31,30,31,30,31 };
		//第一个-1是为了补上0这个位置,保证每一个月对应相应的天数
		//月份数组元素:-1	31  28  31  30  31  31  30  31  30  31  30  31
		//月份数组下标:0	1	2	3	4	5	6	7	8	9	10	11	12
		if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		{
			return 29;	//对应闰年二月的29天
		}
		return monthDayArr[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++(int);//后置++
	Date& operator++();	//前置++

	Date operator--(int);//后置--
	Date& operator--();	//前置--

	int operator-(const Date& d);//日期 - 日期

	bool CheckDate();		//检查日期是否合法


	//void operator <<(ostream& out ,Date& d);		//流插入cout的重载
	//流插入的重载声明放在类中会导致第一个形参为this指针而不是我们想要的的ostreaam& out
	//所以我们只能在全局域中实现流插入的重载

private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, Date& d);	//流插入 cout 的重载
istream& operator<<(istream& in, Date& d);	//流提取 cin 的重载

🐾 Test.cpp

cpp 复制代码
#pragma once
#include"Date.h"

int main()
{
	//+=和+的运算符重载测试
	Date d1(2026, 3, 23);
	d1.Print();
	d1 += 1000;
	d1.Print();

	Date d2 = d1 + 1000;
	d1.Print();

	//-=和-的运算符重载测试
	Date d1(2026, 3, 23);
	d1.Print();
	d1 -= 1000;
	d1.Print();

	Date d2 = d1 - 1000;
	d2.Print();

	//前置、后置++
	Date d1(2026, 3, 23);
	d1.Print();
	Date d2 = d1;
	d2.Print();
	Date d3 = ++d1;
	Date d4 = d2++;
	d3.Print();
	d4.Print();
	d1.Print();
	d2.Print();

	//前置、后置--
	Date d1(2026, 3, 23);
	d1.Print();
	Date d2 = d1;
	d2.Print();
	Date d3 = --d1;
	Date d4 = d2--;
	d3.Print();
	d4.Print();
	d1.Print();
	d2.Print();

	//日期 - 日期
	Date d1(2026, 3, 23);
	d1.Print();
	Date d2(2028, 3, 22);
	cout << d2 - d1 << endl;
	cout << d1 - d2 << endl;

	//流插入 cout 的重载
	Date d1;
	Date d2;
	cout << d1 << d2;

	//流提取 cin 的重载
	Date d1;
	Date d2;
	cin >> d1 >> d2;
	cout << d1 << d2 << endl;
	cout << d1 - d2 << endl;

	return 0;
}

🐾 下一篇我们继续学习:

  • const 成员函数
  • const 对象
  • 取地址运算符重载

🐶 🐾 ✨ 🐾 🐶


谢谢你看到这里呀

如果喜欢这篇内容,点个关注,下次更新不迷路✨

👍 点赞 ⭐ 收藏 💬 评论

相关推荐
郭涤生1 小时前
C++ 高性能状态机
开发语言·c++
weixin_537217061 小时前
Ai教程资源合集
经验分享
SOC罗三炮1 小时前
OpenHuman 源码深度解构:一个 Rust 驱动的本地优先 AI 个人助手
开发语言·人工智能·rust
心怀梦想的咸鱼1 小时前
OpenCode 接入 API 报错 read ECONNRESET:基于环境变量的证书校验绕过方案
开发语言·php
酿情师1 小时前
Microsoft Visual C++ Build Tools 2026 下载与安装指南(Windows)
c++·windows·microsoft
cany10002 小时前
C++ -- 引用悬挂
c++
星栈2 小时前
Rust 单二进制部署,真没你想的那么“单”
前端·后端
angerdream2 小时前
Android手把手编写儿童手机远程监控App之webrtc聊天数据通道
前端
程序大视界2 小时前
【Python系列课程】Python入门教程
开发语言·人工智能·python