c/c++开发,无可避免的操作符operator(篇三),类与操作符

                                让我们自定义的类类型像内置类型一样使用。

目录

一、类基本操作符设计

        1.1 类的数据特性

        1.2 统统交给默认函数

        1.3 请别偷懒

        1.4 构建移动语义的类

        1.5 禁止复制覆盖面

二、类的操作符增效设计

        2.1 类的操作符系列-其他赋值操作符

        2.2 类的操作符系列-自增自减操作符

        2.3 类的操作符系列-类型转换

        2.4 类的操作符系列-成员访问操作

        2.5 类的操作符系列-函数对象

三、非类成员操作符-丰富类的实现

        3.1 算术操作符

        3.2 关系操作符

        3.3 流提取与插入操作符<<、>>

四、附录,本博文涉及完整源码


一、类基本操作符设计

        c++是面向对象编程,在项目开发中,我们会通过抽象思维,将各种数据进行归并成类类型,在类中定义了数据成员和函数成员:数据成员用于存储与该类类型的对象相关联的状态,而函数成员则负责执行赋予数据意义的操作。类类型常被称为抽象数据类型(abstract data types)。抽象数据类型将数据(即状态)和作用于状态的操作视为一个单元,是面向对象编程和泛型编程的基础。通过类我们能够将实现和接口分离,用接口指定类所支持的操作,而实现的细节只需类的实现者了解或关心。

        C++ 中的类能够控制在初始化、复制、赋值和销毁对象时发生的操作。除了一些类型本身特性行为外,大多通用行为都是通过操作符来实现的,前两篇博文就已经展示了如何利用操作符重载,在 C++ 中创建新的类型,就像创建内置类型一样,允许将类类型的操作数与内置操作符一起使用。此外还通过特殊操作符的类成员函数-转换函数,这种函数定义了类类型对象之间的隐式转换。编译器应用这些转换就像它们是在内置类型之间发生的转换一样。

        1.1 类的数据特性

        类的数据表达通常称为成员变量,但是不仅仅限于成员变量,例如来自父类的成员变量、友元的成员变量等都是可操作的数据对象。定义类类型的数据,是数据抽象行为,是一种依赖于接口和实现分离的编程(和设计)技术。类设计者常常封装及隔离数据,使用该类的程序员不必了解这些细节。即,在使用一个类型的程序员仅需了解类型的接口,主要可以抽象地考虑该类型做什么,而不必具体地考虑该类型如何工作。

        类的数据成员通常会有以下几种:

        1)非类类型的内置类型变量;

        2)以裸指针为动态分配内存的句柄,指向非类类型或类类型或函数指针

        3)内置类型容器变量,内置非类类型的数组

        4)用户定义的类类型的变量或指针变量

        5)模板参数类型的成员变量,可能是内置非类类型或类类型,也可能是用户定义类类型

        6)类静态成员、const成员等

        类可操作数据除了自身的成员变量外,还包括:

        1)声明为其他类的友元,就可以直接操作其他类的各种访问权限下的数据成员

        2)来自父类的public、protected访问权限的数据成员。

        3)类类型的成员其内部的public数据成员变量。

        1.2 统统交给默认函数

        对于类类型内只包含非类类型的内置类型成员变量时,最好的办法就是将构造、析构函数、复制/移动构造函数、复制/移动赋值操作符等交付给编译器来完成,我们可以像使用结构体一样使用它们。

//test1.h
class obj
{
    public:
        int id;
        double val;
};
//main.cpp
    obj o1 = {1,1.0};
    obj o2 = o1, o3;
    o3 = o1;

        但是,通常为了封装性的,隐秘数据,一般是将数据成员变量声明为私有,因此在数据成员使用方面还是需要提供设值和获取的成员函数。

//test1.h
class ObjTest1
{
private:
    /* data */
    int id;
    double val;
public:
    void set_id(int id_){ id = id_;};
    int get_id(){ return id;};
    void set_val(double val_){ val = val_;};
    double get_val(){ return val;};
};
//main.cpp
#include "test1.h"
#include <iostream>

int main(int argc, char* argv[])
{
    ObjTest1 a1{};  //默认构造
    a1.set_id(10);
    a1.set_val(10.0);
    ObjTest1 a2 = a1;//拷贝构造
    std::cout << "a2.get_id() = " << a2.get_id() <<","
        << "a2.get_val() = " << a2.get_val() <<"\n";
    ObjTest1 a3 = {};//拷贝构造
    a3 = a1;        //赋值=
    std::cout << "a3.get_id() = " << a3.get_id() <<","
        << "a3.get_val() = " << a3.get_val() <<"\n";
    ObjTest1 a4 = ObjTest1{};//拷贝构造
    a4 = std::move(a1); //移动赋值
    std::cout << "a4.get_id() = " << a4.get_id() <<","
        << "a4.get_val() = " << a4.get_val() <<"\n";
    return 0;
};
//out log
a2.get_id() = 10,a2.get_val() = 10
a3.get_id() = 10,a3.get_val() = 10
a4.get_id() = 10,a4.get_val() = 10

        或者可以显示指定采用构造、析构、拷贝构造、赋值等函数采用默认函数:

#ifndef _TEST_1_H_
#define _TEST_1_H_

class ObjTest1
{
private:
    /* data */
    int id;
    double val;
public:
    ObjTest1() = default;
    //~ObjTest1() = default;
    virtual ~ObjTest1() = default;
    ObjTest1(const ObjTest1&) = default;
    ObjTest1(ObjTest1&) = default;
    ObjTest1& operator=(const ObjTest1&) = default;
    ObjTest1& operator=(ObjTest1&) = default;
    //
    void set_id(int id_){ id = id_;};
    int get_id(){ return id;};
    void set_val(double val_){ val = val_;};
    double get_val(){ return val;};
};
#endif //_TEST_1_H_

        若显式指定了构造、析构函数为=default声明,最好也将拷贝构造、复制赋值等做同样的处理。

        当有意将某个基类用于多态用途时,可能必须将其析构函数声明为公开的虚函数,但请慎用。由于这会阻拦隐式移动(并弃用隐式复制)的生成,因而必须将各特殊成员函数声明为预置的。这使得类有可能被切片,这是多态类经常把复制定义为弃置的原因。

        在前一篇博文提到过,如果满足下列任一条件,那么类 T 的预置的复制赋值运算符还会被定义为弃置的:

  •     T 拥有具有 const 限定的非类类型(或其数组)的非静态数据成员;
  •     T 拥有引用类型的非静态数据成员;
  •     T 拥有无法复制赋值的非静态数据成员,直接基类或虚基类(对复制赋值的重载决议失败,或选择弃置或不可访问的函数);
  •     T 是联合体式的类,且拥有的某个变体成员对应的复制赋值运算符是非平凡的
class Test{};
class Base{ public: Test *ptr; };
class Obj : public Base    隐式赋值运算等被弃置
{
public:
    //...other
    Obj(const Obj& ) = default;//复制拷贝构造运算=
    Obj(const Obj&& ) = default;//移动拷贝构造运算=
    Obj& operator=(const Obj& ) = default;//隐式复制赋值运算=
    Obj& operator=(const Obj&& ) = default;//隐式移动赋值运算=
private:
    int val;
    Test *ptr;        //隐式赋值运算等被弃置
    const int a = 10; //隐式赋值运算等被弃置
};

        因此,通常是对那些包含c++标准内置类型的普通成员变量才采取隐式操作符支持,例如各种原子类型、容器等:

class ObjTest1
{
private:
    /* data */
    int id;
    double val;
    std::vector<int> vec;    //OK
    std::string str;         //OK
public:    
    //default func
};

        1.3 请别偷懒

        因为 C++ 在各种场合(按值传递/返回、操纵容器等)对对象进行复制和复制赋值,若可访问,这些特殊成员函数就会被调用,而且若用户不定义他们,则编译器会隐式定义。

        例如类类型成员变量是一个动态句柄,隐式定义的析构函数是不会正确实现类类型设计者的想法,这就需要手动定义析构函数。若某个类需要用户定义的析构函数、用户定义的复制构造函数或用户定义的复制赋值运算符,则它几乎肯定三者全部都需要。

//test1.h
#include <string>
#include <cstring>

class ObjTest2
{
private:
    /* data */
    char *desc; //动态内存句柄
public:
    ObjTest2(const char* desc_ = ""){
        cpy(desc_);
    };
    ~ObjTest2(){
        delete[] desc;
    };
    ObjTest2(const ObjTest2& other){ 
        cpy(other.get_desc());
    };
    ObjTest2& operator=(const ObjTest2& other){
        if(this != &other) {
            delete[] desc;  // 解分配
            cpy(other.get_desc());
        }
        return *this;
    };
    //
    void set_desc(const char* desc_){ 
        cpy(desc_);
    };
    char* get_desc() const{ 
        return desc;
    };
private:
    void cpy(const char* desc_ )
    {
        if(desc_){
            std::size_t n = std::strlen(desc_) + 1;
            desc = new char[n];
            std::memcpy(desc, desc_, n); // 复制
        }
    }
};
//main.cpp
    ObjTest2 b1("hello!");  //构造函数
    ObjTest2 b2 = b1;       //拷贝构造
    std::cout << "b2.get_desc() = " << b2.get_desc() <<"\n";
    ObjTest2  b3 = ObjTest2{};
    b3 = b1;                //赋值
    std::cout << "b3.get_desc() = " << b3.get_desc() <<"\n";
//out log
b2.get_desc() = hello!
b3.get_desc() = hello!

        如果类对某种资源进行管理,而资源句柄是非类类型的对象(裸指针、POSIX 文件描述符等),则这些隐式定义的成员函数通常都不正确,其析构函数不做任何事,而复制构造函数/复制赋值运算符则进行“浅复制”(复制句柄的值,而不复制底层资源)。

        通过可复制句柄来管理不可复制资源的类(禁止复制),可能必须将其复制赋值和复制构造函数声明为私有的并且不提供其定义,或将它们定义为弃置的。删除其中之一而遗留其他被隐式定义很可能会导致错误。

#include <string>
#include <cstring>

class ObjTest2
{
private:
    /* data */
    char *desc; //动态内存句柄
public:
    ObjTest2(const char* desc_ = ""){
        cpy(desc_);
    };
    ~ObjTest2(){
        delete[] desc;
    };
    //
    void set_desc(const char* desc_){ 
        cpy(desc_);
    };
    char* get_desc() const{ 
        return desc;
    };
private:
    ObjTest2(const ObjTest2& other) = delete;            //复制构造及赋值应该同时禁止
    ObjTest2& operator=(const ObjTest2& other) = delete; //
    void cpy(const char* desc_ )
    {
        if(desc_){
            std::size_t n = std::strlen(desc_) + 1;
            desc = new char[n];
            std::memcpy(desc, desc_, n); // 复制
        }
    }
};

        1.4 构建移动语义的类

        所谓移动语义,指的就是以移动而非深拷贝的方式初始化、构造、赋值含有成员变量的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为己用”。因为用户定义析构函数、复制构造函数或复制赋值运算符的存在阻止移动构造函数和移动赋值运算符的隐式定义,所以任何想要移动语义的类必须声明全部五个特殊成员函数,不提供移动构造函数和移动赋值运算符通常不是错误,但会导致失去优化机会:

#include <string>
#include <cstring>
#include <utility>

class ObjTest2
{
private:
    /* data */
    char *desc; //动态内存句柄
public:
    ObjTest2(const char* desc_ = ""){
        cpy(desc_);
    };
    ~ObjTest2(){
        delete[] desc;
    };
    ObjTest2(const ObjTest2& other){    //复制拷贝构造
        cpy(other.get_desc());
    };
    ObjTest2(const ObjTest2&& other) noexcept //移动拷贝构造
    {
        if(this != &other) { // 自赋值时无操作(delete[]/size=0 也可以)
            char* ptr = other.get_desc();
            std::swap(desc,ptr);    //c++11
            //ptr = nullptr; //加上这个会更好理解
        }
    };
    ObjTest2& operator=(const ObjTest2& other){ //复制赋值
        if(this != &other) {
            delete[] desc;  // 解分配
            cpy(other.get_desc());
        }
        return *this;
    };
    ObjTest2& operator=(const ObjTest2&& other) noexcept { //移动赋值
        char* ptr = other.get_desc();
        std::swap(desc,ptr);//交换两个值,ptr离开作用域被释放
        //ptr = nullptr; //加上这个会更好理解
        return *this;
    };
    //
    void set_desc(const char* desc_){ 
        cpy(desc_);
    };
    char* get_desc() const{ 
        return desc;
    };
private:
    // ObjTest2(const ObjTest2& other) = delete;            //复制构造及赋值应该同时禁止
    // ObjTest2& operator=(const ObjTest2& other) = delete; //
    void cpy(const char* desc_ )
    {
        if(desc_){
            std::size_t n = std::strlen(desc_) + 1;
            desc = new char[n];
            std::memcpy(desc, desc_, n); // 复制
        }
    }
};
//main.cpp
    ObjTest2 b1("hello!");  //构造函数
    ObjTest2 b2 = b1;       //拷贝构造
    std::cout << "b2.get_desc() = " << b2.get_desc() <<"\n";
    ObjTest2  b3 = ObjTest2{};
    b3 = b1;                //赋值
    std::cout << "b3.get_desc() = " << b3.get_desc() <<"\n";
    //
    ObjTest2  b4 = std::move(b1);//移动构造
    std::cout << "b1.get_desc() = " << b1.get_desc() <<"\n";
    std::cout << "b2.get_desc() = " << b2.get_desc() <<"\n";
    ObjTest2  b5 = {};
    b5 = std::move(b1);
    std::cout << "b1.get_desc() = " << b1.get_desc() <<"\n";
    std::cout << "b5.get_desc() = " << b5.get_desc() <<"\n";
    b1.set_desc("world!");
    std::cout << "b1.get_desc() = " << b1.get_desc() <<"\n";
    std::cout << "b2.get_desc() = " << b2.get_desc() <<"\n";
    std::cout << "b5.get_desc() = " << b5.get_desc() <<"\n";
//g++ main.cpp test*.cpp -o test.exe -std=c++11
//out log
b2.get_desc() = hello!
b3.get_desc() = hello!
b1.get_desc() = hello!
b2.get_desc() = hello!
b1.get_desc() = hello!
b5.get_desc() = hello!
b1.get_desc() = world!
b2.get_desc() = hello!
b5.get_desc() = hello!

        对规范的移动赋值,期待它令被移动对象遗留于合法状态(即有完好类不变式的状态),且在自赋值时要么不做任何事,要么至少遗留对象于合法状态,并以非 const 引用返回左操作数,而且是 noexcept 的。

        在赋值不能从资源复用中受益的情形下(它不管理堆分配数组,且不含这么做的(可能传递的)成员,例如 std::vector 或 std::string 成员),有一种流行的便捷方式(c++11起,std::swap):

template< class T > void swap( T& a, T& b );   (C++11 前) 
template< class T > void swap( T& a, T& b ) noexcept(/* see below */);   (C++11 起) (C++20 前) 
template< class T > constexpr void swap( T& a, T& b ) noexcept(/* see below */);   (C++20 起) 
template< class T2, std::size_t N > void swap( T2 (&a)[N], T2 (&b)[N]) noexcept(/* see below */);   (C++11 起) (C++20 前) 
template< class T2, std::size_t N > constexpr void swap( T2 (&a)[N], T2 (&b)[N])  noexcept(/* see below */);   (C++20 起) 

        复制并交换(copy-and-swap)赋值运算符,它按值接收形参(从而根据实参的值类别而同时支持复制和移动赋值),交换形参,并令析构函数进行清理 。noexcept形式自动提供强异常保证,但禁止资源复用。

        1.5 禁止复制覆盖面

        若有任何默认操作被定义或 =delete,则应当对它们全部进行定义或 =delete引用。例如禁止拷贝构造,最好同类功能的赋值功能也同时禁止:

class ObjTest3
{
private:
    /* data */
    double val;
public:
    ObjTest3(double val_=0.0) : val(val_){};    //由于禁止了拷贝构造、赋值等,默认构造可能成为弃置,需要显式定义
    ~ObjTest3(){};
private:
    ObjTest3(const ObjTest3& ) = delete;
    ObjTest3(ObjTest3& ) = delete;
    ObjTest3& operator=(const ObjTest3& ) = delete;
    ObjTest3& operator=(ObjTest3& ) = delete;
    ObjTest3& operator=(const ObjTest3&& ) = delete;
    ObjTest3& operator=(ObjTest3&& ) = delete;
};
//main.cpp
ObjTest3 c1(10);
ObjTest3 c2{1};
// ObjTest3 c3(c1);    //error
// ObjTest3 c4{c1};    //error
// c2 = c1;            //error
// c4 = std::move(c1); //error

二、类的操作符增效设计

        2.1 类的操作符系列-其他赋值操作符

        除了简单赋值(operator=),类也知道其他各式赋值操作符定义,如:

1、
加法赋值     a += b    
减法赋值     a -= b  
2、  
乘法赋值     a *= b    
除法赋值     a /= b    
取模赋值     a %= b    
3、
逐位与赋值   a &= b    
逐位或赋值   a |= b    
逐位异或赋值 a ^= b   
4、 
逐位左移赋值 a <<= b   
逐位右移赋值 a >>= b   

        这些赋值函数通常是按上述序号标号配套出现,另外它们还和非成员操作符函数配套,例如,a += b与c = a + b配套。

//test1.h
#include <ostream>

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    ObjTest4(int ival_=0) : ival(ival_){};
    ~ObjTest4(){};
    ObjTest4& operator+=(const ObjTest4& rhs);//一元操作符+=
    ObjTest4& operator-=(const ObjTest4& rhs);//一元操作符-=
    ObjTest4& operator*=(const ObjTest4& rhs);//一元操作符*=
    ObjTest4& operator/=(const ObjTest4& rhs);//一元操作符/=
    ObjTest4& operator%=(const ObjTest4& rhs);//一元操作符%=
    ObjTest4& operator&=(const ObjTest4& rhs);//一元操作符&=
    ObjTest4& operator|=(const ObjTest4& rhs);//一元操作符|=
    ObjTest4& operator^=(const ObjTest4& rhs);//一元操作符^=
    ObjTest4& operator<<=(const ObjTest4& rhs);//一元操作符<<=
    ObjTest4& operator>>=(const ObjTest4& rhs);//一元操作符>>=
    ObjTest4& operator<<=(const int& rhs);//一元操作符<<=,重载
    ObjTest4& operator>>=(const int& rhs);//一元操作符>>=,重载
    //
    friend std::ostream& operator<<(std::ostream&, const ObjTest4&);//io运算操作符<<
};
//test1.cpp
ObjTest4& ObjTest4::operator+=(const ObjTest4& rhs)
{
    ival += rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator-=(const ObjTest4& rhs)
{
    ival -= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator*=(const ObjTest4& rhs)
{
    ival *= rhs.ival;
    return *this;
}
ObjTest4& ObjTest4::operator/=(const ObjTest4& rhs)
{
    ival /= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator%=(const ObjTest4& rhs)
{
    ival %= rhs.ival;
    return *this;
}
ObjTest4& ObjTest4::operator&=(const ObjTest4& rhs)
{
    ival &= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator|=(const ObjTest4& rhs)
{
    ival |= rhs.ival;
    return *this;
}
ObjTest4& ObjTest4::operator^=(const ObjTest4& rhs)
{
    ival ^= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator<<=(const ObjTest4& rhs)
{
    ival <<= rhs.ival;
    return *this;
}
ObjTest4& ObjTest4::operator>>=(const ObjTest4& rhs)
{
    ival >>= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator<<=(const & rhs)
{
    ival <<= rhs;
    return *this;
}
ObjTest4& ObjTest4::operator>>=(const & rhs)
{
    ival >>= rhs;
    return *this;
}
std::istream& operator>>(std::istream& in, Obj& obj)
{
    //最好安全检查
    in >> obj.val;  //取出数据写入val
    return in;
}
//main.cpp
ObjTest4 d1(10),d2(2),d3(3);
    std::cout << "d1 = " << d1 <<"\n";
    d1 += d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 += d3;
    std::cout << "d1 = " << d1 <<"\n";
    d1 *= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 /= d3;
    std::cout << "d1 = " << d1 <<"\n";
    d1 %= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 &= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 |= d3;
    std::cout << "d1 = " << d1 <<"\n";
    d1 ^= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 <<= d3;
    std::cout << "d1 = " << d1 <<"\n";
    d1 >>= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 <<= 1;
    std::cout << "d1 = " << d1 <<"\n";
    d1 >>= 1;
    std::cout << "d1 = " << d1 <<"\n";
//out log
d1 = 10
d1 = 12
d1 = 15
d1 = 30
d1 = 10
d1 = 0
d1 = 0
d1 = 3
d1 = 1
d1 = 8
d1 = 2
d1 = 4
d1 = 2

        2.2 类的操作符系列-自增自减操作符

        在c++类类型设计中,通常吧自增操作符或自减操作符一定作为类的成员操作符来实现,自增操作符和自减操作符有前缀和后缀两种形式。因此一般只要为类提供自增自减操作符,就配套提供四个操作符:

//test1.h
#include <ostream>

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    ObjTest4(int ival_=0) : ival(ival_){};
    ~ObjTest4(){};
    //
    ObjTest4 operator++();//一元操作符++,前缀式操作符
    ObjTest4 operator--();//一元操作符--,前缀式操作符
    ObjTest4 operator++(int rhs);//一元操作符++,后缀式操作符
    ObjTest4 operator--(int rhs);//一元操作符--,后缀式操作符
    //
    friend std::ostream& operator<<(std::ostream&, const ObjTest4&);//io运算操作符<<
};
//test1.cpp
ObjTest4 ObjTest4::operator++()
{
    ++ival;
    return *this;
}
ObjTest4 ObjTest4::operator--()
{
    --ival;
    return *this;
}
ObjTest4 ObjTest4::operator++(int rhs)
{
    ObjTest4 ret(*this);
    ++*this;
    return ret;
}
ObjTest4 ObjTest4::operator--(int rhs)
{
    ObjTest4 ret(*this);
    --*this;
    return ret;
}

std::ostream& operator<<(std::ostream& os, const ObjTest4& obj)
{
    os << obj.ival;
    return os;
};
//main.cpp,前缀及后缀自增自减测试
    ObjTest4 d4(10);
    std::cout << "d4 = " << d4 <<"\n";
    std::cout << "d4 = " << d4++ <<"\n";
    std::cout << "d4 = " << d4-- <<"\n";
    std::cout << "d4 = " << ++d4 <<"\n";
    std::cout << "d4 = " << --d4 <<"\n";
//out log
d4 = 10
d4 = 10
d4 = 11
d4 = 11
d4 = 10

        2.3 类的操作符系列-类型转换

        自定义类类型时,可以通过构造函数或赋值转义函数定义了其他类到本类型的转换。可以为一个类声明的构造函数的数量没有限制,只要每个构造函数的形参表是唯一的。不同的构造函数允许用户指定不同的方式来初始化数据成员,相当于每个构造函数都定义了一个隐式转换。

        构造函数不能声明为 const,构造函数的工作是初始化对象。不管对象是否为 const,都用一个构造函数来初始化化该对象。

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    ObjTest4(int ival=0) const;    //error
};

        构造函数可以使用初始化列表,以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。建议在构造函数中使用初始化列表,但其他成员函数是不能使用初始化列表的。

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    ObjTest4(int ival_=0) : ival(ival_){};                      //int to ObjTest4
    void set_ival(int ival_=0) : ival(ival_){};     //error
};

         在使用初始化列表时,初始化的次序常常无关紧要,如果一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的,通常建议按成员变量声明次序使用初始化列表:

class X 
{
   public:
    //X (int val): j(val%2), i(val){ };       //waring or error,次序问题
    //X (int val): j(i%2), i(val){ };         //error,次序问题
    //X (int val): i(val), j(i%2){ };         //OK
    //X (int val): j(val%2), i(val){ };         //OK
    X (int val): i(val), j(val%2){ };         //OK,建议
    int  i,j;
};

        可以通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函数 :

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    explicit ObjTest4(int ival_=0) : ival(ival_){};                      //int to ObjTest4
};
//
//ObjTest4 obj = 10;    //error,conversion from 'int' to 'ObjTest4' is ambiguous
ObjTest4 obj = ObjTest4(10);

        也还可以定义从本类型到其他类型的转换。即,我们可以定义转换操作符(其他类类型名),该操作符将产生其他类型的对象。像其他转换一样,编译器将自动应用这个转换。 转换操作符是一种特殊的类成员函数,通用形式是operator type(),type 表示内置类型名、类类型名或由类型别名定义的名字。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型。

//test1.h
#include <ostream>
#include <memory>

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    ObjTest4(int ival_=0) : ival(ival_){};                      //int to ObjTest4
    ~ObjTest4(){};
    //
    ObjTest4(char cval_=0) : ival(static_cast<int>(cval_)){};   //char to ObjTest4
    ObjTest4(double dval_=0.0) : ival(static_cast<int>(dval_)){};//double to ObjTest4
    ObjTest4& operator=(const int& ival_){                      //int to ObjTest4
        ival = ival_;
        return *this;
    };                       
    ObjTest4& operator=(const double& dval_) {                  //double to ObjTest4
        ival = (static_cast<int>(dval_));
        return *this;
    }; 
    ObjTest4& operator=(const char& cval_) {                   //char to ObjTest4
        ival = (static_cast<int>(cval_));
        return *this;
    };   
    operator int() { return ival; }                             //ObjTest4 to int
    operator double() { return static_cast<double>(ival);}      //ObjTest4 to double
    operator char() { return static_cast<char>(ival);}          //ObjTest4 to char
};
//main.cpp
    ObjTest4 d5 = 10;   //int to ObjTest4,构造加赋值(默认赋值)
    d5 = 11;            //int to ObjTest4,直接赋值转换
    int x = d5;         //ObjTest4 to int,转换操作符
    ObjTest4 d6(100);   //int to ObjTest4,构造转换
    char cc = d6;       //ObjTest4 to char,转换操作符
    ObjTest4 d7(3.5);   //
    d7 = 'a';           //

        2.4 类的操作符系列-成员访问操作

        访问类的数据成员除了定义普通成员函数、友元函数或类外,通过访问操作符访问是非常不错的选择。访问操作符包含解引用(间接寻址)操作符(*)、箭头操作符(->)、下标操作符([ ])、取址操作符(&)、对象操作符(.)、对象成员指针(.*)、指针成员指针(->*),其中对象操作符(.)、对象成员指针(.*)属于内置定义,用户不能进行重载设计。

        下标 (subscript)操作符通常用来提供对其指针或数组操作数所指向的对象的访问,例如:

#include <cassert>
//
const int SIZE = 10;
class ObjVec
{
	ObjVec() 
	{
		memset(Buf,0,SIZE);
	};
    ~ObjVec(){};
    int& operator[]( const int pos )
    {
        assert(pos >= 0 && pos < SIZE);//边界判断
        return Buf[pos];
    }
    //其他实现
	int Buf[SIZE];
};

        当然也可以另类一些,用来指向成员对象:

#include <cassert>
class ObjTest5
{
    public:
    int operator[]( const int pos )
    {
        assert(pos >= 0 && pos < 3);//边界判断
        switch(pos)
        {
            case 0: return a;
            case 1: return b;
            case 2: return c;
            default: return 0;
        }
    }
    //其他实现
	int a,b,c;
};

        间接寻址操作符(*)、箭头操作符(->),一般用于指针对象操作,先智能指针等用于间接指向对象指针。对于 operator-> 来说,在对于用户定义运算符的重载决议中,内建运算符不引入任何额外函数签名:若存在作为可行函数的重载 operator&,则不采用内建的 operator->。

        间接寻址操作符的操作数必须是对象指针或函数指针,其结果就是表达式所指向的指针或函数。

#include <iostream>
class Atest
{
    public:
        void doit() {std::cout << "Atest::doit\n";};
};
class ObjTest5
{
    public:
    int& operator*()
    {
        return a;
    }
    int* operator->() { 
        return &atobj;
    };
    //其他实现
	int a,b,c;
    Atest atobj;
};
//
//
    ObjTest5 obj5;
    obj5.a = 100;
    std::cout << "*obj5 = " << *obj5 <<"\n";
    obj5->doit();
//out log
*obj5 = 100
Atest::doit

        取地址运算符,当操作数是某个对象或函数类型 T 的左值表达式时,operator& 创建并返回一个有相同 cv 限定的 T* 类型的纯右值,并指向由该操作数所代表的对象或函数。当其操作数具有不完整类型时,可以构成指针。当其操作数为非静态成员的限定名(比如 &C::member)时,其结果为 C 类中的 T 类型的成员函数指针或数据成员指针的纯右值。对于其类型带有用户定义的 operator& 的操作数,可以使用 std::addressof 来获取真正的指针。

class ObjTest5
{
    public:
    int* operator&()
    {
        return &a;
    }
    //其他实现
	int a,b,c;
};
//
    ObjTest5 obj5;
    obj5.a = 100;
    std::cout << "&obj5 = " << &obj5 <<"\n";
    std::cout << "*(&obj5) = " << *(&obj5) <<"\n";
//
&obj5 = 0x5ffca0
*(&obj5) = 100

        成员指针访问运算符,通过成员指针进行的成员访问运算符表达式的形式为,对于内建类型,表达式 E1->*E2 严格等价于 (*E1).*E2。

//左操作数 必须是指向类类型指针 T* 的表达式
//右操作数都是类型为指向T的(数据或函数)成员指针类型,或为指向T的无歧义且可访问基类 B 成员指针类型的表达式
左操作数 ->* 右操作数

        在表达式 E1.*E2 中:

  1. 当 E2 为指向数据成员的指针时, 如果 E1 为左值,则其结果为代表这个成员的左值,否则其结果为代表这个数据成员的右值 (C++11 前)亡值 (C++11 起);
  2.  当 E2 为指向成员函数的指针时,其结果为代表这个成员函数的一种特殊的纯右值,它只能用作成员函数调用运算符的左运算数,而不能用于其他目的;
  3. 当 E2 为空成员指针值时,其行为未定义;
  4. 当 E1 的动态类型并不包含 E2 所指代的成员时,其行为未定义;
  5. 当 E1 为右值而 E2 指向带有引用限定符 & 的成员函数时,程序非良构;
  6. 当 E1 为左值而 E2 指向带有引用限定符 && 的成员函数时,程序非良构。
#include <iostream>
class Atest
{
    public:
        void doit() {std::cout << "Atest::doit\n";};
};
//
    void (Atest::* pf)() = &Atest::doit;
    Atest at1;
    Atest *pat1 = &at1;
    (at1.*pf)();
    (pat1->*pf)();
//

        2.5 类的操作符系列-函数对象

        调用操作符,即operator(),该操作符可以有零或多个形参和返回值。当用户定义的类重载了函数调用运算符 operator() 时,它就成为函数对象 (FunctionObject) 类型。这种类型的对象能用于函数调用式的表达式。

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    /*explicit*/ ObjTest4(int ival_=0) : ival(ival_){};                      //int to ObjTest4
    ~ObjTest4(){};
    int operator()(){ return ival;};//调用操作符operator()
    int operator()(int val_){       //调用操作符operator()
        if(ival < val_) ival = val_;
        return ival;
    };
};
//main.cpp,对象如函数一样使用
    ObjTest4 obj_op(10);
    std::cout << "obj_op() = " << obj_op() << "\n";
    std::cout << "obj_op(100) = " << obj_op(100) << "\n";

        通过调用操作符operator()的重载实现,尽管 ObjTest4是一个对象而不是函数,我们仍然可以“调用”该对象,如同使用一个函数一样。一个类可以定义函数调用操作符的多个版本,由形参的数目或类型、返回约束加以区别。 

三、非类成员操作符-丰富类的实现

        3.1 算术操作符

        所有算术操作符都返回值,而大多数用户定义重载亦返回值,从而能以与内建版本相同的方式使用用户定义运算操作符。不过,用户定义重载中,任何类型都可以作为返回类型(包括 void)。特别是,operator<< 和 operator>> 为流插入和流抽取的重载所返回的是 T&。算术操作符支持类内定义和类外定义,通常采用类外定义。

运算符名  语法  可重载 类内定义                            类外定义
一元加    +a     是   T T::operator+() const;             T operator+(const T &a); 
一元减    -a     是   T T::operator-() const;             T operator-(const T &a); 
加法      a + b  是   T T::operator+(const T2 &b) const;  T operator+(const T &a, const T2 &b); 
减法      a - b  是   T T::operator-(const T2 &b) const;  T operator-(const T &a, const T2 &b); 
乘法      a * b  是   T T::operator*(const T2 &b) const;  T operator*(const T &a, const T2 &b); 
除法      a / b  是   T T::operator/(const T2 &b) const;  T operator/(const T &a, const T2 &b); 
模        a % b  是   T T::operator%(const T2 &b) const;  T operator%(const T &a, const T2 &b); 
逐位非    ~a     是   T T::operator~() const;             T operator~(const T &a); 
逐位与    a & b  是   T T::operator&(const T2 &b) const;  T operator&(const T &a, const T2 &b); 
逐位或    a | b  是   T T::operator|(const T2 &b) const;  T operator|(const T &a, const T2 &b); 
逐位异或  a ^ b  是   T T::operator^(const T2 &b) const;  T operator^(const T &a, const T2 &b); 
逐位左移  a << b 是   T T::operator<<(const T2 &b) const; T operator<<(const T &a, const T2 &b); 
逐位右移  a >> b 是   T T::operator>>(const T2 &b) const; T operator>>(const T &a, const T2 &b); 

        所有算术运算符计算特定算术运算的结果,并返回其结果,并不修改实参,是通常是通过拷贝左对象构造一个对象,然后该对象与右对象做计算,本质上是按需调用各成员变量的内置算术运算符完成处理,改变的是这个新建对象,返回的也是这个对象的最新态势。

        下面代码是类外定义算术操作符,并声明为这些算术操作符为友元函数:

//test1.h
#include <ostream>
#include <memory>

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    //...other
    friend std::ostream& operator<<(std::ostream&, const ObjTest4&);//io运算操作符<<
    //类外定义,友元声明
    friend ObjTest4 operator+(const ObjTest4& lhs);//一元操作符+
    friend ObjTest4 operator-(const ObjTest4& lhs);//一元操作符-
    friend ObjTest4 operator~(const ObjTest4& lhs);//一元操作符~
    friend ObjTest4 operator+(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator-(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符-
    friend ObjTest4 operator*(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符*
    friend ObjTest4 operator/(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符/
    friend ObjTest4 operator%(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符%
    friend ObjTest4 operator&(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符&
    friend ObjTest4 operator|(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符|
    friend ObjTest4 operator^(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符^
    friend ObjTest4 operator<<(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符<<
    friend ObjTest4 operator>>(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符>>
};
//test1.cpp
std::ostream& operator<<(std::ostream& os, const ObjTest4& obj)
{
    os << obj.ival;
    return os;
};

ObjTest4 operator+(const ObjTest4& lhs)
{
    ObjTest4 ret(lhs);
    ret.ival = +ret.ival;
    return ret;
}

ObjTest4 operator-(const ObjTest4& lhs)
{
    ObjTest4 ret(lhs);
    ret.ival = -ret.ival;
    return ret;
}

ObjTest4 operator~(const ObjTest4& lhs)
{
    ObjTest4 ret(lhs);
    ret.ival = ~ret.ival;
    return ret;
}
ObjTest4 operator+(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival+=rhs.ival;
    return ret;
}

ObjTest4 operator-(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival-=rhs.ival;
    return ret;
}

ObjTest4 operator*(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival*=rhs.ival;
    return ret;
}

ObjTest4 operator/(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival/=rhs.ival;
    return ret;
}

ObjTest4 operator%(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival%=rhs.ival;
    return ret;
}

ObjTest4 operator&(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival&=rhs.ival;
    return ret;
}

ObjTest4 operator|(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival|=rhs.ival;
    return ret;
}

ObjTest4 operator^(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival^=rhs.ival;
    return ret;
}

ObjTest4 operator<<(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival<<=rhs.ival;
    return ret;
}

ObjTest4 operator>>(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival>>=rhs.ival;
    return ret;
}

        若传递给算术运算符的操作数是整型或无作用域枚举类型,则在任何其他行动前(但在左值到右值转换后,若其适用),对操作数实施整型提升。若操作数之一具有数组或函数类型,则实施数组到指针和函数到指针转换。 

        需要注意成员变量有无符号、浮点数精度等对算术的影响。

        3.2 关系操作符

        关系操作符同样支持类内定义及类外定义,但一般建议采用类外定义的方式实现。由于内建运算符返回 bool,所以大多数用户定义重载也返回 bool以使用户定义运算符能以与内建运算符相同的方式使用。并在用户定义运算符重载中,任何类型都可用作返回类型(包含 void)。

运算符名 语法     可重载  作为成员函数                             作为自由(命名空间)函数
等于     a == b   是     bool T::operator ==(const T2 &b) const; bool operator ==(const T &a, const T2 &b); 
不等于   a != b   是     bool T::operator !=(const T2 &b) const; bool operator !=(const T &a, const T2 &b); 
小于     a < b    是     bool T::operator <(const T2 &b) const;  bool operator <(const T &a, const T2 &b); 
大于     a > b    是     bool T::operator >(const T2 &b) const;  bool operator >(const T &a, const T2 &b); 
小于或等于 a <= b 是     bool T::operator <=(const T2 &b) const;  bool operator <=(const T &a, const T2 &b); 
大于或等于 a >= b 是     bool T::operator >=(const T2 &b) const;  bool operator >=(const T &a, const T2 &b); 

三路比较(C++20) a <=> b 是 /*见说明*/ T::operator <=>(const T2 &b) const; /*见说明*/ operator <=>(const T &a, const T2 &b); 

        任何情况下,关系操作符结果均为 bool 纯右值。下面代码是类外定义关系操作符,该案例中,由于成员变量只有一个整型变量,利用其特性提供一个静态成员函数,为各个关系操作符提供归一化实现:

//test1.h
class ObjTest4
{
private:
    /* data */
    int ival;
public:
    static int cmp_Obj(const ObjTest4 &obj1, const ObjTest4 &obj2){
            return obj1.ival - obj2.ival;
    };
};

inline bool operator==(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) == 0; }
inline bool operator!=(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) != 0; }
inline bool operator>=(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) >= 0; }
inline bool operator<=(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) <= 0; }
inline bool operator>(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) > 0; }
inline bool operator<(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) < 0; }

         若是算术比较运算符,两个操作数具有算术或(有作用域或无作用域)枚举类型,则遵循算术运算符的规则,对两个操作数实施一般算术比较。可以实现两个对象类型不一样的比较,但需要做类型转换,转换之后进行值的比较。

        若是指针比较运算符,比较运算符能用于比较二个指针。只有相等性运算符(operator== 与 operator!=)能用于比较下列指针对。

        标准库的算法(如 std::sort)和容器(如 std::set)在默认情况下期待 operator< 对于用户提供的类型有定义,并期待它实现严格弱序(从而满足比较 (Compare) 要求)。通常不必一并提供六路比较关系操作符,典型地,一旦提供了 operator<,其他关系运算符>、<=、>=就都能通过 operator< 来实现:

inline bool operator< (const X& lhs, const X& rhs){ /* 做实际比较 */ }
inline bool operator> (const X& lhs, const X& rhs){ return rhs < lhs; }
inline bool operator<=(const X& lhs, const X& rhs){ return !(lhs > rhs); }
inline bool operator>=(const X& lhs, const X& rhs){ return !(lhs < rhs); }

        同样地,不相等运算符典型地通过 operator== 来实现:

inline bool operator==(const X& lhs, const X& rhs){ /* 做实际比较 */ }
inline bool operator!=(const X& lhs, const X& rhs){ return !(lhs == rhs); }

        当提供了三路比较(即返回与0的比较。或定义了三路比较运算符 operator<=>(c++20起))时,就是如前面示例代码展示的那样,所有六个双路比较运算符都能通过它表达 :

inline bool operator==(const X& lhs, const X& rhs){ return cmp(lhs,rhs) == 0; }
inline bool operator!=(const X& lhs, const X& rhs){ return cmp(lhs,rhs) != 0; }
inline bool operator< (const X& lhs, const X& rhs){ return cmp(lhs,rhs) <  0; }
inline bool operator> (const X& lhs, const X& rhs){ return cmp(lhs,rhs) >  0; }
inline bool operator<=(const X& lhs, const X& rhs){ return cmp(lhs,rhs) <= 0; }
inline bool operator>=(const X& lhs, const X& rhs){ return cmp(lhs,rhs) >= 0; }

         如果定义了 operator== ,那么编译器会自动生成不等运算符。类似地,如果定义了三路比较运算符 operator<=>,那么编译器会自动生成四个关系运算符。如果定义 operator<=> 为预置,那么编译器会生成 operator== 和 operator<=> :

struct X
{
    int         id;
    double      val;
    std::string desc;
    auto operator<=>(const X&) = default;
};

        这样定义后,就能用 ==、!=、<、<=、> 和 >= 比较 X 类实例了。

        3.3 流提取与插入操作符<<、>>

        接受 std::istream& 或 std::ostream& 作为左侧实参的 operator>> 与 operator<< 的重载,被称为插入与提取运算符。因为它们接收用户定义类型为右实参(a@b 中的 b),所以它们必须以非成员实现。

#include <ostream>
#include <istream>

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    friend std::ostream& operator<<(std::ostream&, const ObjTest4&);//io运算操作符<<
    friend std::istream& operator>>(std::istream& in, ObjTest4&);//io运算操作符>>
};

std::ostream& operator<<(std::ostream& os, const ObjTest4& obj)
{
    os << obj.ival;
    return os;
};

std::istream& operator>>(std::istream& in, ObjTest4& obj)
{
    //最好安全检查
    in >> obj.ival;  //取出数据写入val
    return in;
};

四、附录,本博文涉及完整源码

        编译指令:g++ main.cpp test*.cpp -o test.exe -std=c++11

        test1.h

#ifndef _TEST_1_H_
#define _TEST_1_H_

class obj
{
    public:
        int id;
        double val;
};
// #include <vector>
// #include <string>

class ObjTest1
{
private:
    /* data */
    int id;
    double val;
    // std::vector<int> vec;
    // std::string str;
public:
    ObjTest1() = default;
    // ~ObjTest1() = default;
    virtual ~ObjTest1() = default;
    ObjTest1(const ObjTest1&) = default;
    ObjTest1(ObjTest1&) = default;
    ObjTest1& operator=(const ObjTest1&) = default;
    ObjTest1& operator=(ObjTest1&) = default;
    //
    void set_id(int id_){ id = id_;};
    int get_id(){ return id;};
    void set_val(double val_){ val = val_;};
    double get_val(){ return val;};
};

#include <string>
#include <cstring>
#include <utility>

class ObjTest2
{
private:
    /* data */
    char *desc; //动态内存句柄
public:
    ObjTest2(const char* desc_ = ""){
        cpy(desc_);
    };
    ~ObjTest2(){
        delete[] desc;
    };
    ObjTest2(const ObjTest2& other){    //复制拷贝构造
        cpy(other.get_desc());
    };
    ObjTest2(const ObjTest2&& other) noexcept //移动拷贝构造
    {
        if(this != &other) { // 自赋值时无操作(delete[]/size=0 也可以)
            char* ptr = other.get_desc();
            std::swap(desc,ptr);    //c++11
            ptr = nullptr;
        }
    };
    ObjTest2& operator=(const ObjTest2& other){ //复制赋值
        if(this != &other) {
            delete[] desc;  // 解分配
            cpy(other.get_desc());
        }
        return *this;
    };
    ObjTest2& operator=(const ObjTest2&& other) noexcept { //移动赋值
        char* ptr = other.get_desc();
        std::swap(desc,ptr);//交换两个值,ptr离开作用域被释放
        ptr = nullptr;
        return *this;
    };
    //
    void set_desc(const char* desc_){ 
        cpy(desc_);
    };
    char* get_desc() const{ 
        return desc;
    };
private:
    // ObjTest2(const ObjTest2& other) = delete;            //复制构造及赋值应该同时禁止
    // ObjTest2& operator=(const ObjTest2& other) = delete; //
    void cpy(const char* desc_ )
    {
        if(desc_){
            std::size_t n = std::strlen(desc_) + 1;
            desc = new char[n];
            std::memcpy(desc, desc_, n); // 复制
        }
    }
};

class ObjTest3
{
private:
    /* data */
    double val;
public:
    ObjTest3(double val_=0.0) : val(val_){};    //由于禁止了拷贝构造、赋值等,默认构造可能成为弃置,需要显式定义
    ~ObjTest3(){};
private:
    ObjTest3(const ObjTest3& ) = delete;
    ObjTest3(ObjTest3& ) = delete;
    ObjTest3& operator=(const ObjTest3& ) = delete;
    ObjTest3& operator=(ObjTest3& ) = delete;
    ObjTest3& operator=(const ObjTest3&& ) = delete;
    ObjTest3& operator=(ObjTest3&& ) = delete;
};

#include <ostream>
#include <istream>
#include <memory>

class ObjTest4
{
private:
    /* data */
    int ival;
public:
    /*explicit*/ ObjTest4(int ival_=0) : ival(ival_){};                      //int to ObjTest4
    ~ObjTest4(){};
    int operator()(){ return ival;};
    int operator()(int val_){ 
        if(ival < val_) ival = val_;
        return ival;
    };
    //void set_ival(int ival_=0) : ival(ival_){};     //error
    //
    ObjTest4(char cval_=0) : ival(static_cast<int>(cval_)){};   //char to ObjTest4
    ObjTest4(double dval_=0.0) : ival(static_cast<int>(dval_)){};//double to ObjTest4
    ObjTest4& operator=(const int& ival_){                      //int to ObjTest4
        ival = ival_;
        return *this;
    };                       
    ObjTest4& operator=(const double& dval_) {                  //double to ObjTest4
        ival = (static_cast<int>(dval_));
        return *this;
    }; 
    ObjTest4& operator=(const char& cval_) {                   //char to ObjTest4
        ival = (static_cast<int>(cval_));
        return *this;
    };   
    operator int() { return ival; }                             //ObjTest4 to int
    operator double() { return static_cast<double>(ival);}      //ObjTest4 to double
    operator char() { return static_cast<char>(ival);}          //ObjTest4 to char
    //
    ObjTest4& operator+=(const ObjTest4& rhs);//一元操作符+=
    ObjTest4& operator-=(const ObjTest4& rhs);//一元操作符-=
    ObjTest4& operator*=(const ObjTest4& rhs);//一元操作符*=
    ObjTest4& operator/=(const ObjTest4& rhs);//一元操作符/=
    ObjTest4& operator%=(const ObjTest4& rhs);//一元操作符%=
    ObjTest4& operator&=(const ObjTest4& rhs);//一元操作符&=
    ObjTest4& operator|=(const ObjTest4& rhs);//一元操作符|=
    ObjTest4& operator^=(const ObjTest4& rhs);//一元操作符^=
    ObjTest4& operator<<=(const ObjTest4& rhs);//一元操作符<<=
    ObjTest4& operator>>=(const ObjTest4& rhs);//一元操作符>>=
    ObjTest4& operator<<=(const int& rhs);//一元操作符<<=,重载
    ObjTest4& operator>>=(const int& rhs);//一元操作符>>=,重载
    //
    ObjTest4 operator++();//一元操作符++,前缀式操作符
    ObjTest4 operator--();//一元操作符--,前缀式操作符
    ObjTest4 operator++(int rhs);//一元操作符++,后缀式操作符
    ObjTest4 operator--(int rhs);//一元操作符--,后缀式操作符
    //
    friend std::ostream& operator<<(std::ostream&, const ObjTest4&);//io运算操作符<<
    friend std::istream& operator>>(std::istream& in, ObjTest4&);//io运算操作符>>
    //
    friend ObjTest4 operator+(const ObjTest4& lhs);//一元操作符+
    friend ObjTest4 operator-(const ObjTest4& lhs);//一元操作符-
    friend ObjTest4 operator~(const ObjTest4& lhs);//一元操作符-
    friend ObjTest4 operator+(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator-(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator*(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator/(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator%(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator&(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator|(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator^(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator<<(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    friend ObjTest4 operator>>(const ObjTest4& lhs,const ObjTest4& rhs);//二元操作符+
    //
    static int cmp_Obj(const ObjTest4 &obj1, const ObjTest4 &obj2){
            return obj1.ival - obj2.ival;
    };
};

inline bool operator==(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) == 0; }
inline bool operator!=(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) != 0; }
inline bool operator>=(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) >= 0; }
inline bool operator<=(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) <= 0; }
inline bool operator>(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) > 0; }
inline bool operator<(const ObjTest4& obj1, const ObjTest4& obj2) { return ObjTest4::cmp_Obj(obj1, obj2) < 0; }

#include <cassert>
//
const int SIZE = 10;
class ObjVec
{
	ObjVec() 
	{
		memset(Buf,0,SIZE);
	};
    ~ObjVec()
	{
	};
    int& operator[]( const int pos )
    {
        assert(pos >= 0 && pos < SIZE);//边界判断
        return Buf[pos];
    }
    //其他实现
	int Buf[SIZE];
};
//
#include <iostream>
class Atest
{
    public:
        void doit() {std::cout << "Atest::doit\n";};
};

class ObjTest5
{
    public:
    int operator[]( const int pos )
    {
        assert(pos >= 0 && pos < 3);//边界判断
        switch(pos)
        {
            case 0: return a;
            case 1: return b;
            case 2: return c;
            default: return 0;
        }
    }
    int& operator*() {
        return a;
    }
    Atest* operator->() { 
        return &atobj;
    };
    int* operator&() {
        return &a;
    }
    //其他实现
	int a,b,c;
    Atest atobj;
};

#endif //_TEST_1_H_

        test1.cpp

#include "test1.h"

ObjTest4& ObjTest4::operator+=(const ObjTest4& rhs)
{
    ival += rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator-=(const ObjTest4& rhs)
{
    ival -= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator*=(const ObjTest4& rhs)
{
    ival *= rhs.ival;
    return *this;
}
ObjTest4& ObjTest4::operator/=(const ObjTest4& rhs)
{
    ival /= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator%=(const ObjTest4& rhs)
{
    ival %= rhs.ival;
    return *this;
}
ObjTest4& ObjTest4::operator&=(const ObjTest4& rhs)
{
    ival &= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator|=(const ObjTest4& rhs)
{
    ival |= rhs.ival;
    return *this;
}
ObjTest4& ObjTest4::operator^=(const ObjTest4& rhs)
{
    ival ^= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator<<=(const ObjTest4& rhs)
{
    ival <<= rhs.ival;
    return *this;
}
ObjTest4& ObjTest4::operator>>=(const ObjTest4& rhs)
{
    ival >>= rhs.ival;
    return *this;
}

ObjTest4& ObjTest4::operator<<=(const int& rhs)
{
    ival <<= rhs;
    return *this;
}
ObjTest4& ObjTest4::operator>>=(const int& rhs)
{
    ival >>= rhs;
    return *this;
}

ObjTest4 ObjTest4::operator++()
{
    ++ival;
    return *this;
}
ObjTest4 ObjTest4::operator--()
{
    --ival;
    return *this;
}
ObjTest4 ObjTest4::operator++(int rhs)
{
    ObjTest4 ret(*this);
    ++*this;
    return ret;
}
ObjTest4 ObjTest4::operator--(int rhs)
{
    ObjTest4 ret(*this);
    --*this;
    return ret;
}

std::ostream& operator<<(std::ostream& os, const ObjTest4& obj)
{
    os << obj.ival;
    return os;
};

std::istream& operator>>(std::istream& in, ObjTest4& obj)
{
    //最好安全检查
    in >> obj.ival;  //取出数据写入val
    return in;
}

ObjTest4 operator+(const ObjTest4& lhs)
{
    ObjTest4 ret(lhs);
    ret.ival = +ret.ival;
    return ret;
}

ObjTest4 operator-(const ObjTest4& lhs)
{
    ObjTest4 ret(lhs);
    ret.ival = -ret.ival;
    return ret;
}

ObjTest4 operator~(const ObjTest4& lhs)
{
    ObjTest4 ret(lhs);
    ret.ival = ~ret.ival;
    return ret;
}
ObjTest4 operator+(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival+=rhs.ival;
    return ret;
}

ObjTest4 operator-(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival-=rhs.ival;
    return ret;
}

ObjTest4 operator*(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival*=rhs.ival;
    return ret;
}

ObjTest4 operator/(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival/=rhs.ival;
    return ret;
}

ObjTest4 operator%(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival%=rhs.ival;
    return ret;
}

ObjTest4 operator&(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival&=rhs.ival;
    return ret;
}

ObjTest4 operator|(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival|=rhs.ival;
    return ret;
}

ObjTest4 operator^(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival^=rhs.ival;
    return ret;
}

ObjTest4 operator<<(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival<<=rhs.ival;
    return ret;
}

ObjTest4 operator>>(const ObjTest4& lhs,const ObjTest4& rhs)
{
    ObjTest4 ret(lhs);
    ret.ival>>=rhs.ival;
    return ret;
}

        main.cpp

#include "test1.h"
#include <iostream>

int main(int argc, char* argv[])
{
    obj o1 = {1,1.0};
    obj o2 = o1, o3;
    o3 = o1;
    //
    ObjTest1 a1{};  //默认构造
    a1.set_id(10);
    a1.set_val(10.0);
    ObjTest1 a2 = a1;//拷贝构造
    std::cout << "a2.get_id() = " << a2.get_id() <<","
        << "a2.get_val() = " << a2.get_val() <<"\n";
    ObjTest1 a3 = {};//拷贝构造
    a3 = a1;        //赋值赋值=
    std::cout << "a3.get_id() = " << a3.get_id() <<","
        << "a3.get_val() = " << a3.get_val() <<"\n";
    ObjTest1 a4 = ObjTest1{};//拷贝构造
    a4 = std::move(a1); //移动赋值
    std::cout << "a4.get_id() = " << a4.get_id() <<","
        << "a4.get_val() = " << a4.get_val() <<"\n";
    //
    ObjTest2 b1("hello!");  //构造函数
    ObjTest2 b2 = b1;       //拷贝构造
    std::cout << "b2.get_desc() = " << b2.get_desc() <<"\n";
    ObjTest2  b3 = ObjTest2{};
    b3 = b1;                //赋值
    std::cout << "b3.get_desc() = " << b3.get_desc() <<"\n";
    //
    ObjTest2  b4 = std::move(b1);//移动构造
    std::cout << "b1.get_desc() = " << b1.get_desc() <<"\n";
    std::cout << "b2.get_desc() = " << b2.get_desc() <<"\n";
    ObjTest2  b5;
    b5 = std::move(b1);
    std::cout << "b1.get_desc() = " << b1.get_desc() <<"\n";
    std::cout << "b5.get_desc() = " << b5.get_desc() <<"\n";
    b1.set_desc("world!");
    std::cout << "b1.get_desc() = " << b1.get_desc() <<"\n";
    std::cout << "b2.get_desc() = " << b2.get_desc() <<"\n";
    std::cout << "b5.get_desc() = " << b5.get_desc() <<"\n";
    //
    ObjTest3 c1(10);
    ObjTest3 c2{1};
    // ObjTest3 c3(c1);    //error
    // ObjTest3 c4{c1};    //error
    // c2 = c1;            //error
    // c4 = std::move(c1); //error
    //
    ObjTest4 d1(10),d2(2),d3(3);
    std::cout << "d1 = " << d1 <<"\n";
    d1 += d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 += d3;
    std::cout << "d1 = " << d1 <<"\n";
    d1 *= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 /= d3;
    std::cout << "d1 = " << d1 <<"\n";
    d1 %= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 &= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 |= d3;
    std::cout << "d1 = " << d1 <<"\n";
    d1 ^= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 <<= d3;
    std::cout << "d1 = " << d1 <<"\n";
    d1 >>= d2;
    std::cout << "d1 = " << d1 <<"\n";
    d1 <<= 1;
    std::cout << "d1 = " << d1 <<"\n";
    d1 >>= 1;
    std::cout << "d1 = " << d1 <<"\n";
    //
    ObjTest4 d4(10);
    std::cout << "d4 = " << d4 <<"\n";
    std::cout << "d4 = " << d4++ <<"\n";
    std::cout << "d4 = " << d4-- <<"\n";
    std::cout << "d4 = " << ++d4 <<"\n";
    std::cout << "d4 = " << --d4 <<"\n";
    //
    ObjTest4 d5 = 10;   //int to ObjTest4,构造加赋值(默认赋值)
    d5 = 11;            //int to ObjTest4,直接赋值转换
    int x = d5;         //ObjTest4 to int,转换操作符
    ObjTest4 d6(100);   //int to ObjTest4,构造转换
    char cc = d6;       //ObjTest4 to char,转换操作符
    ObjTest4 d7(3.5);   //
    d7 = 'a';           //
    //
    ObjTest4 obj_op(10);
    std::cout << "obj_op() = " << obj_op() << "\n";
    std::cout << "obj_op(100) = " << obj_op(100) << "\n";
    //
    ObjTest5 obj5;
    obj5.a = 100;
    std::cout << "*obj5 = " << *obj5 <<"\n";
    obj5->doit();
    std::cout << "&obj5 = " << &obj5 <<"\n";
    std::cout << "*(&obj5) = " << *(&obj5) <<"\n";
    //
    void (Atest::* pf)() = &Atest::doit;
    Atest at1;
    Atest *pat1 = &at1;
    (at1.*pf)();
    (pat1->*pf)();
    getchar();
    return 0;
};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

py_free-物联智能

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值