<<c++設計與演化>>
1.c++的保護模式來自於訪問權限許可和轉讓的概念;
初始化和賦值的區分來自於轉讓能力的思考;
c++的const概念是從讀寫保護機制中演化出來.c++
2.BCPL註釋:
CPL(Combined Programming language,組合編程語言):CPL是一種在ALGOL60基礎上更接近硬件的一種語言。CPL規模大,實現困難。
BCPL(Basic Combined Programming language,基本的組合編程語言):BCPL是對CPL進行簡化後的一種語言。編程
3.通常地說在世界上,事情並不老是處在很好的狀態,要改進它們,有許多事情是能夠作的。
c++是爲了解決一個問題,而不是想證實一種觀點,而它的成長又可以服務於它的使用者.數組
4.一個類就是一個類型.數據結構
5.對象的分配能夠有三種方式:
在棧上(做爲自動對象),在固定地址(靜態對象),或者在自由存儲區(在堆,或者說動態存儲區)。
與c語言不一樣的是,帶類的c自由存儲的分配和釋放提供了特定的運算符new和delete.編程語言
6.C的意圖是採用按名字物價而不是按結構等價.
struct A { int x; int y; };
struct B { int x; int y; };函數
struct A* pa;
struct B* pb;
pa = pb; /* error : A* expected */
pa = (struct A*) pb; /* OK:explicit conversion */
因此,按名字等價是c++類型系統的基石, 而佈局相容性規則保證了可使用顯示轉換,以便能提供低級的轉換服務.
這樣導出了"唯必定義規則":在c++語言中,每一個函數,變量,類型,常量等都應該剛好有一個定義.佈局
6.設法保證忽略C++的警告將被當作是一種愚蠢行爲.this
7.語言設計並非第一個原理出發的設計,而是一種須要經驗,試驗和有效工程折衷的藝術.編碼
8.C++對於C多名字空間的解決辦法是:
一個名字能夠指稱一個類,同時也能夠指稱一個函數,或者一個變量。若是某個名字真的同時指稱兩種不一樣的東西,那麼這個名字自己的指稱的就是那個非類的東西, 除非在前面明顯地加上關鍵字struct, union或者 class.翻譯
9.虛函數在時間和空間方面都與常規函數一樣有效,即便沒有虛函數概念,在帶類的C裏的派生類也頗有用,能夠用於從老的構造出新的數據結構,用於將操做與結果關聯起來.
10.對象佈局模型
把一個類裏定義的一集虛函數定義爲一個指向函數的指針數組,這樣,對虛函數的簡單地也就是經過該數組一個間接調用。對每一個有虛函數的類都存在一個這樣的數組,通常稱爲虛函數表或者vtbl,這此類的每一個對象都包含一個隱式指針,通常稱爲vprt,指向該對象的類的虛函數表.
class A {
public:
virtual void f();
virtual void g (int);
virtual void h (double);
private:
int a;
};
class B : public A{
public:
int b;
void g(int);
virtual void m(B*);
};
class C : public B{
public:
int c;
void h(double);
virtual void n(C*);
}
類C的一個對象看起來:
+--------------------------+
| a | +----------------------+
| vptr ------>| --> | &A::f |
| b | | &B::g |
| c | | &C::h |
+--------------------------+ | &B::m |
| &C::n |
+----------------------+
對虛函數的調用被編譯系統翻譯成一個引用.
void f(C* p)
{
p ->g(2);
}
變成:
(*(p ->vptr[1])) (p, 2);
11.用省略號制止類型檢查是其中最激烈的也是最不被推薦的東西.
12.變量的做用域:
for (int i = 0; i < 10; ++i)
{
}
i = 0;
在c++語言規範裏,i的做用域不該該超過for裏{}以外,但在vs2003裏能夠.
if ,while也同樣.
13.面向對象的程序設計是利用繼承機制的程序設計;數據抽象是使用用戶定義類型的程序設計;面向對象的程序設計將可以並且應支持數據抽象.
14.類型從新定義規則:
typedef char* T;
class Y {
T f() { T a = 0; return a; }
typedef int T;
};
c++語言規則: 若是一個類型已在某個類中使用過,就不能在那裏從新定義.
<上面T,應該是重定義,但在vs2003裏錯誤提示: 沒法從Y::T轉爲T,由於T f()返回的是最上面的T,而函數體內的是類裏定義的T,因此,此處錯誤有分歧,究竟是不能返回不一樣的類型仍是後面那個T是重定義?>
<此處應按c++標準,不能進行類型的重定義>
15.重寫規則:對於以在線(online)方式定義的成員函數進行分析時,就像它們是在類聲明以後定義那樣去作.
如:
class TT
{
A f();
void g() { A a; }
typedef int A;
};
A f(); 是錯誤的,由於A尚未聲明,而void g(){ A a; }是正確的,由於符合上面寫的規則.
16.基於編譯器出錯:
class T{}; 若是用T作爲類名,在vs2003裏出現內部編譯器出錯信息.
17.c++定義規則(1993):
(1).在一個類裏一個被聲明的名字的做用域不只包括這個名字聲明以後的正文,還包括全部的函數體,默認參數,以及在這個類裏的構造函數初始化(包含嵌套的類和這些部份),但不包括這個名字自己的聲明式.
(2).在類S裏使用過的一個名字,在它的上下文中或在S的完整做用域中從新求值的時候引用的都必須是同一個聲明,S的完整做用域由類S自己,S的基類以及全部包含着S的類組成。這一般稱爲"從新考慮規則".
(3).若是在一個類裏從新排列了成員的聲明,產生的是另外一個符合1和2的合法程序,那麼該程序是無定義,這一般被稱爲"從新排序規則".
18.臨時變量
void f (string s1, string s2)
{
printf ("%s", (s1+ s2).c_str());
const char* p = (s1 + s2).c_str();
printf ("%s", p);
}
const char* p = (s1 + s2).c_str();此句具體的操做:
(1).生成一個string對象
(2).調用拷貝構造
(3).調用重載+=
(4).析構函數
因此p 指針在此語名結束後,根本不能獲取臨時字符串對象的地址<臨時對象的生命期不會到此函數體的結束處>,由於臨時對象的生命期結束.
此外,string庫裏的+操做是返回一個對象的值,不是引用.
19.放置:
前提: (1).須要一種機制把對象安放到特定的地址上(把一個表示進程的對象放到特定的硬件地址上);
(2).須要一種機制在某種特定區域裏分配對象(在一個多處理器系統的共享存儲中,或由某個特定管理器控制的區域中分配對象)。
解決方案:
重載new運算符,又c++規則裏不能對delete進行多樣的重載.
new的重載以下:
class X
{
public:
void* operator new (size, void* p) { return p; }
};
void* buf = (void*)0xF00F; //取特定的地址
X* p1 = new(buf)X; // 這裏就是在特定的地址上分配對象
X* p2 = new(buf)X; // 此時p1和p2對象的地址都爲0xF00F;
new 的第一個參數是對象的長度,由編譯器遞給new,buf是放置對象的地址,p對內存分配函數或對象的引用.
// 以上格式爲放置語法格式
放置機制使new再也不只是一種簡單的存儲分配機制,由於能夠給特定的存儲位置關聯上任意的邏輯性質,使得new起一種通用資源管理的做用.
分配區的一種重要應用是提供特定的存儲管理定義.
釋放問題:
不能指望用戶有關對象是如何分配的,用戶就根本不要去釋放此對象,這是特殊分配場地的 一種用途.
可是能夠用p1 ->X::~X();語句釋放<這是一個特殊的調用格式>
因此上面類x也不要重載new運算符(那只是觀察對象是怎樣分配內存區).
20.C++區分了5種「匹配」:
(1).匹配中不轉換或者只使用不可避免的轉換(例如,從數組到指針,函數名到函數指針,以及T到const T);
(2).使用了整數提高的匹配(像ANSI標準所定義的。也就是說從char到int,short到int以及對應的unsiged類型)以及float到double;
(3).使用了標準轉換的匹配(例如從int到double,derived*到base*,unsigned int 到int);
(4).使用了用戶定義轉換的匹配(包括經過建構造函數和轉換操做);
(5).使用了在函數聲明裏的省略號...的匹配.
以上能夠在ARM裏有更好的匹配規則
21.ARM對指針描述:
空指針並不必定用與整數0一樣的二進制式表示.
C++具備足夠強的類型語言,一個像空指針這樣的概念徹底能夠採用實現者選定的任何方式表示.
因此p = 0,給指針p賦了空指針值,但並不表示必然和整數0徹底同樣.
22.怎樣實現只能在堆上分配一個對象或怎樣只能在全局或棧上分配一個對象
(1).只能在堆上分配
class X {
public:
X() {}
static void free(X* p) { delete p; }
private:
~X(){}
};
X x; //錯誤
X* p = new X(); //OK
X::free(p); //析構
它也能阻止派生一個新類;
(2).只能在全局或棧上分配一個對象
class Y{
public:
Y() {}
private:
class D {};
void* operator new(size_t, D){}
};
或
class Y
{
public:
Y() {}
private:
void* operator new(size_t){}
};
X x; //OK
X* p = new X(); //錯誤
23.怎樣阻止一個類派生一個子類
class usable;
class unable_lock{
friend usable; // 變成友元能夠訪問私有構造函數
private:
unable_lock() {}
};
class usable : public virtual { /*這裏必需要virtual關鍵字*/ unable_lock
public:
usable() { }
};
class DD : public usable{};
usable ua; //OK
DD dd; //錯誤,不能實例化
虛擬繼承後改變了構造的順序:unable_lock -> usable ->DD;
採用非虛擬繼承時順序:usable ->調用usable的父類unable_lock的構造函數;
因此虛擬繼承時,DD類不能調用unable_lock裏的構造函數,構造函數爲私有.
假如:
class X1{
public:
X1() {}
};
class X2_1 : public virtual X1{
public:
X2_1() {}
};
class X2_2 : public virtual X1{
public:
X2_2(){}
};
class X3 : public X2_1, public X2_2{
public:
X3(){}
};
X3 xs;
虛擬繼承的順序:X1 -> X2_1 -> X2_2 ->X3;
非虛擬繼承的順序:X2_1 ->X1 -> X2_2 ->X1 -> X;
採用虛擬繼承時有兩個做用:(1).改變順序;(2).只有X1的一份拷貝,或者說只構造一次.
24.多重繼承:
class AW : public W{};
class BW : public W{};
class CW : public AW, public BW{};
這叫獨立的多重繼承,
結構:
/----------AW --------- W
CW
\----------BW ----------W
虛繼承:
class AW: public virtual W{};
class BW : public virtual W{};
class CW : public AW, public BW{};
結構:
/-----------AW----------\
CW W
\-----------BW----------/
類W的"虛"是AW和BW描述的那些派生的一種性質,而不是W自身的一種性質,每一個virtual基類總表示一個對象.
25.一個正常的基類在一個給定派生類的每一個對象的位置都是固定的.
一個虛基類的對象並不位於某個固定位置,必須經過一個間接才能訪問.基類被定義一個無名成員.若是容許有虛數據成員,那麼虛基類就該是虛數據成員的一個例子.
26.派生類的函數經常是基於同一函數在基類中的版本綜合出來的,這通常稱做方法組合.
27.抽象類概念的重要性在於它能令人對於用戶和實現者作更清晰的劃分,下降用戶和實現者之間的偶合度。
28.const成員函數:
const成員函數可能用於const對象或非const對象,非const成員函數只能用於非const對象,
假若有對象X,此時就是讓X的非const成員函數裏的this指針指向X,而讓其const成員函數裏的this指針指向const X.
29.因爲static成員函數並不關聯於任何特定對象,於是不須要用特定成員函數的語法進行調用,在某些狀況下,類被簡單地婁作一種做用域使用,把不放進就是全局的名字放入其中,做用爲它的static成員,以便使這些名字不會污染全局的名字,這是名字空間概念的一個起源.
30.指向數據成員的指針是一種表達C++的類佈局方式的與實現無關的方式.
指向成員的針指實際上比一個偏移量(標識對象中一個成員的值)還要多一些東西.
31.RTTI(run-time type infomation).
32.運行時類型信息包括3個部份:
(1).一個運算符dynamic_cast,給它一個指向某對象的基類指針,它可以獲得一個到這個對象的派生類指針,只有在被指對象確實屬於所指明的派生類時,運算符dynmaic_cast纔給出這個指針,不然返回0;
(2).一個運算符typeid,它對一個給定的基類指針識別被指對象的確切類型;
(3).一個結構type_info,做爲與有關類型的更多運行時類型住處的掛接點(hook).
33.模板:
template <class T> class vector{
public:
vector(int);
T& operator[] (int);
T& elem(int i) { return v[i]; }
};
解釋:
template 申請模板關鍵字,同時爲模板類和模板函數提供了一種共有語法形式;
<>是爲了強調模板參數具備不一樣的性質(將在編譯時求值)
class T 用於指明類型參數的類型部份
模板是爲生成類型提供的一種機制,自己並非類型,也沒有運行時的表示形式,因此對於對象的佈局沒有任何影響.
除了類型參數外,C++也容許非類型的模板參數,這種機制基本上被看着是容器類提供大小和界限所需的信息。
tempate<class T, int i> class buffer{
public:
buffer():sz(i){}
private:
T v[i];
int sz;
};
在那些運行時效率和緊湊性很是要緊的地方,傳遞大小信息容許實現者不使用自由空間.若是不能使用非類型的參數,用戶就必須把關於大小的信息編碼用到數組類型裏.
template<class T, class A> class buffer{
public:
buffer():sz(sizeof(A) / sizeof(T)){}
private:
A v;
int sz;
};
buffer<int, int[700]> b;
在模板設計中,不容許名字空間和模板做爲模板參數.
類型檢測是在模板實例化的時刻進行.
模板實現是一種基於用戶所描述的須要去生成類型的機制.
34.類模板能夠有默認參數,模板函數則不能夠
template<class T = int> class xyz{}; //OK
template<class T = int> T add(T& t){ return ++t; } //error
由於函數模板的模板參數是在調用的時候編譯器根據實參的類型來肯定的
35.就C++語言規則而論,由同一個類模板生成的兩個類之間沒有任何關係.
class x{};
class xx : public x{};
template<class T> class a{};
a<x> x1;
a<xx>x2;
x1 = x2; //error
x2 = x1; //error
36.成員模板函數不能是虛的 class shape { public: template<class T> virtual bool get() const { return true; } //error be can't virtual }; 由於若是能用virtual合法化時,類的虛函數表會每次因使用者傳遞的類型不一樣,而添加新項.這意味着鏈接程度才能去構造虛函數表,並在表中設置有關的函數.