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

news2025/4/9 1:48:00

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:C++初阶
🎯长路漫漫浩浩,万事皆有期待

上一篇博客:【C++初阶】类和对象(二)

文章目录

  • 1.const 成员
  • 2.取地址与const取地址操作符重载
  • 3. 再谈构造函数
    • 3.1 构造函数体赋值
    • 3.2 初始化列表
      • 3.2.1 **定义**
      • 3.2.2 `注意事项`:
    • 3.3 explicit关键字
  • 4. static成员
    • 4.1 概念
    • 4.2 特性
  • 5.日期类的实现
    • 5.1 构造函数、拷贝构造函数、赋值运算符重载、 析构函数
    • 5.2 打印函数
    • 5.3 日期 += 天数
    • 5.4 日期 + 天数
    • 5.5 日期 -= 天数
    • 5.6 日期 - 天数
    • 5.7 前置 ++
    • 5.8 后置 ++
    • 5.9 前置 – –
    • 5.10 后置– –
    • 5.11 日期类的大小关系比较
      • 5.11.1 >运算符的重载
      • 5.11.2 ==运算符的重载
      • 5.11.3 >=运算符的重载
      • 5.11.4 <运算符的重载
      • 5.11.5 <=运算符的重载
      • 5.11.6 !=运算符的重载
    • 5.17 日期 - 日期
  • 6.总结:

1.const 成员

若定义了一个 const 的对象,然后访问其成员函数,会报错,这是为什么?

因为在传参时,d2 的地址 &d2 会被传递给 Print() ,作为隐藏的参数 this 指针:

void Print(Date* const this) // this 指针隐藏
{
    cout << _year << '-' << _month << '-' << _day << endl;
}

对于 Date d1 ,传递过去的 &d 是 Date* ;而 const Date d2 ,传递过去的 &d2 是 const Date* .
在这里插入图片描述

对于 this 指针本身是 Date* const this ,此刻 const 修饰的是 this ,this 不可改,但是 * this 是可改的 。
而传参时传过来的 &d2 为 const Date* ,这时 const 修饰指针指向的内容,即对象本身不可改了。

但对于Print()函数的 this 来说,*this,也就是指向的内容,即对象本身是可改的,但是现在由于 const 使得指向内容不可改,对于权限来说,只能对等和缩小,但是const Date d2在传递时权限放大了,所以报错 。

为了解决这一问题,C++ 引入了 const 成员 ,在该成员后加上 const :
在这里插入图片描述

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

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

这时 this 指针的类型变为 const Date* const this ,权限对等了,这时 this 指针不能改,且 *this ,即 this 指向的对象也不能改,和 const Date d2 的目的相同:不可改 d2 。 这时,就不会报错了。

而对于 d1 对象,它虽然没有 const ,但是也只是 权限缩小,使得 d1 在 Print() 成员函数中不可修改而已,也是没问题的。
在这里插入图片描述

总结:成员函数加上 const 是好的,建议能加上 const 都加上。这样普通对象和 const 对象,都可以调用。但是如果对于要对 对象 进行修改的成员函数不要加上,不然就完成不了目的了

在这里插入图片描述

:对于构造和析构不能加上const修饰。

2.取地址与const取地址操作符重载

我们知道,对于自定义类型成员来说,平常的操作符需要重载后才能对对象进行操作。但是对于自定义类型的对象来说,如果不写这两个成员函数,使用默认的成员函数照样也可以完成目的:
在这里插入图片描述
打印结果:
在这里插入图片描述

所以一般不写,但是写的话也可以:

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

在这里插入图片描述

就只要返回 this 就可以;对于 const 取地址操作符,则要加上 const 成员,并且返回的指针也要加上 const 修饰。

在这里插入图片描述

取地址与const取地址操作符重载:
可以直接取出成员的地址,一般不自己写

运用场景:使其取不到地址

在这里插入图片描述

总结: 对于六个默认成员函数,前四个最重要:构造、析构、拷贝构造、运算符重载。后两个有一定作用,但是作用不大。

3. 再谈构造函数

3.1 构造函数体赋值

在创建对象时,编译器会通过调用构造函数,给对象中的各个成员变量一个合适的初始值:

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

注意:虽然通过调用上述的构造函数后,对象中的每个成员变量都有了一个初始值,但是构造函数中的语句只能将其称作为赋初值,而不能称作为初始化。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。

class Date
{
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;// 第一次赋值
		_year = 2022;// 第二次赋值
		//...
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

3.2 初始化列表

3.2.1 定义

以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。初始化列表:对象的成员定义的位置

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

3.2.2 注意事项

1.每个成员变量在初始化列表中最多只能出现一次
 因为初始化只能进行一次,所以同一个成员变量在初始化列表中不能多次出现。

在这里插入图片描述

2.类中包含以下成员,必须放在初始化列表进行初始化

2.1 引用成员变量
 引用类型的变量在定义时就必须给其一个初始值,所以引用成员变量必须使用初始化列表对其进行初始化。

	int a = 10;
	int& b = a;// 创建时就初始化

2.2 const成员变量
 被const修饰的变量也必须在定义时就给其一个初始值,也必须使用初始化列表进行初始化。

	const int a = 10;//correct 创建时就初始化
	const int b;//error 创建时未初始化

2.3 自定义类型成员(该类没有默认构造函数)
 若一个类没有默认构造函数,那么我们在实例化该类对象时就需要传参对其进行初始化,所以实例化没有默认构造函数的类对象时必须使用初始化列表对其进行初始化。

默认构造函数是指不用传参就可以调用的构造函数:
 1.编译器自动生成的构造函数。
 2.无参的构造函数。
 3.全缺省的构造函数。

class A //该类没有默认构造函数 
{
public:
	A(int val) //注:这个不叫默认构造函数(需要传参调用)
	{
		_val = val;
	}
private:
	int _val;
};

class B
{
public:
	B()
		:_a(2021) //必须使用初始化列表对其进行初始化
	{}
private:
	A _a; //自定义类型成员(该类没有默认构造函数)
};

总结:在定义时必须进行初始化的变量类型,就必须放在初始化列表进行初始化。

三、尽量使用初始化列表初始化
 因为初始化列表实际上就是当实例化一个对象时,该对象的成员变量定义的地方,所以无论是否使用初始化列表,都会走这么一个过程(成员变量需要定义出来)

严格来说:
 1.对于内置类型,使用初始化列表和在构造函数体内进行初始化实际上是没有差别的,其差别就类似于如下代码:

// 使用初始化列表
int a = 10
// 在构造函数体内初始化(不使用初始化列表)
int a;
a = 10;

2.对于自定义类型,使用初始化列表可以提高代码的效率

class Time
{
public:
	Time(int hour = 0)
	{
		_hour = hour;
	}
private:
	int _hour;
};
class Test
{
public:
	// 使用初始化列表
	Test(int hour)
		:_t(12)// 调用一次Time类的构造函数
	{}
private:
	Time _t;
};

当我们要实例化一个Test类的对象时,我们使用了初始化列表,在实例化过程中只调用了一次Time类的构造函数。

我们若是想在不使用初始化列表的情况下,达到我们想要的效果,就不得不这样写了:

class Time
{
public:
	Time(int hour = 0)
	{
		_hour = hour;
	}
private:
	int _hour;
};
class Test
{
public:
	// 在构造函数体内初始化(不使用初始化列表)
	Test(int hour)
	{ //初始化列表调用一次Time类的构造函数(不使用初始化列表但也会走这个过程)
		Time t(hour);// 调用一次Time类的构造函数
		_t = t;// 调用一次Time类的赋值运算符重载函数
	}
private:
	Time _t;
};

这时,当我们要实例化一个Test类的对象时,在实例化过程中会先在初始化列表时调用一次Time类的构造函数,然后在实例化t对象时调用一次Time类的构造函数,最后还需要调用了一次Time类的赋值运算符重载函数,效率就降下来了。

3.初始化列表虽好,但有些地方还是需要函数体赋值,比如判断开辟空间是否成功·

在这里插入图片描述

还要一些工作是初始化列表做不完的,比如动态开辟二维数组

在这里插入图片描述

四、成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关:

#include <iostream>
using namespace std;
int i = 0;
class Test
{
public:
	Test()
		:_b(i++)
		,_a(i++)
	{}
	void Print()
	{
		cout << "_a:" << _a << endl;
		cout << "_b:" << _b << endl;
	}
private:
	int _a;
	int _b;
};
int main()
{
	Test test;
	test.Print(); //打印结果test._a为0,test._b为1
	return 0;
}

代码中,Test类构造函数的初始化列表中成员变量_b先初始化,成员变量_a后初始化,按道理打印结果test._a为1,test._b为0,但是初始化列表的初始化顺序是成员变量在类中声明次序,所以最终test._a为0,test._b为1。

 例题:
在这里插入图片描述

答案:D,按申明的顺序,先初始化_a2,为随机值,后初始化_a1,为1

在这里插入图片描述
所以在写程序时要尽量按照申明的顺序初始化,否则容易入坑:

在这里插入图片描述
运行以上程序,会崩溃,因为按申明的顺序,先初始化_a,但此时capacity还未初始化,为随机值,开辟的空间太大,程序崩溃。

五、到底是否使用初始化列表,具体问题具体分析

在这里插入图片描述

3.3 explicit关键字

构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还支持隐式类型转换。

#include <iostream>
using namespace std;
class A
{
public:
	 A(int a) //单个参数的构造函数
		:_a(a)
	{
	cout << "A(int a)" << endl;
	}
private:
	int _a;
};
int main()
{
	A aa1 (1);
	A aa2 = 2;//隐式类型转换
	return 0;
}

在语法上,代码中A aa2 = 2等价于以下两句代码:

Date tmp(2); //先构造
Date aa2(tmp); //再拷贝构造

在早期的编译器中,当编译器遇到 A aa2 = 2 这句代码时,会先构造一个临时对象(临时对象具有常性),再用临时对象拷贝构造 aa2;但是现在的编译器已经做了优化,当遇到 A aa2 = 2这句代码时,会按照 A aa2 (2)这句代码处理,这就是隐式类型转换。
在这里插入图片描述

实际上,我们早就接触了隐式类型转换:

int i = 10;
double d = i; //隐式类型转换

在这个过程中,编译器会先构建一个double类型的临时变量接收i的值,然后再将该临时变量的值赋值给d。这就是为什么函数可以返回局部变量的值,因为当函数被销毁后,虽然作为返回值的变量也被销毁了,但是隐式类型转换过程中所产生的临时变量并没有被销毁,所以该值仍然存在。

但是,对于单参数的自定义类型来说,A aa2 = 2 这种代码的可读性不是很好,我们若是想禁止单参数构造函数的隐式转换,可以用关键字explicit来修饰构造函数。
在这里插入图片描述

4. static成员

4.1 概念

声明为static的类成员称为类的静态成员。用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

4.2 特性

一、静态成员为类对象所共享,不属于某个具体的对象

#include <iostream>
using namespace std;
class Test
{
private:
	static int _n;
};
int main()
{
	cout << sizeof(Test) << endl;
	return 0;
}

结果计算Test类的大小为1,因为静态成员_n是存储在静态区的,属于整个类,也属于类的所有对象。所以计算类的大小或是类对象的大小时,静态成员并不计入其总大小之和。

二、静态成员变量必须在类外定义,定义时不添加static关键字

class Test
{
private:
	static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;

注意:这里静态成员变量_n虽然是私有,但是我们在类外突破类域直接对其进行了访问。这是一个特例,不受访问限定符的限制,否则就没办法对静态成员变量进行定义和初始化了。

三、静态成员函数没有隐藏的this指针,不能访问任何非静态成员

class Test
{
public:
	static void Fun()
	{
		cout << _a << endl; //error不能访问非静态成员
		cout << _n << endl; //correct
	}
private:
	int _a; //非静态成员
	static int _n; //静态成员
};

注意:含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量。

四、访问静态成员变量的方法
1.当静态成员变量为公有时,有以下几种访问方式:

#include <iostream>
using namespace std;
class Test
{
public:
	static int _n; //公有
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
	Test test;
	cout << test._n << endl; //1.通过类对象突破类域进行访问
	cout << Test()._n << endl; //3.通过匿名对象突破类域进行访问
	cout << Test::_n << endl; //2.通过类名突破类域进行访问
	return 0;
}

2.当静态成员变量为私有时,有以下几种访问方式:

#include <iostream>
using namespace std;
class Test
{
public:
	static int GetN()
	{
		return _n;
	}
private:
	static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
	Test test;
	cout << test.GetN() << endl; //1.通过对象调用成员函数进行访问
	cout << Test().GetN() << endl; //2.通过匿名对象调用成员函数进行访问
	cout << Test::GetN() << endl; //3.通过类名调用静态成员函数进行访问
	return 0;
}

五、静态成员和类的普通成员一样,也有public、private和protected这三种访问级别
 所以当静态成员变量设置为private时,尽管我们突破了类域,也不能对其进行访问。

提两个问题
 1、静态成员函数可以调用非静态成员函数吗?
答:不可以。因为非静态成员函数的第一个形参默认为this指针,而静态成员函数中没有this指针,故静态成员函数不可调用非静态成员函数。
 2、非静态成员函数可以调用静态成员函数吗?
答:可以。因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制。

六、拓展:设计一个类,只能在栈/堆上创建对象

思路1:将构造函数私有化,但都不用创建对象了

在这里插入图片描述

思路2:利用静态成员函数,通过类名调用静态成员函数进行对象的创建

在这里插入图片描述

同时,单例模式也是类似的思想

5.日期类的实现

在学习了C++的6个默认成员函数后,我们现在动手实现一个完整的日期类,来加强对这6个默认成员函数的认识。

这是日期类中所包含的成员函数和成员变量:

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month);
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1);
	// 拷贝构造函数
	Date(const Date& d);
	// 赋值运算符重载
	Date& operator=(const Date& d);
	// 析构函数
	~Date();
	// 打印函数
	void Print() const;
	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day) const;
	// 日期-=天数
	Date& operator-=(int day);
	// 日期-天数
	Date operator-(int day) const;
	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int);
	// 前置--
	Date& operator--();
	// 后置--
	Date operator--(int);
	// 日期的大小关系比较
	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;
	// 日期-日期
	int operator-(const Date& d) const;

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

5.1 构造函数、拷贝构造函数、赋值运算符重载、 析构函数

进入构造函数体,首先需要检查日期的合法性,只有当日期合法时,才能进行后续的构造操作。

// 获取某年某月的天数
static int GetMonthDay(int year, int month)//inline 
{
	// 数组存储平年每个月的天数
	static int dayArray[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)))
	{
		//闰年2月的天数
		return 29;
	}
	return dayArray[month];
}
// 构造函数
Date::Date(int year, int month, int day)
{
	// 检查日期的合法性
	if (year >= 0
	&& month >= 1 && month <= 12
	&& day >= 1 && day <= GetMonthDay(year, month))
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		// 严格来说抛异常更好
		cout << "非法日期" << endl;
		cout << year << "年" << month << "月" << day << "日" << endl;
	}
}

GetMonthDay函数中的细节:
 1.该函数可能被多次调用,但指令较多,所以我们没有将其设置为内联函数。
 2.函数中存储每月天数的数组最好是用static修饰,存储在静态区,避免每次调用该函数都需要重新开辟数组
 3.逻辑与应该先判断month == 2是否为真,因为当不是2月的时候我们不必判断是不是闰年。
 4.用static修饰就可以在类外面调用该函数了
注意:当函数声明和定义分开时,在声明时注明缺省参数定义时不标出缺省参数

拷贝构造函数:

 // 拷贝构造函数
// d2(d1)
Date(const Date& d)
{
	this->_year = d._year;
	_month = d._month;
	_day = d._day;
}

赋值运算符重载:

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d)
{
	if (this != &d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}
	return *this;
}

析构函数:

// 析构函数
~Date()
{
	// 清理工作
}

5.2 打印函数

// 打印函数
void Date::Print() const
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

5.3 日期 += 天数

对于+=运算符,我们先将需要加的天数加到日上面,然后判断日期是否合法,若不合法,则通过不断调整,直到日期合法为止。
调整日期的思路:
 1.若日已满,则日减去当前月的天数,月加一。
 2.若月已满,则将年加一,月置为1。
反复执行1和2,直到日期合法为止。

// 日期+=天数
Date& Date::operator+=(int day)
{
	if (day<0)
	{
		// 复用operator-=
		*this -= -day;
	}
	else
	{
		_day += day;
		// 日期不合法,通过不断调整,直到最后日期合法为止
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month > 12)
			{
				_year++;
				_month = 1;
			}
		}
	}
	return *this;
}

:当需要加的天数为负数时,转而调用-=运算符重载函数。

5.4 日期 + 天数

+运算符的重载,我们可以复用上面已经实现的+=运算符的重载函数。但是要注意:虽然我们返回的是加了之后的值,但是对象本身的值并没有改变。就像a = b + 1,b + 1的返回值是b + 1,但是b的值并没有改变。所以我们还可以用const对该函数进行修饰,防止函数内部改变了this指针指向的对象。

// 日期+天数
Date Date::operator+(int day) const
{
	Date tmp(*this);// 拷贝构造tmp,用于返回
	// 复用operator+=
	tmp += day;

	return tmp;
}

注意:+=运算符的重载函数采用的是引用返回,因为出了函数作用域,this指针指向的对象没有被销毁。但+运算符的重载函数的返回值只能是传值返回,因为出了函数作用域,对象tmp就被销毁了,不能使用引用返回

5.5 日期 -= 天数

对于-=运算符,我们先用日减去需要减的天数,然后判断日期是否合法,若不合法,则通过不断调整,直到日期合法为止。
调整日期的思路:
 1.若日为负数,则月减一。
 2.若月为0,则年减一,月置为12。
 3.日加上当前月的天数。
反复执行1、2和3,直到日期合法为止。

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		// 复用operator+=
		*this += -day;
	}
	else
	{
		_day -= day;
		// 日期不合法,通过不断调整,直到最后日期合法为止
		while (_day <= 0)
		{
			_month--;
			if (_month == 0)
			{
				_year--;
				_month = 12;
			}
			_day += GetMonthDay(_year, _month);
		}
	}
	return *this;
}

:当需要减的天数为负数时,转而调用+=运算符重载函数。

5.6 日期 - 天数

和+运算符的重载类似,我们可以复用上面已经实现的-=运算符的重载函数,而且最好用const对该函数进行修饰,防止函数内部改变了this指针指向的对象。

// 日期-天数
Date Date::operator-(int day) const
{
	Date tmp(*this);// 拷贝构造tmp,用于返回
	// 复用operator-=
	tmp -= day;

	return tmp;
}

注意:-=运算符的重载函数采用的是引用返回,但-运算符的重载函数的返回值只能是传值返回,也是由于-运算符重载函数中的tmp对象出了函数作用域被销毁了,所以不能使用引用返回。

5.7 前置 ++

前置++,我们可以复用+=运算符的重载函数。

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

5.8 后置 ++

由于前置++和后置++的运算符均为++,为了区分它们的运算符重载,我们给后置++的运算符重载的参数加上一个int型参数,使用后置++时不需要给这个int参数传入实参,因为这里int参数的作用只是为了跟前置++构成重载,仅仅是占位,与前置区分。

// 后置++
Date Date::operator++(int)
{
	Date tmp(*this);// 拷贝构造tmp,用于返回
	// 复用operator+=
	*this += 1;
	return tmp;
}

注意:后置++也是需要返回加了之前的值,只能先用对象tmp保存之前的值,然后再然对象加一,最后返回tmp对象。由于tmp对象出了该函数作用域就被销毁了,所以后置++只能使用传值返回,而前置++可以使用引用返回。
由于前置效率高,且不需要进行拷贝,所以我们一般用前置++

5.9 前置 – –

前置–,我们也是可以复用前面的-=运算符的重载函数。

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

5.10 后置– –

后置–需要注意的事项和后置++是一样的,我这里就不过多阐述了。

// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);// 拷贝构造tmp,用于返回
	// 复用operator-=
	*this -= 1;
	return tmp;
}

5.11 日期类的大小关系比较

日期类的大小关系比较需要重载的运算符看起来有6个,实际上我们只用实现两个就可以了,然后其他的通过复用这两个就可以实现。

注意:进行日期的大小比较,我们并不会改变传入对象的值,所以这6个运算符重载函数都应该被const所修饰。

5.11.1 >运算符的重载

先判断年是否大于,再判断月是否大于,最后判断日是否大于,这其中有一者为真则函数返回true,否则返回false。

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

5.11.2 ==运算符的重载

==,年月日均相等,则为真。

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

5.11.3 >=运算符的重载

>=,即大于或者等于,满足其中之一即可。

bool Date::operator>=(const Date& d) const
{
	return *this > d || *this == d;
}

5.11.4 <运算符的重载

<,大于等于的反面即是小于。

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

5.11.5 <=运算符的重载

<=,大于的返回即是小于等于。

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

5.11.6 !=运算符的重载

!=,等于的反面即是不等于。

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

5.17 日期 - 日期

日期 - 日期,即计算传入的两个日期相差的天数。我们只需要让较小的日期的天数一直加一,直到最后和较大的日期相等即可,这个过程中较小日期所加的总天数便是这两个日期之间差值的绝对值。若是第一个日期大于第二个日期,则返回这个差值的正值,若第一个日期小于第二个日期,则返回这个差值的负值。

// 日期-日期
int Date::operator-(const Date& d) const
{
	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;
}

代码中使用flag变量标记返回值的正负,flag为1代表返回的是正值,flag为-1代表返回的是负值,最后返回总天数与flag相乘之后的值即可。

6.总结:

今天我们认识并具体学习了类和对象的 const成员、取地址与const取地址操作符重载、初始化列表、explicit关键字、隐式类型转换、static成员的知识,并通过实现一个完整的日期类加强了默认成员函数的认识和近期所学知识的理解。接下来,我们将继续学习类和对象的相关知识。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

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

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

相关文章

无线蓝牙耳机佩戴舒适的有哪几款?佩戴舒适的蓝牙耳机推荐

自从真无线蓝牙耳机产品推出以来&#xff0c;它已经逐渐成为了当代年轻人外出必带的随身数码产品。虽然市面上不缺好产品&#xff0c;但大家对于蓝牙耳机的佩戴舒适度害死更加重要的&#xff0c;下面就来分享几款佩戴舒适的蓝牙耳机吧。 一、南卡小音舱Lite2蓝牙耳机 参考价格…

Android studio单独导入官方例程camera-calibration

1.官方例程camera-calibration 2.将官方例程camera-calibration copy到AndroidStudioProjects项目目录下 3修改AndroidManifest.xml <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android“http://schemas.android.com/apk/res/andr…

2007年计算机真题

2007年计算机真题 数学基础部分 一、用逻辑符号表达下列语句&#xff08;每小题 2 分&#xff0c;共 4 分&#xff09; 1&#xff0e;分别用两种量词形式写出&#xff1a;在北京居住的人未必都是北京人。 答: 全域: 所有人 P ( x ) \mathrm{P}(\mathrm{x}) P(x) 表示 x \…

「企业应用架构」应用架构概述

在信息系统中&#xff0c;应用架构或应用架构是构成企业架构&#xff08;EA&#xff09;支柱的几个架构域之一 应用架构描述了业务中使用的应用程序的行为&#xff0c;重点是它们如何相互之间以及如何与用户交互。它关注的是应用程序消费和生成的数据&#xff0c;而不是它们的内…

问题杂谈(三十)项目中引入Geotools

步骤 在pom.xml中确定好需要引入的模块&#xff0c;根据自己的需要修改 <!--geotool工具包--><dependency><groupId>org.geotools</groupId><artifactId>gt-epsg-hsql</artifactId><version>27.1</version></dependency&g…

这个学期,110多所高校把AI和大模型带进校园

2023 年春季学期&#xff0c;飞桨校园 AI Day 已登陆全国 114 所高校&#xff0c;为同学们提供了丰富的 AI 学习交流机会。 截至目前&#xff0c;超过 400 所高校的同学线上参与 AI 知识竞赛 PK &#xff0c;累计 2 万余名同学参与春季 AI Day 活动&#xff0c;更有 65 所高校举…

Express框架的路由配置

Express 是一个流行的基于 Node.js 的 Web 开发框架&#xff0c;它可以帮助我们快速搭建一个 Web 应用程序。在 Express 中&#xff0c;路由是一个非常重要的概念。路由指的是根据客户端请求的不同路径和 HTTP 方法来执行不同的处理逻辑。在本篇博客中&#xff0c;我们将介绍如…

美团太细了,HashMap可以存null,ConcurrentHashMap不可以,为什么?

△Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 420 篇原创分享 作者 l Hollis 来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09; 我们知道&#xff0c;ConcurrentHashMap在使用时&#xff0c;和HashMap有一个比较大的区别&#xff0c;那就是HashM…

基于Spring Boot的在线考试系统

系统分析 可行性分析 一个完整的系统&#xff0c;可行性分析是必须要有的&#xff0c;因为关系到系统生存问题&#xff0c;对开发的意义进行分析&#xff0c;能否通过本系统来补充线下在线考试管理模式中的缺限&#xff0c;去解决其中的不足等&#xff0c;通过对本系统&#…

FastStone Capture安装、注册及使用教程(截屏、滚动截图、录屏、图片编辑工具)

FastStone Capture是一款集截屏、滚动截图、录屏、图片编辑为一体轻量级截图软件。免费试用30天。 如果你需要找一个具有上述红色字描述的功能的软件的话&#xff0c;可以继续往下阅读。若是你想找一个截图贴图的软件&#xff0c;可以参考&#xff1a; Snipaste介绍、安装、使用…

学系统集成项目管理工程师(中项)系列19b_成本管理(下)

1. 成本估算 1.1. 编制完成项目活动所需资源的大致成本 1.2. 在设计阶段多做些额外的工作可能减少执行阶段和产品运行时的成本 1.3. 项目估算的准确性随着项目的进展而提高 1.3.1. 【19下选48】 1.4. 针对完成活动所需资源的可能成本进行的量化评估 1.5. 容易被忽视的主要…

如何从0开始系统的学习kotlin?

Kotlin强大的静态语言特性相信不用我多讲&#xff0c;大家都或多或少明白一些。无论是对于安卓程序员还是JavaEE程序员来说&#xff0c;掌握Kotlin都是十分有必要的。 Kotlin作为谷歌官方支持的编程语言&#xff0c;目前&#xff0c;不少公司的Android开发工程师岗位表示要有K…

基于springboot广场舞团

系统分析 系统可行性分析 1、经济可行性 由于本系统本身存在一些技术层面的缺陷&#xff0c;并不能直接用于商业用途&#xff0c;只想要通过该系统的开发提高自身学术水平&#xff0c;不需要特定服务器等额外花费。所有创造及工作过程仅需在个人电脑上就能实现&#xff0c;使…

[架构之路-193]-《软考-系统分析师》-2-应用数学 - 项目周期与关键路径(PERT图、甘特图、单代号网络图、双代号网络图)

1. 关键概念 1.1 关键路径 关键路径通常&#xff08;但并非总是&#xff09;是决定项目工期的进度活动序列。它是项目中最长的路径&#xff0c;即使很小浮动也可能直接影响整个项目的最早完成时间。关键路径的工期决定了整个项目的工期&#xff0c;任何关键路径上的终端元素…

asp.net+C#德育课程分数统计管理系统

本中小学德育管理系统主要学校内部提供服务&#xff0c;系统分为管理员&#xff0c;教师和学生3个大模块。 本研究课题重点主要包括了下面几大模块&#xff1a;用户登录&#xff0c;管理员信息管理学生信息管理&#xff0c;教师信息管理&#xff0c;班级成绩管理&#xff0c;学…

YOLOv5改进系列(2)——添加CBAM注意力机制

【YOLOv5改进系列】前期回顾&#xff1a; YOLOv5改进系列&#xff08;0&#xff09;——重要性能指标与训练结果评价及分析 YOLOv5改进系列&#xff08;1&#xff09;——添加SE注意力机制 目录 &#x1f680;一、CBAM注意力机制原理 1.1 CBAM方法介绍 1.2 通道注意力机制…

Android车载应用开发和移动应用开发有什么区别?

背景 2022年对我来说是非常糟糕的一年&#xff0c;因为疫情原因导致公司收益不好&#xff0c;所有我被辞退了&#xff0c;离职之后呢有尝试过头简历面试&#xff0c;发现面试的机会很少&#xff0c;经过一段时间的沉淀后&#xff0c;我萌生了转行做车载的想法。为什么会产生这…

Python数据结构与算法

笔记——Python数据结构与算法 一、栈和队列 1.1 栈的定义 栈、队列、双端队列和列表都是有序的数据集合&#xff0c; 其元素的顺序取决于添加顺序或移除顺序。一旦某个元素被添加进来&#xff0c;它与前后元素的相对位置将保持不变。这样的数据集合经常被称为线性数据结构。…

【信息系统项目管理师】计算题-2023年5月8日、9日总结

8日没做太多内容&#xff0c;主要做了计算题。关于上午计算专题&#xff0c;运筹学相关内容。 涉及计算题内容的确不少&#xff0c;风险&#xff0c;决策&#xff0c;加权&#xff0c;采购&#xff0c;沟通渠道&#xff0c;盈亏平衡点。 运筹学涉及最短路径&#xff0c;图论&am…

前端:20 个常见的前端算法题

现在面试中&#xff0c;算法出现的频率越来越高了&#xff0c;大厂基本必考 今天给大家带来 20 个常见的前端算法题&#xff0c;重要的地方已添加注释&#xff0c;如有不正确的地方&#xff0c;欢迎多多指正 &#x1f495; 1、两数之和 题目&#xff1a; 给定一个数组 nums …