类与对象(三)

基于前面两次对类与对象的基本讲解,本次主要通过写一个完整的Date类对前面提到的相关概念的一个更加具体的理解。希望对文中有错误的地方加以指出,感谢!

日期类的实现 

  • Date类的基本框架

首先定义Date类的基本框架:

//一个Date类的简单框架,包含年、月、日三个成员变量
class Date
{

//私有成员变量,在类外不能直接访问
private:
    int _year;
    int _month;
    int _day;
} 
  • 构造函数

上面完成了对类的基本框架的定义之后,我们需要对类中的私有成员变量进行初始化,对于初始化工作,由两种方法,一是通过构造函数,二是通过初始化成员列表: 

//一个Date类的简单框架,包含年、月、日三个成员变量
class Date
{
//公有成员函数,在前面提到过访问限定符的作用范围,可以理解为在这里完成
//对私有成员变量的一系列处理,需要注意初始化成员列表与构造函数只能存在一个
public:
    //全缺省构造函数初始化,即是在形参后面给缺省值,当创建对象时给参数
    //则缺省参数没有用,当没有提供参数时,也不至于出现随机值
    Date(int year = 0,int month = 1,int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //初始化成员列表,对私有变量的初始化
    Date(int year = 0,int month = 1,int day = 1)
            :_year(year)
            ,_month(month)
            ,_day(day)
            {}

//私有成员变量,在类外不能直接访问
private:
    int _year;
    int _month;
    int _day;
} 
  • 析构函数

以上是对构造函数的定义,以下析构函数只是为了表明在(二)中提到的析构函数在程序结束时会自动调用。

//对于析构函数的作用,它主要是对我们在堆上申请的空间之后,在我们不使用时
//需要我们对申请的空间进行销毁工作,在这里不太好表现出析构函数的作用
~Date()
{
    cout << " 调用析构函数" << endl; //主要是为了表现,在程序结束,的确会自动调用析构函数
} 
  • 拷贝构造函数及无穷递归调用的理解

拷贝构造函数,拷贝构造函数时拿一个已经存在的对象去创建一个 新的对象。

 //拷贝构造函数 
//这里const可以加也可以不加,可以简单认为是对对象D1的保护, 
//Date D2(D1); 用对象D1拷贝构造新对象D2 ,可以这样看这一句:D2.Date(D1);
//由于这里的参数时Date这个类类型实例化出来的对象D1,因此我们需要用Date类类型
//来接收这个参数,这里的D1传参给了d,在这里必须用引用传参,否则会引发无穷递归调用
Date(const Date& d)
{
    _year = d._year;
    _month = d._month;
    _day = d._day;
}

对于上述代码为什么会引发无穷递归调用呢?这里进行一个简单的解释: 

 

从内存角度来理解一下传值和引用传参,就可以更加容易理解为什么传值会引发无穷递归调用 

根据上图,我们可以理解成如果传值传参,就会拷贝——>赋值(为了完成赋值)——>拷贝 ——>赋值(为了完成赋值)……

以上就是我对如果采用传值传参会引发无穷递归调用的原因的理解。

  • 运算符重载函数
  •  赋值运算符重载  operator=
//operator=  d2 = d1
//这里为了方便我们理解,可以看成 d2.operator= (d1)  operator=看成整体当做函数名
Date& operator=(const Date& d)
{
    //d2 = d2  表示如果自己对自己赋值,则不处理
    if(this != &d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    return *this;
}

对于以上重载赋值运算符,为什么要用引用传参?又为什么要用引用传返回值呢?

首先,这里采用引用传参,是为了减少拷贝构造,提高效率。那如果运用传值传参会引发无穷递归调用吗? 在这里如果用传值传参并不会引发无穷递归调用,只是会有一次拷贝构造,这里采用引用传参主要就是减少拷贝。提提高效率。

为什么要用引用传返回值呢?我们先来看一下结果:

很明显如果没有用引用传返回值多调用了两次析构函数,这是因为如果没有引用传返回值在执行return *this 时会去调用拷贝构造函数产生一个临时对象。而临时对象出作用域时会进行销毁,也就是会去调用析构函数。前面提到过如果我们没有自定义拷贝构造函数,而是由编译器自动生成拷贝构造函数时,会产生浅拷贝问题(同一块空间被释放两次就会产生崩溃)。如果当我们的对象含有指针类型的成员变量时。如果我们没有使用引用穿返回值,也没有自定义拷贝构造函数时。这是就会造成程序崩溃。因此综上,我们在进行赋值运算符重载时采用引用传返回值,一是为了减少拷贝产生临时对象,提高效率。另一个也是为了保证我们的程序更加安全。

  • 日期的比较·
  • operator==

判断两个日期是否相等

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

判断D1是否大于D2

//D1 > D2
bool operator>(const Date& d)
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year)
	{
		if (_month > d._month)
		{
			return true;
		}
		else if (_month == d._month)
		{
				return _day > d._day;
		}
		else
			return false;
	}
	else
	{
		return false;
	}
}
  • operator<

判断D1 是否小于D2,由于上面已经实现了> 和 ==。因此对于这里可以直接复用上面的代码进行实现,小于即即不大于也不等于,逻辑上对> 和 == 取反即可。以下均可带这这样的逻辑进行处理

//D1 < D2
bool operator<(const Date& d)
{
	return !(*this > d) && !(*this == d);
}
  •  operator >=

判断D1是否大于等于D2 同样的道理,大于等于即是不小于。

//D1 >= D2
bool operator>=(const Date& d)
{
	return !(*this < d) ;
}
  • operator<=
//D1 <= D2
bool operator<=(const Date& d)
{
	return !(*this > d) ;
}
  • operator!= 
//D1 != D2
bool operator!=(const Date& d)
{
	return !(*this == d);
}
  • 日期的加减运算

日期的加减运算由于不是简单的整形加减运算,它涉及到天满进月,月满进年。因此我们需要先获取月份的天数

//获取月份天数
int GetMonthDay()
{
	int _MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	
	//如果是闰年二月则为29天
	if (_year % 4 == 0 || _year % 100 != 0 && _year % 400 == 0)
	{
		_MonthDay[2] = 29;
	}

	return _MonthDay[_month];
}
  •  operator+=

这段的逻辑就是,先计算+=之后的天数,如果天数不合法(即天数大于当前月的天数),就对天数进行处理,直到合法

//D1 += day 
Date& operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay())
	{
        //注意这里月份进位与天数的处理顺序,如果先将月份进一位,那么天数减去的就是下一
        //个月的天数,而我们应该减去的是当前月份的天数
		_day -= GetMonthDay();
		_month += 1;
		while (_month > 12)
		{
			_month -= 12;
			_year += 1;

		}
	}
	return *this;
}
  • operator+

上面实现了日期的+=操作,这里我们实现日期加天数。

//D1 + day
Date operator+(int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}

这里我们可以看到,我们返回的是一个临时对象,因此不能用引用返回,并且我们处理的是*this创建出来的临时对象,而不是*this本身,这是为什么呢? 

区分一下 += 与 + 的区别就很容易明白了,D1 += day, D1自身也会被改变,而D1 + day ,D1自身不会被改变。因此我们要用D1创建临时对象,对临时对象进行处理,而临时对象出作用域会被销毁,因此要返回他的拷贝,而不能用引用返回。

  • operator-=

日期-=日期没什么意义就不写了,就跟上面日期+=日期没有意义类似,因此就不写了。这里写日期-=天数表示一个以前的日期。

    //D1 -= day	
    Date& operator-=(int day)
	{
		_day -= day;

		//这里需要注意条件,因为天数和月份都没有0天0月
		while (_day <= 0)
		{
			_month--;
			if (_month == 0)
			{
				_month = 12;
				_year--;
			}
			_day += GetMonthDay();
		}

		return *this;
	}
  •  operator-

这里的实现类似与上面的operator+的实现

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

日期减去日期表示天数 ,注意循环条件的判断,把大的日期往小的日期上靠,当年月都相同时,天数相减就是两个时期之间的天数。

//D1 - D2 
int operator-( Date& d)
{
	//大的日期减小的日期才有意义
	//默认D1 > D2,如果不是,就交换对象 
	if(*this < d)
	{
		swap(*this, d);
	}
	if (!(*this < d))
	{
        //注意这里的循环条件,
		while (_year > d._year ||_year == d._year && _month != d._month)
		{
			while (_year > d._year || _month != d._month)
			{
				--_month;
				if (_month == 0)
				{
					_month = 12;
					--_year;
				}
				_day += GetMonthDay();
			}

		}
		return _day - d._day;
	}
}
  • operator++(前置++与后置++)

日期增加一天,前置加加。

//前置++
//引用传返回值,减少拷贝提高效率
Date& operator++()
{
	*this += 1;
	return *this;
}

//后置++
//这里不用引用传返回值,因为tmp是临时对象,出作用域会被销毁,如果用引用传返回值
//就会是随机值(简单理解,出作用域对象还存在就用引用返回,否则传值返回)
Date operator++(int) // 这里的int,只是为了与上面前置++构成重载,没有实际意义
{
	Date tmp(*this); // tmp是通过*thid创建的一个临时对象
	*this += 1;
	return tmp;
}
  • operator--(前置--与后置--)

有++就有--,与上面的++实现类似,就不过多赘述了。

//--D1
Date& operator--()
{
	*this -= 1;
	return *this;
}
//D1--
Date operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}
  • 存在的问题

到这里就基本完成了一个Date类的全部。但是还存在一个问题,那就是我们在初始化一个对象传参时,完全可能一开始初始的日期就是不合法的,因此,需要对一开始的日期是否合法进行处理。 

//检查日期是否合法,合法返真,否则返回假
bool CheckNonLegal()
{
	if (_year < 0 || _month >12 || _month < 1 || _day < 1 || _day > GetMonthDay())
	{
		return true;
	}

	return false;
}

 如果一开始时初始化日期就不合法,那么直接退出程序。因此,我们需要对构造函数再进一步处理:

 Date(int year = 0,int month = 1,int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
        if (CheckNonLegal())
        {
	        cout << "初始化日期不合法" << endl;
	        exit(-1);
        }
    }
    //初始化成员列表,对私有变量的初始化
    Date(int year = 0,int month = 1,int day = 1)
            :_year(year)
            ,_month(month)
            ,_day(day)
            {
                 if (CheckNonLegal())
                {
	                cout << "初始化日期不合法" << endl;
	                exit(-1);
                }
            }

  • 34
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想吐泡泡啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值