目录
- 2. 类的六个默认成员函数
- 2.1 构造函数
- 2.1.1 构造函数的定义方式
- 2.2 析构函数
- 2.2.1 析构函数定义方式
- 2.3拷贝构造函数
- 2.3.1 拷贝构造函数的定义方式
- 2.3.2 深拷贝与浅拷贝
- 2.4 赋值运算符的重载
- 2.4.1 运算符重载
- 2.4.2 运算符的重载的定义方式
- 2.4.3 默认成员函数:赋值运算符重载
- 2.4.4 前置++,后置++的运算符重载
- 2.5 类与对象穿插巩固练习:日期类的实现
- 2.6 const成员函数
- 2.7 默认成员函数:取地址操作符重载与const取地址
2. 类的六个默认成员函数
- 默认成员函数:
<1> 成员函数,即所属与定义类的函数,只针对所属类,只有所属类可以调用的函数,而默认成员函数即为定义类时类会默认生成自带的函数。
<2> 既然会默认生成,那么一定的这些函数一定非常重要并且不可或缺,究竟什么样的函数会让类定义时要默认生成呢,我们接下来进行学习
2.1 构造函数
- 在C语言的学习中,我们定义过各种各样的结构体,这些结构体声明出来,只是单纯开辟了对应的空间,而空间里的值是随机的,因此对新创建结构体变量的初始化是必不可少的,会影响到后续操作。
- 对于C++中的类,也是如此,因此对实体化对象的初始化必不可少且十分重要,而我们自主编写时,会经常忘记定义或者调用,所以在C++中就添加了此函数的默认生成,自动调用的内容。
2.1.1 构造函数的定义方式
- 构造函数在整个对象的声明周期中会自动调用,且调用一次
- 构造函数只负责初始化不会开辟空间
- 构造函数的形式:
<1> 函数名与类名相同
<2> 无返回值
<3> 实例化对象会自动调用相应的构造函数
<4> 构造函数可以重载
- 默认生成的构造函数
内置类型:
class Date
{
public:int _year;int _month;int _day;void Print(){cout << _year << ' ' << _month << ' ' << _day << endl;}
};Date d1;
d1.Print();
自定义类型:
class stack
{
public:int _capacity;int _top;stack(int capacity = 0, int top = 0){cout << "stack()" << endl;_capacity = capacity;_top = top;}
};class queue
{
public:stack push_st;stack pop_st;
};queue q1;
- 虽然编译器会帮助我们生成构造函数,可是默认生成的构造函数并不让人满意
<1> 默认生成的构造函数对内置类型会赋予随机值
<2> 对自定义类型会去调用它们的默认构造函数- 所以,在日常中我们还是应该去自己给类定义需要适合的构造函数
- 构造函数的定义方式
[类名](传递形参)
{//函数操作
}
class Date
{
private:int _year;int _month;int _day;Date(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}
};
- 带参数构造函数的传参方式:在实例化对象是直接进行传参,同时也支持缺省参数
Date d1(2024, 3, 8);
Date d2(2024);
- C++11中,为默认构造函数对成员变量初始化为随机值的这一操作,做了补丁优化
我们在定义类的成员变量时,可以在定义处赋予变量初始默认值,这样在我们没有定义构造函数时,自定义类型的成员变量也会有合法的初始值
class Date
{
private:int _year = 2024;int _month = 1;int _day = 1;
};
- 默认构造函数不止仅仅是只,我们未定义编译器默认生成的函数
<1> 未定义编译器默认生成的构造函数
<2> 没有参数的构造函数
<3> 参数为全缺省参数的构造函数
以上三者统称为默认构造函数
2.2 析构函数
- 存在资源申请,就一定会存在资源的返还,我们已经知晓了如何创建一个对象,那么,我们如何销毁它呢。
- 正常情况下创建的变量会在自己的声明周期结束自动销毁,可是我们动态开辟的空间,这类资源只要程序不结束,我们不去主动销毁那么它就会一直存在,当一段程序需要不停的运行时,这一点就是致命的,因此析构函数对实例化对象资源的管理就尤为重要。
2.2.1 析构函数定义方式
- 默认生成的析构函数不会进行资源的深度清理(指针所指向动态开辟的空间),只会进行资源的简单清理
class A
{
private:int* _a;int _b;A(){//销毁_b = 10;//所指向空间不会被释放_a = (int*)malloc(_b * sizeof(int));}
};
- 析构函数的特征及其定义方式
<1> 函数名由:~
+ 类名构成
<2> 没有参数,没有返回值
<3> 一个类只存在一个析构函数,不存在重载
<4> 析构函数会在实例化对象生命周期结束时自动调用
~[类名]()
{//操作
}
class B
{
privare:int* _a;;B(){_a = (int*)malloc(sizeof(int));}~B(){cout << "~B()" << endl;free(_a)}
};
- 自定义类型的成员变量,默认析构函数会去调用它自己的析构函数
拓展练习
2.3拷贝构造函数
- 使用另一个已经存在的实例化对象,将其作为模板,创建一个与之一样的新对象
2.3.1 拷贝构造函数的定义方式
- 拷贝构造函数的特征:
<1> 拷贝构造函数为构造函数的函数重载
<2> 拷贝构造函数只有一个参数,参数类型为所处类的类型引用
- 补充:拷贝构造函数参数类型必须为引用类型的原因(产生无穷递归)
//err
Date (const Date d)
{}
2.3.2 深拷贝与浅拷贝
- 拷贝构造也为类的默认成员函数之一,当我们不去自己定义时,编译器自主生成一个拷贝构造函数,可是此拷贝构造只会进行值拷贝,不会对动态申请得进行拷贝。
- 我们将值拷贝称为浅拷贝,将对原变量的深度资源也进行拷贝的操作称为深拷贝。
- 当自定义类中没有需要深度拷贝的资源时,我们也可以省略拷贝构造的定义,让编译器自动生成。
- 拷贝构造函数使用场景:
<1> 用已存在的对象创建一个一样的新对象
<2> 函数参数为类(生成临时变量)
<3> 函数返回值为类(生成临时变量)
Date d1(2024, 3, 9);
//场景1:
Date d2(d1);void Print(Date d)
{cout << d._year << endl;
}
//场景2
Print(d2);//场景3:
Date f()
{Date d3(2024);return d3;
}
- 上述场景为生成临时变量而调用拷贝构造,在一些时候可以使用,引用传参(不改变值),引用返回(原对象不被销毁)来规避这一额外开销。
2.4 赋值运算符的重载
2.4.1 运算符重载
- 我们知道编程语言也是一门语言,是用来描述问题与计算机沟通的语言。
- C++中,我们引入了类与对象的概念,让计算机可以更好更贴切的描述我们所处的世界。
<1> 对象是更加复杂,贴近于现实的变量,C中的内置类型变量有着自己运算操作符于运算规则
<2>可是,内置类型的运算符不能使用于更复杂的类上,这就导致当对进行这类复杂对象间的计算无法进行,由此,C++引入了运算符重载的概念。
- 运算符重载的本质是函数,这类函数以类似于运算符的函数调用方式,来达到近似运算符的操作。
2.4.2 运算符的重载的定义方式
[函数返回值] operator[操作符](函数参数)
{//.......
}
- 运算符重载的注意事项:
<1> 不能通过运算符重载来创建原本不存在的运算符,如operator¥
<2> 不能通过运算符重载来更改原本内置类型操作符的含义
<3> 操作符.*
,::
,sizeof
,: ?
,.
,这五个操作符不能重载
2.4.3 默认成员函数:赋值运算符重载
- 赋值运算符:拥有两个操作数,会返回被赋值后的变量
- 赋值运算符的优化:
<1> 传参时,因为不会改变参数原本的值,所以可以直接传引用(const 类&)
<2> 因为被赋值的对象不会被销毁,所以可以返回引用(返回自身支持连续赋值)
<3> 赋值时应进行检测,不能让对象自己给自己赋值
class A
{
public:int _a;int _b;A(int a = 0, int b = 0){_a = a;_b = b;}A& operator=(const A& x){if(&x != *this){_a = x._a;_b = x._b;}return *this;}
};
- 注:重载的赋值运算符只能作为类的成员函数,不能作为全局函数(缺少this指针)
- 因为是默认成员函数,当我们没有定义时,编译器会自行生成,但与拷贝构造相似,默认生成的赋值运算符重载进行的也是浅拷贝,可能会导致资源的丢失。
2.4.4 前置++,后置++的运算符重载
- 我们已经初步学习了运算符的重载,现在我们来学习一下运算符重载中比较特殊的两个运算符,前置++与后置++
- 每当需要对一个运算符进行重载时,我们在思考实现逻辑之前,首先应该确定的是运算符有几个操作数,是否有返回值。当我们以这样的视角去观察前置++与后置++这一对操作符时,我们发现它们重载时它们参数是相同,无法进行区分,C++语言在出现了逻辑漏洞,无法形成逻辑的闭环。
- 其实,上述无法逻辑闭环的逻辑漏洞在语言的设计上时有出现,这种情况下,一律都会采取特殊化处理,这里的两个++运算符同样如此。
前置++操作符的重载方式:
[返回值] operator++()
{//......
}
后置++操作符的重载方式:
[返回值] operator++(int)
{//......
}
2.5 类与对象穿插巩固练习:日期类的实现
实现目标:
- 构造,拷贝构造,析构函数
- 赋值运算符==,+,-,前置++,后置++,+=,<,>等运算符的重载
Date.h
class Date
{
private:int _year;int _month;int _day;
public:int GetMonthDay(int year, int month){int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };int day = MonthDay[month];if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){if (month == 2){day++;}}return day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}Date(int year = 2024, int month = 1, int day = 1);//缺省参数在声明处定义Date(const Date& d);~Date();Date& operator=(const Date& d);Date operator+(int day);int operator-(const Date& d);Date operator-(int day);Date& operator+=(int day);Date& operator-=(int day);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);//前置Date& operator++();//后置Date operator++(int);Date& operator--();Date operator--(int);
};
Date.cpp
- 默认成员函数
Date::Date(int year, int month, int day)
{//检查日期合法assert(year >= 1);assert(month >= 1 && month <= 12);assert(GetMonthDay(year, month) >= day && day >= 1);_year = year;_month = month;_day = day;
}Date::Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}Date::~Date()
{}Date& Date::operator=(const Date& d)
{if (&d != this){_year = d._year;_month = d._month;_day = d._day;}return *this;
}
- 其他运算符重载
补充:运算重载的复用顺序
<1>先+,后+=:创建拷贝对象(拷贝构造1次)+ 返回临时变量(拷贝构造1次)+ 赋值给this指针(拷贝1次)+ 返回this指针
<2> 先+=,后+:创建拷贝对象(拷贝构造1次)+返回临时变量(拷贝构造1次)
//2.
Date& Date::operator+=(int day)
{//1. 先加后合法化//2. 累加法_day += day;int month_day = GetMonthDay(_year, _month);while (_day > month_day){_day -= month_day;_month++;if (_month > 12){_year++;_month = 1;}month_day = GetMonthDay(_year, _month);}return *this;
}Date Date::operator+(int day)
{Date d(*this);return *this += day;
}//先实现-=
Date& Date::operator-=(int day)
{_day -= day;while (_day < 1){_month--;if (_month < 1){_year--;_month = 12;}int month_day = GetMonthDay(_year, _month);_day += month_day;}return *this;
}Date Date::operator-(int day)
{Date d(*this);return d -= day;
}//差值法
//int Date::operator-(const Date& d)
//{
// //确定先后,前 - 后
// Date max(*this);
// Date min(d);
//
// if (max._year < min._year)
// {
// max = d;
// min = *this;
// }
//
// //计算到1月1日的差值
// int max_day = 0;
// for (int i = 1; i < max._month; i++)
// {
// max_day += GetMonthDay(max._year, i);
// }
// max_day += max._day - 1;
//
// int min_day = 0;
// for (int i = 1; i < min._month; i++)
// {
// min_day += GetMonthDay(min._year, i);
// }
// min_day += min._day - 1;
//
// //两年1月1日之间的差值
// int year_day = 0;
// while (min._year < max._year)
// {
// year_day += 365;
// if ((min._year % 4 == 0 && min._year % 100 != 0) || min._year % 400 == 0)
// {
// year_day++;
// }
// min._year++;
// }
//
// return year_day - min_day + max_day;
//}//累计法
int Date::operator-(const Date& d)
{Date max(*this);Date min(d);if (max._year < min._year){max = d;min = *this;}int day = 0;while (max != min){min++;day++;}return day;
}
-
操作符重载差值法思路图解:
- 逻辑比较运算符:
bool Date::operator==(const Date& d)
{if (_year == d._year && _month == d._month && _day == d._day){return true;}return false;
}bool Date::operator>(const Date& d)
{if (_year > d._year){return true;}else if (_year == d._year && _month > d._month){return true;}else if (_year == d._year && _month > d._month && _day > d._day){return true;}return false;
}bool Date::operator<(const Date& d)
{return !(*this == d) && !(*this > d);
}bool Date::operator>=(const Date& d)
{return *this > d || *this == d;
}bool Date::operator<=(const Date& d)
{return *this < d || *this == d;
}bool Date::operator!=(const Date& d)
{return !(*this == d);
}//前置
Date& Date::operator++()
{return *this += 1;
}
//后置
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}Date& Date::operator--()
{return *this -= 1;
}Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}
2.6 const成员函数
- 被this指针被修饰const修饰的成员函数即被称为const成员函数(const Date* const this),被const修饰的this指针其指向的内容不能被改变。
- 因为this指针为隐藏参数,我们无法直接对其进行修饰,C++中采用了后缀修饰的方式,具体如下:
Date::Print() const
{count << _year << '-' << _month << '-' << _day << endl;
}
- 函数传参时的权限缩可以缩小但不能扩大,所以
<1> const对象不可以调用非const成员函数(权限扩大,const Date* const this => Date* const this)
<2> 非const对象可以调用const成员函数(权限缩小,Date* const this => const Date* const this)
<3> const成员函数内不可以调用其它的非const成员函数(权限扩大)
<4> 非const成员函数内可以调用其它的const成员函数(权限缩小)
2.7 默认成员函数:取地址操作符重载与const取地址
这两个默认成员函数一般不用我们自己定义,编译器默认生成的已经满足绝大部分情况。(当想要别人获得额外的一些信息时,才会选择重载)
Date* operator&()
{return this;
}const Date* operator&() const
{return this;
}