个人主页:Lei宝啊
愿所有美好如期而遇
目录
类的6个默认成员函数
构造函数
概念
特性
析构函数
概念
特性
拷贝构造函数
概念
特性
赋值运算符重载
运算符重载
赋值运算符重载
前置++和后置++重载
日期类的实现
类的6个默认成员函数
class null //null是类名
{
};
构造函数
概念
我们可以先通过一个对象的初始化函数引入。
#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 << "-";
cout << _month << "-";
cout << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a;
a.Init(2023, 10, 20);
a.Print();
return 0;
}
每一次我们创建出一个对象都要手动为其初始化,如果忘了的话,轻点是随机值,重点程序就崩了,所以我们的构造函数就解决了这个问题。
看代码。
#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;
}
void Print()
{
cout << _year << "-";
cout << _month << "-";
cout << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a;
a.Print();
return 0;
}
我们可以看到我们a对象里的年月日确实有值了,被我们所写的的默认构造函数所调用,并全部赋值为1.
接下来我们试试有参数的构造函数。
int main()
{
Date a(2023,10,21);
a.Print();
return 0;
}
我们发现编译器调用的是我们有参数的构造函数。
特性
其 特征 如下:1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器 自动调用 对应的构造函数。4. 构造函数可以重载。
在上述代码中,也体现出了构造函数的这四个特性。
但是有几点我们要注意
1:没有参数的构造函数,系统默认的构造函数,全缺省的构造函数,都叫做默认构造函数,而这三个构造函数不可同时写出,任意二者不可同时存在,我们看代码解释。
class Date
{
public:
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year = 10, int month = 10, int day = 10)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a;
return 0;
}
因为编译器无法区分到要调用哪个构造函数,所以就报错了。
2:当我们定义了构造函数后,编译器不会再生成默认构造函数,但这样也会出现一个小问题需要我们去掌控。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a;
return 0;
}
因为我们自定义了一个构造函数,所以系统不会再生成默认构造函数,而我们实例化的对象要调用无参的构造函数,但是我们又没有无参的构造函数,所以编译器就只能报错,我们后面会讲到有解决方法。
3.如果说我们没有写构造函数,内置类型的成员变量使用系统的默认构造函数,不会做处理,自定义类型的成员变量会去调用他自己的默认构造函数。
解释:内置类型就是系统自带的,比如int,double之类的,像类和结构体等就是自定义类型。
class Stack
{
public:
Stack(int capacity = 4)
{
_capacity = capacity;
int top = 0;
int* a = (int*)malloc(sizeof(int) * capacity);
}
~Stack()
{
free(_a);
_top = 0;
_capacity = 0;
}
private :
int* _a;
int _top;
int _capacity;
};
class Queue
{
Stack _a;
Stack _b;
int size;
};
int main()
{
Stack a;
Queue b;
return 0;
}
细心的朋友们可能会发现size被初始化为0了,你不是说内置类型的变量不初始化吗?是的,但是不同的编译器处理结果不同,我这个是Visual Studio 2022 ,在2013下是不会给初始化的,我们想要我们写出的代码具有跨平台性,就不要寄希望于编译器会给优化,所以我们就当做他不会给优化处理,写出优质代码。
4:C++11新特性,允许声明成员变量时给默认值。
class Date
{
public:
void Print()
{
cout << _year << _month << _day;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
int main()
{
Date a;
a.Print();
return 0;
}
析构函数
概念
特性
析构函数 是特殊的成员函数,其 特征 如下:1. 析构函数名是在类名前加上字符 ~ 。2. 无参数无返回值类型。3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数
我们来测试一下。
class Stack
{
public:
Stack(int capacity = 4)
{
_capacity = capacity;
int top = 0;
int* a = (int*)malloc(sizeof(int) * capacity);
}
~Stack()
{
free(_a);
_top = 0;
_capacity = 0;
cout << "haha" << endl;
}
private :
int* _a;
int _top;
int _capacity;
};
class Queue
{
Stack _a;
Stack _b;
int size;
};
int main()
{
Queue b;
return 0;
}
结果显而易见,我们的确是调用了析构函数。
拷贝构造函数
概念
特性
我们先来写一段代码证明拷贝构造函数的必要性
首先是日期类的测试
class Date
{
public:
Date(int year = 2023, int month = 10, int day = 22)
{
_year = year;
_month = month;
_day = day;
cout << "Date的构造" << endl;
}
private:
int _year;
int _month;
int _day;
};
void func1(Date a)
{
cout << "func1(Date a)" << endl;
}
int main()
{
Date d1(2023, 10, 22);
func1(d1);
return 0;
}
class Stack
{
public:
Stack(int capacity)
{
_capacity = capacity;
int top = 0;
_a = (int*)malloc(sizeof(int) * _capacity);
if (_a == nullptr)
{
perror("malloc");
}
cout << "Stack的构造" << endl;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
cout << "Stack的析构" << endl;
}
private:
int* _a;
int _top;
int _capacity;
};
void func2(Stack st)
{
//...
}
int main()
{
Stack st1(4);
func2(st1);
return 0;
}
这个栈类再这么调用就会出问题,当st销毁时,去调用我们所写的析构函数,在func2函数结束时会释放一次_a,当主函数中的对象st1销毁时,会对已经释放的那块空间再释放一次,这样程序就崩溃了,因为此时的_a就是野指针了,别忘了我们是传值,空间释放后_a就成了野指针,释放野指针指向的空间是不合法的,所以就崩了。
那也许有人会说,我传引用不就好了吗,但是假设我们有一个需求,不改变对象本身,就是要拷贝一份去实现,那么拷贝构造就凸显出作用来了。(如果还有人说栈这个类我用系统默认的析构函数不好吗?那您可真是昏了头了)
Stack(int capacity)
{
_capacity = capacity;
int top = 0;
_a = (int*)malloc(sizeof(int) * _capacity);
if (_a == nullptr)
{
perror("malloc");
}
cout << "Stack的构造" << endl;
}
//拷贝构造
Stack(const Stack& st)
{
_top = st._top;
_capacity = st._capacity;
_a = (int*)malloc(sizeof(int) * _capacity);
if (_a == nullptr)
{
perror("malloc");
}
}
Stack(const Stack st)
{
_top = st._top;
_capacity = st._capacity;
_a = (int*)malloc(sizeof(int) * _capacity);
if (_a == nullptr)
{
perror("malloc");
}
}
接下来我们通过图解来看一看无穷递归的出现原因。
所以我们要加上&。
Stack st2(st1);
void func2(Stack st)
{
//...
}
Date func3(Date d)
{
//如果出了作用域,传值给d的对象还存在,
//那么引用返回更好,这里我们只是为了举例子
//而且我们是推荐传引用的
return d;
}
返回的时候,d已然销毁,所以我们返回的其实是d的拷贝。
赋值运算符重载
这里我们还是会通过一些例子来引入这个概念。
我们打算写个日期类来解释,在此之前,我们需要先明白,日期和日期的相加减有没有意义,比如
2023-10-23 + 2023-10-24,显然是没有意义的,但是他们之间相减就有了意义;两个日期相差多少天,那么日期间的乘除也是没有意义的;再看2023-10-23 + 50天,这个显然也是有意义的,计算50天日期是多少;这么说的话,其实日期的比较也是有意义的,相等和大小。
那么我们如何去实现日期间的比较和日期加减天数呢?
但是这些成员变量都是私有的,在类外我们不能直接用,当然,我们可以在类里写上GetYear函数等来取得他们的值进行比较,我们这里只是测试,就先将他们公开,当然这样的代码是不健康的,但是我们也说了,只是测试。
class Date
{
public:
Date(int year = 2023, int month = 10, int day = 22)
{
_year = year;
_month = month;
_day = day;
cout << "Date的构造" << endl;
}
int _year;
int _month;
int _day;
};
bool equal(Date d1, Date d2)
{
if (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day)
return true;
else
return false;
}
int main()
{
Date d1;
Date d2(2023, 2, 13);
bool ret = equal(d1, d2);
cout << ret << endl;
return 0;
}
当然,这样会更好些
bool equal(const Date& d1, const Date& d2)
{
if (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day)
return true;
else
return false;
}
接下来进行大小比较函数,起个什么名字好呢?compare?比大还是比小?我怎么知道,compare_large? 当下,我们先随便起名字。
class Date
{
public:
Date(int year = 2023, int month = 10, int day = 22)
{
_year = year;
_month = month;
_day = day;
cout << "Date的构造" << endl;
}
int _year;
int _month;
int _day;
};
bool equal(const Date& d1, const Date& d2)
{
if (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day)
return true;
else
return false;
}
bool compare(const Date& d1, const Date& d2)
{
if (d1._year > d2._year)
{
return true;
}
else if (d1._year == d2._year && d1._month > d2._month)
{
return true;
}
else if (d1._year == d2._year && d1._month == d2._month && d1._day > d2._day)
{
return true;
}
return false;
}
int main()
{
Date d1;
Date d2(2023, 2, 13);
bool ret1 = equal(d1, d2);
bool ret2 = compare(d1, d2);
cout << ret1 << endl;
cout << ret2 << endl;
return 0;
}
我们可以想想内置类型定义出的变量,都是可以直接比较和相加减的,那么我自定义类型定义的对象能不能也直接比较相加减呢?答案是不能,编译器无法理解对象如何比较,比如我们的d1和d2,怎么确定大小呢?年月日都大于才算大于吗?由此,我们引入一个新的概念,运算符重载,不进解决了函数名不便于理解的问题,还可以进行对象的比较。
运算符重载
class Date
{
public:
Date(int year = 2023, int month = 10, int day = 22)
{
_year = year;
_month = month;
_day = day;
cout << "Date的构造" << endl;
}
//比较相等
bool operator==(const Date& d2)
{
if (_year == d2._year &&_month == d2._month && _day == d2._day)
return true;
else
return false;
}
//比较大于
bool operator>(const Date& d2)
{
if (_year > d2._year)
{
return true;
}
else if (_year == d2._year && _month > _month)
{
return true;
}
else if (_year == d2._year && _month == d2._month && _day > d2._day)
{
return true;
}
return false;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2023, 2, 13);
//bool ret1 = equal(d1, d2);
//bool ret2 = compare(d1, d2);
bool ret1 = d1 == d2; //d1.operator==(d2);
bool ret2 = d1 > d2; //d1.operator>(d2);
cout << ret1 << endl;
cout << ret2 << endl;
bool ret3 = d1.operator==(d2);
bool ret4 = d1.operator>(d2);
cout << ret1 << endl;
cout << ret2 << endl;
return 0;
}
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
写在类外面,只写一个参数就是如下图结果;
因为大于的比较有两个操作数。
接下来是日期加上一个数。
·
class Date
{
public:
Date(int year = 2023, int month = 10, int day = 22)
{
_year = year;
_month = month;
_day = day;
cout << "Date的构造" << endl;
}
int GetMonthday(int year, int month)
{
int month_day[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))
{
return 29;
}
return month_day[month];
}
Date& operator+=(int day)
{
_day += day;
while (_day > GetMonthday(_year,_month))
{
_day -= GetMonthday(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1 += 50;
d1.Print();
return 0;
}
但是这样我们把对象d1就做了修改,我们不想修改,只想+然后返回来一个值怎么办?
将下面这个代码加入类中
Date operator+(int day)
{
//实例化一个对象tmp拷贝*this
Date tmp(*this);
//复用operator+=
tmp += 50;
return tmp;
}
int main()
{
Date d1;
Date d2 = d1 + 50;
d2.Print();
return 0;
}
接下来就是我们的赋值运算符重载了。
Date& Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
前置++和后置++重载
前置和后置++,最难以区分的其实是他们的名字,尽管返回值不同,但参数相同,不构成重载,于是就有了一个特殊规定,看代码
//前置++复用operator+=
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++复用operator+=
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
于是我们用参数类型来区分,一个不传,一个传int类型,编译器就可以区分了。
一般情况下我们最好还是用前置++,是引用返回,而且没有拷贝,后置++就有一次拷贝,也许一次调用差距不大,但是如果调用10万次就有差别了。
日期类的实现
接下来我们将使用各种运算符重载实现各种有意义的日期比较和加减,
头文件
#pragma once #include <iostream> using namespace std; class Date { public: Date(int year = 2023, int month = 10, int day = 24); int GetMonthday(int year, int month); //operator+复用opreator+=,优于operator+=复用operator+ Date operator+(int day); Date operator-(int day); //计算日期减天数 int operator-(const Date& d); //计算日期和日期间差几天 Date& operator+=(int day); Date& operator-=(int day); Date& 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); bool operator<=(const Date& d); //前置++复用operator+= Date& operator++(); //后置++复用operator+= Date operator++(int); void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; };
日期类的实现
#include "Date.h" Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; } int Date::GetMonthday(int year, int month) { int month_day[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)) { return 29; } return month_day[month]; } Date& Date::operator+=(int day) { _day += day; while (_day > GetMonthday(_year, _month)) { _day -= GetMonthday(_year, _month); _month++; if (_month == 13) { _year++; _month = 1; } } return *this; } Date Date::operator+(int day) { Date tmp(*this); tmp += 50; return tmp; } Date Date::operator-(int day) { Date tmp(*this); tmp -= day; return tmp; } Date& Date::operator-=(int day) { if (day < 0) { return *this += -day; } _day -= day; while (_day <= 0) { --_month; if (_month == 0) { --_year; _month = 12; } _day += GetMonthday(_year, _month); } } Date& Date::operator=(const Date& d) { _year = d._year; _month = d._month; _day = d._day; return *this; } //前置++复用operator+= Date& Date::operator++() { *this += 1; return *this; } //后置++复用operator+= Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; } bool Date::operator>(const Date& d) { if (_year > d._year) { return true; } else if (_year == d._year && _month > _month) { return true; } else if (_year == d._year && _month == d._month && _day > d._day) { return true; } return false; } bool Date::operator==(const Date& d) { if (_year == d._year && _month == d._month && _day == d._day) return true; else return false; } bool Date::operator!=(const Date& d) { return !(*this == d); } bool Date::operator<(const Date& d) { return !(*this >= d); } bool Date::operator>=(const Date& d) { return *this > d || *this == d; } bool Date::operator<=(const Date& d) { return !(*this <= d); } int Date::operator-(const Date& d) { int flag = 1; Date max = *this; Date min = d; if (max < min) { max = d; min = *this; flag = -1; } int n = 0; while (min != max) { ++min; ++n; } return n * flag; }
测试
#include "Date.h" int main() { Date d1; //测试赋值运算符重载 Date d2 = d1; cout << "测试d2赋值运算符重载" << endl; d2.Print(); //测试+运算符重载 Date d3(2023, 10, 25); d3 = d3 + 50; cout << "测试d3+运算符重载" << endl; d3.Print(); //测试-运算符重载 Date d4(2023, 10, 25); d4 = d4 - 50; cout << "测试d4-运算符重载" << endl; d4.Print(); //测试==运算符重载 Date d5(2023, 10, 25); d5 = d5 - 50; cout << "测试d5==运算符重载" << endl; cout << (d4 == d5) << endl; //测试!=运算符重载 Date d6(2023, 10, 25); cout << "测试d6!=运算符重载" << endl; cout << (d4 == d6) << endl; //测试++运算符重载 Date d7(2023, 10, 25); cout << "测试d7++运算符重载" << endl; ++d7; d7.Print(); //测试>=运算符重载 Date d8(2023, 10, 25); cout << "测试d8>=运算符重载" << endl; cout << (d7 >= d8) << endl; cout << endl << "全部通过" << endl; return 0; }
测试结果