C++类与对象

1.类的定义

1.1定义格式

class为定义类的关键字,Stack是类的名字,{}是类的主体,注意类定义结束后面的分号不能省略,类体中的内容称为类的成员,类中的变量称为类的属性或成员变量,类中的函数称为类的方法或者成员函数

定义在类中的成员函数默认为inline

cpp 复制代码
#include<iostream>
#include<assert.h>
using namespace std;
class Stack
{
public:
	
		void Init(int n = 4)
		{
		array= (int*)malloc(sizeof(int) * n);
		if (nullptr == array)
		{
			perror("malloc fail");
return;
		}
		top = 0;
		capacity = n;
		}
		void Push(int x)
		{
			if (top == capacity)
			{
				int newcapacity = capacity == 0 ? 4 : 2 * capacity;
				int* tmp = (int*)realloc(array, sizeof(int) * 4);
				if (nullptr == tmp)
				{
					perror("realloc fail");
					return;
				}
				array = tmp;
				capacity = newcapacity;
			}
			array[top] = x;
			top++;
		}
		int Top()
		{
			assert(top > 0);
			return array[top - 1];
		}
		void Destory()
		{
			free(array);
			nullptr == array;
			top = capacity = 0;
		}
private:
	int* array;
	int top;
	int capacity;

};
int main()
{
	Stack st;
	st.Init();
	st.Push(1);
	st.Top();
	cout << st.Top()<< endl;
	st.Destory();
	return 0;
}
cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
    void Init(int year, int month, int date)
    {
        _year = year;
        _month = month;
        _date = date;
    }
private:
   int  _year;
   int  _month;
   int   _date;
};
int main()
{
    Date dt;
    dt.Init(2024, 3, 4);
    return 0;
}

1.2访问限定符

C++有一种实现封装的方式,用类将对象的属性与方法结合到一起,通过访问权限选择性的将接口提供给外部用户使用

public修饰的成员可以在类外直接被访问

protected和·private修饰的成员不能直接在类外访问,访问权限作用域从该访问限定符开始到下一个访问限定符为止,如果后面没有被访问限定符,则到}结束。

class定义的成员没有被访问限定符修饰时默认为private

struck默认为public

1.3类域

类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个域。

类域影响的是编译时的查找规则,下面程序Init如果不指定类域Stack,编译器就会在全局中寻找,就会找不到array,如果指定了类域,编译器就会在类域中寻找

cpp 复制代码
#include<iostream>
using namespace std;
class Stack
{
public:
	 void Init(int n = 4);

private:
	int* array;
      size_t top;
	  size_t  capacity;
};
void Stack::Init(int n)
{
	array = (int*)malloc(sizeof(int) * n);
	if (nullptr == array)
	{
		perror("malloc fail");
}
	int top = 0;
	int capacity = n;
}
int main()
{
	Stack st;
	st.Init(100);
	return 0;

}

2.实例化

用类类型在物理内存中创建对象的过程,叫做实例化出对象

类是对象进行的一种抽象描述。这些成员变量只是声明,没有分配空间,用类实例化出对象,才会分配空间。

一个类可以实例化出多个对象,实例化出的对象占用实际物理空间。

cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;

};
int main()
{
	Date d1;
	Date d2;
	d1.Init(2024,1,2);
	d1.Print();
	d2.Init(2932,3,4);
	d2.Print();
	return 0;
}

2.1对象大小

实例化出的每个对象都有独立的空间,对象中包含成员遍历,并不包含成员函数

C++

规定类的实例化对象也满足内存对齐规则

1.第一个成员在与结构体偏移量为0的地址处

2.其他成员变量要对齐到(对齐数)的整数倍的地址处

对齐数=编译器默认的对齐数与该成员大小的较小值

VS中默认对齐数是8

结构体大小为:最大对齐数的正数倍(所有变量的最大值与默认对齐数取小)

嵌套结构体:嵌套的结构体对齐到自己最大对齐数的整数倍,结构体大小就是所有最大对齐数的整数倍

如果看到没有成员变量的话大小为1

3.this指针

Date中Init和Print俩个成员函数,函数体没有关于不同对象的区分,如果d1调用函数时,该如何区分是d1还是d2.C++给了this指针

编译器编译后,类的成员函数默认都会在形参的第一个位置,增加一个当前类类型的指针,叫做this指针。Date Init原型为void Init(Date *const this,int year,int minth,int day).

C++规定不能在实参和形参的位置先生this,但是可以在函数体内显示使用this指针

cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		this->_month = month;
		this->_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;

};
int main()
{
	//实例化

	Date d1;
	Date d2;
	d1.Init(2024,1,2);
	d1.Print();
	d2.Init(2932,3,4);
	d2.Print();
	return 0;
}

4.类的默认成员函数

默认成员函数就是用户没有显现,编译器会自动生成的成员函数

5.构造函数

构造函数是特殊的成员函数,构造函数虽然名称是构造,但是构造函数的主要任务是对象实例化时的初始化。构造函数本质是要代替Init函数功能,构造函数自动调用的特点完美的代替了Init

构造函数特点:

1.函数名与类名相同

2.没有返回值

3.对象实例化会自动调用构造函数

4.构造函数可以重载

5.如果类中没有构造函数,编译器会自动生成一个构造函数,一旦显示定义编译器就不再生成

6.无参构造函数,全缺省构造函数,我们不写构造编译器默认生成的构造函数都叫做默认构造函数

,但这三个函数只能有一个函数存在,总结:不传实参就能调用的构造就叫默认构造

7.编译器默认生成的构造,对于内置类型成员的初始化没有要求,也就是说不知道是否初始化。

对于自定义类型的成员变量,要求调用这个成员变量的默认构造初始化,就必须得将成员初始化,要么给定缺省值,要么就初始化列表

cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	//无参构造函数
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	//有参构造函数
	Date(int year, int month, int day)
	{

		_year = year;
		_month = month;
		_day = day;
	}
	//全缺省构造函数
	/*Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;//无参构造函数
	Date d2(1, 2, 3);
	Date d3();//这不是对象的定义,是函数的声明
		d1.Print();
		d2.Print();
		return 0;
}
cpp 复制代码
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
	public:
	
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc fail");
			return;
		}
		top = 0;
		capacity = n;
	}
private:
	STDataType* _a;
	int top;
	int capacity;
};
class MyQueue
{
public:

private:
	Stack popst;
	Stack pushst;
};
int main()
{
	MyQueue qt;
	return 0;
}

6.析构函数

析构函数与构造函数作用相反,比如局部对象是存在栈帧的,函数结束栈帧销毁了,他就释放了

C++规定对象在销毁时自动调用析构函数,完成对对象中资源的清理情况,析构函数的作用相当与Destroy。

析构函数特点:

1.析构函数函数名是在类名前加~

2.无参数无返回值

3.一个类只能有一个析构函数,若未显式定义,系统会默认生成析构函数

4.对象声明周期结束,会自动调用析构函数

5.编译器自动生成的析构函数不会对内置类型成员做处理,自定义类型成员会调用他们的析构函数

6.自定义类型成员无论什么情况都会调用析构函数

7.如果类中没有申请资源,使用编译器自带的析构函数即可,但是申请资源,就必须得写析构函数,如Stack,否则会造成资源泄露

8.一个局部域的多个对象,C++规定先定义的后析构

cpp 复制代码
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
	public:
	
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc fail");
			return;
		}
		top = 0;
		capacity = n;
	}
	 ~Stack()
	{
		 cout << "~Stack()" << endl;
		 free(_a);
		 _a = nullptr;
		 top = capacity = 0;


	}
private:
	STDataType* _a;
	int top;
	int capacity;
};
class MyQueue
{
public:

private:
	Stack popst;
	Stack pushst;
};
int main()
{
	Stack st;
	MyQueue qt;
	return 0;
}

7.拷贝函数

拷贝函数的第一个参数的自身类类型的引用,且任何额外的参数都有默认值

拷贝函数的特点:

1.拷贝函数是构造函数的重载

2.拷贝函数的第一个参数必须是自身类类型的引用,使用传值直接报错,拷贝函数可以有多个参数,第一个参数必须是自身类类型的引用,且后面的参数都得有缺省值

3.C++规定自定义类型对象进行拷贝必须调用拷贝构造,所以自定义传值传参必须调用拷贝构造

4.若未显示拷贝构造,编译器会自动生成一个拷贝构造,自动生成的拷贝构造会对内置类型的成员变量进行值拷贝(浅拷贝),对自定义类型会调用自身的拷贝构造.

5.像Stack这种内置类型,浅拷贝显然不符合他的需求,所以需要我们自己实现深拷贝

小技巧:如果一个类显示实现了析构并且释放了资源,那么他一定进行了拷贝构造

6.传值返回会产生一个临时变量调用拷贝构造,传值引用返回,返回的是返回对象的别名,没有产生拷贝,如返回对象是一个当前函数局部域的局部对象,就不能使用传值引用返回。

cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Func(Date d)
{
	cout << &d << endl;
	d.Print();
}
//tmp是临时变量,函数结束就会销毁了。
//Date & Func2(Date d)
//{
//	Date tmp(1, 2, 4);
//	tmp.Print();
//	return tmp;
//}
int main()
{
	Date d1(1, 2, 3);
	Func(d1);
	//Date d2 = d1;
	// Date d3(d1);
	//
	//Func(d2);
	//Date ret=Func2(d1);
	//ret.Print();

	return 0;
}
cpp 复制代码
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
	public:
	
	Stack(int n = 4)
	{
		_a = (STDataType*)malloc(sizeof(STDataType) * n);
		if (nullptr == _a)
		{
			perror("malloc fail");
			return;
		}
		top = 0;
		capacity = n;
	}
	Stack(const Stack& st) {
		_a = (STDataType*)malloc(sizeof(STDataType) * st.top);
		if (nullptr == _a)
		{
			perror("malloc fail");
			return;
		}
		memcpy(_a, st._a, sizeof(STDataType) * st.top);
		top = st.top;
		capacity = st.capacity;
		
	}
	void Push(STDataType x)
	{
		if (top == capacity)
		{
		
			int newcapacity = capacity == 0 ? 4 : 2 * capacity;
		STDataType *tmp = (STDataType*)realloc(_a,sizeof(STDataType)*newcapacity);
		if (nullptr == tmp)
		{
			perror("realloc fail");
			return;
		}
		_a = tmp;
		capacity = newcapacity;

		}
		_a[top++] = x;
	}
	 ~Stack()
	{
		 cout << "~Stack()" << endl;
		 free(_a);
		 _a = nullptr;
		 top = capacity = 0;


	}
private:
	STDataType* _a;
	int top;
	int capacity;
};
class MyQueue
{
public:

private:
	Stack popst;
	Stack pushst;
};
int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	Stack st2 = st1;

	/*MyQueue qt;*/
	return 0;
}

8.赋值运算符重载

8.1运算符重载

当运算符被用于类类型对象时,C++允许我们通过运算符重载的形式重新指定新的含义

C++规定类类对象使用运算符时,必须转换成调用相应的运算符重载,若没有相应的运算符重载,则会报错。

运算符重载具有特殊名字的函数,他的名字是由opertor和后面要定义的运算符共同构成的。和其他函数一样,他也具有其返回类型和参数列表及函数体

重载运算符函数的参数应该跟该运算符作用的运算对象一样多。

如果一个重载运算符函数是成员函数,则他的第一个运算对象默认传给隐式的this指针,参数比运算对象少一个。

运算符重载后,优先级和结合性与对应的内置类型保持一致。

不能通过连接语法中没有的符号来创建新的操作符:operator@

.* :: sizeof ?: .以上运算符不能重载

一个类需要重载哪些运算符,是看那些运算符重载有意义

重载++运算符时,为了区分前置后置,在后置重载时增加一个int形参。

重载<<和>>时,需要重载为全局函数,将ostream和istream放到第一个形参,第二个形参位置当类类型对象。

cpp 复制代码
#include<iostream>
using namespace std;
class  A
{
public:
	void Func()
	{
		cout << "A::Func()" << endl;
	}
};
typedef void(A::PF);//成员函数指针类型
int main()
{
	PF pf = &A::Func;
	A OBJ;
	(OBJ.*pf)();
}
cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		 _month=month;
		 _day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
//private:
	int _year;
	int _month;
	int _day;
};
//重载为全局的面临对象访问私有成员变量的问题
//1.成员放公有
//Date提供getxxx函数
//3.友元函数
//4.重载为成员函数
bool operator==(const Date& d1, const Date& d2)
{
	return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
int main()
{
	Date d1(2021, 1, 1);
	Date  d2(2021, 2, 2);
	operator==(d1, d2);
	cout << operator==(d1, d2) << endl;
	return 0;

}
cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year=1, int month=1, int day=1)
	{
		_year = year;
		 _month=month;
		 _day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	bool operator==(const Date& d)
	{
		return _year== d._year && _month == d._month && _day == d._day;
	}
	Date& operator++()
	{
		cout << "前置++" << endl;
		return* this;

	}
	Date operator++(int)
	{
		Date tmp;
		cout << "后置++" << endl;
		return tmp;
	}

//private:
	int _year;
	int _month;
	int _day;
};
//重载为全局的面临对象访问私有成员变量的问题
//1.成员放公有
//Date提供getxxx函数
//3.友元函数
//4.重载为成员函数

int main()
{
	Date d1(2021, 1, 1);
	Date  d2(2021, 2, 2);
	d1.operator==(d2);
	//cout << operator==(d1, d2) << endl;
	cout << d1.operator==(d2) << endl;
	d1++;
	++d1;
	return 0;

8.2赋值运算符重载

赋值运算符重载是一个默认成员函数,用于完成俩个已经存在的对象直接的拷贝赋值,与拷贝构造区分开。

赋值运算符重载特点:

1.规定必须重载为成员函数,赋值重载的参数建议写成const 当前类类型的引用。否则传值传参会有拷贝

2.有返回值,建议写成当前类类型引用,引用返回可以提高效率,有返回值为了支持连续赋值

3。没有显示实现时,编译器会默认生成一个赋值重载,跟拷贝构造类似,自动生成的拷贝构造会对内置类型的成员变量进行值拷贝(浅拷贝),对自定义类型会调用自身的拷贝构造.

像Stack这种内置类型,浅拷贝显然不符合他的需求,所以需要我们自己实现深拷贝

小技巧:如果一个类显示实现了析构并且释放了资源,那么他一定进行了拷贝构造

cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	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;
};
int main()
{
	Date d1(1, 2, 4);
	Date d2(1, 3, 4);
	d1 = d2;
	d1.Print();
	return 0;
}

8.3日期实现

cpp 复制代码
//Date.h
#pragma once
#include<iostream>
#include<cassert>
class Date
{
	friend std::ostream& operator<<(std::ostream& out, const Date& d);
	friend std::istream& operator>>(std::istream& in, Date& d);
public:
	Date(int year = 1999, int month = 1, int day = 1);
	void Print()const;
	int GetMonthDay(int year, int month) const
	{
		assert(month > 0 && month < 13);
			static int monthDayArray[13] = { -1, 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;
			}
			else
			{
				return monthDayArray[month];

			}
			
	}
	bool CheakDate();
	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;
	Date& operator+=(int day);
	Date operator+(int day) const;
	Date& operator-=(int day);
	Date operator-(int day) const;
	int operator-(const Date& d) const;
	 Date & operator++();
	 Date operator++(int);
private:
	int _year;
	int _month;
	int _day; 
};

//Date.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"
#include<cassert>
#include<climits>
bool Date::CheakDate()
{
	if (_month < 1 || _month>12 || _day<1 || _day >GetMonthDay(_year, _month))
	{
		return false;
	}
	else
		return  true;
}

Date::Date(int year,int month,int day)
{
	_year = year;
	_month = month;
	_day = day;
	if (!CheakDate())
	{
		std::cout << "日期不合法,已重置为 1/1/1" << std::endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}
}
void Date::Print()const
{
	std::cout << _year << "/" << _month << "/" << _day << std::endl;
}
bool Date::operator<(const Date& d)const
{
	if (_year != d._year)
	{
		return _year < d._year;
	}
	if (_month != d._month)
	{
		return _month < d._month;
	}
	return _day < d._day;
}
bool Date::operator<=(const Date& d) const
{
	return *this < d || *this == d;
}
bool Date::operator>(const Date& d) const
{
	return !(*this <= d);
}
bool Date::operator>=(const Date& d) const
{
	return !(*this < d);
}

bool Date::operator==(const Date& d) const
{
	return _year == d._year && _month == d._month && _day == d._day;
}
bool Date::operator!=(const Date& d) const
{
	return !(*this == d);
}
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		long long nd = -(long long)day;	// 用 long long 取负,避免 INT_MIN 溢出
		assert(nd <= INT_MAX);
		return *this -= (int)nd;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month > 12)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}
 Date Date::operator+(int day) const
 {
	 Date tmp = *this;
	 tmp += day;
	 return tmp;
 }
 Date& Date::operator-=(int day)
 {
	 if (day < 0)
	 {
		 long long nd = -(long long)day;	// 用 long long 取负,避免 INT_MIN 溢出
		 assert(nd <= INT_MAX);
		 return *this += (int)nd;
	 }
	 _day -= day;
	 while (_day <= 0)
	 {
		 
		 _month--;
		 if (_month ==0)
		 {
			 _year--;
			 _month = 12;
		}
		 _day += GetMonthDay(_year, _month);
	 }
	 return *this;
 }
 Date Date::operator-(int day) const
 {
	 Date tmp = *this;
	 tmp -= day;
	 return tmp;

 }

 int Date::operator-(const Date& d) const
 {
	 // 高效日期差:先累计整年天数,再累加剩余天数,避免逐日循环
	 int flag = 1;
	 Date later = *this;
	 Date earlier = d;
	 if (*this < d)
	 {
		 later = d;
		 earlier = *this;
		 flag = -1;
	 }

	 auto countLeapDays = [](int y) -> int
	 {
		 return Date(y, 1, 1).GetMonthDay(y, 2) == 29 ? 366 : 365;
	 };

	 // 计算某日期距其所在年 1 月 1 日的天数(第1天记1)
	 auto dayOfYear = [](const Date& x) -> int
	 {
		 int t = 0;
		 for (int m = 1; m < x._month; ++m)
		 {
			 t += x.GetMonthDay(x._year, m);
		 }
		 t += x._day;
		 return t;
	 };

	 int sum = 0;
	 for (int y = earlier._year; y < later._year; ++y)
	 {
		 sum += countLeapDays(y);
	 }
	 sum += dayOfYear(later) - dayOfYear(earlier);
	 return flag * sum;
 }

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

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

 std::ostream& operator<<(std::ostream& out, const Date& d)
 {
	 out << d._year << "/" << d._month << "/" << d._day;
	 return out;
 }

 std::istream& operator>>(std::istream& in, Date& d)
 {
	 in >> d._year >> d._month >> d._day;
	 return in;
 }
//test.c
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"
#include<iostream>
#include<sstream>
using namespace std;

static int g_pass = 0;
static int g_fail = 0;

#define CHECK(cond, msg) do{ \
	if (cond) { ++g_pass; } \
	else { ++g_fail; cout << "[FAIL] " << msg << " (line " << __LINE__ << ")" << endl; } \
}while(0)

// 便捷构造,返回指定年月日
Date make(int y, int m, int d)
{
	return Date(y, m, d);
}

void TestConstruct()
{
	cout << "--- 构造测试 ---" << endl;
	Date d1;                               // 默认构造 1999/1/1
	CHECK(d1 == Date(1999, 1, 1), "默认构造应为1999/1/1");

	Date d2(2024, 2, 29);                  // 合法闰年日期
	CHECK(d2 == Date(2024, 2, 29), "合法闰年日期构造");

	// 非法日期构造 → 应重置为 1/1/1
	Date bad1(2024, 2, 30);                // 2月无30日
	CHECK(bad1 == Date(1, 1, 1), "非法: 2024/2/30 应重置为 1/1/1");

	Date bad2(2024, 13, 1);                // 月份越界
	CHECK(bad2 == Date(1, 1, 1), "非法: 月份13 应重置为 1/1/1");

	Date bad3(2024, 1, 0);                 // 天数为0
	CHECK(bad3 == Date(1, 1, 1), "非法: 天数为0 应重置为 1/1/1");

	Date bad4(1900, 2, 29);                // 1900平年2月只有28天
	CHECK(bad4 == Date(1, 1, 1), "非法: 1900/2/29(平年) 应重置为 1/1/1");
}

void TestCompare()
{
	cout << "--- 比较运算符测试 ---" << endl;
	Date a(2024, 1, 1);
	Date b(2024, 1, 1);  // 与 a 相同
	Date c(2024, 1, 2);  // 同年同月不同日
	Date d2(2024, 2, 1); // 同年不同月
	Date e(2023, 12, 31);// 不同年(更小)

	// == !=
	CHECK(a == b, "同年同月同日相等");
	CHECK(a != c, "不同日则不相等");
	CHECK(a != d2, "不同月则不相等");
	CHECK(a != e, "不同年则不相等");

	// <
	CHECK(e < a, "跨年: 2023 < 2024");
	CHECK(a < c, "同月: 1号 < 2号");
	CHECK(a < d2, "同年: 1月 < 2月");
	CHECK(!(a < b), "相等时 a < b 为假");

	// <=
	CHECK(a <= b, "相等时 <= 为真");
	CHECK(a <= c, "a < c 时 <= 为真");
	CHECK(!(c <= a), "c > a 时 <= 为假");

	// >
	CHECK(c > a, "同月: 2号 > 1号");
	CHECK(d2 > a, "同年: 2月 > 1月");
	CHECK(a > e, "跨年: 2024 > 2023");
	CHECK(!(a > b), "相等时 > 为假");

	// >=
	CHECK(a >= b, "相等时 >= 为真");
	CHECK(c >= a, "c > a 时 >= 为真");
	CHECK(!(a >= c), "a < c 时 >= 为假");
}

void TestAdd()
{
	cout << "--- operator+= 测试 ---" << endl;
	// 加 0
	Date a(2024, 1, 15);
	a += 0;
	CHECK(a == Date(2024, 1, 15), "加0不变");

	// 普通加天数
	a = Date(2024, 1, 1);
	a += 10;
	CHECK(a == Date(2024, 1, 11), "加普通天数");

	// 跨月(1月31日加1 → 2月1日)
	a = Date(2024, 1, 31);
	a += 1;
	CHECK(a == Date(2024, 2, 1), "跨月进位");

	// 跨年(12月31日加1 → 次年1月1日)
	a = Date(2024, 12, 31);
	a += 1;
	CHECK(a == Date(2025, 1, 1), "跨年进位");

	// 平年2月(2023年2月28日加1 → 3月1日)
	a = Date(2023, 2, 28);
	a += 1;
	CHECK(a == Date(2023, 3, 1), "平年2月底进位");

	// 闰年2月(2024年2月28日加1 → 2月29日)
	a = Date(2024, 2, 28);
	a += 1;
	CHECK(a == Date(2024, 2, 29), "闰年2月进位到29日");

	// 加负数(等价于减)
	a = Date(2024, 1, 10);
	a += -3;
	CHECK(a == Date(2024, 1, 7), "加负数等价于减");
}

void TestAddConst()
{
	cout << "--- operator+ 测试(不修改原对象) ---" << endl;
	Date a(2024, 1, 28);
	Date r = a + 3;
	CHECK(r == Date(2024, 1, 31), "operator+ 跨月进位");
	CHECK(a == Date(2024, 1, 28), "operator+ 不修改原对象");

	Date b(2024, 12, 30);
	Date r2 = b + 5;
	CHECK(r2 == Date(2025, 1, 4), "operator+ 跨年");
}

void TestSub()
{
	cout << "--- operator-= 测试 ---" << endl;
	// 减 0
	Date a(2024, 1, 15);
	a -= 0;
	CHECK(a == Date(2024, 1, 15), "减0不变");

	// 普通减天数
	a = Date(2024, 1, 11);
	a -= 10;
	CHECK(a == Date(2024, 1, 1), "减普通天数");

	// 跨月回退(3月1日减1 → 2月29日,2024闰年)
	a = Date(2024, 3, 1);
	a -= 1;
	CHECK(a == Date(2024, 2, 29), "跨月回退到闰年2月29");

	// 跨年回退(1月1日减1 → 上年12月31日)------ 验证年份方向修复
	a = Date(2024, 1, 1);
	a -= 1;
	CHECK(a == Date(2023, 12, 31), "跨年回退, 年份应减小");

	// 减负数(等价于加)
	a = Date(2024, 1, 7);
	a -= -3;
	CHECK(a == Date(2024, 1, 10), "减负数等价于加");
}

void TestSubConst()
{
	cout << "--- operator-(int) 测试(不修改原对象) ---" << endl;
	Date a(2024, 3, 1);
	Date r = a - 1;
	CHECK(r == Date(2024, 2, 29), "operator-(int) 跨月回退");
	CHECK(a == Date(2024, 3, 1), "operator-(int) 不修改原对象");

	Date b(2024, 1, 1);
	Date r2 = b - 1;
	CHECK(r2 == Date(2023, 12, 31), "operator-(int) 跨年回退");
}

void TestDateDiff()
{
	cout << "--- operator-(Date) 日期差测试 ---" << endl;
	Date a(2024, 1, 1);
	Date b(2024, 1, 1);
	CHECK((a - b) == 0, "相同日期差为0");

	Date c(2024, 1, 2);
	CHECK((c - a) == 1, "相差1天(正)");
	CHECK((a - c) == -1, "相差1天(负,顺序颠倒)");

	Date d2(2024, 1, 31);
	CHECK((d2 - a) == 30, "1月内差值: 1月1日到1月31日=30天");

	Date e(2024, 12, 31);
	CHECK((e - a) == 365, "2024闰年全年差值365天");

	Date f(2025, 1, 1);
	CHECK((f - a) == 366, "跨年差值: 2024/1/1 到 2025/1/1 = 366天");

	// 大跨度跨多年(验证高效算法按年累计正确)
	Date g(2000, 1, 1);
	Date h(2001, 1, 1);
	CHECK((h - g) == 366, "整闰年 2000/1/1 到 2001/1/1 = 366天");

	Date i(1900, 1, 1);
	Date j(1901, 1, 1);
	CHECK((j - i) == 365, "整平年 1900/1/1(非闰) 到 1901/1/1 = 365天");

	Date k(2000, 3, 1);
	Date l(2000, 2, 1);
	CHECK((k - l) == 29, "闰年2月: 2000/2/1 到 2000/3/1 = 29天");

	Date m(1900, 3, 1);
	Date n(1900, 2, 1);
	CHECK((m - n) == 28, "平年2月: 1900/2/1 到 1900/3/1 = 28天");

	// 跨多个闰年的大跨度 (2024,2025,2026,2027,2028,2029,2030,2031 = 8年, 含2024,2028两个闰年)
	Date o(2024, 1, 1);
	Date p(2032, 1, 1);
	CHECK((p - o) == 2 * 366 + 6 * 365, "跨8年(含2闰年): 2024/1/1 到 2032/1/1");
}

void TestIncrement()
{
	cout << "--- 前置/后置 ++ 测试 ---" << endl;
	Date a(2024, 1, 1);
	Date pref = ++a;                       // 前置: 先自增后返回
	CHECK(pref == Date(2024, 1, 2), "前置++返回自增后的值");
	CHECK(a == Date(2024, 1, 2), "前置++后对象已自增");

	Date b(2024, 1, 1);
	Date post = b++;                       // 后置: 先返回后自增
	CHECK(post == Date(2024, 1, 1), "后置++返回自增前的值");
	CHECK(b == Date(2024, 1, 2), "后置++后对象已自增");

	// 跨年自增
	Date c(2024, 12, 31);
	++c;
	CHECK(c == Date(2025, 1, 1), "前置++跨年");
}

void TestLeapYear()
{
	cout << "--- GetMonthDay 闰年测试 ---" << endl;
	Date d;
	CHECK(d.GetMonthDay(2000, 2) == 29, "2000整除400, 闰年2月29天"); // 修复前的bug场景
	CHECK(d.GetMonthDay(2024, 2) == 29, "2024(year%4==0&&year%100!=0), 闰年2月29天");
	CHECK(d.GetMonthDay(1900, 2) == 28, "1900整除100不整除400, 平年2月28天");
	CHECK(d.GetMonthDay(2100, 2) == 28, "2100整除100不整除400, 平年2月28天");
	// 修复前的bug: 非2月却返回29
	CHECK(d.GetMonthDay(2000, 3) == 31, "2000年3月应为31天(修复优先级bug)");
	// 各月天数抽查
	CHECK(d.GetMonthDay(2024, 1) == 31, "1月31天");
	CHECK(d.GetMonthDay(2024, 4) == 30, "4月30天");
	CHECK(d.GetMonthDay(2024, 12) == 31, "12月31天");
}

void TestBoundary()
{
	cout << "--- 边界测试 ---" << endl;
	// 每月最后一天 +1 → 下月1号
	Date a(2024, 1, 31);
	a += 1;
	CHECK(a == Date(2024, 2, 1), "1月31+1=2月1");

	Date b(2024, 4, 30);
	b += 1;
	CHECK(b == Date(2024, 5, 1), "4月30+1=5月1");

	// 每月1号 -1 → 上月最后一天
	Date c(2024, 3, 1);
	c -= 1;
	CHECK(c == Date(2024, 2, 29), "3月1-1=2月29(闰年)");

	Date d2(2024, 5, 1);
	d2 -= 1;
	CHECK(d2 == Date(2024, 4, 30), "5月1-1=4月30");

	// 12月31 +1 → 次年1月1
	Date e(2024, 12, 31);
	e += 1;
	CHECK(e == Date(2025, 1, 1), "12月31+1=次年1月1");

	// 1月1 -1 → 上年12月31
	Date f(2024, 1, 1);
	f -= 1;
	CHECK(f == Date(2023, 12, 31), "1月1-1=上年12月31");
}

void TestStream()
{
	cout << "--- 流 << >> 测试 ---" << endl;
	Date d(2024, 2, 29);
	ostringstream oss;
	oss << d;
	CHECK(oss.str() == "2024/2/29", "operator<< 输出格式 年/月/日");

	istringstream iss("2025 12 31");
	Date in;
	iss >> in;
	CHECK(in == Date(2025, 12, 31), "operator>> 读取年/月/日");
}

int main()
{
	TestConstruct();
	TestCompare();
	TestAdd();
	TestAddConst();
	TestSub();
	TestSubConst();
	TestDateDiff();
	TestIncrement();
	TestLeapYear();
	TestBoundary();
	TestStream();

	cout << "\n========== 测试汇总 ==========" << endl;
	cout << "通过: " << g_pass << "  失败: " << g_fail << endl;
	if (g_fail == 0)
	{
		cout << "全部测试通过!" << endl;
	}
	else
	{
		cout << "存在失败的测试用例!" << endl;
	}
	return g_fail == 0 ? 0 : 1;
}

9.取地址运算符重载

9.1const成员函数

将const修饰的成员函数叫做const成员函数,const修饰成员函数放到成员函数参数列表的后面

const修饰隐含的this指针表明,该成员函数不能对类中任何成员进行修改

const修饰Date中的Print函数,变成const Date*const this

cpp 复制代码
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(234, 5, 6);
	d1.Print();
	const Date d2(1, 2, 3);
	d2.Print();
	return 0;
}

9.2取地址运算符重载

取地址运算符分为普通取地址运算符和const取地址运算符。一般编译器自动生成的就够我们使用

cpp 复制代码
class Date
{
public:
	//Date* operator&()
	//{
	//	return this;
	//}
	const Date* operator&()
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;

};

10.再探构造函数

之前我们实现构造函数时,初始化成员变量注意使用函数体内赋值,构造函数初始化还有另一种形式"初始化列表"。使用方式是:冒号开头,接着是以一个逗号分隔数据成员列表,每个成员变量后面跟一个放在括号中的初始值或者表达式。

每个成员变量在初始化列表中只能出现一次

引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表中进行初始化,否则汇会报错。

C++11支持在成员声明的位置给缺省值,这个缺省值主要是给没有在初始化列表中初始化的成员使用的。

尽量使用初始化,因为那些你不在初始化列表中的成员也会走初始化列表,如果这个成员在声明的位置给了缺省值,就会用这个缺省值初始化,如果没有给缺省值,对于内置类型成员是否初始化就会取决于编译器,对于自定义类型成员,就会调用他的默认构造函数,如果没有默认构造函数就会报错。

初始化列表按照声明顺序进行初始化,与在初始化列表中出现的顺序无关。

总结:无论是否显示写初始化列表,每个构造函数都会有初始化列表

无论是否在初始化列表中显示初始化成员的变量,每个成员都会走初始化列表

cpp 复制代码
#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{

	}
	void Print()
	{
		cout << _hour << endl;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int& x, int year = 1, int month = 1, int day = 1)
		:_year(year),
		_month(month),
		_day(day),
		_t(2),
		_ref(x),
		_n(1)
	{
		
	}
	void Print()const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t; // 没有默认构造
	int& _ref; // 引⽤
	 const int _n; 
};
int main()
{
	int i = 0;
		Date d1(i);
		d1.Print();
	return 0;
}
cpp 复制代码
#include<iostream>
using namespace std;
class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{

	}
	void Print()
	{
		cout << _hour << endl;
	}
private:
	int _hour;
};
class Date
{
public:
	Date()
		:_month(2)
	{
		cout << "Date()" << endl;
	}
	void Print()const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}//如果没有初始化就会使用缺省值
private:
	int _year = 1;
	int _month;
	int _day;
	Time _t=1; // 没有默认构造
	int* _ref=(int*)malloc(sizeof(int)*12); // 引⽤
	 const int _n=2; 
};
int main()
{
	Date d1;
		d1.Print();
	return 0;
}

11.类型转换

C +支持内置类型隐式类型转换为类类型对象,需要相关内置类型为参数的构造函数

构造函数前面加上explicit就不再支持隐式类型转换

类类型的对象之间也可以隐式转换,需要相应的构造函数支持

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a1)
		:_a1(a1)
	{

	}
	A(int a1, int a2)
		:_a1(a1),
		_a2(a2) {

	}

	A(const A& aa)
		:_a1(aa._a1),
		_a2(aa._a2)
	{
	}
	int Sum() const
	{
		return _a1 + _a2;
	}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a1=1;
	int _a2=2;
	
};
class B
{
public:
	B(const A& a)
		:_b1(a.Sum())
	{

	}
	void Print()
	{
		cout << _b1 << endl;
	}
private:
	int _b1=1;
};
int main()
{
	A aa4(1);
	aa4.Print();
	A aa1 = 1;
	aa1.Print();
	A aa3 = { 2,2 };
	B b1 = aa3;
	const B& rb = aa3;
	b1.Print();
	
	return 0;

}

12.static成员

用static修饰的成员变量叫做静态成员变量,静态成员变量要在类外进行初始化

静态成员变量为所以类成员所共享,不属于某个对象,不存在于对象中,存在于静态区中。

用static修饰的成员函数叫做静态成员函数,静态成员函数没有this指针

静态成员函数可以访问其他的静态成员,不能访问非静态的。

非静态的成员函数可以访问静态的成员变量和成员函数

突破类域就可以访问静态成员,类名::静态成员或者对象.静态成员来访问静态成员和静态函数。

静态成员也是类的成员,受public private访问限定符的限制

静态成员变量不能在声明位置中给定缺省值。

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		_scount++;
	}
	A(const A& t)
	{
		t._scount++;
	}
	~A()
	{
		--_scount;
	}
	static int Get()
	{
		return _scount;
	}
private:
	static int _scount;
};
int A::_scount = 0;
int main()
{
	cout << A::Get()<<endl;
	A a1;
	cout << a1.Get() << endl;
	A a3(a1);
	cout << a3.Get() << endl;
	return 0;
}

13.友元

友元提供了一种突破访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明中或类声明中加上friend,并把友元声明放到一个类里面

外部的友元函数可以访问类的私有和保护成员,友元函数仅仅是一种声明,不是类成员函数

友元函数可以在类的任意地方进行声明,不受访问限定符的限制

一个函数可以是多个类的友元函数

友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类的私有和保护成员

友元是单向的

友元的关系不能传递

友元不宜多用

cpp 复制代码
#include<iostream>
using namespace std;
class B;
class A
{
	friend void Func(const A& aa, const B& bb);
private:
	int _a1 = 1;
	int _a2 = 2;
};
class B
{
	friend void Func(const A& aa, const B& bb);
private:
	int _b1 = 1;
	int _b2 = 2;
};
void Func(const A& aa, const B& bb)
{
	cout << aa._a1 << endl;
	cout << bb._b1 << endl;
}
int main()
{
	A a1;
	B b1;
	Func(a1, b1);
	return 0;
}
cpp 复制代码
#include<iostream>
using namespace std;

class A
{
	friend class B;
private:
	int _a1 = 1;
	int _a2 = 2;
};
class B
{
public:
	void Func(const A& aa)
	{
		cout << aa._a1 << endl;
		cout << _b1 << endl;
	}
private:
	int _b1 = 1;
	int _b2 = 2;
};
int main()
{
	A a1;
	B b1;
	b1.Func(a1);
	return 0;
}

14.内部类

如果一个类定义在另一个类的内部,这个类叫做内部类,内部类是一个独立的类,他只受外部类类域限制和访问限定符限制,所以外部类定义的对象,不包含内部类。

内部类默认是外部类的友元类

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
private:
	static int _k;
	int _h = 2;
public:
	class B
	{
	public:
		void Func(const A& a)
		{
			cout << a._h << endl;
			cout << _k << endl;

		}
	};
};
int  A::_k = 1;
int main()
{
	A a;
	A::B b;
	b.Func(a);
	return 0;

}
相关推荐
hansang_IR1 小时前
【代码】分层最短路模板
c++·算法·最短路
暖焰核心1 小时前
C++模板进阶——特化全解
javascript·c++·jquery
lfSeanDragon1 小时前
数据结构-前缀树(Trie)
开发语言·c#
励志不掉头发的内向程序员1 小时前
【LibreCAD 2D架构】从两个坐标到图形实体:RS_ActionDrawLine如何创建RS_Line
开发语言·c++·qt·学习·系统架构
啊阿狸不会拉杆1 小时前
《计算机网络-自顶向下方法》5.2 路由选择算法 读书笔记
计算机网络·算法·路由
Thomas21431 小时前
scala 闭包
开发语言·后端·scala
rannn_1111 小时前
【力扣hot100】多维动态规划|62、64、5、1143、72
算法·leetcode·动态规划
sunshine22 girl1 小时前
Java学习一 环境配置1 安装JDK,配置环境变量
java·开发语言·学习
似水এ᭄往昔2 小时前
【Qt】--常用控件(按钮类控件)
开发语言·qt