注銷

          注銷

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            112 隨筆 :: 7 文章 :: 18 評論 :: 0 Trackbacks

          我們在使用vc進行比較復雜的編程時,經常需要用到復雜的數(shù)組結構,并希望能實現(xiàn)動態(tài)管理。由于C++并不支持動態(tài)數(shù)組,MFC提供了一個CArray類來實現(xiàn)動態(tài)數(shù)組的功能。有效的使用CArray類,可以提高程序的效率。
          MFC提供了一套模板庫,來實現(xiàn)一些比較常見的數(shù)據結構如Array,List,Map。CArray即為其中的一個,用來實現(xiàn)動態(tài)數(shù)組的功能。
          CArray是從CObject派生,有兩個模板參數(shù),第一個參數(shù)就是CArray類數(shù)組元素的變量類型,后一個是函數(shù)調用時的參數(shù)類型。
          我們有一個類 class Object,我們要定義一個Object的動態(tài)數(shù)組,那么我們可以用以下兩種方法:

          CArray<Object,Object>  Var1;
          CArray<Object,Object&>  Var2;

          Var1與Var2哪一個的效率要高呢? Var2的效率要高。為什么呢?接下來我們對CArray的源代碼做一個剖析就清楚了。
          先了解一下CArray中的成員變量及作用。TYPE* m_pData;   // 數(shù)據保存地址的指針
          int m_nSize;       // 用戶當前定義的數(shù)組的大小
          int m_nMaxSize;   // 當前實際分配的數(shù)組的大小
          int m_nGrowBy;   // 分配內存時增長的元素個數(shù)

          首先來看它的構造函數(shù),對成員變量進行了初始化。 CArray<TYPE, ARG_TYPE>::CArray()
          {
           m_pData = NULL;
           m_nSize = m_nMaxSize = m_nGrowBy = 0;
          }

          SetSize成員函數(shù)是用來為數(shù)組分配空間的,從這里著手,看CArray是如何對數(shù)據進行管理的。SetSize的函數(shù)定義如下: void SetSize( int nNewSize, int nGrowBy = -1 );
          nNewSize 指定數(shù)組的大小
          nGrowBy 如果需要增加數(shù)組大小時增加的元素的個數(shù)。
          對SetSize的代碼,進行分析。(由于代碼太長,只列出部分重要部分) void CArray<TYPE, ARG_TYPE>::SetSize(int nNewSize, int nGrowBy)
          {
           if (nNewSize == 0)
           {
            // 第一種情況
            // 當nNewSize為0時,需要將數(shù)組置為空,
            // 如果數(shù)組本身即為空,則不需做任何處理
            // 如果數(shù)組本身已含有數(shù)據,則需要清除數(shù)組元素
            if (m_pData != NULL)
            {
             //DestructElements 函數(shù)實現(xiàn)了對數(shù)組元素析構函數(shù)的調用
             //不能使用delete m_pData  因為我們必須要調用數(shù)組元素的析構函數(shù)
             DestructElements<TYPE>(m_pData, m_nSize);
             //現(xiàn)在才能釋放內存
             delete[] (BYTE*)m_pData;
             m_pData = NULL;
            }
            m_nSize = m_nMaxSize = 0;
           }
           else if (m_pData == NULL)
           {
            // 第二種情況
            // 當m_pData==NULL時還沒有為數(shù)組分配內存
            //首先我們要為數(shù)組分配內存,sizeof(TYPE)可以得到數(shù)組元素所需的字節(jié)數(shù)
            //使用new 數(shù)組分配了內存。注意,沒有調用構造函數(shù)
            m_pData = (TYPE*) new BYTE[nNewSize * sizeof(TYPE)];
            //下面的函數(shù)調用數(shù)組元素的構造函數(shù)
            ConstructElements<TYPE>(m_pData, nNewSize);
            //記錄下當前數(shù)組元素的個數(shù)
            m_nSize = m_nMaxSize = nNewSize;
           }
           else if (nNewSize <= m_nMaxSize)
           {
            // 第三種情況
            // 這種情況需要分配的元素個數(shù)比已經實際已經分配的元素個數(shù)要少
            if (nNewSize > m_nSize)
            {
             // 需要增加元素的情況
             // 與第二種情況的處理過程,既然元素空間已經分配,
             // 只要調用新增元素的構造函數(shù)就Ok
             ConstructElements<TYPE>(&m_pData[m_nSize], nNewSize-m_nSize);
            }
            else if (m_nSize > nNewSize)
            {
             // 現(xiàn)在是元素減少的情況,我們是否要重新分配內存呢?
             //  No,這種做法不好,后面來討論。
             //  下面代碼釋放多余的元素,不是釋放內存,只是調用析構函數(shù)
             DestructElements<TYPE>(&m_pData[nNewSize], m_nSize-nNewSize);
            }
            m_nSize = nNewSize;
           }
           else
           {
            //這是最糟糕的情況,因為需要的元素大于m_nMaxSize,
            // 意味著需要重新分配內存才能解決問題

            // 計算需要分配的數(shù)組元素的個數(shù)
            int nNewMax;
            if (nNewSize < m_nMaxSize + nGrowBy)
             nNewMax = m_nMaxSize + nGrowBy;
            else
             nNewMax = nNewSize; 
            // 重新分配一塊內存
            TYPE* pNewData = (TYPE*) new BYTE[nNewMax * sizeof(TYPE)];
            //實現(xiàn)將已有的數(shù)據復制到新的的內存空間
            memcpy(pNewData, m_pData, m_nSize * sizeof(TYPE));
            // 對新增的元素調用構造函數(shù)
            ConstructElements<TYPE>(&pNewData[m_nSize], nNewSize-m_nSize);

            //釋放內存
            delete[] (BYTE*)m_pData;

            //將數(shù)據保存
            m_pData = pNewData;
            m_nSize = nNewSize;
            m_nMaxSize = nNewMax;
           }
          }

          注意上面代碼中標注為粗體的代碼,它們實現(xiàn)了對象的構造與析構。如果我們只為對象分配內存,卻沒有調用構造與析構函數(shù),會不會有問題呢?
          如果只是使用c++的基本數(shù)據類型,如果int,long,那的確不會有什么問題。如果使用的是一個類,比如下面的類: class Object
          {
          public:
            Object(){ ID = 0; }
            ~Object();
          protected:
            int ID;
          };
          我們只為Object類分配了空間,也能正常使用。但是,類的成員變量ID的值卻是不定的,因為沒有初始化。如果是一個更復雜的組合類,在構造函數(shù)中做了許多工作,那可能就不能正常運行了。
          同樣,刪除的數(shù)組元素時,也一定要調用它的析構函數(shù)。
          我們來看下面的Preson類 class Preson
          {
          public:
           Preson()
           {
            name = new char[10];
           }
           ~Preson()
           {
            delete []name;
           }
           char* name;
           int   age;
          }
          如果我沒調用構造函數(shù),那么對name操作肯定會出錯。我們調用了構造函數(shù)后,刪除元素時,如果不調用析構函數(shù),那么,name所指向的內存不能正確釋放,就會造成內存泄漏。

          我們來看一下ConstructElements與DestructElements如何實現(xiàn)構造與析構函數(shù)的調用。
          下面是ConstructElements函數(shù)的實現(xiàn)代碼template<class TYPE>
          AFX_INLINE void AFXAPI ConstructElements(TYPE* pElements, int nCount)
          {
           // first do bit-wise zero initialization
           memset((void*)pElements, 0, nCount * sizeof(TYPE));

           for (; nCount--; pElements++)
            ::new((void*)pElements) TYPE;
          }
          ConstructElements是一個模板函數(shù)。對構造函數(shù)的調用是通過標為黑體的代碼實現(xiàn)的。可能很多人不熟悉new 的這種用法,它可以實現(xiàn)指定的內存空間中構造類的實例,不會再分配新的內存空間。類的實例產生在已經分配的內存中,并且new操作會調用對象的構造函數(shù)。因為vc中沒有辦法直接調用構造函數(shù),而通過這種方法,巧妙的實現(xiàn)對構造函數(shù)的調用。
          再來看DestructElements 函數(shù)的代碼template<class TYPE>
          AFX_INLINE void AFXAPI DestructElements(TYPE* pElements, int nCount)
          {
           for (; nCount--; pElements++)
            pElements->~TYPE();
          }
          DestructElements函數(shù)同樣是一個模板函數(shù),實現(xiàn)很簡單,直接調用類的析構函數(shù)即可。
          如果定義一個CArray對象 CArray<Object,Object&> myObject ,對myObject就可象數(shù)組一樣,通過下標來訪問指定的數(shù)組元素。通過[]來訪問數(shù)組元素是如何實現(xiàn)的呢?其實只要重載運算符[]即可。
          CArray[]有兩種實現(xiàn),區(qū)別在于返回值不同。我們來看看代碼:

          template<class TYPE, class ARG_TYPE>
          AFX_INLINE TYPE CArray<TYPE, ARG_TYPE>::operator[](int nIndex) const
           { return GetAt(nIndex); }
          template<class TYPE, class ARG_TYPE>
          AFX_INLINE TYPE& CArray<TYPE, ARG_TYPE>::operator[](int nIndex)
           { return ElementAt(nIndex); }
          前一種情況是返回的對象的實例,后一種情況是返回對象的引用。分別調用不同的成員函數(shù)來實現(xiàn)。我們來比較一下這兩個函數(shù)的實現(xiàn)(省略部分): TYPE    GetAt(int nIndex) const
           { ASSERT(nIndex >= 0 && nIndex < m_nSize);
            return m_pData[nIndex]; }
          TYPE&  ElementAt(int nIndex)
           { ASSERT(nIndex >= 0 && nIndex < m_nSize);
            return m_pData[nIndex]; }
          除了返回值不同,其它都一樣,有什么區(qū)別嗎?我們來看一個實例說明。 CArray<int,int&> arrInt;
          arrInt.SetSize(10);
          int n = arrInt.GetAt(0);
          int& l = arrInt.ElementAt(0);
          cout << arrInt[0] <<endl;
          n = 10;
          cout << arrInt[0] <<endl;
          l = 20;
          count << arrInt[0] << endl;
          結果會發(fā)現(xiàn),n的變化不會影響到數(shù)組,而l的變化會改變數(shù)組元素的值。實際即是對C++中引用運算符的運用。
          CArray下標訪問是非安全的,它并沒有超標預警功能。雖然使用ASSERT提示,但下標超范圍時沒有進行處理,會引起非法內存訪問的錯誤。
          前面談到模板實例化時有兩個參數(shù),后一個參數(shù)一般用引用,為什么呢?看看Add成員函數(shù)就可以明。Add函數(shù)的作用是向數(shù)組添加一個元素。下面是它的定義: int CArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement)
          Add函數(shù)使用的參數(shù)是模板參數(shù)的二個參數(shù),也就是說,這個參數(shù)的類型是我們來決定的,可以使用Object或Object&的方式。熟悉C++的朋友都知道,傳引用的效率要高一些。如果是傳值的話,會在堆棧中再產生一個新的對象,需要花費更多的時間。
          下面來分析一下Add函數(shù)的代碼:
          template<class TYPE, class ARG_TYPE>
          AFX_INLINE int CArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement)
          {
           int nIndex = m_nSize;
           SetAtGrow(nIndex, newElement);
           return nIndex;
          }
          它實際是通過SetAtGrow函數(shù)來完成這個功能的,它的作用是設置指定元素的值。下面是SetAtGrow的代碼: template<class TYPE, class ARG_TYPE>
          void CArray<TYPE, ARG_TYPE>::SetAtGrow(int nIndex, ARG_TYPE newElement)
          {
           if (nIndex >= m_nSize)
            SetSize(nIndex+1, -1);
           m_pData[nIndex] = newElement;
          }
           

          posted on 2007-06-03 11:54 注銷..... 閱讀(2610) 評論(0)  編輯  收藏 所屬分類: c++
          主站蜘蛛池模板: 大安市| 湘潭市| 揭西县| 鄂温| 邛崃市| 彰武县| 阿巴嘎旗| 南昌市| 湟源县| 诸城市| 桃江县| 石屏县| 尚义县| 汤原县| 淮阳县| 漯河市| 绥德县| 甘德县| 门头沟区| 敦化市| 左云县| 阿拉善盟| 苍南县| 横峰县| 手机| 新野县| 伊宁市| 蓝山县| 景东| 彭州市| 清苑县| 外汇| 年辖:市辖区| 临潭县| 永川市| 确山县| 平塘县| 巴林右旗| 松溪县| 文登市| 三都|