C++进阶:普通函数, 友元函数, 成员函数重载运算符.

先决条件

我们按照多文件的方式来新建这个项目:
在前面两篇文章(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” 就不能连着输出啦.

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值