C++类与对象(二)

news2025/1/13 2:58:19

目录

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

2..构造函数

2.1概念

2.2 特征

3.析构函数

3.1 概念

3.2 特性

4.拷贝构造函数

4.1 概念

4.2 特征

5.赋值运算符重载函数

5.1 运算符重载(是否重载这个运算符是看这个运算符对这个类是否有意义)

5.2 赋值运算符重载

6.const成员

7.取地址以及const取地址操作符重载

8.日期类的实现

Date.h

Date.cpp

 9 流插入和流提取运算符重载

9.1自定义类型流插入的实现

9.2 自定义类型流提取的实现


本次大纲内容:

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

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

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。

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

class Date {};

2..构造函数

2.1概念

对于以下Date类:

class Date
{
public:
	void Init(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;
	d1.Init(2022, 7, 5);
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置 信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

2.2 特征

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

其特征如下:

1. 函数名与类名相同。

2. 无返回值(也不需要写void)。

3. 对象实例化时编译器自动调用对应的构造函数。

struct Stack
{
public:
	Stack(int capacity = 4)
	{
        cout << "Stack" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("Stack:;malloc");
			return;
		}

		_top = 0;
		_capacity = capacity;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
    Stack st1;
    
    return 0;
}

测试运行:

可以看到,确实是自动调用了

注:

4. 构造函数可以重载。

class Date
{
public:
	Date()
	{}

	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;
	d1.Print();
	Date d2;
	d2.Print();
	return 0;
}

可以看到,构造函数可以构成重载

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

class Date
{
public:

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

	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	return 0;
}

可以看到函数还是随机值

这是因为C++规定编译器生成的默认构造函数,只会初始化构造类型,而不会初始化内置类型

6.关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默 认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的 默认构造函数并没有什么用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char/double...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。

我们不写,编译器默认生成构造函数,内置类型不做处理,自定义类型会去调用它的默认构造函数

有些编译器也会处理内置函数,但是那是个性化处理,不是所有编译器都会处理

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

class Date
{
public:
	void Print()
	{
		cout << _year <<' ' << _month << ' ' << _day << endl;
	}
private:
	// 基本类型(内置类型)
	int _year = 1;
	int _month = 1;
	int _day = 1;
};
int main()
{
	Date d;
	d.Print();
	return 0;
}

tips:

在类中给声明赋初始值,不是给它们初始化的意思,是给默认构造函数的形参赋予缺省值

7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为 是默认构造函数。

所以这两个只能留一个

结论:

  • 一般情况下,有内置类型成员的,就需要自己些构造函数,不能使用编译器生成的
  • 全部都是自定义类型可以考虑让编译器自己生成

3.析构函数

3.1 概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

3.2 特性

析构函数是特殊的成员函数,其特征如下:

1. 析构函数名是在类名前加上字符 ~

2. 无参数无返回值类型。

struct Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("Stack:;malloc");
			return;
		}

		_top = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		cout << "~Stack" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};


int main()
{
	Stack st1;
	return 0;
}

运行:

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(内置类型不做处理,自定义类型会去调用它的析构函数)。

注意:析构函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

结论:

  • 一般情况下,有动态申请资源,就需要显示写析构函数释放资源
  • 没有动态申请资源,不需要写析构函数
  • 需要释放资源的成员都是自定义类型,不需要写析构

4.拷贝构造函数

4.1 概念

那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

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

4.2 特征

拷贝构造函数也是特殊的成员函数,其特征如下:

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

class Date
{
public:
	Date(const Date& d)
	{
		cout << "const Date& d" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	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;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

这里解释一下为什么拷贝构造函数中需要使用const Date& d这种形式来接收形参

为什么不能使用Date d呢?

  • 内置类型,不会去调用拷贝构造函数拷贝
  • 自定义类型,必须借用拷贝构造函数来完成拷贝

看图:

因为我们传的是自定义类型,当我们将d1传到函数Date的途中,d1就会被调用到新的拷贝构造函数中,重复前面的步骤,不断递归,最后肯定会栈溢出,但是编译器不允许这样传参,会直接报错

解决方法:

  • 传指针:任何类型的指针都是内置类型,拷贝时不会调用拷贝构造函数   
  • 传引用:直接使用d1的别名

这里建议使用引用,并且加个const(类中不受访问限定符的影响)

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

  • 编译器自动生成的默认拷贝构造函数,会对内置类型完成值拷贝/浅拷贝
  • 自定义类型,调用它的拷贝构造函数
class Date
{
public:
	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;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 9, 7);
	Date d2(d1);
	return 0;
}

对于这种日期类还是可以使用默认构造函数的

但是有另一种情况

因为默认拷贝构造函数是浅拷贝,遇到动态申请空间的情况就会出现问题

struct Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("Stack:;malloc");
			return;
		}

		_top = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		cout << "~Stack" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};


int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

这里的问题:

(1)析构函数对同一个空间清理了两次,会报错

(2)改变一个空间的内容会对另外一个空间造成影响

如图:

指向同一块空间

这里使用浅拷贝肯定不行

这里栈得我们手动写一个深拷贝,后期我们会学到深拷贝来解决这里的问题

这里给大家浅浅讲一下深拷贝

深拷贝就是为了弥补浅拷贝的缺点而存在的

如图:

代码实现:

struct Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack" << endl;
		_a = (int*)malloc(sizeof(int) * capacity);
		if (nullptr == _a)
		{
			perror("Stack:;malloc");
			return;
		}

		_top = 0;
		_capacity = capacity;
	}

	//深拷贝需要自己手动实现
	Stack(const Stack& st1)
	{
		_a = (int*)malloc(sizeof(int) * st1._capacity);
		if (nullptr == _a)
		{
			perror("Stack::malloc");
			return;
		}

		memcpy(_a, st1._a, sizeof(int) * st1._top);
		_capacity = st1._capacity;
		_top = st1._top;
	}

	~Stack()
	{
		cout << "~Stack" << endl;
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};


int main()
{
	Stack st1;
	Stack st2(st1);
	return 0;
}

这里看几个情况:

(1).

1. void func1(Date& d);
2. void func2(Date d);
  • 如果没有动态资源的类类型,这上面两种形式肯定是第一个好,因为使用引用为形参在语法上是直接使用d的空间,不会去额外开辟栈帧,这样会减少消耗,加快程序运行效率

(2).

1.
Stack& func1()
{
    static Stack st;
    return st;
}

2.
Stack func1()
{
    Stack st;
    return st;
}

3.
Stcak& func1()
{
    Stack st;
    return st;
}

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

(1).st出栈帧后不会被销毁,因为st存放在静态区,故而可以使用引用作为返回值

(2)(3).在2,3中只能使用3这种形式

  • 解释(2):调用func1函数,在func1函数中创建一个变量st,然后返回st变量,返回时编译器会产生一个临时变量来保存st的内容,这时就需要调用拷贝构造函数,将st的内容拷贝给这个临时变量,通过这个临时变量将st的内容给ret,因为是深拷贝,所以会增加空间的使用
  • 解释(3):因为st是临时变量出栈帧结束就销毁了,销毁之后st会调用析构函数来清理它的资源,因为是引用,所以不会产生临时变量,这时ret和st指向的内容相同,即编译器报错

5.赋值运算符重载函数

5.1 运算符重载(是否重载这个运算符是看这个运算符对这个类是否有意义)

C++为什么要加入运算符重载函数呢?

1. C++提供的一些运算符只能供 内置类型/基本类型使用,自定义类型使用不了,因为内置类型原本就是C++本来就有的,创造C++的大佬知道怎么实现它们的运算符,而自定义类型是我们自己定义的所以,需要我们自己实现,运算符重载就是 让自定义类型支持运算符

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

d1 == d2; //书写简单,可读性高
assignment(d1, d2); //书写复杂,可读性低

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

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

注意:

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

下面我通过创建日期类来了解运算符重载函数

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

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

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

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

int main()
{
	Date d1(2024, 9, 10);
	Date d2(2024, 9, 10);

	//operator==(d1, d2);与下面那个效果一样
	int ret = d1 == d2;//自定义类型使用运算符
	cout << ret << endl;
	return 0;
}

这里我使用的是全局的运算符重载函数,但是有一定的缺陷,保证不了类的封装性,类中的对象全部公开了

注:operator==(d1, d2);和d1 == d2是一样的

我们看看底层实现(汇编代码):

这里有两个办法

1.将运算符重载函数写成日期类成员函数,写到类中不会受访问限定符的影响

2.在日期类中声明operator==为友元函数

第二个办法我们后面在讲,这里说明第一个方法

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

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

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

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

int main()
{
	Date d1(2024, 9, 10);
	Date d2(2024, 9, 10);

	//d1.operator==(d2);与下面那个效果一样
	int ret = d1 == d2;、
	cout << ret << endl;
	return 0;
}

:写成成员函数要注意operator==的有操作数是this指针,所以operator==函数参数只有一个,但实际上有两个,只是this指针不能显示在参数表和函数调用中(形参和实参)

5.2 赋值运算符重载

1. 赋值运算符重载格式

参数类型:const T&,传递引用可以提高传参效率

返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

检测是否自己给自己赋值

返回*this :要复合连续赋值的含义

class Date
{
public:
	Date(int year = 1, 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;
	}

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

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

int main()
{
	Date d1(2024, 9, 10);
	Date d2;

	d2 = d1;
	d2.Print();

	return 0;
}

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

class Date
{
public:
	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;
	int _month;
	int _day;
};

// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& d1, const Date& d2)
{
	if (&d1 != &d2)
	{
		d1._year = d2._year;
		d1._month = d2._month;
		d1._day = d2._day;
	}

	return d1;
}

int main()
{
	Date d1(2024, 9, 10);
	Date d2;

	d2 = d1;
	d2.Print();

	return 0;
}

编译失败:error C2801: “operator =”必须是非静态成员

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

3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注 意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。

默认生成的赋值重载函数的作用和拷贝构造一样:

  1. 内置类型进行值拷贝/深拷贝
  2. 自定义类型调用它的赋值重载函数
class Time //我这里将std全展开了,如果写time会和库中的time冲突
{
public:
	Time(int hour = 1, int minute = 1, int second = 1)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
	Time& operator=(const Time& T)
	{
		if (this != &T)
		{
			_hour = T._hour;
			_minute = T._minute;
			_second = T._second;
		}
		
		return *this;
	}

//private:
	int _hour;
	int _minute;
	int _second;
};

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

	void Print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
		cout << _T._hour << ' ' << _T._minute << ' ' << _T._second << endl;
	}

private:
	int _year;
	int _month;
	int _day;

	Time _T;
};

int main()
{
	Date d1(2024, 9, 10);
	Date d2;

	d2 = d1;
	d2.Print();

	return 0;
}

总结:

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

如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

2.赋值前检查是否在给自己赋值

C++支持d1 = d1这样赋值的情况,这种赋值是没有意义的,这种操作只会增加空间复杂度,所以我们可以在赋值函数中增加一个判断语句,如果是d1 = d1这种情况,返回d1就可以了

3.函数的返回值使用引用返回

实际上若是出现d2 = d1;这种情况我们没有必要有返回值,因为在函数内已经通过this指针改变了对象d2,但是为了支持连续赋值d3 = d2 = d1 ,我们就需要为函数设置一共返回值,显而易见的我们应该返回运算符的左操作数,即*this

如果返回的*this出作用域不会被销毁,那么为了避免不必要的消耗,最好使用引用返回

4.参数类型设置为引用,并使用const修饰

 赋值运算符重载函数第一个参数默认是this指针,第二个参数是运算符的右操作数

由于是自定义类型,我们若使用传值传参,这里系统就会自动调用一次拷贝构造函数,所以我们这里使用引用传递(第一个参数this是不显示的,我们管不了)

第二个操作数原本就是给第一个操作数赋值的,所以这里我们还能在它前面加上一个const防止它的数据被篡改

5.拷贝构造函数和赋值运算符

拷贝构造函数:用一个已经存在的对象初始化另一个对象

赋值运算符重载:两个存在对象之间的赋值拷贝

例:

int main()
{
    Date d1(2024, 9, 11);
    Date d2(d1);  //或 d2 = d1; 拷贝构造函数
    Date d3(2024, 9, 12);
    d3 = d1; // 或d3.operator(d1); 赋值运算符重载函数
    return 0;
}

6.const成员

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

 

class Date
{
public:
    void Print() const
    {
        cout << _year << "年" << _month 
        << "月" << _day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
}

实际编译器的处理,这段代码只是给予理解的,实际上this指针是不能显现的

这里其实是const Date* const this,但是为了好理解,写成 const Date* this

class Date
{
public:
    void Print(const Date *this) 
    {
        cout << _year << "年" << _month 
        << "月" << _day << "日" << endl;  
    }
private:
    int _year;
    int _month;
    int _day;
}

请思考下面的几个问题:

1. const对象可以调用非const成员函数吗? 不可以

2. 非const对象可以调用const成员函数吗? 可以

3. const成员函数内可以调用其它的非const成员函数吗? 不可以

4. 非const成员函数内可以调用其它的const成员函数吗?  可以

解释:

1. 我们将const对象传进非const函数中,函数中的this指针没有被const修饰,而我们传进来的形参是被const所修饰的不能随意篡改内容,但是一旦传进函数中,形参的权限就被放大了,不受const的限制了

2.将非const对象传进const成员函数中,属于权限的缩小,这样是可行的

3.在const成员函数内调用非const成员函数,是将被const修饰的指针this的值赋值给另一个非const成员函数,这属于权限的放大,不可行

4.在非const成员函数内调用const成员函数,是将指针this的值赋值给另一个const成员函数,这属于权限的缩小,是可行的

7.取地址以及const取地址操作符重载

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

class Date
{ 
public:
    Date* operator&()
    {
        return this ;
    }
    const Date* operator&()const
    {
         return this ;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

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

特殊使用场景,让别人取不到对象的地址:

class Date
{ 
public:
    Date* operator&()
    {
        //return this;
          return nullptr;
    }

    const Date* operator&()const
    {
         return this;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

8.日期类的实现

Date.h

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1);
    //获取这个月的天数
	int GetMonthDay(int _year, int _month);
    //日期 += 天数
	Date& operator+=(int day);
    //日期 -= 天数
	Date& operator-=(int day);
    //日期 + 天数
	Date operator+(int day) const;
    //日期 - 天数
	Date operator-(int day) 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;
    //日期 != 日期
	bool operator!=(const Date& d) const;
    //日期 - 日期
    Date operator-(const Date& d);
    //前置++
    Date& operator++();
    //后置++
    Date operator++(int);
	void Print();

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

Date.cpp

Date::Date(int year, int month, int day)
{
	if (month > 0 && month < 13 
		&& day > 0 && day <= GetMonthDay(month, day))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "无效天数,请重新输入" << endl;
		assert(false);
	}
}

//获取这个月的天数
int Date::GetMonthDay(int year, int month)
{
	int MonthDay[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 MonthDay[month];
	}
}

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

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

	return *this;
}

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

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

	return *this;
}

//日期 + 天数
Date Date::operator+(int day) const
{
	//Date tmp = *this;
	//tmp += 100;
	//return tmp;

	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;

}

//日期 - 天数
Date Date::operator-(int day) const
{
	//Date tmp = *this;
	//tmp -= 100;
	//return tmp;

	Date tmp = *this;
	tmp._day -= day;

	while (tmp._day < 0)
	{
		tmp._day += GetMonthDay(tmp._year, tmp._month);
		--tmp._month;
		if (tmp._month == 0)
		{
			--tmp._year;
			tmp._month = 12;
		}
	}
	return tmp;
}

//日期 == 日期
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)
	{
		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) 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++() 
{
	*this += 1;
	return *this;
}

//后置++
//创建了两个新对象,因为需要构成重载,所以在后置++上的参数表中需要带上int
//一般前置++用的多一些,所以在后置上加int
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;

	return tmp;
}

//日期 - 日期
Date Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;

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

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

	return n * flag;
}

void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

 9 流插入和流提取运算符重载

我们知道cin和cout是在iostream库中的

通过这张图可知cin是定义istream中,而cout定义在osteam中

看图:

这张图解释了

cout会自动识别内置类型,是因为它会去调用与之对应的运算符重载函数。

cout可以直接支持内置类型,是因为它已经在库中实现了

9.1自定义类型流插入的实现

class Date
{
public:
    void operator<<(ostream& out)
    {
	    out << _year << "年" << _month << "月" << _day << "日" << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1(2024, 9, 10);
    //d1.operator<<(cout);
    d1 << cout;
    return 0;
}

但是这样看起来是不是很奇怪我们能不能将d1<<cout 改为 cout<<d1;

将operator<<函数改为全局函数

class Date
{
private:
    int _year;
    int _month;
    int _day;
};

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

int main()
{
    Date d1(2024, 9, 10);
    //d1.operator<<(cout);
    d1 << cout;
    return 0;
}

这里写成全局函数有两个要注意的点

1. 因为日期类中的成员变量是私有的,不能在类外使用

2. 写成全局函数,参数需要都显示出来

这里有两个解决方法:

1. 我们可以将类成员变量写成函数 

例:

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

    int getyear()
    {
        return _year;
    }

    int getmonth()
    {
        return _month;
    }

    int getday()
    {
        return _day;
    }
private:
    int _year;
    int _month;
    int _day;
};

void operator<<(ostream& out,const Date& d)
{
	out << d.getyear() << "年" << d.getmonth() << "月" << d.getday() << "日" << endl;
}

int main()
{
    Date d1(2024, 9, 10);
    //cout.operator<<(d1);
    cout << d1;
    return 0;
}

2.在类中声明友元函数

class Date
{
//声明友元函数
friend void operator<<(ostream& out, const Date& d);
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
		_year = year;
		_month = month;
		_day = day;
	}
private:
    int _year;
    int _month;
    int _day;
};

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

int main()
{
    Date d1(2024, 9, 10);
    //cout.operator<<(d1);
    cout << d1;
    return 0;
}

声明成友元函数就可以使用类中的资源了

cout连续打印:cout << d1 << d2 << d3; ,这时重载函数就需要一个返回值来完成这一操作

cout << d1 << d2 << d3;的顺序是  d1先输出,然后d2,d3。它们的返回值是cout 即:ostream

连续赋值的函数实现:

class Date
{
//声明友元函数,在类中哪定义都可以
friend ostream& operator<<(ostream& out, const Date& d);
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
		_year = year;
		_month = month;
		_day = day;
	}
private:
    int _year;
    int _month;
    int _day;
};

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

int main()
{
    Date d1(2024, 9, 10);
    Date d2, d3;
    cout << d1 << d2 << d3;

    return 0;
}

9.2 自定义类型流提取的实现

class Date
{
//声明友元函数,在类中哪定义都可以
friend istream operator>>(istream& in, Date& d);
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
		_year = year;
		_month = month;
		_day = day;
	}
private:
    int _year;
    int _month;
    int _day;
};

istream& operato>>(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;
    }

    return in;
}

int main()
{
    Date d1(2024, 9, 10);
    Date d2, d3;
    cin >> d1 >> d2 >> d3;
    cout << d1 < d2 << d3;

    return 0;
}

istream& operato>>(istream& in, Date& d);这条语句中的参数都不可以加const

in是向对象d中写数据,d不能加const

写数据的过程中会改变in中的一些状态值,in不能加const

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2137509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3 + Echarts 实现中国地图

基本概念 echarts是一个基于JavaScript的开源可视化库&#xff0c;用于创建和展示各种交互式图表和图形。它可以用于数据分析、数据可视化、数据探索和数据报告等方面。我们一般使用echarts来实现数据可视化&#xff0c;本文我们使用vue3 echars来实现中国地图。 准备echarts…

Node.js 多版本安装与切换指南

一.使用nvm的方法 1. 卸载nodejs 如果你的电脑有安装nodejs&#xff0c;需要先卸载掉&#xff1b;若没有请直接下一步。 2. 前往官网下载nvm nvm&#xff1a;一个nodejs版本管理工具&#xff01; 官网地址&#xff1a;nvm文档手册 - nvm是一个nodejs版本管理工具 - nvm中文…

智能数据体系,新突破?

智能数据体系&#xff0c;新突破&#xff1f; 前言智能数据体系 前言 我们正处于一个数智融合的新时代&#xff0c;数据的价值和作用日益凸显。如何更好地理解和利用数据&#xff0c;构建先进的智能数据体系&#xff0c;成为了摆在我们面前的重要课题。 在这个背景下&#xf…

归并排序(Merge Sort)

什么是归并排序 归并排序&#xff08;Merge Sort&#xff09;是一种经典的排序算法&#xff0c;它采用分治法&#xff08;Divide and Conquer&#xff09;策略&#xff0c;将一个大数组分为两个小数组&#xff0c;分别进行排序&#xff0c;然后将这两个已排序的小数组合并成一个…

Cortex-M3架构学习:异常

异常类型 Cortex-M3 在内核水平上搭载了一个异常响应系统&#xff0c;支持为数众多的系统异常和外部中断。其 中&#xff0c;编号为 1&#xff0d;15 的对应系统异常&#xff0c;大于等于 16 的则全是外部中断。 Cortex-M3支持的中断源数目为 240 个&#xff0c;做成芯片后&…

docker进入容器运行命令详细讲解

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; 在 Docker 中&#xff0c;进入容器并运行命令是常见的操作&#xff0c;尤其是当你想要调试、检查日志或手动运行某些程序时。Docker 提供了几种方式来进入容器和执行命令。 前提条件 确保你的 Docker 容器…

C++基础面试题 | 什么是C++中的虚继承?

文章目录 回答重点菱形继承问题虚继承解决菱形继承问题虚继承的二义性解决 虚继承总结拓展知识&#xff1a;virtual关键字的用法1. 虚函数 (Virtual Function)2. 纯虚函数 (Pure Virtual Function)3. 虚析构函数 (Virtual Destructor)4. 虚继承 (Virtual Inheritance)5. 虚函数…

一篇文章带你入门机器学习 Part1 -->Machine Learning from Scratch

学习网站&#xff1a;Machine Learning from Scratch Machine Learning from Scratch (Part1神经网络&#xff09; 神经网络——Neural Networks神经网络是如何工作的&#xff1f;训练神经网络 神经网络——Neural Networks 在人工神经网络的背景下&#xff1a;一个神经元是一…

046全排列

题意 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 提示&#xff1a; 1 < nums.length < 6 -10 < nums[i] < 10 nums 中的所有整数 互不相同 难度 中等 示例 示例 1&#xff1a; 输入&#xff1…

uniapp+若依 开发租房小程序源码分享

1、使用Uniapp开发的前台&#xff0c;基于 Vue.js 开发所有前端应用的框架&#xff0c;开发者编写一套代码&#xff0c;可发布到iOS、Android、Web&#xff08;响应式&#xff09;、以及各种小程序 2、基于SpringBoot的权限管理系统&#xff0c;易读易懂、界面简洁美观。 核心…

WordBN字远笔记!更新1.2.2版本|Markdown编辑器新增高亮功能,界面新增深色模式

WordBN字远笔记1.2.2版本更新描述 WordBN字远笔记在1.2.2版本中进行了多项重要的更新与改进&#xff0c;旨在提升用户的编辑体验和视觉舒适度。 以下是本次更新的两大亮点&#xff1a;Markdown编辑器新增高亮功能以及界面新增深色模式。 1. Markdown编辑器新增高亮功能 在1…

零倾覆力矩点(ZMP)

系列文章目录 前言 在机器人学中&#xff0c;零倾力矩点&#xff08;ZMP&#xff09;是一个特征点&#xff0c;主要用于足式运动。在下文的一些假设中&#xff0c;我们将看到&#xff0c;它非正式地代表了一个系统接触反作用力的结果点。例如&#xff0c;下图中的刚体处于静态平…

leetcode:布尔运算(动态规划版)

最近又要考试&#xff0c;勉励自己复习一些之前学过的&#xff01;&#xff01;&#xff01; 开始使用的是DFS&#xff0c;遍历所有可能的情况&#xff0c;发现超时&#xff01; 下面的是动态规划的一个模板&#xff0c;dp[i][j][result]表示从s的第i个元素到第个元素&#xf…

Auracast认证:蓝牙广播音频的革新之旅

低功耗音频&#xff08;LE Audio&#xff09;技术的突破&#xff0c;为蓝牙世界带来了前所未有的广播音频功能。Auracast™&#xff0c;作为蓝牙技术联盟精心打造的音频广播解决方案&#xff0c;正引领着一场全新的音频分享革命。它不仅革新了传统蓝牙技术的局限&#xff0c;更…

HuggingFace Embedding 转为 Ollama Embedding

Ollama 是基于 LlamaCpp 开发的 CPU 上的推理引擎&#xff0c;通过 LlamaCpp 提供的脚本可以将大语言模型装换为 gguf 的二进制跟是文件&#xff0c;从而通过 Ollama 就行推理。Ollama 支持HuggingFace 大多开源模型&#xff0c;例如 Llama、Qwen、Gemma 和 Phi3 等等。 GGUF …

【Leetcode:2848. 与车相交的点 + 模拟计数】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

软件研制功能点拆分

最近需要进行软件研制概算明细表中的估算对象原始功能点&#xff0c;记录一下学习过程&#xff0c;共有EI(external input 外部输入)、EO(外部输出)、EQ(外部查询)、ILF(internal logic 内部逻辑文件)、EIF(外部接口文件)五个。 功能点计数项分为数据功能&#xff08;逻辑文件&…

【数据仓库】数据仓库常见的数据模型——范式模型

目录 一、范式 1、第一范式 2、第二范式 3、第三范式 4、进一步范式化&#xff1a;BCNF、4NF 和 5NF 简介 &#xff08;1&#xff09;Boyce-Codd 范式&#xff08;BCNF&#xff09; &#xff08;2&#xff09;第四范式&#xff08;4NF&#xff09; &#xff08;5&#x…

光华里社区“电亮生活”行动:智能科技携手志愿温情,老旧小区焕发新生机

在朝阳区建外街道光华里社区&#xff0c;一场关于“电”的革命正悄然改变着居民的生活面貌。面对老旧小区普遍存在的电力设施陈旧、线路老化、电压不稳等老大难问题&#xff0c;社区党委没有坐视不管&#xff0c;而是携手北京中兴物业管理有限公司广联物业管理中心党支部&#…

泽众P-One性能测试平台支持分布式全链路压测

在当今数字化转型加速的时代&#xff0c;高性能、高可用性的系统已成为企业竞争力的核心要素之一。为了确保系统能够在高并发、大数据量的环境下稳定运行&#xff0c;分布式全链路压测成为了不可或缺的一环。P-One凭借其强大的功能&#xff0c;支持分布式全链路压测&#xff0c…