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


    大家好我是沐曦希💕

    1.赋值运算符重载

    1.1 运算符重载

    C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

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

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

    注意:

    • 不能通过连接其他符号来创建新的操作符:比如operator@
    • 重载操作符必须有一个类类型参数
    • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
    • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
    • .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
    //全局operator==
    class Date
    {
    public:
    	Date(int year = 1970, 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;
    }
    int main()
    {
    	Date d1(2022, 10, 15);
    	Date d2(2022, 10, 14);
    	//cout << d1 == d2 << endl;
    	//<<--流插入,运算符优先级大于==,所以d1==d2要加括号
    	cout << (d1 == d2) << endl;//d1 == d2 会转换成operator==(d1,d2)
    	cout << operator==(d1, d2) << endl;//也可以显示调用,一般不会这样
    	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

    在这里插入图片描述

    class Date
    {
    public:
    	Date(int year = 1970, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    		//检测日期的合法性
    		if (!(year >= 1 
    			&& (month >= 1 && month <= 12) 
    			&& (day >= 1 && day <= GetMonthDay(year, month))))
    		{
    			cout<<"非法日期"<<endl;
    		}
    	}
    	// bool operator==(Date* this, const Date& d2)
    	// 这里需要注意的是,左操作数是this,指向调用函数的对象
    	bool operator==(const Date& d2)
    	{
    		return _year == d2._year
    			&& _month == d2._month
    			&& _day == d2._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
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    1.1.1 运算符重载实现

    >
    bool 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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    >=,<,<=,!=
    bool operator>=(const Date& d)
    {
    	return *this == d || *this > d;
    }
    bool operator<(const Date& d)
    {
    	return !(*this >= d);
    }
    bool operator<=(const Date& d)
    {
    	return !(*this > d);
    }
    bool operator!=(const Date& d)
    {
    	return !(*this == d);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    +=,+
    int GetMonthDay(int year, int month)
    {
    	int MonthDayArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    	if ((((year % 4 == 0) && (year % 100 != 0)) 
    		|| (year % 400 == 0)) && month == 2)
    	{
    		return 29;
    	}
    	else
    	{
    		return MonthDayArray[month];
    	}
    }
    Date& operator+=(int day)
    {
    	_day += day;
    	while (day > GetMonthDay(_year, _month))
    	{
    		_day -= GetMonthDay(_year, _month);
    		++_month;
    		if (_month == 13)
    		{
    			_year += 1;
    			_month = 1;
    		}
    	}
    	return *this;
    }
    Date operator+(int day)
    {
    	Date ret(*this);
    	ret._day += 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

    1.2 赋值运算符重载

    1. 赋值运算符重载格式
      1.1 参数类型:const T&,传递引用可以提高传参效率
      1.2 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
      1.3 检测是否自己给自己赋值
      1.4 返回*this :要复合连续赋值的含义

    赋值重载既是默认成员函数,又是运算符重载。

    void TestDate()
    {
    	Date d1;
    	Date d2(2022, 10, 9);
    
    	Date d3(d2);//拷贝构造(初始化)  一个初始化另一个马上要创建的对象
    	 
    	d1 = d2;//赋值重载(复制拷贝)     已经存在两个对象之间拷贝
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    赋值重载实现:

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

    对于赋值重载(复制拷贝)和拷贝构造的区别:
    赋值重载(复制拷贝) 已经存在两个对象之间拷贝
    拷贝构造(初始化) 一个初始化另一个马上要创建的对象

    赋值运算符重载不写,编译器会生成一个默认的,对内置类型进行值拷贝(即浅拷贝),对自定义类型调用器赋值重载。
    但是如果用编译器默认的赋值运算符重载又会发生什么?

    #include
    using namespace std;
    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(const Stack& st)
    	{
    		cout << "Stack(const Stack&st" << endl;
    
    		_a = (int*)malloc(sizeof(int) * st._capacity);
    		if (nullptr == _a)
    		{
    			perror("malloc fail");
    			exit(-1);
    		}
    		memcpy(_a, st._a, sizeof(int) * st._top);
    		_top = st._top;
    		_capacity = st._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;
    };
    void TestStack()
    {
    	Stack st1;
    	st1.Push(1);
    	st1.Push(2);
    
    	Stack st2;
    	st2.Push(3);
    	st2.Push(4);
    	st2.Push(5);
    	st2.Push(6);
    
    	st1 = st2;
    }
    int main()
    {
    	TestStack();
    	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
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    在这里插入图片描述
    发生了崩溃,实际上这里的问题还是析构了两次(直接把st2的空间拷贝给st1,st1和st2的_a指向了同一个)发生了崩溃,但是此时的st1的内存还发生了泄露。栈的赋值运算符重载实现:

    //st1 = st2;
    //st1 = st1;
    Stack& operator = (const Stack& st)
    {
    	if (this != &st)
    	{
    		free(_a);
    		_a = (int*)malloc(sizeof(int) * st._capacity);
    		if (nullptr == _a)
    		{
    			perror("malloc fail");
    			exit(-1);
    		}
    		memcpy(_a, st._a, sizeof(int) * st._top);
    		_top = st._top;
    		_capacity = st._capacity;
    	}
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    那么要写析构函数的,就必须要显示写赋值构造函数。

    << 和>>重载

    cin的类型是istream
    cout的类型是ostream
    其中cout,cerr和clog的功能相似
    在这里插入图片描述

    对于<<和>>,我们一般不写成员函数,因为this默认抢了第一个参数位置,Date对象就是左操作数,不符合使用习惯和可读性,这点值得我们去关注哈。但是如果写在全局,又引发了另一个问题:

    如何去访问类的私有属性?
    1.直接把私有权限改为公共权限
    2.在类中设置get和set方法,然后在类外直接调用即可
    3.友元声明
    4.加inline改成内联函数

    同时,全局变量/全局函数在所有文件中(这里我们Date.cpp,Date.h,test.cpp)都可见的,在经过编译会生成Date.o,test.o,此时里面都会有全局函数的定义,放进符号表,此时就会产生冲突。所以声明与定义应该进行分离。(也可以加上static进行修饰,static修饰全局函数会改变链接属性,只在当前文件可见)。所以尽量在。h文件中不要定义全局的函数

    同时,对于频繁调用,我们可以直接用内联函数,直接展开,不进符号表

    class Date
    {
    	friend ostream& operator<<(ostream& out, const Date& d);
    public:
    	Date(int year = 1970, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    		//检测日期的合法性
    		if (!(year >= 1 
    			&& (month >= 1 && month <= 12) 
    			&& (day >= 1 && day <= GetMonthDay(year, month))))
    		{
    			cout<<"非法日期"<<endl;
    		}
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    //cout << dl << d2 << endl;
    //返回cout是为了链式访问
    static ostream& operator<<(ostream& out, const Date& d)
    {
    	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    	return out;
    }
    int main()
    {
    	Date d1(2022, 10, 15);
    	Date d2(2022, 10, 14);
    	cout << d1 << 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

    在这里插入图片描述

    #include
    using namespace std;
    class Date
    {
    	friend ostream& operator<<(ostream& out, const Date& d);
    	friend istream& operator>>(istream& in, Date& d);
    public:
    	Date(int year = 1970, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    		//检测日期的合法性
    		if (!(year >= 1 
    			&& (month >= 1 && month <= 12) 
    			&& (day >= 1 && day <= GetMonthDay(year, month))))
    		{
    			cout<<"非法日期"<<endl;
    		}
    	}
    	int GetMonthDay(int year, int month)
    	{
    		static int monthDayArray[13] = { 0,31,30,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 monthDayArray[month];
    		}
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    //cout << dl << d2 << endl;
    static 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;
    }
    int main()
    {
    	Date d1(2022, 10, 15);
    	Date d2(2022, 10, 14);
    	cout << "cout << d1 << d2结果是:" << d1 << d2 << endl;
    	Date d3;
    	cin >> d3 >> d1;
    	cout << d3 << 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
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    在这里插入图片描述

    1.3 前置++和后置++重载

    对于自定义类型尽量使用前置++,因为后置++要进行深度拷贝。

    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 d;
    	Date d1(2022, 1, 13);
    	d = d1++; // d: 2022,1,13 d1:2022,1,14
    	d = ++d1; // d: 2022,1,15 d1:2022,1,15
    	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

    2.const成员

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

    对于const的修饰,我们要注意权限可以进行缩小和平移,但是不能进行放大,这是在之前对于this指针( *const this)所说的。

    简单来说,凡是内部不改变成员变量,其实也就是*this对象数据的,这些成员函数都应该加const

    在这里插入图片描述

    using namespace std;
    class Date
    {
    public:
    	Date(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()// Date* const this
    	{
    		cout << "Print()" << endl;
    		cout << "year:" << _year << endl;
    		cout << "month:" << _month << endl;
    		cout << "day:" << _day << endl << endl;
    	}
    	void Print() const//const Date* const this
    	{
    		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 Test()
    {
    	Date d1(2022, 1, 13);
    	d1.Print();
    	const Date d2(2022, 1, 13);
    	d2.Print();
    }
    int main()
    {
    	Test();
    	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

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

    这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

    class Date
    {
    public:
    	Date* operator&()
    	{
    		return this;
    	}
    	const Date* operator&()const
    	{
    		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

    这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。

    Date* operator&()
    {
    	return nullptr;
    }
    const Date* operator&()const
    {
    	return nullptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    C++源程序语法检查器
    TensorFlow 2.9的零零碎碎(一)
    C语言进阶文件操作
    尚硅谷Java数据结构--希尔排序
    自动驾驶学习笔记(五)——绕行距离调试
    Vue2基础
    Finereport11 类Excel筛选
    大模型分布式训练并行技术(六)-多维混合并行
    uniapp/微信小程序 项目day03
    Hive综合应用案例——用户学历查询
  • 原文地址:https://blog.csdn.net/m0_68931081/article/details/127327068