C++类和对象详解(2)

在上一篇中我们讨论了什么是面向对象编程,类的定义,作用域,实例化,访问限定符,大小计算,以及一个很重要的知识this指针。
https://blog.csdn.net/hellooworldd/article/details/122641166?spm=1001.2014.3001.5501
下面介绍进一步的内容。

类的6个默认成员函数

c++类中如果一个成员没有,那么这个类叫空类,但是这个类虽然我们在代码中什么东西都看不见,但是编译器会给我们生成六个默认成员函数: 1构造函数,2析构函数,3拷贝构造函数,4赋值重载函数,56还有两个取地址重载函数。这六个函数我们主要看前四个函数。后两个函数我们自己很少实现,这里并不提及。

这里我们以日期类举例来实现前四个默认成员函数,其他的类都可以迁移。

#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month);
	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);
	// 拷贝构造函数
  // d2(d1)
	Date(const Date& d);
	// 赋值运算符重载
  // d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d);
	// 析构函数
	~Date();
	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day);
	// 日期-天数
	Date operator-(int day);
	// 日期-=天数
	Date& operator-=(int day);
	// 前置++
	Date& operator++();
	// 后置++
	Date operator++(int);
	// 后置--
	Date operator--(int);
	// 前置--
	Date& operator--();
	// >运算符重载
	bool operator>(const Date& d);
	// ==运算符重载
	bool operator==(const Date& d);
	// >=运算符重载
	inline bool operator >= (const Date& d);
	// <运算符重载
	bool operator < (const Date& d);
	// <=运算符重载
	bool operator <= (const Date& d);
	// !=运算符重载
	bool operator != (const Date& d);
	// 日期-日期 返回天数
	int operator-(const Date& d);
private:
	int _year;
	int _month;
	int _day;
};

以上是我们需要实现的所有函数,此类主要涉及年月日的计算比较等。一开始看不懂没关系,下面我们一个一个举例。

构造函数

在用类创建对象时,我们创建对象后对象的个类信息并没有被设置,虽然我们可以使用一些成员函数对其进行设置,但是比较麻烦,所以我们就有了构造函数,在创建对象时对对象初始化。

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

构造函数有以下特征:

  1. 函数名与类名相同。

  2. 无返回值。

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

  4. 构造函数可以重载。

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

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

  7. 当我们没写构造函数时,编译器会生成默认构造函数,他对内置类型不做处理(像char int double等),对自定义类型会调用它的构造函数(如class struct等类)。

所以日期类的构造函数可以这样写

Date::Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
	if (!(_year >= 0 && (_month > 0 && _month < 13) && (_day > 0 && _day <= GetMonthDay(_year, _month))))
	{
		cout << "非法输入" << endl;
		cout << _year << "-" << _month << "-" << _day << endl;
	}
}

析构函数

既然我们在创建对象时已经可以自动初始化对象了,那么我们是不是可以在对象不使用或者或出了作用域后自动销毁呢?这就用到了我们接下来讲的析构函数,此函数会在销毁时自动调用,完成类的资源清理工作。

其特征如下:

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

  2. 无参数无返回值。

  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

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

  5. 编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

日期类中成员变量均会随栈帧销毁而销毁所以可以不调用析构函数,只需要用默认生成的析构函数就可以了,但是我们也可以写出来。

Date::~Date()
{
	cout << "~Date()" << endl;
}

在有需要释放空间的类中可以使用如free或者delete等在析构函数中自动销毁。

拷贝构造函数

当我们在创建一个对象时,想要创建一个和一个已经创造的对象一模一样的对象时,我们可以用拷贝构造函数。

拷贝构造函数有以下特征:

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

  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

  3. 若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝

  4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,所以日期类其实我们可以不用写拷贝构造。

注:篇幅受限本文并不涉及深拷贝知识,想要了解请自行学习或在本博客以后的文章中搜索。

我们可以用日期类来演示以下拷贝构造:

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

运算符重载

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

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

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

要注意以下几点

​ 不能通过连接其他符号来创建新的操作符:比如operator@

​ 重载操作符必须有一个类类型或者枚举类型的操作数

​ 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其 含义

​ 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的

​ 操作符有一个默认的形参this,限定为第一个形参

​ .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。

下面给出日期类的代码:

Date& Date:: operator+=(int day)
{
	if (day < 0)
	{
		*this -= (-day);
		return *this;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_month == 1;
			_year++;
		}
	}
	return *this;
}
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		*this += (-day);
		return *this;
	}
	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}
Date Date::operator-(int day)
{
	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--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}
// 前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}
// >运算符重载
bool Date::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;
	}
	else
	{
		return false;
	}
}
// ==运算符重载
bool Date::operator==(const Date& d)
{
	return ((_year == d._year) && (_month == d._month) && (_day == d._day));
}
// >=运算符重载
inline bool Date::operator >= (const Date& d)
{
	return *this > d || *this == d;
}
// <运算符重载
bool Date::operator < (const Date& d)
{
	return !(*this >= d);
}
// <=运算符重载
bool Date::operator <= (const Date& d)
{
	return !(*this > d);
}
// !=运算符重载
bool Date::operator != (const Date& d)
{
	return !(*this == d);
}
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int count = 0;
	while (max != min)
	{
		min++;
		count++;
	}
	return count * flag;
}

这里需要注意的是由于前置++和后置++都只有一个隐含的this指针为参数,所以为了区别,将后置++的参数列表添加一个int占位符用来区分,这个地方编译器会自动识别,只需要记住格式就可以。当然–运算符也类似。

赋值运算符重载

赋值运算符重载需要考虑几个因素

  1. 参数类型

  2. 返回值

  3. 检测是否自己给自己赋值

  4. 返回*this

  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

以下为详细代码

Date&Date:: operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

简单提一下取地址及const取地址操作符重载

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

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

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

补充知识

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。当这个成员函数你并不想对成员变量进行更改时可以再函数名后加const有利于检测出是否你误用修改了成员变量。

如比较大小的运算符重载函数

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;
	}
	else
	{
		return false;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值