文章目录

  • 1. 前言
  • 2. 类的6个默认成员函数
  • 3. 构造函数
  • 4. 析构函数
  • 5. 拷贝构造函数
  • 6. 运算符重载
    • 6.1 赋值运算符重载
  • 7. const成员
  • 8. 取地址及const取地址操作符重载
  • 9. 日期类对象的完整实现
    • 9.1 头文件
    • 9.2 源文件
    • 9.3 测试代码
  • 10. 结尾

1. 前言

今天我们来继续学习C++类与对象(中)的相关知识点。

2. 类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

6个默认成员函数:

初始化和清理:构造函数主要完成初始化工作、析构函数主要完成清理工作。
拷贝赋值:拷贝构造是使用同类对象初始化创建对象、赋值重载主要是把一个对象赋值给另一个对象。
取地址重载:主要是普通对象和const对象取地址,这两个很少会自己实现。

3. 构造函数

构造函数是一个特殊(不能以普通函数的定义和调用规则去理解)的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
特性
1.函数名与类名相同。
2.无返回值,也不用写void
3.对象实例化时编译器自动调用对应的构造函数。
4.构造函数可以重载。

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 << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};int main(){Date d1;Date d2(2023, 4, 27);d1.Print();d2.Print();return 0;}

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
C++把类型分成内置类型(基本类型)和自定义类型。
内置类型就是语言提供的数据类型如:int / char…,自定义类型就是我们使用class / struct / union等自己定义的类型。
默认生成的构造函数:1、内置类型成员不做处理,2、自定义类型成员会去调用他的默认构造函数
6.C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
7.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

class Date{public://Date()//{//_year = 1;//_month = 1;//_day = 1;//}//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 = 2;//此处不是初始化,是给缺省值int _month = 2;int _day = 2;};int main(){Date d1;//Date d2(2023, 4, 27);d1.Print();//d2.Print();return 0;}

4. 析构函数

与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数是特殊的成员函数
特性
1.析构函数名是在类名前加上字符 ~。
2.无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
注意:析构函数不能重载、内置类型不处理、自定义类型去调用他的析构函数
4.对象生命周期结束时,C++编译系统系统自动调用析构函数。
5.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

typedef int DataType;class Stack{public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType* _array;int _capacity;int _size;};void TestStack(){Stack s;s.Push(1);s.Push(2);}int main(){TestStack();return 0;}

5. 拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
拷贝构造函数也是特殊的成员函数
特性
1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3.若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。而自定义类型是调用其拷贝构造函数完成拷贝的。
4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,类中如果没有涉及资源申请时,拷贝构造函数是否写都可以,一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
5.拷贝构造函数典型调用场景:
使用已存在对象创建新对象、函数参数类型为类类型对象、函数返回值类型为类类型对象

class Date{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date(const Date a)错误Date(const Date& a)//正确{_year = a._year;_month = a._month;_day = a._day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}private:int _year;int _month;int _day;};int main(){Date a1(2023, 5, 10);Date a2(a1);a1.Print();a2.Print();return 0;}

6. 运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表。
其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

注意
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型参数
3.用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不能改变其含义
4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5.(.*)(::)(sizeof)(?:)(.) 注意以上5个运算符不能重载

class Date{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& a){_year = a._year;_month = a._month;_day = a._day;}bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}private:int _year;int _month;int _day;};int main(){Date a1(2023, 5, 10);Date a2(2024, 5, 10);//内置类型可以直接使用运算符运算,编译器知道要如何运算//自定义类型无法直接使用运算法,编译器也不知道要如何运算。想支持,自己实现运算符重载即可cout << (a1 == a2) << endl;return 0;}

6.1 赋值运算符重载

1.参数类型:const T& ,传递引用可以提高传参效率
2.返回值类型:T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3.检测是否自己给自己赋值
4.返回* this :要复合连续赋值的含义
注意
1.赋值运算符只能重载成类的成员函数不能重载成全局函数
2.赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。如果类中未涉及到资源管理,赋值运算符是否实现都可以,一旦涉及到资源管理则必须要实现。

class Date{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator=(const Date& a){if (this != &a){_year = a._year;_month = a._month;_day = a._day;}return *this;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}private:int _year;int _month;int _day;};int main(){Date a1(2023, 5, 10);Date a2;a2.Print();a2 = a1;a2.Print();return 0;}

7. const成员

将const修饰的“成员函数”称之为const成员函数
const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

class A{public:A(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << '-' << _month << '-' << _day << endl;}//void Print(const A* const this)//修饰了this指向的内容,保证了成员函数内部不会修改成员变量,const对象和非const对象都可以调用这个成员函数void Print()const{cout << _year << '-' << _month << '-' << _day << endl;}private:int _year;int _month;int _day;};int main(){A a1(2023,5,13);const A a2(2022, 5, 13);a1.Print();a2.Print();return 0;}

8. 取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义,编译器默认会生成。
特殊使用场景:不想让别人取到这个类型对象的地址

class A{public:A(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}A* operator&(){return nullptr;}const A* operator&()const{return nullptr;}private:int _year;int _month;int _day;};int main(){A a1;const A a2;cout << &a1 << endl;cout << &a2 << endl;return 0;}

9. 日期类对象的完整实现

下面我通过前面所学知识点来实现了一个完整的日期类对象,大家可以通过这个完整的类对象,来和之前所学知识点对比学习。

9.1 头文件

#include using namespace std;class Date{// 友元函数 -- 这个函数内部可以使用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){_year = year;_month = month;_day = day;}//拷贝构造函数Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}//获取某个月的天数int GetMonthDay(int year, int month){static int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };int day = days[month];if (month == 2 && ((year % 4 == 0) && (year % 100 != 0) || year % 400 == 0)){day += 1;}return day;}//打印日期void Print()const;//赋值运算符重载Date& operator=(const Date& d);//==运算符重载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+=(int day);//日期+天数Date operator+(int day)const;//日期-=天数Date& operator-=(int day);//日期-天数Date operator-(int day)const;//日期前置++和后置++//前置++和后置++直接按特性重载,无法区分,所以要特殊处理,使用函数重载区分,后置++重载增加一个int参数跟前置构成函数重载进行区分Date& operator++();Date operator++(int);//日期前置--和后置--Date& operator--();Date operator--(int);//日期-日期int operator-(const Date& d)const;private:int _year;int _month;int _day;};//流插入重载inline ostream& operator<<(ostream& out, const Date& d){out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;}//流提取重载inline istream& operator>>(istream& in, Date& d){in >> d._year >> d._month >> d._day;return in;}

9.2 源文件

#include "Date.h"void Date::Print()const{cout << _year << "年" << _month << "月" << _day << "日" << endl;}Date& Date::operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}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)|| (_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)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+=(int day){if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month > 12){_year++;_month = 1;}}return *this;}Date Date::operator+(int day)const{Date ret = *this;return ret += day;}Date& Date::operator-=(int day){if (day < 0){return *this += -day;}_day -= day;while (_day < 0){_month--;if (_month < 1){_year--;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;}Date Date::operator-(int day)const{Date ret = *this;return ret -= day;}Date& Date::operator++(){return *this += 1;}Date Date::operator++(int){Date ret = *this;*this += 1;return ret;}Date& Date::operator--(){return *this -= 1;}Date Date::operator--(int){Date ret = *this;*this -= 1;return ret;}int Date::operator-(const Date& d)const{int flag = 1;Date max = *this;Date min = d;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){min++;n++;}return n * flag;}

9.3 测试代码

int main(){Date d1(2023, 5, 13);Date d2(2022, 5, 13);Date d3 = d1;d3.Print();cout << (d1 == d2) << endl;cout << (d1 != d2) << endl;cout << (d1 == d3) << endl;cout << (d1 < d2) << endl;cout << (d1 > d2) << endl;cout << (d1 >= d3) << endl;cout << (d1 <= d3) << endl;(d1 + 4000).Print();(d1 - 4000).Print();(++d1).Print();(--d1).Print();cout << (d1 - d2) << endl;(d1 + 40000).Print();(d1 - 40000).Print();cout << d1 << endl;cin >> d1;cout << d1 << endl;return 0;}

测试结果如下

10. 结尾

本篇博客所讲内容我认为是C++类与对象板块乃至C++初学阶段最重要的地方,6个默认成员函数必须掌握,并能自己实现一个完整的简单的类对象,此处一定要多复习,多思考。
最后,感谢各位大佬的耐心阅读和支持,觉得本篇文章写的不错的朋友可以三连关注支持一波,如果有什么问题或者本文有错误的地方大家可以私信我,也可以在评论区留言讨论,再次感谢各位。