1. 程式人生 > >c++筆記05---雙目運算子,單目操作符,下標操作符,函式操作符,解引用

c++筆記05---雙目運算子,單目操作符,下標操作符,函式操作符,解引用

1.    雙目複合運算子 1
    += / -= / *=
    左值,左運算元的引用;
    左變右不變
    (a += b) = c;
    這裡 a 得到 c 的值,b 沒起作用;
    下面這個例子實現上面這個效果:
    
    Complex{
        public:
            Complex (int r = 0, int i = 0):m_r(r), m_i(i){}
            void print(void) const {                             // 輸出
                cout << m_r << '+' << m_r << 'i' << endl;
            }
            Comples& opeartor+= (const Complex& r) {
                m_r += r.m_r;
                m_i += r.m_i;
                return *this;                                        // 因為返回的是引用,所以這裡加星
            }
            // 用友元實現 -= 操作,這裡用了 frined,所以雖然在 Complex 類裡面,但是是全域性函式
            friend Complex& operator-= (Complex& l, const Complex& r) {
                l.m_r -= r.m_r;
                l.m_i -= r.m_i;
                return l;                                            // 返回左值,給c1
            }
        private:
            int m_r;
            int m_i;
    };
    int main(){
        Complex c1(1, 2), c2(3, 4);
        c1 += c2;                        // 相當於 c1.operator += (c2);
        c1.print();
        Complex c3(5, 6);
        (c1 += c2) = c3;
        c1.print();                    // 5+6i
        c1 -= c2;                        // 相當於 ::operator-= (c1, c2);
        c1.print();                    // (5+6i) - (3+4i) = 2+2i;
        (c1 += c2) = c3;
        c1.print();                    // 5+6i
        return 0;
    }
    
    操作符過載-成員函式形式:AA& opeartor+= (const AA&);
    操作符過載-友員函式形式:friend AA& operator-= (AA&, const AA&);
    
2.    雙目運算子 2
    <<    /    >>
    左運算元型別為:ostream/istream,不能是常量,不能拷貝;
    右運算元為自定義型別;對於輸入,右運算元不能是常量;輸出可以;
    表示式的值是左運算元的引用;
    cout << c << i << endl; 相當於下面表示式:
    cout.operator<< (c).operator<< (i).operator<< (endl);
    一般用全域性/友員方式實現:
    ::operator<< (cout, c).operator<< (i).operator<< (endl);    
    因為輸入輸出是 c++ 標準庫裡的類函式,不能自己寫,所以不能過載成成員函式;
    
    Complex{
        public:
            Complex (int r = 0, int i = 0):m_r(r), m_i(i){}
            void print(void) const {                                         // 輸出
                cout << m_r << '+' << m_r << 'i' << endl;
            }
            friend ostream& operator<< (ostream& os, const Complex& r)    // 輸出,const為了支援常量型運算元
            // cout 是私有的,不能拷貝構造,所以給 os 加引用,不創造新物件;
            // operator 返回的是當前物件 cout,所以返回值應該加引用;
            // 這個函式只是需要列印,不需要創造新物件,所以最後 r 也加引用;既然加了引用,就加 const,可以傳常量進來;
            { return os << r.m_r << '+' << r.m_i << 'i'; }
            friend istream& operator>> (istream& is, Complex& r)            // 輸入
            { return is >> r.m_r >> r.m_i; }
        private:
            int m_r;    // 實步
            int m_i;    // 虛步
    };
    int main(){
        Complex c1(1, 2), c2(3, 4);
        cout << c1 << endl << c2 << endl;
        //::operator<<(::operator<<(cout,c1)).operator<<((endl),c2).operator<<(endl);
        cin >> c1 >> c2;
        c1.print();
        c2.print();
        return 0;
    }    
    
3.    單目運算子 1
    -(取負),!(非),~(反)
    運算元不變,表示式的值是右值;    
    
    Complex{
        public:
            Complex (int r = 0, int i = 0):m_r(r), m_i(i){}
            void print(void) const {                                     // 輸出
                cout << m_r << '+' << m_r << 'i' << endl;
            }
            const Complex operator-(void) const {
                return Complex (-m_r, -m_i);
            }
            friend const Complex operator~ (const Complex& o){
                return Complex (o.m_i, o.m_r);                            // 實步虛步交換
            }
        private:
            int m_r;
            int m_i;
    };
    int main(){
        Complex c1(1, 2);
        Complex c2 = -c1;        // c2 = c1.opeartor-();
        c2.print();            // -1+-2i
        Complex c3 = ~c1;        // 這裡的~不一定是按位取反,可以自定義,現在實現運算元交換 c3 = ::operator~(c1);
        c3.print();            // 2+1i
        return 0;
    }        
    
4.    單目運算子 2
    前++,前--;
    運算元發生改變,表示式為左值,運算以後的值;就是運算元的引用;
        (++i) = 100;            // i = 100;
        ++++++i;                // i = 103;
    Complex{
        public:
            Complex (int r = 0, int i = 0):m_r(r), m_i(i) {}
            void print(void) const {                             // 輸出
                cout << m_r << '+' << m_r << 'i' << endl;
            }
            Complex& operator++ (void){                            // 單目無須引數
                // 返回左值,所以返回&,也就是左值本身,可以修改;如果沒有&,返回的是副本;
                ++m_r;
                ++m_i;
                return *this;                                        // 返回自引用
            }
            friend Complex& operator-- (complex& o){
                --o.m_r;
                --o.m_i;
                return o;
            }
        private:
            int m_r;
            int m_i;
    };
    int main(){
        Complex c1(1, 2);
        Complex c2 = ++c1;            // c2 = c1.operator++();
        c1.print();                    // 2+3i
        c2.print();                    // 2+3i
        (++c1) = Complex (10, 20);
        c1.print();                    // 10+20i
        (++++++c1).print();            // 13+23i
        c2 = --c1;                    // c2 = c1.operator--();
        c1.print();                    // 12+22i
        c2.print();                    // 12+22i
        return 0;
    }
    
5.    單目運算子 3
    後++,後--
    運算元變,表示式的值是右值,運算前的值;
        (i++) = 100;                    // error
        i++++++;                        // error
        
    Complex{
        public:
            Complex (int r = 0, int i = 0):m_r(r), m_i(i){}
            void print(void) const {                                     // 輸出
                cout << m_r << '+' << m_r << 'i' << endl;
            }
            const Complex operator++ (int) {                            // 啞元
                Complex old(*this);
                ++m_r;
                ++m_i;
                return old;                                                // 返回加之前的值        
            }
            friend const Complex operator-- (Complex& o, int){        // 啞元
                Complex old(o);
                --m_r;
                --m_i;
                return old;
            }
        private:
            int m_r;
            int m_i;
    };
    int main(){
        Complex c1(1, 2);
        Complex c2 = c1++;        // c2 = c1.operator++(0); 這裡帶引數只是為了區分前++,引數無用
        c1.print();                // 2+3i
        c2.print();                // 1+2i
        (c1++) = c2;                // error
        c2 = c1--;                // c2 = ::operator--(c1, 0);
        c1.print();                // 1+2i
        c2.print();                // 2+3i
        return 0;
    }
    
    備註:a++++; 錯誤,返回為 const 型;
        ++++a;    可以,返回為引用;
    
6.    其他操作符
    下標操作符 [ ],類似於陣列:
    int arr[10] = {...};
    cout << arr[1] << endl;
    
    下標表達式是雙目運算子;
    左運算元是一個具有容器特性的物件,右運算元是容器中特定資料元素的索引(基零下標);
    下標表達式的值可以是左值,也可以是右值,由容器物件的常屬性決定;
    常容器下標表達式的值是右值;反之,非常容器下標表達式為左值;    
    class Array {...};            // 用於有容器特性的類
    Array arr (...);                // 容器物件,無常屬性,可以修改
    arr[1] = 10;                    // 當左值
    cout << arr[1] << endl;        // 當右值

    class Array {
        public:
            Array (size_t size = 1) : m_data (new int[size]) {}
            ~Array (void) {
                if (m_data) {
                    delete m_data;
                    m_data = NULL;                // 把 m_data 置空,防止重複釋放出錯;
                }
            }
            int& operator[] (size_t i) { return m_data[i]; }
            const int& operator[] (size_t i) const { return m_data[i]; }
            /*
            上面兩條語句冗餘了,兩個 return 一樣,可以寫成下面這樣:
            const int& operator[] (size_t i) const {
                    return const_cast<Array&>(*this)[i];}                // 不能直接返回 (*this)[i]
            int operator[] (size_t i) const { return m_data[i]; }    // 直接返回值也可以
            */
        private:
            int* m_data;
        };
        int main(void) {
            Array arr (10);                        // 因為返回左值,所以這裡的容器 arr 就不能是 const
            for (size_t i = 0; i < 10; ++i)
                arr[i] = i;                        // arr.operator[](i) = i; 返回第 i 個元素的引用
            arr[0]++;                                // ok
            const Array& cr = arr;                // 返回右值
            for (size_t i = 0; i < 10; ++i)
                cout << cr[i] << ' ';
            cr[0]++;                                // error,因為 cr 具有常屬性            
            return 0;
        }

7.    其他操作符 2
    函式操作符:()
    如果為一個類定義了形如:
        返回型別 operator()(行參表){. . .}
    那麼這個類所例項化的物件可以當函式使用;

    class Square {
        double operator() (double x) {
            return x * x;
        }
    };
    class Integer {
        public:
            Integer (int i = 0) : m_i (i) {}
            void print () { cout << m_i << endl; }
            Integer& operator() (int i) {
                m_i += i;
                return *this;
            }
            Integer& operator, (int i) {        // 逗號運算子
                m_i += i;
                return *this;
            }
        private:
            int m_i;
    };
    int main (void) {
        Square square;
        cout << square(3) << endl;                // square.operator()(3);
        Integer i (10);
        i (1)(2)(3);
        i.print();                                // 10 + 1 + 2 + 3 = 16
        i, 1, 2, 3;
        i.print();                                // 16 + 1 + 2 + 3 = 22
        return 0;
    }
    
8.    其他操作符 3
    間接解引用( * )和間接訪問(->)操作符
    以指標的方式使用類型別物件;
    
    class A {
        public:
            A (void) {};
            ~A (void) {};
    };
    class PA {
        public:
            PA (A* p = NULL) : m_p (p) {}
            ~PA () {
                if (m_p) {
                    delete m_p;
                    m_p = NULL;
                }
            }
            A& operator* (void) const {            // 為了讓下面的 pa 可以用箭頭訪問
                return *m_p;
            }
            A* operator-> (void) const {
                return &**this;                    // return m_p;
            }
            void hello(void) {};
        private:
            A* m_p;
    };
    void foo () {
        A* p = new A;            // 不執行析構,因為沒有 delete;p是棧物件
        A a;                    // 棧物件,執行析構,因為有下面那個花括號;
        PA pa = new A;        // PA自己delete,pa 是物件,和上面的p不一樣
        pa -> hello();        // pa.operator->()->hello();
        (*pa).hello();        // pa.operator*().hello();
        PA pb = pa;            // 淺拷貝,執行錯誤
    }
    int main () {
        foo ();
        return 0;
    }
    
9.    其他操作符 4
    new 和 delete 過載
    #include <cstdlib>
    class A {
        public:
            static void* operator new (size_t size) {        // 不同平臺,size_t 自動換為不同型別
                void* p1 = malloc (size);                    // 動態分配記憶體給 p
                cout << size << p1 << endl;                    // size = 16
                return p1;                                    // 返回記憶體地址
            }
            static void operator delete (void* p1){
                free (p1);
            }
            static void* operator new[] (size_t size){
                void* p2 = malloc (size);                    // size = 32
                return p2;
            }
            static void operator delete[] (void* p2){
                free(p2);
            }
        private:
            int m_i;
            double m_d;
            char m_c;
    };
    int main(){
        cout << sizeof(A) << endl;    // IIIIDDDDDDDDCXXX, 16位元組
        A* pa = new A;
        cout << pa << endl;            // pa 地址和上面 p1 地址一樣
        pa = new A[2];
        cout << pa << endl;            // pa 地址和上面 p2 地址一樣
        delete[] pa;
        return 0;
    }
    
    操作符過載小結:
        只能被過載成成員函式的運算子:
        =   +=   -=   *=   /=   []   ()   ->
        只能被過載成全域性函式的運算子:
        <<    >>

10.    自定義型別轉換
    通過單參構造實現自定義型別轉換;
    如果在 A 類中有一個可以接受 B 類物件為唯一引數的建構函式;
    那麼 B 型別的物件就可以根據建構函式轉換為 A 型別;

    class Integer {
        public:
            Integer (int i = 0) : m_i (i) {}            // 通過建構函式實現隱式轉換;
            void print() { cout << m_i << endl; }
            Integer& operator() (int i) {
                m_i += i;
                return *this;
            }
            Integer& operator, (int i) {                // 逗號運算子
                m_i += i;
                return *this;
            }
        private:
            int m_i;
    };
    Integer bar() {
        return 300;        // return 和函式返回值不一樣,也會自動隱式轉換;
    }
    int main (void) {
        Integer i(10);
        i = 100;            // 100為常量,i為Integer型別,編譯器通過上面 Integer(int) 把右邊轉為左邊型別
        i.print();
        bar().print();
    }    

    通過 explicit 可以強制建構函式使用顯示轉換;
    如果寫成: explicit Integer (int i = 0) : m_i (i) {}
    那麼下面 main 函式裡的 i 也要顯示轉換:i = (Integer)(100);

11.    通過型別轉換操作符函式實現自定義型別轉換:
        operator B (void) const {...}
    那麼 A 類物件就可以轉換為 B 類物件;    

    上面例子可以把 Integer 轉換為 int,需要在 public 裡面加上如下函式:    
        operator int (void) const {
            return m_i;
        }
    main 函式裡面加上如下程式碼:
        int n = i;
        cout << n << endl;
    
    舉例:如果 s 是 string 型別
    如下程式碼:const char* c = s; 會報錯,需要加上如下程式碼:
        operator const char*() const { return s; }

12.    自定義型別轉換總結:

    如果目標型別是類型別,源型別是基本型別;
    那麼就只能通過在目標型別中,定義以源型別為單參的建構函式,實現型別轉換;

    如果目標型別是基本型別,源型別是類型別;
    那麼就只能通過在源型別中定義以目標型別為函式名的型別轉換操作符函式實現型別轉換;
    
    如果目標型別和源型別都是類型別,那麼以上兩種方法任取其一;但是不能同時使用;
    
    如果目標型別和源型別都是基本型別,那麼就無法實現自定義型別轉換;
    
13.    操作符過載的一些限制:
    1)要實現操作符過載,至少有一個是類物件;
        int a = 10, b = 23;
        int c = a + b;
        int operator+(int a, int b) {
            return a * b;
        }
        // ERROR! 兩個運算元都是普通變數,所以無法用這個加法函式實現乘法運算;
        
    2)下面的操作符不可以過載:
        ::        作用域限定
        .            直接成員訪問
        .*            直接成員指標解引用(間接的可以)
        ? :        三目運算子
        sizeof        獲取位元組數(注意:sizeof 是運算子,不是函式)
        typeid        獲取型別資訊
        
    3)下面的操作符不可以用全域性函式的方式實現,只能用成員函式的方式實現:
        =            拷貝賦值
        [ ]            下標
        ( )        函式
        ->            間接成員訪問
    
        如果操作符既能用全域性,也能用成員,我們首先選擇成員,限定其作用域,提高安全性;
        
    4)不能自己發明新的運算子:
        x ** y;        // error, 沒有乘乘運算子;

14.    練習:實現 3 * 3 矩陣
        支援 + / += / * / *= / 前++ / 後-- / <<
        
15.    練習:實現一個 Date 類,實現一下功能:
        + / += :增加 n 天后是哪一天
        - / -= :減去 n 天后是哪一天
        - :計算兩個日期間隔
        >> :讀取,例如 2013 5 24
        << :輸出,例如 2013-5-24