美文网首页
18/19章 操作符重载/特殊运算符

18/19章 操作符重载/特殊运算符

作者: my_passion | 来源:发表于2022-06-24 17:38 被阅读0次

第18章 操作符重载

像 内置类型对象 一样便捷地操作 类类型对象

Note

    (1) 何时用 非成员函数?
    
        必须用: 第1操作数 为 `内置类型`
        
        应该用:
            不需要直接访问 类的表示部分 的函数
            `对称` 的 操作符(函数) 
            
    (2) 必须为 `non-static 成员函数` 的 操作符     
    
        ————————————————————
        赋值      =
        下标      []
        函数调用    ()
        箭头      ->
        ————————————————————
    
    (3) `临时量` 可作 `成员操作符` 的 `左运算对象`
        
        complex x {3, 4};
        complex z { sqrt(x) += {1, 2} }; //ok: tmp = sqrt(x), tmp += {1, 2}
    
    (4) `返回值类型 不参与 重载解析`
    
    (5) helper 函数
    
        类内 `private 成员函数` 
        类外 `非成员 函数` 
        
    (6) 用 namespace 包含 `helper 函数` & 关联类
    
        非成员 helper 函数: `不需要 直接访问` 类的表示 
        
        1) 其声明 与 类声明 放  `同一 头文件` 
        2) 与 类 放 `同一 namespace`
        
    (7) 用户无权自定义 的 操作符 
        
        ::  作用域解析 
        .   成员选择
        .*  指向成员的指针
    
    (8) 逻辑 与/或 (&& ||) 逗号 (,)
        
        默认含义 包含 顺序信息, 该规则 并不适用于 用户自定义版本     
    
    (9) 隐式 / 显式调用 
        
        complex d = a + b;
        complex d = a.operator+(b);

    (10) 把 简单成员操作 简化为 `非成员函数` (不增加性能开销时) 
            
        inline bool operator==(complex a, complex b)
        {
            return a.real() == b.real() && a.imag() == b.imag();
        }

18.1 操作符函数

成员函数&非成员函数 version 均定义 => 由 重载解析规则 决定选哪个

1 二元/一元 操作符

(1) 二元 操作符 @

    ————————————————————
        a@b
    ————————————————————
        a.operator@(b)
    ————————————————————
        operator@(a, b)
    ————————————————————

(2) 一元 操作符

    前置 @
        
        ————————————————————
            @a
        ————————————————————
            a.operator@()
        ————————————————————
            operator@(a)
        ————————————————————
    
    后置 @ : 相比前置版本, compiler 只加1各 dummy 参数 以区分
        
        ————————————————————
            a@
        ————————————————————
            a.operator@(int)
        ————————————————————
            operator@(a, int)
        ————————————————————

2 3种具有 预置含义 的 成员操作符: compiler 为 用户自定义类型 隐含生成, 与 内置类型 操作符等价含义

    = 赋值 
    & 取地址 
    , 顺序 

3 3种操作符

    (1) new / delete
    (2) 成员函数 
    (3) 非成员函数: 至少含 1 个 用户自定义类型 

4 传递对象: 传参 & 返回

(1) `传参` 
    
    1) 值传递: 小对象 -> 值传递 -> 性能最好 
            
    2) 引用传递 
    
        大对象
            引用传递 (指 左值引用)
                |
                |   不想改变 对象内容 
                |/
            const 引用(传递)
                |
                |   不想 copy, 只想 move 
                |/
            右值引用(传递)
            
(2) `返回` 
    
    1) `internal 新建对象: 值返回 
            大对象 -> 应该定义 move 操作: 形似传值,实则 `move` 
        
    2) `返回 已有(参数)对象: 引用返回 

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

5 非成员操作符: 必然在某 namespace(如 全局 namespace ::) 中

二元运算符 @ 解析过程: x @ y // x/y 类型为 X/Y
 
    find scope 和 order: 见 12.3.3&4      
        1) X 或 X 的基类 
            X 中有 operator@, 就不用去 X 的基类找了(函数重载不会跨越作用域: 20.3.5); 
            X 中没有才会去 X 的 基类中找
        2) x @ y 的 context
        3) X 所在 namespace 
        4) Y 所在 namespace

18.2 复数类型

1 成员/非成员 操作符

(1) non-mem func 直接 操作对象不是好做法 -> 更好的设计: 加中间层 memFunc, non-mem func 调 memFunc, 让 memFunc 直接操作对象

    operator+(a, b) 调 a.operator+=(b)
        
    f(a, b)         调 a.f(b)
        
    => 复合赋值运算符(+= 等) 比 相应的非复合赋值运算符 (+等) 简单 
        
        + 涉及 3 个对象 (含 结果对象/临时对象 )
        
        += 只涉及 2 个对象, `不` 需要处理 `临时对象` 
            1] 运行效率提升
            2] 编译器 易 inline 它
                
    class complex
    {
        double re, im;
    public:
        complex& operator+=(complex); // 直接访问 类的 成员数据
    };
    
    inline complex& complex::operator+=(complex rhs)
    {
        re += rhs.re;
        im += rhs.im;
        return *this;
    }
    
    complex operator+(complex a, complex b); 
    {
        return a += b; // 通过 += 访问 类的 成员数据
    }

2 单参数 Ctor 类型转换: 解决 混合模式运算 组合爆炸问题

    运算符 参数个数多&类型多 -> 组合过多-> 易错
    各种组合共性: 几乎都有 `paraType 到 类类型` 的 `类型转换`

    解决:
        1] `类型转换 的 通用版本`
        2] 辅以 `必要变形` 
    
    complex operator+(complex, complex); 
    complex operator+(complex, double);
    complex operator+(double, complex);
            |
            |   类型转换
            |/
    complex operator+(complex, complex);        
    complex::complex(double d): re{d}, im{0} {}

3 字面值常量(2种实现)

(1) constexpr Ctor + 编译期计算(如 能 inline) -> 构造的对象为 字面值常量

(2) 用户自定义 字面值常量, 如 虚部 字面值常量: 把 i 定义成 后缀

    // === 1
    class complex
    {
    public:
        constexpr complex(double r = 0, double i = 0)
            : re{r}, im{i} { }
    };

    // === 2
    constexpr complex operator "" i(long double d)  
    {
        return {0, d};
    }
    
    complex f(double d)
    {
        complex x {0, 2.1};
        return x + 12e3i;
    }
    // === 1
    class complex
    {
    public:
        constexpr complex(double r = 0, double i = 0)
            : re{r}, im{i} { }
    };

    // === 2
    constexpr complex operator "" i(long double d)  
    {
        return {0, d};
    }
    
    complex f(double d)
    {
        complex x {0, 2.1};
        return x + 12e3i;
    }

18.3 类型转换

2种方式 * 显式/隐式 => 4 种组合

    —————————————————
    单参数 Ctor            
    
    类型转换运算符 
    —————————————————
    
    ——————————————————————————————————————————————————————————————————
                        只在何时才会执行 
    ——————————————————————————————————————————————————————————————————
    explicit        `直接初始化(未使用 =)` 时
        
    隐式          不引起 二义性时 
    ——————————————————————————————————————————————————————————————————
    
    
    单参数 Ctor 类型转换 <---目标相反---> 类型转换运算符`
    
    X::X(T t)                           X::operator T()
    
    otherType -> 类类型                    类类型 -> otherType

1 类型转换运算符 & cin

成员函数 X::operator T() 定义了 从 类类型 X 到 otherType T 的 类型转换, returnType 在 运算符名字中已经出现, 不再出现在 通常的 retrunType 位置

    while (cin >> x)
        cout << x;
        
    cin >> x 返回 istream& -> 隐式转化为 `表示 cin 状态 的 值` 

2 explicit 类型转换运算符 & 智能指针

    template <typename T, typename D = default_delete<T> >
    class unique_ptr
    {
    public:
        // ...
        explicit operator bool() const noexcept; 
        // ...
        
    };
    
    void user(unique_ptr<Record> p, unique_ptr<int> q ) 
    {
        if(!p)    // <=> if ( !( bool tmp{p} ) )
            // ...
            
        // explicit =>
        bool b = p;     // error
        int x = p + q;  // error
    }

3 二义性 & implicitly 类型转换 不跨层

    // (1) 二义性
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    |         |                                 |
    |         |/                                |
    |   class X { /* ... */ X(int); X(const char*); };
    |                       |\
    |                       |  (1) int 可隐转为 X 或 Y => 二义性 
    |                       |/
    |   class Y { /* ... */ Y(int); };
    |   
    |   class Z { /* ... */ Z(X); };
    |                         |\
    |_ _ _ _ _ _ _ _ _ _ _ _ _| 
    
     const char* -> X -> Z
        
 
    // (2) implicitly 类型转换 不跨层 
    X f(X);
    Y f(Y);
    
    Z g(Z);
    
    void user()
    {
        f(1);       // error, 二义性: f( X(1) ) 还是  f( Y(1) ) 
        
        g("hello"); // error, 需要用到 `跨层` 类型转换 => `不支持 隐式` 
    }

第19章 特殊运算符

Note

    (1) (重载) 操作符 是 `资源管理类(容器 / 智能指针 / 迭代器)` 的 设计关键

19.1 特殊运算符

1 下标 []: paraType(即下标) 可以是 任意类型

    // 关联数组: map / unordered_map 继承 关联数组 的 思想 + 高级技术 
    struct Assoc
    {
        vector< pair<string, int> > vec; // vector 元素数 {名字, 值} 对
        
        // 通常提供 const + non-const 版本
        int& operator[](const string&);
        const int& operator[](const string&) const;
    };              
    
    // 查找 s: 找到 -> 返回 s 的引用; 否则, 创建 新 pair{s, 0} -> 返回 新 pair 的 s.second 引用 
    int& operator[](const string&)
    {
        for(auto x: vec)
            if(s == x.first)
                return x.second;
        
        vec.push_back( {s, 0} );
        return vec.back().second;
    }                           

2 函数调用 (): 为 函数对象 提供 函数调用 语法

    //模板 for_each: 对 `第3参数/操作对象` 调 函数调用操作符 ()
    template<typename Iter, typename F>
    F for_each(Iter b, Iter e, F f)
    {
        while(b != e) 
            f(*b++); // f(...) 即调 函数调用操作符 () = operator()
        return f;
    }

3 解引用 ->: 箭头运算符

智能指针 对象 sp(类型) 到其 internalPtr p.operator->() (类型) 的 类型转换, 与 sp 所指成员 m 无关: 箭头运算符 operator->() 是 一元后置运算符

隐式调用: 需要带 1个 成员名; 显式调用: 箭头 -> 之后可以只带 ()

    template<typename T>
    class Ptr
    {
        T* p;
    public:
        T* operator->()     { return p; };    // 解引用 以访问 `成员` 
        T& operator*()      { return *p; };   // ...           `整个对象` // note: * 也是 解引用运算符 
        T& operator[](int i){ return p[i]; }; // ...           `元素` 
        // ...
    };  
    
    void f(Ptr sp)
    {
        // (1) 隐式调用: 需要带 1个 `成员名`
        sp->m = 7; // (sp.operator->() )->m = 7;

        // (2) 显式调用: 箭头 -> 之后可以只带 ()
        T* q1 = sp.operator->(); // OK
        
        T* q1 = sp->; // error
    }

4 递 增/减 ++ --: 智能指针 的移动

前置: 递增, 返回 当前对象的引用(左值); 后置: 递增, 返回旧对象(右值)

    // 前置
    template<typename T>
    Ptr& Ptr<T>::operator++()
    {
        // 检查 ptr + 1 是否有效 
        return *++ptr;
    }
    
    // 后置 
    template<typename T>
    Ptr Ptr<T>::operator++(int) // dummyParaType
    {
        // 检查 ptr + 1 是否有效 
        Ptr<T> old { /* ... */ }; // 临时对象 record oldObj
        ++ptr;                    // 调 前置 
        return old;
    }

5 分配/释放 new/delete: 成员函数 operator new() / operator delete() 是 隐式的 static 成员

6 用户自定义 字面值常量: 如 虚数

19.2 字符串类 String

与 std::string 不同

1 值语义: 赋值 后, 2 个 String 对象 独立

2 move

3 短字符串优化: 短字符 String 分配在 stack(char 数组), 而不是 自由存储

    ch 和 space 不会同时使用: 匿名 union`             
    
    高效 
        尤其用于 `多线程`
        
    经验表明, 短字符串 居多
    
    class String
    {
        char* ptr;
        int sz;         // 字符数
        union {
            int space; // 已分配但 unused free space 大小
            char ch[short_max + 1]; 
        }
    };
    
    1) 默认 Ctor
    
        String::String()
            : sz{0}, ptr{ch}
        {
            ch[0] = 0;
        }   
            
    2) Ctor 参数为 C 风格字符串: `不用判断 是否为 短字符串`, 就将 space 初始化为 0 ?
            
        长字符串 时, space 才真正有效, 且 space = 0
        else,        space{0} <=> ch[0] = 0
        
        String::String(const char* p)
            : sz{strlen(p) }, 
              ptr{ (sz <= short_max) ? ch : new char[sz + 1] },
              space{0}
        {
            strcpy(ptr, p);
        }

19.3 友元

1 引入

(1) non-static / non-friend 成员函数 
    
    3 层 含义 
        
    1) 有权访问 类的 private 成员   
    2) 位于 类 的 scope 内   
    3) 必须用 `含 this 指针 的 对象` 调用

        |
        |   限制只有 `前 2 层含义` 
        |/
     static 成员函数 
        |
        |   限制只有 `第 1 层含义` 
        |/
     友元函数:  friend 非成员函数 
        
        1) 显式声明 在 `dstClass 内`
        2) 可以是 `another 类的成员函数`
    
    class List_iterator
    {
        // ...
        int* next();
    };
    
    class List
    {
        friend int* List_iterator::next();
        // ...
    };
        |
        |   想 `class1 所有成员函数` 成为 class2 的 友元
        |/
    `友元类`
        class List
        {
            friend class List_iterator;
            // ...
        };

(2) 模板参数 可为 friend 

    template <typename T>
    class X
    {
        friend T;
        friend class T; // 多余的 "class"
    };

2 发现 友员

(1) class 内 friend 声明友元性延伸

[1] 直接外层 非类(namespace) scope : 该 scope 在 friend 延后定义 了友元

[2] 间接/第2外层 非类(namespace) scope: 该 scope 在 friend 提前声明 了友元

Note: 测试表明 [2] 对 友元函数 不成立 -> 待查 ???

    class C1;  // 间接外层 提前声明 
    void f1(); //

    namespace N
    {
        class C
        {
            int x;
        public:
            friend class C1;  // 
            friend void f1(); // 

            friend class C3;  // 
            friend void f3(); // 
        };

        class C3 // 直接外层 延后定义
        {
        public:
            void f()
            {
                C c;
                c.x = 1;
            }
        };

        void f3()
        {
            C c;
            c.x = 1;
        }
    }

    class C1
    {
    public:
        void f()
        {
            N::C c;
            c.x = 1;
        }
    };

    void f1()
    {
        N::C c;
        c.x = 1; // compile error: "N::C::x": 无法访问 private 成员(在“N::C”类中声明)
    }


    int main()
    {
        C1 c1;
        c1.f();

        f1();

        N::C3 c3;
        c3.f();

        N::f3();
    }

(2) 友元函数: paraType 常为 const dstClass&, 使自己可通过 ADL 被 found

    class X
    {
        friend void f();         // 没用 
        friend void h(const X&); // 可 通过 参数找到 
    };
    
    void g(const X& x)
    {
        f();  // scope 内 找不到 f()
        
        h(x); // X 的 友元 h()
    }     

3 友员 与 成员

(1) 应该选 成员、static 成员、友元?
    
    本质问题是: "真的应该具有 访问权限 吗 ?"
            
    1) 需 `直接访问 类的表示部分` 的 函数,才应该作 `成员` 
    
    2) 需 `修改 对象状态`
        
        [1] 成员函数 
        
        [2] 接受 X& / X* 参数 的 `非成员函数`  
        
    3) `二元运算符` 作用于 基本类型时, 
        
        `常需 访问 运算对象 所属类的表示部分`
                
            => 作 `友元函数` 

相关文章

  • Kotlin操作符重载

    操作符重载 一元操作符 示例: 二元操作符 示例: 没有用于位运算的特殊运算符 shl — 带符号左移 shr —...

  • C++面向对象-运算符重载

    运算符重载 运算符重载又称为操作符重载,可以为运算符增加一些新的功能,全局函数和成员函数都支持运算符重载,我们通过...

  • 运算符重载(Operator Overloading)

    运算符重载(Operator Overloading) 操作符重载的要点 操作符的通用语法 双目操作符:<左操作数...

  • C++基础-(重载)

    C++基础 重载 哪些运算符可以被重载:::,.,->,*,?:不能被重载 重载操作符的标志(operator) ...

  • 13. C++基本运算符重载

    基本上我们进行运算符重载时有两种形式,类内的运算符重载和顶层函数位置的运算符重载。 操作符重载指的是将C++提供的...

  • 1.2.20_C++ 下标运算符 [] 重载

    C++ 重载运算符和重载函数 下标操作符 [] 通常用于访问数组元素。重载该运算符用于增强操作 C++ 数组的功能...

  • C++中的操作符重载

    操作符重载 C++中的重载能够扩展操作符的功能 操作符的重载以函数的方式进行 本质:用特殊形式的函数扩展操作符的功...

  • c++学习笔记 三(geekband)

    类型转换运算符conversion operator 类型转换是操作符重载的一种形式,是类的特殊成员,上文是一个分...

  • 18/19章 操作符重载/特殊运算符

    第18章 操作符重载 像 内置类型对象 一样便捷地操作 类类型对象 Note 18.1 操作符函数 成员函数&非成...

  • C++运算符重载形式--成员函数or友元函数?

    1.C++操作符重载形式---成员函数or友元函数 1.对运算符重载,需要坚持四项基本原则:不可臆造运算符;运算符...

网友评论

      本文标题:18/19章 操作符重载/特殊运算符

      本文链接:https://www.haomeiwen.com/subject/vcgvvrtx.html