目录
前言:类的6个默认成员函数
一, 构造函数
1. 概念
2. 特性
二, 析构函数
2.1 概念
2.2 特性
2.3 牛刀小试
三, 拷贝构造函数
3.1概念
3. 2 特点
四, 赋值运算符重载
4. 1 运算符重载
五, const成员函数
六,取地址及const取地址操作符重载
七,练习——实现日期计算器
结语
前言:类的6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
一, 构造函数
1. 概念
2. 特性
- 1. 函数名与类名相同。
- 2. 无返回值
- 3. 对象实例化时编译器自动调用对应的构造函数。
- 4. 构造函数可以重载。(后面拷贝构造函数会体现)
- 5. 如果存在未自定义默认构造函数,编译器不再生成默认构造函数。
下面是一段自定义构造函数的:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 5, int day = 9) // 自定义默认构造函数,设置全缺省参数,对数据进行初始化。
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date z;
Date z1(2012, 12, 12); // 由于我们自定义构造函数支持带参数设置数据初始化。
// 接下来,我们注释掉自定义默认构造函数,来测试一下编译器默认自动生成的构造函数。
}
当我们注释掉我们自定义的构造函数时,我们会发现z对象 的类成员变量,依旧是随机值。这里我们不禁会想,编译器自动生成的默认构造函数似乎什么也没做??
- 内置类型就是语言提供的数据类型,如:int/char...;
- 自定义类型就是我们使用class/struct/union等自己定义的类型。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 5, int day = 9) // 自定义默认构造函数,设置全缺省参数,对数据进行初始化。
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
class Time
{
public:
Date _t; // 自定义类型
private: // 内置类型
int _hour;
int _minute;
int _second;
};
int main()
{
Time k;
}
调试结果如下:
特点:
1. 内置函数不做处理。
2. 自定义类型会调用(自定义类型的)默认构造函数。
这里我们就有了两种对内置成员初始化的方法:
- 1. 通过C++补丁初始化;()
- 2. 自定义默认构造函数,同时给缺省参数。
二, 析构函数
2.1 概念
2.2 特性
- 1. 析构函数名是在类名前加上字符 ~。
- 2. 无参数无返回值类型。
- 3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
首先我们来看下面代码:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 5, int day = 9) // 默认构造函数
{
_year = year;
_month = month;
_day = day;
}
~Date() // 默认析构函数
{
_year = _month = _day = 0;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date z;
return 0;
}
我们将显式析构函数注释掉,让我们测试一下编译器自动生成的默认析构函数的结果,下面的程序我们会看到:编译器生成的默认析构函数,对自定类型成员调用它的析构函数。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 2023, int month = 5, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
cout << "~Date()" << endl; // 对调用Date的析构函数进行标记
_year = _month = _day = 0;
}
private:
int _year;
int _month;
int _day;
};
class Time
{
public:
Date _t; // 自定义类型
~Time()
{
cout << "~Time()" << endl; // 对调用Time的析构函数进行标记
}
private: // 内置类型
int _hour = 10;
int _minute = 10;
int _second = 10;
};
void func()
{
Time z;
}
int main()
{
func();
return 0;
}
结果可以看出:
2.3 牛刀小试
看下面代码,输出顺序是什么?
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" <<_a<< endl;
}
~A()
{
cout << "~A()->" <<_a<<endl;
}
private:
int _a;
};
A aa3(3);
void f()
{
static int i = 0;
static A aa0(0);
A aa1(1);
A aa2(2);
static A aa4(4);
}
// 构造顺序:3 0 1 2 4 1 2
// 析构顺序:~2 ~1 ~2 ~1 ~4 ~0 ~3
int main()
{
f();
f();
return 0;
}
核心思路:遵循栈的思路,先进后出。
三, 拷贝构造函数
3.1概念
在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。
3. 2 特点
- 1. 拷贝构造函数是构造函数的一个重载形式。
- 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
下面是会发生无穷递归代码:
class Date
{
public:
Date(int year = 2023, int month = 5, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
Date(Date b1) // 正确代码:Date(Date& b1)
{
_year = b1._year;
_month = b1._month;
_day = b1._day;
}
private:
int _year;
int _month;
int _day;
};
无穷递归如图:
换成类类型引用即可解决无穷递归的问题。
class Time
{
public:
Time(int hour = 1 , int minute = 2, int second = 3)
{
_hour = hour;
_minute = minute;
_second = second;
}
Time(const Time& a)
{
_hour = a._hour;
_minute = a._minute;
_second = a._second;
cout << "Time(Time& a)" << endl;
}
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 2023, int month = 5, int day = 9)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& b1) // 正确代码:Date(Date& b1)
{
_year = b1._year;
_month = b1._month;
_day = b1._day;
}
Time _t;
private:
int _year;
int _month;
int _day;
};
void func()
{
Date z;
Date k(z);
}
int main()
{
func();
return 0;
}
我们发现,编译器自动生成的拷贝构造函数,足够我们进行值拷贝,那么我们还需要自定义拷贝构造函数吗?
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
所以类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,需要深度拷贝,则拷贝构造函数是一定要写的,否则就是浅拷贝。
四, 赋值运算符重载
4. 1 运算符重载
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义。
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
下面有有几个应用例子:
class Time
{
public:
Time(int hour = 1 , int minute = 2, int second = 3)
{
_hour = hour;
_minute = minute;
_second = second;
}
Time(const Time& a)
{
_hour = a._hour;
_minute = a._minute;
_second = a._second;
cout << "Time(Time& a)" << endl;
}
bool operator==(const Time& a)
{
return _hour == a._hour &&
_minute == a._minute &&
_second == a._second;
}
bool operator>(const Time& a)
{
return _hour > a._hour &&
_minute > a._minute &&
_second > a._second;
}
Time& operator=(const Time& a) // 内成员赋值运算符重载
{
_hour = a._hour;
_minute = a._minute;
_second = a._second;
return *this;
}
~Time()
{
cout << "~Time()" << endl;
}
private: // 内置类型
int _hour;
int _minute;
int _second;
};
这里对赋值运算符进行补充;
1.赋值运算符只能重载成类的成员函数不能重载成全局函数
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2;
s2 = s1;
return 0;
}
结果分析:
五, const成员函数
让我们看看下面的实例代码吧:
class moss
{
public:
moss(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Print() const // 这就是const成员函数,同上面的Print函数是重载函数。
// 试试将尾巴上的const 删去,看看会有什么事情发生。
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test3()
{
moss d1(2022, 1, 13);
d1.Print();
const moss d2(2022, 1, 13);
d2.Print();
}
首先我们已经知道const具有限制权限的功能,比如 int this,如图:
对本次事例解析:
六,取地址及const取地址操作符重载
这个比较容易理解,这两个默认成员函数一般不用重新定义 ,编译器默认会生成。 看下面代码:
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
七,练习——实现日期计算器
用C++类编写一个日期计算器,利用今天的运算符重载知识,实现日期+天数,日期-天数,日期-日期的功能。
下面是代码分享:
头文件:
#pragma once
#include<iostream>
using namespace std;
#include <assert.h>
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month)
{
// 因为需要频繁调用,所以写成内联函数。
static int M[13] = { 0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = M[month];
if (month == 2 &&
((year % 4 == 0 && year % 100 != 0) ||
(year % 400 == 0)))
{
day++;
}
return day;
}
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
if (!CheckDate())
{
cout << "非法日期" << endl;
exit(-1);
}
}
bool CheckDate()
{
if (_month > 12 || _day > GetMonthDay(_year, _month))
{
return false;
}
else
{
return true;
}
}
// 拷贝构造函数
// d2(d1)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
// 析构函数, 全是内置函数,没什么好清理的,调用编译器自动生成的就行。
~Date()
{
;
}
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day);
// 日期-天数
Date operator-(int day);
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
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);
// 日期-日期 返回天数
int operator-(const Date& d);
int WeekDay()
{
Date start(1, 1, 1);
int LeveDay = 1;
int day = (*this - start);
LeveDay += day;
int WeekDay = LeveDay % 7;
return WeekDay;
}
// 友元函数为外边全局函数拥有调用类成员的权限。
friend ostream& operator<<(ostream& cou, const Date& a);
friend istream& operator>>(istream& cin, Date& a);
void Print()const // 本身是 Date* const this--是不能修改其指向的this,
//但可以修改其内容,为了能接受被const Date* const this的对象所以需要缩小权限(方法就是函数名后加const)
//反正print函数没有修改功能。
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
// 放在全局中可以外部调用重载后的运算符,但放在外面又不能访问类成员,这里会用到后面的友元函数。
// 重载cout输出流
inline ostream& operator<<(ostream& cou, const Date& a)
{
cou << a._year << "年" << a._month << "月" << a._day << "日";
return cou;
}
// 重载cint提取流
inline istream& operator>>(istream& cin, Date& a)
{
cin >> a._year >> a._month >> a._day;
assert(a.CheckDate());
return cin;
}
函数实现源文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
// 日期+=天数
Date& Date::operator+=(int day)
{
_day += day;
while ( Date::GetMonthDay(_year, _month) < _day)
{
_day -= Date::GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
// 日期+天数 让我们得出结果的临时拷贝,所以可以复用+= ,
// 但有一个缺点是,日期其实已经被污染,不能在原日期下重新调用。
Date Date::operator+(int day)
{
*this += day;
Date tmp = *this;
return tmp;
}
// 日期-天数 //可以复用 -= ,但原日期被修改。
Date Date::operator-(int day)
{
*this -= day;
Date tmp = *this;
return tmp;
}
// 日期-=天数
Date& Date::operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
_month--; // 如果_month == 0了,就提取不了对应月份的天数
if (_month == 0)
{
_year--;
_month = 12;
}
_day += Date::GetMonthDay(_year, _month);
}
return *this;
}
// 前置++ 加完后返回
// 为了区分重载函数前置与后置++,C++做了重载区分,就是后置++增加一个int参数。
Date& Date::operator++()
{
// 可以进行复用+=
* this += 1;
return *this;
}
// 后置++
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
// 后置--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// // >运算符重载
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 _year == d._year &&
_month == d._month &&
_day == d._day;
}
// >=运算符重载
bool Date::operator >= (const Date& d)
{
return (*this > d) || (*this == d);
}
// <运算符重载
bool Date::operator < (const Date& d)
{
if ((_year < d._year) ||
((_year <= d._year) && (_month < d._month)) ||
((_year <= d._year) && (_month <= d._month) && _day < d._day))
{
return true;
}
else
{
return false;
}
}
// <=运算符重载
bool Date::operator <= (const Date& d)
{
return (*this < d) || (*this == d);
}
// !=运算符重载
bool Date::operator != (const Date& d)
{
return _year != d._year &&
_month != d._month &&
_day != d._day;
}
// 日期-日期 返回天数 思路:用小的加到大的,计算中间的次数。
int Date::operator-(const Date& d)
{
int flog = 1;
Date a1(*this); // 假设a1大
Date a2(d);
if (a1 < a2) // a1小替换为a2
{
a1 = d;
a2 = *this;
flog = -1;
}
int n = 0;
while (a2 < a1)
{
/*if (a2._day == 9 && a2._month == 5)
{
int x = 0;
}*/
++a2;
n++;
}
return n * flog;
}
测试源文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
void Test()
{
const char* WeekRoom[7] = { "周天", "周一", "周二", "周三", "周四", "周五", "周六" };
do
{
cout << "------------------------------------" << endl;
cout << "--------请输入要操作的选项----------" << endl;
cout << "----1. 日期+天数---2.日期-天数----" << endl;
cout << "----3. 日期-日期---0.结束程序 --------" << endl;
cout << "----4. --------" << endl;
int opertaton = 0;
cin >> opertaton;
Date a1, a2;
int day;
switch (opertaton)
{
case 1:
cout << "-----------"<< endl;
cin >> a1 >> day;
cout << (a1 += day) << endl;
cout << WeekRoom[a1.WeekDay()] << endl;
break;
case 2:
cout << "-----------" << endl;
cin >> a1 >> day;
cout << (a1 -= day) << endl;
cout << WeekRoom[a1.WeekDay()] << endl;
break;
case 3:
cout << "-----------" << endl;
cin >> a1 >> a2;
cout << (a1 - a2);
break;
case 0:
cout << "结束程序";
exit(-1);
default:
break;
}
} while (true);
}
int main()
{
Test();
return 0;
}
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论;如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。