C++基础学习之使用类(8)

本文介绍了C++中的运算符重载,包括其定义、示例和限制,并讨论了友元的概念,如友元函数、友元类和友元成员函数,以及它们在重载运算符中的应用。同时,文章还探讨了类的自动转换和强制类型转换,特别是转换函数的使用及其注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

运算符重载

C++允许将运算符重载扩展到用户定义的类型。定义方法类似下面的例子:
operator+()重载+运算符,operator*()重载*运算符。下面讲一个运算符重载的示例:
定义一个类:

// mytime0.h -- Time class before operator overloading
#ifndef MYTIME0_H_
#define MYTIME0_H_

class Time
{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0, int m = 0);
	Time Sum(const Time & t) const;
	//或者使用下面的运算符重载函数
	Time operator+(const Time & t) const;
	void Show() const;
}

如果不定义一个运算符重载函数,那么就需要上面的那个Sum()函数,Sum函数实现如下:

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

如果定义运算符重载函数,则其实现如下:

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

这样就可以使用+运算符直接对对象进行操作,例如:

total = coding + fixing;

这里需要注意的是,在运算符表示法中,运算符左侧的对象(这里是coding)是调用对象,运算符右侧的对象(这里为fixing)是作为参数被传递的对象。而且可以连加。

重载的限制

  1. 重载后的运算符必须至少有一个操作数是用户定义的类型(防止用户为标准类型重载运算符)。
  2. 使用运算符时不能违反原来的句法规则。例如不能将求模运算符(%)重载成使用一个操作数。同样,也不能修改运算符的优先级。
  3. 不能创建新的运算符。例如,不能定义operator**()函数来表示求幂。
  4. 不能重载下面的运算符:
  • sizeof运算符
  • ‘.’ 成员运算符
  • ‘.*’ 成员指针运算符
  • ‘::’ 作用域解析运算符
  • ‘?:’ 条件运算符
  • typeid:一个RTTI运算符
  • const_cast :强制类型转换运算符
  • dynamic_cast:强制类型转换运算符
  • reinterpret_cast:强制类型转换运算符
  • static_cast:强制类型转换运算符
  1. 只能通过成员函数重载的运算符:
  • =: 赋值运算符
  • (): 函数调用运算符
  • []: 下标运算符
  • ->: 通过指针访问类成员的运算符

成员函数和非成员函数均可重载运算符:

+-*/%^
&|~=!=<
>+=-=*=/=%=
^=&=|=<<>>>>=
<<===!=<=>=&&
||++,->*->
()[]newdeletenew[]delete[]

友元

C++控制类对象私有部分的访问。通常,公有类方法提供唯一的访问途径,但是有时候这种限制太严格,以至于不适合特定的编程问题。这种情况下,C++提供了另外一种形式的访问权限:友元。友元有三种:

  • 友元函数;
  • 友元类;
  • 友元成员函数。

通过让函数成为类的友元,可以赋予该函数域类的成员函数相同的访问权限。
成员函数重载运算符的时候,只需要传递右操作数,这样产生了一些限制。非成员函数重载运算符时,可以将符号两端的操作数作为第一个和第二个参数传入函数中。所以需要友元定义一个非成员函数,其目的是让非成员函数也可以访问类的私有数据。

创建友元

创建友元函数的第一步是将其原型放在类声明中,并在原型前面加上关键字friend:

friend Time operator*(double m, const Time & t);	// goes in class declaration

该原型意味着下面两点:

  • 虽然operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用。
  • 虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同。

第二步是编写定义。因为它不是成员函数,所以不需要使用Time::限定符。另外,也不需要在定义中使用关键字friend。定义应该如下:

Time operator*(double mult, const Time & t)	// friend not used int definition
{
	Time result;
	long totalminutes = t.hours * mult * 60 + t.minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}
常用的友元:重载<<运算符

重载<<运算符可以使自己定义的类直接使用<<运算符进行打印输出。重载形式如下:

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

这样就可以直接如下使用:

cout << "Trip time: " << trip << " (Tuesday)\n";

而且这个版本的定义还可以用于将输出写入文件中:

#include <fstream>
...
ofstream fout;
fout.open("savetime.txt");
Time trip(12, 40);
fout << trip;

最后一条语句将被转换成这样:

operator<<(fout, trip);

由于类继承的属性让ostream引用能够指向ostream对象和ofstream对象。(ofsream是派生类,ostream是基类,基类引用可以指向派生类对象)
下面是一个Time类的完整声明:

#ifndef MYTIME_H_
#define MYTIME_H_
#include <iostream>
class Time
{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0, int m = 0);
	Time operator+(const Time & t) const;
	Time operator-(const Time & t) const;
	Time operator*(double n) const;
	friend Time operator*(double n, const Time & t)
	{ return t * m; }
	friend std::ostream & operator<<(std::ostream & os, const Time & t);
}

最后一个函数的定义:

std::ostream & operator<<(std::ostream & os, const Time & t)
{
	os << t.hours << " hours, " << t.minutes << " minutes";
	return os;
}

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

标准类型变量都是可以进行自动的类型转换的,我们希望类也可以与某些基本类型或者其它的类进行类型转换,一般来说是用构造函数进行类型转换的。比如有一个类Stonewt,其构造函数是Stonewt(double lbs),那么就可以利用这个构造函数将double类型的数转换为Stonewt类型,而且C++中可以隐式的使用这个方法,也就是说,可以这么写代码:

Stonewt myCat;	// creat a Stonewt object
myCat = 19.6;	//use Stonewt(double) to convert 19.6 to Stonewt

程序将使用构造函数Stonewt(double)来创建一个临时的Stonwt对象,并将19.6作为初始化值。随后,采用逐成员赋值方式将该临时对象的内容复制到myCat中。这一过程称为隐式转换。
只有接受一个参数的构造函数才能作为转换函数。然而如果给第二个参数提供默认值,它就也可以作为转化函数。
这种自动类型转换特性有时会带来问题,所以是可以关闭的,使用explicit关键字在声明构造函数前可以关闭这个特性:

explicit Stonewt(double lbs);	// no implicit conversions allowed

但是还是可以显式转换:

Stonewt myCat;	// creat a Stonewt object
myCat = 19.6;	// not valid if Stonewt(double) is declared as explicit
myCat = Stonewt(19.6);	// ok, an explicit conversion
myCat = (Stonewt) 19.6;	// ok, old form for explicit typecast

注意: 只接受一个参数的构造函数定义了从参数类型到类类型的转换。如果使用关键字explicit限定了这种构造函数,则它只能用于显示转换,否则也可以用于隐式转换。

隐式转换发生的一些情况:

  • 将Stonewt对象初始化为double值时。
  • 将double值赋给Stonewt对象时。
  • 将double值传递给接受Stonewt参数的函数时。
  • 返回值被声明为Stonewt的函数试图返回double值时。
  • 在上述任意一种情况下,使用可转换为double类型的内置类型时。

最后一句话很重要,就是说传入的值不是double,但是可以转换为double,那么就会先转换为double然后再转换为Stonewt类。然而,当且仅当转换不存在二义性时,才会进行这种二步转换。也就是说,如果这个类还定义了函数Stonewt(long),则编译器将拒绝这些语句,可能指出:int可被转换为long或者double,因此调用存在二义性。

转换函数

如果想做相反的变换必须使用特殊的C++运算符函数——转换函数。转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。例如,如果定义了从Stonewt到double的转换函数,可以使用下面的转换:

Stonewt wolfe(285.7);
double host = double(wolfe);	// syntax #1
double thinker = (double)wolfe;	//syntax #2

也可以让编译器来决定如何做:

Stonewt wells(20, 3);
double star = wells;	// implicit use of conversion function

编译器发现,右侧是Stonewt类型,而左侧是double类型,因此它将查看程序员是否定义了与此匹配的转换函数。(如果没有找到这样的定义,编译器将生成错误消息,指出无法将Stonewt赋给double。)

创建转换函数:要转换为typeName类型,需要使用这种形式的转换函数:
operator typeName();
注意:

  • 转换函数必须是类方法;
  • 转换函数不能指定返回类型;
  • 转换函数不能有参数。
    例如,转换为double类型的函数的原型如下:
    operator double();

示例如下:

class Stone
{
...
public:
...
// conversion functions
	operator int() const;
	operator double() const;
}
//conversion functions
Stonewt::operator int() const
{
	return int(pounds + 0.5);
}
Stonewt::operator double() const
{
	return pounds;
}

如果担心隐式转换出问题,那么也可以声明成显式的:

class Stone
{
...
public:
...
// conversion functions
	explicit operator int() const;
	explicit operator double() const;
}

这样就必须显式的调用转换函数了,应该谨慎的使用隐式转换函数,通常最好选择仅在被显式地调用时才会执行的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值