C++的学习之旅——使用类

目录

一、运算符重载

1、什么是运算符重载

2、运算符重载处理时分相加 

3、重载限制 

二、友元

1、什么是友元

2、友元函数

(1)创建友元

(2)常用的友元:重载<<运算符

 四、重载运算符:作为成员函数还是非成员函数

五、类的自动转换和强制类型转换

1、基本类型到类类型的转换

2、类类型到基本类型的转换

3、转换函数和友元函数


一、运算符重载

1、什么是运算符重载

运算符重载将重载概念扩展到运算符上。允许C++运算符多种含义。可以定义一个表示数组的类,并使用+重载运算符,将两个数组相加。

evening=sam+janet;

要使用这种重载运算符,需要使用被称为运算符函数的特殊函数形式,其格式如下:

operator(argument-list)

 operator+()将重载+运算符,operator*()将重载*运算符。这样子,当将两数组对象相加时,实际上调用operator函数。

evening=sam+janet;//等价于
evening=sam.operator+(janet);

2、运算符重载处理时分相加 

下面以一个例子来学习运算符重载

class Time
{
	private:
		int hours;
		int minutes;
	public:
		Time operator+(const Time & t) const;
		......
}

 定义了一个处理时分相加的类,其中operator+(const Time & t)使用了运算符重载。

Time Time::operator+(const Time & t) const
{
	Time sum;
	sum.minutes=minutes+t.minutes;
	sum.hours=hours+t.hours+sum.minutes;
	sum.minutes%=60;
	return sum;
}

 具体的函数定义如上,将传入的显式对象和隐式调用对象时分相加

Time a,b,c,d;
c=a+b;
d=a+b+c;

 这样就可以使用+运算符重载,处理时分相加,上面等价于

c=a.operator+(b);
d=a.operator+(b.operator+(c));

3、重载限制 

 运算符存在以下限制:

①重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。例如不可以将-运算符重载为计算两个double的和。

②使用运算符时不能违反运算符原来的句法规则,不能修改运算符的优先级

③不能创建新的运算符

不能重载以下运算符:

sizeof、.(成员运算符)、.*(成员指针运算符)、::(作用域解析运算符)、?:(条件运算符)、typeid(一个RTTI运算符)、const_cast(强制类型转换运算符)、dynamic_cast(强制类型转换运算符)、reinterpret_cast(强制类型转换运算符)、static_cast(强制类型转换运算符)。

⑤大多数运算符都可以通过成员或非成员函数进行重载,但下面运算符只能通过成员函数进行重载:

=(赋值运算符)、()(函数调用运算符)、[ ]、下标运算符、->(通过指针访问类运算符)

二、友元

1、什么是友元

在前面说到,类私有对象通常只能由公有类方法提供唯一访问途径,不过C++提供了另外一种形式的访问权限:友元,友元有3种:友元函数、友元类、友元成员函数。

下面介绍友元函数,其他两类后面再看。

2、友元函数

 使用前面运算符重载,使用下面语句可以将两个对象的时间相加。

Timer a,b,c;
c=a+b;
//等价于
c=a.operator+(b);

a=b*2.75;
//等价于
a=b.operator*(2.75)

使用如下语句时,由于2.75不是对象,没法调用上述函数

a=2.75*b;

 这时可以通过非成员函数来解决,由于非成员函数不是由对象调用的,所以使用的所有值均是显式的。以上语句可以编写一个如下的非成员函数原型。

Time operator*(double m,const Time & t);

 这样使得a=2.75*b等价于调用如下语句

a=operator*(2.75,b);

 但是由于非成员函数无法直接访问私有数据,使用上述函数,即使传入Time的对象,也无法访问相关数据。这时使用友元函数可以很好解决这问题。

(1)创建友元

创建友元函数第一步是将原型放在类声明中,并在原型前面加上关键字friend,这样意味着operator*()函数虽然在类声明中,但是不是成员函数,不能使用成员运算符来调用,不过具有访问私有数据的权限。

friend Time operator*(double n,const Time & t);

 第二步是编写函数定义,由于不是成员函数,不需要使用Timer::限定符,另外定义的时候不用加friend

friend Time operator*(double mult,const Time & t)
{
    Timer result;
    long totalminutes=t.hours*mult*60+t.minutes*mult;
    result.hours=totalminutes/60;
    result.minutes=totalminutes%60;
    return result;
}

有了上述定义后,再次使用如下语句将调用刚才定义的非成员友元函数

a=2.75*b;
//等价于
a=operator*(2.75,b);

(2)常用的友元:重载<<运算符

常用的输出语句cout<<...中的<<即使用了运算符重载,cout是一个ostream对象,而在ostream类中定义了对应每种基本类型的重载的operator<<()定义,使得cout可以识别所有基本类型并输出在终端。若要使cout能够识别Timer对象,则同样可通过运算符重载让cout知道如何输出Timer,不过由于不太适合修改iostream文件。我们可以将其定义放在Timer类声明中,而当operator<<()定义为Timer成员函数时,意味着需要使用如下语句,Timer对象才会是隐式。

Timer trip;
trip<<cout;
//等价于
trip.operator<<(cout)

 这样看着比较奇怪,我们还是习惯于cout在前面,这时则可以引入友元函数;

void operator<<(ostream & os,const Timer & t)
{
    os<<t.hour<<"hours, "<<t.minutes<<" minutes";
    return os
}

//定义完即可使用如下语句
Timer trip;
cout<<trip;
//由于返回的是os对象,则还可以进行如下语句:
//int x,y;
cout<<"x="<<x<<trip",y="<<y;

 最后的语句执行流程为:执行cout<<"x="返回cout对象,执行cout<<x返回cout对象,执行cout<<trip返回cout对象......

 四、重载运算符:作为成员函数还是非成员函数

对于很多运算符来说,可以使用成员函数和非成员函数实现运算符重载。而非成员函数一般使用友元函数,这可以访问对象的私有数据。如下两种运算符重载方式等价,不过一个使用成员函数,一个不是。

Timer operator+(const Timer & t) const;//成员函数
friend Timer operator+(conat Timer & t1,const Timer & t2);非成员友元函数

 对于某些运算符来说,只能使用成员函数重载,而其他情况下,两种格式没有很大区别。有时,根据类设计,使用非成员函数可能更好。

五、类的自动转换和强制类型转换

1、基本类型到类类型的转换

 当我们定义了一个类Stonewt,并包含3个私有数据,3个构造函数

class Stonewt
{
        int stone;
        double pds_left;
        double pounds;
        ...
    public
        Stonewt(double lbs);
        Stonewt(int stn,double lbs);
        Stonewt();
        ...
}

 则可以编写如下代码,由于19.6是浮点型,这将调用 Stonewt(double lbs)来创建一个临时Stonewt对象,并将19.6作为该构造函数参数进行初始化,之后将该临时对象的成员内容复制到myCat中。

Stonewt myCat;
myCat=19.6;

 这种特性只适用于一个参数的构造函数,而由于第二个构造函数有两个参数,不可以使用,若给第二个参数赋默认值,则可用于转换int型

Stonewt(int stn,double lbs=0);

 这种转换称为隐式转换,有时候这种自动转换会带来问题,C++11新增关键字explicit用于关闭这种特性,使用时将关键字放置于声明构造函数前即可。

explicit Stonewt(double lbs);

 这样使得该构造函数只能用于如下显式强制转换

myCat=Stonewt(19.6);
myCat=(Stonewt)19.6;

若为使用explicit,还可以用于如下隐式转换:

①将 Stonewt 对象初始化为 double 值时
②将double值赋给 Stonewt 对象时
③将double 值传递给接受 Stonewt 参数的函数时
④返回值被声明为Stonewt的函数试图返回double 值时
⑤在上述任意一种情况下,使用可转换为 double 类型的内置类型时。(如将int赋给Stonewt 对象时,程序会先将int转换为double,再使用构造函数)

2、类类型到基本类型的转换

前面将数字转换为Stonewt对象,当然,也可以将对象转换为数字。不过必须使用特殊的C++运算符函数——转换函数。转换函数是用户自定义的强制类型转换,可以像使用强制类型转换一样使用它们,例如,定义了从Stonewt到double的转换函数,则可以使用如下转换

Stonewt wolfe(285.7);
//显式强制类型转换
double host=double(wolfe);
double thinker=(double)wolfe;
//隐式强制类型转换
double star=wolfe;

 创建转换函数需要注意如下几点:

①转换函数必须是类方法

②转换函数不能指定返回类型

③转换函数不能有参数

创建转换函数需要使用如下格式:

operator typename();

若将Stonewt转换为int和double,只需要将如下函数声明以及函数定义放到类声明和类方法定义即可

//类声明
operator int()const;
operator double()const;
//类方法定义
Stonewt::operator int()const
{
	return int(pounds+0.5);//+0.5用于四舍五入,pounds在前面类声明过,为double类型
}
Stonewt::operator double()const
{
	return pounds;
}

这样,即可将对象赋值给int和double类型,不过还需要注意如下情况:

Stonewt poppins(9,2.8)
double p_wt=poppins;//可以编译,调用double转换函数

cout<<poppins<<endl;//出现错误,由于定义了两个转换函数,这里编译器不知道使用哪个,将指出二义性转换。若只定义double转换函数,则可以编译。

long=poppins;//出现错误,由于int和double都可以赋给long变量,仍会出现二义性转换

 若要应用于以上场景,需要使用强制类型转换。另外,C++98中,关键字explicit不能用于转换函数,但在C++11可以,使用explicit关键字可以使得转换函数只能被显式使用。 

3、转换函数和友元函数

当一个类中定义了 Stonewt(double lbs)构造函数和operator double()转换函数,以及一个用于加法的友元函数或成员函数,则会出现如下问题

//友元函数原型
friend operator+(Stonewt & n,Stonewt & t);

//成员函数原型
operator+(Stonewt & t);


Stonewt jennyst(9,12);
double pennyD=146.0;
Stonewt total;
total=penntD+jennyst;//①
total=jennyst+penntD;//②

①若只有Stonewt(double lbs),以及成员函数:语句①不会将penntD转换为临时Stonewt对象,再调用成员函数,会报错;语句②先将penntD转换为临时Stonewt对象,再将jennyst作为隐式对象调用成员函数。

②若只有Stonewt(double lbs),operator double()以及友元函数:语句①语句②会出错,编译器不知道是将jennyst转换为double类型进行单纯的加法运算还是将penntD转换为临时Stonewt对象调用友元函数。


C++的学习笔记持续更新中~

要是文章有帮助的话

就点赞收藏关注一下啦!

感谢大家的观看

欢迎大家提出问题并指正~

猜你喜欢

转载自blog.csdn.net/qq_47134270/article/details/128702748