C++動的配列クラスのカプセル化例

8075 ワード

C++の動的配列(Dynamic Array)とは、必要に応じて動的にメモリを消費できる動的に割り当てられた配列を指す.動的配列クラスのカプセル化を実現するには、new/deleteの使用、メモリ割り当てポリシー、クラスの4つの関数(コンストラクション関数、コピーコンストラクション関数、コピー付与演算子、コンストラクション関数)、演算子の再ロードといういくつかの問題を考慮する必要があります.関連する知識点が多く,これについては簡単に紹介するだけである.
一、メモリ分配策略
newを使用して動的配列のメモリを申請すると、配列内の要素はvectorやstringなどの連続的に格納されます.動的配列に要素を追加する場合、新しい要素を格納するスペースがない場合は、要素が連続的に格納される必要があるため、メモリ内の他の場所に新しい要素を簡単に追加することはできません.そのため、より大きなメモリ領域を再割り当てし、元の要素を古い場所から新しい空間に移動し、新しい要素を追加し、古いメモリ領域を解放する必要があります.新しい要素を追加するたびに、このようなメモリ割り当てと解放操作を実行すると、効率が低下します.
上記のコストを回避するためには、メモリの再割り当て回数を減らす必要があります.したがって、新しいメモリ領域を割り当てなければならない場合、新しい領域よりも大きなメモリ領域(通常は2倍)を割り当てる戦略を採用しています.これにより、エレメントを追加するときにメモリ領域を再申請する必要がなくなります.新しいメモリ領域は、やむを得ない場合にのみ割り当てることができます.
二、クラスの四大関数
1つのC++クラスには、一般に少なくとも4つの関数があります.すなわち、構造関数、コピー構造関数、コピー付与演算子、構造関数です.クラスが上記の関数を独自に定義していない場合、C++コンパイラは4つのデフォルトバージョンを合成します.しかし、コンパイラが合成されるのは私たちが望んでいるものではありません.そのため、自分で定義する必要があります.
1.コンストラクタ
クラスのコンストラクション関数(constructor)は、クラスオブジェクトの非staticデータメンバーを初期化するために使用され、クラスのオブジェクトが作成されるたびにコンストラクション関数が実行されます.

class Foo { 
public: 
  Foo(); //      
  Foo(string &s); 
  // ... 
}; 

コンストラクション関数の名前はクラス名と同じで、戻りタイプはありません.クラスには、パラメータの数やタイプの違いを必要とする複数のコンストラクション関数(リロード)が含まれます.コンストラクション関数には初期化部分と関数体があり、メンバーの初期化は関数体が実行される前に完了します.
2.コピーコンストラクタ
コンストラクション関数の最初のパラメータが自己クラスタイプの参照であり、追加のパラメータにデフォルト値がある場合、このコンストラクション関数はコピーコンストラクション関数(copy constructor)です.

class Foo { 
public: 
  Foo(); 
  Foo(const Foo&); //        
  // ... 
}; 

コピーコンストラクション関数は、1つのオブジェクトで別の同じタイプのオブジェクトを初期化する方法を定義します.コピー初期化は、通常、コピーコンストラクション関数を使用して行われます.コピーの初期化は、次の場合に行われます.
等号(=)を使用して変数を初期化し、オブジェクトを実パラメータとして非参照タイプのパラメータに渡します.戻りタイプが非参照タイプの関数から、オブジェクトを返します.カッコリストで配列内の要素を初期化します.
3.コピー付与演算子
クラスのコピー付与演算子(copy-assignment operator)はoperator=という関数です.他の関数と同様に、戻りタイプとパラメータのリストもあります.

class Foo { 
public: 
  Foo(); 
  Foo& operator=(const Foo&); //       
  // ... 
}; 

コピー付与演算子は、あるオブジェクトを別の同じタイプのオブジェクトに割り当てる方法を定義します.付与演算子は、メンバー関数であり、2元演算子でもあり、左側の演算オブジェクトは暗黙的なthisポインタにバインドされ、右側の演算オブジェクトは明示的なパラメータとして渡されます.注:組み込みタイプの賦値と一致するように、賦値演算子は通常、左側の演算オブジェクトへの参照を返します.
4.解析関数
クラスの構造関数(destructor)は、クラスオブジェクトが使用するリソースを解放し、クラスオブジェクトの非staticデータメンバーを破棄するために使用され、いつでも1つのオブジェクトが破棄されると、構造関数が自動的に実行されます.

class Foo { 
public: 
  ~Foo(); //      
  // ... 
}; 

構造関数の名前は波番号(~)にクラス名を付けて構成され,戻りタイプもない.構造関数はパラメータを受け入れないため、リロードできません.構造関数には、オブジェクトを破棄する関数体と構造部分があり、まず構造関数体を実行し、初期化順の逆順序でメンバーを破棄します.
三、演算子のリロード
リロードされた演算子は、キーワードoperatorとその後定義する演算記号からなる特殊な名前の関数です.他の関数と同様に、リロードされた演算子には、コピー付与演算子などの戻りタイプ、パラメータリスト、関数体も含まれます.
リロードされた演算子を定義する場合は、クラスのメンバー関数として宣言するか、通常の非メンバー関数として宣言するかを最初に決定する必要があります.一部の演算子はメンバーとして使用する必要がありますが、他の演算子はメンバーとして使用するよりも一般的な関数として使用します.
割り当て(=)、下付き([])、呼び出し((())、およびメンバー・アクセス矢印(->)演算子は、メンバーである必要があります.複合賦値演算子は、一般にメンバーであるべきですが、必須ではありません.これは賦値演算子とは少し異なります.オブジェクトのステータスを変更する演算子、または特定のタイプに密接に関連する演算子(増分、減算、逆参照演算子など)は、通常メンバーである必要があります.対称性のある演算子は、算術、等化、関係、ビット演算子などの任意の端の演算オブジェクトを変換することができるので、通常は通常の非メンバー関数であるべきです.もちろん、割り当て演算子に加えて、ダイナミック配列の下付き演算子operator[]を定義する必要があります.下付き演算子はメンバー関数でなければなりません.下付き文字が付与演算子の任意の端に表示されるように、下付き文字演算子関数は通常、アクセスした要素の参照を返します.
四、動的配列クラスのパッケージ
動的配列DArrayクラスのインタフェースを次に示します.

class DArray 
{ 
private: 
  double *m_Data; //             
  int m_Size;   //         
  int m_Max;    //              
private: 
  void Init();   //     
  void Free();   //        
  inline bool InvalidateIndex(int nIndex); //          
public: 
  DArray();    //        
  DArray(int nSize, double dValue = 0); //     ,      ,    dValue 
  DArray(const DArray& arr); //        
  DArray& operator=(const DArray& arr); //         
  ~DArray();    //      
 
  void Print();  //              
  int GetSize();  //        (    ) 
  void SetSize(int nSize); //          , nSize     ,  ;  ,    0 
  double GetAt(int nIndex); //          
  void SetAt(int nIndex,double dValue); //          
  void PushBack(double dValue); //              
  void DeleteAt(int nIndex);   //           
  void InsertAt(int nIndex, double dValue); //              
  double operator[](int nIndex) const;   //        [] 
}; 


実装方法は次のとおりです.

void DArray::Init() 
{ 
  m_Size = 0;  //              
  m_Max = 1; 
  m_Data = new double[m_Max]; 
} 
 
void DArray::Free() 
{ 
  delete [] m_Data; 
} 
 
bool DArray::InvalidateIndex(int nIndex) 
{ 
  if(nIndex>=0 && nIndex m_Max)  /*        */ 
  { 
    m_Max = nSize; 
    double *temp = new double[m_Max]; 
    memcpy(temp, m_Data, m_Size*sizeof(double)); 
    for(int i=m_Size; im_Size) 
  { 
    cout << "Error: the index of InsertAt is invalid!" << endl; 
    exit(0); 
  } 
 
  if(m_Size < m_Max) /*   ,   */ 
  { 
    for(int i=m_Size-1; i>=nIndex; --i) 
      m_Data[i+1] = m_Data[i]; 
    m_Data[nIndex] = dValue; 
  } 
  else        /*        */ 
  { 
    m_Max = m_Max*2; 
    double* temp = new double[m_Max]; 
    memcpy(temp, m_Data, m_Size*sizeof(double)); 
    delete [] m_Data; 
    m_Data = temp; 
    for(int i=m_Size-1; i>=nIndex; --i) 
      m_Data[i+1] = m_Data[i]; 
    m_Data[nIndex] = dValue; 
  } 
  ++m_Size; /*      1 */ 
} 
 
//        [] 
double DArray::operator[](int nIndex) const 
{ 
  if(nIndex<0 || nIndex>=m_Size) 
  { 
    cout << "Error: the index in [] is invalid!" << endl; 
    exit(0); 
  } 
  return m_Data[nIndex]; 
} 

簡単なテストの結果、Bugはまだ発見されていません.テストが全面的ではない可能性があります.興味のある読者は、このプログラムをさらにテストし、改善することができます.
附:String類の実現
C++の一般的な面接問題は、時間に限られたStringクラスを実現することです.std::stringの機能を必要とすることはできませんが、少なくともリソースを正しく管理することが必要です.
上のDArrayクラスの書き方が分かれば、Stringクラスを実現するのは難しくないはずです.面接官は、コンストラクション関数、コンストラクション関数、コピーコンストラクション関数、コピー付与演算子、+、[]、<>演算子の再ロードなどを正しく書くことができるかどうかを調べたいだけです.次にStringクラスのインタフェースを示します.自分で実現してみてください.

class String{  
  friend ostream& operator<< (ostream&,String&); //  <> (istream&,String&); //  >>     
public:  
  String();  //        
  String(const char* str);    //         
  String(const String& rhs);    //         
  String& operator=(const String& rhs);  //          
  String operator+(const String& rhs) const; //operator+  
  bool operator==(const String&);       //operator== 
  bool operator!=(const String&);       //operator!=   
  char& operator[](unsigned int);       //operator[]  
  size_t size() const;  
  const char* c_str() const; 
  ~String();  //       
private:  
  char *m_data; //          
}; 


本明細書で説明するDArrayクラスとStringクラスのソースコードとテストコードは、ここをクリックして本サイトでダウンロードできます.