C++类与对象(中)第二篇

目录

前言:

运算符重载

!=运算符重载

>运算符重载

>=运算符重载

<运算符重载

<=运算符重载

=运算符重载

+=运算符重载

 +运算符重载

-=运算符重载

-运算符重载

前置++运算符重载

后置++运算符重载

 前置--运算符重载

后置--运算符重载

-运算符重载

<<流插入运算符重载

>>流提取运算符重载

 运算符重载的注意事项:

const成员函数

const修饰普通变量

const修饰指针

const修饰函数形参


前言:

C语言中使用运算符是对内置类型的数据进行操作,但是在C++中有了对象,导致对象无法通过运算符进行运算,故引入了运算符重载即需要重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作;运算符重载就是对已有的运算符重新进行定义而且不能改变它本来的寓意,以达到适应不同的数据类型

运算符重载

class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	//构造函数
	Date(int year=2023, int month=12, int day=20)
	{
		_year = year;
		_month = month;
		_day = day;
	}
}
int main()
{
   Date d1(2023,12,20)
   Date d2(2000,10,20)
   
   return 0;
}

 定义了两个日期类对象d1,d2,如果想判断两个日期是否相同,需要比较各自的年、月、日,但是对象是自定义类型,运算符只支持内置类型的数据进行相关操作,此时我们需要重新定义运算符的规则,通过函数传参的形式可以对对象进行比较,但是函数名的命名可以千奇百怪,为规避命名不规则,增强代码的可读性,一律采用 operator运算符 同时也就实现自定义类型直接使用运算符

返回值类型  operator运算符(形参列表)
{
    ....
}
采取成员函数的声明与定义相分离的方式,即类声明存放于Date.h文件中,成员函数的定义存放于Date.cpp文件中, 运算符重载为成员函数 ;一个类重载那些运算符呢?取决于运算符对于该类有没有实际意义;
// Date.h文件
class Date
{
private:
	int _year;
	int _month;
	int _day;
public:
	//函数声明

    //d1==d2
	bool operator==(const Date& d);
    //显示对象信息
    void Print();
}
//Date.cpp文件

//显示对象信息
void Date::Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}
//d1==d2
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

!=运算符重载

对于类中的成员函数而言,第一个参数为隐藏的this指针;

重载双目运算符,只需要设置一个参数作为右操作数,而左操作数即为this指针指向的对象;

重载单目运算符,不必设置参数,操作数只有一个即为this指针指向的对象;

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

>运算符重载

bool operator>(const Date& x, const Date& y)编译错误:参数太多,==运算符只有两个操作数;

运算符重载函数作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this;

//d1>d2
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;
	}
	return false;
}

>=运算符重载

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

<运算符重载

//d1= d);
}

<=运算符重载

//d1<=d2 注: 小于等于的对立面为大于;
bool Date::operator<=(const Date& d)
{
	return !((*this) > d);
}

=运算符重载

赋值运算符的重载格式:

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

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

原因:赋值运算符如果不显式实现,编译器会自动生成一个默认的赋值运算符重载以值的方式逐字节拷贝;此时用户再在类外自己实现一个全局的赋值运算符重载与编译器在类中生成的默认赋值运算符重载冲突,故赋值运算符重载只能是类的成员函数

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

+=运算符重载

由于日期类对象的成员变量_month(月份)非固定,先获取每个月份的具体天数,然后实现日期+=函数的功能;

//获取每个月份的天数
int Date::GetMonthDay(int year, int month)
{
	assert(year >= 1 && (month <= 12 && month >= 1));
	int MonthArray[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;
	}
	return MonthArray[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)
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}

-=运算符重载

//日期-=天数
Date& Date::operator-=(int day)
{

	if (day < 0)
	{
		return *this += (-day);
	}
	_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;
}

前置++运算符重载

前置++:返回+1之后的结果;
注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率;
//++d1 前置++无参
Date& Date::operator++()
{
	*this = *this + 1;
	return *this;
}

后置++运算符重载

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,
C++规定 后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递;
注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1 ,而temp是临时对象,因此只能以值的方式返回,不能返回引用;
//d1++
Date Date::operator++(int x)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

 前置--运算符重载

//--d1
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

后置--运算符重载

//d1--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

-运算符重载

//d1-d2
int Date::operator-(const Date&d)
{
	//假设左大右小
	int flag = 1;
	Date max = *this;
	Date min = d;

	//假设错了,左小右大
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int count = 0;//计数
	while (min != max)
	{
		++min;
		++count;
	}
	return count * flag;
}

<<流插入运算符重载

流插入运算符为 “<<",与cout配合使用,其作用是支持内置类型的数值输出,若要使用cout,需要包含头文件#include

cout为ostream类的对象,cin为istream类的对象;

为了使自定义类型直接使用运算符,实现流插入运算符重载,<<流插入运算符是双目运算符,具有两个操作数,对于日期类,一个操作数为日期类对象,一个操作数为ostream类对象(cout) ,若在类中存放<<流插入运算符重载的声明,则形式如下:

void operator<<(ostream& out)//成员函数第一个参数为this指针,本质形式如下
//void operator<<(Date* const this,ostream& out)

当用户在主函数调用时cout<

原因:成员函数第一个参数为隐藏的this指针,将<<流插入运算符的左操作数传递给this指针,所以调用d1<

上述调用形式 d1<<<运算符重载为全局函数,但是涉及到成员变量私有问题,不能直接访问成员变量,采用友元函数解决此问题,将友元函数在类中声明;声明形式如下:

//Date.h
class Date
{
private:
       int _year;
       int _month;
       int _day;
public:
       friend 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
{
private:
       int _year;
       int _month;
       int _day;
public:
    //友元函数的声明
    friend istream& operator>>(istream& in, Date& d);
    //...
}
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;

	return in;
}

 运算符重载的注意事项:

  1. 不能通过连接其他符号(C++中不存在的运算符)来创建新的操作符:比如operator@;
  2. 重载操作符必须有一个类类型参数(因为运算符重载主要是为了让自定义类型能够像内置类型那样去使用运算符);
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义(因为运算符重载主要是为了让自定义类型能够像内置类型那样去使用运算符,内置类型不需要运算符重载);
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的this指针;
  5. .* :: sizeof ?: .  注意以上5个运算符不能重载

const成员函数

const修饰普通变量

const修饰普通变量的本质为定义了一个常量,形式如下:

Type const ValueName=value;
const Type valueName=value;

此时变量具有了常属性,变量里的数据只能被访问而且不能被修改,也就是意味着“只读”;

const修饰指针

const修饰指针变量本身,指针变量本身不允许被修改,即一旦得到了某个变量的地址,不能再指向其它变量;

int n = 20;
int* const p = &n; //const修饰指针变量本身

const修饰指针变量指向的内容,不可以通过指针修改其指向的变量的值,但是指针变量可以被修改;

int n = 10;
const int *p = &n;//const修饰指针变量指向的内容,即n=10;

const修饰函数形参

const修饰指针形参,形式如下:

void function(const Date* Var);

将外部实参的地址赋值给用const修饰的指针形参,用户无法通过指针修改其所指的外部实参,保护了数据的安全性;

void function(Date* const Var);

表示指针形参不会改变,用户依然可以通过指针修改传过来的外部实参,无法保证外部数据的安全性,const指针形参毫无意义;

 const修饰引用形参,形式如下:

void function(const Date& Var); //引用参数在函数内不可以改变

参数为引用,将外部实参传递给引用形参,传递的是外部实参本身,无需进行拷贝,增加了效率,同时参数是const引用,无法通过引用修改实参,保证了外部数据的安全性;

将const修饰的“成员函数”称之为const成员函数 const修饰类成员函数,实际修饰该成员函数 隐含的this指针 ,表明在该成员函数中 不能对类的任何成员进行修改;
C++类与对象(中)第二篇_第1张图片

注意:

  1. const对象不可以调用非const成员函数,因为const对象具有常属性,非const成员函数形参的隐藏this指针是没有常属性的,会导致权限放大的问题;
  2. 非const对象可以调用const成员函数,因为权限可以被缩小,但是不能被放大;
  3. const成员函数内不可以调用其它的非const成员函数,因为const成员函数的this指针是const修饰的,在调用其他非cosnst成员函数的时候,还是使用的这个指针,但是非const成员函数的this指针没有被const修饰,所以也会导致权限放大的问题;
  4. 非const成员函数内可以调用其它的const成员函数,同样的道理,权限可以被缩小,但是不能被放大;

你可能感兴趣的:(c++,算法,开发语言)