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