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

重载I / O运算符

对于具有多个成员变量的类,在屏幕上打印每个变量可能会非常烦人。例如,请考虑以下类:

class Point
{
private:
    double m_x, m_y, m_z;
 
public:
    Point(double x=0.0, double y=0.0, double z=0.0): m_x(x), m_y(y), m_z(z)
    {
    }
 
    double getX() { return m_x; }
    double getY() { return m_y; }
    double getZ() { return m_z; }
};

如果你想在屏幕上打印这个类的实例,你必须做这样的事情:

Point point(5.0, 6.0, 7.0);
 
std::cout << "Point(" << point.getX() << ", " <<
    point.getY() << ", " <<
    point.getZ() << ")";

当然,将此作为可重用的函数更有意义。在前面的示例中,您已经看到我们创建了如下所示的print()函数:

class Point
{
private:
    double m_x, m_y, m_z;
 
public:
    Point(double x=0.0, double y=0.0, double z=0.0): m_x(x), m_y(y), m_z(z)
    {
    }
 
    double getX() { return m_x; }
    double getY() { return m_y; }
    double getZ() { return m_z; }
 
    void print()
    {
        std::cout << "Point(" << m_x << ", " << m_y << ", " << m_z << ")";
    }
};

虽然这要好得多,但仍有一些缺点。因为print()返回void,所以无法在输出语句的中间调用它。相反,你必须这样做:

int main()
{
    Point point(5.0, 6.0, 7.0);
 
    std::cout << "My point is: ";
    point.print();
    std::cout << " in Cartesian space.\n";
}

如果您只需输入以下内容就会容易得多:

Point point(5.0, 6.0, 7.0);
cout << "My point is: " << point << " in Cartesian space.\n";

并得到相同的结果。在多个语句中不会有分解输出,也不必记住您命名为print函数的内容。

幸运的是,通过重载<<运算符,你可以!

重载运算符<<

重载operator <<类似于重载operator +(它们都是二元运算符),但参数类型不同。

考虑一下表达式std::cout << point。如果运算符是<<,那么操作数是什少?左操作数是std :: cout对象,右操作数是Point类对象。std :: cout实际上是std :: ostream类型的对象。因此,我们的重载函数将如下所示:

// std::ostream是对象std :: cout的类
friend std::ostream& operator<< (std::ostream &out, const Point &point);

我们的Point类实现operator <<非常简单 - 因为C ++已经知道如何使用operator <<输出双精度数,而我们的成员都是双精度数,我们可以简单地使用operator <<来输出Point的成员变量。以下是带有重载运算符<<的上述Point类。

#include <iostream>
 
class Point
{
private:
    double m_x, m_y, m_z;
 
public:
    Point(double x=0.0, double y=0.0, double z=0.0): m_x(x), m_y(y), m_z(z)
    {
    }
 
    friend std::ostream& operator<< (std::ostream &out, const Point &point);
};
 
std::ostream& operator<< (std::ostream &out, const Point &point)
{
    // 由于operator <<是Point类的友元函数,所以我们可以直接访问Point的成员。
    out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ")"; // actual output done here
 
    return out; // 返回std :: ostream以至于我们可以链接调用operator <<
}
 
int main()
{
    Point point1(2.0, 3.0, 4.0);
 
    std::cout << point1;
 
    return 0;
}

这非常简单 - 注意我们的输出行与我们之前编写的print()函数中的行有多相似。最显着的区别是std :: cout已成为参数输出(在调用函数时将引用std :: cout)。

这里最棘手的部分是返回类型。使用算术运算符,我们计算并按值返回单个答案(因为我们创建并返回新结果)。但是,如果您尝试按值返回std :: ostream,则会出现编译器错误。发生这种情况是因为std :: ostream特别禁止被复制。

在这种情况下,我们返回左边参数作为参考。这不仅可以防止生成std :: ostream的副本,还可以让我们将输出命令“链接”在一起,例如std::cout << point << std::endl;

您可能最初认为,因为operator <<不向调用者返回值,我们应该将函数定义为返回void。但是考虑如果我们的运算符<<返回void会发生什么。当编译器评估时std::cout << point << std::endl;,由于优先级/关联性规则,它将此表达式计算为(std::cout << point) << std::endl;。 std::cout << point会调用我们的void返回重载的operator <<函数,它返回void。然后部分调用的表达式变为:void << std::endl;,这没有任何意义!

通过返回out参数作为返回类型,(std::cout << point)返回std :: cout。然后我们的部分调用表达式变为:std::cout << std::endl;,然后自己进行调用!

任何时候我们希望我们的重载二元运算符能够以这种方式链接,应该返回左操作数(通过引用)。在这种情况下,通过引用返回左侧参数是可以的 - 因为左侧参数是由调用函数传入的,所以当被调用函数返回时它必须仍然存在。因此,我们不必担心引用超出范围的内容并在操作返回时被销毁。

只是为了证明它正确的,请考虑以下示例,它将Point类与重载运算符<<我们在上面写到:

#include <iostream>
 
class Point
{
private:
    double m_x, m_y, m_z;
 
public:
    Point(double x=0.0, double y=0.0, double z=0.0): m_x(x), m_y(y), m_z(z)
    {
    }
 
    friend std::ostream& operator<< (std::ostream &out, const Point &point);
};
 
std::ostream& operator<< (std::ostream &out, const Point &point)
{
    // Since operator<< is a friend of the Point class, we can access Point's members directly.
    out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ")";
 
    return out;
}
 
int main()
{
    Point point1(2.0, 3.5, 4.0);
    Point point2(6.0, 7.5, 8.0);
 
    std::cout << point1 << " " << point2 << '\n';
 
    return 0;
}

这会产生以下结果:

Point(2,3.5,4)Point(6,7.5,8)
重载运算符>>

输入操作符也可能重载。这是以类似于重载输出操作符的方式完成的。您需要知道的关键是std :: cin是std :: istream类型的对象。这是带有重载运算符>>的Point类:

#include <iostream>
 
class Point
{
private:
    double m_x, m_y, m_z;
 
public:
    Point(double x=0.0, double y=0.0, double z=0.0): m_x(x), m_y(y), m_z(z)
    {
    }
 
    friend std::ostream& operator<< (std::ostream &out, const Point &point);
    friend std::istream& operator>> (std::istream &in, Point &point);
};
 
std::ostream& operator<< (std::ostream &out, const Point &point)
{
    // Since operator<< is a friend of the Point class, we can access Point's members directly.
    out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ")";
 
    return out;
}
 
std::istream& operator>> (std::istream &in, Point &point)
{
    // Since operator>> is a friend of the Point class, we can access Point's members directly.
    // note that parameter point must be non-const so we can modify the class members with the input values
    in >> point.m_x;
    in >> point.m_y;
    in >> point.m_z;
 
    return in;
}

这是一个使用重载运算符<<和operator >>的示例程序:

int main()
{
    std::cout << "Enter a point: \n";
 
    Point point;
    std::cin >> point;
 
    std::cout << "You entered: " << point << endl;
 
    return 0;
}

假设用户输入3.0 4.5 7.26输入,程序将产生以下结果:

You entered:Point(3,4.5,7.26)
结论

重载operator <<和operator >>可以非常轻松地将类输出到屏幕并接受来自控制台的用户输入。

Quiz Time:

参考我们在上一个测验中编写的Fraction类(如下所示),并为其添加一个重载的运算符<<和operator >>。

以下程序应编译:

int main()
{
 
	Fraction f1;
	std::cout << "Enter fraction 1: ";
	std::cin >> f1;
 
	Fraction f2;
	std::cout << "Enter fraction 2: ";
	std::cin >> f2;
 
	std::cout << f1 << " * " << f2 << " is " << f1 * f2 << endl; // 注意:f1 * f2的结果的右值
 
	return 0;
}

并产生结果:

Enter fraction 1:2/3
Enter fraction 2: 3/8
2/3 * 3/8is1/4
这是Fraction类:

#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(),以确保我们的分数不会出现假分数
		// 任何被覆盖的分数都需要重新调用reduce()
		reduce();
	}
 
	//我们将使gcd静态,以便它可以成为类Fraction的一部分,而不需要使用类型为Fraction的对象
	static int gcd(int a, int b)
	{
		return b == 0 ? 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);
}

解决方案:

#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)
	{
		//我们在构造函数中放置了reduce(),以确保我们的分数不会出现假分数
		// 任何被覆盖的分数都需要重新调用reduce()
		reduce();
	}
 
	static int gcd(int a, int b)
	{
		return b == 0 ? 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);
 
	friend std::ostream& operator<<(std::ostream &out, const Fraction &f1);
	friend std::istream& operator>>(std::istream &in, 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);
}
 
std::ostream& operator<<(std::ostream &out, const Fraction &f1)
{
	out << f1.m_numerator << "/" << f1.m_denominator;
	return out;
}
 
std::istream& operator>>(std::istream &in, Fraction &f1)
{
	char c;
 
	// 覆盖f1的值
	in >> f1.m_numerator;
	in >> c; // ignore the '/' separator
	in >> f1.m_denominator;
 
	// 由于我们覆盖现有的f1,我们需要再次调用reduce()
	f1.reduce();
 
	return in;
}
 
int main()
{
 
	Fraction f1;
	std::cout << "Enter fraction 1: ";
	std::cin >> f1;
 
	Fraction f2;
	std::cout << "Enter fraction 2: ";
	std::cin >> f2;
 
	std::cout << f1 << " * " << f2 << " is " << f1 * f2 << endl; // 注意:f1 * f2的结果是右值
 
	return 0;
}

猜你喜欢

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