ホームページ >バックエンド開発 >C#.Net チュートリアル >C++ で考える第 1 巻の読書ノートの主要な要約

C++ で考える第 1 巻の読書ノートの主要な要約

php是最好的语言
php是最好的语言オリジナル
2018-07-28 11:43:281554ブラウズ

この記事は主に、『Thinking in C』第 1 巻のメモを読むことについてです。主に注意すべき点

  • ##C で考える第 2 章

    • ##翻訳者:
    • プログラムをコンパイルするためのコンパイラ手順:
    • 関数または変数の宣言と定義
    • 接続
    C で考える第 3 章
    • 関数の戻り値の説明
    • 通常の関数ライブラリ
    • for ループの説明
    • スイッチの説明
    • Specifier(指定子)
    • #void*ポインタの説明
    • 変数の有効範囲
    • #グローバル変数

    • 静的 (静的) 変数

    • #接続 (リンケージ型)

    • #前処理マクロ
    • typedef 構文の説明
    • enum の説明:
    • C で考える第 5 章

  • 友達

    • ネストされた友達
    • 他の構造
    • C で考える 第 6 章

    #コンストラクタ対応説明
  • ##delete void*
    • #デフォルトのコンストラクター

    • ##C で考える第 7 章

    • #overload

  • Type -safe linkage

    • Union
    • デフォルトのパラメータ使用規則:
    • プレースホルダー パラメータ
    • #C で考える第 8 章
    • const の説明

    const ポインタ
  • 代入と型のチェック
    • 一時変数
    • 転送アドレスと戻りアドレス
    • 標準パラメータ転送
    • クラス内データ メンバーは const 初期化されます
    • コンパイル中の定数
    • const オブジェクトとメンバー関数
    • C で考える第 9 章
    • インライン関数

    コンストラクターとデストラクターの隠された動作
  • 前処理デバイス
    • C で考える第 10 章
    • static

    静的オブジェクト
  • 内部リンク
    • その他
    • stactic/const/stctic const
    • #静的メンバー関数

    • #ポインター配列/配列ポインター

    • 関数ポインター

    • #関数アドレス

    #関数ポインター
  • 関数ポインターの呼び出し
  • 関数ポインターの配列
    • typedef はワークロードを簡素化します
    • 名前空間
    • 名前空間
    • ##名前のない名前空間
    • using ディレクティブ& using 宣言
  • new/malloc/delete/free
    • new/ malloc
    • delete/free
    • グローバル new および delete のオーバーロード
  • クラスの new および delete のオーバーロード

    • #配列のオーバーロード new と delete
    • Position new/delete
    • 継承と構成

    • 初期化式

    • コンストラクターとデストラクターの呼び出しの順序

    • 非-自動的に継承される関数

  • 継承と静的メンバー関数

    • プライベート継承
    • アップ型変換およびコピー コンストラクター
    • 演算子のオーバーロード

    • =、-=、*=、/=、=、およびその他の型演算子のオーバーロード

    • #関数のパラメーターと戻り値の説明

    • #接頭辞と接尾辞

    定数値戻り値と戻り値の最適化
  • operator[]
    • ##operator->(ポインター間接参照演算子)

    • operator-> *

    • 演算子メンバー関数の基本ガイドライン

    • コピーオンライト

    • 自動入力翻訳

    • ##C で考える第 2 章
    • 翻訳者:

    • Interpreter(interpreter)

    • Compiler

    • Compiler コンパイラの手順:

Preliminaryプロセッサ (プリプロセッサ) は、前処理命令

を処理します。コンパイルは 2 つのパスに分割されます。最初のパスでは、前処理コードを解析してノード数を生成し、グローバル オプティマイザを実行してから次の処理に進みます。 )、2 番目のパス コード ジェネレーターはコード ツリーを解析して機械語またはアセンブリ言語を生成します

  1. 静的型チェックは最初のパスで実行されます

    関数または変数の宣言と定義

    void function();//声明void function(){}//定义extern int a;//声明int a;//定义

    接続

    コンパイル プロセスの最終段階では、コンパイラによって生成されたターゲット モジュールを実行可能ファイルに接続します ( )

    C で考える第 3 章

    関数 return の説明

    return ステートメントは関数を終了し、関数呼び出し後のポイント (操作) に戻ります。 Lenovo スタックの

    通常は関数ライブラリ

    ##オブジェクト モジュールはライブラリ マネージャーによって管理されます。このライブラリ マネージャーは .lib/.a ファイルを管理します

    ## loop

    for(initialization;conditional;step)

    for ループは、まず初期化を行い、条件判定を行い、満たされていればループに入り、ループ実行後、ステップ step

    switch description## に進みます。 #

    switch(selector){
        case integral-value:statement;break;    ...
        defualt:statement;
    }

    スイッチ内のセレクターは整数値である必要があり、整数値は次のとおりである必要があります。整数値

    セレクターは列挙型の値

    # である必要があります。 ##指定子 (specifier)

    は、基本的な組み込み型の意味を変更し、基本的な型を変換するために使用されます。型はより大きなセットに展開されます
    1. int: short int / int / long int (short と long を使用する場合、int キーワードは省略できます)

    2. float/double: long float はなく、long double のみ、つまり float / double /long double のみです

    3. signed /unsigned: 符号ビット (整数型および文字型に適用)


    void* ポインタの説明

    void* ポインタは任意のタイプのアドレスとして割り当てることができますが、タイプ情報は失われます。

    変数の有効範囲

    定義点から始まり、変数が定義される前の最も近い位置まで 開き括弧のペアの最初の閉じ括弧つまり、スコープは、変数が配置されている最後の括弧のペアによって決まります。

    グローバル変数

    グローバル変数のライフサイクルはプログラムの終了までです。extern キーワードを使用すると、別のファイルでグローバル変数を使用できます

    静的 (静的) ) 変数

    静的変数の利点は、関数のスコープ外では使用できず、簡単に変更できないため、エラーが局所化されることです。静的変数が関数名と関数のすべての外部変数に適用される場合、関数の場合、これは「ファイル内ではこの名前は外部では使用できません」という意味です。つまり、

    //file1.cppstatic int fs;int main(){
        fs = 1;
    }//file2.cppextern int fs;//编译器不会找到file1.cpp文件中的fs,即文件作用域void function(){
        fs = 100;
    }
    extern static int i;int main(){    cout << i << endl;
    }static int i = 0;
    将出现error,即全局静态变量声明是有文件作用域的,编译器将会产生错误

    Link (リンク タイプ)

    のようなファイル スコープがあります。内部リンク: コンパイル中のファイルのみが保存されます。 スペース、識別子ごとに個別のストレージ スペースを作成します。内部接続はキーワード static

    1. 外部接続: 個別のストレージを作成します。すべてのコンパイル済みファイル用のスペース。つまり、すべての変数と関数がこのスペースに含まれます。

    2. 自動 (ローカル) 変数はスタック上に一時的にのみ存在します。リンカーは自動変数について認識しません。変数が接続されていません


      前処理マクロ
      #define PRINT(STR,VAR) \
          cout << STR << VAR << endl; \
          cout << VAR << STR <<endl
      
      即可以像调用函数一样调用PRINT,这里预处理宏分行使用&#39;\&#39;,宏只是展开,并替换
      
      #define PRINT(STR,VAR) \
          cout << #STR << VAR << endl
      
      这里&#39;#&#39;表示STR字符串化(stringizing),比如:int i = 0;PRINT(i,i); //这里输出应该是 i:0
    3. typedef構文の説明

    typedef既存の型の説明別名

    typedef unsinged long ulong;

    1. typedef int* intPtr

    2. #typedef struct MyStruct{

      //これは構造体ですC Changying の定義

      } MyStruct;

    3. typedef int (*fun)(int,int) //関数ポインタの別名は fun

    4. です
    5. enum description:

      c での enum のチェックはより厳密です。A (a は色の列挙) は c では許可されますが、a は 2 回変換されているため、c では許可されません。 、色の型は int に変換され、その後 1 ずつ増分されます。 、値を色に変換します。2 回目の変換中は不正です。
    C で考える第 5 章

    友達

    friend キーワードを使用してアクセスできます。内部プライベート メンバー変数またはメンバー関数

    #
    struct X;struct Y{    void f(X*);
    };struct X{    private:    int i;    public:    void initialize();    friend void g(X*,int); //Global friend
        friend void Y::f(X*); //struct member friend
        friend struct z;    //Entire struct is a friend
        friend void h();
    }

    Y::f(X*) は、X のアドレスを参照します。オブジェクト。コンパイラは、何が渡されるかに関係なく、アドレスを渡す方法を知っています。オブジェクト、アドレスは固定サイズです。オブジェクト全体を渡そうとするとき、コンパイラは、X の定義全体を知っていて、そのサイズと渡し方を決定する必要があります。不完全な型仕様を使用している、つまり、構造体 Y の前に構造体 X を宣言している;

    ネストされたフレンド
    ネストされた構造は、プライベート メンバーへのアクセスを自動的に取得できません。次のメソッドにアクセスします。

    宣言はネストされた Set 構造体です。

      構造体がグローバル スコープで使用されるフレンドであることを宣言します。
    1. 構造体の定義
    2. const in sz = 20;struct Holder{    private:    int a[sz];    public:    void initialize();    struct Pointer;
          friend Pointer;    struct Pointer{    private:
          Holder* h;    int* p;    public:    void initialize(Holder* h);    void next();    void previous();    void top();    void end();    int read();    void set(int i);
          };
      };void Holder::initialize(){
          memset(a,0,sz*sizeof(int));
      }void Holder::Pointer::initialize(Holder* rv){
          h = rv;
          p = rv->a;
      }
      ...int main(){
          Hodler h;
          Holder::Pointer hp;    int i;
          h.initialize();
          hp.initialize(&h);
          ...
      }
    3. struct Others
    4. //: Hadler.h
      class Handler{
          struct Cheshire;
          Cheshire* smile;
          public:    ...};
      //:~
      //:Handler.cpp
      struct Handler::Cheshire{
          int i;    ...}...

      Handler.h ファイル内の struct Cheshire は不完全な型の説明またはクラスです。

    Thinking in C Chapter 6

    コンストラクターに対応する命令

    class Object{public:    Object(int number = 0);private:    int m_number;
    };//:~//: mainint main(int argc, char *argv[])
    {    int i = 0;    switch(i){    case 0:        Object obj1{1};        break;    case 1:            //error: cannot jump from switch statement to this case label "case 1:"
            Object obj2{2};   //jump bypasses variable initialization "Object obj1{1};"
            break;
        }    return 0;
    }

    上記のコードはレポートを報告しますエラー ### Switch はコンストラクタのシーケンスポイントをスキップします。コンストラクタが呼び出されない場合でも、このオブジェクトは後続のプログラムブロックでも役割を果たし、ここでエラーが発生します

    オブジェクトの作成と同時に初期化されるようにするためです。 goto もこのようなエラーを生成します。

    delete void*

    void* が非組み込み型オブジェクトを指している場合、メモリのみが解放され、デストラクタは実行されません

    默认构造函数

    class Object{public:    Object(int number);private:    int m_number;
    };Object object[2] = {Object{1}};

    Object没有默认构造函数,数组声明初始化时将报错,object[1]必须有默认构造函数进行初始化,否则报错当且仅当没有构造函数时编译器会自动创建一个默认构造函数

    Thinking in C++ Chapter 7

    overload

    使用范围和参数可以进行重载

    void f();class X{void f();};

    类型安全连接(type-safe linkage)

    1.cppvoid functin(int);2.cppvoid function(char);int main(){    function(1); //cause a linker error;
        return 0;
    }

    编译成功,在C中连接成功,但是在C++中连接出错,这是C++中的一种机制:类型安全连接

    Union

    class SuperVar{    enum{
        character,
        integer,
        floating_point
        } vartype;    union{    char c;    int i;    float f;
        };    public:
        SuperVal(char ch);
        SuperVal(int ii);
        SuperVal(float ff);    void print();
    };
    
    SuperVal::SuperVali(char ch){
        vartype = character;
        c = ch;
    }
    SuperVal::SuperVali(int ii){
        vartype = integer;
        i = ii;
    }
    SuperVal::SuperVali(float ff){
        vartype = floating_type;
        f = ff;
    }void SuperVal::print(){    switch(vartype){    case character:    cout << "character:" << c <<endl;    break;    case integer:    cout << "integer :" << i <<endl;    break;    case floating_point:    cout << "float :" << f <<endl;    break;
        }
    }int main(){
        SuperVar A(&#39;c&#39;),B(12),C(1.44f);
        A.print();
        B.print();
        C.print();    return 0;
    }
    1. enum没有类型名,因为后面没有必要涉及美剧的类型名称,所以枚举类型名可选,非必须

    2. union没有类型名和标识符,称为匿名联合(anonymous union),不需要使用标识符和以点操作符方式访问这个union的元素

    3. 访问一个匿名联合成员就像访问普通变量一样,唯一区别在于:该联合的两个变量占用同一内存空间,如果匿名union在文件作用域内(在所有函数和类之外),则它必须声明为static,以使它有内部的连接

    默认参数使用规则:

    1. 只有参数列表的后部参数才可以是默认的

    2. 一旦在一个函数调用中开始使用默认参数,那么这个参数后面的所有参数都必须为默认的

    占位符参数

    void f(int i, int = 0, float = 1.1); //version 1void f(int i ,int , float flt); // version 2

    其中version 2除了i,flt之外中间参数就是占位符参数

    Thinking in C++ Chapter 8

    const说明

    C++中const默认为内部连接,仅在const被定义的文件中才可见,在连接时不能被其它编译单元看见,当定义一个const时必须赋值给它,除非使用extern进行说明

    extern const int bufsize;

    通常c++不为const创建空间,将其定义保存在符号表内,但是上面的extern进行了强制内存空间分配,另外如取const的地址也是需要存储空间的分配。

    对于复杂的结构,编译器建立存储,阻止常量折叠。在C中const默认为外部连接,C++默认为内部连接.出现在所有函数外部的const作用域是整个文件,默认为内部连接

    const指针

    1. const修饰指针正指向的对象 const int* a;

    2. const修饰在指针中的地址  int* const a;

    赋值和类型检查

    1. const对象地址不可以赋值给一个非const指针,但是可以吧一个非const对象地址赋值给一个const指针

    2. 字符数据的字面值:

    char * cp = "howdy";char cp[] = "howdy";

    指针cp指向一个常量值,即常量字符数组,数组cp的写法允许对howdy进行修改

    临时变量

    在求表达式值期间,编译器必须创建零时变量,编译器为所有的临时变量自动生成为const

    传递和返回地址

    void t(int*) {}void u(const int* clip){  //*clip = 2; error
      int i = *clip;  //int * ip2 = clip error;}const char* v(){  return "result of functin 0";
    }const int * const w(){  static int i;  return &i;
    }int main(){  int x= 0;  int * ip = &x;  const int * cip = &x;
      t(ip); //ok
      //t(cip);  not ok;
      u(ip); //ok
      u(cip);//ok
      //char * cp = v(); not ok
      const char* ccp = v();//ok
      //int * ip2 = w(); not ok
      const int * const ccip = w();// ok
      const int* cip2 = w();//ok
      //*w() = 1; not ok}
    1. const指针不可以赋值给非const指针,但是非const指针可以赋值给const指针

    2. 函数v()返回一个从字符数组的字面值中建立的const char *,在编译器建立了它并把它存储在静态存储区之后,该声明实际上产生该字符数组的字面值的地址

    3. 函数w()返回值要求这个指针以及这个指针所指向的对象均为常量,与函数v()类似,因为i是静态的,所以函数返回后返回值仍然有效

    4. const int* const w()只有在作左值时第二个const才能显现作用,所以w()返回值可以赋值给const int *

    标准参数传递

    可以将临时对象传递给const引用,但不能将一个临时对象传递给接收指针的函数,对于指针必须明确接受地址(临时变量总是const)

    class X
    {public:    X() {}
    };
    X f(){return X();}void g1(X&){ }void g2(const X&){ }int main(int argc, char *argv[])
    {
        g1(f()); //error!
        g2(f());    return 0;
    }

    类内数据成员为const初始化

    必须在构造函数的初始化列表中进行初始化

    编译期间的常量

    1. 一个内建类型的static const可以看成编译期间的常量,但是该static const必须在定义的地方进行初始化

    2. 无标记enum也可以看成为编译期间常量,一个枚举在编译期间必须有值

    class X{
      enum {size = 1000};  //same as static const
      static const int size = 1000;  int i[size];
    }

    const对象和成员函数

    1. 若将一个成员函数声明为const,则该成员函数可以被const对象调用

    2. const成员函数可以调用非const成员和const成员,非const成员函数同样可以使用const成员

    3. const对象只能调用const成员函数,非const对象调用非const成员函数

    Thinking in C++ Chapter 9

    内联函数

    内联函数与普通函数一样执行,但是内联函数在适当的地方像宏一样展开,不需要函数调用的开销(压栈,出栈),任何在类内部定义的函数自动成为内联函数

    1. 内联函数体过大时,编译器将放弃使用内联

    2. 当取函数地址时,编译器也将放弃内联

    3. 一个内联函数在类中向前引用一个还没有声明的函数时,是可以的,因为C++规定只有在类声明结束后,其中的内联函数才会被计算

    class Forward{    int i;    public:    Forward():i(0){}    int f() const {return g()+i;}    int g() const {return i;}
    }

    构造函数和析构函数隐藏行为

    class X
    {    int i,j,k;public:
        X(int x = 0):i(x),j(x),k(x) { cout << "X" <<endl;}   //X(int x):i(x),j(x),k(x){cout << "X" <<endl;}
        ~X(){cout << "~X" <<endl;}
    };class Y{
        X q,r,s;    int i;public:
        Y(int ii):i(ii){cout << "Y" <<endl;}
        ~Y(){        cout << "~Y" <<endl;
        }
    };int main(int argc, char *argv[])
    {
        Y y(1);    return 0;
    }//:~//:outputX
    X
    X
    Y
    ~Y
    ~X
    ~X
    ~X
    1. 类中包含子对象,构造函数先调用子对象的构造函数,如果没有默认构造函数,则必须在初始化列表中进行初始化,然后在调用类的构造函数

    2. 类中包含子对象,先调用类的析构函数在调用子类的析构函数

    预处理器

    1. #define DEBUG(x) cout << #x "=" << x<<endl;#:字符串化,详见REF2. #define FIELD(a) char* a##_string;int a##_size##:标志粘贴,允许设两个标识符并将它们粘贴在一起产生新的标识符

    Thinking in C++ Chapter 10

    static

    1. 在固定地址上进行存储分配,在一个特殊的静态数据区上创建,不是在堆栈上产生

    2. 对一个特定编译单位来说是局部的,static可以控制名字的可见性,该名字在这个单元或者类以外是不可见的

    static对象

    1. 如果没有为一个内建类型的静态变量提供一个初始化值,编译器会确保在程序开始时它被初始化为零(转化成适当的类型)

    2. 如果在定义一个静态对象时没有指定构造参数时,该类必须有默认的构造函数

    内部链接

    常量、内联函数默认情况下为内部链接

    others

    1. 所有全局对象隐含为静态存储

    2. 对static函数意味着只在本单元可见,成为文件静态(file static)

    stactic/const/stctic const

    1. static成员必须在类外初始化,如果不初始化,则编译器不会进行默认初始化,对于非内建类型,可以使用构造函数初始化代替“=”操作符

    2. 类内const成员必须在构造函数的初始化列表中进行初始化

    3. static const变量(内建类型)必须在声明的地方就初始化

    4. static对象数组,包括const和非const数组必须在类外部初始化

    5. 自定义class类声明为stctic,不管其为const或者非const都必须在类外初始化

    6. 类的静态成员必须进行初始化后才可以使用

    静态成员函数

    1. 类的静态成员函数不能访问一般数据成员或者函数,只能访问静态数据成员,也能调用其他静态成员函数

    2. 静态成员函数没有this指针

    指针数组/数组指针

    优先级低的先读
    *p[] 指针数组
    (*p)[] 数组指针,即指向一个数组

    函数指针

    函数地址(Function Address)

    函数的地址:函数名后不跟参数

    void fun(){}fun即为函数地址fun()为函数的调用

    函数指针

    double pam(int); //prototypedouble (*pf)(int); //function pointerpf=pam;//pf now points to the pam();

    调用函数指针

    double x=pf(5);double x=(*pf)(5);

    函数指针数组

    const double* f1(const double ar[],int n);const double* f2(const double [],int);const double* f3(coanst double *,int);//f1,f2,f3函数声明本质一样const double* (*pa[3])(const double * , int) = {f1,f2,f3};  //[]优先级高于*,所以表示pa是个数组,数组中包含三个指针auto pb = pa;const double * px = pa[0](av,3);const double * py = (*pb[1])(av,3);double x = *pa[0](av,3);double y = *(pb[1])(av,3);

    指向整个数组的指针,即是一个指针,而不是一个数组,优先级低的先读
    *p[] 指针数组
    (*p)[] 数组指针,即指向一个数组

    const double*  (*(*pd)[3])(const double* , int) = &pa;->等价形式 auto pd = &pa;

    pd指向数组,*pd就是数组,而(*pd)[i]是数组中的元素,即函数指针
    函数调用:

    (*pd)[i](av,3)->此处返回const double *
    *(*pd)[i](av,3)->此处返回 double
    另外一种函数调用略复杂(*(*pd)[i])(av,3)->返回const double *
    *(*(*pd)[i])(av,3)->返回 double

    typedef简化工作量

    typedef const double* (*p_fun)(const double* ,int);
    p_fun p1 = f1;
    p_fun pa[3] = {f1,f2,f3};
    p_fun (*pd)[3] = &pa;

    namespace

    namespace

    1. namespace只能在全局范围内定义,但是可以相互嵌套

    2. 在namespace定义的结尾,右花括号后面不必跟一个分号

    3. 可以按类的语法来定义一个namespace,定义的内容可以在多个头文件中延续,就好像重复定义这个namespace

    //:header1.h
    namespace MyLib{
        extern int x;
        void f();
        //...}
    //:~
    //:header2.h
    namespace MyLib{
        extern int y;
        void g();
        //...}
    1. 一个namespace的名字可以用另一个名字来作为它的别名
      namespace lib = MyLib;

    2. 不能像类一样创建一个名字空间的实例

    未命名的名字空间

    namespace {
        class A{};
        class B{};
        int i,j,k;
        //...}

    将局部名字放在一个未命名的名字空间中,不需要加上static就可以作为内部连接

    using directive& using declaration

    using directive:using namespace xxusing declaration:using xx::f;

    new/malloc/delete/free

    new/malloc

    new计算内存大小,并调用构造函数,malloc需要手工计算内存大小,不调用构造函数

    delete/free

    delete先执行析构函数,在清空内存,free直接清空内存
    delete用于void*时将不会调用析构函数,直接清空内存

    重载全局new和delete

    1. 重载的new必须有一个size_t参数,该参数由编译器产生并传递给我们,分配内存的长度,必须返回一个指向等于该长度的对象的指针,如果没有找到存储单元,则返回一个0,然而如果找不到存储单元,不能仅仅返回0,还应该有new-handler或产生一个异常信息

    2. new返回void*,而不是指向任何特定类型的指针,只需要完成内存分配,而不是完成对象的创建,直到构造函数调用才能完成对象的创建,调用构造函数是编译器完成的

    3. delete参数是由new分配的void*指针,该参数是在调用析构函数后得到的指针,析构函数从存储单元中移去对象

    #include <cstdio>#include <cstdlib>using namespace std;void* operator new(size_t sz){    printf("operator new:%d Bytes\n",sz);    void* m = malloc(sz);    if(!m) puts("out of memry");    return m;
    }void operator delete(void* m){    puts("operator delete");    free(m);
    }class S{    int i[100];public:
        S(){puts("S::S()");}
        ~S(){puts("S::~S()");}
    };int main(int argc, char *argv[])
    {    int * p = new int(47);    delete p;
        S* s = new S;    delete s;
        S* sa = new S[3];    delete [] sa;    return 0;
    }//:outputoperator new:4 Bytesoperator deleteoperator new:400 Bytes
    S::S()
    S::~S()operator deleteoperator new:1208 Bytes
    S::S()
    S::S()
    S::S()
    S::~S()
    S::~S()
    S::~S()operator delete

    这里使用printf(),puts()等函数,而不是iostreams,因为使用iostreams对象时(全局对象cin,cout,cerr),调用new分配内存,printf不会进入死锁状态,它不调用new来初始化自身

    类重载new和delete

    //p328

    主要思想:使用static数组以及一个bool数组,返回static数组的下标地址,进行new,得到static下标数组进行delete
    void* operator new(size_t) throw(bad_alloc);
    void operator delete(void*);

    为数组重载new和delete

    //p331

    定位new/delete

    //p333

    主要思想:使用new运算符重载,但是不对delete运算符重载,指定内存位置new
    void* operator new(size_t,void*);

    继承与组合(Inheritance&Composition)

    初始化表达式

    1. 成员对象如果没有默认的构造函数,必须显示的进行初始化,即在构造函数初始化列表中进行初始化

    2. 只有执行成员类型初始化之后,才会进入构造函数

    3. 没有对所有成员以及基类对象的构造函数调用之前,若基类没有默认构造函数则必须初始化,即在初始化列表中进行初始化,否则无法进入构造函数体

    构造函数和析构函数调用的顺序

    1. 构造函数调用的顺序:首先调用基类构造函数,然后调用成员对象的构造函数,调用成员对象构造函数的顺序是按照成员对象在类中声明的顺序执行,最后调用自己的构造函数

    2. 析构函数调用次序与构造函数调用次序相反,先调用自己的析构函数,在调用成员函数的析构函数,最后调用基类析构函数

    3. 对于多重继承,构造函数调用顺序为继承时的声明顺序

    非自动继承的函数

    1. 构造函数

    2. 析构函数

    3. operator=

    继承与静态成员函数

    静态成员函数与非静态成员函数的共同特点:
    1. 均可以被继承到派生类中
    2. 重新定义一个静态成员,所有基类中的其他重载函数会被隐藏
    3. 如果我们改变了基类中的函数的特征,所有使用该函数名字的基类版本将会被隐藏。
    4. 静态成员函数不可以是虚函数

    私有继承

    1. 使用私有继承是为了不允许该对象的处理像一个基类对象,一般private更适合于组合

    2. 私有继承成员公有化

    class Base{    public:    Base(){}
        ~Base(){}    void name() {cout << "Base name" <<endl;}
    }
    class Derived:private Base{    public:    Derived(){}
        ~Derived(){}    using Base::name; //私有继承公有化}

    向上类型转换和拷贝构造函数

    class Base{    public:    Base(){}
        Base(const Base&){}
        ~Base(){}    void name() {cout << "Base name" <<endl;}
    }
    class Derived:public Base{    public:    Derived(){}
        Derived(const Derived& d):Base(d){ }//这里是调用基类的拷贝构造函数
        ~Derived(){}
    }

    基类拷贝构造函数的调用将一个Derived引用向上类型转换成一个Base引用,并且使用它来执行拷贝构造函数,向上类型转换是安全的

    运算符重载

    +=,-=,*=,/=,=等一类运算符重载

    首先进行自我检查(是否对自身赋值),即this == &val,在进行运算,“=”运算符只允许作为成员函数进行重载

    函数参数和返回值说明

    1. 对于任何函数参数,如果仅需要从参数中读而不改变它,默认地应当做const引用传递。普通算数运算符(像“+”,“-”)和bool运算符不会改变参数,所以const引用为主要传递方式,当函数时成员函数时,就转换为const成员函数。只有会改变左侧参数的运算符赋值(如+=)和operator=,左侧参数不是常量,但因参数将被改变,所以参数仍然按地址传递

    2. 返回值类型取决于运算符的具体含义,如果使用该运算符产生一个新值,就需要产生一个作为返回对象的新对象。例如operator+必须生成一个操作数之和的对象,该对象作为一个常量通过返回值返回,所以作为一个左值不会被改变

    3. 所有赋值运算符均改变左值,为了使赋值结果能用于链式表达式(如a=b=c),应该能够返回一个刚刚改变了的左值的引用。但该引用并非一定要是常量引用,如(a=b).f(),这里b赋值给a,a调用成员函数f,因此所有赋值运算符的返回值对于左值应该是非常量引用(如果是常量引用,则f成员函数必须为const函数,否则无法调用该函数,这与愿望相违背)

    4. 对于逻辑运算符,人们至少希望得到一个int返回值,或者最好是bool值

    Prefix ++ & Postfix ++

    1. 成员函数

    const Object& operator++(){}const Object operator++(int){}
    1. 友元函数

    const Object& operator++(Object& obj){}const Object operator++(Object& obj,int){}

    对于友元函数重载来说,因为传入的Object对象被改变,所以使用非常量引用
     前缀通过引用返回,后缀通过值(临时对象)返回,因为后缀返回临时对象,所以后缀通过常量值返回,前缀返回引用,如果希望可以继续改变对象则返回引用,否则通过常量引用返回比较合适,这样与后缀保持了一致性

    常量传值返回与返回值优化

    1. 作为常量通常通过传值方式返回。考虑二元运算符+,假设在表达式f(a+b)中使用,a+b的结果变为一个临时对象(Object),该对象被f()调用,因为它为临时的,所以自动被定义为常量,所以无论是否返回值为常量都没有关系。但是如果使用(a+b).f(),这里设返回值为常量规定了对于返回值只有常量成员函数才可以调用

    2. 返回值优化通过传值方式返回要创建的的新对象时,注意使用的形式,如operator+

    version 1:return Object(lObj.i+rObj.i);
    version 2:
    Object tmp(lObj.i+rObj.i);return tmp;

    version 2将会发生三件事,首先创建tmp对象,然后调用拷贝构造函数把tmp拷贝到外部返回值的存储单元中,最后当tmp在作用域的结尾时调用析构函数
     version 1编译器直接将该对象创建在外部返回值的内存单元,不是整的创建一个局部变量所以仅需要一个普通的构造函数调用(不需要拷贝构造函数),且不会调用析构函数,效率高。这种方式被称为返回值优化。

    operator[]

    该运算符必须是成员函数,而且只接受一个参数,可以返回一个引用,可以用于等号左侧。

    operator->(指针间接引用运算符)

    该运算符一定是一个成员函数,它必须返回一个对象(或者引用),该对象也有一个指针间接引用运算符;或者必须返回一个指针,被用于选择指针间接引用运算符箭头所指的内容

    class Obj{    static int i,j;    public:    void f() const {cout<<i++<<endl;}    void g() const {cout<<j++<<endl;}
    };int Obj::i = 47;int Obj::j = 11;class ObjContainer{    vector<obj* >a;    public:    void add(Obj* obj) {a.push_back(obj);}    friend class SmartPointer;
    };class SmartPointer{
        ObjContainer& oc;    int index;    public:
        SmartPointer(ObjContainer & obj):oc(obj){
        index = 0;
        }    bool operator++(){    if(index >= oc.a.size()) return false;    if(oc.a[++index] == 0) return false;    return true;
        }    bool operator++(int){    return operator++();
        }
        Obj* operator->() const{    return oc.a[index];
        }
    };

    指针间接引用运算符自动的为用SmartPointer::operator->返回的Obj*调用成员函数

    class Obj{    static int i,j;    public:    void f() const {cout<<i++<<endl;}    void g() const {cout<<j++<<endl;}
    };int Obj::i = 47;int Obj::j = 11;class ObjContainer{    vector<obj* >a;    public:    void add(Obj* obj) {a.push_back(obj);}    class SmartPointer; //声明友元之前必须告知该类存在
        friend SmartPointer;    class SmartPointer{
            ObjContainer& oc;        int index;    public:
        SmartPointer(ObjContainer & obj):oc(obj){
            index = 0;
        }    bool operator++(){        if(index >= oc.a.size()) return false;        if(oc.a[++index] == 0) return false;        return true;
        }    bool operator++(int){        return operator++();
        }
        Obj* operator->() const{        return oc.a[index];
        }
        };
        SmartPointer begin(){    return SmartPointer(*this);
        }
    };

    operator->*

    //p294

    运算符成员函数基本方针

    1. 所有一元运算符建议为成员

    2. =()[]->->*必须为成员

    3. += -= /= *= ^= &= |= %= >>= <<=建议为成员

    4. 所有其他二元运算符为非成员

    copy-on-write

    //p301引用计数

    自动类型转换

    • 构造函数转换:构造函数能把另外一个类型对象(或者引用)作为它的单个参数,该构造函数允许编译器执行自动类型转换

    • 运算符转换:运算符重载,创建一个成员函数,该函数通过关键字operator后跟随想要转换的类型的方法,将当前类型转换为希望的类型,自动类型转换只发生在函数调用值中,而不在成员选择期间

    class Three{    int i;    public:    Three(int ii = 0,int = 0):i(ii){}
    };
    class Four{    int x;    public:    Four(int xx):x(xx){}    operator Three() const {return Three(x);}
    };void g(Three){}int main(){
        Four four(1);
        g(four);
        g(1);
    }
    • 二义性错误

    class Orange;
    class Apple{    public:    operator Orange() const;
    };
    class Orange{    public:    Orange(Apple);
    };void f(Orange){}int main(){
        Apple a;    // f(a);  error:二义性错误}
    • 扇出错误:提供不止一种类型的自动转换

    class Orange{};class Pear{};class Apple{
        public:
        operator Orange() const;
        operator Pear() const;
    };void eat(Orange);void eat(Apple);int main(){
        Apple a;    //eat(a); error:扇出错误}

    相关文章:

    【读书笔记】精通CSS 第二版

    《深入理解bootstrap》读书笔记:第一章 入门准备

    相关视频:

    李炎恢PHP视频教程第一季

以上がC++ で考える第 1 巻の読書ノートの主要な要約の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。