C++基礎

C++基礎

第一章、概述

一、在學習C++編程前,首先來重複一個基本的問題:程序由什麼組成、算法的5大特徵、以及面向對象的5大原則?

答:程序=數據結構+算法html

算法的5個基本特徵:肯定性、有窮性、輸入、輸出、可行性。java

肯定性:算法的每一步驟必須有確切的定義;ios

有窮性:算法的有窮性是指算法必須能在執行有限個步驟以後終止;c++

輸入:一個算法有0個或多個輸入,以刻畫運算對象的初始狀況,所謂0個輸入是指算法自己定出了初始條件;程序員

輸出:一個算法有一個或多個輸出,以反映對輸入數據加工後的結果。沒有輸出的算法是毫無心義的;正則表達式

可行性:算法中執行的任何計算步驟都是能夠被分解爲基本的可執行的操做步,即每一個計算步均可以在有限時間內完成;算法

面向對象的5大原則:單一職責原則(SRP)、開放封閉原則(OCP) 、里氏替換原則(LSP)、依賴倒置原則(DIP) 、接口隔離原則(ISP);編程

 

二、C++不是類型安全的

答:C++ 是類型不安全的,C#和java是類型安全的。windows

對於C++類型不安全舉個例子:C++中能夠直接將本應返回bool型的函數返回int,而後由編譯器本身將int轉化爲bool型(非零轉化爲true,零轉化數組

false)。注意:類型安全就是指兩個類型直接要相互轉換,必需要顯示的轉換,不能隱式的只用一個等於號就轉換了。

補充:①string及STL模板庫是類型安全的;②MFC中CString是類型安全的類,其中全部類型轉換必須顯示轉換;

 

三、C++中常見的關鍵字含義

答:以下:

①inline:定義內聯函數,該關鍵字是基於定義,若是隻在函數聲明時給出inline,則函數不會被認爲是內聯函數,因此必須在函數定義的地方也加上inline,同時inline只是向編譯器建議函數之內聯函數處理,不是強制的;

②const:定義常成員,包括const數據成員和const成員函數,const數據成員必須,也只能經過構造函數的初始化列表進行初始化,const成員函數只能訪問類的成員,不能進行修改,若是須要修改,則引入下面的mutable關鍵字;

③mutable:這個關鍵字的引入是解決const成員函數要修改爲員變量,一般而言,const成員函數只能訪問成員變量,不能修改,可是若是成員變量被mutable修飾了,則在const成員函數中能夠修改該變量。mutable和const不能同時用於修飾成員變量;

④ static:聲明靜態成員,包括靜態數據成員和靜態成員函數,它們被類的全部對象共享,靜態數據成員在使用前必須初始化,而靜態成員函數只能訪問靜態數據成員,不能訪問非靜態數據成員,由於該函數不含有this指針;

static成員函數不能夠訪問非靜態成員的詳細解釋:

普通的非靜態成員函數訪問非靜態成員變量是由於類實例化生成爲對象後,對象的非靜態成員函數都擁有一個this指針,而實際上非靜態成員函數對成員變量的訪問都是經過這個this指針實現的(this就是對象指針)。而非靜態成員函數並不包含this指針,因此只能經過類名形式如A::n訪問成員變量,而支持該訪問方式的只有靜態成員變量。

⑤virtual:聲明虛函數,用於實現多態,該關鍵字是基於聲明的;

⑥friend:聲明友元函數和友元類,該關鍵字也是基於聲明的;

⑦volatile:被該關鍵字修飾的變量是指其值可能在編譯器認識的範圍外被修改,所以編譯器不要對該變量進行的操做進行優化。能夠與const同時修飾一個變量。

 

四、程序編輯、預編譯、編譯與連接

答:①編輯:也就是編寫C/C++程序。

②預處理:至關於根據預處理指令組裝新的C/C++程序。通過預處理,會產生一個沒有宏定義,沒有條件編譯指令,沒有特殊符號的輸出文件,這個文件的含義同本來的文件無異,只是內容上有所不一樣。

預處理注意事項:

1)預處理指令在程序編譯時就由編譯器操做,能夠放在程序的任意位置;
2)由於預處理指令後沒有分號,因此一行只能放一條,若要放多條,能夠用/來區分;
3)宏名推薦用大寫字母,但不是必須的;
4)宏是在編譯期間進行的,因此不佔用程序運行的時間。

③編譯:將預處理完的文件進行一系列詞法分析、語法分析、語義分析及優化後,產生相應的彙編代碼文件。

④連接:經過連接器將一個個目標文件(或許還會有庫文件)連接在一塊兒生成一個完整的可執行程序。連接是將各個編譯單元中的變量和函數引用與定義進行綁定,保證程序中的變量和函數都有對應的實體,若被調用函數未定義,就在此過程當中會發現。

 

五、引用庫文件時使用雙引號和尖括號的區別

答:使用#include」 「表示引用用戶庫文件,在當前目錄下查找,若沒有就到標準庫查找;

使用#include< >表示引用標準庫文件,直接到到標準庫查找;

因此,若引用標準庫文件如stdio.h,用< >會比用" "查找快一些。

 

六、C/C++中的.h頭文件中ifndef/define/endif

答:主要做用是防止重複引用,好比一個頭文件定義以下:
#ifndef _HEAD_H_
#define _HEAD_H_
//主體代碼
#endif
假如該頭文件第一次被引用,_HEAD_H_沒有被定義,因此就執行宏定義,直到#endif;
該頭文件第二次被引用的時候,_HEAD_H_已經被定義,下面的語句就不會執行。

 

七、動態連接和靜態連接

答:動態連接庫:windows中是.dll,Linux中通常是 libxxx.a;

靜態連接庫:windows中是.lib,Linux中通常是 libxxx.so;

(1)靜態連接與動態連接區別:

①靜態函數庫在連接時將整個函數庫整合到中應用程序中並生成,因此程序生成文件較大;動態庫相反,連接時不是全部數據都加載應用程序文件(只是加載導入庫文件),因此生成文件較小。

②靜態連接生成後的執行程序不須要外部的函數庫支持,能夠保持獨立且可移植性好;動態庫相反,須要庫函數繼續存在,可移植性很差。

③若是靜態函數庫改變了,那麼你的程序必須從新編譯連接;動態庫相反,升級方便。

④靜態庫函數在程序連接階段和應用程序一塊兒完成連接生成;動態庫函數要到程序運行時才連接生成。

⑤靜態連接庫不能夠實現多程序共享,由於每一個須要的程序都要將其編譯生成到本身的目標文件中;而動態連接庫則能夠一個庫文件被多個程序共享。

(2)動態連接狀況下程序如何得到動態庫函數?

動態連接時雖然不加載庫函數自己,可是會加載一個導入庫(lib包含目標庫和導入庫),導入庫裏面保存了動態庫函數的函數名、參數等信息,程序運行時操做系統就經過這些信息找到函數庫自己並加載。

(3)動態連接分爲顯式加載與隱式加載:

顯式加載:在程序剛運行時就加載dll;

隱式加載:在程序運行須要庫函數時再加載dll;

(4)


 

第二章、各類常規數據類型及相關運算

一、32位機和64位機的不一樣類型數據佔用存儲空間不一樣

答:注意:如下所區分的32位系統和64位系統都是針對Linux而言的:

通常注意32位系統中,short爲2字節,int是4字節,float爲4字節,long爲4字節,double是8字節,指針佔用4字節等就能夠,64位除了指針佔用8字節和long佔用8字節,其餘與32位相同。但注意,16位機器與32位有較大區別,如Int佔用2字節,指針佔用2字節等。

 

二、爲何const比宏定義好?

答:①能夠指定類型,且有類型檢查功能;②const量規定做用域規則,如在函數中定義常量,從而將其限制在函數內起做用;③能夠將const用於更復雜的類型,好比數組和結構。

 

三、自動變量

答:(1)早在C++98標準中就存在了auto關鍵字,那時的auto用於聲明變量爲自動變量,自動變量意爲擁有自動的生命期,這是多餘的,由於就算不使用auto聲明,變量依舊擁有自動的生命期:

  1.  
    int a =10 ; //擁有自動生命期
  2.  
    auto int b = 20 ;//擁有自動生命期
  3.  
    static int c = 30 ;//延長了生命期

(2)C++11中的自動變量不在是上述無關緊要的做用,變爲能夠在聲明變量的時候根據變量初始值的類型自動推導此變量匹配的類型,相似的關鍵字還有decltype。舉個例子:

  1.  
    auto au_a = 10;//自動類型推斷,au_a爲int類型
  2.  
    cout << typeid(au_a).name() << endl;

這種用法就相似於C#或java中的var關鍵字。auto的自動類型推斷髮生在編譯期,因此使用auto並不會形成程序運行時效率的下降。

 

(3)auto的做用:用於代替冗長複雜、變量使用範圍專注的變量聲明。

(4)auto的用法:

①代替比較長的類型名:

原始代碼:

  1.  
    std::vector<std::string> vs;
  2.  
    for (std::vector<std::string>::iterator i = vs.begin(); i != vs.end(); i++)
  3.  
    {
  4.  
    //...
  5.  
    }

使用auto後的用法:

  1.  
    std::vector<std::string> vs;
  2.  
    for (auto i = vs.begin(); i != vs.end(); i++)
  3.  
    {
  4.  
    //..
  5.  
    }

 

for循環中的i將在編譯時自動推導其類型,而不用咱們顯式去定義那長長的一串。

②在定義模板函數時,用於聲明依賴模板參數的變量類型

  1.  
    template <typename _Tx,typename _Ty>
  2.  
    void Multiply(_Tx x, _Ty y)
  3.  
    {
  4.  
    auto v = x*y;
  5.  
    std::cout << v;
  6.  
    }

 

若不使用auto變量來聲明v,那這個函數就難定義啦,不到編譯的時候,誰知道x*y的真正類型是什麼。

 

四、默認類型自動轉換

答:在運算式中有多種數據類型,在沒有強制類型轉換狀況下就須要編譯器按照默認的自動轉換,以下圖:

其中,橫向的箭頭表示,在運算以前必須轉換的;豎向的箭頭表示運算過程當中默認轉換的順序,也就是說,float類型的數據在運算以前,都轉換爲double類型的數據進行運算,同理,short和char類型的數據在運算以前,都是轉換爲int類型的數據進行運算。

這裏補充一個知識點:浮點型數據在C++中不說明,默認是double。
例如,0.5默認就是double,因此fun(float c)調用時直接fun(0.5)就是不對的,會報參數不匹配錯誤。正確應該爲:fun(0.5f)。

 

 

五、計算機中有符號數據存儲形式

答:正數存儲原碼,負數存儲爲補碼。
數據輸出結果根據輸出類型決定;
短類型數據轉長類型數據,用短類型數據的符號位填充新增長的空白高位,例如:1000 0000(char)->1111 1111 1000 0000(short);

 

 

六、C++位操做符與邏輯運算符

答:(1)位操做以下圖:

(2)邏輯運算符

A && B:A不成立,B就不判斷了;

A || B:A成立,B就不判斷了;

 

七、如何判斷double變量是否爲0?

答:首先,double變量因爲精度截尾問題,是沒法用==直接做比較的。因此通常就認定小於小數點後多少位開始是0。好比:
if( abs(f) <= 1e-15 )就是判斷f是否爲0的;
對於比較兩個雙精度a和b是否相等,相應的應該是:abs(a-b)<=1e-6;
對於float型數據比較,與double相似;

 

八、float和double數據類型的不一樣

答:(1)這裏介紹下浮點型數據float和雙精度double在32位機的存儲方式(並非直接將十進制轉換爲普通二進制就能夠存儲了),不管是float仍是double,在內存中的存儲主要分紅三部分,分別是:
①符號位(Sign):0表明正數,1表明負數
②指數位(Exponent):用於存儲科學計數法中的指數部分,而且採用移位存儲方式
③尾數位(Mantissa):用於存儲尾數部分
指數位用於表示該類型取值範圍;尾數位用於反映有效數字。

(2)具體存儲模式:
類型 符號位 階碼 尾數 長度 
float 1 8 23 32 
double 1 11 52 64 
臨時數 1 15 64 80
float的指數部分有8bit,因爲指數也要分正負數(這個正負數不是float型數據的正負,而只是存儲指數的正負)例如2^-2,其中-2就是負指數,因此獲得對應的指數範圍-128~128。 float的尾數位是23bit,對應7位十進制數;
double的指數部分有11bit,因爲指數分正負,因此獲得對應的指數範圍-1024~1024,52個尾數位,對應十進制爲15位;

(3)取值範圍看指數,有效數字個數看尾數。
float取值範圍:2^-128~2^128;有效數據位數:因爲2^23=8388608爲7位,因此理論上是7爲有效數據,但實際編譯器是8位(緣由見下面);
double取值範圍:2^-1024~2^1024;有效數據位數:因爲2^52=4503599627370496爲15位,因此有效數據位是15爲有效數據;

(4)說了半天還只說了尾數能夠反映有效數據位數,但並沒說尾數是什麼?下面解釋:
例如9.125的表示成二進制就是:1001.001,進一步將其表示成二進制的科學計數方式爲:1.001001*2^3 ;
實際上,在計算機中任何一個數均可以表示成1.xxxxxx*2^n 這樣的形式。其中xxxxx就表示尾數部分,n表示指數部分。因此尾數部分就是待存儲二進制數據的有效數據;
其中,由於最高位橙色的1這裏,因爲任何的一個數表示成這種形式時這裏都是1,因此在存儲時實際上並不保存這一位,這使得實際尾數位比存儲的多一位,即float的23bit的尾數能夠表示24bit(2^24十進制結果是8位,解釋了3)的疑問),double中52bit的尾數能夠表達53bit(2^53十進制依然是15位)。

(5)舉個例子:

如下數字在表示爲double(8字節的雙精度浮點數)時存在舍入偏差的有()。
A、2的平方根
B、10的30次方
C、0.1
D、0.5
E、100
答案:ABC
解析:A:2開平方根後結果的十進制都是無窮數,因此二進制形式必定是無窮的,那麼根據二進制有效數據最多53個(或十進制15個),必定有捨棄部分;
B:2^90=(2^3)^30<10^30<2^100,那麼10的30次方變二進制至少有90個有效數據位數,假定就是90個。而後將該十進制轉化爲二進制數(方法見下面(2)),10^30=5^30*2^30,故最後一個有效二進制數1在由低到高第31位處,而有效位數前面獲得爲90,因此有效位數爲90-30=60>53,意味着要捨棄。
C:0.1變二進制形式是無窮位0.001100110011……,因此有捨棄;
D:二進制爲0.1,無捨棄;
E:100二進制1100100,符合要求無捨棄;

 

九、幾種常見有符號數的溢出問題

答:1)有符號數的取值範圍都是負數部分絕對值比整數部分大1;,以char爲例-128~127;
2)有符號數溢出後都是循環到數據範圍的另外一端,例如char型127+1結果就是-128,127+2結果是-127,……還有-128-1結果是127,-128-2結果是126,……以此類推;
下面分別解釋上述1)、2):
①因爲有符號數最高位爲符號位,那麼實際數據位就是n-1位。例如char型只有7個數據位,能夠表示-127~127,但這樣問題來了,數據位0分別和符號位組成了-0和+0,那麼設計者就用+0表示0,-0表示-128,這就是-128的由來,也就是說用-128代替-0。
那爲何-128能夠用-0表示呢?多是由於-128二進制爲1 1000 0000,但char型只有8位,因此要截斷最高位,這樣就和-0(1000 0000)同樣了;
-128 二進制補碼也爲1 1000 0000,和原碼相同,計算機中負數都是用補碼存儲的,所以-128在計算機存儲中也應該是截斷後的1000 0000,那麼也能夠看做-0的補碼也不變化。
②這就能夠解釋爲何上述2)中char型127+1=-128和-128-1=127了?

127+1=

  01111 1111

+0000 0001

=1000 0000

該值沒有數據位溢出不用捨棄,結果恰好是-0,也就是-128的二進制值。

計算機二進制運算中沒有減法或負數運算,只有加法運算,全部的負數或減法(減號當作負號,與減數組合成一個負數)都是先轉化爲相應補碼,再進行二進制加法運算,那麼-128-1就應該是(-128)補+(-1)補的結果:

-128-1=

    1 1000 0000  (-128的補碼與原碼同)

+     1111 1111

=11 0111 1111

高位溢出捨棄,用二進制表示結果爲:0111 1111,即爲127。

 

十、C++運算符優先級

答: 可以下記憶:
記住一個最高的:構造類型的元素或成員以及小括號;
記住一個最低的:逗號運算符;
剩餘的是單目、雙目、三目和賦值運算符(賦值運算符包括=、+=、-=、*=、/=、%=、……)。

注意如有逗號表達式的是:a=(表達式1,表達式2,……,表達式n) 

結果爲:a=表達式n,即逗號表達式的結果是最後一個表達式的值。但若是不加括號就是第一個表達式的值,由於賦值運算符優先級比逗號運算符高。

特別提出:指針運算符混合運算的優先級:因爲單目運算符*和其餘的一些單目運算符優先級同級,所以運算時根據從右至左結合方式。以下:

①一元運算符 * 和前置 ++ 具備相等的優先級別,可是在運算時它是從右向左順序進行的,即在 *++p中,++ 應用於p而不是應用於*p,實現的是指針自增1,而非數據自增1;後置自增++的優先級實際上比*高,但使用右結合方式獲得的結果與之一致,故爲了統一表述,均可以直接使用右結合方式。如*p++,使用優先級是先++後*,右結合也是先++後*;

②指針運算符* 與取地址運算符&的優先級相同,按自右向左的方向結合;

 

十一、爲何前綴自增自減運算符比後綴自增自減運算符效率高?

答:以自增運算爲例,由於前綴運算符進行運算時是將值加1,而後返回結果便可;但後綴版本首先複製一個副本做爲返回值,而後纔是原值加1。所以,前綴版本的效率比後綴版本高。

 

十二、C++中的四種類型轉換方式(重要)

答:(1)常見的(type)類型轉換是C語言中的類型轉換方式,其有不少缺陷如:容易產生兩個不和互相轉換的類型被轉換,從而引起錯誤等等。C++爲了克服C中的缺陷,引入了四種更加安全的cast類型轉換模式,以下:

①static_cast:最經常使用的類型轉換符,正常情況下的類型轉換,如把int轉換爲float,如:int i;float f;f=(float) i;或者用C++類型轉換模式:f=static_cast<float>(i);

②const_cast:用於取出const屬性,把const類型的指針變爲非const類型的指針,如:const int *fun(int x,int y){}; int *ptr=const_cast<int *>(fun(2,3));

③dynamic_cast:一般在它被用於安全地沿着類的繼承關係向下進行類型轉換,可是被轉換類必須是多態的,即必須含有虛函數。例如:dynamic_cast<T*> (new C);其中類C必須含有虛函數,不然轉換就是錯誤的;

④reinterpret_cast::interpret是解釋的意思,reinterpret即爲從新解釋,能夠把一個指針轉換成一個整數,也能夠把一個整數轉換成一個指針。 如:int i; char *ptr="hello freind!"; i=reinterpret_cast<int>(ptr);

注:C++計算中的隱式類型轉換是static_cast,轉換中若表達式包含signed和unsigned int,signed會被轉換爲unsigned。例如:

int i = -1;
unsigned j = 1;
if(j > i)與if(i < j)

答案:上述兩個判斷都是false。

(2)這裏補充「無符號數和有符號之間的轉化關係」:

① 無符號數轉換爲有符號數:

看無符號數的最高位是否爲1,若是不爲1(即爲0),則有符號數就直接等於無符號數;

若是無符號數的最高位爲1,則將無符號數取補碼,獲得的數就是有符號數。

②有符號數轉換爲無符號數:

看有符號數的最高位是否爲1,若是不爲1(即爲0),則無符號數就直接等於有符號數;

若是有符號數的最高位爲1,則將有符號數取補碼,獲得的數就是無符號數。

具體解釋可參見:http://blog.csdn.net/starryheavens/article/details/4617637

 

1三、別名與宏的區別——typedef與#define的區別

答:類型別名與宏的三點區別,如typedef char *String_t; 和#define String_d char * 兩句:

(1)前者是類型別名,要作類型檢查,後者只是一個替換,不作類型檢查;
(2)前者編譯時處理,後者預編譯時處理,即預編譯期間替換掉宏;
(3)前者能保證定義的全都是char* 類型,String_d卻不能,如String_t a,b;String_d x,y;其中a、b、x爲char*類型,而y倒是char類型,這點要注意(由於簡單替換就是:char *x,y,y前面沒有指針符就不是指針)。

 

1四、C中的宏定義的開始做用位置

答:宏定義是在編譯器預處理階段中就進行替換了,替換成什麼只與define和undefine文本中的定義位置有關係,與它們在哪一個函數中無關。例如:

  1.  
    #define a 10
  2.  
    void foo();
  3.  
    void prin();
  4.  
    int main()
  5.  
    {
  6.  
    prin();
  7.  
    printf("%d ", a);
  8.  
    foo();
  9.  
    printf("%d ", a);
  10.  
    }
  11.  
    void foo()
  12.  
    {
  13.  
    #undef a
  14.  
    #define a 50
  15.  
    }
  16.  
    void prin()
  17.  
    {
  18.  
    printf("%d ", a);
  19.  
    }

 

輸出結果是:50 10 10

爲何?由於a從新定義只在print()前,估值對它有效。

 

 

第三章、複合類型——數組、字符串、string、指針與鏈表等

一、C++中字符串和普通數組初始化相關問題

答:C++有兩種字符串表示模式:C風格的char []和string模式;其中C風格的char c[]初始化要注意的原則:char c[]數組末尾是‘\0’,沒有就不是字符串,如char str[4]={'1','2','s','r'}就不是字符串。

數組初始化注意事項:

(1)使用{ }初始化只有定義是能夠,之後就不能用了,如int c[4]={2,3,4,5}能夠,但int d[4];d[4]={1,2,3,4}就錯誤;

(2)不能夠將一個數組賦給另外一個數組,如c=d是錯誤的,由於數組名是常量;

(3)C++11中用{ }初始化數組,能夠省略=,如int c[4] {2,3,4,5}合法;

(4)使用{ }初始化禁止縮窄轉換,如int p[3]={1,2,3.0}非法,由於浮點型的小數點後面數據被捨棄了,就是縮窄了;

下面介紹下C++的int型數組初始化:

二維數組初始化分爲多種形式。注意,當只對部分元素賦初值時,未賦初值的元素自動取0值。例如:

①按行賦值

int a[ ][3]={{1,2,3},{4,5,6}};——至關於{{1,2,3},{4,5,6}}

int a[ ][3]={{1,2},{0}};——至關於{{1,2,0},{0,0,0}}

②連續賦值

int a[ ][3]={1,2,3,4,5,6};——至關於{{1,2,3},{4,5,6}}

int a[ ][3]={2};——至關於{{2,0,0}};

 

二、多維數組定義要點

答:多維數組定義中只有最靠近數組名的那個維數能夠省略,其他不容許省略不然沒法肯定數組。例如:a[m][n]僅僅能夠省略m,n必須定義。

 

三、int *p=new int(12)與int *p=new int[12]的區別

答:前者表示建立一個指針變量,其指向一個存儲數字12的地址,後者表示建立一個長度爲12的數組。

 

四、柔性數組

答:柔性數組在C++中只用在結構體中,且只能定義在結構體的末尾,以下:
struct Node
{
    int size;
    char data[0];
};
注:上述也能夠char data[];
此時data只是表示一個數組符號,不佔內存空間,就是一個偏移地址(必定注意,此時數組名data不是一個指針常量,是不佔內存的)。
做用:長度爲0的數組的主要用途是爲了知足須要變長度的結構體,實現結構靈活使用,方便管理內存緩衝區,減小內存碎片化,通常都在結構體末尾。

在網絡通訊中的實際用途:因爲考慮到數據的溢出,數據包中的data數組長度通常會設置得足夠長足以容納最大的數據,所以packet中的data數組不少狀況下都沒有填滿數據,所以形成了浪費,而若是咱們用變長柔性數組來進行封包的話,就不會浪費空間浪費網絡流量。因此,柔性數組在通訊中主要做用是,保證了維護數據包空間的連續性。

 

 

五、數組名的做用以及數組名前面添加取地址符的做用

答:1)數組名是數組首地址,是一個常量,不能夠看成指針變量用,如:若str爲數組名,str++就不合法,至關於常量自增。

再次注意:數組名是常量!常量!常量!常量就不可被賦值,如有char s[10];char *pt,則以下:

s="hello";//將常量賦給s,實質就是將常量首地址賦值給s;

s=pt;

都是錯誤的,s是數組名不可被賦值,任何形式的賦值都不能夠。

2)同時,一維數組名當被直接使用時,是一個指向數組首地址的指針。

3)若是數組A是多維數組,那麼數組名A是數組地址(是一個多級指針),而不是第一行地址(是一級指針),雖然它們的值都是首地址值。也就是說:以二維數組A[][]爲例,A和A[0]是不一樣的,雖然地址值都是數組首元素地址,可是一個是二級指針,一個是一級指針。因此,只有*(A+i)與A[i]是等效的。

對於二維數組a[i][j],此時*(a+i)與a[i]是一個意思,當直接用a[i]時表明的是該數組第i行地址,因此多維數組a[][]的*(a[i]+j)或者*(*(a+i)+j)是與a[i][j]等效。

4)還有,數組名錶示首地址,那麼數組名前有取地址符是什麼意思?例如:數組a[],a表示數組首元素地址,&a表示數組總體地址,&a+1就是該數組末尾後一個地址。

5)printf打印數組名:若char a[5]="abcd",printf("%d \n",a)則輸出是數組首地址;若要輸出字符須要printf("%C \n",a[i])一個個輸出,如printf("%C \n",a[0])輸出第一個元素。固然,printf("%s \n",a)是能夠將數組做爲字符串一塊兒輸出的。

 

六、sizeof對數組作運算的兩種狀況

答:分兩種狀況:

(1)直接計算:

  1.  
    char str[]="Hello";
  2.  
    cout<<sizeof(str)<<endl;

結果:6

(2) 經過形參傳入:

  1.  
    void func(char str_arg[100])
  2.  
    {
  3.  
    cout<<sizeof(str_arg)<<endl;
  4.  
    }
  5.  
    func( "test");

結果:4;

分析:(1)中是直接將數組名做爲sizeof的操做數,因此就是計算數組自己空間大小(注意,不是數組長度),包括"\0",應該爲6*1=6(單個char字符佔一個字節),比較容易;(2)中的特色是數組做爲函數的參數傳遞時,就是計算指針大小了。char str_arg[100]實際爲 char *str_arg,傳遞的是指向數組首元素的指針,那麼sizeof(char *str_arg)=4(32位Linux系統)。

補充注意:1)sizeof(malloc)等於4,由於malloc()返回的是一個指針。sizeof只對數組名的指向內容做內存計數運算;2)如有printf("%%%%\n"),只輸出%%。由於每一個輸出前都有一個%表示輸出。

 

七、sizeof以及sizeof與strlen的區別

答:(1)sizeof 操做符不能返回動態分派的數組或外部的數組尺寸(只用於返回靜態數組的尺寸,且通常都在編譯時就會運算結果)。具體以下:
sizeof 返回的值表示的含義以下(單位字節):
數組 —— 編譯時分配的數組空間大小(不是數組長度); 
指針 —— 存儲該指針所用的空間大小(存儲該指針的地址的長度,是長整型,應該爲 4 ); 
類型 —— 該類型所佔的空間大小; 
對象 —— 對象的實際佔用空間大小; 
函數 —— 函數的返回類型所佔的空間大小。函數的返回類型不能是 void 。
(2)sizeof 返回所有數組的尺寸;strlen時字符串長度計算函數,返回字符串實際長度,遇到'/0'則結束。遇到如:char str[20]="0123456789"; int a=strlen(str); //a=10; >>> strlen 計算字符串的長度,以結束符 0x00 爲字符串結束。 int b=sizeof(str); //而b=20; >>> sizeof 計算的則是分配的數組 str[20] 所佔的內存空間的大小,不受裏面存儲的內容改變。 上面是對靜態數組處理的結果。
還有如:charc1[]={'a','b','\0','d','e'};strlen(c1)=2;由於strlen計算字符串的長度,會把c1當作字符串,遇到'/0'結束。
注:sizeof對於字符串會包括'\0',strlen不會包括'\0'。
(3)對於指針,sizeof會將其視爲類型:
char str[] ="aBcDe";
char *str1 ="aBcDe";
cout << "str[]字符長度爲: "<<sizeof(str)<<endl;//結果爲6
cout<<"*str1字符長度爲: "<<sizeof(str1)<<endl; //結果爲4 系統32位
cout << "str字符長度爲: " << sizeof(str) / sizeof(str[0]) << endl;//結果爲6
cout << "str字符長度爲: " << strlen(str)<< endl;//結果爲5

(4)sizeof是運算符,strlen是函數;


八、C++中的字符串變量和字符串數組變量定義

答:(1)字符串變量定義:

1)用字符數組定義:char str[10];//靜態定義,這種方式定義最廣泛,適用於做爲cin,scanf的輸入接口;

2)用字符指針定義字符數組:char* str;//注意這種只是定義了一個指針,並未分配內存區域,沒法直接向其寫入數據,因此不適用於cin和scanf輸入;

3)用字符指針定義字符數組並開闢內存區域:char *str=new char[10];//動態定義,這種是能夠做爲cin、scanf輸入使用的,由於開闢了內存區域;

4)使用容器string也能夠定義字符串,可是注意string字符串的長度獲取通常使用size(),而char []主要是用<string.h>(屬於C的庫文件)中的strlen()函數。string str可直接用於cin或者scanf輸入。

(2)字符串數組定義:

1)用char數組定義:char s[10][100];//靜態定義

2)字符指針數組定義:char *s[10];//注意這種只是定義了指針變量,並未分配內存空間,沒法向其直接寫入數據,不可用於cin或scanf。使用前必須先賦值或分配內存。

3)用字符指針定義字符串數組並開闢內存區域:char *str[10]=new char[][100]; //動態定義,可用於cin或scanf輸入;

4)使用string容器定義字符串數組:string str[10]; //靜態定義,可用於cin或者scanf輸入;

5)使用string容器定義字符串指針數組:string* str; //未分配內存,不可用於cin或者scanf輸入;

6)使用string容器定義字符串指針數組並分配內存區:string* str=new string[10]; //動態定義,可用於cin或者scanf輸入;

 

九、C++11中的原始字符串

答:C++11中的原始字符串用R表示,定義爲:R 「xxx(raw string)xxx」 ;
其中,原始字符串必須用括號()括起來,括號的先後能夠加其餘字符串,所加的字符串會被忽略,而且加的字符串必須在括號兩邊同時出現。

原始字符串能夠直接把雙引號"或者轉義字符\n當作普通字符串輸出,而標準的字符串則會將其當作特定功能符號如\n換行。

 

十、空串和空格串

答:長度爲0 的串爲空串,即爲「」 。由多個空格字符構成的字符串稱爲空格串。

補充:空串是任何字符串的子串;

 

十一、字符串比較

答:字符串char [](注意不是string,string是能夠直接==比較的)比較須要使用函數strcm(str1,str2)函數,而不能直接使用「字符串1==字符串2」、「字符串1<字符串2」或者符串1>字符串2」;如有以下狀況:

比較符號兩邊分別是一個地址或指針與字符串,那麼比較的是字符串地址是否與被比較地址之間的關係,如:

char *p="hello";

return p=="hello";

返回值是1;

補充:若是是char[]類型的字符串,須要使用strcmp()函數;如果string串,直接使用if(s1>s2)之類比較便可。

 

十二、(void *)ptr和(*(void**))ptr的結果是否相同?

答:相同。由於第一個就是把ptr強制轉化爲指向空類型的指針;
第二個(*(void**))ptr中的(void**)ptr是將str轉化成指向void類型值的指針的指針,也就是二級指針。再在前面加上*就是取內容,那麼內容也就是一個只想空類型的一級指針,因此強制轉化後和第一個是同樣的。


1三、指針變量所佔內存大小和指針加減有關的問題

答:指針相減的值:只有同類型的指針才能夠相減,且「相減結果/單個變量類型內存大小」纔是最終結果;

如若int *p=a[0],int *q=a[2],那麼q-p=(q-p)/sizeof(int)=8/4=2是編譯器默認的結果。

指針變量所佔內存大小:32位操做系統是32位(4字節),64位操做系統是64位(8字節);

指針變量自增自減:自增自減中指針變量值的改變大小等於所指向的對象類型的內存大小。如:char* pt1,那麼pt1++就是加1,若爲int* pt2,那麼pt2++就是增長4。指針運算問題都是以相應變量的類型大小做爲基本單位的,例如int p[4]={0,0,0,0},p+1就是指p的地址基礎上偏移4字節。同理,&p[0]+1也是同樣的。除非(char*)&p[0]+1纔是偏移一個字節,由於地址被強制轉化爲char*了。

補充:
(1)指針所指向的對象類型判斷方法——去掉指針名及和它前面的*,剩下的就是指針類型。
(2)上面所說的地址地址++或--都是對於偏移地址offset而言的。

下面舉例:

  1.  
    //驗證32位系統中指針變量所佔內存大小爲4字節,而且指針變量自增(或自減)不一
  2.  
    //定是增長1,需根據所指向的對象類型決定
  3.  
    short strpt1[3]={1,2,3};
  4.  
    short* pt1=strpt1;
  5.  
    cout<<"char*指針變量所佔內存大小:"<<sizeof(pt1)<<"\n";
  6.  
    cout<<"char*指針變量自增前結果:"<<pt1<<"\n";
  7.  
    pt1++;
  8.  
    cout<<"char*指針變量自增後結果:"<<pt1<<"\n";
  9.  
    //比較
  10.  
    int strpt2[3]={1,2,3};
  11.  
    int* pt2=strpt2;
  12.  
    cout<<"int*指針變量所佔內存大小:"<<sizeof(pt2)<<"\n";
  13.  
    cout<<"int*指針變量自增前結果:"<<pt2<<"\n";
  14.  
    pt2++;
  15.  
    cout<<"int*指針變量自增後結果:"<<pt2<<"\n";

結果:

 

 

 

1四、數組指針、指針數組、函數指針

答:區別以下:
(1)數組指針(也稱行指針),定義 int (*p)[n];其中( )優先級高,首先說明p是一個指針,指向一個整型的一維數組(或二維數組的某一行),這個一維數組的長度是n,也能夠說是p的步長。也就是說執行p+1時,p要跨過n個整型數據的長度。

 

(2)指針數組,定義 int *p[n];其中[]優先級高,先與p結合成爲一個數組,再由int*說明這是一個整型指針數組,它有n個指針類型的數組元素。這裏執行p+1時,則p指向下一個數組元素,這樣賦值是錯誤的:p=a;由於p是個不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],並且它們分別是指針變量能夠用來存放變量地址。但能夠這樣 *p=a; 這裏*p表示指針數組第一個元素的值,a的首地址的值。

若已定義: int a[]=[0,1,2,3,4,5,6,7,8,9],*p=a,i; 其中0≤i≤9,那麼p[i]是否合法,若何法那麼表示什麼?

p[i]表示a[i]的指針地址,即p+i。


(3)函數指針,定義 int (*pf)(int *)爲一個返回值爲int,參數爲int*的。
假如函數指針定義以下例:
void (*fp)(int);
則函數指針調用的兩種格式:
1)(*fp)(9);
2)fp(9);
函數指針賦值的兩種格式:
void fun(int s){……}
1)fp=fun;(函數名就是函數起始地址)
2)fp=&fun;(這種也能夠)

注意,函數名後面必定不能有括號,不然就被認爲是函數調用。

 

1五、空指針問題

答:(1)空指針與野指針的區別:

空指針也就是一般指向爲NULL的指針,常見的空指針通常指向 0 地址;

野指針是指向一個已刪除或釋放的對象或指向未申請訪問受限內存區域的指針;

具體以下三種狀況:

1)指針變量未初始化
任何指針變量剛被建立時不會自動成爲NULL指針,它的缺省值是隨機的,它會亂指一氣。因此,指針變量在建立的同時未被初始化,它就是野指針。定義指針變量時要避免野指針,要麼將指針設置爲NULL,要麼讓它指向合法的內存;
2)指針釋放後以後未置空
釋放內存後指針要手動置爲空,不然會產生野指針。有時指針在free或delete後未賦值 NULL,便會令人覺得是合法的。別看free和delete的名字(尤爲是delete),它們只是把指針所指的內存給釋放掉,但並無把指針自己幹掉。此時指針指向的就是被釋放區的「垃圾」內存,也就成爲了野指針。釋放後的指針應當即將指針置爲NULL,防止產生「野指針」;
3)指針操做超越變量做用域
不要返回指向棧內存的指針或引用,由於棧內存在函數結束時會被釋放。

(2)空指針性質:

空指針不指向任何的對象或者函數,任何對象或者函數的地址都不多是空指針;

malloc()申請內存空間失敗的時候,人家返回的值爲NULL,而不是任意的;

雖然空指針可能大多數指向0地址,但對0x0這個地址取值是非法的,由於系統規定是不會給你分配0地址的。

(3)空指針的printf輸出

例如:char* str=null;

printf("%s\n",str);

在win系統輸出結果:(null)

在Linux系統輸出結果:Segmentation fault(出錯)

注:若str是野指針,即未初始化,那麼這個輸出代碼會形成程序崩潰。

 

1六、靜態鏈表

答:靜態鏈表是用數組來描述的,也能夠說是靜態鏈表就是結構體數組。

數組中的結構體元素包括兩部分:數據和指針,其中指針指向下一個元素在數組中所對應的下標。

1)使用數組對元素進行存儲,在定義時大小已經肯定,後期不可擴展容量;

2)靜態鏈表的例子正好說明了數組相對鏈表除了能夠隨機訪問外,還有另外一個優點:相同的數據佔用存儲空間小(由於鏈表還要存儲節點指針)。

 

 

第四章、複合類型——結構體、共用體、枚舉等

一、C/C++中的結構體對齊原則

答:關於結構體內存對齊(在沒有#pragma pack宏的狀況下):
•原則一、數據成員對齊規則:結構(struct或聯合union)的數據成員,第一個數據成員放在offset爲0的地方,之後每一個數據成員存儲的起始位置要從該成員類型大小(注意是成員類型大小,而不是成員大小,如char a[10],應該取類型大小爲1字節而不是數組大小10字節)的整數倍開始(好比int在32位機爲4字節,則要從4的整數倍地址開始存儲)。
•原則二、結構體做爲成員:若是一個結構裏有某些結構體成員,則結構體成員要從其內部類型最大元素大小的整數倍地址開始存儲。(struct a裏存有struct b,b裏有char,int,double等元素,那b應該從8的整數倍開始存儲。)
•原則三、收尾工做:結構體的總大小,也就是sizeof的結果,必須是其內部最大成員類型長度的整數倍,不足的要補齊(注意:補齊是補高位,就是填充0)。
舉個例子:
struct stc
{
int a;
char b[9];
char c;
};
這個結構體的大小是16字節=4字節(int)+9字節(char*9)+1字節(char)+2字節(補全)

注意:

1)若結構體中含有靜態成員,sizeof不包括靜態和成員,由於靜態成員存儲在全局區(或靜態區),sizeof計算的是棧空間大小(此結論亦適用於類);

2)若是編譯器中提供了#pragma pack(n),上述對其模式就不適用了,例如設定變量以n字節對齊方式,則上述成員類型對齊寬度(應當也包括收尾對齊)應該選擇成員類型寬度和n中較小者;

3)通常數據存儲都是小端機器模式,即數據低位存儲在低地址處。

補充:

(1)位域:所謂"位域"是把一個字節中的二進位劃分爲幾 個不一樣的區域, 並說明每一個區域的位數。每一個域有一個域名,容許在程序中按域名進行操做。 這樣就能夠把幾個不一樣的對象用一個字節的二進制位域來表示。結構體中存在位域時,對於位域變量大小有以下規定:

1) 若是相鄰位域字段的類型相同,且緊鄰的位域位寬之和小於給定類型的sizeof大小,則後面的字
段將緊鄰前一個字段存儲; 
2) 若是相鄰位域字段的類型相同,但其位寬之和大於給定類型的sizeof大小,則後面的字 
段將重新的存儲單元開始,其偏移量爲其類型大小的整數倍; 
3) 若是相鄰的位域字段的類型不一樣,則各編譯器的具體實現有差別,VC6採起不壓縮方 
式,Dev-C++採起壓縮方式; 
4) 若是位域字段之間穿插着非位域字段,則不進行壓縮; 
5) 整個結構體的總大小爲最寬基本類型成員大小的整數倍。

(2) sizeof(類):類的大小計算:類的存儲大小sizeof運算也能夠當作結構體來計算,注意函數聲明不佔內存。

可是要注意繼承問題:好比派生類繼承基類,那麼:
派生類大小=基類成員(不包括靜態成員)+本身自己

 

注意:

1)類的靜態成員因爲存放在全局區域,爲全部類共享,sizeof運算不包括靜態成員;

2)虛函數在類中有一個函數指針指向虛表,例如,對於一個類對象如obj,其中指針運算*obj,有以下兩種狀況:
①類中有虛函數,那麼虛函數表的指針將放在全部類對象成員最前面,那麼*obj指向虛函數表指針。在32位系統中,指針是4個字節,那麼對象內存的前4個字節都是存儲虛函數表指針,第5個字節纔開始存放類成員;
②若無虛函數,則*obj直接指向按順序類中的第一個成員。

③因此如有虛函數聲明的類,sizeof運算會將虛函數表指針大小算在內(32bit爲4字節)。不管類中有幾個虛函數,sizeof類都等於sizeof(數據成員)的和+sizeof(一個V表指針,爲4);

④對於上述③中所說的只是在單繼承或非繼承時成立,派生類多繼承時每繼承一個含有虛函數的基類,其就增長一個虛函數表指針。若其自己還有虛函數,則該虛函數地址存放在派生類的第一個虛函數表中;

3)空類,或者類中只有函數聲明(虛函數除外),沒有成員變量的類,其大小sizeof(空類)=1。注意,空類求sizeof不考慮對齊問題;

4)對於子類,它的sizeof是它父類成員(不管成員是public或private),再加上它本身的成員,對齊後的sizeof;

5)對於子類和父類中都有虛函數的狀況,子類的sizeof是它父類成員(不管成員是public或private,但不包括父類虛表指針),再加上它本身的成員,對齊後的sizeof,再加4(子類本身的虛表指針);

6)若既有虛函數又有虛繼承,那麼子類sizeof=基類sizeof(包括基類虛指針)+子類sizeof(包括子類虛指針)+子類指向基類的指針(32bit爲4字節)(這個要注意)

更多sizeof運算參考:http://blog.csdn.net/u014186096/article/details/48290013

 

(3)C/C++中的聯合體(共用體):聯合體是相似結構體的,但聯合體中的成員是共用內存的,也就是聯合體中的成員共用同一段內存(這也是聯合體存在的做用,用於節省內存),其內存大小根據成員類型最大長度的對齊原則肯定,並且,聯合體中每一時刻只有當前的一個成員有效,即須要用哪一個成員,就對共用內存從新寫入該成員的數據,不重寫讀出來的就仍是前一個成員使用的數據;

注意:有人說聯合體的變量不能在定義時初始化,這是錯誤的。聯合體變量能夠在定義時初始化,可是初始化的值必須是第一個成員變量,並且要用{}括起來。以下:
union Test
{ char m[6];
int a;
float b;
};
Test test = {1};

由於最大長度的類型是int或者float,都爲4,而m[6]佔用6字節內存。因此採用補齊原則,聯合體應爲4*2=8字節內存,即sizeof(test)=8;

 

二、共用體union用於判斷機器是小端存儲仍是大端存儲

答:因爲union只存儲一個成員,若一個union有一個int變量和一個插入變量,那麼若前一個int變量被賦值後,此時union存儲的就是該int變量。若此時讀取後一個char變量,因爲char並無被重寫,因此讀取的仍是int變量的低8位。根據讀取的int低八位數字就能夠判斷大端或小端存儲了。

代碼以下:

  1.  
    bool IsLittleEndian(){
  2.  
    union{
  3.  
    int a;
  4.  
    char b;
  5.  
    }u;
  6.  
     
  7.  
    int k=15;//要在char範圍內
  8.  
    u.a=k;
  9.  
    if((int)u.b==k)
  10.  
    return true;
  11.  
    return false;
  12.  
    }

 

 

 

三、C Plus的struct及其與class的區別

答:(1)C++的結構體與C的結構體區別:

C++中的struct對C中的struct進行了擴充,它已經再也不只是一個包含不一樣數據類型的數據結構了,它已經獲取了太多的功能:
①C的struct不能夠包含成員函數,C++的struct能包含成員函數;
②C的struct不能夠繼承,C++的struct能繼承;
③C的struct不能夠實現多態,C++的struct能實現多態;

(2)C++的struct和class的區別:
最本質的一個區別:成員默認屬性和默認繼承權限的不一樣;
①若不指明,struct成員的默認屬性是public的,class成員的默認屬性是private的;
②若不指明,struct成員的默認繼承權限是public的,class成員的默認繼承權限是private的;c和c++中struct的主要區別是c中的struct不能夠含有成員函數,而c++中的struct能夠。

補充:c++中struct和class的主要區別在於默認的存取權限不一樣,struct默認爲public,而class默認爲private。

 

四、C++中枚舉變量的定義

答:舉例:
enum en1{ 
    x1, 
    x2, 
    x3=10, 
    x4, 
    x5, 
} emx1;
其中,emx1就是一個枚舉變量,也可en1 emx1來定義枚舉變量。

枚舉變量不初始化,若該變量是全局變量,則系統自動初始化爲0;若爲局部變量,則爲隨機值。注:上述枚舉缺省賦值,則系統自動根據已有元素值依次賦值(首位缺省自動賦值爲0),因此x1~x5依次爲:0,1,10,11,12。

 

 

第五章、靜態變量、常量、全局變量與局部變量等

一、C++中類的靜態成員static和常量(準確應該是「只讀」)成員const

答:(1)靜態成員static

特性:在C++中,靜態成員是屬於整個類的而不是某個對象,靜態成員變量只存儲一份供全部對象共享。

注意事項:
①、靜態成員在C++中既能夠被類名引用,也能夠被對象引用,這與C#不太同樣。但注意不能經過類名來調用類的非靜態成員函數;
②、靜態成員函數不能夠引用非靜態成員變量,由於靜態成員函數屬於整個類,在類實例化對象以前就已經分配空間了,而類的非靜態成員必須在類實例化對象後纔有內存空間。但反之,類的非靜態成員函數能夠調用用靜態成員函數;
③、類的靜態成員變量必須先初始化再使用,而且靜態成員必須在類體外進行初始化(由於它是多個對象共享的),不能夠在類中聲明時就初始化,格式如:int Info::mm=9;

④靜態成員變量和函數在類聲明外定義實現時都不能再加static關鍵字。
舉例:

  1.  
    class Info{
  2.  
    public:
  3.  
    static int mm;
  4.  
    static const char nn=97;
  5.  
    Info()
  6.  
    {
  7.  
    }
  8.  
    };
  9.  
    int Info::mm=9;
  10.  
    int _tmain(int argc, _TCHAR* argv[])
  11.  
    {
  12.  
    Info info; //調試可知,在類對象聲明時就會調用構造函數
  13.  
    //靜態數據成員初始化及調用
  14.  
    cout<<Info::mm<<"\n";
  15.  
    cout<<info.nn<<"\n";
  16.  
    getchar(); //等待鍵盤Enter鍵關閉
  17.  
    return 0;
  18.  
    }

結果:

可見:static const能夠在聲明時賦值,其餘的都在外面用::引用賦值。且靜態成員支持類::引用和對象引用兩種模式。
補充:類的靜態成員變量是被全部類對象共享的;
類成員函數中定義的靜態變量是被全部類對象中對應的那個成員函數所共享的(對其餘成員函數無效)。

 

(2)常量const

在類中,咱們可能不但願某個值在程序中的任何地方被修改,就像Math類中的PI那樣,那麼,咱們能夠使用成員常量來實現。聲明方式如:const 類型 常量名;

  1.  
    Info():ee( 111)
  2.  
    {
  3.  
    //mm=8;
  4.  
    cout<<"我是構造函數";
  5.  
    }

即將const int 變量ee初始化爲111。如有多個要初始化,在ee()後面用逗號分隔。注意:C++中單純的const常量不能夠直接在聲明時初始化(這是與C、C#不一樣的),須要在構造函數初始化表中初始化,以下:

提醒:靜態變量不能夠使用初始化列表。

 

(3)static const類型的類成員

也就是(1)中所提到的,c++中static const類型成員應該是static和const(只讀)合在一塊兒的解釋,那麼應該有以下原則:
①、有const修飾表示初始化值不可修改;
②、有static修飾表示是對象共有的,只是因爲它是在類成員函數中定義的,那麼其做用範圍只在對應的對象成員函數中,在對象其餘成員函數中是無效的。
其初始化有以下規定:
①、static const和const static是一樣的;

②、static const因爲有static修飾,因此能夠在類體外進行初始化,方式同單純的static變量;但注意,雖然有const修飾,但不能夠像const那樣用初始化列表初始化;

③、但其又和單純的static有所不一樣,static const int型在聲明時初始化在C++中也是合法的,如static const int nn=97;

 

二、全局變量的聲明定義與引用

答:全局變量聲明與定義:

(1)在庫文件中用extern聲明全局變量(不包括定義)

舉個例子,在test1.h中有下列聲明:

  1.  
    #ifndef TEST1H
  2.  
    #define TEST1H
  3.  
    extern char g_str[]; // 聲明全局變量g_str
  4.  
    void fun1();
  5.  
    #endif
  6.  
    在test1.cpp中
  7.  
    #include "test1.h"
  8.  
    char g_str[] = "123456"; // 定義全局變量g_str
  9.  
    void fun1()
  10.  
    {
  11.  
    cout << g_str << endl;
  12.  
    }

以上,如今庫文件用extern聲明全局變量,那麼全局變量的引用方式有兩種:

 

①文件中引用庫文件並定義(若全局變量已經在其餘引用程序中定義,此處不需再定義);

②文件中使用extern引用並定義(同①,若已定義不需再定義);

(2)在庫文件中用extern聲明並定義全局變量(包括定義)

把全局變量的聲明和定義放在一塊兒,這樣能夠防止忘記了定義,如把上面test1.h改成:extern char g_str[] = "123456"; 

那麼全局變量的引用只有一種方式:extern引用(注:若還用引用庫文件會形成全局變量定義語句執行兩次,引起錯誤);

 

 

三、類成員函數中的靜態變量做用域

答:靜態變量定義在類成員函數中的做用週期是和該成員函數同樣的,成員函數被銷燬該靜態變量存儲內存就會釋放。因此有以下結果:

  1.  
    void Info::showtest()
  2.  
    {
  3.  
    static int num=0;
  4.  
    num++;
  5.  
    cout<<num<<" ";
  6.  
    }
  7.  
    Info info;
  8.  
    info.showtest();
  9.  
    info.showtest();
  10.  
    info.showtest();

運行:

可知:並非每次調用showtest()都會初始化num=0,因爲它是靜態的,因此只有第一次初始化有效。編譯到成員函數中變量定義處時進行初始化,之後就不在進行初始化。其有全局變量的做用週期,但只有局部變量的做用域。

 

四、const在指針符*前和後的區別

答:int const *p是常量指針(const of point),int * const p是指針常量(point of const),名稱與指針符*與const的位置關係是對應的;const在*號前表示指針所指向的數據是常量,不可更改;const在*號後表示指針自己是常量,不可更改。下面有一個例子:
char a[] = "hello";
const char* p1 = a;
注意:int const與const int同樣。

 

五、常量摺疊

答:​c++裏的常量摺疊(或者常量替換)是將const常量放在符號表中,而並不給其分配內存,編譯器直接進行替換優化(就像宏同樣)。
可是常量若是被分配內存空間,那麼內存中存儲的常量值在程序運行階段是能夠改變的,例如:
  1.  
    #include <iostream>
  2.  
    using namespace std;
  3.  
    int main(void)
  4.  
    {
  5.  
    const int a = 10;
  6.  
    int * p = (int *)(&a);
  7.  
    *p = 20;
  8.  
    cout<<"a = "<<a<<", *p = "<<*p<<endl;
  9.  
    return 0;
  10.  
    }
輸出結果是:a = 10, *p = 20
 
解析:
a、*p指向同一塊內存,可是*p = 20對內存進行修改後,按道理來講,*p==20,a也應該等於20,而實驗結果倒是a等於10,這是爲何呢?就是本節所說的內容,a是可摺疊常量,在編譯階段a的引用已經被10替換爲結束了,而對a開闢內存空間是運行階段的事,因此修改後的數據是在運行階段寫入的,並不影響a的輸出。
補充:
常量的定義能夠沒有類型說明,例如直接用#define宏定義(必定要注意宏定義的量也是常量);可是常量仍是不可改變的,前面說的可改變只是在常量被分配內存空間後才能夠。
 
 

 

第六章、邏輯表達式與關係語句等

一、C++中的switch語句

答:(1)不能做爲switch語句參數的有字符串和浮點型(float、double)
① char、short、int、long、bool 基本類型均可以用於switch語句。
② float、double都不能用於switch語句(由於一旦在比較中出現精度不一致問題就無法繼續了)。
③ enum類型,即枚舉類型能夠用於switch語句。
④ 全部類型的對象都不能用於switch語句。
⑤ 字符串也不能用於switch語句(switch是用"="進行比較,而char[]沒有"="的概念,只有strcmp)。
(2)switch在編程語言中被劃爲「循環語句」
可是注意,switch循環中不能夠使用continue,只能夠使用break;
continue只能夠在for/while/do while中使用;

 

二、C++中的單獨{ }做用

答:在C++中單獨{ }括起來的叫作程序塊,其對程序執行順序並沒有改變,只是限定其中定義的變量,即其中定義的變量出了該花括號就無效了。

時刻謹記:局部變量只要出了{ }就失效。

 

 

三、標籤label

答:如:

  1.  
    int main(void) {
  2.  
    http: //www.taobao.com
  3.  
    cout << "welcome to taobao" << endl;
  4.  
    }

該代碼段是能夠編譯並執行經過的,由於編譯器會把http:當作label,即goto語句的目標。相似:

step1:a=fun(); 

goto step1; 

總結:標籤經常使用在goto語句中;

 

 

四、賦值表達式做爲判斷語句的問題(特殊問題)

答:賦值表達式做爲判斷語句使用時,若賦值爲非零時表達式爲真,賦值爲0時爲假。例如:

  1.  
    for(i=0,j=-1;j=0;i++,j++)
  2.  
    {
  3.  
    k++;
  4.  
    }

結果是:循環一次都不會執行。

 

 

第七章、函數相關

一、函數重載

答:函數重載目的:使用方便,規範編程代碼,提升可靠性。(注:並不能夠節省空間)

其3個判斷依據分別是:參數類型、參數個數、const;

 

二、函數默認的傳參順序爲從右到左

答:函數的默認參數傳遞順序是從右到左。

例如:

①int i = 3; printf("%d %d", ++i, ++i),運行輸出爲:5  4;

②對於printf函數,變量參數多於輸出格式符:

  1.  
    int a=12,b=2,c=89;
  2.  
    printf("%d%d",a,b,c);

結果爲:2 89。

 

三、函數中不可被修改的參數肯定原則

答:a) 始終用const限制全部指向只輸入參數的指針和引用;

b) 優先經過值傳遞來取得原始類型int、float、char等和開銷比較低的值的對象;

c) 優先按const的引用取得其餘用戶定義類型的輸入;

d) 若是函數須要其參數的副本,則能夠考慮經過值傳遞代替經過引用傳遞。這在概念上等同於經過const引用傳遞加上一次複製,能

夠幫助編譯器更好的優化掉臨時變量。

 

四、C++函數參數取默認值相關問題

答:(1)做用:C++中有時屢次調用同一函數時用一樣的實參,C++提供簡單的處理辦法:給形參一個默認值,這樣形參就沒必要必定要從實參取值了。若有一函數聲明:float area(float r=6.5);指定r的默認值爲6.5,若是在調用此函數時,確認r的值爲6.5,則能夠沒必要給出實參的值。
如:area( ); //至關於area(6.5);

(2)規定:指定某個參數的默認值,那麼必須也指定其右端的全部參數默認值,不然調用函數省略有默認值的實參時,編譯器匹配參數會出錯。

如:如有float volume(float h,float r=12.5); //只對形參r指定默認值12.5函數調用能夠採用如下形式:

  1.  
    volume( 45.6); //至關於volume(45.6,12.5)
  2.  
    volume( 34.2,10.4) //h的值爲34.2,r的值爲10.4

可是以下可能就有問題:

  1.  
    void f1(float a,int b=0,int c,char d); //不正確
  2.  
    void f2(float a,int c,int b=0, char d='a'); //正確

對於f1,若調用時是f1(1.2, 2, 3),那麼編譯器不知道參數2究竟是賦給b仍是c的,因此報錯。


五、main主函數的返回值的做用

答:main主函數不能被調用,爲何還有返回值?
由於C語言實現都是經過函數mian的返回值來告訴操做系統函數的執行是否成功,0表示程序執行成功,返回非0表示程序執行失敗,具體值表示某種
具體的出錯信息.還有雖然別的函數不能調用main函數,但系統能夠調用main的。

 

六、帶參數的主函數相關問題

答:通常狀況下,咱們在寫程序的時候,每每忽略了主函數的參數,例如:

int main(){ }

在命令行下,輸入程序的名稱就能夠運行程序了。實際上,咱們還能夠經過輸入程序名和相關的參數來爲程序的運行提供更多的消息。參數緊跟在程序名後面,參數之間用空格分開。這些參數被稱爲:command-line arguments(命令行參數),也每每被稱爲程序的argument list(參數表)。main函數經過兩個參數獲取輸入參數表信息,分別是argv和argc。第一個參數是一個整型的變量,它記錄了用戶輸入的參數的個數(參數個數包括程序名),第二個參數argc是一個char型的指針數組,它的成員記錄了指向各參數的指針,而且argc[0]是程序名,argc[1]是第一個參數。例如:

  1.  
    #include <stdio.h>
  2.  
    int main(int argv, char *argc[])
  3.  
    {
  4.  
    printf("/nthe name of the program is %s /n", argc[0]);
  5.  
    printf(" the program has %d argument! /n", argv - 1);
  6.  
    if(argv > 1)
  7.  
    {
  8.  
    int i;
  9.  
    printf("the arguments are:");
  10.  
    for(i=1; i<argv; i++)
  11.  
    {
  12.  
    printf("%s/t",argc[i]);
  13.  
    }
  14.  
    }
  15.  
    return 0;
  16.  
    }

 

若該程序名爲mytest,那麼輸入:

mytest aa bb cc dd e

則輸出結果是:

the name of the program D:/WINYES/TCPP30E/OUTPUT/MYTEST.EXT
the program has 5 argument!
The arguments are: aa   bb   cc  dd  e

由上可知:argv=6,argc[i]是第i個參數的首地址。

 

 

七、C++中的引用變量如何定義與使用?

答:(1)C++引用變量定義舉例:
int a;
int& b=a;

 

上述就是引用變量定義方式,引用變量沒什麼特別的意義,僅僅是變量的一個別名,如上b就是a的一個別名。

①注意不要把int&看作地址符;

②還有就是除了把引用變量當作函數返回值或參數外,其餘狀況聲明引用變量就須要直接在當前初始化,以告訴編譯器該引用屬於哪一個變量的別名。

(2)通常做爲函數參數使用,但引用變量使用中要注意的地方:

1)不要將局部引用變量做爲返回值,不然編譯警告且執行出錯。由於引用變量返回是返回自己,不是像普通局部變量同樣拷貝返回,因此一旦函數結束,局部應用變量就會自動銷燬,從而形成返回出錯。

2)引用做函數的形參,函數將使用原始數據而不是其拷貝。

3)常引用的做用在於提升程序效率(沒有拷貝、進棧出棧等操做),同時還使得函數不能夠更改引用變量值。

 

 

八、函數體內對形參進行修改會對實參產生改變嗎?

答:分兩種狀況:

1)若傳的是地址或引用,那麼形參,實參指向同一內容,修改形參會影響實參;

2)若傳的是值,由於實參值是拷貝到形參的,因此修改形參不會影響實參。

 

九、函數返回值問題——局部指針和局部數組地址是否都不能夠做爲返回值

答:常說的局部變量的地址不能夠做爲返回值,由於函數結束就釋放了,但有例外,即局部指針指向字符串時能夠做爲返回值,見以下解析:

(1)字符串賦值給指針時字符串是常量,賦值給數組時字符串是變量
對上述所說的字符串因初始化對象不一樣而成爲變量或常量,補充一個例子:

  1.  
    char*p1 = 」123」, *p2 = 」ABC」, str[50] = 「xyz」;
  2.  
    strcpy(str + 2, strcat(p1, p2));
  3.  
    printr(「%s\n」, str);

該程序回編譯出錯。
由於*p1="123"是常量,長度不可變化,strcat()函數顯然不可用。

 

(2)根據上述可知:局部指針變量指向字符串時能夠做爲函數返回值,而局部數組變量地址則必定不能夠做爲返回值;

以局部數組和局部指針變量爲例:當局部指針變量的初始化值是字符串時如char* p="hello",能夠做爲返回值,由於指針所指的字符串是常量,存儲在常量區,不隨函數結束而銷燬;

但當返回值是局部數組時,即便初始化值和上述同樣是字符串,例如char s[]="12345",但該字符串也只是變量,數組內容也就也就是局部變量。簡單說:因爲初始化數組的字符串是變量,用於初始化指針的字符串是常量。局部數組變量在函數調用結束以後,也就被操做系統銷燬了,即回收了他的內存空間,它裏面的東西隨時都有可能被覆蓋。雖然此時咱們得到了指向這一塊內存的指針,但指向的內容已改變。
例以下面的例子就是返回值錯誤:

  1.  
    char* test2()
  2.  
    {
  3.  
    char p[] = "hello world";
  4.  
    return p;
  5.  
    }

若其中p改成char *p="hello world",那麼返回值不會出錯。由於這裏的"hello world"是做爲字符串常量存在的,存儲在常量區域,函數結束也不會銷燬。而前者數組中"hello world"是做爲變量存在的,其每一個字符都是局部變量,存儲棧中,函數結束就會銷燬,因此最後返回的p所指向的內容並非"hello world"。

 

結論:在子函數中,用於初始化指針變量的字符串是常量,該字符串自己不可被修改,存儲在常量區,生命週期至程序結束;用於初始化數組的字符串是局部變量,該字符串自己能夠被修改,存儲在棧中,生命週期隨子函數結束。

(2)進一步得出:除局部靜態常量外的其餘局部變量(包括局部常量)的地址都不能夠做爲指針類型返回值;

注意:上述僅僅是返回值是指針類型時,若返回不是指針類型,返回數據會直接拷貝,就不會存在本小節所面臨的問題;

 

關於局部變量地址作返回值有個例外,就是靜態局部變量,靜態局部變量地址可作指針返回值。例如:

  1.  
    #include <stdio.h>
  2.  
    #include<iostream>
  3.  
    using namespace std;
  4.  
     
  5.  
    char* getStr1()
  6.  
    {
  7.  
    char ch='a';
  8.  
    printf("%p\n", &ch); //輸出地址
  9.  
    return &ch; //報錯,局部變量的地址/指針不可做爲返回值
  10.  
    }
  11.  
    const char* getStr2()
  12.  
    {
  13.  
    const char ch='a';
  14.  
    printf("%p\n", &ch);
  15.  
    return &ch; //可返回地址,但主函數無輸出或輸出值不肯定
  16.  
    }
  17.  
    char* getStr3()
  18.  
    {
  19.  
    static char ch='a';
  20.  
    printf("%p\n", &ch);
  21.  
    return &ch; //正確
  22.  
    }
  23.  
     
  24.  
    int main(void)
  25.  
    {
  26.  
    char* p=getStr3(); //僅僅靜態局部變量的地址或指針可做爲返回值
  27.  
    printf(" ");
  28.  
    printf("%c\n", *p);
  29.  
    return 0;
  30.  
    }
 

十、回調函數

答:回調函數就是一個經過函數指針調用的函數,當該函數的指針(地址)做爲參數傳遞給被調函數時才能稱做回調函數。回調函數也能夠象普通函數同樣被程序調用;

  1.  
    void perfect(int n)
  2.  
    {
  3.  
    //******//
  4.  
    }
  5.  
    void myCallback(void (*perfect)(int ),int n)
  6.  
    {
  7.  
    perfect(n);
  8.  
    }
  9.  
    int main()
  10.  
    {
  11.  
    int n=9;
  12.  
    myCallback(perfect,n);
  13.  
    return 0;
  14.  
    }

在main中調用myCallback時,perfect做爲函數指針參數傳入被調用的函數,此時perfect就是回調函數。
由上可知pa->out()和pa->out(3)調用都是函數A::out(int i),
由上可知pb->out()和pb->out(4)調用都是函數B::out(int i),
由上可知p1b->out()和p1b->out(5)調用都是函數B::out(int i),

p1b->out()時,p1b的靜態類型是B*,它的缺省參數是2;調用的也是B::out(int i);

做用:回調函數的做用在於保證調用函數內部實現的靈活性。

 

十一、內聯函數

答:內聯函數:即函數定義(注意是定義不是聲明)位於類聲明中的函數,且內聯函數比較小如:

  1.  
    class stock
  2.  
    {
  3.  
    private:
  4.  
    char name[];
  5.  
    int score;
  6.  
    void set_tot(){cout<<」my name is XC」;} //set_tot()就是內聯函數
  7.  
    public:
  8.  
    void acquire();
  9.  
    void buy();
  10.  
    }

 

固然,除了這種內聯函數外,還能夠像傳統的同樣在類外使用關鍵字inline定義內聯函數。

做用:避免普通的函數調用所產生的額外時間消耗,提升函數調用效率。就像宏同樣,內聯函數的代碼是放到了符號表中的,使用時直接複製過來就行了,不用去作調用中耗時的內部處理過程(即:PC進棧出棧過程)。

補充:inline函數並不必定就被編譯器視爲內聯函數,編譯器會就狀況自動肯定;

內聯函數在編譯階段直接替換函數體,相似宏在預編譯階段替換同樣。但內聯函數和宏有最大不一樣點:

內聯函數在編譯階段會作類型參數檢查,這也是其相對於宏最大的優點。

十二、函數調用約定方式

答:函數調用約定(calling convention)不只決定了發生函數調用時函數參數的入棧順序,還決定了是由調用者函數仍是被調用函數負責清除棧中的參數,還原堆棧。函數調用約定有不少方式,除了常見的__cdecl,__fastcall和__stdcall以外,C++的編譯器還支持thiscall方式,很多C/C++編譯器還支持naked call方式。
(1)__cdecl調用方式是由函數調用者負責清除棧中的函數參數,因此這種方式支持可變參數,好比printf和windows的API wsprintf就是__cdecl調用方

式。

 

1三、C語言中的strcpy和strcat函數

答:(1)strcpy(dest,src):C語言標準庫函數strcpy,把從src地址開始且含有'\0'結束符的字符串複製到以dest爲開始的地址空間,返回指向dest的指針。注意,dest必定要先分配足夠大小地址空間,不然複製操做會形成程序崩潰。以下就有問題:

  1.  
    int main()
  2.  
    {
  3.  
    char a;
  4.  
    char *str=&a;
  5.  
    strcpy(str,"hello"); //str沒有分配合適大小的地址空間,str只是一個字符的地址空間指針
  6.  
    printf(str);
  7.  
    return 0;
  8.  
    }

(2)strcat(dest,src):把src所指字符串添加到dest結尾處(覆蓋dest結尾處的'\0')並添加'\0',返回指向dest的指針。

 

 

1四、C++中c_str()函數

答:c_str()返回值是const char*,返回一個指向正規C字符串的指針;

  1.  
    string a="hello world";
  2.  
    string b=a;
  3.  
    if(a.c_str()==b.c_str())
  4.  
    {
  5.  
    cout<<"true"<<endl;
  6.  
    }
  7.  
    else
  8.  
    cout<<"false"<<endl;

上述代碼中,b=a是拷貝賦值操做,即b開闢新內存,將a所指向的字符串常量拷貝到其中,那麼b.c_str()的的返回指針天然與a.c_str()不一樣,因此打印false。若b="hello world",那麼打印true,由於"hello world"是字符串常量,保存在全局區,這樣賦值是直接將字符串常量地址賦給b。

 

1五、STL容器名如string做函數參數是屬於值傳遞

答:STL容器做爲函數參數是值傳遞而不是地址傳遞,即便實參是容器名,但其與數組名做實參是徹底不一樣的。

要實現函數中對實參的修改就是對原容器修改,通常使用引用傳遞。兩種以下:

STL容器值傳遞:會將建立一個新的容器並將原容器數據拷貝過來

STL容器引用傳遞:傳遞原容器的別名,函數中操做的就是原容器自己

 

 

第八章、C++內存模型

一、C++中各存儲區域劃分、內存分配時間、內存釋放模式

答:(1)棧區(stack)—— 由編譯器自動分配釋放 ,存放爲運行函數而分配的局部變量、函數參數、返回數據、返回地址等。
(2)堆區(heap)——通常由程序員分配釋放, new, malloc之類的,若程序員不釋放,程序結束時可能由OS回收 (注意:堆不能夠靜態分配,靜態分配都是在編譯階段分配的)。
(3)全局區(或者叫靜態區)(static)— 存放全局變量、靜態數據、常量。程序結束後由系統釋放。
(4)文字常量區 — 常量字符串就是放在這裏的,如string str="Hello!"中的"Hello!"就存放在文字常量區。程序結束後由系統釋放。
(5)程序代碼區 — 存放函數體(類成員函數和全局函數)的二進制代碼。

以下:

  1.  
    int a=0;
  2.  
    class someClass{
  3.  
    int b;
  4.  
    static int c;
  5.  
    };
  6.  
    int main(){
  7.  
    int d=0;
  8.  
    someClass *p= new someClass();
  9.  
    return 0;
  10.  
    }

 

a、c在全局區;b、p在堆區(因爲成員變量會成爲對象的成員,因此b在堆區);d在棧區。

內存分配時間:

①編譯時不分配內存

編譯時是不分配內存的。此時只是根據聲明時的類型進行佔位,到之後程序執行時分配內存纔會正確。因此聲明是給編譯器看的,聰明的編譯器能根據聲明幫你識別錯誤;

②運行時必分配內存

運行時程序是必須調到「內存」的。由於CPU(其中有多個寄存器)只與內存打交道的。程序在進入實際內存以前要首先分配物理內存。注意,涉及到內存分配的都是在運行階段分配纔有意義。

內存釋放的兩種模式:兩個關鍵字delete和free釋放內存,new和delete搭配、malloc和free搭配;

 

二、C++內存模型

答:C++內存模型組成有三部分:自由區、靜態區、動態區;
根據c/c++對象生命週期不一樣,c/c++的內存模型有三種不一樣的內存區域,即:自由存儲區,動態區、靜態區。
自由存儲區:局部非靜態變量的存儲區域,即日常所說的棧;
動態區: 用new ,malloc分配的內存,即日常所說的堆;
靜態區:全局變量,靜態變量,字符串常量存在的位置;
注:代碼雖然佔內存,但不屬於c/c++內存模型的一部分;

更多參考:http://blog.csdn.net/xiongchao99/article/details/74524807#t3

 

三、realloc,malloc,calloc的區別

答:三個函數的申明分別是: 
void* realloc(void* ptr, unsigned newsize); 
void* malloc(unsigned size); 
void* calloc(size_t numElements, size_t sizeOfElement); 
都在stdlib.h函數庫內,它們的返回值都是請求系統分配的地址,若是請求失敗就返回NULL ;

malloc用於申請一段新的地址,參數size爲須要內存空間的長度,如: 
char* p; 
p=(char*)malloc(20);

calloc與malloc類似,參數sizeOfElement爲申請地址的單位元素長度,numElements爲元素個數,如: 
char* p; 
p=(char*)calloc(20,sizeof(char)); 
這個例子與上一個效果相同

realloc是給一個已經分配了地址的指針從新分配空間,參數ptr爲原有的空間地址,newsize是從新申請的地址長度 ,
如: 
char* p; 
p=(char*)malloc(sizeof(char)*20); 
p=(char*)realloc(p,sizeof(char)*40);

注意,這裏的空間長度都是以字節爲單位。 

C語言的標準內存分配函數:malloc,calloc,realloc,free等;

C++中爲new/delete函數。

 

 

四、C++存儲方案

答:C++有三種,C++11有四種,這些方案的區別就在於數據保留在內存中的時間。

 

自動存儲持續性:在函數定義中聲明的變量(包括函數參數)的存儲持續性爲自動的。它們在程序開始執行其所屬的函數或代碼塊時被建立,在執行完函數或代碼塊時,它們使用的內存被釋放。C++有兩種存儲持續性爲自動的變量;

靜態存儲持續性:在函數定義外定義的變量和使用關鍵字static定義的變量的存儲持續性都爲靜態。它們在程序整個運行過程當中都存在。C++有3種存儲持續性爲靜態的變量;

線程存儲持續性(C++11):當前,多核處理器很常見,這些CPU可同時處理多個執行任務。這讓程序可以將計算放在可並行處理的不一樣線程中。若是變量是使用關鍵字thread_local聲明的,則其生命週期與所屬的線程同樣長。本書不探討並行編程;

動態存儲持續性:用new運算符分配的內存將一直存在,直到使用delete運算符將其釋放或程序結束爲止。這種內存的存儲持續性爲動態,有時被稱爲自由存儲(free store)或堆(heap)。

 

第九章、類與對象

一、C++中聲明對象時使用new和不使用new的區別?

答:簡而言之:C++中類對象聲明中使用new通常是定義類指針(注意C++的new只可用於類指針,不可用於類對象),這種方式建立的類指針須要在使用結束時進行delete釋放內存,不然會形成內存泄露;而不使用new的對象聲明是由系統自動建立並釋放內存的,不須要咱們手動釋放。具體區別可見以下代碼:

  1.  
    class Info{
  2.  
    private:
  3.  
    string name;
  4.  
    int age;
  5.  
    public:
  6.  
    Info()
  7.  
    {
  8.  
    cout<<"我是構造函數";
  9.  
    }
  10.  
    void show(string);
  11.  
    };
  12.  
    void Info::show(string name)
  13.  
    {
  14.  
    cout<<name<<"\n\n";
  15.  
    }
  16.  
    int _tmain(int argc, _TCHAR* argv[])
  17.  
    {
  18.  
    cout<<"聲明info對象"<<"\n";
  19.  
    Info info; //調試可知,在類對象聲明時就會調用構造函數
  20.  
    info.show( "info");
  21.  
    //注意:如下這種作法在C++中是錯誤的,會提示: 沒法從「Info *」轉換爲「Info」,
  22.  
    //這種方式只適合C#和Java中
  23.  
    //Info infor=new Info();
  24.  
    cout<<"聲明infom類通用指針"<<"\n";
  25.  
    Info* infom; //此處僅僅至關於一個通用的類指針,不會調用構造函數
  26.  
    cout<<"infom指針初始化"<<"\n";
  27.  
    infom= new Info();
  28.  
    infom->show( "infom");
  29.  
    cout<<"infoma指針聲明並建立new"<<"\n";
  30.  
    Info* infoma= new Info(); //類指針建立new時會調用構造函數
  31.  
    infoma->show( "infoma");
  32.  
    getchar(); //等待鍵盤Enter鍵關閉
  33.  
    delete infom;
  34.  
    delete infoma;
  35.  
    return 0;
  36.  
    }

運行結果以下圖:

 

另外由上述結果可知:聲明對象Info info和new Info()會調用構造函數,但Info* infom不會調用構造函數。但對於A* p=new B之類的對象指針建立,會調用B的構造函數(若B是A的子類,根據繼承關係的對象構造,會先調用A的構造函數,再調用B的構造函數)。

 

二、malloc與new在建立對象內存時的主要區別

答:new 不止是分配內存,並且會調用類的構造函數,同理delete會調用類的析構函數,而malloc則只分配內存,不會調用類的構造函數進行初始化類成員的工做,一樣free也不會調用析構函數。
malloc函數的用法:void *malloc(int size);

注意:malloc只能夠用來分配內存(分配的是虛擬內存,不是物理內存),還有void*並不表示返回空,這裏表示須要程序員自行指定返回指針類型,是強制返回任何類型的指針,好比:int *p=(int *)malloc(size)。

更詳細區別參見另外一博文:http://blog.csdn.net/xiongchao99/article/details/74524807#t18

 

三、多態種類

答:多態分爲兩類:通用多態和特定多態;

(1)通用多態:參數多態、包含多態;

①參數多態:包括函數模板和類模板

②包含多態:virtual虛函數

(2)特定多態:重載多態、強制多態

①重載多態:重載多態是指函數名相同,但函數的參數個數或者類型不一樣的函數構成多態

②強制多態:強制類型轉換

 

 

四、純虛函數

答:定義:virtual void funtion1()=0;

有純虛函數的類是抽象類,不能夠生成對象,抽象類只能夠派生,由他派生的類的純虛函數沒有重寫的話派生類仍是一個抽象類;

 

五、C++多態性(包含多態)以及虛函數應用

答:C++的多態性:多態性表示使用父類的指針指向子類的對象,這樣就能夠使用父類類型的對象指針調用子類的成員函數,其實

就是父類虛函數的應用。

虛函數和純虛函數的主要做用:實現多態性,即:虛函數的做用是容許在派生類中從新定義與基類同名的函數,而且能夠經過基類指針或引用來訪問基類和派生類中的同名函數。;

(1)虛函數動態綁定

  1.  
    class A{
  2.  
    public:
  3.  
    virtual void print(){ cout<<」This is A」<<endl;}//如今成了虛函數了
  4.  
    };
  5.  
    class B:public A{
  6.  
    public:
  7.  
    void print(){ cout<<」This is B」<<endl;}//這裏須要在前面加上關鍵字virtual嗎?(不須要,派生類直接就是虛函數)
  8.  
    };
  9.  
    int main(){
  10.  
    A a;
  11.  
    B b;
  12.  
    A* p1=&a;
  13.  
    A* p2=&b;
  14.  
    p1->print();
  15.  
    p2->print();
  16.  
    }

如上,因爲基類函數是虛函數(派生類相應函數就自動變虛函數,因此派生類同名函數能夠不指定爲虛函數),指向不一樣對象的基類指針就能夠調用各對象本身的函數。因此結果爲:This is A,This is B;
若是上述基類的print()不是虛函數,那麼結果就是This is A,This is A。

這就是虛函數在多態性繼承和動態綁定方面的做用。

上述說到動態綁定,即經過基類指針對象或引用(注意:引用也可)指向派生類,調用重寫的虛擬函數時直接調用被指對象(即派生類)所包含的相應虛擬函數;若調用的不是虛函數,那麼直接調用基類的函數;

這裏還介紹一下靜態綁定,例如:((A)b).print(),輸出This is A,屬於靜態綁定。((A)b).print()中不存在指針或引用問題,因此不是動態綁定;

其能夠理解爲:A temp=(A)b;temp.print();b的做用僅僅是用於初始化臨時對象temp;

(2)虛函數要遵循「毫不從新定義繼承而來的缺省參數」

說白了就是虛函數雖然是動態綁定的,但其參數是靜態綁定(就是靜態變量),只和對象指針的靜態類型有關,即只能夠初始化一次。

以下例:

  1.  
    #include <iostream>
  2.  
    using namespace std;
  3.  
    class A
  4.  
    {
  5.  
    public:
  6.  
    virtual void out(int i = 1)
  7.  
    {
  8.  
    cout << "class A " << i <<endl;
  9.  
    }
  10.  
    };
  11.  
    class B : public A
  12.  
    {
  13.  
    public:
  14.  
    virtual void out(int i = 2)
  15.  
    {
  16.  
    cout <<"class B " <<i <<endl;
  17.  
    }
  18.  
    };
  19.  
    int main()
  20.  
    {
  21.  
    A a;
  22.  
    B b;
  23.  
    A * p = &a;
  24.  
    p->out();
  25.  
    p->out( 3);
  26.  
    p = &b;
  27.  
    p->out();
  28.  
    p->out( 4);
  29.  
    B * p1 = &b;
  30.  
    p1->out();
  31.  
    p1->out( 5);
  32.  
    return 0;
  33.  
    }

缺省參數是靜態綁定的,pb->out()時,pb的靜態類型是A*,它的缺省參數是1;可是調用的是B::out(int i);
輸出:

(3)一個類中將全部的成員函數都儘量地設置爲虛函數老是有益的,但如下不能夠設置爲虛函數: 
①只有類的成員函數才能說明爲虛函數; 
②靜態成員函數不能是虛函數(虛函數是動態綁定的,靜態函數必然不可); 
③內聯函數不能爲虛函數(虛函數在調用中須要從虛函數表中取地址的,而內聯函數是沒有指定地址的); 
④構造函數不能是虛函數(虛函數表是在構造函數運行時初始化(給虛函數分配地址)的,若構造函數是虛函數,那麼就會出現本身在運行時纔給本身分配地址,顯然不可); 

(4)析構函數一般聲明爲虛函數

由於多態(即基類對象指向派生類)狀況下,若析構函數是虛函數,則對象在釋放時會首先調用派生類繼承的析構函數,而後再調用基類的析構函數,實現二者的同時釋放。若析構函數不是虛函數,多態下對象是釋放時就只會調用基類的析構函數,而形成派生類對象未釋放而內存泄漏。

 

六、構造函數、析構函數的調用順序

答:見以下代碼:

  1.  
    //基類
  2.  
    class CPerson
  3.  
    {
  4.  
    char *name; //姓名
  5.  
    int age; //年齡
  6.  
    char *add; //地址
  7.  
    public:
  8.  
    CPerson(){ cout<<"constructor - CPerson! "<<endl;}
  9.  
    ~CPerson(){ cout<<"deconstructor - CPerson! "<<endl;}
  10.  
    };
  11.  
    //派生類(學生類)
  12.  
    class CStudent : public CPerson
  13.  
    {
  14.  
    char *depart; //學生所在的系
  15.  
    int grade; //年級
  16.  
    public:
  17.  
    CStudent(){ cout<<"constructor - CStudent! "<<endl;}
  18.  
    ~CStudent(){ cout<<"deconstructor - CStudent! "<<endl;}
  19.  
    };
  20.  
    //派生類(教師類)
  21.  
    //class CTeacher : public CPerson//繼承CPerson類,兩層結構
  22.  
    class CTeacher : public CStudent//繼承CStudent類,三層結構
  23.  
    {
  24.  
    char *major; //教師專業
  25.  
    float salary; //教師的工資
  26.  
    public:
  27.  
    CTeacher(){ cout<<"constructor - CTeacher! "<<endl;}
  28.  
    ~CTeacher(){ cout<<"deconstructor - CTeacher! "<<endl;}
  29.  
    };
  30.  
    void main()
  31.  
    {
  32.  
    CTeacher teacher;
  33.  
    }

結果:

上述例子說明:

(1)當創建一個對象時,若派生類中沒有對象成員,首先調用基類的構造函數,而後調用下一個派生類的構造函數,依次類推,直至到達派生類次數最多的派生次數最多的類的構造函數爲止。由於,構造函數一開始構造時,老是要調用它的基類的構造函數,而後纔開始執行其構造函數體,調用直接基類構造函數時,若是無專門說明,就調用直接基類的默認構造函數。在對象析構時,其順序正好相反。

(2)若派生類中有對象成員,首先調用基類的構造函數,而後調用下一個派生類中對象成員的構造函數,再調用該派生類的構造函數,以此類推,析構順序正好相反。以下:

  1.  
    B:A
  2.  
    {
  3.  
    public:
  4.  
    C c;
  5.  
    }

那麼構造函數調用順序:A()->C()->B();

 

(3)析構函數也遵循類多態性規則:若基類析構函數是虛函數(通常都是),釋放指向派生類對象的基類指針或引用時會先調用派生類析構函數釋放派生類,而後再調用基類析構函數釋放基類。若不是虛析構函數,就直接調用基類析構函數,而再也不調用派生類析構函數。

注:根據多態性的動態綁定和靜態綁定,用對象指針來調用一個函數有如下兩種狀況:
①若是是虛函數,會調用派生類中的版本。
②若是是非虛函數,會調用指針所指類型的實現版本。

爲何析構函數要設置成虛函數:基類析構函數是虛函數virtual,在C++中咱們能夠使用基類cBase的指針pBase(或引用)指向一個子類cChild,當pBase指針被撤銷的時候,會先調用子類的析構函數,再調用基類的構造函數。若是不是virtual,那麼撤銷pBase指針時,將不會調用子類的析構函數,形成了內存泄露。

補充:

①析構函數通常都是虛函數,但構造函數不能夠是虛函數;

②析構函數因爲沒有參數、沒有返回值,因此是不能夠被重載的。

 

 

七、拷貝構造函數、賦值構造函數與析構函數

答:(1)做用:

拷貝構造函數:用原對象建立並初始化新對象;

賦值構造函數:用原對象對已有的其餘對象進行從新賦值;

析構函數:釋放對象等做用。

注意:拷貝構造函數中建立的對象是一個實實在在的新開闢內存區域的對象,而並非一個指向原對象的指針。

(2)聲明方式:

拷貝構造函數 MyClass(const MyClass & x);
賦值構造函數 MyClass&MyClass::operator= (const MyClass & x);
析構函數 ~MyClass();

(3)注意事項:

①拷貝構造函數也是構造函數,因此沒有返回值。拷貝構造函數的形參不限制爲const,可是必須是一個引用,以傳地址方式傳遞參數,不然致使拷貝構造函數無窮的遞歸下去,指針也不行,本質仍是傳值。

②賦值構造函數是經過重載賦值操做符實現的,它接受的參數和返回值都是指向類對象的引用變量。

(4)區別與共同點:

注意,拷貝構造函數和賦值構造函數的調用都是發生在有賦值運算符‘=’存在的時候,只是有一區別:

拷貝構造函數調用發生在對象尚未建立且須要建立時,如:

MyClass obj1;

MyClass obj2=obj1或MyClass obj2(obj1);

賦值構造函數僅發生在對象已經執行過構造函數,即已經建立的狀況下,如:

MyClass obj1;

MyClass obj2;

obj2=obj1;

區別:拷貝構造函數就像變量初始化,賦值構造函數就如同變量賦值。前者是在用原對象建立新對象,然後者是在用原對象對已有對象進行賦值。

共同點:拷貝構造函數和賦值構造函數都是淺拷貝,因此遇到類成員含有指針變量時,類自動生成的默認拷貝構造函數和默認賦值構造函數就不靈了。由於其只能夠將指針變量拷貝給新對象,而指針成員指向的仍是同一內存區域,容易產生:衝突、野指針、屢次釋放等問題。解決方法就是本身定義具備深拷貝能力的拷貝構造函數或者賦值構造函數。

 

(5)拷貝與賦值構造函數內在原理(m_data是String類成員):

  1.  
    // 拷貝構造函數
  2.  
    String::String( const String &other)
  3.  
    {
  4.  
       //容許操做other 的私有成員m_data
  5.  
       int length = strlen(other.m_data);
  6.  
      m_data = new char[length+1];(1)//開闢新對象內存
  7.  
       strcpy(m_data, other.m_data);(2)//複製內容到新對象
  8.  
    }
  9.  
    // 賦值函數
  10.  
    String & String:: operator =(const String &other)
  11.  
    {
  12.  
       //(1) 檢查自賦值
  13.  
       if(this == &other)
  14.  
       return *this;
  15.  
       //(2) 釋放原有的內存資源
  16.  
       delete [] m_data;
  17.  
       //(3)分配新的內存資源,並複製內容
  18.  
       int length = strlen(other.m_data);
  19.  
      m_data = new char[length+1];
  20.  
       strcpy(m_data, other.m_data);
  21.  
       //(4)返回本對象的引用
  22.  
       return *this;
  23.  
    }

上述是內部實現原理,可知:

 

①拷貝和賦值構造函數都是新開闢內存,而後複製內容進來;

②賦值構造函數必定要最早檢測本操做是否爲本身給本身賦值,如果就會直接返回自己。若直接從第(2)步開始就會釋放掉自身,從而形成第(3)步strcpy中的other找不到內存數據,從而使得賦值操做失敗。

(6)將類中的析構函數設爲私有,類外就不能夠自動調用銷燬對象,因此只能夠經過new建立對象,手動銷燬。

 

八、關於禁用拷貝構造函數和賦值構造函數

答:(1)包括兩步:

一、將兩個構造函數聲明爲私有private;

二、僅僅聲明函數就能夠了,不作定義;

解釋:前者保證外部不可調用,後者保證內部成員he友元不可調用,所以可實現禁用。

(2)爲何通常要禁用兩個構造函數:如上所述,拷貝構造函數和賦值構造函數是都是淺拷貝,若成員含有指針,易產生衝突、野指針、屢次釋放等問題。因此通常直接禁用,以防不測。

(3)可不能夠不由用?能夠,如今通常藉助智能指針就能夠不由用。

 

 

十、淺拷貝與深拷貝

答:淺拷貝:好比拷貝類對象時,對象含有指針成員,只是拷貝指針變量自身,這樣新舊對象的指針仍是指向同一內存區域;
深拷貝:同理,對象含有指針成員,拷貝時不只拷貝指針變量,還從新在內存中爲新對象開闢一塊內存區域,將原對象次指針成員所指向的內存數據都拷貝到新開闢的內存區域。

 

 

 

十一、常忽視的問題——構造函數通常都要定義爲公有的

答:構造函數若是被定義爲私有或者不代表私有公有(編譯器默認爲私有),就會形成建立對象時沒法調用構造函數而出錯。例如:

  1.  
    #include <stdio.h>
  2.  
    class A
  3.  
    {
  4.  
    A()
  5.  
    {
  6.  
    printf("A()");
  7.  
    }
  8.  
    };
  9.  
    void main()
  10.  
    {
  11.  
    A a;
  12.  
    }

 

就會出錯:error: ‘A::A()’ is private。緣由就是類外沒法調用私有的構造函數。

可是也有例外,好比單例模式下,構造函數就是私有的。由於單例模式下,類對象是類本身以公有成員函數模式建立的;

 

十二、類的成員變量初始化的兩種方式

答:構造函數裏初始化方式和構造函數初始化列表方式;
後者效率通常高於前者(尤爲是在對象指針變量初始化中),由於前者要先運行構造函數,後執行賦值操做,然後者只須要運行復制構造函數便可。

實際上,在構造函數類初始化應該叫作賦值而不是初始化。

 

 

1三、必須在構造函數列表中初始化的3種狀況

答:注意必須初始化就是表示:對象成員不可被修改,只能夠在聲明是初始化;

因此,必定包含const、引用成員。固然還包括其餘的,以下:

1.帶有const修飾的類成員 ,如const int a ;

2.包含引用成員,如 int& p;

3.類類型的成員沒有默認構造函數(就是類中的另外一個類類型的成員沒有默認的參數爲空的構造函數):

  1.  
    class a{
  2.  
    private:
  3.  
    int aa;
  4.  
    public:
  5.  
    a( int k):aa(k){
  6.  
     
  7.  
    };
  8.  
    //a(){
  9.  
    //
  10.  
    //};
  11.  
    }
  12.  
     
  13.  
    class b{
  14.  
    private:
  15.  
    int bb;
  16.  
    a A;
  17.  
    public:
  18.  
    b( int k):bb(k){
  19.  
     
  20.  
    };
  21.  
    }

如上所說:class b中的類類型成員A,但構造函數並無在初始化列表中顯示初始化它,因此b類的構造函數只會隱私的初始化它(注意全部成員變量都會通過構造函數初始化),而隱式初始化時就至關於b(NULL):A(NULL),而a沒有參數爲空的默認構造函數,因此會報錯。兩種解決方法:

 

①如上註釋部分,添加默認構造函數;

②使用b類的初始化列表顯示初始化。

 

 

1四、派生類繼承問題

答:(1)無論是私有仍是公有繼承,基類的私有成員都是會被派生類繼承的嗎?

是的。派生類會繼承基類的公有、私有成員和保護成員,只是根據繼承方式和成員類型限制,不能訪問私有等成員而已。

(2)派生類訪問屬性

① 基類的私有成員不管什麼繼承方式,在派生類中均不能夠直接訪問;
②在公有繼承下,基類的保護成員和公有成員均保持原訪問屬性;
③在保護繼承方式下,基類的保護和公有成員在派生類的訪問屬性均爲保護屬性;
④在私有繼承下,基類的保護和公有成員在派生類中的訪問屬性均爲私有屬性。

(3)補充:除了public能夠類外訪問外(所謂類外訪問通常就是類對象訪問),其餘兩個都不能被類外訪問;

可是protect相比private的訪問權限仍是大一些,由於派生類的成員函數能夠訪問繼承而來的保護(protect)成員,而不能訪問繼承而來的private成員。

總結訪問屬性:

①private:

本身所在類的成員函數訪問。被派生類繼承後,派生類的成員函數不可訪問它(這一點比較特殊,雖然基類私有成員在派生類中仍然爲私有成員,但不可被派生類的成員函數訪問)。類外(類對象或者派生類對象)均不可訪問它;

②protect:

本身所在類的成員函數可訪問;

被非私有繼承後,派生類的成員函數可訪問它;

類外(本身所在類的類對象或派生類對象)均不可訪問;

③public:本身所在類的成員函數可訪問;

被非私有繼承後,派生類成員函數能夠訪問它;

本身所在類對象、公有繼承的派生類對象都可訪問它;

(4)禁止類被繼承的方法:將類的構造函數設置爲私有,這樣派生類在實例化時首先要先實例化基類,但基類的構造函數私有不可被訪問,因此就會出錯。所以能夠得出結論:類的構造函數爲私有,該類就不可被繼承。

 

 

1五、this指針

答:this指針:類的每一個成員函數都有一個this指針,其指向調用本身的類對象。this是一個指針,其值爲*const類型的地址,不可改變不可被賦值。只有在成員函數中才能調用this指針。靜態函數(方法)因爲是全部類對象共享的,因此沒有this指針,this指針原本就是成員函數的一個參數。如

MovePoint(int a,int b)函數的原型應該是 void MovePoint( Point *this, int a, int b)。

this指針的使用:通常都是隱式應用,顯式應用通常爲返回整個對象如return *this(*this就是所指向對象的別名)。

 

1六、基類和派生類之間的賦值問題

答:基類對象與派生類對象之間存在賦值相容性,包括如下幾種狀況:

 

– 把派生類對象賦值給基類對象。
– 把派生類對象的地址賦值給基類指針。
– 用派生類對象初始化基類對象的引用。
• 反之則不行

 

結論:基類與派生類之間的的對象強制轉化通常只向上(向父輩)進行,不使用父輩向下轉換。即:通常都是基類指針指向派生類對象,而非派生類指針指向基類對象,由於後者是不安全。
注:向下轉換在C++中雖然不安全,但並不由止這種作法。

 

1七、對象數組

答:1)該數組中若干個元素必須是同一個類的若干個對象。對象數組的定義、賦值和引用與普通數組同樣,只是數組的元素與普通數組不一樣,它是同類的若干個對象。定義例如:DATE dates[7];
代表dates是一維對象數組名,該數組有7個元素,每一個元素都是類DATE的對象。

注:有人可能不一樣意「同類的若干個對象」,認爲派生類對象也能夠,可是考慮到實際中計算數組存儲空間=數組長度×單個元素大小,這就要求各個元素大小相同,顯然派生類對象大於基類對象,不適合做爲元素。

2)對象數組能夠被賦初值,也能夠被賦值

例以下面是定義對象數組並賦初值和賦值:

DATE dates[4]={ DATE(7, 7, 2001), DATE(7, 8, 2001), DATE(7, 9, 2001), DATE(7, 10, 2001) }//賦初值

dates[0] = DATE(7, 7, 2001);//賦值
dates[1] = DATE(7, 8, 2001);
dates[2] = DATE(7, 9, 2001);
dates[3] = DATE(7, 10, 2001);

在創建數組時,一樣要調用構造函數。若是有50個元素,就須要調用50次構造函數。在須要的時候,能夠在定義數組時提供實參以實現初始化。

 

1八、常量對象

答:常量指針指向常對象, 常對象只能調用其常成員函數。例如:

  1.  
    class A{
  2.  
    virtual void f() { cout << "A::f() "; }
  3.  
    void f() const { cout << "A::f() const "; }
  4.  
    };
  5.  
    const A* a;
  6.  
    a->f();

 

所以經過a->f()調用的結果是voidf() const;

 

1九、C/C++容許多繼承

答:C/C++是容許多繼承的,例如:class C : public A, public B。可是Java是不容許多繼承的,Java對於多繼承的功能是用接口來實現的。

 

20、虛擬繼承

答:即Derive::virtual public Base{ },這種用法主要是爲了在多重繼承的狀況下節省空間,保證被屢次繼承的基類自備拷貝一份。

如:類D繼承自類B一、B2,而類B一、B2都繼承自類A,所以在類D中兩次出現類A中的變量和函數。爲了節省內存空間,能夠將B一、B2對A的繼承定義爲虛擬繼承,而A就成了虛擬基類。以下圖區別:

                             普通多繼承                                                            虛繼承

 

2一、函數模板與模板函數、類模板與模板類的介紹

答:不管是類模板或是函數模板,都是在函數或類前面加上template<class T>,而後將函數或者類中的參數類型改成T,就成爲了類/函數模板。

(1)函數模板的目的:函數模板能夠用來建立一個通用的函數,以支持多種不一樣的形參,避免重載函數的函數體重複設計。

函數模板聲明格式如:

  1.  
    template<class T> T min(T x,T y)
  2.  
    {
  3.  
    函數體
  4.  
    }

具體定義以下:

  1.  
    template<class 數據類型參數標識符1,…,class 數據類型參數標識符n><返回類型><函數名>(參數表)
  2.  
    {
  3.  
    函數體
  4.  
    }

 

注意:函數模板最大特色是把函數使用的數據類型做爲參數,有多個類型參數則每一個參數前面要有class或者typename(class和typename能夠混合着用)。函數模板的實例化由編譯器在調用時自動完成,但下面幾種狀況須要程序員本身指定,急不可省略實參:

1)從模板函數實參表得到的信息有矛盾之處。

2)須要得到特定類型的返回值,而無論參數的類型如何。

3)虛擬類型參數沒有出如今模板函數的形參表中。

4)函數模板含有常規形參。

(2)模板函數
在使用函數模板時,要將這個形參實例化爲肯定的數據類型,將類型形參實例化的參數稱爲模板實參,用模板實參實例化的函數稱爲模板函數。模板函數的生成就是將函數模板的類型形參實例化的過程。
(3)類模板
類模板定義:一個類模板(也稱爲類屬類或類生成類)容許用戶爲類定義一種模式,使得類中的某些數據成員、默寫成員函數的參數、某些成員函數的返回值,可以取任意類型(包括系統預約義的和用戶自定義的)。
類模板格式:

  1.  
    template<class T>
  2.  
    class Test{
  3.  
    private:
  4.  
    T n;
  5.  
    const T i;
  6.  
    static T cnt;
  7.  
    public:
  8.  
    Test():i( 0){}
  9.  
    Test(T k);
  10.  
    ~Test(){}
  11.  
    void print();
  12.  
    T operator+(T x);
  13.  
    };

類體外的成員函數定義應以下(必須在傳統定義前進行模板聲明):
template <類型名 參數名1,類型名 參數名2,…>
函數返回值類型 類名<參數名 1 參數名 2,…>::成員函數名(形參表)
{
  函數體
}
舉例:

  1.  
    template<class T>
  2.  
    void Test<T>::print(){
  3.  
    std::cout<<"n="<<n<<std::endl;
  4.  
    std::cout<<"i="<<i<<std::endl;
  5.  
    std::cout<<"cnt="<<cnt<<std::endl;
  6.  
    }

類模板的實例化不一樣於函數模板自動進行,必須由程序員顯示指定,格式如:Test<int> ts;

(4)模板類

模板類:就是類模板實例化後的結果。該實例化如上所述須要程序員顯示指定。

 

 

2二、模板的實現和聲明是否必定要在同一個頭文件中,爲何?

答:是的。雖然平時聲明通常都是在頭文件.h中,實現是在.cpp源文件中,但使用模板時C++編譯器是直接到聲明代碼的頭文件中尋找實現部分的。若是模板的聲明和實現分離,那麼編譯不會報錯但連接會報錯,由於編譯器在聲明的頭文件裏面找不到實現。
至於緣由:由於模板定義很特殊。因爲template<…> 的參數類型在實例化前並不明確,因此編譯器在不爲它分配存儲空間。它一直處於等待狀態直到被一個模板實例告知,編譯器和鏈接器的某一機制去掉指定模板的多重定義。因此爲了容易使用,幾乎老是在頭文件中放置所有的模板聲明和定義。

 

 

2三、模板的特化和偏特化

答:(1)定義:特化是模板中的概念,指模板中有一些特殊類型須要單獨定義的狀況。

(2)特化實現:在原模板下添加一個template<>開頭的同名模板,參數定義爲你須要的特殊類型,內容則根據本身需求定義。

①類模板特化:例如stack類模板針對bool類型有特化,由於實際上bool類型只須要一個二進制位,就能夠對其進行存儲,使用一個字或者 一個字節都是浪費存儲空間的。以下:

  1.  
    template <class T>
  2.  
    class stack {};
  3.  
    template < >
  4.  
    class stack<bool> { //…// };

上述template<> class stack<bool> {……}就是對stack的特化部分。

 

②函數模板特化:一樣,函數模板特化也是針對某個特定類型的特殊處理,一個比較經典的例子:

  1.  
    template <class T>
  2.  
    T mymax(const T t1, const T t2)
  3.  
    {
  4.  
    return t1 < t2 ? t2 : t1;
  5.  
    }
  6.  
    main()
  7.  
    {
  8.  
    int highest = mymax(5,10);//正確結果
  9.  
    char c = mymax(‘a’, ’z’);//正確結果
  10.  
    const char* p1 = 「hello」;
  11.  
    const char* p2 = 「world」;
  12.  
    const char* p = mymax(p1,p2);//錯誤結果,由於比較的是指針,而不是內容
  13.  
    }

若是須要獲得正確結果就須要針對const char*的函數模板特化:

  1.  
    const char* mymax(const char* t1,const char* t2)
  2.  
    {
  3.  
    return (strcmp(t1,t2) < 0) ? t2 : t1;
  4.  
    }

 

(3)模板偏特化

①定義:模板的偏特化是指須要根據模板的某些但不是所有的參數進行特化。
1)類模板的偏特化
例如c++標準庫中的類vector的定義:

  1.  
    template <class T, class Allocator>
  2.  
    class vector { // … // };
  3.  
    template <class Allocator>
  4.  
    class vector<bool, Allocator> { //…//};

這個偏特化的例子中,一個參數被綁定到bool類型,而另外一個參數仍未綁定須要由用戶指定。
2)函數模板偏特化
嚴格的來講,函數模板並不支持偏特化,但因爲能夠對函數進行重載,因此能夠達到相似於類模板偏特化的效果。

  1.  
    template <class T> void f(T); //(a)
  2.  
    //根據重載規則,對(a)進行重載
  3.  
    template <class T> void f(T*); //(b)

若是將(a)稱爲基模板,那麼(b)稱爲對基模板(a)的重載,而非對(a)的偏特化。C++的標準委員會仍在對下一個版本中是否容許函數模板的偏特化進行討論。

 

(4)模板特化時的匹配規則
1)類模板的匹配規則
最優化的優於次特化的,即模板參數最精確匹配的具備最高的優先權。例子:

  1.  
    template <class T> class vector{//…//}; // (a) 普通型
  2.  
    template <class T> class vector<T*>{//…//}; // (b) 對指針類型特化
  3.  
    template <> class vector <void*>{//…//}; // (c) 對void*進行特化

每一個類型均可以用做普通型(a)的參數,但只有指針類型才能用做(b)的參數,而只有void*才能做爲(c)的參數
2)函數模板的匹配規則
非模板函數具備最高的優先權。若是不存在匹配的非模板函數的話,那麼最匹配的和最特化的函數具備高優先權。例子:

  1.  
    template <class T> void f(T); // (d)
  2.  
    template <class T> void f(int, T, double); // (e)
  3.  
    template <class T> void f(T*); // (f)
  4.  
    template <> void f<int> (int) ; // (g)
  5.  
    void f(double); // (h)
  6.  
    bool b;
  7.  
    int i;
  8.  
    double d;
  9.  
    f(b); // 以 T = bool 調用 (d)
  10.  
    f(i, 42,d) // 以 T = int 調用(e)
  11.  
    f(&i) ; // 以 T = int* 調用(f)
  12.  
    f(d); // 調用(h)

 

 

2四、友元類與友元函數

答:(1)友元類

A是B的友元類,則表示A不須要繼承B類也能夠訪問B的成員(包括公有,保護,私有成員)。但注意,友元關係不可逆,即B不必定是A的友元類;友元關係也不可傳遞,即A的派生類不必定是B的友元類。若要使B爲A的友元類,在A類的成員列表中定義:friend class B;

例如:

  1.  
    class TV
  2.  
    {
  3.  
    public:
  4.  
    friend class Tele;//友元類,表示tele是TV的友元類,能夠訪問TV的全部成員,但不是TV的派生類
  5.  
    TV():on_off(off),volume( 20),channel(3),mode(tv){}
  6.  
    private:
  7.  
    enum{on,off};
  8.  
    enum{tv,av};
  9.  
    enum{minve,maxve=100};
  10.  
    enum{mincl,maxcl=60};
  11.  
    bool on_off;
  12.  
    int volume;
  13.  
    int channel;
  14.  
    int mode;
  15.  
    };

(2)友元函數

若是要在類外訪問類的全部成員,或者類A中的函數要訪問類B中的成員,那麼該函數就應該是友元函數。如上面所述的友元類聲明同樣,友元函數的聲明也是在須要被訪問的類中聲明友元函數如:friend +普通函數聲明。

說明:友元類便可以聲明爲類的公有、也能夠是私有成員,只要聲明爲友元函數,就能夠訪問類的全部成員。

友元函數聲明與調用舉例:

  1.  
    class B
  2.  
    {
  3.  
       friend void Print(const B& obj);//聲明友元函數
  4.  
    }
  5.  
    void Print(const B& obj)
  6.  
    {
  7.  
       //函數體
  8.  
    }
  9.  
    void main()
  10.  
    {
  11.  
      B obj;
  12.  
      obj.Print(obj); //直接調用,能夠訪問B類中的全部成員
  13.  
    }

 

 

2五、操做符重載問題

答:C++中規定,重載運算符必須和用戶定義的自定義類型的對象一塊兒使用,即重載運算的參數中必須包括用戶自定義的類型對象。
C++中絕大部分的運算符可重載,有幾個不能重載的運算符,分別是: . 和 .* 和 ?: 和 :: 和 sizeof。
運算符重載規則以下: 
①、 C++中的運算符除了少數幾個以外,所有能夠重載,並且只能重載C++中已有的運算符。
②、 重載以後運算符的優先級和結合性都不會改變。
③、 運算符重載是針對新類型數據的實際須要,對原有運算符進行適當的改造。通常來講,重載的功能應當與原有功能相相似,不能改變原運算符的操做對象個數,同時至少要有一個操做對象是自定義類型。
運算符重載爲類的成員函數的通常語法形式爲: 
函數類型 operator 運算符(形參表) { 函數體;}
運算符重載爲類的友元函數的通常語法形式爲:
friend 函數類型 operator 運算符(形參表) { 函數體;} 
以下:
const Point Point::operator+(const Point& p) { return Point(x+p.x); } 
Point const operator-(const Point& p1,const Point& p2){ return Point(p1.x-p2.x); }
就是運算符的重載定義
注意:算符重載爲類的成員函數時,形參個數比原始的少一個。
(1) 雙目運算符重載爲類的成員函數時,函數只顯式說明一個參數,該形參是運算符的右操做數。
(2) 前置單目運算符重載爲類的成員函數時,不須要顯式說明參數,即函數沒有形參。
(3)後置單目運算符重載爲類的成員函數時,函數要帶有一個整型形參(該形參無實質用處,是用於和前置區別的)。

 

具體可參考:http://blog.csdn.net/dingyuanpu/article/details/5852825

注意:運算符重載通常有兩種:重載爲類的成員函數或重載爲友元函數
(1)只能使用成員函數重載的運算符有:=、()、[]、->、->*、new、delete。
(2)單目運算符最好重載爲成員函數。 
(3) 對於複合的賦值運算符如+=、-=、*=、/=、&=、!=、~=、%=、>>=、<<=建議重載爲成員函數。 
(4) 對於其它運算符,建議重載爲友元函數。

更多描述見:http://blog.chinaunix.net/uid-21411227-id-1826759.html

 

2六、抽象類

答:抽象類是含有純虛函數的類,其做用是做爲多個表象不一樣,但本質相同類的抽象。
故抽象類僅能夠做爲基類被繼承,不能夠實例化生成對象,不能初始化,不能被當作返回值,不能當作參數,但能夠作指針類型和引用。

 

 

2七、C++中箭頭操做符(→)與點操做符(.)的區別?

答:箭頭操做符左邊必須是指針,點操做符左邊必須是實體(類對象或者結構體)。舉個栗子以下:

  1.  
    struct MyStruct
  2.  
    {
  3.  
    int member_a;
  4.  
    };
  5.  
    MyStruct * ps;

那麼訪問member_a有兩種方式:(*ps).member_a = 1;或者ps->member_a = 1;
其中*ps是結構體,用點操做符;而ps是指向結構體的地址指針,須要用箭頭操做符。

 

2八、關於類成員函數的重載、覆蓋和隱藏的區別

答:成員函數被重載特徵是:

(1)相同的範圍(在同一個類中);

(2)函數名字相同

(3)參數不一樣

(4)virtual 關鍵字無關緊要

覆蓋就是指派生類函數覆蓋基類virtual函數,特徵是:

(1)不一樣的範圍(分別位於派生類與基類)

(2)函數名字相同

(3)參數相同

(4)基類函數必須有virtual 關鍵字

「隱藏」是指派生類的函數屏蔽了與其同名的基類函數,派生對象都是調用派生類的同名函數。規則以下:

(1)若是派生類的函數與基類的函數同名,可是參數不一樣,此時不論有無virtual關鍵字、基類的函數將被隱藏;

(2)若是派生類的函數與基類的函數同名,而且參數也相同、可是基類函數沒有virtual 關鍵字,此時基類的函數被隱藏(注意別與覆蓋混淆);

總結:

①同一類中的同名函數是重載;

②不一樣類中同名函數多是覆蓋,也多是隱藏。根據是否有virtual以及函數參數是否相同區分;

 

注意:若派生類中從新定義了基類的成員變量,則在使用派生類對象調用該對象時,只要對象沒有virtual修飾,調用哪一個根據當前成員實際屬於哪一個類肯定。以下:

  1.  
    class A{
  2.  
    private:
  3.  
    int k;
  4.  
    public:
  5.  
    int a;
  6.  
    A(){
  7.  
    a= 1;
  8.  
    }
  9.  
    void print(){
  10.  
    printf("%d",a);
  11.  
    }
  12.  
    };
  13.  
     
  14.  
    class B: public A{
  15.  
    public:
  16.  
    int a;
  17.  
    B(){
  18.  
    a= 2;
  19.  
    }
  20.  
    };
  21.  
     
  22.  
    int main(){
  23.  
    B b;
  24.  
    b.print();
  25.  
    printf("%d",b.a);
  26.  
    }

輸出結果:12

 

由於print()屬於A,那麼其中調用的a就屬於a,故爲1。同理下面的printf(「%d」,b.a)中的a屬於b,因此就是2.

 

 

2九、智能指針類

答:(1)智能指針(smart pointer)類是存儲指向動態分配(堆)對象指針的類,智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。

做用:因爲 C++ 語言沒有自動內存回收機制,程序員每次 new 出來的內存都要手動 delete。程序員忘記 delete,流程太複雜,最終致使沒有 delete,異常致使程序過早退出,智能指針就是用來有效緩解這類問題的。由於智能指針就是一個類,當超出了類的做用域是,類會自動調用析構函數,析構函數會自動釋放資源。

如原始程序:

  1.  
    void remodel(std::string & str)
  2.  
    {
  3.  
    std::string * ps = new std::string(str);
  4.  
    ...
  5.  
    if (weird_thing())
  6.  
    throw exception();
  7.  
    str = *ps;
  8.  
    delete ps;
  9.  
    return;
  10.  
    }

若在delete以前發生異常,就會致使指針ps指向的內存未釋放而內存泄漏。若改成使用智能指針,以下:

  1.  
    # include <memory>
  2.  
    void remodel (std::string & str)
  3.  
    {
  4.  
    std::auto_ptr<std::string> ps (new std::string(str));
  5.  
    ...
  6.  
    if (weird_thing ())
  7.  
    throw exception();
  8.  
    str = *ps;
  9.  
    // delete ps; NO LONGER NEEDED
  10.  
    return;
  11.  
    }

即便程序出現異常,只要ps指針失效(程序運行範圍超出函數)就會被智能指針類自動釋放。

 

原理:每次建立類的新對象時,初始化指針並將引用計數置爲1;當對象做爲另外一對象的副本而建立時,拷貝構造函數拷貝指針並增長與之相應的引用計數;對一個對象進行賦值時,賦值操做符減小左操做數所指對象的引用計數(若是引用計數爲減至0,則刪除對象),並增長右操做數所指對象的引用計數;調用析構函數時,構造函數減小引用計數(若是引用計數減至0,則刪除基礎對象)。

(2)c++裏面的經常使用的智能指針包括:auto_ptr、unique_ptr、shared_ptr和weak_ptr,第一個auto_ptr已經被c++11棄用,爲何摒棄auto_ptr呢?由於auto_ptr可能會出現兩個或更多智能指針指向同一目標,從而形成在結束時對同一內存指針屢次釋放而致使程序崩潰。share_ptr智能指針能夠避免這種狀況,由於share_ptr自帶了引用型計數器,能夠記錄同一內存指針被share_ptr指針指向的次數,釋放時先查看計數器的值,從而決定是否delete。unique_ptr則是能夠在編譯時檢測出該類錯誤,直接不讓編譯經過,從而避免程序崩潰。因此相對而言auto_ptr是最容易形成程序崩潰的。

(3)智能指針類一般用類模板實現:

通常用兩個類來實現智能指針的功能,其中一個類是指針類,另外一個是計數類,以下:

  1.  
    template <class T>
  2.  
    class SmartPclass Counter //計數類,用於存儲指向同一對象的指針數
  3.  
    {
  4.  
    private:
  5.  
    T* ptr;
  6.  
    int cnt;
  7.  
    public:
  8.  
    friend class SmartPointer;
  9.  
    Counter()
  10.  
    {
  11.  
    ptr = NULL;
  12.  
    cnt = 0;
  13.  
    }
  14.  
    Counter(T* p)
  15.  
    {
  16.  
    ptr = p;
  17.  
    cnt = 1;
  18.  
    }
  19.  
    ~Counter()
  20.  
    {
  21.  
    delete ptr;
  22.  
    }
  23.  
    };
  24.  
    template <class T>
  25.  
    class SmartPointer
  26.  
    {
  27.  
    private:
  28.  
    Counter* ptr_counter;
  29.  
    public:
  30.  
    SmartPointer(Object* p)
  31.  
    {
  32.  
    ptr_counter = new Counter(p);//新建立一個指針類先初始化計數類
  33.  
    }
  34.  
    SmartPointer( const SmartPointer &sp)
  35.  
    {
  36.  
    ptr_counter = sp.ptr_counter; //拷貝操做則直接將目標對象的計數類存儲值加1
  37.  
    ++ptr_count->cnt;
  38.  
    }
  39.  
    SmartPointer& operator=(const SmartPointer &sp)
  40.  
    {
  41.  
    ++sp.ptr_counter->cnt; //賦值操做在調用賦值構造函數前,會調用前面傳統構造函數新建一個對象,因此這裏對還須要對這個新建立的對象進行處理
  42.  
    --ptr_counter->cnt;
  43.  
    if (ptr_counter->cnt == 0)
  44.  
    {
  45.  
    delete ptr_counter;
  46.  
    }
  47.  
    ptr_counter = sp.ptr_counter;
  48.  
    }
  49.  
    ~SmartPointer()
  50.  
    {
  51.  
    --ptr_counter->cnt;
  52.  
    if (ptr_counter->cnt == 0) //當計數減爲0時,表示全部的指針都已釋放,能夠釋放內存;
  53.  
    {
  54.  
    delete ptr_counter;
  55.  
    }
  56.  
    }
  57.  
    };

 

(3)智能指針應用

在C++中智能指針的引用庫是<memory>,C++11後包括上述提到的後三種智能指針。經常使用的也就是兩種:unique_ptr和share_ptr。其中後者更好用,用法以下:

  1.  
    shared_ptr<string> s1(new string);
  2.  
    shared_ptr<string> s2 = s1;

就定義並初始化了兩個share_ptr型智能指針s1,s2。

 

30、explicit關鍵字的做用

答:explicit用來防止構造函數初始化的隱式轉換。

發生隱式轉換,除非有心利用,隱式轉換經常帶來程序邏輯的錯誤,並且這種錯誤一旦發生是很難察覺的。原則上應該在全部的構造函數前加explicit關鍵字,當你有心利用隱式轉換的時候再去解除explicit,這樣能夠大大減小錯誤的發生。

  1.  
    class String{
  2.  
    explicit String(int n);
  3.  
    String( const char *p);
  4.  
    };
  5.  
    String s1 = 'a'; //錯誤:不能作隱式char->String轉換
  6.  
    String s2(10); //能夠:調用explicit String(int n);
  7.  
    String s3 = String( 10);//能夠:調用explicit String(int n);再調用默認的複製構造函數
  8.  
    String s4 = "Brian"; //能夠:隱式轉換調用String(const char *p);再調用默認的複製構造函數
  9.  
    String s5("Fawlty"); //能夠:正常調用String(const char *p);

 

3一、構造函數和析構函數能夠拋出異常嗎?

答:(1)構造函數能夠。可是不建議拋出異常,由於拋出異常後析構函數就不會執行了,從而須要手動釋放內存;

(2)析構函數不能夠拋出異常,由於容易形成死循環。

①緣由:C++異常處理模型是處理那些由於出現異常而失效的對象,處理方式是調用這些失效對象的析構函數,釋放掉它們佔用的資源。若是析構函數向外拋出異常,則異常處理代碼會調用本身,而後本身又拋出異常,……陷入無盡遞歸嵌套之中,所以這是不被容許的。

②處理析構函數異常的正確方式:將異常封裝在析構函數內部,而不是拋出異常。

 

第十章、string類和STL模板庫

一、C++中string的find、rfind、find_first_of和find_first_not_of,以及substr函數

答:string函數庫中有以上幾個關於字符搜索的函數,返回地址;

常見用法:

string s,str;

s.find(str);//查找s字符串中含有的str,並返回str首字符的地址,沒查找到就返回string::npos;

s.rfind(str);//反向查找s字符串中的str,並返回str首字符的地址,沒查找到就返回string::npos;

s.find_first_of(str);    //函數是查找s中查找與str字符串中任何一個字符匹配的字符,若是有,則返回第一個找到的字符索引,不然返回string::npos;

s.find_first_not_of(str);    //函數是查找s中查找與str字符串中任何一個字符都不匹配的字符,若是有,則返回第一個找到的字符索引,不然返回string::npos;

substr(index,length); //函數是截取字符串,第一個參數爲起始位置,第二個參數是長度;

二、STL標準庫相關內容集中介紹

答:STL的最大特色就是:
數據結構和算法的分離,非面向對象本質。訪問對象是經過象指針同樣的迭代器實現的;
容器是象鏈表,矢量之類的數據結構,並按模板方式提供;
算法是函數模板,用於操做容器中的數據。因爲STL以模板爲基礎,因此能用於任何數據類型和結構。

STL中六大組件:

1)容器(Container),是一種數據結構,如list,vector,和deques ,以模板類的方法提供。爲了訪問容器中的數據,能夠使用由容器類輸出的迭代器;
2)迭代器(Iterator),提供了訪問容器中對象的方法。例如,能夠使用一對迭代器指定list或vector中的必定範圍的對象。迭代器就如同一個指針。事實上,C++的指針也是一種迭代器。可是,迭代器也能夠是那些定義了operator*()以及其餘相似於指針的操做符地方法的類對象;
3)算法(Algorithm),是用來操做容器中的數據的模板函數。例如,STL用sort()來對一個vector中的數據進行排序,用find()來搜索一個list中的對象,函數自己與他們操做的數據的結構和類型無關,所以他們能夠在從簡單數組到高度複雜容器的任何數據結構上使用;

4)仿函數(Function object)
5)迭代適配器(Adaptor)
6)空間配製器(allocator)

注意:本文中前三者是主要解釋部分;

 

在C++標準中,STL被組織爲下面的13個頭文件:

<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 和<utility>。


 

 

1)C++的STL容器介紹:

STL的容器能夠分爲如下幾個大類:

 一:序列容器, 有vector, list, deque, string、array(array是C++11新增的).(該類型容器也屬於STL一級容器,注:STL中一級容器是容器元素自己是基本類型,非組合類型。)

二 : 關聯容器,     有set, multiset, map, mulmap, hash_set, hash_map, hash_multiset, hash_multimap

三: 其餘的雜項: stack, queue, valarray, bitset

如上,咱們重點關注vector,deque,list,map:

其中vector和deque內部是數組結構,也就是順序存儲結構。

deque是雙端隊列;

list是雙向循環鏈表;

關聯容器的元素是自動按key升序排序,因此是排好序的。例如Map、mulmap;

部分容器的介紹以下:

 

2)C++中的容器迭代器iterator應用以及迭代失效問題

(1)迭代器(iterator)是檢查容器內元素並遍歷元素的數據類型

在C++相似指針的做用,對操做容器很方便。在java和C#中直接讓其代替了指針。每種容器都有本身的迭代器類型,這裏以vector爲例:

vector<int>::iterator iter //iter是vector<int>類型容器的迭代器
迭代器都有begin()和end()兩個函數能夠獲取指向容器起始地址和結束地址。
iterator迭代器能夠使用的操做符有:
++、--是基本的雙向迭代符;
*用於取指向地址的元素;
==用於判斷兩個迭代器是否相等;
(2)迭代失效

vector非末尾位置進行插入或刪除(erase)都會導致迭代器失效;

失效緣由:

①插入操做:
由於vector 動態增長大小時,並非在原空間後增長新空間,而是以原大小的兩倍在另外配置一個較大的新空間,然
後將內容拷貝過來,接着再原內容以後構造新元素,並釋放原空間,故容器原先的全部迭代器都會失效;

②刪除操做:
刪除操做後,被刪除數據對應的迭代器及其後面的全部迭代器都會失效。最多見的解決方法:從新對迭代器賦初值,對於erase()函數,因爲其返回值是下一個元素的迭代器iter,因此刪除後直接使用iter = v.erase(iter)從新對iter賦值便可;
補充:而對於關聯結構如鏈表list,哈希表map等刪除一段連續元素偏偏要使用iter++。

(3)vector的訪問方式
訪問vector中的數據 使用兩種方法來訪問vector。 
一、 vector::at() 
二、 vector::operator[],就是好比對於vector<int> array,能夠用array[1]之類的訪問;
operator[]主要是爲了與C語言進行兼容。它能夠像C語言數組同樣操做。但at()是咱們的首選,由於at()進行了邊界檢查,若是訪問超過了vector的範圍,將拋出一個例外。因爲operator[]不作邊界檢查,容易形成一些錯誤,全部咱們不多用它。

三、vector建立二維數組,如vector<vector<int>> array,array就是一個行和列都是可變的二維數組;

四、對於vector數組長度求取,好比vector<vector<int>> array,array.size()表示二維數組行數,array[0].size()表示列數;

3)算法部分介紹:

STL算法部分主要由頭文件<algorithm>, <numeric>, <functional>組成;要使用STL中的算法函數必須包含頭文件<algorithm>,對於數值算法須包含<numeric>,<functional>中則定義了一些模板類,用來聲明函數對象;

STL中算法大體分爲四類:
非可變序列算法:指不直接修改其所操做的容器內容的算法。
可變序列算法:指能夠修改它們所操做的容器內容的算法。
排序算法:包括對序列進行排序和合並的算法、搜索算法以及有序序列上的集合操做。
數值算法:對容器內容進行數值計算。

查找算法(13個):判斷容器中是否包含某個值;

adjacent_find:在iterator對標識元素範圍內,查找一對相鄰重複元素,找到則返回指向這對元素的第一個元素的Forward Iterator;不然返回last;
binary_search:在有序序列中查找value,找到返回true。重載的版本實用指定的比較函數對象或函數指針來判斷相等;
count:利用等於操做符,把標誌範圍內的元素與輸入值比較,返回相等元素個數;
count_if:利用輸入的操做符,對標誌範圍內的元素進行操做,返回結果爲true的個數;
equal_range:功能相似equal,返回一對iterator,第一個表示lower_bound,第二個表示upper_bound;
其餘:find,find_end,find_first_of,find_if,lower_bound,upper_bound,search,search_n;

排序和通用算法(14個):提供元素排序策略;
inplace_merge:合併兩個有序序列,結果序列覆蓋兩端範圍。重載版本使用輸入的操做進行排序;
merge:合併兩個有序序列,存放到另外一個序列。重載版本使用自定義的比較;
nth_element:將範圍內的序列從新排序,使全部小於第n個元素的元素都出如今它前面,而大於它的都出如今後面。重載版本使用自定義的比較操做;
partial_sort:對序列作部分排序,被排序元素個數正好能夠被放到範圍內。重載版本使用自定義的比較操做;
partial_sort_copy:與partial_sort相似,不過將通過排序的序列複製到另外一個容器;
其餘:partition,random_shuffle,reverse,reverse_copy,rotate, rotate_copy,sort,stable_sort,stable_partition;


刪除和替換算法(15個);
排列組合算法(2個):提供計算給定集合按必定順序的全部可能排列組合;
算術算法(4個);
生成和異變算法(6個);
關係算法(8個);
集合算法(4個);
堆算法(4個);

更多介紹見:http://blog.csdn.net/xiongchao99/article/details/73694530

 

第十一章、輸入、輸出和文件

一、C語言中的scanf和printf函數

答:scanf的調用格式爲:scanf(格式控制,地址表列),注意是地址表,就是說後面參數必須是地址符,和printf參數表不同;

printf的調用格式爲:printf(「格式控制字符串」,輸出表列),注意是輸出列表,即直接是須要輸出的變量名。但有一個例外,若格式控制符爲s表示字符串,即輸出字符串則需輸出參數是數組名(數組首地址)。

printf("%02x",xx)表示xx輸出16進制,且至少要2位。若輸出位數不到2位16進制,根據二進制負數高位補1,正數補0進行填充;

 

二、C++輸出小數點後多少位

答:cout輸出小數點後幾位能夠實現,比較麻煩,本文采用C的方式:

首先要引入庫文件include<stdio.h>,而後使用printf("%.5f",d);

其中,d能夠是double或者float類型。

 

三、printf等可變長參數函數輸出問題

答:在可變長參數函數(例如printf函數)或者不帶原型聲明函數中,在調用該函數時C自動進行類型提高,float類型的實際參數將提高到double,但float是32位的,double是64位的,而%d只輸出低32位的數據,並將這些32位二進制以十進制數輸出。注意:printf()函數須要轉移字符%表示輸出。

 

四、C++中經常使用的字符串輸入方式

答:(1)直接使用cin>>str:
char str[10];
cin>>str;//這種方式遇到空白(如空格、製表符、換行符)就結束,如:輸入姓名 Bill Colintun就只會讀入Bill,捨棄後半部分;

(2)cin.getline(str,20):
int size=20;
char str[size];
cin.getline(str,size);//能夠讀取size-1個字符,最後一個自動添加'\0',這種方只會在讀取到指定數目字符或遇到換行符時才結束。

 

五、文件流與文件操做

答:涉及函數fopen、fclose、fprintf、fscanf、feof等。
(1)舉例以下:

  1.  
    FILE *fp;
  2.  
    fp = fopen( "filel.txt", "w");//打開文件,並賦予寫權限,返回FILE類型的指針返回值
  3.  
    fprintf(fp, "%s%d%f\n", s, a, f)//從文件給定的FILE指針地址處fp寫入
  4.  
    fclose(fp); //每一次fopen後必定要關閉
  5.  
    fp = fopen( "file1.txt", "r"); //打開文件並賦予讀取權限,返回FILE指針
  6.  
    fscanf( fp, "%s%s%s", str, str1, str2); //從文件指定地址fp讀取
  7.  
    fclose(fp); //每一次fopen後必定要關閉


(2)feof( fp)函數

上述函數中fp指向文件的指針,feof函數的用法是從輸入流讀取數據,若是到達穩健末尾(遇文件結束符),返回值eof爲非零值,不然爲0。

 

 

第十二章、C++多線程編程

一、C++的多線程編程

答:(1)Windows下的C++多線程
首先須要引用<windows.h>庫文件,建立和關閉線程以下調用:
①建立一個線程
HANDLE thread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL); //括號中爲參數,其中ThreadProc爲該線程運行的函數
注意:通常只用到第3和第4個參數,第四個是傳遞給子線程的參數;

②關閉該線程

CloseHandle(thread); 

代碼示例:

  1.  
    #include <windows.h>
  2.  
    #include <iostream>
  3.  
    using namespace std;
  4.  
     
  5.  
    //線程函數
  6.  
    DWORD WINAPI ThreadProc(LPVOID lpParameter)
  7.  
    {
  8.  
    for (int i = 0; i < 100; ++ i){
  9.  
    cout << "子線程:i = " << i << endl;
  10.  
    Sleep( 1000);
  11.  
    }
  12.  
    return 0L;
  13.  
    }
  14.  
     
  15.  
    int main()
  16.  
    {
  17.  
    //建立一個線程
  18.  
    HANDLE thread = CreateThread( NULL, 0, ThreadProc, NULL, 0, NULL);
  19.  
    //關閉線程
  20.  
    CloseHandle(thread);
  21.  
    //主線程的執行路徑
  22.  
    for (int i = 0; i < 100; ++ i){
  23.  
    cout << "主線程:i = " << i << endl;
  24.  
    Sleep( 1000);
  25.  
    }
  26.  
    return 0;
  27.  
    }

 

③保證同步的互斥量應用

上述示例中,並不能保證主線程和子線程交替運行,要確切保證交替運行通常要使用互斥量Mutex;

  1.  
    HANDLE CreateMutex(
  2.  
    LPSECURITY_ATTRIBUTES lpMutexAttributes,
  3.  
    BOOL bInitialOwner, // initial owner
  4.  
    LPCTSTR lpName // object name
  5.  
    );

該函數用於創造一個獨佔資源,第一個參數咱們沒有使用,能夠設爲NULL,第二個參數指定該資源初始是否歸屬建立它的進程,第三個參數指定資源的名稱。

HANDLE hMutex = CreateMutex(NULL,TRUE,"screen"); 

這條語句創造了一個名爲screen而且歸屬於建立它的進程的資源。

  1.  
    BOOL ReleaseMutex(
  2.  
    HANDLE hMutex // handle to mutex
  3.  
    );

該函數用於釋放一個獨佔資源,進程一旦釋放該資源,該資源就再也不屬於它了,若是還要用到,須要從新申請獲得該資源。申請資源的函數以下:

  1.  
    DWORD WaitForSingleObject(
  2.  
    HANDLE hHandle, // handle to object
  3.  
    DWORD dwMilliseconds // time-out interval
  4.  
    );

第一個參數指定所申請的資源的句柄,第二個參數通常指定爲INFINITE,表示若是沒有申請到資源就一直等待該資源,若是指定爲0,表示一旦得不到資源就返回,也能夠具體地指定等待多久才返回,單位是千分之一秒。

具體對互斥量在多線程中應用以下:

  1.  
    #include <windows.h>
  2.  
    #include <iostream>
  3.  
    using namespace std;
  4.  
     
  5.  
    HANDLE hMutex;
  6.  
    //線程函數
  7.  
    DWORD WINAPI ThreadProc(LPVOID lpParameter)
  8.  
    {
  9.  
    for (int i = 0; i < 100; ++ i){
  10.  
    WaitForSingleObject(hMutex, INFINITE);
  11.  
    cout << "子線程:i = " << i << endl;
  12.  
    Sleep( 1000);
  13.  
    ReleaseMutex(hMutex);
  14.  
    }
  15.  
    return 0L;
  16.  
    }
  17.  
     
  18.  
    int main()
  19.  
    {
  20.  
    //建立互斥量,FALSE表示其不只僅屬於主線程(公用的)
  21.  
    hMutex = CreateMutex( NULL, FALSE,"pthread");
  22.  
     
  23.  
    //建立一個線程
  24.  
    HANDLE thread = CreateThread( NULL, 0, ThreadProc, NULL, 0, NULL);
  25.  
    //關閉線程
  26.  
    CloseHandle(thread);
  27.  
     
  28.  
    //主線程的執行路徑
  29.  
    for (int i = 0; i < 100; ++ i){
  30.  
    WaitForSingleObject(hMutex, INFINITE);
  31.  
    cout << "主線程:i = " << i << endl;
  32.  
    Sleep( 1000);
  33.  
    ReleaseMutex(hMutex);
  34.  
    }
  35.  
    return 0;
  36.  
    }

本例程與②中比較就是在每一次線程運行前添加了互斥量,在本次線程運行結束時釋放互斥量資源,從而保證兩個線程的同步關係;

(2)Linux下的C++多線程

須要遵循POSIX接口,稱爲pthread,POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX標準。該標準定義了建立和操縱線程的一整套API。在類Unix操做系統(Unix、Linux、Mac OS X等)中,都使用Pthreads做爲操做系統的線程。線程庫實行了POSIX線程標準一般稱爲Pthreads。
引入庫:pthread.h

①數據類型:
pthread_t:線程句柄;
pthread_attr_t:線程屬性;

②操縱函數:
pthread_create():建立一個線程;
pthread_exit():終止當前線程;
thread_cancel():中斷另一個線程的運行;
pthread_join():阻塞當前其餘的線程,直到join指定的線程運行結束;
pthread_attr_init():初始化線程的屬性;
thread_attr_setdetachstate():設置脫離狀態的屬性(決定這個線程在終止時是否能夠被結合);
thread_attr_getdetachstate():獲取脫離狀態的屬性;
pthread_attr_destroy():刪除線程的屬性;
thread_kill():向線程發送一個信號;

③同步函數:用於 mutex 和條件變量
thread_mutex_init() 初始化互斥鎖;
thread_mutex_destroy() 刪除互斥鎖;
thread_mutex_lock():佔有互斥鎖(阻塞操做);
thread_mutex_trylock():試圖佔有互斥鎖(不阻塞操做)。即,當互斥鎖空閒時,將佔有該鎖;不然,當即返回;
thread_mutex_unlock(): 釋放互斥鎖;
pthread_cond_init():初始化條件變量;
thread_cond_destroy():銷燬條件變量;
thread_cond_signal(): 喚醒第一個調用pthread_cond_wait()而進入睡眠的線程;
thread_cond_wait(): 等待條件變量的特殊條件發生;
Thread-local storage(或者以Pthreads術語,稱做 線程特有數據);
thread_key_create(): 分配用於標識進程中線程特定數據的鍵;
thread_setspecific(): 爲指定線程特定數據鍵設置線程特定綁定;
thread_getspecific(): 獲取調用線程的鍵綁定,並將該綁定存儲在 value 指向的位置中;
thread_key_delete(): 銷燬現有線程特定數據鍵;

④工具函數:
thread_equal(): 對兩個線程的線程標識號進行比較;
pthread_detach(): 分離線程;
pthread_self(): 查詢線程自身線程標識號;

⑤經過pthread實現C++多線程:

  1.  
    #include<stdio.h>
  2.  
    #include<string.h>
  3.  
    #include <pthread.h>
  4.  
     
  5.  
    void* print1(void* data){
  6.  
    printf("1 ");
  7.  
    }
  8.  
     
  9.  
    void* print2(void* data){
  10.  
    printf("2 ");
  11.  
    }
  12.  
     
  13.  
    void* print3(void* data){
  14.  
    printf("3 ");
  15.  
    }
  16.  
     
  17.  
    int main(void){
  18.  
    pthread_t t1,t2,t3;//句柄
  19.  
    int i=10;
  20.  
    while(i--)
  21.  
    {
  22.  
    pthread_create(&t1, 0,print1,NULL);//建立
  23.  
    pthread_create(&t2, 0,print2,NULL);
  24.  
    pthread_create(&t3, 0,print3,NULL);
  25.  
     
  26.  
    pthread_join(t1, NULL);//阻塞其餘線程,保證指定線程運行結束並返回值
  27.  
    pthread_join(t2, NULL);
  28.  
    pthread_join(t3, NULL);
  29.  
    }
  30.  
    printf("\n");
  31.  
    }

 

注:因爲pthread庫不是Linux系統默認的庫,鏈接時須要使用庫libpthread.a,因此在使用pthread_create建立線程時,在編譯中要加-lpthread參數。

 

更多Linux的C++ 多線程請參考:http://www.cnblogs.com/youtherhome/archive/2013/03/17/2964195.html

 

 

二、C++線程安全問題

答:線程安全就是多線程訪問時,採用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其餘線程不能進行訪問直到該線程讀取完,其餘線程纔可以使用。不會出現數據不一致或者數據污染;線程不安全就是不提供數據訪問保護,有可能出現多個線程前後更改數據形成所獲得的數據是髒數據。
1)線程安全問題都是由全局變量及靜態變量引發的。如有多個線程同時對全局變量、靜態變量執行寫操做,通常都須要考慮線程同步,不然的話就可能影響線程安全;

2)局部變量局部使用是安全的,由於每一個thread 都有本身的運行堆棧,而局部變量是被拷貝副本到各自堆棧中,互不干擾;

3)標準庫裏面的string在多線程下不是安全的,它只在兩種狀況下安全:①多個線程同時讀取數據是安全的;②只有一個線程在寫數據是安全的;

4)MFC的CString也不是多線程安全的;

5)volatile不能保證全局整形變量是多線程安全。volatile是一個類型修飾符(type specifier),volatile變量是用於多個線程訪問的變量,被設計用來修飾被不一樣線程訪問和修改的變量。volatile提醒編譯器它後面所定義的變量隨時都有可能改變,所以編譯後的程序每次須要存儲或讀取這個變量的時候,都會直接從變量地址中讀取數據。若是沒有volatile關鍵字,則編譯器可能優化讀取和存儲,可能暫時使用寄存器中的值,若是這個變量由別的程序更新了的話,將出現不一致的現象。。(注意,volatile變量也能夠是const變量);

6)安全性:局部變量>成員變量>全局變量;

7)給出一個全局變量在多線程中的應用實例:兩個線程併發執行如下代碼,假設a是全局變量,初始值是1:
Void foo ( )
{
    ++a
    printf("%d",a);
}
那麼輸出結果有4種可能:3 2; 2 3; 3 3; 2 2;

緣由:①每一個線程對a作++先讀取,再++,最後寫(a++或者++a之類的都不是原子操做,因此若不想被其餘線程中途打斷,都須要作線程同步);

②每一個線程對a作printf前,先將數據讀取到臨時變量並壓棧,再彈出打印;

③以上每一小步(例如先、再、後分3小步)完成後,下一小步進行前均可能被另外一線程打斷;

 

 

第十三章、其餘

一、C++中的左值和右值

答:在 C中:能夠放到賦值操做符=左邊的是左值,能夠放到賦值操做符右邊的是右值。有些變量既能夠當左值又能夠當右值,這一說法不是很準確,左值爲Lvalue,其實L表明Location,表示在內存中能夠尋址,能夠給它賦值(常量const類型也能夠尋址,可是不能賦值),Rvalue中的R表明Read,表示能夠讀取它的值,不能夠取地址和修改。
在 C++中,每個表達式都會產生一個左值或者右值,相應的該表達式也就被稱做「左值表達式", "右值表達式"。對於基本數據類型來講左值右值的概念和C沒有太多不一樣,不一樣的地方在於自定義的類型,並且這種不一樣比較容易讓人混淆,有兩點要注意:
1) 對於基礎類型,右值是不可被修改的,也不可被 const, volatile 所修飾;
2) 對於自定義的類型,右值卻容許經過它的成員函數進行修改;

補充:臨時值是const屬性的,不可更改,因此臨時值也能夠是右值。

以下:
int a;
int b;
a = 3;
b = 4;
a = b;
b = a;
// 如下寫法不合法。
3 = a;
a+b = 4;

如上,a,b都是左值,也能夠是右值;但三、4爲常量,只可爲右值;a+b的結果變了成了一個臨時值,臨時值是const類型的,不可被賦值,故不可做爲左值,因此a+b=4錯誤。

結論:1)左值、右值廣義上都是表達式;

2)C++中左值是能夠取地址的值和被賦值修改的,或者說是指表達式結束後仍然存在的值;相反右值不可取地址和修改的了,如常量或臨時值;

3)++a是左值,由於a先進行自增運算,後返回本身,返回值仍然爲本身自己,能夠取地址和賦值,因此++a是左值;

a++先將a賦值給臨時變量,而後返回臨時變量(接着本身進行自增運算),臨時變量不可更改,因此a++不是左值;

4)左值引用和右值引用;

記住:很是量引用必須是左值(即很是量引用必須指向左值),由於很是量引用面臨被修改的可能,左值纔可知足;

常量引用必須是右值(即常量引用必須指向右值),由於左值可能被修改,不知足常量要求;

例如:

const cs& ref1 = 1是對的的; 

cs& ref2 = 1就是錯誤的。ref2是很是量引用,必須指向左值,而1明顯是右值。

5)若想把左值看成右值來使用,例如一個變量的值,再也不使用了,但願把它的值轉移出去,C++11中的std::move就爲咱們提供了將左值引用轉爲右值引

用的方法。

 

二、C編程中,用指數形式書寫數據

答:格式以下:

 

其中,指數部分必須爲整數,不但是小數。

 

三、面向對象程序測試對象——mock

答:在爲傳統面嚮對象語言的程序作單元測試的時候,常常用到mock對象。Mock對象經過反射機制,能夠訪問類的全部成員(包括公有、保護和私有成員),因此mock的反射機制也就最大程度破壞了面向對象的封裝性。

 

四、實現交換兩個整型變量的值,要求不使用第三個變量

答:例如,a=5,b=3:
方法一:算術法,a=a+b;b=a-b;a=a-b;
方法二:異或法,a=a^b;b=a^b;a=a^b;

 

五、十進制變二進制方法

答:通常是將整數和小數部分分開轉化,而後加在一塊兒;
整數部分:十進制整數轉換爲二進制整數採用"除2取餘,逆序排列"法。具體作法是:十進制整數除以2,能夠獲得一個商和餘數;再把商除以2,又會獲得一個商和餘數,如此進行,直到商爲0時爲止,而後把先獲得的餘數做爲二進制數的低位有效位,後獲得的餘數做爲二進制數的高位有效位,依次排列起來。也叫「倒序取餘」。

小數部分:十進制小數轉換成二進制小數採用"乘2取整,順序排列"法。具體作法是:用2乘以十進制小數,能夠獲得積,將積的整數部分取出,再用2乘餘下的小數部分,又獲得一個積,再將積的整數部分取出,如此進行,直到積中的小數部分爲零,此時0或1爲二進制的最後一位。或者達到所要求的精度爲止。

 

 

六、轉義字符

答:轉義字符除了\n \t \\等外,還有\後面接數字的,如:

'\123'表示\ddd模式的3位八進制,ASCII碼是83;(注:int m=0123,數值前有一個0,也表示123是八進制)

'\x12'表示\xdd模式的2位十六進制,ASCII碼是18;

 

 

七、正則表達式

答:(1)正則表達式用處:主要用於字符串匹配、字符串合法性驗證(如郵箱名合法性驗證)、替換等。
(2)C++裏面使用正則表達式通常有三種:C regex,C ++regex,boost regex,其處理速度依次減慢;
(3)C++中正則表達式用於郵箱合法性驗證:

  1.  
    //電子郵件匹配
  2.  
    void is_email_valid(string email_address)
  3.  
    {
  4.  
    string user_name, domain_name;
  5.  
    // 正則表達式,匹配規則:
  6.  
    // 第1組(即用戶名),匹配規則:0至九、A至Z、a至z、下劃線、點、連字符之中的任意字符,重複一遍或以上
  7.  
    // 中間,一個「@」符號
  8.  
    // 第2組(即域名),匹配規則:0至9或a至z之中的任意字符重複一遍或以上,接着一個點,接着a至z之中的任意
  9.  
    //字符重複2至3遍(如com或cn等), 第二組內部的一組,一個點,接着a至z之中的任意字符重複2遍(如cn或fr等)
  10.  
    // 內部一整組重複零次或一次
  11.  
    regex pattern("([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)");
  12.  
    if ( regex_match( email_address, pattern ) )
  13.  
    {
  14.  
    cout << "您輸入的電子郵件地址合法" << endl;
  15.  
    // 截取第一組
  16.  
    user_name = regex_replace( email_address, pattern, string("$1") );
  17.  
    // 截取第二組
  18.  
    domain_name = regex_replace( email_address, pattern, string("$2") );
  19.  
    cout << "用戶名:" << user_name << endl;
  20.  
    cout << "域名:" << domain_name << endl<< endl;
  21.  
    }
  22.  
    else
  23.  
    {
  24.  
    cout << "您輸入的電子郵件地址不合法" << endl << endl;
  25.  
    }
  26.  
    }

 

八、memset()函數的做用

答:(1)memset()函數原型:extern void *memset(void *buffer, int c, int size) 其中,buffer:爲指針或是數組,c:是賦給buffer的值,size:是buffer的長度;

 

(2)做用:Memset用來將buffer開始的長爲size的內存空間所有設置爲字符c,通常用在對定義的字符串進行初始化爲''或'/0';這個函數在socket中多用於清空數組。
舉例:原型是memset(buffer, 0, sizeof(buffer)),應用如char a[100];memset(a,'/0',sizeof(a));

(3)對於常見的一個問題「memset函數可否用來初始化一個類對象」,有以下答案:
理論上是能夠,可是很危險,尤爲是在類中有虛函數的狀況下,不能夠使用memset初始化,不然會破壞原對象的虛函數表指針。總之,memset如上所述通常只用於常規的數據存儲區的清零或初始化,不要用於其餘。

  1.  
    const char* mymax(const char* t1,const char* t2)
  2.  
    {
  3.  
    return (strcmp(t1,t2) < 0) ? t2 : t1;
  4.  
    }


九、內存碎片

答:內存碎片分爲內部碎片和外部碎片。

(1)內部碎片就是已經被分配出去(能明確指出屬於哪一個進程)卻不能被利用的內存空間;如某一數組容量爲90,但實際只能夠分配8字節的倍數大小的容量即96,剩下的6個字節內存在當前程序中得不到利用也不能再次分配給其餘程序,因此成爲了碎片。

(2)外部碎片是由於頻繁的分配與回收物理頁面會致使大量的、連續且小的頁面塊夾雜在已分配的頁面中間,就會產生外部碎片;假設申請內存塊就0~9區間,繼續申請一塊內存爲10~14區間。把第一塊內存塊釋放,而後再申請一塊大於10個單位的內存塊,好比說20個單位。由於剛被釋放的內存塊不能知足新的請求,因此只能從15開始分配出20個單位的內存塊。如今整個內存空間的狀態是0~9空閒,10~14被佔用,15~24被佔用。其中0~9就是一個內存碎片了。若是10~14一直被佔用,而之後申請的空間都大於10個單位,那麼0~9就永遠用不上了,變成外部碎片。

(3)如何解決內存碎片? 採用Slab Allocation機制:整理內存以便重複使用。

Slab Allocator的基本原理是按照預先規定的大小,將分配的內存分割成特定長度的塊,以徹底解決內存碎片問題。以下圖:

每次根據須要,從上述合適大小的chunks內存組中選擇一塊進行存儲,從而減小內部碎片產生。同時,使用結束後並不釋放而是在機制下進行整理以便下次重複使用,從而又能夠減小外部碎片(外部碎片主要因爲頻繁分配釋放產生)。

 

 

十、棧溢出

答:最簡單的棧溢出就是無限遞歸調用,即函數遞歸調用時沒有設置終止條件,就會發生棧溢出。

相關文章
相關標籤/搜索