• 【C++初阶】三、类和对象(中)


    在上一篇类和对象中,我们初步了解了类和对象的基本概念,知道了什么是类,接下来一起看看类和对象的具体细节和实现吧。(以日期类举例)

    类和对象【中】

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

    如果一个类中什么成员都没有,简称为空类

    class Date {};//空类
    
    • 1

    空类中什么都没有吗?并不是的,任何类在什么都不写时,编译器会自动生成6个默认成员函数
    在这里插入图片描述

    默认成员函数: 用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

    注意: 这里说的是用户没有显示实现,编译器才会自动生成,若我们自己显示定义了,那编译器就不会生成了。

    2. 构造函数

    2.1 构造函数定义

    构造函数是一个特殊的成员函数,名字与类名相同创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次

    构造函数结构如图:
    在这里插入图片描述

    2.2 构造函数特性

    构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

    其特征如下:

    1. 函数名和类名相同
    2. 无返回值
    3. 对象实例化时编译器自动调用对应的构造函数。
    4. 构造函数可以重载
    class Date
    {
    public:
    	//无参的构造函数
    	Date()//构造函数--函数名和类名相同,且无返回值
    	{
    		_year = 0;
    		_month = 1;
    		_day = 1;
    	}
    	//带参的构造函数
    	Date(int year, int month, int day)//构造函数可以重载
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    //调用无参的构造函数
    Date d1;
    //调用带参的构造函数
    Date d2(2022,3,13);
    
    • 1
    • 2
    • 3
    • 4

    注意:调用构造函数只能在对象实例化的时候,且调用无参的构造函数不能带括号,否则会当成函数声明。

    // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
    Date d3();//Err - 函数声明
    
    • 1
    • 2

    从上方我们可以总结构造函数显示定义的三种方式,如下:
    构造函数显示定义的三种方法:

    • 无参构造函数
    • 带参的构造函数
    • 全缺省构造函数
    1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

     那编译器自动生成的默认构造函数能否实现成员变量的初始化呢?

    class Date
    {
    public:
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	Date  d1;
    	d1.Print();
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    我们看如下运行结果:
    在这里插入图片描述

    可以看到这3个值依旧是随机值,那是不是说在这里编译器生成的默认构造函数没有什么用呢??

    解答:
      不是的,C++吧类型分成 内置类型(基本类型)自定义类型(class、struct等)

    1. 编译器生成的默认构造函数对内置类型的成员不进行处理,对自定义类型的成员会去调用它自己的默认构造函数。如果不存在(调有参时),编译器会报错

    示例:

    class Time
    {
    public:
    	Time()
    	{
    		cout << "Time()" << endl;
    		_hour = 0;
    		_minute = 0;
    		_second = 0;
    	}
    private:
    	int _hour;
    	int _minute;
    	int _second;
    };
    class Date
    {
    public:
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    private:
    	// 基本类型(内置类型)
    	int _year;
    	int _month;
    	int _day;
    
    	// 自定义类型
    	Time _t;
    };
    int main()
    {
    	Date d1;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    运行结果如下:
    在这里插入图片描述
    显然这是调用了 _t的默认构造函数

    注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
    示例

    class Time
    {
    public:
    	Time()
    	{
    		cout << "Time()" << endl;
    		_hour = 0;
    		_minute = 0;
    		_second = 0;
    	}
    private:
    	int _hour;
    	int _minute;
    	int _second;
    };
    class Date
    {
    public:
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    private:
    	// 基本类型(内置类型)
    	int _year = 1970;//注意:这里不是初始化,这里相当于给的缺省值
    	int _month = 1;
    	int _day = 1;
    
    	// 自定义类型
    	Time _t;
    };
    int main()
    {
    	Date d1;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    运行结果如下:
    在这里插入图片描述

    看到这里我们可能有个疑惑?那什么是默认的构造函数呢??

    1. 无参的构造函数全缺省的构造函数我们没写,编译器默认生成的构造函数都称为默认构造函数(即不传参也可以调用的构造函数),并且防止冲突默认构造函数只能有一个

    举例:

    class Date
    {
    public:
    	Date()
    	{
    		_year = 1900;
    		_month = 1;
    		_day = 1;
    	}
    	Date(int year = 1900, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	//这里能编译的过吗
    	Date d1;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    如下:
    在这里插入图片描述
    注意: 有参、半缺省的都不是默认构造函数,仅仅是普通的构造函数,只有不用传参就可一调用的才是默认构造函数,且默认构造函数只能有一个

    一个类中最好要一个默认构造函数,因为当该类对象被当作其他类的成员时,系统只会调用默认的构造函数。

    3.析构函数

    3.1 析构函数定义

    析构函数:与构造函数功能相反,对象在销毁时会自动调用析构函数,完成类的一些资源清理工作,主要清理的是对象中创建的一些成员变量比如动态开辟的空间等。
    在这里插入图片描述

    3.2 析构函数特性

    1. 析构函数名是在类名前加上字符 ~
    2. 无参无返回类型
    3. 一个类有且只有一个析构函数 ,故不支持重载。若未显式定义,系统会自动生成默认的析构函数。
    4. 对象生命周期结束时,系统自动调用析构函数完成清理工作
    class Stack
    {
    public:
    	Stack(int capacity = 4)
    	{
    		cout << "Stack(int capacity = 4)" << endl;
    
    		_a = (int*)malloc(sizeof(int)*capacity);
    		if (_a == nullptr)
    		{
    			perror("malloc fail");
    			exit(-1);
    		}
    
    		_top = 0;
    		_capacity = capacity;
    	}
    	//析构函数
    	~Stack()
    	{
    		cout << "~Stack()" << endl;
    
    		free(_a);
    		_a = nullptr;
    		_top = _capacity = 0;
    	}
    
    
    private:
    	int* _a;
    	int _top;
    	int _capacity;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    1. 编译器生成的默认析构函数,对内置类型不进行处理,对自定类型成员调用它的析构函数。

    在这里插入图片描述

    1. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

    析构函数调用的顺序

    多个对象调用析构函数的顺序和创建对象的顺序是相反的,因为哪个对象先压栈哪个对象就后销毁。
    在这里插入图片描述
    注意: 在有全局对象和静态对象的时候,先构造全局对象,在构造局部静态对象,最后才构造普通对象

    对象在销毁时自动调用析构函数,这样的机制可以避免忘记释放空间以免内存泄漏的问题

    不释放内置类型的成员也是有一定道理的,防止释放一些文件指针等等可能导致程序崩溃。

    总结:系统默认生成构造函数和析构函数会对自定义类型成员会调用它的构造和析构


    4. 拷贝构造函数

    4.1 拷贝构造函数定义

    拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

    拷贝构造函数也是构造函数,所以拷贝构造函数也是构造函数的一个重载。

    拷贝构造函数结构与调用:
    在这里插入图片描述

    4.2 拷贝构造函数特征

    1. 拷贝构造函数是构造函数的一个重载形式
    2. 拷贝构造函数的参数只有一个必须使用引用传参,使用传值方式会引发无穷递归调用

    解释传值为什么会引起无穷递归:
      因为传值调用要将实参传给形参,而将实参传给形参只是临时拷贝一份给形参,形参的改变不影响实参,而拷贝给形参的过程也是一个拷贝构造,而调用拷贝构造函数又要传值调用。这样就会造成死递归的情况。
    在这里插入图片描述

    1. 一般拷贝构造另一个对象时,都不希望原对象发生改变,所以形参引用,用const修饰。
    2. 若未显示定义,系统会生成默认的拷贝构造函数。 默认的拷贝构造函数对内置类型按内存存储的字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。对自定义类型是调用该自定义类型成员的拷贝构造函数完成拷贝的

    浅拷贝是有风险的,请看如下代码:

    class Stack
    {
    public:
    	//构造
    	Stack(int capacity = 4)
    	{
    		cout << "Stack(int capacity = 4)" << endl;
    
    		_a = (int*)malloc(sizeof(int)*capacity);
    		if (_a == nullptr)
    		{
    			perror("malloc fail");
    			exit(-1);
    		}
    
    		_top = 0;
    		_capacity = capacity;
    	}
    	//析构
    	~Stack()
    	{
    		cout << "~Stack()" << endl;
    
    		free(_a);
    		_a = nullptr;
    		_top = _capacity = 0;
    	}
    	void Push(int x)
    	{
    		// ....
    		// 扩容
    		_a[_top++] = x;
    	}
    private:
    	int* _a;
    	int _top;
    	int _capacity;
    };
    int main()
    {
    	Stack st1;
    	st1.Push(1);
    	st1.Push(2);
    
    	Stack st2(st1);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    程序运行结果如下:
    在这里插入图片描述
    直接崩了,这是为什么呢?我们进行调试一下看看:
    在这里插入图片描述
    我们调试一下就可以发现,这两个对象的指针指向的地址是同一块空间的,那么当我们程序结束,会调用析构函数,那么就相当于对同一块空间析构了两次,所以编译才会报错。
    在这里插入图片描述

    所以注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

    因为对于析构函数来说,如果涉及到资源申请,就需要自己写析构函数,如果没有,则可以不用写,所以我们可以总结一下:

    总结:

    需要写析构函数的类,都需要写深拷贝的拷贝构造
    不需要写析构函数的类,默认生成的浅拷贝的拷贝构造就可以用

    5.运算符重载

    运算符重载是C++的一重点内容,运算符重载使得对象也可以用加减乘除等利用各种运算符来进行相加相减比较大小等有意义的运算。默认情况下C++不支持自定义类型像内置类型变量一样使用运算符的,需通过运算符重载函数来定义才可以使用。

    5.1 运算符重载

    定义: 运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似,重载函数实现后由编译器自动识别和调用。

    函数名为:关键字operator后面接需要重载的运算符符号。如:operator+,operator=

    函数原型返回值类型 operator 操作符(参数列表)

    注意:

    • 只能重载已有的运算符,不能通过连接其他符号来创建新的操作符:比如 operator@
    • 重载操作符必须有一个类类型参数
    • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
    • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
    • .*::sizeof?:.  注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

    运算符重载不像构造函数是固定在类中的特殊成员函数,运算符重载适用于所有自定义类型对象,并不单独局限于某个类。运算符想使其作用于某个类时,有以下几种方法:

    1. 运算符重载成全局的,这时需要将成员变量的访问权限变成共有,但这样就破环了类的封装性,是最不可取的。
    2. 运算符重载成全局的,使用友元函数,这样就不需要修改访问限定符了 - - - 我们在下方类和对象下中会详细说明
    3. 提供获取成员变量的接口。比如Java中经常使用Getter Setter方法。
    4. 将运算符重载函数放到类中变成成员函数,但需要注意修改一些细节。作为类成员的重载函数,形参列表默认隐藏 this 指针,所以必须去掉一个引用参数。

    运算符重载成全局的:

    // 全局的operator==
    class Date
    { 
    public:
     Date(int year = 1900, int month = 1, int day = 1)
       {
            _year = year;
            _month = month;
            _day = day;
       }    
    //private:         //成员变量的访问权限为共有
     int _year;
     int _month;
     int _day;
    };
    // 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
    // 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
    bool operator==(const Date& d1, const Date& d2)
    {
        return d1._year == d2._year
       		&& d1._month == d2._month
            && d1._day == d2._day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    使用友元函数:

    // 全局的operator==
    class Date
    { 
    public:
    	//友元函数
    	friend bool operator==(const Date& d1, const Date& d2);
    	
    	Date(int year = 1900, int month = 1, int day = 1)
        {
             _year = year;
             _month = month;
             _day = day;
        }    
    private:        
    	int _year;
    	int _month;
    	int _day;
    };
    
    bool operator==(const Date& d1, const Date& d2)
    {
        return d1._year == d2._year
       		&& d1._month == d2._month
            && d1._day == d2._day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    提供获取成员变量的接口:

    class Date
    {
    public:
    	Date(int year = 1900, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	
    	//提供函数接口
    	int Getyear()
    	{
    		return _year;
    	}
    	
    	int Setyear(int year)
    	{
    		_year = year;
    	}
    	//....
    private:         
    	int _year;
    	int _month;
    	int _day;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    将运算符重载函数放到类中变成成员函数:

    class Date
    {
    public:
    	Date(int year = 1, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	
    	// operator==
    	// bool operator==(Date* this, const Date& d)
        // 这里需要注意的是,左操作数是this,指向调用函数的对象
    	bool operator==(const Date& d)
    	{
    		return _year == d._year
    			&& _month == d._month
    			&& _day == d._day;
    	}
    	
    private:
    	int _year;
    	int _month;
    	int _day;
    
    };
    
    int main()
    {
    	Date d1(2022, 1, 20);
    	Date d2(2022, 1, 19);
    
    	cout << (d1 == d2) << endl;// ==>d1.operator==(d2) 
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    调用方式与普通运算调用类似
    d1 == d2 <= => d1.operator==(d2)  d1 > d2 <==> d1.operator>(d2)
    两种调用方式等价

    5.2 赋值运算符重载

    赋值运算符重载属于类的默认成员函数的一种

    1. 赋值运算符重载的格式
      • 参数类型: const T&,传递引用可以提高传参效率
      • 返回值类型: T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
      • 检测是否自己给自己赋值
      • 返回*this: 要复合连续赋值的含义
    class Date
    {
    public:
    	Date(int year = 1900, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    	//赋值运算符重载
    	Date& operator=(const Date& d)
    	{
    		// 检测是否自己给自己赋值
    		if (this != &d)
    		{
    			_year = d._year;
    			_month = d._month;
    			_day = d._day;
    		}
    
    		return *this;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    1. 赋值运算符只能重载成类的成员函数不能重载成全局函数
    class Date
    {
    public:
    	Date(int year = 1900, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	//注意,这里没有将类的成员变量设为私有
    	int _year;
    	int _month;
    	int _day;
    };
    // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
    Date& operator=(Date& left, const Date& right)
    {
    	if (&left != &right)
    	{
    		left._year = right._year;
    		left._month = right._month;
    		left._day = right._day;
    	}
    	return left;
    }
    
    // 编译时会失败:
    // error C2801: “operator =”必须是非静态成员
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述
    原因: 赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数

    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;
    	}
    
    	~Stack()
    	{
    		if (_array)
    		{
    			free(_array);
    			_array = nullptr;
    			_capacity = 0;
    			_size = 0;
    		}
    	}
    	void Push(const DataType& data)
    	{
    		// CheckCapacity();
    		_array[_size] = data;
    		_size++;
    	}
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    在这里插入图片描述
    我们运行之后就会报错,这里的原因和我们上方讲到的浅拷贝的风险是一样的:

    在这里插入图片描述

    5.3 与拷贝构造函数的异同

    相同:没有显示定义时,编译器都会默认生成一个,对于内置类型进行字节序的浅拷贝,对自定义类型会调用它自身的拷贝构造函数或operator=。
    不同:
    拷贝构造:用一个已经存在的对象初始化一个马上要创建的对象
    赋值重载:两个已经存在的对象之间进行赋值。

    Date d1(2022, 11, 11);
    Date d2 = d1; // 这是拷贝构造不是赋值
    
    Date d3;
    d3 = d1;//这才是赋值,d1与d3都已经存在
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.4 前置++和后置++重载

    前置++和后置++最大的区别就是返回值不同,前置是返回变化之后的值后置是返回变化之前的值,两个重载后,都是operator++,我们如何区分呢?
    一般operator++默认是前置++为了区分后置++,我们通常会在参数列表加一个占位参数,这个参数必须是int类型的,从而构造成函数重载。实现如下:

    class Date
    {
    public:
    	Date(int year = 1900, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	// 前置++:返回+1之后的结果
    	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
    	Date& operator++()
    	{
    		_day += 1;
    		return *this;
    	}
    	// 后置++:
    	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
    	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
    	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
    	//而temp是临时对象,因此只能以值的方式返回,不能返回引用
    	Date operator++(int)
    	{
    		Date temp(*this);
    		_day += 1;
    		return temp;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    int main()
    {
    	Date d1(2022, 1, 13);
    	Date d;
    	d = d1++;    // d: 2022,1,13   d1:2022,1,14
    
    	Date d2(2022, 11, 11);
    	Date A;
    	A = ++d2;    // A: 2022,11,12   d2:2022,11,12
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    运行结果如图:
    在这里插入图片描述

    6. const成员函数

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

    一般我们在类成员函数后面加一个const 就会使得this指针的类型变为const Date* const

    比如:

    // d1 == d2
    //const Date* const this
    bool Date::operator==(const Date& d)  const
    {
    	return _year == d._year
    		&& _month == d._month
    		&& _day == d._day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    7. 取地址重载

    这两个默认成员函数一般不用重新定义,编译器默认会生成。
    这两个运算符一般不需要重载,使用编译器默认生成的即可。

    class AA
    {
    public:
    	AA* operator&()
    	{
    		return this;
    	}
    
    	const AA* operator&() const
    	{
    		return this;
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    8. 日期类的实现

    学完上述几个默认成员函数后,我们就可以实现一个比较完整的类了。

    8.1 日期类各接口的声明

    class Date
    {
    	//构造函数
    	Date(int year = , int month = 1, int day = 1);
    	//打印
    	void Print() const ;
    	//获取每月的天数
    	int GetMonthDay(int year, int month) const;
    	//拷贝构造
    	Date(const Date& d);
    	//赋值运算符重载 - - - 赋值运算符重载,只能定义成类的成员函数
    	Date& operator=(const Date& d);
    	//析构函数- - - 可以不用显示定义,编译器生成的默认析构函数就够用了,因为没有涉及到资源的管理
    	//~Date();
    
    	//比较运算符重载
    	// 实现 > 或 < 和 == 其他的复用
    	// 不仅仅是Date类可以这样子,所有的类要实现比较,都可以用这种方式
    	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;
    
    	//算数运算符重载
    	// d1 += 100
    	Date& operator+=(int day);
    	// d1 + 100
    	Date operator+(int day)const;
    	//d1 -= 100
    	Date& operator-=(int day);
    	// d1 - 100
    	Date operator-(int day)const;
    
    	// ++d1;
    	Date& operator++();
    	
    	// d1++; 后置为了跟前置++,进行区分
    	// 增加一下int参数占位,跟前置++,构成函数重载
    	Date operator++(int);
    	
    	Date& operator--();
    	Date operator--(int);
    
    	// offerDay - today
    	int operator-(const Date& d) const;//求两日期之间的天数
    	
    
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    8.2 日期类的接口实现

    获取每月日期天数:

    //获取某年某月的天数
    int Date::GetMonthDay(int year, int month)
    {
    	static int monthDayArry[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;
    	}
    	else
    	{
    		return monthDayArry[month];
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    构造函数:
    我们在定义一个日期类时,要注意日期是否合法:

    	//构造函数 -- 直接定义在类内
    	Date(int year = 1949, int month = 9, int day = 27)
    		:_year(year)
    		, _month(month)
    		,_day(day)
    	{
    		if (!(year > 1
    			&& (month >= 1 && month <= 12)
    			&& (day >= 1 && day <= GetMonthDay(year, month))))
    		{
    			cout << "非法日期" << endl;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    拷贝构造函数:

    //拷贝构造函数
    Date::Date(const Date& d)
    {
    	_year = d._year;
    	_month = d._month;
    	_day = d._day;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    赋值运算符重载:

    //赋值运算符重载
    Date& Date::operator=(const Date& d)
    {
    	if (&d != this)
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    比较运算符重载:
    我们可以实现 == 和 > 的运算符重载,其他的我们可以进行复用

    // ==运算符重载
    //d1 == d2
    bool Date::operator==(const Date& d) const
    {
    	return _year == d._year
    		&& _month == d._month
    		&& _day == d._day;
    }
    
    // >运算符重载
    //d1 > d2
    bool  Date::operator>(const Date& d)  const
    {
    	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;
    }
    
    // >=运算符重载
    //d1 >= d2
    bool  Date::operator >= (const Date& d) const
    {
    	return *this == d || *this > d;
    }
    
    // <运算符重载
    //d1 < d2
    bool  Date::operator < (const Date& d) const
    {
    	return !(*this >= d);
    }
    
    // <=运算符重载
    //d1 <= d2
    bool  Date::operator <= (const Date& d) const
    {
    	return !(*this > d);
    }
    
    // !=运算符重载
    //d1 != d2
    bool  Date::operator != (const Date& d) const
    {
    	return !(*this == d);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    算数运算符重载:

    1. 加法运算符重载
      加法运算符重载实现的意义是:日期 + 天数。不是日期 + 日期(无意义)
      那日期+天数如何实现呢?如下:
      在这里插入图片描述
      我们的加法运算符重载有 += 和 +,这两种有什么区别呢?

    注意:+= 改变了自身的值,而 + 并未改变自身

    //d1 += 100 -- 注意:d1+=100,d1变了,返回的是d1变化之后的日期
    Date& Date::operator+=(int day)
    {
    	if (day < 0)//天数如果是小于0,就相当于 - 
    	{
    		//可以复用 -=
    		return *this -= abs(day);//abs是库里的函数,取绝对值
    	}
    	_day += day;
    	//检查天数是否合法
    	while (_day > GetMonthDay(_year, _month))
    	{
    		_day -= GetMonthDay(_year, _month);
    		++_month;
    
    		if (_month == 13)
    		{
    			++_year;
    			_month = 1;
    		}
    	}
    	return *this;
    }
    
    //d1 + 100 -- 注意:d1+100,d1是不变的,返回的是加了之后的结果
    Date Date::operator+(int day) const
    {
    	Date ret(*this);
    	ret += day;
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    1. 减法运算符重载
      原理与加法运算符重载一致
    // 日期-=天数
    Date& Date::operator-=(int day)
    {
    	//天数如果是小于0,就相当于 +
    	if (day < 0)
    	{
    		return *this += abs(day);
    	}
    
    	_day -= day;
    
    	while (_day <= 0)
    	{
    		--_month;
    		if (_month == 0)
    		{
    			--_year;
    			_month = 12;
    		}
    
    		_day += GetMonthDay(_year, _month);
    	}
    
    	return *this;
    }
    
    // 日期-天数
    Date Date::operator-(int day) const
    {
    	Date ret(*this);
    
    	ret -= day;
    
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    1. 前置++(- -)与后置++(- -)
      我们这个在上方的前置++与后置++重载的区别那讲过

    前置++,返回的是变化之后的值(先++后使用)
    后置++,返回的是变化之前的值(先使用后++)
    前置- - 与 后置- - 和++原理一致

    
    //前置++ -- 返回++之后的值
    //this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
    Date& Date::operator++()
    {
    	*this += 1;
    	return *this;
    }
    
    //后置++ -- 多一个int参数主要是为了和前置区分(不能擅自修改成其他的类型参数 -- 这是语法规定死的)
    //返回++之前的值
    // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
    //而tmp是临时对象,因此只能以值的方式返回,不能返回引用
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    日期 - 日期:

    // 日期-日期 返回天数  d1-d2
    int Date::operator-(const Date& d) const
    {
    	Date Max = *this;
    	Date Min = d;
    	int flag = 1;
    
    	if (*this < d) //如果 *this小的话,两者相减,得到的天数就是负数
    	{
    		Max = d;
    		Min = *this;
    		flag = -1;
    	}
    
    	int count = 0;//记录天数
    	while (Min != Max)
    	{
    		++count;
    		++Min;
    	}
    
    	return count * flag;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    8.3 日期类实现整体代码

    代码我上传到了我的gitee上,感兴趣的小伙伴可以去查看呀~
    日期类实现

  • 相关阅读:
    跨境电商旺季来临,做好这8点爆单不用愁
    JS提升:手写发布订阅者模式(小白篇)
    IDT 一款自动化挖掘未授权访问漏洞的信息收集工具
    Git合并出现MERGING有效解决方法
    Java新手小白入门篇 API -Socket网络编程
    企业电子招标采购系统源码Spring Cloud + 前后端分离 + 二次开发
    学习Opencv(蝴蝶书/C++)——1. 前言 和 第1章.概述
    力扣第406题 根据身高重建队列 c++ 贪心思维
    主流跨域方式解析!
    Java版工程行业管理系统源码-专业的工程管理软件-提供一站式服务
  • 原文地址:https://blog.csdn.net/m0_58124165/article/details/123341210