Effective C++是世界頂級C++大師Scott Meyers的成名之作,初版于1991年。在國際上,這本書所引起的反響之大,波及整個計算機技術(shù)出版領(lǐng)域,余音至今未絕。幾乎在所有C++書籍的推薦名單上,這部專著都會位于前三名。
該篇為我在實習(xí)期間學(xué)習(xí)《Effective C++》的一些整理,會每一兩天定期更新。
insert new line 代碼的自動補全問題
Ctrl + . : 參數(shù)提示
Re-Indent 格式化代碼
零、術(shù)語
第0章代碼及注釋:
#ifndef shuyu_h
#define shuyu_h
#endif /* shuyu_h */
typedef int NUM[100];//聲明NUM為整數(shù)數(shù)組類型,可以包含100個元素
NUM n;//定義n為包含100個整數(shù)元素的數(shù)組,n就是數(shù)組名
typedef struct //在struct之前用了關(guān)鍵字typedef,表示是聲明新類型名
{
int month;
int day;
int year;
} TIME; //TIME是新類型名,但不是新類型,也不是結(jié)構(gòu)體變量名
#include <iostream>
class Shuyu{
public:
Shuyu(); //default 構(gòu)造函數(shù)
// explicit可以抑制內(nèi)置類型隱式轉(zhuǎn)換,
// 所以在類的構(gòu)造函數(shù)中,最好盡可能多用explicit關(guān)鍵字,防止不必要的隱式轉(zhuǎn)換.
// 顯示轉(zhuǎn)換如 new Shuyu(10);
explicit Shuyu(int number);
//copy構(gòu)造函數(shù)
Shuyu(const Shuyu ©);
/*
如果 Shuyu s1 = s2; (s2 已經(jīng)被定義)
那么這個時候會有構(gòu)造函數(shù)被調(diào)用,而不是賦值操作
如果是單純的 s1 = s2 那么此時為賦值操作
*/
Shuyu& operator=(const Shuyu& copy);
/*
運算符重載的部分說明:
加const是因為:
①我們不希望在這個函數(shù)中對用來進行賦值的“原版”做任何修改。
②加上const,對于const的和非const的實參,函數(shù)就能接受;如果不加,就只能接受非const的實參。
用引用是因為:
這樣可以避免在函數(shù)調(diào)用時對實參的一次拷貝,提高了效率。
copy構(gòu)造函數(shù)是一個比較重要的函數(shù),因為它定義了一個對象如何Pass By Value 即值傳遞
*/
};
explicit可以抑制內(nèi)置類型隱式轉(zhuǎn)換,
所以在類的構(gòu)造函數(shù)中,最好盡可能多用explicit關(guān)鍵字,防止不必要的隱式轉(zhuǎn)換.
1、typedef:為一種數(shù)據(jù)類型定義一個新名字。
在平臺一上使用typedef long double REAL;,平臺二如果不支持Long Double類型,就改為typedef float REAL,這樣在別的用到REAL的地方就不需要修改了。
- 理解復(fù)制聲明的技巧:從變量名看起,先往右,再往左
int (*func[5])(int *);
func 右邊是一個[]運算符,說明func是具有5個元素的數(shù)組;func的左邊有一個*,說明func的元素是指針(注意這里的 * 不是修飾func,而是修飾 func[5]的,原因是[]運算符優(yōu)先級比 * 高,func先跟[]結(jié)合)。跳出這個括號,看右邊,又遇到圓括號,說明func數(shù)組的元素是函數(shù)類型的指 針,它指向的函數(shù)具有int * 類型的形參,返回值類型為int。
- const char * p 的意思是p指向的目標空間的內(nèi)容不可變化,
char * const p 的意思是指針p的值不可變,但它指向目標的值可變。
2、#define
#define 指令將標識符定義一個宏,程序在編譯的時候會將相同的字符進行替換,也不作正確性檢查,當替換列表中含有多個字符的時候,最好的將替換列表用圓括號括起來。宏定義不是說明或者語句,在行末尾不必添加分號;
- 在大規(guī)模的開發(fā)過程中,特別是跨平臺和系統(tǒng)的軟件里,define最重要的功能是條件編譯。
#ifdef windows
...
#else
...
#endif
#ifdef debug
...
...
#endif
- define與Typedef的區(qū)別
#define是預(yù)處理指令,在編譯預(yù)處理時進行簡單的替換,不作正確性檢查;
而typedef是在編譯時處理的,它在自己的作用域內(nèi)給一個已經(jīng)存在的類型一個別名,
本書啟示一:避免不明確(未定義)行為。
其它補充:
char name[] = " hello"
注意name數(shù)組大小為6,別忘了最后的null
一、聯(lián)邦語言C++
C++是一種支持過程形式、面向?qū)ο笮问?、函?shù)形式、泛型形式、元編程形式的語言。
a、C:C++是以C為基礎(chǔ)的,block、預(yù)處理器、數(shù)組、指針等都來自于C
b 面向?qū)ο?:封裝、繼承、多態(tài)、封裝
c Template
-d STL:STL是整個Template程序庫
第一章練習(xí)代碼:
.h文件:
/*
#define 不被視為語言的一部分 盡量不要用
#define 不能提供任何的封裝性 即不存在 private #define 一類的東西
*/
//大寫名稱通常用于宏
const double Ratio = 1.65;
//由于常量通常在頭文件內(nèi)部(會被不同的源碼調(diào)用) 因此需要將指針聲明為const
const char* const authorName = "wushuohan";
#include <iostream>
class GamePlayer{
private:
// 頭文件內(nèi)常量聲明
static const double Ratio;
//a=1是一個聲明式定義
/*
通常C++需要一個定義式
但是如果這個該常量既是static又是整s數(shù)類型 可以忽略
*/
static const int a = 1;
std::string name;
std::string age;
public:
/*
const在*左邊,被指物是常量
const在*右邊,指針是常量
*/
void func1(const int * a);
void func2(int const* a );
//賦值說明 見函數(shù)的實現(xiàn)
GamePlayer(const std::string &name);
//構(gòu)造函數(shù)的最佳寫法
GamePlayer(const std::string &name, const std::string &age);
/*
盡量用local-static代替non-local static
構(gòu)造順序之Non-local static
函數(shù)內(nèi)的static對象為local-static對象 其余均為non-local
static對象的析構(gòu)函數(shù)會在main()方法結(jié)束時自動調(diào)用
C++對non-local static的構(gòu)造順序沒有規(guī)定,
如有需要,可以把他們搬到自己的專屬函數(shù)內(nèi),在函數(shù)內(nèi)部是static,用函數(shù)返回一個reference;
函數(shù)內(nèi)static對象會在函數(shù)被調(diào)用期間、首次遇上該對象定義時被初始化。
*/
int test(){
static int a = 6;
return a;
}
};
/*
取一個const的地址是合法的
但取enum的地址是非法的 指針指不到
單純對于常量 盡量用 const enum 替換define 可以h降低對預(yù)處理器的需求
對于函數(shù)形式的宏,用inline函數(shù)來替換
*/
template<typename T>
inline void callWithMax(const T& a , const T& b){
f(a>b?a:b);
}
/*
內(nèi)聯(lián)函數(shù)是指那些定義在類體內(nèi)的成員函數(shù),即該函數(shù)的函數(shù)體放在類體內(nèi)。
為什么inline能取代宏?
1、 inline 定義的類的內(nèi)聯(lián)函數(shù),函數(shù)的代碼被放入符號表中,在使用時直接進行替換,(像宏一樣展開),沒有了調(diào)用的開銷,效率也很高。
2、 類的內(nèi)聯(lián)函數(shù)也是一個真正的函數(shù),編譯器在調(diào)用一個內(nèi)聯(lián)函數(shù)時,會首先檢查它的參數(shù)的類型,保證調(diào)用正確。然后進行一系列的相關(guān)檢查。這樣就消除了它的隱患和局限性。
3、 inline 可以作為某個類的成員函數(shù),當然就可以在其中使用所在類的保護成員及私有成員。
宏是由預(yù)處理器對宏進行替代,而內(nèi)聯(lián)函數(shù)是通過編譯器控制來實現(xiàn)的
*/
class textBlock{
public:
//成員函數(shù)length()不該動對象內(nèi)的任何一個Bit
std::size_t length() const;
//當const和Non-const有著相同的實現(xiàn)時,讓non-const調(diào)用const
private:
//mutable定義的成員變量總是可能會更改,即使是在Const函數(shù)中
mutable bool lengthIsValid;
mutable std::size_t textLength;
};
內(nèi)聯(lián)函數(shù)是指那些定義在類體內(nèi)的成員函數(shù),即該函數(shù)的函數(shù)體放在類體內(nèi)。
為什么inline能取代宏?
1、 inline 定義的類的內(nèi)聯(lián)函數(shù),函數(shù)的代碼被放入符號表中,在使用時直接進行替換,(像宏一樣展開),沒有了調(diào)用的開銷,效率也很高。
2、 類的內(nèi)聯(lián)函數(shù)也是一個真正的函數(shù),編譯器在調(diào)用一個內(nèi)聯(lián)函數(shù)時,會首先檢查它的參數(shù)的類型,保證調(diào)用正確。然后進行一系列的相關(guān)檢查。這樣就消除了它的隱患和局限性。
3、 inline 可以作為某個類的成員函數(shù),當然就可以在其中使用所在類的保護成員及私有成員。
.cpp文件
#include "part1.h"
#include <iostream>
//實現(xiàn)文件常量定義
const double GamePlayer::Ratio = 1.5;
GamePlayer::GamePlayer(const std::string &name){
//注意:這里是賦值 不是對Name的初始化
//初始化應(yīng)該在進入構(gòu)造函數(shù)之前就發(fā)生了
(*this).name = name;
}
//構(gòu)造函數(shù)的最佳寫法 這樣 構(gòu)造函數(shù)不需要執(zhí)行賦值操作
//先設(shè)新值再賦值太浪費了
/*注意:C++兩個成員的初始化順序也有差異 先base 再derived
這里先name,再age
*/
GamePlayer::GamePlayer(const std::string &name,
const std::string &age):name(name),age(age){}
std::size_t textBlock::length()const{
//具體略
return NULL;
}
二、構(gòu)造/析構(gòu)/賦值運算
2.1、構(gòu)造
編譯器會自動為一個類聲明一個copy構(gòu)造函數(shù)、一個copy assignment操作符、一個析構(gòu)函數(shù)。如果沒有聲明任何構(gòu)造函數(shù),那么編譯器會聲明一個default 構(gòu)造函數(shù)。
注意:C++ 不允許讓reference改指向不同的對象
本章練習(xí)代碼
#include <iostream>
template <class T>
class NamedObject{
public:
NamedObject(std::string &name,const T & value);
private:
//注意:C++ 不允許讓reference改指向不同的對象
//因此如果需要用 object1 = object2 時 需要自己定義一個copy assignment操作符
std::string& nameValue;
//同時如果類內(nèi) 內(nèi)置了 const成員 編譯器不會生成賦值函數(shù) 因為修改const是不合法的
const T objectValue;
//在private里聲明 copy構(gòu)造和copy assignment可以防止編譯器自行創(chuàng)建
//而只聲明不定義 是為了防止friend可以調(diào)用他們
//這樣就阻止了編譯器自動創(chuàng)建這些函數(shù)了
NamedObject(const NamedObject&);
NamedObject& operator=(const NamedObject&);//沒有定義
};
2.2、析構(gòu)
補充:C++的三種訪問權(quán)限
三種訪問權(quán)限
- public:可以被任意實體訪問
- protected:只允許子類及本類的成員函數(shù)訪問
- private:只允許本類的成員函數(shù)訪問
三種繼承方式:public、private、protected
1、public繼承不改變基類成員的訪問權(quán)限
2、private繼承使得基類所有成員在子類中的訪問權(quán)限變?yōu)閜rivate
3、protected繼承將基類中public成員變?yōu)樽宇惖膒rotected成員,其它成員的訪問 權(quán)限不變。
4、基類中的private成員不受繼承方式的影響,子類永遠無權(quán)訪問。
帶有多態(tài)性質(zhì)的base-class必須聲明一個virtual的析構(gòu)函數(shù),如果class的設(shè)計目的不是base-class,即不聲明。
若 TimerKeeper * pw = new AtomicClock();
而此時 基類TimerKeeper的析構(gòu)函數(shù)是non-virtual的
那么derived-class會經(jīng)由based-class的析構(gòu)函數(shù)銷毀
那么derived-class的derived部分就不會被銷毀
這種局部銷毀會導(dǎo)致資源泄露
本章代碼:
class TimerKeeper {
private:
public:
TimerKeeper();
//錯誤的寫法 ---如果用作繼承的話
// ~TimerKeeper();
virtual ~TimerKeeper();
};
/*
若 TimerKeeper *pw = new AtomicClock();
而此時 基類TimerKeeper的析構(gòu)函數(shù)是non-virtual的
那么derived-class會經(jīng)由based-class的析構(gòu)函數(shù)銷毀
那么derived-class的derived部分就不會被銷毀
這種局部銷毀會導(dǎo)致資源泄露
解決方案:析構(gòu)函數(shù)前+virtual關(guān)鍵字
*/
class AtomicClock:public TimerKeeper{
public:
void close();
};
/*如果class不帶virtual,通常說明它不被意圖用作一個基類
這時不應(yīng)將它的析構(gòu)函數(shù)聲明為virtual
因為會多些帶一個virtual tavle pointer 來決定哪個方法被調(diào)用
會增加對象的體積
*/
class AbstractTest{
public:
//聲明為抽象類 即該類不能創(chuàng)建對象
//最深層的derived-class的析構(gòu)會先被調(diào)用,其次是每一個based-class的析構(gòu)
//因此除了聲明外,還需要對這個抽象類的析構(gòu)函數(shù)創(chuàng)造一個定義。
virtual ~AbstractTest()=0;
};
//析構(gòu)函數(shù)絕對不要吐出異常
//如果需要對某個異常的情況作出反應(yīng),那么可以在class內(nèi)寫一個普通函數(shù)執(zhí)行該操作
class DBCon {
private:
AtomicClock ac;
bool closed;
public:
//有效的異常處理方法 定義自己的close 讓異常 有處可尋
void close(){
ac.close();
closed = true;
}
~DBCon(){
if(!closed){ //如果客戶端不關(guān)閉的g話
try {
ac.close();//可能會拋出異常
} catch (int e) {
std::abort();
/*
abort強迫結(jié)束程序
阻止異常從析構(gòu)函數(shù)傳播出去
當然可以不使用abort,在catch后吞下異常,讓程序在遭遇錯誤后繼續(xù)執(zhí)行
*/
}
}}
};
析構(gòu)函數(shù)絕對不要吐出異常
如果需要對某個異常的情況作出反應(yīng),那么可以在class內(nèi)寫一個普通函數(shù)執(zhí)行該操作
條款9:在析構(gòu)和構(gòu)造時不要調(diào)用virtual函數(shù),因為這類調(diào)用從不下降至derived-class這一層。
令賦值操作符返回一個reference to this *
2.3、自動賦值出現(xiàn)的問題
class BitMap {
};
//解決自我賦值的問題
//可能會出現(xiàn)指針指向一塊已經(jīng)被釋放過的地址
class WidGet {
private:
BitMap *bp;
public:
WidGet& operator=(const WidGet &rhs){
/*
如果是同一個對象
delete bp刪除的既是this的bp又是rhs的bp
因此需提前加一個證同測試
*/
// if(this == &rhs)return *this;
//更好的方法是不去理會 而創(chuàng)建一個新的拷貝
BitMap * bptemp = bp;
bp = new BitMap(*rhs.bp); //拷貝構(gòu)造
delete bptemp;
return *this;
}
};
三、資源管理
3.1、以對象管理資源
如果在……發(fā)生了異常、return、goto等語句,那么delete將不會執(zhí)行。
解決方法是將資源放進對象,對象的析構(gòu)函數(shù)會自動釋放這些資源。
本章代碼1:
兩種智能指針 以及 隱式轉(zhuǎn)換Operator
//智能指針的原理是,接受一個申請好的內(nèi)存地址,構(gòu)造一個保存在棧上的智能指針對象,
//當程序退出棧的作用域范圍后,由于棧上的變量自動被銷毀,智能指針內(nèi)部保存的內(nèi)存也就被釋放掉了
//智能指針會自動銷毀它指向的對象,所以不能同時指向同一個對象
//為防止資源泄露 請使用智能指針對象 它們將在構(gòu)造函數(shù)中獲得資源并在析構(gòu)函數(shù)中釋放資源
class Investment{
public:
int daysHeld(const Investment*);
bool isTaxFree();
Investment* createInvestMent(){
/*在堆中分配*/
Investment *invest = new Investment();
return invest;
}
/*
如果在……發(fā)生了異常、return、goto等語句,那么delete將不會執(zhí)行。
解決方法是將資源放進對象,對象的析構(gòu)函數(shù)會自動釋放這些資源。
*/
void func1(){
Investment *pInv = createInvestMent();
/*
調(diào)用指針……
*/
delete pInv;
}
//許多動態(tài)資源被分配后都用于單一的區(qū)塊和函數(shù)中
//auto_ptr 智能指針
void funcAdvice(){
//createInvestment()的返回結(jié)果會作為智能指針的初值
std::auto_ptr<Investment> pInv(createInvestMent());
/*
像原來一樣調(diào)用指針……
*/
}//會由智能指針的析構(gòu)函數(shù)釋放掉資源
void funcAdviceSecond(){
// shared_ptr也是一個智能指針,使用引用計數(shù),每一個shared_ptr的拷貝都指向相同的內(nèi)存。
// 每使用一次,內(nèi)部的引用計數(shù)加1,每析構(gòu)一次,內(nèi)部的引用計數(shù)減1,減為0時,刪除所指向的堆內(nèi)存。
// 相比之前的auto_ptr多了拷貝構(gòu)造 但不能打破環(huán)狀引用
std::shared_ptr<Investment> pInv1(createInvestMent());
std::shared_ptr<Investment> pInv2(pInv1);
// ……………
}//pInv1 pInv2被銷毀了
int test(){
//shared_ptr不會進行隱式轉(zhuǎn)換 需用構(gòu)造
std::shared_ptr<Investment> pInv(createInvestMent());
// 直接傳原始指針
int days = daysHeld(pInv.get());
bool tax = pInv->isTaxFree();
bool tax1 =(*pInv).isTaxFree();
if(tax==tax1){
return days;
}
return 0;
}
// 隱式轉(zhuǎn)換 operator + 返回結(jié)果 可以根據(jù)需要自動改變類型 但是不太安全
operator double()const{
// …………
return 0;
}
private:
};
本章代碼2:
定義刪除器
//每個人的地址有四行,每行是一個string
//在delete中也要使用delete[]
typedef std::string AddressLines[4];
/*
注意:
auto_ptr 與 shared_ptr都在其析構(gòu)函數(shù)內(nèi)做delete 而不是delete[]
*/
class Lock{
public:
int priority();
void processWidget(std::shared_ptr<int> pw,int priority);
void test1(){
// int 類型指針的賦值
int *a;
*a= 5;
int b = 5;
a = &b;
// 注意 構(gòu)造函數(shù)里面為指針類型
//使用分離語句 務(wù)必以獨立的new 語句將對象存入智能指針
//如果不這么做,將下面兩句寫成一句 一旦異常發(fā)生 可能會有難以察覺的內(nèi)存泄漏
std::shared_ptr<int> pw(a);
processWidget(pw,priority());
}
void lock(int *);
static void unLock(int *);
//注意初始化 與 賦值 的區(qū)別 ptr先變成pm 然后再上鎖
//shared_ptr的第二個參數(shù)是刪除器,當引用計數(shù)為0時就會調(diào)用
explicit Lock(int *pm):sharedPt(pm,unLock){
lock(sharedPt.get());
}
private:
//智能指針的復(fù)制很有可能不合理
//方法1 把copy構(gòu)造放在private里 不去定義
Lock(const Lock &);
Lock& operator=(const Lock &);
//方法二 使用shared_ptr 并且指定 刪除器
std::shared_ptr<int> sharedPt;
};
四、設(shè)計與聲明
注意:絕對不要返回一個reference 指向一個 local-棧 對象 或者返回reference指向 堆-allocated 對象
本章代碼如下:
#include <iostream>
//類定義里面的成員變量和函數(shù)默認都是private型 類本身默認為public型
class Window {
public:
std::string name()const;
virtual void display()const;
/*
Window的copy構(gòu)造會被調(diào)用 w會被初始化
當isOK返回時w會被銷毀
*/
bool isOK(Window w);
// 可以用pass-by reference to const來避免這些構(gòu)造和析構(gòu)
bool isOkAdvice(const Window& w);
};
class WindowWithScrollBars:public Window {
public:
void display()const;
void printAndDisplay(const Window& w){
std::cout<<w.name();
w.display();
}
//對于內(nèi)置類型 如(int),以及STL 通常用pass—By-value比較合適
};
/*
注意:絕對不要返回一個reference 指向一個 local-棧 對象
或者返回reference指向 堆-allocated 對象
*/
class Rational {
private:
int n,d;
/*
錯誤的寫法
result是一個local對象,會在函數(shù)退出前被銷毀
因此返回的reference指向舊的Rational
*/
// const Rational& operator *(const Rational & lhs,
// const Rational & rhs){
// Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
// return result;
// }
//
/*
注意區(qū)別:
上面的Rational沒有使用new 關(guān)鍵字
它在??臻g創(chuàng)建對象
函數(shù)退出時棧內(nèi)存會被回收
而下面的Rational采用了new 只要是new就在堆空間分配
記住一個死規(guī)則 只要是new 就需要delete
*/
/*
更垃圾的寫法
而這里new了以后,內(nèi)存無人來釋放delete
*/
// const Rational& operator *(const Rational & lhs,
// const Rational & rhs){
// Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
// return result;
// }
//友元函數(shù)可以在類內(nèi)的任何地方聲明 不受域的影響
friend const Rational operator * (const Rational & lhs,
const Rational & rhs);
public:
Rational(int a= 0, int b =0);
};
從封裝性來看,只有兩種封裝性,private和其它。
如果有一個public或者protected的成員變量被更改,會有不可預(yù)知的大量代碼被更改。
切記將成員變量聲明為private,這可以提供客戶訪問的一致性。
如果某些東西被封裝,它們便不再可見。它可以使我們改變事物而只影響有限客戶。愈多的函數(shù)可以訪問數(shù)據(jù),它的封裝性越差
偏特化:
函數(shù)模板沒有偏特化,因為有函數(shù)重載的概念,C++根據(jù)參數(shù)的類型來判斷重載哪一個函數(shù),如果還進行偏特化,這就與重載相沖突。但是,我們可一個對模板進行重載,從而實現(xiàn)偏特化。
模板的實例化類型確定是在編譯期間
練習(xí)代碼:
/*
模板的實例化類型確定是在編譯期間
全特化一般用于處理有特殊要求的類或者函數(shù),此時的泛型模板無法處理這種情況。
模板為什么要特化,因為編譯器認為,
對于特定的類型,如果你對某一功能有更好地實現(xiàn),那么就該聽你的。
C++只允許對class template偏特化
對function-template的偏特化是不合法的
模板實例化只會實例化用到的部分,沒有用到的部分將不會被實例化
*/
//原始的模版
template <typename T,typename T1>
class Test{
public:
bool compare(T& a,T &b){
return (a<b)?true:false;
}
};
//全特化 可以看作是一種重載
template <>//參數(shù)都指定了 所以參數(shù)列表為空
class Test<int,char> {
public:
bool compare(int &a,char &b){
return false;
}
};
//偏特化 只指定部分的參數(shù)類型
template <typename T>
class Test<int,T>{
public:
bool compare(int & a,T& b){
return false;
}
};
//注意 函數(shù)沒有偏特化 只有全特化
//函數(shù)模板沒有偏特化,因為有函數(shù)重載的概念,C++根據(jù)參數(shù)的類型來判斷重載哪一個函數(shù),如果還進行偏特化,這就與重載相沖突。但是,我們可一個對模板進行重載,從而實現(xiàn)偏特化。