• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 13, comments - 4, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            Imperfect C++ 讀書筆記(三)

            Posted on 2008-11-20 00:28 Batiliu 閱讀(373) 評論(0)  編輯 收藏 引用 所屬分類: 讀書筆記

            數組和指針

            首先我們來看一個經典的C/C++求數組元素個數的解決方案:

            #define NUM_ELEMENTS(x) (sizeof((x)) / sizeof((x)[0]))

            利用C/C++編譯器將表達式 ar[n] 在編譯器解釋為 *(ar+n) 的特性,我們可以提供一個更“先進”的版本:

            #define NUM_ELEMENTS(x) (sizeof((x)) / sizeof(0[(x)]))

            這種下標索引的可交換性,只對內建的下標索引操作符有效,這一限制可以被用于約束NUM_ELEMENTS宏只對數組/指針有效,而拒絕重載了下標索引操作符的類類型。

             

            接下來,我們來看NUM_ELEMENTS的實際運用:

            int ar[10];
            cout << NUM_ELEMENTS(ar) << endl;        // 毋庸置疑,輸出為10。
            ...
            void fn(int ar[10])
            {
                cout << NUM_ELEMENTS(ar) << endl;    // 本行結果呢?如果你說10,那么將會使你大失所望,
            }                                        // 事實上,程序輸出為1。

            看起來一切都井井有條,問題究竟出在哪里呢?呃,是這樣的,在C/C++中你無法將數組傳給函數!在C里面,數組被傳遞函數時總是被轉換成指針,非常干脆地阻止了你獲取數組大小的企圖。而C++基于兼容性的考慮,亦是如此。

            所以先前給出的NUM_ELEMENTS宏定義依賴于預處理器進行的文本替換,因而存在一個嚴重的缺陷:如果我們(不小心)將它應用到一個指針身上,文本替換出來的結果將是錯誤的。

             

            幸運的是,我們可以利用大多數現代編譯器都支持的一個特性來將數組和指針區別對待,那就是:從數組到指針的轉換(退化)在引用類型的模板實參決議中并不會發生。因而我們可以重新定義NUM_ELEMENTS宏為:

            template<int N>
            struct array_size_struct
            {
                byte_t c[N];
            };
             
            template<typename T, int N>
            array_size_struct<N> static_array_size_fn(T(&)[N]);
             
            #define NUM_ELEMENTS(x) sizeof(static_array_size_fn(x).c)

            其基本原理是:聲明(但不定義)一個模板函數,它接受一個元素類型為T、大小為N的數組的引用。這樣一來,指針類型以及用戶自定義類型就被拒之門外了(編譯報錯)。并且由于C++標準中,sizeof()的操作數不會被求值,所以我們無需定義static_array_size_fn()函數體,從而上述設施完全是零代價的。沒有任何運行時開銷,也不會導致代碼膨脹。

             

            讓我們回到“C/C++數組在被傳遞給函數時會退化成指針”的問題上來,如果我們在現實中需要將一個將任意長度的數組傳遞給一個期望接受數組的函數,那么該怎么辦呢?困惑的實質在于數組的大小在傳遞過程中丟失了,因此,如果我們可以找到一種將數組大小隨之傳遞給函數的機制,問題就會迎刃而解。有了上面宏定義的經驗,通過模板我們找到一個解決方案:

            template<typename T>
            class array_proxy
            {
            public:
                typedef T               value_type;
                typedef array_proxy<T>  class_type;
                typedef value_type *    pointer;
                typedef value_type *    const_pointer;      // Non-const!
                typedef value_type &    reference;
                typedef value_type &    const_reference;    // Non-const!
                typedef size_t          size_type;
            // 構造函數
            public:
                template<size_t N>
                explicit array_proxy(T(&t)[N])    // 元素類型為T的數組
                    : m_begin(&t[0])
                    , m_end(&t[N])
                {}
                template<typename D, size_t N>
                explicit array_proxy(D(&d)[N])    // 元素類型為T兼容類型的數組
                    : m_begin(&d[0])
                    , m_end(&d[N])
                {
                    constraint_must_be_same_size(T, D);    // 確保D和T大小相同
                }
                template<typename D>
                array_proxy(array_proxy<D> &d)
                    : m_begin(d.begin())
                    , m_end(d.end())
                {
                    constraint_must_be_same_size(T, D);    // 確保D和T大小相同
                }
            // 狀態
            public:
                pointer             base();
                const_pointer       base() const;
                size_type           size() const;
                bool                empty() const;
                static size_type    max_size();
            // 下標索引操作符
            public:
                reference        operator [](size_t index);
                const_reference  operator [](size_t index) const;
            // 迭代操作
            public:
                pointer          begin();
                const_pointer    begin() const;
                pointer          end();
                const_pointer    end() const;
            // 數據成員
            private:
                pointer const m_begin;
                pointer const m_end;
            // 聲明但不予實現
            private:
                array_proxy & operator =(array_proxy const &);
            };
             
            // 轉發函數
            template<typename T, size_t N>
            inline array_proxy<T> make_array_proxy(T(&t)[N])
            {
                return array_proxy<T>(t);
            }
            template<typename T>
            inline array_proxy<T> make_array_proxy(T * base, size_t size)
            {
                return array_proxy<T>(base, size);
            }

            客戶代碼修改為:

            void process_array(const array_proxy<int> & ar)
            {
                std::copy(ar.begin(), ar.end(), ostream_iterator<int>(cout, " "));
            }
             
            int _tmain(int argc, _TCHAR* argv[])
            {
                int ar[5] = {0, 1, 2, 3, 4};
                
                process_array(make_array_proxy(ar));
             
                return 0;
            }

            我們的問題終于有了一個徹底的解決方案。該解決方案是高效的(在任何一個說得過去的編譯器上它都沒有任何額外的開銷),是類型安全的,并且完全使得函數的設計者能夠防止潛在的誤用(更確切的說,讓代碼能夠更強的抵御派生類數組的誤用)。此外,它還足夠智能,允許派生類跟父類具有相同大小的情況下,它們的數組被“代理”。

            最后一個優點是現在再也不可能將錯誤的數組長度傳給被調函數了,以前我們慣用的使用兩個參數(一個傳遞數組指針,一個傳遞數組長度)的函數版本中誤傳長度的危險是時時存在的。這個優勢使我們得以遵從DRY(Don't Repeat Yourself!)原則。

             

            NULL宏

            在C語言中,void*類型可以被隱式地轉換為其他任何指針類型,所以我們可以將NULL定義為((void*)0),從而跟其他任何指針類型間實現互相轉換。然而,C++不允許從void*到任何指針的隱式轉換,又因為C++中0可以被轉換為任何指針類型,因此,C++標準規定:NULL宏是一個由實現定義的C++空指針常量....其可能的定義方式包括0和0L,但絕對不是(void*)0。

            由于0不可置疑的可以轉換成任何整型,甚至wchar_t和bool,以及浮點類型,這就意味著,使用NULL的時候,類型檢查將不再發生,我們很容易毫無察覺的走向厄運的深淵,連個警告都沒有。考慮如下情況:

            // 自定義的字符串類
            //
            class String
            {
                explicit String(char const *s);                  // 接受外界傳入的空指針
                explicit String(int cch, char chInit = '\0');    // 根據可能被使用的字符數估計,來初始化底層存儲
            };

            現在當我們用NULL做參數構造String時,第二個構造函數會被調用!也許和你的初衷不同,編譯器卻會一聲不吭的編譯通過。這可不妙。如果你將int改為size_t(或short、或long、或任何不是int的內建類型),編譯器將會在兩個轉換之間左右為難,結果是得到一個二義性錯誤。

             

            我們想要個完善的空指針關鍵字!很快作者想出了辦法,你不應該感到驚訝,解決方案離不開模板:

            struct NULL_v
            {
            // 構造函數
            public:
                NULL_v()
                {}
            // 轉換操作符
            public:
                template<typename T>
                operator T* () const
                {
                    return 0;
                }
                template<typename T2, typename C>
                operator T2 C::*() const
                {
                    return 0;
                }
                template<typename T>
                bool equals(T const & rhs) const
                {
                    return rhs == 0;
                }
            // 聲明但不予實現
            private:
                void operator &() const;    // Scott: 純粹是值的東西的地址是沒有任何意義的。
                NULL_v(NULL_v const &);
                NULL_v& operator =(NULL_v const &);
            };
             
            template<typename T>
            inline bool operator ==(NULL_v const & lhs, T const & rhs)
            {
                return lhs.equals(rhs);
            }
             
            template<typename T>
            inline bool operator ==(T const & lhs, NULL_v const & rhs)
            {
                return rhs.equals(lhs);
            }
             
            template<typename T>
            inline bool operator !=(NULL_v const & lhs, T const & rhs)
            {
                return !lhs.equals(rhs);
            }
             
            template<typename T>
            inline bool operator !=(T const & lhs, NULL_v const & rhs)
            {
                return !rhs.equals(lhs);
            }
            久久久久亚洲精品无码蜜桃 | 狠狠色丁香久久婷婷综合图片 | 国产精品热久久无码av| 久久国产精品99精品国产| 久久99亚洲网美利坚合众国| 尹人香蕉久久99天天拍| 中文国产成人精品久久亚洲精品AⅤ无码精品 | 久久精品这里只有精99品| 久久99精品国产麻豆不卡| 久久久久久噜噜精品免费直播| 国产2021久久精品| 国内精品久久久久影院薰衣草 | 品成人欧美大片久久国产欧美| 青青草国产精品久久| 久久久久久毛片免费看| 亚洲乱码中文字幕久久孕妇黑人| 伊人久久大香线蕉亚洲| 久久久91精品国产一区二区三区 | 久久午夜免费视频| 国产精品永久久久久久久久久 | 久久午夜无码鲁丝片秋霞| 色婷婷久久综合中文久久蜜桃av| 久久精品国产亚洲av水果派 | 国产一区二区精品久久岳| 精品多毛少妇人妻AV免费久久| 国产高潮国产高潮久久久| 久久久无码精品午夜| 久久久久亚洲AV无码网站| 久久影院亚洲一区| 久久精品国产亚洲5555| 国内精品久久久久影院一蜜桃| 久久se这里只有精品| 久久精品国产亚洲AV无码偷窥| 久久亚洲电影| 色婷婷狠狠久久综合五月| 久久精品国产亚洲av高清漫画| 一本色道久久综合狠狠躁篇| 国产91色综合久久免费| 久久99久久99精品免视看动漫| 久久99精品久久久久久秒播| 亚洲中文久久精品无码|