《C++ Primer》 Part III(Classes and Data Abstraction)

29689 ワード

1、構造関数:
コンストラクション関数の作業は、各オブジェクトのデータ・メンバーに適切な初期値があることを保証することです.
class Father

{

    public:

        Father(int _i):i(_i),j(3){}   //      ,               。

    private:

        int i;

        int j;

};

クラスの定義は、クラス定義後にオブジェクト定義のリストを受け取ることができるため、セミコロンで終了する必要があります.
class Father{/*...*/};

class Father{/*...*/} obj1,obj2;

しかし、通常、オブジェクトをクラス定義の一部として定義するのは悪い考えです.
 
2、メンバー関数:
クラスの内部で定義された関数はデフォルトでinlineです.クラスの外部で定義されたメンバー関数は、クラスの役割ドメインにあることを示す必要があります.メンバー関数には、呼び出し関数のオブジェクトに関数をバインドする追加の暗黙の実パラメータがあります.キーワードconstをパラメータテーブルに追加すると、メンバー関数を定数として宣言できます.constメンバーは、操作するオブジェクトのデータメンバーを変更することはできません.constは宣言と定義に同時に表示される必要があります.どちらかにのみ表示されると、コンパイル時にエラーが発生します.
 
3、クラスタイプのオブジェクトを定義する:
クラスの名前を直接タイプ名として使用するか、キーワードclassまたはstructを指定し、次にクラスの名前を付けます.
Father obj1;

class Father obj2;

2つの参照クラスタイプメソッドは等価です.第2の方法はCから継承され,C++で依然として有効である.
 
4、thisポインタを使用するタイミング:
メンバー関数内でthisを明示的に参照する必要はありませんが、オブジェクトを参照するメンバーではなく、オブジェクトを全体的に参照する必要がある場合には、このようにする必要があります.最も一般的な場合は、このような関数でthisを使用します.この関数は、関数を呼び出すオブジェクトへの参照を返します.
Father& get()

{

    return *this;

}

 
5、const関数のリロード:
class Father

{

    public:

        Father():j(3){}

        void setj(int _j){j = _j;}

        void setj(int _j) const{cout<<"this is const fun!"<<endl;}



    public:

        int j;

};



int main()

{

    Father f1;

    const Father f2;

    f1.setj(10);

    f2.setj(10);

    cout<<"j: "<<f1.j<<endl;

    cout<<"j: "<<f2.j<<endl;

}

 
6、可変データメンバー:
class Father

{

    public:

        void setj(int _j) const{j = _j;}

    public:

        mutable int j;

};



int main()

{

    Father f;

    f.setj(10);

    cout<<"j: "<<f.j<<endl;

}

mutableとして宣言されたメンバー変数は、constオブジェクトのメンバーである場合でもconstにはなりません.したがって、constメンバー関数はmutableメンバーを変更できます.
 
7、関数の初期化リストを構築する必要がある場合があります.
class Father

{

    public:

        Father(int _j,int _i):j(_j){i = _i;}  

        //Father(int _j,int _i){j=_j;i=_i;}   //Error        ,       

        int getj(){return j;}

    public:

        int i;

        const int j;

};

constオブジェクトまたは参照タイプのオブジェクトを初期化できますが、値を割り当てることはできません.constまたは参照タイプのデータ・メンバーを初期化する唯一の機会は、コンストラクション関数の初期化リストです.コンストラクション関数の初期化リストには、メンバーの初期化に使用する値のみが指定され、これらの初期化の実行順序は指定されません.メンバーが初期化される順序は、メンバーを定義する順序であり、上記のプログラムは、iとjを順次初期化する
 
8、デフォルトの実パラメータを使用して関数を構築し、コードの重複を減らすことができます.
class Father

{

    public:

        //Father(int _i = 10,int _j):i(_i),j(_j){}   //             

        Father(int _i,int _j = 10):i(_i),j(_j){}

        int i,j;

};

int main()

{

    Father f(3);

    cout<<"i: "<<f.i<<"
j:
"<<f.j<<endl; }

すべてのパラメータにデフォルトの実パラメータを提供するコンストラクション関数も、デフォルトのコンストラクション関数を定義することに相当します.各コンストラクション関数は、組み込み型または複合型の各メンバーに初期化式を提供する必要があります.組み込み型または複合型のメンバーのコンストラクション関数が初期化されていない場合、それらのメンバーは未定義の状態になります.割り当てられたターゲットとして使用する以外は、定義されていないメンバーを任意の方法で使用するのは間違っています.
 
9、コンストラクション関数の暗黙的な変換:
class Father

{

    public:

        Father(int _i):i(_i),j(10){}

        //Father(int _i,int _j = 10):i(_i),j(_j){}  //           

        int i,j;

};

int main()

{

    Father f = 20;    //    Father f(20);                 ,         explicit       ,            。

  //cout<<"i:"<<Father(20).i<<endl;   //    explicit      ,          ,      

    cout<<"i: "<<f.i<<"
j:
"<<f.j<<endl; }

この変換を抑制すると、関数を構築する前にexplicitキーワードを使用できます.このキーワードは、クラス内のコンストラクション関数宣言でのみ使用できます.クラスの定義体の外部で行った定義を繰り返す必要はありません.
 
10、クラスメンバーの明示的な初期化:
クラスメンバー全体がpublicの場合、User user={1,“name”,“pwd”}を構造体のように使用できます.を選択して設定できます.ただし、メンバーを追加または削除すると、変更箇所が非常に多い可能性がありますので、お勧めしません.
 
11、友元は、通常の非メンバー関数、または前に定義した他のクラスのメンバー関数、またはクラス全体であってもよい.クラスを友元に設定すると、友元クラスのすべてのメンバー関数が友元関係を付与するクラスの非公有メンバーにアクセスできます.
class Father

{

    public:

        Father(int _i,int _j):i(_i),j(_j){}

        friend void print();   //        friend

    private:

        int i,j;

};

void print()   //   Father      ,            

{

    Father f(10,20);

    cout<<"i:"<<f.i<<"   j:"<<f.j<<endl;

}

int main()

{

    print();

}

 
12、staticメンバーは、クラスタイプオブジェクトの構成部分ではなく、任意のオブジェクトとは独立して存在します.
クラスが共有するstaticデータメンバーを定義できるように、クラスはstaticメンバー関数を定義することもできます.staticメンバー関数にはthisパラメータはありません.所属するクラスのstaticメンバーに直接アクセスできますが、非staticメンバーを直接使用することはできません.クラスの任意のオブジェクトは、クラスのstaticメンバーにアクセスできます.また、ドメインオペレータを使用してクラスからstaticメンバーを直接呼び出すこともできます.static関数にはthisポインタがありません.staticメンバーはオブジェクトのコンポーネントではないため、staticメンバー関数はconstとして宣言できません.結局、メンバー関数をconstと宣言することは、その関数が属するオブジェクトを変更しないことを約束します.最後にstaticメンバー関数も虚関数として宣言できません.staticデータメンバーは一般的にクラス定義体では初期化されませんが、例外として、初期化式が定数式である限り、クラス全体のconst staticデータメンバーはクラスの定義体で初期化できます. 
//static           

    class Screen {

     public:

         // bkground refers to the static member

         // declared later in the class definition

         Screen& clear(char = bkground);

     private:

         static const char bkground = '#';
      //int i = 3; //Error! ‘i’
};

 
13.コンストラクション関数をコピーする必要がある場所:
1).一方のオブジェクトが関数体2に値伝達される).一方のオブジェクトが値伝達されるように関数から戻る3).一方のオブジェクトは他方のオブジェクトによって初期化される必要がある.
 
14、直接初期化と複製初期化は、内蔵タイプに違いはありません.ただし、クラスタイプでは、直接初期化は対応するコンストラクション関数を呼び出し、レプリケーション初期化はまず指定したコンストラクション関数を使用して一時オブジェクトを作成し、次にレプリケーションコンストラクション関数を使用してその一時オブジェクトを作成中のオブジェクトにレプリケーションします.
int i(10);    

int i = 10;

string str(5,"a");   //        

string str = "aaaaa";   //         ,           。      ,                     ,           。

 
15、デフォルトのレプリケーションコンストラクタを無効化または書き換えます.
class Father

{

    private:   //      ,               private。  ,              。                 ,       (private)            。
Father(const Father& f){ i = f.i; j = f.j;}; //explicit public: Father(int _i,int _j):i(_i),j(_j){} public: int i,j; }; int main() { Father f1(10,20); Father f2 = f1; // =, 。 :Father f2(f1); // cout<<"i:"<<f2.i<<" j:"<<f2.j<<endl; }

コピーを許可しないクラスオブジェクトは、参照として関数に渡すか、関数から返すか、コンテナの要素として使用できません.
 
16.独自の構造関数を記述しても、合成構造関数は実行されます.クラスが構造関数を明示的に記述する必要がある場合は、オペレータの割り当てと構造関数のコピーも必要です.これは有用な経験則です.このルールは、構造関数が必要な場合、この3つのレプリケーション制御メンバーをすべて必要とする3つの法則と呼ばれます.クラスにポインタメンバーがない場合、通常は構造関数を解析する必要はなく、デフォルトのレプリケーション構造関数とoperator=を使用して要件を満たすことができます.クラスにポインタメンバーがある場合は、構造関数を解析し、構造関数とoperator=を自分で書く必要があります.デフォルトのレプリケーションコンストラクション関数は、浅いレプリケーションであり、ポインタが直接レプリケーションされ、あるオブジェクトが破棄されると、別のオブジェクトのポインタメンバーが懸垂ポインタになります.
 
17、ポインタを含むクラスは、ポインタが指すオブジェクトをコピーしないため、ポインタをコピーするときにポインタのアドレスだけをコピーするため、コピー制御に特に注意する必要があります.2つのポインタが同じオブジェクトを指している場合、1つのポインタを使用して1つのオブジェクトを削除する可能性がある場合、別のポインタのユーザーは、ベースオブジェクトがまだ存在していると考え、サスペンションポインタをもたらします.この問題を解決するために、いわゆるスマートポインタのクラスを定義することができ、その汎用技術は使用カウントを採用することである.インテリジェントポインタクラスは、カウンタをクラスが指すオブジェクトに関連付けます.同じポインタを共有するオブジェクトの数をカウントで追跡します.カウントが0の場合、オブジェクトを削除します.使用カウントは参照カウントとも呼ばれる場合があります.
class Use_Count_Father

{

    friend class Father;

    Use_Count_Father(int *p_):p(p_),use(1){}

    ~Use_Count_Father(){delete p;}

    size_t use;

    int *p;   //  Father                  。
};
class Father { public: Father(int* p_):ucf(new Use_Count_Father(p_)){} ~Father(){if(--ucf->use == 0) delete ucf;} Father(const Father& f):ucf(f.ucf){++ucf->use; } Father operator=(const Father&); private: Use_Count_Father *ucf; }; int main() { int i = 10; Father f1(&i); // Use_Count_Father delete p; , 。 }

 
18.ポインタメンバーを処理するもう一つの全く異なる方法は、ポインタメンバーに値の意味を提供することである.値型オブジェクトをコピーすると、別のメモリが割り当てられます.stringクラスは値型クラスの一例である.実は深くコピーしています.
 
19.リロード可能なオペレータ:
+
-
*
/
%
^
&
|
~
!
,
=
<
>
<=
>=
++
--
<<
>>
==
!=
&&
||
+=
-=
/=
%=
^=
&=
|=
*=
<<=
>>=
[]
()
->
->*
new
new []
delete
delete []
再ロードできないオペレータ:
::
.*
.
?:
そしてnew、delete、sizeof、typeid、static_cast、const_cast、synamic_cast、reinterpret_cast
 
20.リロードオペレータは、少なくとも1つのクラスタイプまたは列挙タイプのオペランドを持つ必要があります.このルールでは、リロードオペレータは、組み込みタイプのオブジェクトに使用されるオペレータの意味を再定義できません.
21、ネーミング操作を作成するのではなく、リロードオペレータを使用することで、プログラムをより自然で直感的にすることができますが、オペレータのリロードを乱用することで、クラスが理解しにくくなります.
class Father

{

    public:

        Father(int i_):i(i_){}

        int operator+(Father &f);

        friend ostream& operator<<(ostream &os,Father &f);

    private:

        int i;

};

int Father::operator+(Father &f)

{

    return i + f.i;

}

ostream& operator<<(ostream &os,Father &f)

{

    os<<f.i;

    return os;

}

int main()

{

    Father f1(13);

    Father f2(7);

    cout<<f1+f2<<endl;     //20

    cout<<f1<<"\t"<<f2<<endl;          //13     7

}

 
22、組み込みタイプの自動初期化について.
 1   #include <iostream>

 2   #include <string>

 3 

 4   using namespace std;

 5 

 6   class Test

 7   {

 8       public:

 9           int get_j(){return j;}

10       private:

11           int j;

12   };

13   int i1;

14   int array1[3];

15   string str1;  //

16   int main()

17   {

18       int i2;

19       int array2[3];

20       string str2;

21       cout<<"i1:"<<i1<<endl;   //0

22       cout<<"i2:"<<i2<<endl;   //   

23       cout<<"---------------------"<<endl;

24       cout<<"array1[1]:"<<array1[1]<<endl;  //0

25       cout<<"array2[2]:"<<array2[2]<<endl;  //   

26       cout<<"---------------------"<<endl;

27       cout<<"str1:"<<str1<<endl;   //""

28       cout<<"str2:"<<str2<<endl;   //""

29       cout<<"---------------------"<<endl;

30       Test test;

31       cout<<"j:"<<test.get_j()<<endl;  //   

32   }

 
23.呼び出しオペレータ(関数オブジェクト)と変換オペレータ:
#include <iostream>



using namespace std;



class Test

{

    public:

        void operator()(int i)

        {

            cout<<"operator()"<<endl;

        }

        operator int() const

        {

            cout<<"operator int()"<<endl;

            return id;

        }

    int id;

};



int main()

{

    Test test;

    test(3);    //    

    test.id = 10;

    cout<<test<<endl;   //    

}

呼び出しオペレータのクラスが定義され、通常は関数オブジェクト、すなわち動作類似関数のオブジェクトと呼ばれます.