首頁 > 後端開發 > C#.Net教程 > Thinking in C++ 第一卷閱讀全書筆記重點總結

Thinking in C++ 第一卷閱讀全書筆記重點總結

php是最好的语言
發布: 2018-07-28 11:43:28
原創
1551 人瀏覽過

本文主要是閱讀Thinking in C 第一卷的一些筆記。主要是一些注意點

  • Thinking in C Chapter 2

    • Translator:

    • 編譯器編譯程式步驟:

    • 函數或變數的宣告與定義

    • 連接

  • Thinking in C Chapter 3

    • #函數傳回說明

    • 通常函數函式庫

    • 關於for迴圈的說明

    • switch說明

    • 說明詞(specifier)

    • void*指標說明

    • 變數的有效作用域

    • 全域變數

    • 靜態(static)變數

    • 連接(linkage種類)

    • 預處理巨集

    • typedef語法說明

    • enum說明:

  • Thinking in C Chapter 5

    • #友元

    • #嵌套友元

    • #struct其他

    • #Thinking in C Chapter 6
    #建構子對應說明
  • ##delete void*
    • #預設建構子

    • Thinking in C Chapter 7
    • overload
    • #類型安全連線(type-safe linkage)
    • #Union
  • 預設參數使用規則:
    • 佔位參數

    • Thinking in C Chapter 8
    • const說明
    • const指標
    • 賦值與型別檢查
    • 暫存變數
    • 傳遞與傳回位址
    • 標準參數傳遞
    • #類別內資料成員為const初始化
  • #編譯期間的常數
    • const物件和成員函數

    • Thinking in C Chapter 9
    • 內聯函數
  • 建構子與析構函式隱藏行為
    • 預處理器

    • Thinking in C Chapter 10
    • static
    • ##static物件
    • 內部連結
    • others
  • stactic/const/stctic const

    • ##靜態成員函數
    • 指標陣列/陣列指標

    • 函數指標

    • 函數位址(Function Address)

    • #函數指標

  • 呼叫函數指標

    • #函數指標陣列
    • typedef簡化工作量
    • namespace

    #namespace
  • 未命名的名字空間
    • using directive& using declaration
    • #new/malloc/delete/free
    • new/malloc

    • delete/free

    • 重載全域new和delete

    • 類別重載new和delete

    • 為陣列重載new和delete

    • 定位new/delete

    • #繼承與組合(Inheritance&Composition)

初始化表達式

建構子和析構函式呼叫的順序

  1. 非自動繼承的函式

  2. #繼承與靜態成員函數

私人繼承

  1. #向上類型轉換與拷貝建構子

######################################### #########運算子重載############### =,-=,*=,/=,=等一類運算子重載######## #####函數參數與傳回值說明############Prefix & Postfix ############常數傳值傳回與傳回值最佳化#### ########operator[]############operator->(指標間接引用運算子)############operator-> *############運算子成員函數基本方針############copy-on-write############自動類型轉換#####################Thinking in C Chapter 2######Translator:###########解解釋器( interpreter)############編譯器(complier)#############編譯器編譯程式步驟:############預處理器(preprocessor)處理預處理指令#############編譯分為兩遍,第一遍解析預處理程式碼產生節點數,在進行第二步之前進行全域最佳化(global optimizer ),第二遍程式碼產生器(code generator)解析程式碼樹產生機器語言或組合語言################靜態型別檢查在第一遍中進行###

函數或變數的宣告與定義

void function();//声明void function(){}//定义extern int a;//声明int a;//定义
登入後複製

連線

編譯過程的最後階段,把編譯器產生的目標模組連結成執行檔(作業系統可以識別)

Thinking in C Chapter 3

函數傳回說明

return語句退出函數,回到函數呼叫後的那點,聯想堆疊的動作

#通常函數庫

由庫管理器來管理物件模組,這個庫管理器就是管理.lib/.a檔案的

關於for循環的說明

for(initialization;conditional;step)
登入後複製

for循環先執行initialization,其次判斷conditional,滿足則進入循環,執行完循環進行step步驟

switch說明

switch(selector){
    case integral-value:statement;break;    ...
    defualt:statement;
}
登入後複製

switch中的selector必須為整數值,integral-value必須為整形數值

selector也可以為enum類型值

#說明符(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關鍵字來使用另一個檔案中的全域變數

靜態(static)變數

static變數優點是它在函數範圍之外是不可用的,不可以輕易改變,使錯誤局部化,當應用static於函數名和所有函數外部變數時,它的意思是「在檔案的外部不可以使用該名字”,即擁有文件作用域如

//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,即全局静态变量声明是有文件作用域的,编译器将会产生错误
登入後複製

連接(linkage種類)

  1. 內部連接:只對正在編譯的文件穿件存儲空間,為每個識別碼建立單獨的儲存空間,內部連接由關鍵字static指定

  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
登入後複製

typedef語法說明

typedef existing-type-description alias-name

  1. ##typedef unsinged long ulong;

  2. typedef int* intPtr

  3. typedef struct MyStruct{

    //這裡是C常營的結構定義
    } MyStruct;

  4. typedef int (*fun)(int,int) //函數指標別名為fun

enum說明:

c 中對enum的檢查更為嚴格,c中允許a (a為color型枚舉),但c 中不允許,因為a 做了兩次轉換,首先將color類型轉換為int,然後自增1之後,將該值在轉換成color,第二次轉換時非法的

Thinking in C Chapter 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的全部定義以確定它的大小以及如何傳遞,使用不完全類型說明(incomplete type specification),即在struct Y之前聲明struct X ;

嵌套友元

嵌套結構不能自動取得存取private成員權限,可以使用下列方法存取

  1. 聲明嵌套結構

  2. 聲明該結構是全域範圍內使用的一個friend

  3. 定義該結構

  4. 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);
        ...
    }
    登入後複製
struct其他

//: 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视频教程第一季

以上是Thinking in C++ 第一卷閱讀全書筆記重點總結的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板