文章目录
先决条件
我们按照多文件的方式来新建这个项目:
在前面两篇文章(C++进阶:使用普通函数重载算数运算符和(C++进阶:使用友元函数重载算术运算符的基础上,增加成员函数的重载.
源文件(3个)
main.cpp用来测试
Fraction.h 用来定义类和声明函数原型;
Fraction.cpp 用来实现函数原型.
它们在同一目录下:

main.cpp
#include "Fraction.h"
#include <iostream>
void overload_mul_test()
{
Fraction f1{ 1, 4};
f1.print();
Fraction f2{ 1, 2 };
f2.print();
Fraction f3{ f1 * f2 };
f3.print();
Fraction f4{ 2 * f1 };
f4.print();
Fraction f5{ f1 * 3 };
f5.print();
Fraction f6{ Fraction{1, 2} * Fraction{2, 3} * Fraction{3, 4} };
f6.print();
}
void overload_in_test()
{
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 << '\n';
}
void overload_comparison_test()
{
Fraction f1{3, 2};
Fraction f2{5, 8};
std::cout << f1 << ((f1 == f2) ? " == " : " not == ") << f2 << '\n';
std::cout << f1 << ((f1 != f2) ? " != " : " not != ") << f2 << '\n';
std::cout << f1 << ((f1 < f2) ? " < " : " not < ") << f2 << '\n';
std::cout << f1 << ((f1 > f2) ? " > " : " not > ") << f2 << '\n';
std::cout << f1 << ((f1 <= f2) ? " <= " : " not <= ") << f2 << '\n';
std::cout << f1 << ((f1 >= f2) ? " >= " : " not >= ") << f2 << '\n';
}
int main()
{
overload_mul_test();
overload_in_test();
overload_comparison_test();
return 0;
}
Fraction.h
#ifndef FRACTION_H
#define FRACTION_H
#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 }
{ }
int getNumerator() const
{
return m_numerator;
}
int getDenominator() const
{
return m_denominator;
}
void reduce();
void print();
friend std::ostream& operator<<(std::ostream& out, const Fraction& f1);
friend bool operator==(const Fraction& f1, const Fraction& f2);
friend bool operator!=(const Fraction& f1, const Fraction& f2);
friend bool operator< (const Fraction& f1, const Fraction& f2);
friend bool operator> (const Fraction& f1, const Fraction& f2);
friend bool operator<=(const Fraction& f1, const Fraction& f2);
friend bool operator>=(const Fraction& f1, const Fraction& f2);
};
Fraction operator*(const Fraction& f1, const Fraction& f2);
Fraction operator*(const Fraction& f1, int value);
Fraction operator*(int value, const Fraction& f1);
std::istream& operator>>(std::istream& in, Fraction& f1);
#endif
Fraction.cpp
#include "Fraction.h"
#include <numeric>
#include <iostream>
Fraction operator*(const Fraction& f1, const Fraction& f2)
{
return Fraction{ f1.getNumerator() * f2.getNumerator(), f1.getDenominator() * f2.getDenominator() };
}
Fraction operator*(const Fraction& f1, int value)
{
return Fraction{ f1.getNumerator() * value, f1.getDenominator() };
}
Fraction operator*(int value, const Fraction& f1)
{
return Fraction{ f1.getNumerator() * value, f1.getDenominator() };
}
void Fraction::reduce()
{
int m_gcd{ std::gcd(m_numerator, m_denominator) };
if (m_gcd) // Make sure we don't try to divide by 0
{
m_numerator /= m_gcd;
m_denominator /= m_gcd;
}
}
void Fraction::print()
{
Fraction::reduce();
std::cout << m_numerator<< "/" << m_denominator << '\n';
}
std::istream& operator>>(std::istream& in, Fraction& f1)
{
int numerator{};
char ignore {};
int denominator{};
in >> numerator >> ignore >> denominator;
if (denominator == 0)
in.setstate(std::ios_base::failbit);
if(in)
f1 = Fraction{numerator, denominator};
return in;
}
std::ostream& operator<<(std::ostream& out, const Fraction& f1)
{
out << f1.m_numerator << "/" << f1.m_denominator;
return out;
}
bool operator==(const Fraction& f1, const Fraction& f2)
{
return f1.m_numerator == f2.m_numerator
&& f1.m_denominator == f2.m_denominator;
}
bool operator!=(const Fraction& f1, const Fraction& f2)
{
return !(operator==(f1, f2));
}
bool operator< (const Fraction& f1, const Fraction& f2)
{
return (f1.m_numerator * f2.m_denominator < f2.m_numerator * f1.m_denominator);
}
bool operator> (const Fraction& f1, const Fraction& f2)
{
return operator< (f2, f1);
}
bool operator<=(const Fraction& f1, const Fraction& f2)
{
return !(operator>(f1, f2));
}
bool operator>=(const Fraction& f1, const Fraction& f2)
{
return !(operator<(f1, f2));
}
功能测试:
测试分数算术相乘*运算符的重载(只需在main.cpp微调)
int main()
{
overload_mul_test();
// overload_in_test();
// overload_comparison_test();
return 0;
}

注意:reduce()函数是化简用的,调用了标准库里的std::gcd(m_numerator, m_denominator) 函数, 来提取分子和分母的最的公约数,做了分母不为0的处理.
测试分数输入>>运算符的重载(只需在main.cpp微调)
int main()
{
// overload_mul_test();
overload_in_test();
// overload_comparison_test();
return 0;
}

注意:第二个对于输入符号的重载测试虽然能够满足简单的需求,但还是存在很多问题, 比如第三个最后结果并没有化简, 第四个测试的分母为0时调用了输入流状态函数setstate(std::ios_base::failbit),会设置流为failbit失败状态,告诉编译器不要执行后面的了, 尽管防住了第一次输入, 但因为第二次输入时它仍处于:failbit态,默认为构造函数的值,以0/1输出,细节见下:
第一次输入:1/0
in >> numerator >> ignore >> denominator; // 成功读取: numerator=1, ignore='/', denominator=0
if (denominator == 0) // 条件为true
in.setstate(std::ios_base::failbit); // 设置失败状态
if(in) // 检查失败,因为failbit被设置了
f1 = Fraction{numerator, denominator}; // 这行被跳过!!!
关键点:一个分数 f1 没有被赋值,它保持原来的值(默认构造的 0/1)
第二次输入:
// in 仍然处于 failbit 状态
in >> numerator >> ignore >> denominator; // 这行直接失败,不读取任何数据!
if (denominator == 0) // denominator 还是上一次的值 0
in.setstate(std::ios_base::failbit); // 再次设置失败状态(虽然已经是失败状态)
if(in) // 条件为false,跳过赋值
f1 = Fraction{numerator, denominator}; // 不会执行
读入失败,它保持原来的值(默认构造的 0/1)
测试比较运算符的重载(只需在main.cpp微调)
int main()
{
// overload_mul_test();
// overload_in_test();
overload_comparison_test();
return 0;
}

目前未发问题, 这里有个很重要的思想:
标准库中的某些容器类(包含其他类集合的类)需要重载运算符<,以便它们可以保持元素排序。
- operator!= 可以实现为 !(operator==)
- operator> 可以实现为 operator< ,并将参数的顺序翻转
- operator>= 可以实现为 !(operator<)
- operator<= 可以实现为 !(operator>)
这意味着我们只需要实现 operator== 和 operator< 的逻辑,其他四个比较运算符就可以根据这两个运算符来定义.
源文件结构(看普通, 友元, 成员函数重载运算符的特点)
(举例,并非源文件代码,伪代码)

| who重载? | 头文件声明.h | 实现.cpp | 对类class 数据成员访问权限 |
|---|---|---|---|
| 成员函数 | 类中 | 函数名加类限定 | 直接访问 |
| 友元函数 | 类中带friend关键字 | 舍弃friend | 直接访问 |
| 普通函数 | 类外 | 无变化 | 通过public 成员函数访问 |
什么时候用?(普通, 友元, 成员)
补充成员函数重载运算符知识:
使用成员函数重载运算符时:
- 重载运算符必须添加为左操作数的成员函数。
- 左操作数成为隐式的 *this 对象
- 所有其他操作数都成为函数参数。
赋值 (=)、下标 ([])、函数调用 (()) 和成员选择 (->) 运算符必须重载为成员函数,因为语言要求它们这样做。
我们不能将 operator<< 重载为成员函数。为什么?因为重载的运算符必须添加为左操作数的成员。在这种情况下,左操作数是 std::ostream 类型的对象。std::ostream 是标准库的一部分。我们无法修改类声明以将重载添加为 std::ostream 的成员函数.
| 成员函数 | 普通函数 | 友元函数 | |
|---|---|---|---|
| 重载赋值(=)、下标([])、函数调用(())或成员选择(->) | 对 | ||
| 重载一元运算符 | 对 | ||
| 重载不修改其左操作数的二元运算符(例如 operator+) | (首选)对 | 对 | |
| 重载修改其左操作数的二元运算符,但不能向左操作数的类定义添加成员(例如,运算符<<,其左操作数为 ostream 类型) | (首选)对 | 对 | |
| 重载修改其左操作数的二元运算符(例如 operator+=),并且可以修改左操作数的定义 | 对 |
Q:成员函数是operator<<是怎么修改左操作数 ostream& 对象的?
A:std::cout不会立即打印或读取。它们将输出/输入存储在缓冲区中,稍后再打印或读取。打印/读取时,该缓冲区会被修改。您还可以更改流的标志,例如,当您输入无效内容并std::cin进入失败状态时.
Q:左操作数 ostream& 对象可以定义成const 吗?
A:不能. operator<<(作为全局函数)通过接收一个 std::ostream&(非const引用)类型的参数来修改左操作数。这个引用使得函数能够直接作用于原始的流对象,从而改变其缓冲区内容、格式化标志位和错误状态。这种设计既保证了语法上的直观(cout << obj),又提供了为自定义类型扩展输出功能的能力。如果是const ostream& , std::cout << " hello, " << “world!\n” 就不能连着输出啦.
1037

被折叠的 条评论
为什么被折叠?



