当前位置: 首页 > news >正文

类和对象(二) - 实践

类和对象(二) - 实践

一、默认成员函数

什么是默认成员函数:用户不自己显示写,编译器默认生成的成员函数就是默认成员函数。

下面是 8 个默认成员函数

  1. 默认构造函数:用于创建对象时初始化对象。如果没有为类定义构造函数,编译器会自动生成一个默认构造函数。
  2. 析构函数:用于在对象生命周期结束时进行清理工作。如果没有定义析构函数,编译器也会提供一个默认的析构函数。
  3. 拷贝构造函数:用于创建一个新对象,该对象是其参数(另一个同类型对象)的副本。如果没有显式定义,编译器会生成一个默认的拷贝构造函数。
  4. 拷贝赋值运算符:用于将一个对象的值赋给另一个同类型的对象。如果没有显式定义,编译器会生成一个默认的拷贝赋值运算符。
  5. 移动构造函数:C++11 引入,用于通过移动而非复制的方式转移资源。如果没有定义,编译器会生成一个默认的移动构造函数(仅当类中没有定义任何构造函数时)。
  6. 移动赋值运算符:C++11 引入,用于通过移动而非复制的方式转移资源给另一个对象。如果没有定义,编译器会生成一个默认的移动赋值运算符(仅当类中没有定义任何赋值运算符时)。
  7. 类型转换运算符:允许类的对象被用作其他类型。例如,可以将类的一个对象用作内置类型(如int或double)。
  8. 虚析构函数:当类包含虚函数时,析构函数通常被声明为虚函数,以支持多态删除。虽然这不是一个默认成员函数,但它是与虚拟函数相关的一个重要概念。

其中 5,6 为 C++ 11 加入,7,8 不是很重要,因此下面介绍前四个。

对于编译器自己生成的函数我们需要了默认生成的函数的行为是什么,能否满足我们的需要,如果不满足自己显示写该如何写。

(一)、构造函数

#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;}private:int _year;int _month;int _day;};int main(){//Date d1(); // 不能这样定义,会和函数声明分不清Date d1;d1.print();Date d2(2025, 11, 12);d2.print();return 0;}

(二)、析构函数

  • 析构函数的作用也不是销毁一个对象,而是在对象声明周期结束的时候清理对象中所调用的资源。
  • 析构函数的特点(定义方式)
    • 函数名为:~类名。
    • 没有参数。
    • 没有返回值。
    • 对象声明周期结束的时候自动调用。
    • 不写编译器会自动生成,其行为对于内置类型不做处理,对于自定义类型调用他们的析构函数。一般没有资源占用的可以不写,编译器生成的就够用了,但是像 stack 这样的需要申请堆上的空间的类需要自己实现来完成资源的释放。
  • C++ 规定对于多个对象,后定义的先析构。
#include <iostream>#include <stdlib.h>using namespace std;class Stack{public:Stack(int n = 4){cout << "Stack(int n = 4)" << endl;_a = (int*)malloc(sizeof(int) * n);_capacity = n;_size = 0;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_capacity = 0;_size = 0;}private:int* _a;int _size;int _capacity;};class Date{public:Date(int year = 1, int month = 1, int day = 1){//  构造函数会先调用自定义类型的构造函数cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;_year = year;_month = month;_day = day;}~Date(){// 析构函数后调用自定义类型的析构函数cout << "~Date" << endl;// 内置类型不做处理// 自定义类型调用他们的析构函数}void print(){cout << _year << " " << _month << " " << _day << endl;}private:int _year;int _month;int _day;Stack st;};int main(){Date d1;return 0;}

(三)、拷贝构造

  • 拷贝构造的功能是用另外一个对象创建初始化一个新的对象。
  • 拷贝构造是构造函数的一个重载函数,它的参数固定为自身类型的重载,外加一些有缺省值的参数。
  • 如果参数不是自身类型的引用,传值传参或者传值返回会调用拷贝构造,这样就会递归下去,因此这样写不合法。
  • 若为显示定义拷贝构造编译器会自动生成,对内置类型完成浅拷贝工作,自定义类型调用他们的拷贝构造。
  • 对于一些指向资源的类型,单单的浅拷贝会导致这些成员指向同一块空间,这时需要深拷贝来为成员变量重新申请空间。
#include <iostream>using namespace std;class Date{public:// 构造函数Date(int year = 1, int month = 1, int day = 1, int n = 4){_year = year;_month = month;_day = day;_size = n;_a = (int*)malloc(n * sizeof(int));}void print(){cout << _year << '/' << _month << '/' << _day << '/' << endl;}// 拷贝构造//Date(const Date d) // 这样写会无穷递归Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;_size = d._size;// 针对于指向资源成员的拷贝不能用浅拷贝,因为资源是相互独立的// 可以为这个对象重新分配资源_a = (int*)malloc(d._size * sizeof(int));}~Date(){free(_a);_a = nullptr;}private:int _year;int _month;int _day;int* _a;int _size;};int main(){Date d1(2025, 1, 1); // 构造Date d2(d1); // 拷贝构造Date d3 = d1; // 拷贝构造 return 0;}

(四)、赋值运算符重载

1. 运算符重载

2. 赋值运算符重载

赋值运算符重载用于已经存在的两个对象的拷贝。

  • 赋值运算符重载是一个运算符重载,规定必须为成员函数。赋值运算符重载的参数建议为const 自身类型的引用, 减少传参拷贝。
  • 有返回值,建议写成自身类型的引用,这样就既可以减少拷贝,又能支持连续的赋值。
  • 没有显示写,编译器生成的赋值运算符重载的行为是对内置类型不做处理,自定义类型调用他们的赋值运算符重载。
  • 还是那句话,如果类对象有指向什么资源就自己实现,否则编译器生成的就够用。

(五)、取地址运算符重载

二、日期类的实现

#include "Date.h"
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if (!checkdate())
{
cout << "非法日期:" << *this << endl;
}
}
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Date::print() const
{
cout << _year << '/' << _month << '/' <<  _day << endl;
}
bool Date::checkdate() const
{
return (_month >= 1 && _month <= 12 && _day >= 1 && _day <= get_monthday());
}
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);
}
bool Date::operator<(const Date& d) const
{
if (_year != d._year) return _year < d._year;
if (_month != d._month) return _month < d._month;
if (_day != d._day) return _day < d._day;
return false;
}
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);
}
Date& Date::operator+=(const int day)
{
if (day < 0) return *this -= -day;
_day += day;
int monthday = -1;
while (_day > (monthday = this->get_monthday()))
{
_day -= monthday;
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
monthday = this->get_monthday();
}
return *this;
}
Date Date::operator+(const int day) const
{
Date tmp(*this);
tmp += day;
return tmp;
}
Date& Date::operator-=(const int day)
{
if (day < 0) return *this += -day;
_day -= day;
int monthday = -1;
while (_day <= 0)
{
_month--;
if (_month <= 0)
{
_year--;
_month = 12;
}
monthday = this->get_monthday();
_day += monthday;
}
return *this;
}
Date Date::operator-(const int day) const
{
Date tmp(*this);
tmp -= day;
return tmp;
}
Date& Date::operator--()
{
*this -= 1;
return *this;
}
Date Date::operator--(int)
{
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;
}
int Date::operator-(Date& d) const
{
Date tmp1(*this);
if (tmp1 < d) return -(d - tmp1);
// d > *this
int day = 0;
while (d != tmp1)
{
day++;
tmp1--;
}
return day;
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "请输入日期:" << endl;
while (1)
{
in >> d._year >> d._month >> d._day;
if (d.checkdate())
{
break;
}
else
{
cout << "非法日期请重新输入" << endl;
}
}
return in;
}
#pragma once
#include <iostream>#include <assert.h>using namespace std;class Date{// 友元函数声明// 允许类外函数访问类的私有成员friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);public:Date(int year = 1, int month = 1, int day = 1);Date(const Date& d);void print() const;bool checkdate() const;int get_monthday() const{assert(_year > 0 && _month <= 12 && _month >= 1);static int monthday[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;return monthday[_month];}//// 取地址运算符重载//Date* operator&()//{//	return this;//}//const Date* operator&() const//{//	return this;//}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+=(const int day);Date operator+(const int day) const;Date& operator-=(const int day);Date operator-(const int day) const;int operator-(Date& d) const;Date& operator--(); // 前置Date operator--(int); // 后置Date& operator++();Date operator++(int);private:int _year;int _month;int _day;};ostream& operator<<(ostream& out, const Date& d);istream& operator>>(istream& in, Date& d);