《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採用相同的形式
的