摘自:《effetive c++》中文版第三版從編譯器來看,符號表與編譯的各個階段都有互動,一般來講,符號表有記憶體位址和函式/變數的對應關係,編譯時節點的各種屬性(型別,作用域,分配空間大小,(函式)的引數型別等)
因為例如
#define kkk 1.653
**段在編譯器開始處理原始碼之前可能會被預處理器取走了,以至於如果在這之後程式中用到了kkk這個常量而且出錯時,編譯錯誤資訊將會返回1.653而不是kkk。這種情況在多個檔案共同編譯時而且是有重複定義數值情況下,將無法分辨究竟是哪乙個常量出現了編譯問題。
而作為乙個語言常量,const肯定會被編譯器看到,那麼一定就會進入符號表,使用const可以避免上述出現多份1.653的情況。
對enum來說,行為會比較像#define而不像const,這三者中只有const變數能夠取其位址,enum和#define取位址不合法。
對於形似函式的巨集,最好改用inline函式替換#define,因為將函式以巨集的形式定義容易出現在預處理器中已經改變引數值的問題。
bitwise:成員函式不改變該class的任何成員變數->不改變class內的任何乙個bit。
logical:成員函式可以修改class內的某些bits,但是只有客戶端監測不楚的情況才行。
const成員函式不能修改普通成員變數,這裡是根據bitwise原則來的。但是如果在成員變數前面加上了mutable修飾詞,那麼const成員函式就可以修改這些普通成員變數了。
在之前的編碼中,以帶引數建構函式例如
newclass::newclass(const
int& num)
形式,這種不叫作初始化,而叫做類成員函式的賦值,而成員初始化列表才是真兒八經地對成員變數初始化操作。
newclass
::newclass(const
int& num):num_(num)
{}
區別在於使用賦值操作的建構函式,成員變數是在進入該類的建構函式之後獲得的,而c++規定,物件的成員變數的初始化動作發生在進入建構函式本體之前。
主要會產生由變數的區域性作用域和全域性作用域衝突而產生變數覆蓋的問題。考慮以下**:
int x;
void func()
讀取這個語句時,編譯器將讀取輸入的double x而不是global int x,因為內層作用域的名稱會覆蓋外圍作用域的名稱。
以類的區域性作用域為例
class base
class derived:public base
若一段**
void derived::mf4()
編譯器的查詢順序為
mf4覆蓋的作用域
derived覆蓋的作用域
base覆蓋的作用域
若base中也不存在
1. 在base所屬的namespace中查詢
2. 在global作用域中查詢
成員函式的分類:
pure virtual
virtual
void func() = 0;
為了讓派生類只繼承函式介面而且必須在派生類中實現該函式
impure virtual
會在基類中提供乙份實現**,派生類可以繼承該函式介面和預設實現,也可以自行過載。這個設計突顯出了基類和派生類的共同性質,避免**的重複。 例如
class person
}class a : public person;
class b : public person;
/*在這種情況下a,b都在呼叫dickcheck()函式時都將返回true*/
現在出現了乙個女人,她繼承自person類
class c : public: person;
person* human =
new c;
human->dickcheck();
這就出現了問題,dickcheck()函式返回為true,但是男女有別。此時class c是無辜的,它並沒有說明自己在沒有dick的情況下就呼叫了dickcheck函式,意味著無辜地繼承了預設實現。實際上我們可以將dickcheck函式宣告為pure virtual,然後再讓每個具有不同特性的派生類單獨實現該函式。
class person
class a : public person
}class b : public person
}
本質上,通過定義我們將dickchenck這個基類中函式劃分了n種形態,每種形態對應乙個特性的派生類,宣告部分成為介面,定義部分表現單個派生類的特性。我們在這裡不提供預設實現是避免某些特殊派生類的加入會使得修改基類**。
non virtual
宣告non-virtual函式是為了讓派生類繼承介面和乙份強制性實現,由於non-virtual代表的是不變性和凌駕特異性,所以它絕不該在derived class中被重新定義。
non-virtual inte***ce手段
通過public的non-virtual函式=pnv呼叫private的virtual=函式pv。這使得可以在pnv中呼叫pv的前後執行一些工作,例如互斥變數鎖定和系統日誌。其實virtual函式不一定得是private,protected的virtual函式使得派生類可以繼承基類的pnv呼叫方式。
std::function實現strategy模式
例如現有基類flower代表一種植物,a中有乙個方法void flower1(const flower&)可以讓這種植物向著太陽開花,void flower2(const flower&)可以讓這種植物向著土地開花。好,
我們寫出函式的簽名
typedef std::function
<
void(const flower&)> flower;
flower a; //向著太陽開花的植物
flower b; //向著大地開花的植物
flower a_f = std::bind(&flower::flower1, a, _1);
flower b_f = std::bind(&flower::flower2, b, _1);
a_f(a);
b_f(b);
詞條在5中已經提到過,這要求我們在設計基類時充分考慮到該類的特性,使得其派生類都無條件繼承non-virtual函式時不會產生函式冗餘。畢竟non-virtual函式靜態繫結,而virtual函式時動態繫結。
因為預設引數值是靜態繫結,在編譯器決定的而不是執行期。
復合就是組合。組合(has-a)和繼承(is-a)都是**復用的方式。
組合:黑盒復用,被包含的物件的內部細節對對外是不可見的。但是為了使得包含另外乙個物件的類能夠友好地操縱被包含的物件,設計被包含的物件時應該安全合理地考慮對外的介面函式。
繼承:白盒復用。派生類會隨著基類的改變而改變。
private繼承不是is-a的關係,而是implemented-in-terms-of,有點類似組合。
- private繼承時,編譯器不會講派生類物件轉換為基類物件,這意味著在以基類物件為引數的傳遞時並不能用派生類物件。
- private繼承過來的成員在派生類中將全部轉變為private屬性。
在此處要注意派生類內部和派生類物件的區別。private繼承在派生類的子類內部無法訪問基類的任何成員。考慮下面的情況:
class base
class pri : private base}#
pri pri_1;
//錯誤,在private繼承下,派生類物件不能訪問基類的任何成員。
pri_1.a = 1;
#/* 派生類的子類對基類成員進行訪問*/
class subpri : public pri
//此時派生類子類內部無法訪問基類的任何成員,對比public繼承,後者的子類仍然可以訪問基類的成員。
}
使用情形:當派生類需要訪問基類的protected成員或者需要重新定義繼承而來的virtual函式時。 —這句話完全不能理解
再列舉一下有關protected繼承的特性:
class pro : protected base
}pro pro_1;
//錯誤,在protected繼承下,派生類物件無法訪問基類的任何成員。
pro_1.a = 1;
空白基類最優化–與組合相比較而言class
empty
class
emptypri : private
base
class
composition
當我們用sizeof檢查這兩個類的大小時,可以發先
sizeof(emptypri)為4;僅為int的大小
而sizeof(composition)為8
Effective C 學習筆記
學習effective c 已經有相當長的一段時間了,今天抽出時間又堵了一遍第一部分 c語言 c 以c語言為基礎,幾乎支援所有的c語言成分,例如區塊 語句 預處理 內建資料型別 陣列 指標等,c語言的侷限是 沒有模板 沒有異常 沒有過載 物件導向的c 也就是加上了物件特性的c,類 封裝 繼承 多型 ...
Effective C 學習筆記
1 c 是乙個複合式的語言 c 中不同部分有著不同的語言特性,例如 1.1 在c中傳遞形參時,按照值傳遞比按照指標傳遞效率更高 1.2 在物件導向程式中,物件要按照const引用而不是按照值傳遞 1.3 在stl程式設計中採取按照值傳遞方式 所以說c 中沒有統一的準則,要按照不同的特性採取不同的使用...
effective c 學習筆記
如果不考慮應用程式的使用場合,僅僅考慮語言的靈活性,我贊成作者的想法。但是不同的應用它會有不同的效能要求,所以語言的選擇,應該是用 最適合 條款去選擇。使用巨集定義常量,若定義在標頭檔案中,則所有包含標頭檔案的都可以使用。巨集定義,在預處理的時候進行替換。巨集定義一些簡單的函式,可以減少呼叫開銷,但...