如何在 C++ 中重载两个不同的 I/O 运算符

How do I overload two different I/O operator in C++

我有一个 class 这样的:

MyClass {
public:
    int a;
    unsigned char b,c,d;
    size_t e,f;
    double g, h;
    friend ostream& operator<< (ostream& os, const MyClass& mc) {
        os<<mc.a<<mc.b<<mc.c<<mc.d<<mc.e<<mc.f<<mc.g<<mc.h;
        return os;
    }
};

我重载了 << 运算符,但我还想要另一个 << 案例,我怎样才能重载两个不同的 << 运算符?

我是这样想的:

MyClass {
public:
    int a;
    unsigned char b,c,d;
    size_t e,f;
    double g, h;
    friend ostream& operator<< (ostream& os, const MyClass& mc, int type) {
        if(type==1){
            os<<mc.a<<mc.b<<mc.c<<mc.d<<mc.e<<mc.f<<mc.g<<mc.h;
            return os;
        } else if(type==2){
            os<<mc.a<<mc.c<<mc.d<<mc.e<<mc.g;
            return os;
    }
};

但是没用,Too many arguments for this operator function

我该怎么做?

您不能拥有两个具有相同签名的函数,而且正如您发现的那样,您不能向该运算符重载添加额外的参数。

这可以编译,但我建议您找到另一种设计,因为这是一种令人厌恶的设计:

#include <iostream>

class MyClass
{

};

std::ostream& operator<<( std::ostream& os, const MyClass& myClass ) { return os; }
std::ostream& operator<<( std::ostream& os, MyClass& myClass ) { return os; }

Temp.h

#ifndef TEMP_H
#define TEMP_H

class Temp {
    friend std::ostream& operator<<(std::ostream& os, const Temp& t);
private:
    int m_a;
    double m_b;

    bool m_updated;

public:
    Temp();
    explicit Temp( int a, double b = 0 );

    int     getA() const;
    void    setA( int a );
    double  getB() const;
    void    setB( double b );

    bool    isUpdated() const;

}; // Temp

#endif // TEMP_H

Temp.cpp

#include "stdafx.h"
#include "Temp.h"

std::ostream& operator<<( std::ostream& os, const Temp& t ) {
    if ( t.isUpdated() ) {
        os << t.getA() << " " << t.getB();
        return os;
    } else {
        os << t.getA();
        return os;
    }

} // operator<<

Temp::Temp() :
m_a( 0 ),
m_b( 0 ),
m_updated( false ) {
} // Temp

Temp::Temp( int a, double b ) :
m_a( a ),
m_b( b ),
m_updated( false ) {
    if ( m_b != 0 ) {
        m_updated = true;
    } 
} // Temp

int Temp::getA() const {
    return m_a;
} // getA

void Temp::setA( int a ) {
    m_a = a;
} // setA

double Temp::getB() const {
    return m_b;
} // getB

void Temp::setB( double b ) {
    m_b = b;
    if ( m_b != 0 ) {
        m_updated = true;
    }
} // setB

bool Temp::isUpdated() const {
    return m_updated;
} // isUpdated

main.cpp

#include "stdafx.h"
#include "Temp.h"

int main() {
    Temp t1( 3 );

    std::cout << "Before Updated" << std::endl;
    std::cout << t1 << std::endl << std::endl;

    std::cout << "After Updated" << std::endl;
    t1.setB( 4.2 );
    std::cout << t1 << std::endl << std::endl;

    Temp t2( 7, 12.5 );

    std::cout << "Updated Values During Construction" << std::endl;
    std::cout << t2 << std::endl;


    std::cout << std::endl;

    return 0;
} // main

std::ostream << operator 不允许您传入 3 个值,它需要一个 ostream 对象,以及您要传递给它的对象。所以我在这里所做的是创建 class 以具有默认构造函数和显式构造函数,其中最后一个参数是可选的。现在这还需要 class 维护 1 个额外的变量,一个布尔类型。此布尔类型跟踪以查看可选参数是否已在初始构造函数期间或通过更新函数或设置函数随时更新。然后当这个 class 对象与 std::ostream << operator 一起使用时,它首先检查这个布尔值是真还是假,然后从那里分支到它应该使用的流类型。

现在,当您开始使用 class 时,您必须考虑;更新值是否发生在两个直接阶段?或者它们可以一次更新一个吗?提前了解这一点很重要。如果您知道稍后或在第二阶段将向您的 class 添加或更新多个变量,那么我提供的方法将起作用并且易于管理。

现在,如果您在两个以上的多个阶段中一次添加组件 1,那么此方法会变得更加复杂。现在,我展示的示例 class 中的这一设计过程存在一个缺陷。设计缺陷是这样的:如果 0 是 m_b 的可接受答案怎么办?那么这种更新方法将不适用于这种情况,因为 class 将表明它没有更新并且不会输出第二个字段。这是需要考虑的事情。

一种可能的解决方法是采用与我提供的相同的设计方法,但将所有初始值设为基本数据类型,并将在第二阶段添加的所有参数设为指针到他们的类型。然后这样你就可以根据指针是否有值或者是 nullptr.

来设置你的 m_updated

但此代码确实展示了一种通过单个 std::ostream << operator 调用分支不同 std::ostream << operators 的方法。

编辑

另一种可能的解决方案不是在 class 中使用单个布尔值,而是在 class 中使用枚举,如下所示:

class SomeClass {
public:
    enum Stage {
        S_1 = 1,
        S_2,
        S_3,
        S_LAST,
    };

private:
    Stage m_stage;

public:
    Stage getStage() const { return m_stage; }
};

然后当你用默认类型构造你的 class 时; m_stage 将设置为 S_1,然后当您执行特定的更新组时,您可以经历多个阶段并在 std::ostream << operator 方法内,而不是使用带有 bool 的 if 语句,您可以根据它所在的 classes 阶段使用 switch 和 case 语句,在 switch 语句中,默认情况可以是第一阶段或错误情况。

我修改了这个class并决定保留原答案以供参考。现在这个版本在设计上更优雅一些;然而,如果我使用我的错误处理程序/记录器库,它可能会更干净,但是将它们包含在这个答案中是为了适应这里的规模,但主要功能将设置在 try catch 块中,而我的错误如果错误很严重,处理程序和记录器将抛出错误并退出程序,或者如果值无效但程序的操作仍处于可接受的状态以继续,则将消息记录到控制台 window 或文件。

这个 class 有点臃肿,包含通常不需要的错误消息,但用作演示以显示正确的程序逻辑流程。

这是更新后的 class 和我向 user1024 声明的主要函数,它解决了基于字段值而不是调用哪个函数设置 bool 标志变量的问题。有了这个,他现在可以拥有未初始化为 class 的默认值,然后使用相同的默认值初始化 class。 class 的状态现在基于函数调用而不是成员值。

Temp.h

#ifndef TEMP_H
#define TEMP_H

class Temp {
    friend std::ostream& operator<<(std::ostream& os, const Temp& t);
private:
    int m_a, m_b, m_c;
    double m_d, m_e, m_f;

    bool m_isInitialized;
    bool m_updated;

    const std::string m_strInitMessage = std::string( "First stage values must be initalized before calling this funciton.\n" );
    const std::string m_strUpdateMessage = std::string( "setUpdateStage needs to be called first before modifying this value.\n" );
public:
    Temp();
    Temp( int a, int b, int c );
    Temp( int a, int b, int c, double d, double e, double f );

    void    setInitialStage( int a, int b, int c );
    void    setUpdateStage( double d, double e, double f );

    bool    isInitialized() const;
    bool    isUpdated() const;

    int     getA() const;
    int     getB() const;
    int     getC() const;

    // These Are Updating Functions Not Setting Functions setInitialStage Must Be Called First
    void    updateA( int a );
    void    updateB( int b );
    void    updateC( int c );

    double  getD() const;
    double  getE() const;
    double  getF() const;

    // These Are Updating Functions Not Setting Functions Both setInitialStage & setUpdateStage Must Be Called First
    void    updateD( double d );
    void    updateE( double e );
    void    updateF( double f );

private:
    // Helper Function
    bool   testStages();

}; // Temp

#endif // TEMP_H

Temp.cpp

#include "stdafx.h"
#include "Temp.h"

std::ostream& operator<<( std::ostream& os, const Temp& t ) {
    if ( t.isUpdated() ) {
        os << t.getA() << " " << t.getB() << " " << t.getC() << " "
           << t.getD() << " " << t.getE() << " " << t.getF() << std::endl;
        return os;
    } else {
        os << t.getA() << " " << t.getB() << " " << t.getC() << std::endl;
        return os;
    }

} // operator<<

Temp::Temp() :
m_a( 0 ),
m_b( 0 ),
m_c( 0 ),
m_d( 0 ),
m_e( 0 ),
m_f( 0 ),
m_isInitialized( false ),
m_updated( false ) {
} // Temp

Temp::Temp( int a, int b, int c ) :
m_a( a ),
m_b( b ),
m_c( c ),
m_d( 0.0 ),
m_e( 0.0 ),
m_f( 0.0 ),
m_isInitialized( true ),
m_updated( false ) {
} // Temp

Temp::Temp( int a, int b, int c, double d, double e, double f ) :
m_a( a ),
m_b( b ),
m_c( c ),
m_d( d ),
m_e( e ),
m_f( f ),
m_isInitialized( true ),
m_updated( true ) {
} // Temp

void Temp::setInitialStage( int a, int b, int c ) {
    // Do Nothing With 2nd Stage Variables And Update Flag

    if ( !m_isInitialized ) {
        m_a = a;
        m_b = b;
        m_c = c;
        m_isInitialized = true;
    } else {
        // Do not Reinitalize
        std::cout << "Initial stage values are already initialized, please use the individual update functions.\n";
        return;
    }   
} // setInitialStage

void Temp::setUpdateStage( double d, double e, double f ) {
    // Check To See If This Has Been Intialized First
    if ( !m_isInitialized ) {
        std::cout << "\nFirst Stage values must be initialized first\n";
        return;
    } else {
        if ( !m_updated ) {
            // Do nothing with Initial Values
            m_d = d;
            m_e = e;
            m_f = f;
            m_updated = true;
        } else {
            // Do Not Reinitalize
            std::cout << "Update stage values have already been initialized, please use the individual update functions.\n";
            return;
        }
    }
} // setUpdateStage 

bool Temp::isInitialized() const {
    return m_isInitialized;
} // isInitialized

bool Temp::isUpdated() const {
    return m_updated;
} // isUpdated

int Temp::getA() const {
    if ( !m_isInitialized ) {
        std::cout << "m_a has not been initialized\n";
        return 0;
    }
    return m_a;
} // getA

int Temp::getB() const {
    if (!m_isInitialized) {
        std::cout << "m_b has not been initialized\n";
        return 0;
    }
    return m_b;
} // getB

int Temp::getC() const {
    if ( !m_isInitialized ) {
        std::cout << "m_c has not been initialized\n";
        return 0;
    }
    return m_c;
} // getC

void Temp::updateA( int a ) {
    if ( !m_isInitialized ) {
        std::cout << m_strInitMessage;
        return;
    }
    m_a = a;
} // updateA

void Temp::updateB( int b ) {
    if ( !m_isInitialized ) {
        std::cout << m_strInitMessage;
        return;
    }
    m_b = b;
} // updateB

void Temp::updateC( int c ) {
    if ( !m_isInitialized ) {
        std::cout << m_strInitMessage;
        return;
    }
    m_c = c;
} // updateC

double Temp::getD() const {
    if ( !m_updated ) {
        std::cout << "m_d has not been initialized\n";
        return 0;
    }
    return m_d;
} // getD

double Temp::getE() const {
    if (!m_updated) {
        std::cout << "m_e has not been initialized\n";
        return 0;
    }
    return m_e;
} // getE

double Temp::getF() const {
    if (!m_updated) {
        std::cout << "m_f has not been initialized\n";
        return 0;
    }
    return m_f;
} // getF

bool Temp::testStages() {
    if ( !m_isInitialized ) {
        std::cout << m_strInitMessage;
        return false;
    } else {
        if ( !m_updated ) {
            std::cout <<  m_strUpdateMessage;
            return false;
        }
    }   
    return true;
} // testStages

void Temp::updateD( double d ) {
    if ( !testStages() ) {
        return;
    }
    m_d = d;
} // updateD

void Temp::updateE( double e ) {
    if ( !testStages() ) {
        return;
    }
    m_e = e;
} // updateE

void Temp::updateF( double f ) {
    if ( !testStages() ) {
        return;
    }
    m_f = f;
} // update

main.cpp

#include "stdafx.h"
#include "Temp.h"

int main() {

    Temp t1;
    std::cout << "Default constructor called." << std::endl;
    std::cout << t1 << std::endl;

    // Error Cases
    std::cout << "Error Cases For Default Constructor Before setInitialStage is called:" << std::endl;
    std::cout << "---------------------------------------------------------------------" << std::endl;
    std::cout << "Trying to update a first stage value before setInitialStage is called." << std::endl;
    t1.updateA( 1 );
    std::cout << t1 << std::endl;
    std::cout << "Trying to update a second stage value before setInitialStage is called." << std::endl;
    t1.updateD( 2.3 );
    std::cout << t1 << std::endl;
    std::cout << "Trying to call setUpdateStage before m_isInitialized = true" << std::endl;
    t1.setUpdateStage( 4.5, 6.7, 8.9 );
    std::cout << t1 << std::endl;

    // 1st Stage Initialization WRT To Using A Default Constructor
    std::cout << "After setInitalStage is called" << std::endl;
    t1.setInitialStage( 1, 2, 3 );
    std::cout << t1 << std::endl;

    // Error Cases
    std::cout << "Error Cases For Default Constructor After setInitialStage is called:" << std::endl;
    std::cout << "--------------------------------------------------------------------" << std::endl;
    std::cout << "Calling setInitialStage after it has already been called." << std::endl;
    t1.setInitialStage( 4, 5, 6 );
    std::cout << t1 << std::endl;
    std::cout << "Trying to update a second stage value after setInitialStage and before setUpdateStage have been called." << std::endl;
    t1.updateD( 7.8 );
    std::cout << t1 << std::endl;

    std::cout << "Updating a first stage value after setInitialStage is called." << std::endl;
    t1.updateB( 9 );
    std::cout << t1 << std::endl;

    std::cout << "Calling setUpdatedStage." << std::endl;
    t1.setUpdateStage( 10.11, 12.13, 14.15 );
    std::cout << t1 << std::endl;

    // Error Case
    std::cout << "Error Case For Default Constructor After Both\n setInitialStage & setUpdateStage have been called." << std::endl;
    std::cout << "------------------------------------------------" << std::endl;
    std::cout << "Calling setUpdateStage after it has already been called." << std::endl;
    t1.setUpdateStage( 16.17, 18.19, 20.21 );
    std::cout << t1 << std::endl;

    std::cout << "Updating second stage value afer both setInitializeStage & setUpdateStage have been called." << std::endl;
    t1.updateF( 22.23 );
    std::cout << t1 << std::endl << std::endl;

    Temp t2( 1, 2, 3 );
    std::cout << "First stage constructor called" << std::endl;
    std::cout << t2 << std::endl;

    // Error Cases
    std::cout << "Error Cases For 1st Stage Constructor" << std::endl;
    std::cout << "-------------------------------------" << std::endl;
    std::cout << "Calling setInitialStage after using this constructor." << std::endl;
    t2.setInitialStage( 4, 5, 6 );
    std::cout << t2 << std::endl;

    std::cout << "Trying To Update Second Stage Value Before setUpdateStage is called." << std::endl;
    t2.updateD( 7.8 );
    std::cout << t2 << std::endl;

    std::cout << "Updating 1st Stage Value" << std::endl;
    t2.updateB( 9 );
    std::cout << t2 << std::endl;

    std::cout << "Calling setUpdateStage" << std::endl;
    t2.setUpdateStage( 10.11, 12.13, 14.15 );
    std::cout << t2 << std::endl;

    // Error Case
    std::cout << "Error Case For 1st Stage Constructor After setUpdateStage has been called." << std::endl;
    std::cout << "-------------------------------------------------------------------------" << std::endl;
    t2.setUpdateStage( 16.17, 18.19, 20.21 );
    std::cout << t2 << std::endl;

    std::cout << "Updating 2nd stage value." << std::endl;
    t2.updateE( 22.23 );
    std::cout << t2 << std::endl << std::endl;


    Temp t3( 1, 2, 3, 4.5, 6.7, 8.9 );
    std::cout << "Full Stage Constructor Called" << std::endl;
    std::cout << t3 << std::endl;

    // Error Cases
    std::cout << "Error Cases For Full Stage Constructor:" << std::endl;
    std::cout << "---------------------------------------" << std::endl;
    std::cout << "Calling setInitialStage" << std::endl;
    t3.setInitialStage( 10, 11, 12 );
    std::cout << t3 << std::endl;
    std::cout << "Calling setUpdateStage" << std::endl;
    t3.setUpdateStage( 13.14, 15.16, 17.18 );
    std::cout << t3 << std::endl;

    std::cout << "Updating 1st & 2nd Stage Values" << std::endl;
    t3.updateA( 19 );
    t3.updateD( 20.21 );
    std::cout << t3 << std::endl;

    std::cout << "With this design 0 is now an acceptable value." << std::endl;
    std::cout << "Updating all of t3's values." << std::endl;
    t3.updateA( 0 );
    t3.updateB( 0 );
    t3.updateC( 0 );
    t3.updateD( 0 );
    t3.updateE( 0 );
    t3.updateF( 0 );
    std::cout << t3 << std::endl;

    std::cout << "Using Default Constructor To Show That Both stageFunctions Can accept 0 as value" << std::endl;
    Temp t4;
    std::cout << "Unitialized:" << std::endl
              << t4 << std::endl;

    std::cout << "Calling setInitialStage" << std::endl;
    t4.setInitialStage( 0, 0, 0 );
    std::cout << t4 << std::endl;
    std::cout << "Calling setUpdateStage" << std::endl;
    t4.setUpdateStage( 0, 0, 0 );
    std::cout << t4 << std::endl;


    std::cout << std::endl; // Used As A Break Point Before Application End

    return 0;
} // main

有了这个 class 有 3 个构造函数,您可以通过 3 种方式创建这个 class:创建一个空的未初始化版本以稍后填充所有部分,在构造时设置第一个阶段以更新第二阶段之后,最后在建造时完全设置所有阶段。这也表明所有 3 个构造函数、所有初始化函数、更新函数和 getter 都通过相同的 std::ostream << operator 工作。它还演示了如何以特定顺序调用特定函数,还演示了何时不重复调用特定函数。我相信还有许多其他方法可以解决这个问题,但是能够看到以多种成功方式完成相同的任务有其优势。