1. 程式人生 > >《Effective C++》讀書筆記一

《Effective C++》讀書筆記一

1、C++中std是什麼意思?

摘自:https://blog.csdn.net/calvin_zhou/article/details/78440145

在程式中像vector ,cout,這類東西都是在std內,有時會忽略std::,你得自己認清

 

2、

 

3、儘量以Const、enum、inline替換#define

舉例:定義常量可以用define方式

#define ASPECT_RATIO 1.653

不好之處:

ASPECT_RATIO無型別,在進行預處理階段只有ASPECT_RATIO被替換成1.653的過程,不會進行型別安全檢查

改成:

const double AspectRatio = 1.653

好處:

AspectRatio有型別double,在編譯階段進行型別安全檢查

如果定義常量指標,記得*前後都要加const

const char* const authorName = "Scott Meyers";

用inline函式代替巨集函式

#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))

改成(至於原因有點複雜,檢視https://blog.csdn.net/u013517637/article/details/51154422

template<typename T>
inline void callWithMax(const T& a,const T& b)
{
	f(a>b?a:b);
}

建議傳參採用const 引用形式,提高效率

最後請記住:

1、對於單純常量,最好以const物件或enums替換#defines

2、對於形似函式的巨集,最好改用inline函式替換#define

總結:

有了consts, enums、inlines 我們對前處理器(特別是#define)的需求降低了,但並非完全消除,#include仍然是必需品,#ifdef/#ifndef也繼續扮演控制編譯的重要角色。

最後瞭解下前處理器和編譯器:

從原始碼到獲取到可執行程式大致流程如下所示:

Step1:原始碼(source code) 

Step2:前處理器(preprocessor)

Step3:編譯器(compiler)

Step4:目的碼(object code)

Step5:連結器(Linker)

Step6:可執行檔案(executables)

前處理器:

早於 編譯器。

工作內容:刪除註釋、包含(include)其他檔案以及巨集替換等

常用的預處理有:

#include 功能:包含標頭檔案、#if 功能:條件、#else 功能:否則、#elif 功能:否則如果、#endif 功能:結束條件、#ifdef 功能如果定義了一個符號,就執行操作,等效#if !defined、#define 功能:定義一個符號、#undef 功能:刪除一個符號

編譯器

工作時間:晚於前處理器

工作任務:語法分析、語義分析等,最後生成目標檔案

摘自:https://blog.csdn.net/u012421852/article/details/51167668?utm_source=copy

 

4、儘可能使用const

(1)const修飾函式(函式內不能修改成員的值且不能呼叫非const函式)

int get() const
{ 
    //get函式返回i的值,不需要對i進行修改,則可以用const修飾。防止在函式體內對i進行修改。
	return i;
}

將成員函式設為const又分為兩個概念:

第一種:bitwise constness,認為成員函式設為const,那麼不能改變物件的任何成員變數(static除外)

第二種:logical constness,一個const成員函式可以修改它所處理的物件的某些bits,但要保證在使用者使用中偵測不出。

 

對於第一種bitwise constness,有個特殊情況,雖然將operator[]宣告為const成員函式,如下:

但是外部可以通過指標指向的值可以改變,與常量物件矛盾,如下:

解決方法:

 

可使用mutable關鍵字解除bitwise constness,例如 mutable char *mPointer;

在程式中:

(2)const修飾函式引數(在函式內不能修改指標所指內容,起到保護作用)

void fun(const char * src, char * des){  //保護源字串不被修改,若修改src則編譯出錯。
	strcpy(des,src);
}

(3)如果引數是引用,為了避免函式通過引用修改實參的內部資料成員,加const來保護實參

void h(const A & a){
}

(4)const修飾函式返回值

也是用const來修飾返回的指標或引用,保護指標指向的內容或引用的內容不被修改,也常用於運算子過載。歸根究底就是使得函式呼叫表示式不能作為左值。

#include <iostream>  
using namespace std;    
class A 
{
private:	
    int i;
public:	
    A(){i=0;}	
    int & get()
    {		
        return i;	
    }
}; 

void main()
{	
    A a;	
    cout<<a.get()<<endl; //資料成員值為0	
    a.get()=1; //嘗試修改a物件的資料成員為1,而且是用函式呼叫表示式作為左值。	
    cout<<a.get()<<endl; //資料成員真的被改為1了,返回指標的情況也可以修改成員i的值,所以為了安全起見最好在返回值加上const,使得函式呼叫表示式不能作為左值
}

摘自:https://blog.csdn.net/my_mao/article/details/22872149

最後請記住:

 

5、確定物件被使用前已被初始化

(1)對於變數初始化

一定在宣告的時候先初始化

(2)對於建構函式初始化

不好:

不好之處:c++規定,物件的成員變數初始化動作發生在進入建構函式本體之前

好:

好的地方:通過初始化列表,效率比賦值更高,且注意,初始化列表的成員變數排列次序和他們在類中宣告的次序相同

 

5、知道c++預設呼叫了哪些函式

如果寫一個空類,c++預設會宣告一個預設建構函式、拷貝建構函式和一個解構函式,這些函式都是public且inline

因此你寫下:

class Empty{};

等同於寫下:

只有當這些函式被呼叫,才會被編譯器創建出來,下面程式碼造成每一個函式被編譯器產出:

對於拷貝建構函式:

類NamedObject的建構函式這樣:

(1)如果呼叫了拷貝建構函式:

在將第一個引數no1的string傳給no2時,會呼叫string的拷貝建構函式並以"Smallest Prime Number"為實參,第二個引數型別int,所以會拷貝no1中value的每一個bits完成初始化

(2)如果呼叫賦值操作:

編譯器拒絕執行,因為引用沒法賦值,無法改變,編譯器拒絕編譯

NamedObject<int> p("Persephone", 2);
NamedObject<int> s("Satch", 36);
p=s;

6、為多型基類的解構函式宣告為virtual

父類解構函式如果不設為virtual,那麼下列程式

    CEmployee *oper = new COperator() ;   //CEmployee是父類,COperator是子類
    delete oper;  

執行順序為:

父類構造->子類構造->父類析構

後果:子類析構沒有呼叫,記憶體洩露

如果父類析構加virtual,子類析構不加virtual,執行順序為:

父類構造->子類構造->子類析構->父類析構

注意點:

1、既不能將所有類解構函式都宣告為virtual,因為Point class內含virtual函式,物件體積會增加百分之50-100,也不能所有類析構都不宣告為virtual

2、給基類一個virtual解構函式只適用於帶多型性質的基類

3、如果類帶有任何virtual函式,解構函式就應該設為virtual

4、如果該類設計不是作為基類使用,或不是為了具備多型性,解構函式不該設為virtual

7、別讓異常逃離解構函式

8、在構造和析構期間不要呼叫virtual函式,因為呼叫從不下降至子類

9、在賦值操作符operator= 返回一個*this,型別為物件本身

10、在賦值操作符operator= 中處理“自我賦值”

11、複製物件勿忘其每一個成分

不對做法:

正確做法:

12、以物件管理資源

1、建立了一個資源後立即交給管理manager類管理

2、管理物件運用解構函式確保資源被釋放

13、成對使用new和delete採用相同的形式