C++基础教程面向对象(学习笔记(19))

使用友元函数重载算术运算符

C ++中一些最常用的运算符是算术运算符 - 即加号运算符(+),减运算符( - ),乘法运算符(*)和除法运算符(/)。请注意,所有算术运算符都是二元运算符 - 这意味着它们需要两个操作数 - 运算符的每一侧都有一个操作数。所有这四个运算符都以完全相同的方式过载。

事实证明,有三种不同的方法来重载运算符:成员函数方式,友元函数方式和正常函数方式。在本课中,我们将介绍友元函数的方式(因为它对大多数二元运算符更直观)。下一课,我们将讨论正常的函数方式。最后,在本章后面的课程中,我们将介绍成员函数的方式。当然,我们还将总结何时更详细地使用每一个。

使用友元函数重载运算符

考虑以下的类:

class Cents
{
private:
	int m_cents;
 
public:
	Cents(int cents) { m_cents = cents; }
	int getCents() const { return m_cents; }
};

以下示例显示如何重载operator +(+)以便将两个“Cents”对象一起添加:

#include <iostream>
 
class Cents
{
private:
	int m_cents;
 
public:
	Cents(int cents) { m_cents = cents; }
 
	// 用友元函数添加Cents + Cents
	friend Cents operator+(const Cents &c1, const Cents &c2);
 
	int getCents() const { return m_cents; }
};
 
// 注意: 这个函数不是成员函数
Cents operator+(const Cents &c1, const Cents &c2)
{
	//用Cents构造函数和operator +(int,int)
	// 我们可以直接访问m_cents因为这是一个友元函数
	return Cents(c1.m_cents + c2.m_cents);
}
 
int main()
{
	Cents cents1(6);
	Cents cents2(8);
	Cents centsSum = cents1 + cents2;
	std::cout << "I have " << centsSum.getCents() << " cents." << std::endl;
 
	return 0;
}

这会产生结果:
I have14cents。
重载加号运算符(+)就像声明一个名为operator +的函数一样简单,为它提供两个我们想要添加的操作数类型的参数,选择一个合适的返回类型,然后编写该函数。

对于我们的Cents对象,实现我们的operator +()函数非常简单。首先,参数类型:在这个版本的operator +中,我们将两个Cents对象一起添加,因此我们的函数将采用两个Cents类型的对象。第二,返回类型:我们的运算符+将返回类型为Cents的结果,因此这是我们的返回类型。

最后,实现:要将两个Cents对象一起添加,我们确实需要从每个Cents对象添加m_cents成员。因为我们重载的operator +()函数是类的友元函数,所以我们可以直接访问参数的m_cents成员。此外,因为m_cents是一个整数,并且C ++知道如何使用与整数操作数一起使用的plus运算符的内置版本将整数添加到一起,所以我们可以简单地使用+运算符来进行添加。

重载减法运算符( - )也很简单:

#include <iostream>
 
class Cents
{
private:
	int m_cents;
 
public:
	Cents(int cents) { m_cents = cents; }
 
	// 用友元函数添加Cents + Cents
	friend Cents operator+(const Cents &c1, const Cents &c2);
 
	// 用友元函数实现减法 Cents - Cents
	friend Cents operator-(const Cents &c1, const Cents &c2);
 
	int getCents() const { return m_cents; }
};
 
// 注意: 这个函数不是成员函数
Cents operator+(const Cents &c1, const Cents &c2)
{
	//用Cents构造函数和operator +(int,int)
	// 我们可以直接访问m_cents因为这是一个友元函数
	return Cents(c1.m_cents + c2.m_cents);
}
 
// 注意: 这个函数不是成员函数
Cents operator-(const Cents &c1, const Cents &c2)
{
	//用Cents构造函数和operator-(int,int)
	// 我们可以直接访问m_cents因为这是一个友元函数
	return Cents(c1.m_cents - c2.m_cents);
}
 
int main()
{
	Cents cents1(6);
	Cents cents2(2);
	Cents centsSum = cents1 - cents2;
	std::cout << "I have " << centsSum.getCents() << " cents." << std::endl;
 
	return 0;
}

重载乘法运算符(*)和除法运算符(/)就像定义operator *和operator /的函数一样简单。

友元函数可以在类中定义

即使友元函数不是类的成员,如果需要,它们仍然可以在类中定义:

#include <iostream>
 
class Cents
{
private:
	int m_cents;
 
public:
	Cents(int cents) { m_cents = cents; }
 
	// 用友元函数添加Cents + Cents
        // 即使定义在类中,该函数也不被视为类的成员
	friend Cents operator+(const Cents &c1, const Cents &c2)
	{
	//用Cents构造函数和operator +(int,int)
	// 我们可以直接访问m_cents因为这是一个友元函数
		return Cents(c1.m_cents + c2.m_cents);
	}
 
	int getCents() const { return m_cents; }
};
 
int main()
{
	Cents cents1(6);
	Cents cents2(8);
	Cents centsSum = cents1 + cents2;
	std::cout << "I have " << centsSum.getCents() << " cents." << std::endl;
 
	return 0;
}

我们通常不建议这样做,因为非一般的函数定义最好保存在类定义之外的单独的.cpp文件中。但是,我们将在以后的教程中使用此模式来保持示例简洁。

为不同类型的操作数重载运算符

通常情况下,您希望重载的运算符使用不同类型的操作数。例如,如果我们有Cents(4),我们可能想要将整数6加到此处以产生结果Cents(10)。

当C ++计算表达式时x + y,x成为第一个参数,y成为第二个参数。当x和y具有相同的类型时,如果添加x + y或y + x无关紧要 - 无论哪种方式,都会调用相同版本的operator +。但是,当操作数具有不同的类型时,x + y不会调用与y + x相同的函数。

例如,Cents(4) + 6将调用operator +(Cents,int),并6 + Cents(4)调用operator +(int,Cents)。因此,每当我们为不同类型的操作数重载二元运算符时,我们实际上需要编写两个函数 - 每种情况一个。这是一个例子:

#include <iostream>
 
class Cents
{
private:
	int m_cents;
 
public:
	Cents(int cents) { m_cents = cents; }
 
	// 用友元函数添加Cents + int
	friend Cents operator+(const Cents &c1, int value);
 
	// 用友元函数添加 int + Cents
	friend Cents operator+(int value, const Cents &c1);
 
 
	int getCents() { return m_cents; }
};
 
//注意: 这个函数不是成员函数
Cents operator+(const Cents &c1, int value)
{
	// 用友元函数添加Cents + Cents
   // 即使定义在类中,该函数也不被视为类的成员
	return Cents(c1.m_cents + value);
}
 
// 注意: 这个函数不是成员函数
Cents operator+(int value, const Cents &c1)
{
	// 用友元函数添加Cents + Cents
    // 即使定义在类中,该函数也不被视为类的成员
	return Cents(c1.m_cents + value);
}
 
int main()
{
	Cents c1 = Cents(4) + 6;
	Cents c2 = 6 + Cents(4);
 
	std::cout << "I have " << c1.getCents() << " cents." << std::endl;
	std::cout << "I have " << c2.getCents() << " cents." << std::endl;
 
	return 0;
}

请注意,两个重载函数都具有相同的实现 - 这是因为它们执行相同的操作,它们只是以不同的顺序获取它们的参数。

另一个例子

我们来看看另一个例子:

class MinMax
{
private:
	int m_min; // 存放最小值
	int m_max; // 存放最大值
 
public:
	MinMax(int min, int max)
	{
		m_min = min;
		m_max = max;
	}
 
	int getMin() { return m_min; }
	int getMax() { return m_max; }
 
	friend MinMax operator+(const MinMax &m1, const MinMax &m2);
	friend MinMax operator+(const MinMax &m, int value);
	friend MinMax operator+(int value, const MinMax &m);
};
 
MinMax operator+(const MinMax &m1, const MinMax &m2)
{
	// 在m1和m2中获取最小值
	int min = m1.m_min < m2.m_min ? m1.m_min : m2.m_min;
 
	// 在m1和m2中获取最大值
	int max = m1.m_max > m2.m_max ? m1.m_max : m2.m_max;
 
	return MinMax(min, max);
}
 
MinMax operator+(const MinMax &m, int value)
{
	// 在m1和value中获取最小值 
	int min = m.m_min < value ? m.m_min : value;
 
	// 在m1和value中获取最大值 
	int max = m.m_max > value ? m.m_max : value;
 
	return MinMax(min, max);
}
 
MinMax operator+(int value, const MinMax &m)
{
	// 调用operator+(MinMax, int)
	return m + value;
}
 
int main()
{
	MinMax m1(10, 15);
	MinMax m2(8, 11);
	MinMax m3(3, 12);
 
	MinMax mFinal = m1 + m2 + 5 + 8 + m3 + 16;
 
	std::cout << "Result: (" << mFinal.getMin() << ", " <<
		mFinal.getMax() << ")\n";
 
	return 0;
}

MinMax类跟踪它到目前为止所见的最小值和最大值。我们已经重载了+运算符3次,因此我们可以将两个MinMax对象一起添加,或者将整数添加到MinMax对象。

这个例子产生了结果:

Result:(3,16)
您将注意到我们添加到mFinal的最小值和最大值。

让我们再谈谈“MinMax mFinal = m1 + m2 + 5 + 8 + m3 + 16”的调用方式。请记住,operator +的优先级高于operator =,operator +从左到右进行求值,因此m1 + m2首先求值。这成为对运算符+(m1,m2)的调用,它产生返回值MinMax(8,15)。然后MinMax(8,15)+ 5接下来调用。这成为对operator +(MinMax(8,15),5)的调用,它产生返回值MinMax(5,15)。然后MinMax(5,15)+ 8以相同的方式调用以产生MinMax(5,15)。然后MinMax(5,15)+ m3调用产生MinMax(3,15)。最后,MinMax(3,15)+ 16调用为MinMax(3,16)。然后将最终结果分配给mFinal。

换句话说,此表达式的计算结果为“MinMax mFinal =((((((m1 + m2)+ 5)+ 8)+ m3)+ 16)”,对于之后的对象,每次连续操作返回一个成为左操作数的MinMax对象。

使用其他运算符实现运算符

在上面的例子中,请注意我们通过调用operator +(MinMax,int)来定义operator +(int,MinMax)(它产生相同的结果)。这允许我们将operator +(MinMax,int)的实现减少到一行,通过最小化冗余并使函数更易于理解,使我们的代码更易于维护。

通常可以通过调用其他重载运算符来定义重载运算符。如果这样做会产生更简单的代码,您应该这样做。在实现很简单的情况下(例如,单行),通常不值得这样做,因为附加函数调用比直接实现函数更复杂。

Quiz Time:

1a)编写一个名为Fraction的类,它具有整数分子和分母成员。编写print()函数打印出分数。

以下代码应该编译:

#include <iostream>
 
int main()
{
    Fraction f1(1, 4);
    f1.print();
 
    Fraction f2(1, 2);
    f2.print();
}

这应该打印:

1/4
1/2
解决方案:

#include <iostream>
 
class Fraction
{
private:
	int m_numerator = 0;
	int m_denominator = 1;
 
public:
	Fraction(int numerator=0, int denominator=1):
		m_numerator(numerator), m_denominator(denominator)
	{
	}
 
	void print()
	{
		std::cout << m_numerator << "/" << m_denominator << "\n";
	}
};
 
int main()
{
	Fraction f1(1, 4);
	f1.print();
	
	Fraction f2(1, 2);
	f2.print();
 
	return 0;
}

1b)添加重载乘法运算符以处理分数和整数之间以及两个分数之间的乘法。使用友元函数方法。

提示:要将两个分数相乘,首先将两个分子相乘,然后将两个分母相乘。要将分数和整数相乘,请将分数的分子乘以整数,并使分母单独使用。

以下代码应该编译:

#include <iostream>
 
int main()
{
    Fraction f1(2, 5);
    f1.print();
 
    Fraction f2(3, 8);
    f2.print();
 
    Fraction f3 = f1 * f2;
    f3.print();
 
    Fraction f4 = f1 * 2;
    f4.print();
 
    Fraction f5 = 2 * f2;
    f5.print();
 
    Fraction f6 = Fraction(1, 2) * Fraction(2, 3) * Fraction(3, 4);
    f6.print();
}

这应该打印:

2/5
3/8
6/40
4/5
6/8
6/24
解决方案

1c)额外信用:分数2/4与1/2相同,但2/4不减少到最低项。我们可以通过找到分子和分母之间的最大公约数(GCD),然后将分子和分母除以GCD,将任何给定分数减少到最低项。

以下是查找GCD的功能:

int gcd(int a, int b)
 {
    return (b == 0) ? (a > 0 ? a : -a) : gcd(b, a % b);
}

将此函数添加到您的类中,并编写一个名为reduce()的成员函数来减少您的分数。确保所有馏分都适当减少。

以下应编译:

#include <iostream>
 
int main()
{
    Fraction f1(2, 5);
    f1.print();
 
    Fraction f2(3, 8);
    f2.print();
 
    Fraction f3 = f1 * f2;
    f3.print();
 
    Fraction f4 = f1 * 2;
    f4.print();
 
    Fraction f5 = 2 * f2;
    f5.print();
 
    Fraction f6 = Fraction(1, 2) * Fraction(2, 3) * Fraction(3, 4);
    f6.print();
 
    return 0;
}

并产生结果:

2/5
3/8
3/20
4/5
3/4
1/4
解决方案

#include <iostream>
 
class Fraction
{
private:
	int m_numerator;
	int m_denominator;
 
public:
	Fraction(int numerator=0, int denominator=1):
		m_numerator(numerator), m_denominator(denominator)
	{
		// 我们在构造函数中放置了reduce()以确保我们不会得到假分数
		// 由于所有重载运算符都会创建新的Fractions,因此我们可以保证在此处调用它
		reduce();
	}
 
        // 我们将使gcd静态,以便它可以成为类Fraction的一部分,而不需要使用类型为Fraction的对象
	static int gcd(int a, int b)
	{
		return (b == 0) ? (a > 0 ? a : -a) : gcd(b, a % b);
	}
 
	void reduce()
	{
		int gcd = Fraction::gcd(m_numerator, m_denominator);
		m_numerator /= gcd;
		m_denominator /= gcd;
	}
 
	friend Fraction operator*(const Fraction &f1, const Fraction &f2);
	friend Fraction operator*(const Fraction &f1, int value);
	friend Fraction operator*(int value, const Fraction &f1);
 
	void print()
	{
		std::cout << m_numerator << "/" << m_denominator << "\n";
	}
};
 
Fraction operator*(const Fraction &f1, const Fraction &f2)
{
	return Fraction(f1.m_numerator * f2.m_numerator, f1.m_denominator * f2.m_denominator);
}
 
Fraction operator*(const Fraction &f1, int value)
{
	return Fraction(f1.m_numerator * value, f1.m_denominator);
}
 
Fraction operator*(int value, const Fraction &f1)
{
	return Fraction(f1.m_numerator * value, f1.m_denominator);
}
 
int main()
{
	Fraction f1(2, 5);
	f1.print();
 
	Fraction f2(3, 8);
	f2.print();
 
	Fraction f3 = f1 * f2;
	f3.print();
 
	Fraction f4 = f1 * 2;
	f4.print();
 
	Fraction f5 = 2 * f2;
	f5.print();
 
	Fraction f6 = Fraction(1, 2) * Fraction(2, 3) * Fraction(3, 4);
	f6.print();
 
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41879485/article/details/83059983