【C++】类和对象(中)拷贝构造、赋值重载

发布于:2025-07-31 ⋅ 阅读:(22) ⋅ 点赞:(0)

三. 拷贝构造函数

1. 功能

用同类型对象创建新对象 时由编译器自动调用                用一个已经存在的对象初始化另一个对象

2. 特征

1. 拷贝构造函数是构造函数的一个重载形式(就是构造函数,用同类型的对象来进行构造)

2. 拷贝构造函数的参数只有一个必须是类类型对象的引用(常用const修饰,防止被改变导致 Bug);使用传值方式编译器直接报错, 因为会引发无穷递归调用

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1) // 构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// d2(d1) 拷贝构造函数
    // Date(Date& d)
	Date(const Date& d) // 就是一个构造,函数名/类名相同
	{
		_year = d._year; // this就是d2,d就是d1
		_month = d._month;
		_day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 4, 25);
	Date d2(d1); // 用同类型的对象来进行构造,d2是d1的拷贝

	return 0;
}

问:为什么不能传值?

void func(Date d)	{	}
void func(int i)	{	}

int main()
{
	Date d1(2023, 4, 25);
    
	func(d1);
	func(10);
    
	return 0;
}

要调用 func:
        内置类型:没有限制,以字节为单位直接拷贝(形参是实参一份拷贝)
        自定义类型(传值):先传参(祖师爷规定:必须要调用拷贝构造函数去完成(正常的赋值也一样))         <==> 用实参的对象初始化形参

图解:

验证:


拷贝构造使用传值:
本来就是拷贝构造,要调用函数。先传参,传参又是拷贝构造;拷贝构造先传参,传参又是拷贝构造……

所以编译器不允许在这里传值

拷贝构造使用传引用:

不会形成拷贝构造,因为 d 是 d1 的别名,d2 传给了 this                d 就是 d1        this 就是 d2

Date(const Date& d)
{
    cout << "Date(Date d)" << endl;
    this->_year = d._year; 
    _month = d._month;
    _day = d._day;
}

特征 3

特征 3:若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储 按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

  1. 内置类型成员完成值拷贝 / 浅拷贝(memcpy)
  2. 自定义类型成员会调用它的拷贝构造

内置类型成员 · 无动态申请资源:日期类

不写拷贝构造函数

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;
};

int main()
{
	Date d1(2023, 4, 25);
	Date d2(d1);

	return 0;
}

日期类可以不写拷贝构造,因为默认生成的拷贝构造就可以用

内置类型成员 · 动态申请资源:

不写拷贝构造函数

int main()
{
	Stack st1;
	Stack st2(st1);

	return 0;
}

    有拷贝(值拷贝 / 浅拷贝),但不是我想要的结果:

指向同一块空间问题的:
        1. st1.Push(10);         也影响了 st2
        2. 出了作用域,同一块空间析构了2次(同一块空间不能析构2次),程序崩溃
                在栈里面,后进先出(后定义的先析构),st2先析构,st1再析构


所以必须自己实现拷贝构造函数,实现深拷贝

class Stack
{
public:
	Stack(int capacity = 4) // 构造函数
	{
		cout << "Stack(int capacity = 4)" << endl;
		_a = (DateType*)malloc(sizeof(DateType) * capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}

    // st2(st1)
	Stack(const Stack& st) // 拷贝构造函数
	{
		_a = (DateType*)malloc(sizeof(DateType) * st._capacity);
		if (nullptr == _a)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		memcpy(_a, st._a, sizeof(DateType) * st._size);
		_size = st._size;
		_capacity = st._capacity;
	}

	~Stack() // 析构函数
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = nullptr;
		_capacity = _size = 0;
	}

private:
	DateType* _a = nullptr;
	int _capacity;
	int _size = 0;
};

int main()
{
	Stack st1;
	Stack st2(st1);

	return 0;
}

自定义类型成员:

class MyQueue
{
private: // 自定义类型成员
	Stack _pushst;
	Stack _popst;
};

3. 总结

这就是不让像C语言一样,把 st 直接拷贝给 s,非得调拷贝构造的原因

可以认为:拷贝构造是为自定义类型的深拷贝的对象而产生的,有资源的基本都需要深拷贝

1. Stack 的拷贝构造函数需要自己实现
2. Date,MyQueue 都不需要自己写拷贝构造函数
        Date:内置类型可以完成值拷贝
        MyQueue:自定义类型会调用里面 Stack 的拷贝构造(前提:Stack 的拷贝构造实现好了)

4. 拷贝构造函数的调用场景

日期类:占12字节

// void func(Date d) 拷贝12字节(传值)
void func(Date& d) // 语法上不开空间,底层开空间,但忽略不计(传引用)
{ }

int main()
{
	Date d1;
	func(d1);

	return 0;
}

这里还能忍,下面就忍不了了

// void func(Stack st)
void func(Stack& st)
{ }

int main()
{
	Stack st1;
	func(st1);

	return 0;
}

这里不想用传值传参了:
        自定义类型的传值传参要调拷贝构造,是深拷贝,要开空间,代价大。传引用就解决了

// Stack func()
Stack& func()
{
	static Stack st;
	return st;
}

int main()
{
	Stack ret = func();
	return 0;
}

如果出了作用域对象还在,愿不愿意传值返回?
        传值返回,不会返回 st,会生成1个拷贝,这个拷贝又是深拷贝

出了作用域,对象还在,一定要引用返回,减少拷贝

Stack func()
{
	Stack st;
	return st;
}

如果出了作用域对象不在(是局部对象),不能用引用返回。要硬着头皮传值
        因为它调用析构函数,指向的空间都销毁了

四. 赋值运算符重载

1. 运算符重载

比较日期大小

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 Less(const Date& x1, const Date& x2)
{
	if (x1._year < x2._year)
	{
		return true;
	}
	else if (x1._year == x2._year && x1._month < x2._month)
	{
		return true;
	}
	else if (x1._year == x2._year && x1._month < x2._month && x1._day < x2._day)
	{
		return true;
	}

	return false;
}

int main()
{
	Date d1(1949, 10, 1);
	Date d2(1949, 11, 1); // 比较日期大小

	cout << Less(d1, d2) << endl;

	return 0;
}

要是有人把函数名写成 Func1,还不写注释,那就不好读

int main()
{
	Date d1(1949, 10, 1);
	Date d2(1949, 11, 1); // 比较日期大小

	//cout << Less(d1, d2) << endl;
    
	cout << (d1 < d2) << endl; // 报错 如果能这样用就挺美
    // 带 () :运算符优先级,<< 优先级很高
	return 0;
}

为什么内置类型可以直接比较,自定义类型不可以直接比较?
        内置类型是祖师爷定的,祖师爷肯定知道 int double …… 这些怎么比
        自定义类型,只有你知道怎么比,怎么才能让你用 < 这个运算符呢?

bool operator<(const Date& x1, const Date& x2)

编译器做了特殊处理。看是内置类型,它知道怎么做(比较大小),就转化成对应指令
自定义类型会转换成调用这个函数,它会去看有没有重载这个日期的operator<

cout << (d1 < d2) << endl;
// 编译器自动转化成:
cout << (operator<(d1, d2)) << endl;	//等价于编译器自动传 this
// 就相当于调 bool operator<(const Date& x1, const Date& x2) 这个函数

d1 < d2; // 转换成 operator<(d1, d2);
operator<(d1, d2); // 显示调用也可以

1.1 概念

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

自定义类型的运算符重载的本质:调用函数

注意:

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

操作符是几个操作数,重载函数就有几个参数
规定:第一个参数是做操作数,第二个参数是右操作数


并不是所有的运算符对它(日期类)都有意义
eg:比较大小、日期-日期 = 天数(有意义)                 日期+日期(无意义)

是否要重载运算符,取决于这个运算符对这个类是否有意义

重载操作符必须有一个类类型参数,不能重载这个:

bool operator<(const int& x1, const int& x2)

上面是私有的,编不过。可以把它放到类里,搞成成员函数。但还是编不过

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	bool operator<(const Date& x1, const Date& x2)
	{    }

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(1949, 10, 1);
	Date d2(1949, 11, 1); // 比较日期大小

	d1 < d2; // 报错
	operator<(d1, d2); // 报错

	return 0;
}

报错:"operator<" 的参数太多了。         隐藏的this

operator< 应该有2个参数,上面实际是3个

改正:

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{    }

	bool operator<(const Date& x)
	{
		if (_year < x._year)
		{
			return true;
		}
		else if (_year == x._year && _month < x._month)
		{
			return true;
		}
		else if (_year == x._year && _month == x._month && _day < x._day)
		{
			return true;
		}
	
		return false;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(1949, 10, 1);
	Date d2(1949, 11, 1);

	d1 < d2; // 自动转换为:d1.operator<(d2);  d1传给了隐含的this
	d1.operator<(d2);

	return 0;
}

2. 赋值运算符重载

已经存在的两个对象之间的复制拷贝

2.1 格式

eg场景:d2赋值给d1,用“=”赋值运算符,规定必须调用 operator=(const Date& d);函数

最简单版本的赋值:

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{    }

	void operator=(const Date& d)
	{
        _year = d._year;
        _month = d._month;
        _day = d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(1949, 10, 1);
	Date d2(1949, 11, 1);

	// 已经存在的两个对象之间复制拷贝        -- 运算符重载函数
	d1 = d2;

	// 用一个已经存在的对象初始化另一个对象   -- 构造函数
	Date d3(d1);

	return 0;
}

这个赋值存在问题

C语言存在连续赋值的场景:

int i, j, k;
i = j = k = 0;

从右往左赋值。k = 0 这个赋值以 k 为返回值再赋给 j                 j = k 这个赋值以 j 为返回值再赋给 i

Date d5, d4;
d5 = d4 = d1;
// 报错:二元“=”: 没有找到接受“void”类型的右操作数的运算符(或没有可接受的转换)

我们应该让这里返回 d4 作为右操作数再赋给 d5

// d4 = d1
void operator=(const Date& d)

Date operator=(const Date& d)
{
    this->_year = d._year;
    _month = d._month;
    _day = d._day;

    return *this;
}

this 是 d4 的地址           d 就是 d2

不足:传值返回,返回的是 d4 的拷贝,代价大

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{    }

	Date(const Date& d) // 拷贝构造
	{
		cout << "Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	Date operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(1949, 10, 1);
	Date d2(1949, 11, 1);
    
    d1 = d2;

	Date d5, d4;
	d5 = d4 = d1; // d4.operator=(d1)

	return 0;
}

        3个赋值 ==> 有3个拷贝构造

出了作用域,对象还在 ==> 引用返回

this 是形参,出了opoerator( )函数销毁
*this(就是 d4)的生命周期不在opoerator( )函数里,在外面。所以出了作用域,*this 还在

Date& operator=(const Date& d) // 引用
{
    if (this != &d) // 取地址 (d1 = d1; 相同不用赋值)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    
    return *this;
}

  此时打印结果没有拷贝构造,提高了效率

总结要点:

参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this:要复合连续赋值的含义

2.2 自动生成

赋值运算符重载是默认成员函数。用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

默认生成的赋值运算符重载 和 拷贝构造行为一样:

  1. 内置类型成员完成值拷贝 / 浅拷贝(memcpy)
  2. 自定义类型成员会调用它的赋值运算符重载

Date 和 MyQueue 不需要自己实现赋值运算符重载
Stack 要自己实现,因为默认生成的是浅拷贝

2.3 不能写成全局的,只能写成成员函数

赋值运算符只能重载成类的成员函数不能重载成全局函数

赋值运算符重载是特殊的成员函数:默认成员函数

默认成员函数是亲女儿,不能在外面过夜,必须在家里过夜

默认成员函数不能写在 类外面(全局)。写在全局,类里面会默认生成一个,2个无法区分
可以声明在类里,定义在类外 ==> 此时还是成员函数

进阶玩法

重载 < == 其他复用

针对所有类

        Date.h

#pragma once
#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1); // 构造 

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	bool operator<(const Date& x);
	bool operator==(const Date& x);
	bool operator<=(const Date& x);
	bool operator>(const Date& x);
	bool operator>=(const Date& x);
	bool operator!=(const Date& x);

private:
	int _year;
	int _month;
	int _day;
};

        Date.cpp

#include "Date.h"

Date::Date(int year, int month, int day) // 构造函数
{
	_year = year;
	_month = month;
	_day = day;
}

bool Date::operator<(const Date& x)
{
	if (_year < x._year)
	{
		return true;
	}
	else if (_year == x._year && _month < x._month)
	{
		return true;
	}
	else if (_year == x._year && _month == x._month && _day < x._day)
	{
		return true;
	}

	return false;
}

bool Date::operator==(const Date& x)
{
	return _year == x._year
		&& _month == x._month
		&& _day == x._day;
}

// d1(*this) <= d2(x) 复用
bool Date::operator<=(const Date& x)
{
	return *this < x || *this == x;
}

bool Date::operator>(const Date& x)
{
	return !(*this <= x);
}

bool Date::operator>=(const Date& x)
{
	return !(*this < x);
}

bool Date::operator!=(const Date& x)
{
	return !(*this == x);
}

日期+天数

        Date.h

int GetMonthDay(int year, int month); // 天数复杂,最好实现子函数

// d1 + 100
Date operator+(int day);

        Date.cpp

int Date::GetMonthDay(int year, int month)
{
	// 不需要:每次调用都创建
	static int daysArr[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)
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
		return 29;
	else
		return daysArr[month];
}

Date Date::operator+(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}

        Test.cpp

void TestDate1()
{
	Date d1(1949, 10, 1);
	d1 + 100;
	d1.Print();
}

 有问题,i + 100,i 不变,是表达式有个返回值。所以不嫩能对 _year _month 改变

严格来说,我们实现的不是 + ,是 +=


        Date.h

Date& operator+=(int day); // d1 += 100
Date operator+(int day); // d2 + 100

        Date.cpp

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 == 13)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}

// d2 + 100	不能改变d2
Date Date::operator+(int day)
{
	Date tmp(*this); // 拷贝构造一个

	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		tmp._month++;
		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}

	return tmp; // 出了作用域,tmp销毁,不能用引用返回
}

        Test.cpp

void TestDate1()
{
	Date d1(1949, 10, 1);
	d1 += 100;
	d1.Print();

	Date d2(1945, 9, 3);
	Date d3(d2 + 100); // "d2+100"有返回值,拿d3接收
    // 也可以写成 Date d3 = d2 + 100;
	d2.Print();
	d3.Print();
}

// 用一个已经存在的对象初始化另一个对象  -- 拷贝构造函数
Date d4 = d2;  // 等价于 Date d4(d2);

//已经存在的两个对象之间复制拷贝        -- 运算符重载函数
d4 = d1;


        + 复用 +=(更好)

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 == 13)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+(int day)
{
	Date tmp(*this); // 拷贝构造一个
    tmp += day;

	return tmp; // 出了作用域,tmp销毁,不能用引用返回
}

        += 复用 +

Date Date::operator+(int day)
{
	Date tmp(*this); // 拷贝构造一个

	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year, tmp._month);
		tmp._month++;
		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}

	return tmp; // 出了作用域,tmp销毁,不能用引用返回
}

Date& Date::operator+=(int day)
{
	*this = *this + day;
	return *this;
}

对于 +、+ 复用 += 没有区别,都要创建一个对象、传值返回(拷贝构造2次)

+= 没有创建对象(拷贝构造0次)

+= 复用 +:调用 + 时,再次创建对象,传值返回(拷贝构造各2次,共4次)

日期-天数

        Date.h

Date& operator-=(int day);
Date operator-(int day);

        Date.cpp

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			_month = 12;
			--_year;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}

++、--

++是单运算符,因为只有一个操作数

前置++返回++以后的对象,后置++返回++之前的对象。
所以这俩函数内部是一样的,但返回值不一样,所以不能用一个运算符重载替代

        Date.h

Date& operator++(); // 前置++
Date operator++(int); // 后置++

        Date.cpp

Date& Date::operator++() // 前置++
{
	*this += 1;
	return *this;
}

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;
}

如果接收了也没意义,可以不写形参。增加这个 int 参数不是为了接收具体的值,仅仅是占位,跟前置++构成重载

        Test.cpp

void TestDate2()
{
	Date d1(1949, 10, 1);
	++d1; // d1.operator++(&d1)
	d1++; // d1.operator++(&d1, 0)
	d1.Print();
}

void TestDate5()
{
	Date d2(1949, 10, 1);
	Date ret2 = --d2;
	d2.Print();
	ret2.Print();

	Date d1(1949, 10, 1);
	Date ret1 = d1--;
	d1.Print();
	ret1.Print();
}

内置类型前置、后置++效率没有区别

自定义类型 前置++效率好。后置++ 会多创建2个对象。这也是牺牲后置++ 的原因

日期 - 日期

        Date.h

int operator-(const Date& d);

        Date.cpp

// d1 - d2
int Date::operator-(const Date& d)
{
	// 默认认为d1大,d2小
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}

	return n * flag;
}

流插入打印:全局+友元

d1.Print(); 这种打印方式麻烦。

printf 不能解决问题,只能打印内置类型%d %f ...... 不能解决自定义类型。所以 C++ 要搞自己的运算符

cout << d1; // 流插入

这时要实现运算符重载

流插入运算符 << 是双操作数:日期类对象 和 ostream 类对象 cout

ostream 是 iostream 库定义的

所以包含 <iostream> 这个头文件,再展开命名空间就可以了

double d = 1.1;
int i = 2;
cout << d;
cout << i;

C语言要指定类型打印

C++可以直接支持内置类型是因为库里面实现了 可以直接支持自动识别类型是因为函数重载


// Date.h
void operator<<(ostream& out);

// Date.cpp
void Date::operator<<(ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
}

// Test.cpp
void TestDate7()
{
	Date d1(1949, 10, 1);
	cout << d1; // 报错:二元“<<”: 没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)
}

cout << d1; 正常情况下要转换成调函数 d1.operator<<(cout)                 但不能

d1 << cout; 才能转换成 d1.operator<<(cout)

规定:第一个参数是做操作数,第二个参数是右操作数


// Test.cpp
void TestDate7()
{
	Date d1(1949, 10, 1);
	d1 << cout;
	d1.operator<<(cout);
}

流插入不能写成成员函数因为Date对象默认占用第一个参数,就是做了左操作数

写出来就一定是这样子:d1 << cout; // d1.operator<<(cout);     不符合使用习惯


写成成员函数第一个位置被 Date(this)自动占领

必须让 cout 去第一个参数:写成全局

// Date.h
class Date {	};
void operator<<(ostream& out, const Date& d);

// Date.cpp
void operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

此时无法访问私有,会报错

方法1:写几个公有成员函数

        Date.h

class Date
{
public:
    int GetYear() const // 为什么加 const 下面讲
    {
    	return _year;
    }

private:
};

        Test.cpp

void operator<<(ostream& out, const Date& d)
{
	out << d.GetYear() << "年" << d.GetMonth() << "月" << d.GetDay() << "日" << endl;
}

方法2:友元函数,突破私有的限定

        Date.h

class Date
{
    // 友元函数声明
    friend void operator<<(ostream& out, const Date& d);
};
void operator<<(ostream& out, const Date& d);

友元声明在任意位置都可以,不考虑访问限定符

我是你的朋友,在我里面就可以用对象访问你的私有

        Date.cpp

void operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

        Test.cpp

void TestDate7()
{
	Date d1(1949, 10, 1);
	cout << d1; // 改为调全局函数 operator<<(cout, d1);
    operator<<(cout, d1);
}

不能写成 const ostream& out :流插入就是往 out(cout 的别名)写东西,会改变 cout


连续流插入

// Test.cpp
void TestDate8()
{
	Date d1(1945, 9, 3);
	Date d2(1949, 10, 1);
	Date d3(2025, 9, 3);
	// d1 = d2 = d3; 连续赋值,从右往左
	cout << d1 << d2 << d3;
}

运算符特性:连续流插入,从左往右

先 d2 流向 cout(调上面的函数);为了支持连续,得有返回值,返回值应该是 cout;cout 再做左操作数支持 d3 流插入

out 就是 cout(全局对象),出了作用域还在。所以应该返回 ostream&

        Date.h

class Date
{
    // 友元函数声明
    friend ostream& operator<<(ostream& out, const Date& d);
};
ostream& operator<<(ostream& out, const Date& d);

        Date.cpp

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

流提取

        Date.h

class Date
{
    friend istream& operator>>(istream& in, Date& d);
};
istream& operator>>(istream& in, Date& d);

不能写成 const istream& in:因为要改 istream 这个对象里的一些状态值

        Date.cpp

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

        Test.cpp

void TestDate9()
{
	Date d1;
	Date d2;
	cin >> d1 >> d2; // 输入数据到终端,再提取到内存
	cout << d1 << d2; // 内存流向终端
}

一切皆可输入输出

检查

因为我们封装了,所有的对象都是构造出来的。在构造时加些检查

// Date.cpp
Date::Date(int year, int month, int day) // 构造
{
	if (month > 0 && month < 13
		&& day > 0 && day <= GetMonthDay(year, month))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "非法日期" << endl;
		assert(false);
	}
}

// Test.cpp
void TestDate10()
{
	Date d1(2019, 13, 1);
	cout << d1;
}


void TestDate10()
{
	Date d1;
	cin >> d1;
	cout << d1;
}

// Date.cpp
istream& operator>>(istream& in, Date& d)
{
	int year, month, day;
	in >> year >> month >> day;

	if (month > 0 && month < 13
		&& day > 0 && day <= d.GetMonthDay(year, month))
	{
		d._year = year;
		d._month = month;
		d._day = day;
	}
	else
	{
		cout << "非法日期" << endl;
		assert(false);
	}

	return in;
}

const

void TestDate11()
{
	Date d1(2015, 9, 3);
	d1.Print();

	const Date d2(2019, 10, 1);
	d2.Print(); // 报错
    // “void Date::Print(void)”: 不能将“this”指针从“const Date”转换为“Date &”
}

d2 不能被改变,取地址 --> 指针指向的内容不能被改变

怎么把 this 指针改为 const Date* ?                 因为 this 是隐含的,所以不能显示写

        Date.h

class Date
{
public:
    void Print() const
    {
    	cout << _year << "-" << _month << "-" << _day << endl;
    }
}

成员函数后面加 const 以后,普通和 const 对象都可以调用。const 修饰 *this,不修饰 this

结论:不是所有的成员函数后面都加这个 const 。要修改对象成员变量的函数不能加

        只要成员函数内部不修改成员变量,都在后面加 const
                这样 const对象 和 普通对象 都可以调用

上面为了看起来方便,实际 const 修饰后会变成 const Date* const this

+= 不能加 const                 +、比较大小 可以加 const

五. 取地址运算符重载

自定义类型只要用运算符就要重载

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

class Date
{
public:
	Date* operator&()
	{
		cout << "Date* operator&()" << endl;
		return this;
        // return nullptr; 不想让别人取到这个普通对象的地址
	}

	const Date* operator&() const
	{
		cout << "const Date* operator&() const" << endl;
		return this;
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d1;
	const Date d2;

	cout << &d1 << endl;
	cout << &d2 << endl;

	return 0;
}

这俩函数构成重载

六. 日期类

Date.h

class Date
{
	// 友元函数声明
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1); // 构造 

	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	bool operator<(const Date& x) const;
	bool operator==(const Date& x) const;
	bool operator<=(const Date& x) const;
	bool operator>(const Date& x) const;
	bool operator>=(const Date& x) const;
	bool operator!=(const Date& x) const;

	int GetMonthDay(int year, int month); // 天数复杂,最好实现子函数

	Date& operator+=(int day); // d1 + 100
	Date operator+(int day) const; // d2 + 100

	Date& operator-=(int day); // d1 + 100
	Date operator-(int day) const; // d2 + 100

	Date& operator++(); // 前置++
	Date operator++(int); // 后置++

	Date& operator--(); // 前置--
	Date operator--(int); // 后置--

	int operator-(const Date& d) const;

private:
	int _year;
	int _month;
	int _day;
};


ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

Date.cpp

Date::Date(int year, int month, int day) // 构造
{
	if (month > 0 && month < 13
		&& day > 0 && day <= GetMonthDay(year, month))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "非法日期" << endl;
		assert(false);
	}
}

bool Date::operator<(const Date& x) const
{
	if (_year < x._year)
	{
		return true;
	}
	else if (_year == x._year && _month < x._month)
	{
		return true;
	}
	else if (_year == x._year && _month == x._month && _day < x._day)
	{
		return true;
	}

	return false;
}

bool Date::operator==(const Date& x) const
{
	return _year == x._year
		&& _month == x._month
		&& _day == x._day;
}

// d1(*this) <= d2(x) 复用
bool Date::operator<=(const Date& x) const
{
	return *this < x || *this == x;
}

bool Date::operator>(const Date& x) const
{
	return !(*this <= x);
}

bool Date::operator>=(const Date& x) const
{
	return !(*this < x);
}

bool Date::operator!=(const Date& x) const
{
	return !(*this == x);
}

int Date::GetMonthDay(int year, int month)
{
	// 不需要:每次调用都创建
	static int daysArr[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)
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
		return 29;
	else
		return daysArr[month];
}

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 == 13)
		{
			_year++;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+(int day) const // + 复用 +=
{
	Date tmp(*this); // 拷贝构造一个
	tmp += day;

	return tmp; // 出了作用域,tmp销毁,不能用引用返回
}


Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			_month = 12;
			--_year;
		}

		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

Date Date::operator-(int day) const
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}


Date& Date::operator++() // 前置++
{
	*this += 1;
	return *this;
}

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;
}


// d1 - d2
int Date::operator-(const Date& d) const
{
	// 默认认为d1大,d2小
	Date max = *this;
	Date min = d;
	int flag = 1;

	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}

	return n * flag;
}

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

istream& operator>>(istream& in, Date& d)
{
	int year, month, day;
	in >> year >> month >> day;

	if (month > 0 && month < 13
		&& day > 0 && day <= d.GetMonthDay(year, month))
	{
		d._year = year;
		d._month = month;
		d._day = day;
	}
	else
	{
		cout << "非法日期" << endl;
		assert(false);
	}

	return in;
}

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章


网站公告

今日签到

点亮在社区的每一天
去签到