C++ 學習2 Class with Pointer member

Boolan C++ 學習2 Class with Pointer member

記錄知識點:

  • BigThree
  • 空指針
  • const static sizeof explicit關鍵字
  • new delete 內存分佈
  • C++ 數組指針
  • 類/函數模板
  • Pointer/Function like object
  • C++ 11 待補充知識點

BigThree

拷貝構造, 複製構造, 析構函數
注意深淺拷貝:
如果你拷貝的對象中引用了某個外部的內容(比如分配在堆上的數據),那麼在拷貝這個對象的時候,讓新舊兩個對象指向同一個外部的內容,就是淺拷貝;如果在拷貝這個對象的時候爲新對象製作了外部對象的獨立拷貝,就是深拷貝 。
參考 http://blog.chinaunix.net/uid-24981687-id-3038952.html

空指針

早在 1972 年,C語言誕生的初期,常數 0 帶有常數及空指針的雙重身分。 C 使用 preprocessor macro NULL 表示空指針, 讓 NULL 及 0 分別代表空指針及常數 0。 NULL 可被定義爲 ((void*)0) 或是 0。
C++ 並不採用 C 的規則,不允許將 void* 隱式轉換爲其他類型的指針。 爲了使代碼 char* c = NULL; 能通過編譯,NULL 只能定義爲 0。 這樣的決定使得函數重載無法區分代碼的語義:
void foo(char *);
void foo(int);
C++ 建議 NULL 應當定義爲 0,所以foo(NULL); 將會調用 foo(int), 這並不是程序員想要的行爲,也違反了代碼的直觀性。0 的歧義在此處造成困擾。
C++11 引入了新的關鍵字來代表空指針常數:nullptr,將空指針和整數 0 的概念拆開。 nullptr 的類型爲nullptr_t,能隱式轉換爲任何指針或是成員指針的類型,也能和它們進行相等或不等的比較。 而nullptr不能隱式轉換爲整數,也不能和整數做比較。
爲了向下兼容,0 仍可代表空指針常數。
char* pc = nullptr; // OK
int * pi = nullptr; // OK
int i = nullptr; // error

foo(pc); // 呼叫 foo(char *)
參考https://zhidao.baidu.com/question/239782243804514164.htm

const static volatile sizeof explicit關鍵字

  1. const 關鍵字
    this指針如果在非const成員函數中,this指針只是一個類類型的;如果在const成員函數中,this指針是一個const類類型的;如果在volatile成員函數中,this指針就是一個volatile類類型的。
    class A
    {
    ……
    void f(int i) {……} //一個函數
    void f(int i) const {……} //上一個函數的重載
    ……
    };
    上面是重載是沒有問題的了,那麼下面的呢?
    class A
    {
    ……
    void f(int i) {……} //一個函數
    void f(const int i) {……} //??
    };
    這個是錯誤的,編譯通不過。那麼是不是說明內部參數的const不予重載呢?再看下面的例子:
    class A
    {
    ……
    void f(int& ) {……} //一個函數
    void f(const int& ) {……} //?????
    };
    這個程序是正確的,看來上面的結論是錯誤的。
    關於const 重載幾乎在所有c++的書中者提到過但大部分只是一句話,例如在《C++ primer》一書中這樣描述:“可基於函數的引用形參是指向 const 對象還是指向非 const 對象,實現函數重載。將引用形參定義爲 const 來重載函數是合法的,因爲編譯器可以根據實參是否爲 const 確定調用哪一個函數。”
    但是這一段描述並沒有給出引用、指針和值傳遞前加const的實質區別是什麼。在用非const的指針,引用和值均可轉化爲const的。這一點沒有太多可說明的東東。

對於函數值傳遞的情況,因爲參數傳遞是通過複製實參創建一個臨時變量傳遞進函數的,函數內只能改變臨時變量,但無法改變實參。則這個時候無論加不加const對實參不會產生任何影響。但是在引用或指針傳遞函數調用中,因爲傳進去的是一個引用或指針,這樣函數內部可以改變引用或指針所指向的變量,這時const 纔是實實在在地保護了實參所指向的變量。因爲在編譯階段編譯器對調用函數的選擇是根據實參進行的,所以,只有引用傳遞和指針傳遞可以用是否加const來重載。 臨時變量都是const的所以傳值加const並不能使編譯器判斷重載。
類內部的常量限制:使用這種類內部的初始化語法的時候,常量必須是被一個常量表達式

初始化的整型或枚舉類型,而且必須是static和const形式。

· 如何初始化類內部的常量:一種方法就是static 和 const 並用,在外部初始化,例如:

class A { public: A() {} private: static const int i; file://注意必須是靜態的! };

const int A::i=3;另一個很常見的方法就是初始化列表: class A { public: A(int

i=0):test(i) {} private: const int i; }; 還有一種方式就是在外部初始化,

· 如果在非const成員函數中,this指針只是一個類類型的;如果在const成員函數中,

this指針是一個const類類型的;如果在volatile成員函數中,this指針就是一個

volatile類類型的。

· new返回的指針必須是const類型的。
參考http://blog.csdn.net/net_assassin/article/details/9997257
http://blog.chinaunix.net/uid-20443320-id-1945980.html
2. Static 關鍵字
1) 靜態全局變量
在全局變量前,加上關鍵字static,該變量就被定義成爲一個靜態全局變量。
• 該變量在全局數據區分配內存;
• 未經初始化的靜態全局變量會被程序自動初始化爲0(自動變量的值是隨機的,除非它被顯式初始化);
• 靜態全局變量在聲明它的整個文件都是可見的,而在文件之外是不可見的; 
靜態變量都在全局數據區分配內存,包括後面將要提到的靜態局部變量。對於一個完整的程序,在內存中的分佈情況如下圖: 
代碼區
全局數據區
堆區
棧區
一般程序的由new產生的動態數據存放在堆區,函數內部的自動變量存放在棧區。自動變量一般會隨着函數的退出而釋放空間,靜態數據(即使是函數內部的靜態局部變量)也存放在全局數據區。全局數據區的數據並不會因爲函數的退出而釋放空間。細心的讀者可能會發現,Example 1中的代碼中將 “static int n; //定義靜態全局變量”改爲“int n; //定義全局變量”。程序照樣正常運行。的確,定義全局變量就可以實現變量在文件中的共享,但定義靜態全局變量還有以下好處:
• 靜態全局變量不能被其它文件所用;
• 其它文件中可以定義相同名字的變量,不會發生衝突;
static 可以表示內部。
2) 靜態局部變量
通常,在函數體內定義了一個變量,每當程序運行到該語句時都會給該局部變量分配棧內存。但隨着程序退出函數體,系統就會收回棧內存,局部變量也相應失效。但有時候我們需要在兩次調用之間對變量的值進行保存。通常的想法是定義一個全局變量來實現。但這樣一來,變量已經不再屬於函數本身了,不再僅受函數的控制,給程序的維護帶來不便。
靜態局部變量正好可以解決這個問題。靜態局部變量保存在全局數據區,而不是保存在棧中,每次的值保持到下一次調用,直到下次賦新值。
靜態局部變量有以下特點:
• 該變量在全局數據區分配內存;
• 靜態局部變量在程序執行到該對象的聲明處時被首次初始化,即以後的函數調用不再進行初始化;
• 靜態局部變量一般在聲明處初始化,如果沒有顯式初始化,會被程序自動初始化爲0;
• 它始終駐留在全局數據區,直到程序運行結束。但其作用域爲局部作用域,當定義它的函數或語句塊結束時,其作用域隨之結束;
3) 靜態函數
在函數的返回類型前加上static關鍵字,函數即被定義爲靜態函數。靜態函數與普通函數不同,它只能在聲明它的文件當中可見,不能被其它文件使用。
• 靜態函數不能被其它文件所用;
• 其它文件中可以定義相同名字的函數,不會發生衝突;
4)靜態數據成員
在類內數據成員的聲明前加上關鍵字static,該數據成員就是類內的靜態數據成員。
• 對於非靜態數據成員,每個類對象都有自己的拷貝。而靜態數據成員被當作是類的成員。無論這個類的對象被定義了多少個,靜態數據成員在程序中也只有一份拷貝,由該類型的所有對象共享訪問。也就是說,靜態數據成員是該類的所有對象所共有的。對該類的多個對象來說,靜態數據成員只分配一次內存,供所有對象共用。所以,靜態數據成員的值對每個對象都是一樣的,它的值可以更新;
• 靜態數據成員存儲在全局數據區。靜態數據成員定義時要分配空間,所以不能在類聲明中定義。在Example 5中,語句int Myclass::Sum=0;是定義靜態數據成員;
• 靜態數據成員和普通數據成員一樣遵從public,protected,private訪問規則;
• 因爲靜態數據成員在全局數據區分配內存,屬於本類的所有對象共享,所以,它不屬於特定的類對象,在沒有產生類對象時其作用域就可見,即在沒有產生類的實例時,我們就可以操作它;
• 靜態數據成員初始化與一般數據成員初始化不同。靜態數據成員初始化的格式爲:
<數據類型><類名>::<靜態數據成員名>=<值>
• 類的靜態數據成員有兩種訪問形式:
<類對象名>.<靜態數據成員名> 或 <類類型名>::<靜態數據成員名>
如果靜態數據成員的訪問權限允許的話(即public的成員),可在程序中,按上述格式來引用靜態數據成員 ;
• 靜態數據成員主要用在各個對象都有相同的某項屬性的時候。比如對於一個存款類,每個實例的利息都是相同的。所以,應該把利息設爲存款類的靜態數據成員。這有兩個好處,第一,不管定義多少個存款類對象,利息數據成員都共享分配在全局數據區的內存,所以節省存儲空間。第二,一旦利息需要改變時,只要改變一次,則所有存款類對象的利息全改變過來了;
• 同全局變量相比,使用靜態數據成員有兩個優勢:
1. 靜態數據成員沒有進入程序的全局名字空間,因此不存在與程序中其它全局名字衝突的可能性;
2. 可以實現信息隱藏。靜態數據成員可以是private成員,而全局變量不能;
5)靜態成員函數
與靜態數據成員一樣,我們也可以創建一個靜態成員函數,它爲類的全部服務而不是爲某一個類的具體對象服務。靜態成員函數與靜態數據成員一樣,都是類的內部實現,屬於類定義的一部分。普通的成員函數一般都隱含了一個this指針,this指針指向類的對象本身,因爲普通成員函數總是具體的屬於某個類的具體對象的。通常情況下,this是缺省的。如函數fn()實際上是this->fn()。但是與普通函數相比,靜態成員函數由於不是與任何的對象相聯繫,因此它不具有this指針。從這個意義上講,它無法訪問屬於類對象的非靜態數據成員,也無法訪問非靜態成員函數,它只能調用其餘的靜態成員函數。
• 出現在類體外的函數定義不能指定關鍵字static;
• 靜態成員之間可以相互訪問,包括靜態成員函數訪問靜態數據成員和訪問靜態成員函數;
• 非靜態成員函數可以任意地訪問靜態成員函數和靜態數據成員;
• 靜態成員函數不能訪問非靜態成員函數和非靜態數據成員;
• 由於沒有this指針的額外開銷,因此靜態成員函數與類的全局函數相比速度上會有少許的增長;
• 調用靜態成員函數,可以用成員訪問操作符(.)和(->)爲一個類的對象或指向類對象的指針調用靜態成員函數,也可以直接使用如下格式:
<類名>::<靜態成員函數名>(<參數表>)
調用類的靜態成員函數。
靜態成員函數由於沒有this指針所以不能定義const函數。
class::static func() const {} // error
C++編譯器在實現const的成員函數的時候爲了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數const this*。但當一個成員爲static的時候,該函數是沒有this指針的。也就是說此時const的用法和static是衝突的。
參考:http://www.cnblogs.com/BeyondAnyTime/archive/2012/06/08/2542315.html
http://blog.csdn.net/bxyill/article/details/8444391
3. volatile 關鍵字
C/C++ 中的 volatile 關鍵字和 const 對應,用來修飾變量,通常用於建立語言級別的 memory barrier。這是 BS 在 “The C++ Programming Language” 對 volatile 修飾詞的說明:

A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.

  volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。聲明時語法:int volatile vInt; 當要求使用 volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。而且讀取的數據立刻被保存。例如:

1 volatile int i=10;
2 int a = i;
3 …
4 // 其他代碼,並未明確告訴編譯器,對 i 進行過操作
5 int b = i;
volatile 指出 i 是隨時可能發生變化的,每次使用它的時候必須從 i的地址中讀取,因而編譯器生成的彙編代碼會重新從i的地址讀取數據放在 b 中。而優化做法是,由於編譯器發現兩次從 i讀數據的代碼之間的代碼沒有對 i 進行過操作,它會自動把上次讀的數據放在 b 中。而不是重新從 i 裏面讀。這樣以來,如果 i是一個寄存器變量或者表示一個端口數據就容易出錯,所以說 volatile 可以保證對特殊地址的穩定訪問。注意,在 VC 6 中,一般調試模式沒有進行代碼優化,所以這個關鍵字的作用看不出來。

參考:http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html
4. sizeof 運算符
(0)sizeof是運算符,不是函數;

(1)sizeof不能求得void類型的長度;

(2)sizeof能求得void類型的指針的長度;

(3)sizeof能求得靜態分配內存的數組的長度!

(4)sizeof不能求得動態分配的內存的大小!

(5)sizeof不能對不完整的數組求長度;

(6)當表達式作爲sizeof的操作數時,它返回表達式的計算結果的類型大小,但是它不對錶達式求值!

(7)sizeof可以對函數調用求大小,並且求得的大小等於返回類型的大小,但是不執行函數體!

(8)sizeof求得的結構體(及其對象)的大小並不等於各個數據成員對象的大小之和!

(9)sizeof不能用於求結構體的位域成員的大小,但是可以求得包含位域成員的結構體的大小!
指針變量的sizeof值與指針所指的對象沒有任何關係。

參考http://www.cnblogs.com/bigbigtree/p/3580585.html
http://blog.csdn.net/candyliuxj/article/details/6307814
5. explicit關鍵字
C++提供關鍵字explicit,可以阻止不應該允許的經過轉換構造函數進行的隱式轉換髮生.

聲明爲explicit的構造函數不能在隱式轉換中使用.

C++中,一個參數的構造函數(或者除了第一個參數外其餘參數都有默認值的多參構造函數),承擔了兩個角色.

1.是個構造器,2.是個默認且隱含的類型轉換操作符.

寫下如AAA = XXX,這樣的代碼,且恰好XXX的類型正好是AAA單參數構造的參數類型,這時候編譯器就自動調用這個構造器,創建一個AAA的對象.

使用explicit聲明構造函數,則可防止隱式轉換,避免上述情況的發生 。
參考:http://www.cnblogs.com/dwdxdy/archive/2012/07/17/2595479.html
http://blog.csdn.net/chollima/article/details/3486230
http://www.jb51.net/article/52164.htm

new delete 內存分佈

new 三步驟
complex *pc = new complx(1,2);
1) void * mem = operator new(sizeof(complex))//分配空間
2) pc = static_cast<”complex>( mem)
3) pc-> complex::complex(1,2) //構造函數
delete 兩步先析構再釋放mem。
delete pc;
string ::~string(pc);
operator delete(ps);
array new 要用array delete
delete [] 會調用幾次析構函數。

C++ 數組指針 
數組名不是指針但是可以用作指針。
(1)數組名的內涵在於其指代實體是一種數據結構,這種數據結構就是數組;

(2)數組名的外延在於其可以轉換爲指向其指代實體的指針,而且是一個指針常量;

(3)指向數組的指針則是另外一種變量類型(在WIN32平臺下,長度爲4),僅僅意味着數組的存放地址!

數組名可能失去其數據結構內涵

(1)數組名作爲函數形參時,在函數體內,其失去了本身的內涵,僅僅只是一個指針;

(2)很遺憾,在失去其內涵的同時,它還失去了其常量特性,可以作自增、自減等操作,可以被修改。

所以,數組名作爲函數形參時,其全面淪落爲一個普通指針!它的貴族身份被剝奪,成了一個地地道道的只擁有4個字節的平民。
數組指針 int (*p)[4]; //p是指針,指向一維數組,每個一維數組有4個int元素
指針數組 int* q[4];///指針數組 q是數組,數組元素是指針,3個int指針
參考:http://www.cnblogs.com/wuzhenbo/archive/2012/05/29/2523777.html
http://blog.csdn.net/kaiming2008/article/details/5617155/

類/函數模板

  1. 在進行模板的類型推導時,傳入參數如果是引用,會被當作非引用,即忽略掉引用部分。

  2. 對統一引用參數進行類型推導時,左值參數會獲得特殊處理。

  3. 按值傳遞的參數進行類型推導時,const或者volatile參數會被處理成非const或非volatile。
    參考:http://blog.csdn.net/coolmeme/article/details/43986163
    https://www.zhihu.com/question/24671324
    http://blog.csdn.net/zhang810413/article/details/1948603

Pointer/Function like object

一個函數對象,即一個重載了括號操作符“()”的對象。當用該對象調用此操作符時,其表現形式如同普通函數調用一般,因此取名叫函數對象。舉個最簡單的例子:
class FuncObjType
{
public:
void operator() ()
{
cout<<”Hello C++!”<

G++ inline不能定義在CPP中只能在源文件中

C++的標準行爲需要

1.inline寫在定義上而不是聲明上
2.inline函數寫頭文件而不是源文件

但目前大多數編主流譯器,都是可以將cpp文件內的函數展開的,只要你是從源碼編譯的,能找到源碼實現就行

對於二進制連接時的優化展開(將obj、lib中的函數內聯展開,dll中目前還沒有誰能做到),屬於鏈接時優化,目前還遠不如編譯時優化成熟,但VC、icc也都是能做的(gcc不確定,沒研究過)

編譯器發展到今天,inline關鍵字已經退居二線,類似於register關鍵字,絕大部分情況下並沒有一定得用的必要,本身inline和register都屬於“建議而非強制”、“性能相關而非功能相關”,他們所做的事情本就不應該有程序員干預,而應該是編譯器優化做的事情,換句話說inline和register是在編譯器不夠智能的時代的人爲干預優化。

目前的主流編譯器,可以做到即便你不寫inline,適合內聯的函數也會內聯,正如你不寫register,編譯器也會盡量使用寄存器而非堆棧一樣的道理

C++ 11 待補充知識點

待添加。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章