类和对象(中)
类的六个默认成员函数
如果一个类中一个默认成员函数也没有那这个类我们叫作空类。
但是如果一个类中每有默认成员函数的话,会怎样呢?答案是编译器会帮我们自动实现类。
拷贝构造
构造函数是一个特殊的成员函数,函数名和类名相同没有返回值。创建类类型对象时,由编译器自动调用。并且在对象整个生命空间周期类只调用一次。
构造函数作用在于初始化对象类似于int a=1;
给啊初始化为1;
我们看一下具体的代码片段:
//重点关注注释部分
//代码是我做题通过后进行拷贝下来的代码,可以适当理解一下,题目最后放在末尾
class date {
public:date(int year, int month, int day , int days) //构造函数,没有返回值,但是不需要void{_year = year;_month = month;_day = day;_days = days;} date(int year = 2025, int month = 4 , int day =23 , int days = 100) //函数重载《也是无参构造函数,无参构造函数一个类中只能存在一个》{_year = year;_month = month;_day = day;_days = days;} int Get_month(int month){int arr_month[13] = { 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){arr_month[2] = 29;}return arr_month[month];}date datecount(){int i = Get_month(_month);_days += _day;while (_days > i){_month++;if (_month > 12){_year++;_month = 1;}_days -= i;i= Get_month(_month);}_day = _days;return *this;}void Print(){//打印年cout << _year << "-";//打印月份if (_month < 10){cout << 0 << _month << "-";}elsecout << _month << "-";//打印日if (_day < 10){cout << 0 << _day << endl;}elsecout << _day << endl;}private:int _year;int _month;int _day;int _days;
};int main()
{int n;cin >> n;while (n--){int year, month, day;int days;cin >> year >> month >> day >> days;date a(year, month, day, days); //编译器自动调用构造函数,用来初始化对象。a.datecount().Print();//日期打印}return 0;
}
还有一点是,构造函数跟其他成员函数一样可支持重载。
你可能会问,如果我没有写构造函数会怎样?
这个时候编译器就会有自己的想法,编译器会自己生成一个无参的构造函数,并进行调用。但是这有时候我们并不知道编译器构造的成员函数是怎样的,不知道是否能正确的完成初始。所以在C++11中打了一个补丁。即在内置类型成员变量在类中声明时可以给默认值
private:int _year = 2025; //这时声明不是初始化int _month = 4; //这时声明不是初始化int _day = 23; //这时声明不是初始化int _days = 24; //这时声明不是初始化
析构函数
析构函数也是一个特殊的成员函数,函数名和类名前加一个 ~
符号,同样没有返回值,也不需要写 void
。
它的作用是:在对象生命周期结束时自动调用,用来进行清理工作,比如释放内存、关闭文件、断开网络连接等。
基本格式:
~类名()
{// 清理操作
}
对于这段日期代码来说,我们的类中没有使用 new
动态分配内存,因此不写析构函数也没关系,编译器会默认生成一个“空析构函数”。
但出于规范,我们可以手动写一个出来作为示例:
class date {
public:// 构造函数略~date(){// 目前这个类没有资源需要手动释放// 这里只是说明析构函数的位置cout << "析构函数被调用,日期对象被销毁" << endl;}// 其他成员函数略private:int _year;int _month;int _day;int _days;
};
注意事项:
- 析构函数不能有参数、不能被重载。
- 如果类中有用
new
申请了堆空间(如int* p = new int
),必须写析构函数释放资源,否则会内存泄漏。 - 析构函数通常不需要手动调用,在对象生命周期结束时自动调用。
示例输出位置:
当 main()
函数中执行完这一行:
date a(year, month, day, days);
对象 a
在这个作用域结束时就会被销毁,自动调用析构函数。为了验证我们可以加入一段测试:
int main()
{{date a(2025, 4, 23, 100);a.datecount().Print();} // 在这个大括号结束的位置,a 被销毁,会调用析构函数return 0;
}
运行时会输出:
2025-08-01
析构函数被调用,日期对象被销毁
小结
函数 | 特点 | 何时调用 |
---|---|---|
构造函数 | 用于初始化对象 | 创建对象时 |
析构函数 | 用于清理资源 | 销毁对象时 |
你可以简单记住它们是“出生与销毁”这一对搭档。
拷贝构造函数
拷贝构造函数用于创建一个新对象,并用一个已有对象进行初始化。
格式:
date(const date& d)
{_year = d._year;_month = d._month;_day = d._day;_days = d._days;cout << "调用拷贝构造函数" << endl;
}
特点:
- 参数是一个同类对象的引用,并且通常加上
const
。 - 如果不写,编译器会默认生成一个“浅拷贝”的拷贝构造函数。
示例:
date d1(2025, 4, 23, 100);
date d2 = d1; // 调用拷贝构造函数
当我们传值或返回对象时,也可能调用拷贝构造:
date foo(date d) // 这里传值会调用拷贝构造
{return d; // 返回也可能触发拷贝
}
赋值运算符重载
用于对象赋值,即:一个已经存在的对象被另一个对象赋值。
格式:
date& operator=(const date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;_days = d._days;}cout << "调用赋值运算符重载" << endl;return *this;
}
注意:
- 需要返回
*this
的引用,以支持链式赋值a = b = c;
- 自赋值检查很重要:
if (this != &d)
- 如果类中有资源分配(如
new
),一定要手动写赋值运算符重载,避免浅拷贝问题。
const 成员函数
void Print() const
{cout << _year << "-" << _month << "-" << _day << endl;
}
特点:
- 在函数后加
const
表示这个函数不会修改对象的成员变量。 - 只能调用其他
const
成员函数。 - 常用于
const
对象,如:
const date d(2025, 4, 23, 100);
d.Print(); // 只能调用 const 函数
取地址及 const 取地址操作符重载
普通取地址:
date* operator&()
{cout << "调用取地址操作符&" << endl;return this;
}
const 取地址:
const date* operator&() const
{cout << "调用 const 取地址操作符&" << endl;return this;
}
这两个可以帮助我们在调试或做特殊封装时了解对象的地址行为。
日期打印