1. 程式人生 > >C++常考面試題

C++常考面試題

轉自:https://www.cnblogs.com/liufei1983/p/7099401.html

1 new/delete 與 malloc/free的區別

    運算子是語言自身的特性,有固定的語義,編譯器知道意味著什麼,由編譯器解釋語義,生成相應的程式碼。

    庫函式是依賴於庫的,一定程度上獨立於語言的。編譯器不關心庫函式的作用,只保證編譯,呼叫函式引數和返回值符合語法,生成call函式的程式碼。

     實際中,一些高階點的編譯器,都會對庫函式進行特別處理。

      malloc/free是庫函式,new/delete是C++運算子。對於非內部資料型別而言,光用malloc/free無法滿足動態物件都要求。new/delete是運算子,編譯器保證呼叫構造和解構函式對物件進行初始化/析構。但是庫函式malloc/free是庫函式,不會執行構造/析構。

2 delete與delete[ ] 區別

     delete只會呼叫一次解構函式,而delete[] 會呼叫沒一個成員的解構函式。

     delete 與 new 配套使用; delete[] 與new[]配套使用。

     對於內建的簡單資料型別,delete和delete[] 功能相同。

     對於複雜資料型別,delete和delete[]不同,前者刪除單個物件,後者刪除陣列。

3 子類析構時,要呼叫父類的解構函式嗎?

 

     解構函式呼叫的次序時先派生類後基類的。和建構函式的執行順序相反。並且解構函式要是virtual的,否則如果用父類的指標指向子類物件的時候,解構函式靜態繫結,不會呼叫子類的析構。

     不用顯示呼叫,自動呼叫。

4 多型, 虛擬函式, 純虛擬函式

多型:不同物件接收相同的訊息產生不同的動作。多型包括 編譯時多型和 執行時多型

    執行時多型是:通過繼承和虛擬函式來體現的。
       編譯時多型:運算子過載上。

虛擬函式: 在基類中用virtual的成員函式。允許在派生類中對基類的虛擬函式重新定義。
       基類的虛擬函式可以有函式體,基類也可以例項化。
       虛擬函式要有函式體,否則編譯過不去。
       虛擬函式在子類中可以不覆蓋。
       建構函式不能是虛擬函式。

純虛擬函式:基類中為其派生類保留一個名字,以便派生類根據需要進行定義。
       包含一個純虛擬函式的類是抽象類。
       純虛擬函式後面有 = 0;
       抽象類不可以例項化。但可以定義指標。
       如果派生類如果不是先基類的純虛擬函式,則仍然是抽象類。
        抽象類可以包含虛擬函式。

5 抽象類和介面的區別  

       在C++裡面抽象類就是介面

     抽象類:定義了純虛擬函式的類是抽象類,不能例項化。
        抽象類包括抽象方法(純虛方法),也可以包含普通方法。
        抽象類可以派生自一個抽象類,可以覆蓋基類的抽象方法也可以不覆蓋。
        雖不能定義抽象類的例項,但是可以定義抽象類的指標。

6 什麼是“引用”?宣告和使用“引用”要注意哪些問題?

         引用的特性

                 引用是目標變數的別名,對引用的操作與對變數的操作效果一樣。宣告引用的時候要必須對其初始化。引用宣告完後,相當於目標變數有兩個名稱,不能     再把引用作為其他變數的別名。

                 引用不是新定義一個變數,它只是表示該引用是目標變數名的一個別名,它本身不是一種資料型別,因此引用不佔用儲存單元。

                 無法建立陣列的引用。因為陣列是一個由若干元素組成的集合,無法建立陣列的別名。

         引用的作用

                 作為函式的引數,以前用值傳遞,現在用指標或引用。

                 傳引用和傳指標給函式效果一樣的。

                 傳遞引用,記憶體中沒有生成實參副本,是直接對實參操作。如果傳遞的是值型別,需要在棧上生成副本,如果是物件,還要呼叫建構函式。

                  指標呼叫的時候,其實也會形參分配儲存單元,且需要用“指標變數名”的形式運算,容易產生錯誤並且可讀性差;呼叫的時候,需要用變數的地址作為實             參,呼叫形式不好看。引用沒有這些            問題。

                  引用作為返回值最大的好處是:記憶體中不會產生副本。

                  但是,引用作為返回值注意事項:

                  A:不能返回區域性變數的引用。

                  B:不能返回函式內部new的變數。因為引用僅僅是別名,無法釋放記憶體。

                  C: 可以返回類成員的引用,但是最好是const

                  D : 引用和指標一樣,可以產生多型的效果。

   總結:    

             A: 引用的使用主要用於函式傳參,解決大塊資料或物件的問題。
             B: 用引用傳遞函式引數,不產生副本,通過const,保證引用傳遞的安全性。
             C:比指標的可讀性好,

7 將引用作為函式引數有哪些特點   

(1)與指標呼叫效果一樣。 
(2)引用傳參,記憶體中並沒有產生副本。
(3)用指標傳遞,也要給形參分配儲存單元;並且需要使用"*變數的"的形式,可讀性差;另外,呼叫的地方還得用地址作為實參。

8 什麼時候用常引用

         const int &ra = a;   // 不能通過引用對目標變數的值進行修改,從而使引用的目標成為const的,安全。

         void bar(String &ra)

         bar("AA")  // 這個會報錯,因為 ”AA“相當於 const char[], 不能傳遞給bar函式。

          可以把函式宣告為Void bar(Const String &ra), 上述語句就不會報錯。

9  引用作為函式返回值型別的格式,好處和規則?

            int &fun(int a) {}

             好處:不會生成副本。

             規則:    不能返回區域性變數的引用;不能返回函式內部new分配的記憶體引用; 如果返回成員的話,返回const'

10  結構與聯合的區別

              聯合是公用儲存單元的,任何一個時刻只有一個被選中的成員。一旦賦值後,其他成員也覆蓋了。

11 過載(overload)和重寫(override)?

       過載:多個同名函式,引數不同(個數不同,引數型別不同);是同一層級的函式;靜態繫結;編譯期繫結。

       重寫:子類重新定義父類函式的方法;是動態繫結。

12  有幾種情況用intialization list(初始化列表)而不是assignment(賦值)?

           當類中含有const成員變數; reference成員變數; 基類建構函式需要初始化列表。

13  C++是不是型別按群的?

         不是; 兩個不同型別的指標之間可以強制轉換。

14  main函式之前會執行什麼程式碼?

      全域性變數的初始化。

15 記憶體分配方式和區別

  (1)靜態儲存區:在編譯時就分配好,在整個執行期間都存在。比如全域性變數,static變數。

  (2)常量區: 存放常量的,比如字串常量。

  (3)堆

  (4)棧

16 BOOL,int,float,指標型別,於”零“的比較語句。

   BOOL: if(!a)  or  if(a)

           int:  if(a == 0)

           float:  const EXPRESSION exp = 0.000001

                     if (a < EXP && a > -EXP)

           指標:  if(a != NULL)

17  const 與 #define相比,優點?

       const:    定義常量;  修飾函式引數;   修飾函式返回值;     修飾類成員函式。

       好處:     const 修飾的有資料型別,而巨集沒有,所以可以做型別檢查;而巨集僅作字元替換,無安全檢查。

                     const常量可以除錯

                     巨集有副作用。不加括號的話有副作用。

18 陣列和指標的區別

     陣列要麼在靜態儲存區建立,要麼在棧上建立。指標可以隨時指向任意型別的記憶體塊。

      char a[] = "khell"; // 棧中分配記憶體,所以可以修改。
      a[0] = 'x'; // 可以 沒有問題
      char *p = "khell";//常量字串,儲存在字元常量區,不可以修改


       p[0] = 'x'; // 編譯可以,執行時錯誤。
       sizeof(a) //是陣列的大小;
       sizeof(p) // 是指標的大小4.

       當陣列作為函式引數進行傳遞時,該陣列退化成指標

  void Func(char a[100])
  {
    cout<< sizeof(a) << endl; // 4 位元組而不是100 位元組
  }

        陣列名不能自加自減,但是指標可以。

        int a[ ] = {1, 2, 3, 4, 5};

        int *ptr = (int *) (&a + 1)  // a的地址加1後,其實是加了 4*5  = 20那麼多。每次把一個地址加1,都是走資料結構那麼大的步長

19 int(*s[10])(int)         

        函式指標陣列,S[10]裡面每個元素都是函式指標,指向函式的型別是 int  fun(int a)

                  void add(int a, int b)
                   {
                     cout << a + b << endl;
                   }

      void (*p1)(int a, int b);
      p1 = add;

20 為什麼基類的解構函式是虛擬函式?

       動態繫結,不會造成潛在的記憶體洩漏

21 全域性變數和區域性變數的區別?如何實現的?作業系統和編譯器是怎麼知道的?

        全域性變數分配在全域性資料段(靜態儲存區),在程式開始執行時候載入。區域性變數則分配在堆疊裡面。

22  記憶體分配方式

堆:有記憶體碎片的問題。一定的演算法去找合適的記憶體。效率低。OS有記錄空閒記憶體地址的連結串列

棧:專門的暫存器存放棧地址。效率高。有大小限制。

自由儲存區:用malloc /free分配釋放。 和堆類似。

全域性/靜態儲存區:全域性變數,靜態變數。

常量儲存區:放常量,不允許修改。

int a=0;    全域性/靜態儲存區
char *p1;  全域性/靜態儲存區 
int main() 

 int b; //棧 
 char s[]="abc"; //棧 
 char *p2; //棧 
 char *p3="123456"; //123456在常量區,p3在棧上。

static int c =0;//全域性(靜態)初始化區 
 p1 = (char *)malloc(10); //分配得來得10和20位元組的區域就在堆區
p2 = (char *)malloc(20); 
 strcpy(p3,"123456"); //123456/0放在常量區,編譯器可能會將它與p3所指向的"123456" 優化成一個地方。 
}

23 void *(*(*fp1)(int)[10]  / float(*(*fp2)(int, int, int))(int)  /  int (*(*fp3())[10]()

24 引用與指標區別
  引用必須初始化,指標不用。
  引用初始化後不能改變,指標可以改變所指的內容。
  不存在指向空值的引用,但是存在指向空值的指標。
  指標可以有多級;引用就一級。
  指正要解引用,引用不用。
  引用沒有const, 但是指標有。
  sizeof結果不同。
  自增的語義不同。

25 int id[sizeof(unsigned long)] 合法嗎?
可以。陣列的大小在編譯的時候就要確認。

26 棧記憶體與文字常量區域
       char str1[] = "abc";
  char str2[] = "abc";
  const char str3[] = "abc";
  const char str4[] = "abc";
  const char *str5 = "abc";
  const char *str6 = "abc";
  char *str7 = "abc";
  char *str8 = "abc";
  cout << ( str1 == str2 ) << endl;//0 分別指向各自的棧記憶體
  cout << ( str3 == str4 ) << endl;//0 分別指向各自的棧記憶體
  cout << ( str5 == str6 ) << endl;//1指向文字常量區地址相同
  cout << ( str7 == str8 ) << endl;//1指向文字常量區地址相同
  結果是:0 0 1 1
  解答:str1,str2,str3,str4是陣列變數,它們有各自的記憶體空間;而str5,str6,str7,str8是指標,它們指向相同的常量區域。

27 虛擬函式 VS 純虛擬函式
  虛擬函式為了過載和多型,在基類中是有定義的,即便定義為空。在子類中可以重寫。
  純虛擬函式在基類中沒有定義,必須在子類中實現。
  多型的基礎是繼承,需要虛擬函式的支援。

28 子類不能繼承父類的函式
  子類繼承父類大部分的資源,不能繼承的有建構函式,解構函式,拷貝建構函式,operator=函式,友元函式。

29 開發中常用的資料結構:
  A:陣列和連結串列:
    陣列大小不能動態定義。連結串列和動態分配大小的。 
    陣列不適應動態增/減的情況,因為大小固定,一旦定義就不能改變。
    連結串列適合動態的增加、刪除資料。
    陣列的隨機訪問快。
    陣列棧中分配; 連結串列在堆中。
  B:二叉樹遍歷:
    先序、中序、後序。

30 const與static的用法
  const:
    修飾類成員變數,成員不可以改。

               修飾函式引數;

               修飾返回值;
    修飾函式,函式不會修改類內的資料成員。不會呼叫非const成員函式。(在函式末尾,預設是const this指標,不能修改成員)
    const函式只能呼叫const函式,非const函式可以呼叫const函式。
  static:
    區域性static變數:區域性靜態變數,處於記憶體中的靜態儲存區;只能初始化一次;作用域是區域性。
    全域性static變數:全域性靜態變數,靜態儲存區;全域性靜態變數的作用局是宣告它的檔案,在檔案之外是不可見的。其實是從
    定義的地方到檔案結尾。

  類的static成員:類的全域性變數,被類的所有獨享共享,包括派生類的物件。按照這種方式int base::var = 10;進行
  初始化,不能在建構函式內初始化,但是可以用const修飾的static資料成員在類內初始化。

  static修飾成員函式,類只有一份,不含this指標。
  static成員變數定義放在cpp檔案中。 const static 可以就地初始化。

31 類的static變數在什麼時候初始化,函式的static變數什麼時候初始化?
         類的靜態成員在類例項化之前就存在了; 函式的static變數在執行此函式時進行例項化(第一次呼叫的時候,只初始化一次)

32 棧溢位的原因:

  棧大小有限制:分過多的陣列; 
  遞迴呼叫層太深;

33 switch引數型別
  可以是:byte short int long bool
  不能是: float double(這種浮點型的不能精確的比較,所以不能) string
  但是在c++ 11裡面, string可以作為switch的條件了。

35 頻繁出現的短小的函式,在c/C++中分別如何實現
  c中用巨集定義; C++ 內聯

36 C++函式傳引數方式

  值傳遞、指標、引用

37 定義巨集注意什麼?
  定義部分的每個形參和整個表示式都必須用括號括起來。

38 .h標頭檔案中的ifndef/define/endif作用
  防止標頭檔案重複包含

39 struct VS class
  struct的成員預設是共有的,而類的成員預設是私有的。
  繼承的的時候,class預設是私有繼承;結構體是共有繼承;
  class還用於定義模板引數,就像typename

40 系統會自動開啟和關閉的三個標準檔案是?

  在C語言中,在程式開始執行時,系統自動開啟3個標準檔案:標準輸入、標準輸出、標準出錯輸出。通常這3個檔案都與終端相聯絡。因此,以前我們所用到的從終端輸入或輸出都不需要開啟終端檔案。系統自定義了3個檔案指標 stdin、stdout、stderr,分別指向終端輸入、終端輸出和標準出錯輸出(也從終端輸出)。

  標準輸入流:stdin

       標準輸出流:stdout

  標準錯誤輸出流:stderr

41 如何判斷浮點數是否是0.5
  fabs(x - 0.5)< DBL_DEPSILON

42 記憶體洩漏? 指標越界和記憶體洩漏,有哪些方法?
  new/delete, new[]/delete, malloc/free 配套使用
  對指標賦值的時候,一定要看原來指向的東西是否需要釋放
  指標指向的東西釋放後,一定要將指標設定為null。

43 TCP、UDP差別
  TCP: 面向連線, 有保障, 效率低, 基於流的,重要資料
  udp:    無連線 無保障 效率高 基於資料報文 不重要的資料

44  #include<file.h> #include "file.h" 
  前者是從標準庫路徑尋找
  後者是從當前工作路徑

45 sizeof
  sizeof計算的是棧中分配的記憶體大小
  A: 類中static的變數,計算static的時候不算在內
  B: 指標大小是4個位元組。
  C: char = 1; int = 4; short in = 2; long int = 4; float = 4; double=8, 
       string = 4, 空類=1(物件在記憶體中都有獨一無二的地址,空類會隱含的加一個位元組)), 單一繼承的空類佔一個位元組;虛繼承涉及的虛指標佔4個位元組
  D:陣列: 如果指定陣列長度,則總位元組數=陣列長度 * sizeof(元素型別),如果沒有指定長度,則按照實際元素個數;如果是字元陣列,則應考慮末尾空字元。
  E: unsigned影響的只是最高位的意義,資料長度不變,sizeof(unsigned int) = 4
  F:對函式取sizeof,在編譯階段會被函式返回值的型別代替。
  G:sizeof不能返回動態陣列的大小。

        H:sizeof不能返回外部陣列的大小,因為sizeof是編譯時常量(函式呼叫的時候,陣列退化成指標)

38 sizeof VS strlen
  sizeof() 返回值型別為size_t(unsigned int) 
  sizeof是運算子,strlen是函式
  sizeof的引數可以是型別,變數或函式。而strlen只能用char*做引數,必須以'\0'結尾
  陣列指標作為sizeof引數會退化為指標,但是傳遞給strlen的無論是陣列還是指標不會退化為指標。
  char *p = "chinaaaa";
  char q[] = "chinaaaa";
  cout << sizeof(p) << endl; 4 // 就是指標
  cout << sizeof(q) << endl; 9
  cout << strlen(p) << endl; 8 
  cout << strlen(q) << endl; 8

  sizeof是編譯時常量,而strlen執行的時才會計算處理,而且是字元個數,不算最後的結尾字元。

39 const用法
  const常量:定義的時候必須初始化。const int a 和 int const a 是一個意思。
  const指標: 常量指標 和 指標常量
  int a;
  const int *p = &a; 常量指標,不能通P改變所值物件的值。但是可以其他方式修改。並且指標
  還可以指向其他的int變數。const int *p 和 int const *p一樣。

  int * const p = &i; 指標常量,p中存放的地址不可以變化,可以通過P改變變數的值,但是指標不能
  再指向其他的變數。

     注意const int *p 和 int const *p一樣

  const引用: 可以繫結常量,也可以繫結變數。不能通過這個const引用來改變繫結物件的值。但是變數本身可以改。
  非const 引用不能與const 物件繫結;但是const 引用可以繫結非const 變數。

40 空指標和懸掛指標
  空指標是等於null的指標; 懸掛指標是delete後沒有置空的野指標。
  A: 空指標可以被delete多次,而野指標多次delete會很不穩定。
  B: 二者都容易導致程式崩潰。

41 C++空類,有哪些成員函式?
  預設建構函式, 解構函式, 賦值建構函式, 賦值函式。
  class Empty
  { Empty(); // 預設建構函式,如果使用者定義建構函式,就沒有這個預設的了。無this指標。
    // 兩種辦法初始化:
      初始化列表:效率高。常量成員變數/引用型別/無預設建構函式的類成員,必須用初始化列表,函式體內賦值
  Empty(const Empty&)     // 拷貝建構函式,直接用物件賦值。必須是引用。注意深拷貝/淺拷貝的問題。
             // 3種情況呼叫拷貝建構函式 :  一個物件初始化另一個物件;
                                函式形參是類物件,呼叫函式的時候;
                                 函式返回值是物件

  ~Empty(); 
  // 解構函式,無參,無返回值,所以不能過載。

  Empty& operator=(const Empty&);
  }

42 所有的函式都設定為虛擬函式?
  不行。 每個虛擬函式的物件要維護虛擬函式表。代價高。

43 共有繼承 受保護繼承 私有繼承
  共有繼承:可以訪問基類中的公有成員,派生類可以訪問公有的和受保護的成員;
  受保護繼承:
  私有繼承:

44 阻止類例項化

         抽象基類 或者 建構函式是private

46 main函式執行之前會執行什麼?執行之後還能執行程式碼嗎?
  全域性物件的建構函式在main函式之前執行。
  用_onexit註冊一個函式,在main執行之後就會呼叫這個函式.

47 函式引數入棧的順?
  從右端往左進入棧的。為了支援可變引數(原理要懂得)。

48 類的static變數的定義和初始化

   static int Sum;//在標頭檔案中宣告靜態資料成員
  int Myclass::Sum=0;//定義並初始化靜態資料成員,在類的外部。

49 多型類中虛擬函式表是compilie-Time 還是 Run-time時建立的
  虛擬函式表是在編譯時就建立了,各個虛擬函式這時被組織成了一個虛擬函式的入口地址的陣列。
  而物件的隱藏成員--虛擬函式表指標是在執行期-也就是建構函式被呼叫時進行初始化的,這是實現多型的的關鍵。、

50 父類寫了一個virtual函式,如果子類覆蓋它函式不加virtual,可以多型嗎?
  可以; 子類可寫,可不寫。

51 子類的空間中,有無父類的virtual函式,或者私有變數?

      答:除了static的,其他的都有。

52 sprintf/strcpy/memcpy
  sprintf: 其他字串或基本型別向字串的轉換功能。是一種格式化。
  strcpy: 操作的是字串,從源字元到目的字串拷貝功能。
  memcpy:記憶體拷貝。記憶體塊內容複製。

53 行內函數在編譯時是否做型別檢查
  行內函數要做型別檢查,這是行內函數比巨集的優勢

54 c/C++的結構體區別:
      c++的結構體和class幾乎一樣。結構體可以繼承,可以有函式,可以實現多型。

          區別是:結構體成員預設是public的,而類是private的;

                         另外class可以作為模板中的一個關鍵字 template<Class T>

                         預設的繼承訪問許可權不同,struct是public的繼承;class 是private的繼承。

 

  c 的結構體不具備面向物件的特徵,有變數,沒有函式,但是可以有函式指標。

55 如何判斷一段程式是C編譯程式還是C++編譯程式編譯的?
  #ifdef __cplusplus
    cout << "c++" << endl;
  #else
    cout << "c";
  #endif

56 C ++ 在c基礎上加了什麼?
  A:包含全部的C語言部分。
  B:面向物件部分,封裝,繼承,多型。
  C:泛型程式設計部分,模板,方便使用。
  D:STL庫。

57 全域性變數和區域性變數
  分配的區域不同: 全域性資料區 vs 棧
  宣告週期不同: 主程式 vs 函式內部
  可見性不同: 全域性 VS 區域性

58 有N個大小不等的自然數(1–N),請將它們由小到大排序.要求程式演算法:時間複雜度為O(n),空間複雜度為O(1)。

#include <iostream>  
#include <algorithm>  
#include <iterator>  
  
void sort(int*, int);  
int main(){  
    int arr[] = {5,9,3,7,4,2,8,6,1,10};  
    int n = sizeof(arr)/sizeof(int);  
    sort(arr, n);  
    std::copy(arr,arr+n,std::ostream_iterator<int>(std::cout, ","));  
    std::cout << std::endl;  
    return 0;  
}  
  
void   sort(int *arr,   int  n)       
{       
    int count = 0;//此資料不是演算法必須,用來計算演算法迴圈次數。  
    int i;  
    int tmp;  
    for(i=0;i<n;++i){  
        while( (i + 1) != arr[i]){  
            tmp = arr[i];  
            arr[i] = arr[tmp-1];  
            arr[tmp-1] = tmp;  
            ++count;  //演算法每迴圈一次加一。  
        }  
    }  
    std::cout << "count = " << count << std::endl; //最後得出的迴圈次數小於等於n。  
}

59 巨集,行內函數,函式 區別

  巨集效率高,編譯時替換,沒有型別檢查,可能有副作用。
  行內函數有型別檢查,效率高,替換,當然也有可能不替換。一般函式短可以用內聯,長的話編譯器可以優化不內聯。
  函式,呼叫過程入棧、出棧,效率低。

60 棧, 堆區別
  申請方式:自動 vs 手動
  響應方式: 只要棧沒有超過最大,不statck overflow, 就能分配成功 ; 堆,遍歷空閒記憶體地址連結串列,找到第一個大於申請空間的節點,從空閒連結串列刪除,並                                  且在這個塊的首地址處記錄分配的大小,以便delete語句正確執行,並且,堆的大小如果大於申請的大小,多餘的部分還會記錄到空閒連結串列。
  申請大小限制:棧的大小有限制; 堆的話比較大。
  效率:棧快, 自動的; 堆慢,容易產生記憶體碎片。
  儲存的內容:在函式呼叫時,先入棧的是函式呼叫的下一條語句的地址,然後從左到右函式入棧,然後是區域性變數
        靜態區域性變數不入棧; 堆的頭部用一個位元組大小存堆的大小。堆中的具體內容程式設計師安排。

61 常引用的作用
  傳遞給函式的資料在函式中不被改變

62 引用與多型的關係?  

        引用和指標可以實現多型。

63 多型作用
  程式碼模組化,擴充套件程式碼模組,實現程式碼重用。

64 隱藏

  隱藏:派生類的函式遮蔽了同名的基類函式:
  派生類函式與基類函式同名,引數不同,無論有無virtual關鍵字,基類函式被隱藏(不是過載)
  派生類函式與基類函式同名,引數相同,基類無virtual, 基類被隱藏。

65 a,b兩個變數,不用 if,else, 不用switch,不用三目表示式。找到最大的那個?

       找最大的:  (a + b + abs(a-b) )  /  2   

       找最小的:  (  a + b - abs(a-b)  )   / 2 

 

66  列印檔名行號
  cout << __FILE__ << " " << __LINE__ << endl;

67 程式在結束的時候,系統會自動析構所有的全域性變數。
       事實上,系統也會析構所有的類的靜態成員變數,就像這些靜態成員也是全域性變數一樣

68:pragma once ,  ifdefine

  #pragma once是編譯器相關的,有的編譯器支援,有的編譯器不支援,具體情況請檢視編譯器API文件,不過現在大部分編譯器都有這個雜注了。
  #ifndef,#define,#endif是C/C++語言中的巨集定義,通過巨集定義避免檔案多次編譯。所以在所有支援C++語言的編譯器上都是有效的,如果寫的程式要跨平臺,最好使用這種方式

69: 函式的const引數構成過載函式
  如果是值傳遞的函式,值的const 和非const 不構成過載。
  如果是引用和指標的可以構成過載。
  非const 可以呼叫const。
  const 不可呼叫非const的。
  呼叫過載函式的時候 會選擇最匹配的那個

70:C++ 全域性變數/常量解析

  編譯單元:編譯成obj檔案,然後link成.exe,編譯主要是看語法錯誤;連結主要是重複定義或者沒有定義。

  宣告與定義:函式或變數在宣告的時候,沒有給實際的實體記憶體空間,它有時候可以保證編譯能通過;

                             當函式或變數定義的時候,它就在記憶體中有了實際的物理空間。宣告可以多次,但是定義只能一次。

      extern 作用: 

                   A: extern "C"  void fun(int a, int b),在編譯fun這個函式時按照C的規則區翻譯相應的函式名,而不是c++. 

                   B: extern作用:你現在編譯的檔案中,有一個變數或函式雖然在本檔案中沒有定義,但是在別的檔案中定義的全域性變數。

          在標頭檔案中: extern int g_int 作用是宣告函式或全域性變數的作用範圍的關鍵字,其宣告的函式或變數

                    可以在本模組或其他模組中使用,記住,這是宣告不是定義!也就是說b模組引用a 中定義的全域性變數或

                    函式,它包含a的標頭檔案,就可以在編譯階段通過,b模組在連結的時候從模組a生成的目的碼中找到
                    此函式或變數。

                        錯誤的做法:在stdafx.h中定義int globalINt = 0; 然後在其他.cpp檔案中extern int globalINt ;總是提示重複定義。 原因是:.h檔案會被包含多次,相當於

                                             定義多次。

                         正確的做法: 不在.h檔案中定義, 而是在.cpp檔案中定義。在.h檔案中宣告。這樣就是多次宣告。 連結的

             時候會找到這個變數的實體地址。

                         

       注意:  int a ;   // 這個也是定義,雖然沒有給賦值。extern int a 才是宣告
                                 extern const int globalINt = 1;    //當這個給它賦值了,也可以看做是定義。
                                       只有當extern宣告位於函式外部時,才可以含有初始化式。

                         

        問題1: 一個原始檔定義了 char a[6];  另外一個檔案用下列語句進行了宣告: extern char *a, 這樣可以嗎?
        答案:不可以。因為指向型別T的指標並不等價於型別T陣列。提示我們:宣告和定義要嚴格一樣的格式。

                            

        問題2: 如果用extern函式的方式引用全域性函式,當函式原型修改後,比如加了個引數,編譯居然不報告錯。
        解決方案:通常提供函式放在自己的XX.h檔案中宣告和這個函式,其他的呼叫方include該標頭檔案,從而省去
             extern這一步,可以避免上述錯。

  

71: extern "C "
  #ifndef XXXXXXX
  #define XXXXXXX //避免重複包含標頭檔案, #pragma once 可以實現同樣的功能

  #ifdef __CPLUSCPLUS
    extern "C"{
  #endif

    #ifdef __cplusplus
  }
  #endif
  # endif

72 C和 c++ 互相呼叫

  因為C++ 過載,而C不過載,函式名編譯的結果都不一樣。 
  如果C++ 直接呼叫C的函式,因為二者編譯的不同,就會失敗。

  C++ 呼叫C: 比在一個.h檔案中有個 foo(int),其實現是在 .c中,
        當C++ 包含這個.h檔案的時候就要用extern "C", 否則編譯器編譯的不一樣,根本呼叫不到。
        c++ 呼叫一個C語言編寫的DLL時,當包括.DLL的標頭檔案或宣告介面函式時,應加入extern "C"

        c呼叫C++ : 非類成員函式的話,就用extern “C”;
                              如果要呼叫成員函式(虛擬函式,過載函式),可以提供封裝函式,封裝函式內部呼叫實際的東西。

73 字元陣列和字串          

        注意最後一個'\0'.

  char str[10] =  {'a','b','c'}  // 不是以 '\0'結尾
  char *p = "abc";   // 是以'\0'結尾

74 static 檔案作用域的問題   

  當同時編譯多個檔案時,所有未加static的全域性變數和函式都是全域性可見的(其他檔案加上的extern就行)。
  用static修飾全域性變數,可見性就是本檔案,這樣可以在不同的原始檔中定義同名的函式和變數,不擔心衝突。
  static函式: 主要就是為了隱藏(只在本檔案中可以看到)。
  static變數: 一是隱藏; 另外是保持變數內容的持久。儲存在靜態區域的變數會在程式剛剛執行時就完成初始化,
                         也是唯一的一次初始化(初始化只是一次,但是可以改變值)
  static 還有一個作用:預設初始化為0,其實全域性變數也是這樣的。

75 位元組對齊,類物件佔據記憶體
  位元組對齊好處:為了提高存取效率,讀取int型別的時候,一次讀取就OK。否則要高低位元組拼接才行。

  位元組對齊:有助於加快計算機的取數速度,否則就得多花指令週期了。寬度為2的基本資料型別都位於能被2整除的地址上,
        4的位於能被4整除的地址上。
   

  struct S2
  {
   int i;
   char c;
  };
  規律:i 的地址低, C的地址高,結構體是往高地址擴充套件的。
  A:結構體變數首地址能被其最寬基本型別成員的大小整除。(首地址能整除)
  B:結構體每個成員相對於結構體首地址的偏移都是成員大小的整數倍,如有需要,會在成員之間加上填充位元組。(偏移量能整除)
  C:  結構體總大小為結構體最寬基本型別成員的整數倍,如有需要,會在最後一個成員之後加上填充位元組。(結尾填充)
  D:如果成員是複合型別,比如另外一個結構體,應該考慮子成員。

  但是:還有一個影響sizeof的重要引數還沒有提及:pack指令。 
  #paragma pack(n) // n是位元組對齊數,取值是1,2,4,8,16; 預設是8
        如果這個值比結構體成員的sizeof小, 那麼該成員的偏移量應該以此為準: 也就是結構體成員的偏移量取二者最小值。

  下面程式碼演示了:單獨對一個結構體的位元組對齊方式進行設定。 
  #pragma pack(push) // 將當前pack設定壓棧儲存
  #pragma pack(2)// 必須在結構體定義之前使用
  struct S1
  {
  char c;
  int i;
  };
  struct S3
  {
  char c1;
  S1 s;
  char c2
  };

  pack影響的的是偏移量。

  注意:空結構體,空物件的佔據空間是1個位元組。

  對於聯合體: int從首地址開始佔據4個自己; char從首地址開始佔據2個位元組,有重合。

  #include <stdio.h>
  union
  {
    int i;
    char x[2];
  }a;

  void main()
  {
    a.x[0] =10; 
    a.x[1] =1;
    printf("%d",a.i);
  }

76  程序之間通訊
  訊息佇列:存放在核心中,是連結串列的形式。
  匿名管道:CreatePipe(); 只能本地使用。管道是半雙工的。只能是父子程序之間通訊
  命名管道:也是半雙工,但是可在無親緣關係的程序之間通訊。可用於網路通訊,可以通過名稱引用;支援多客戶端連結,雙向通訊;
  共享記憶體(記憶體對映檔案):CreateFileMapping .建立記憶體對映檔案,是多個程序對同一塊實體記憶體的對映。(因為是共享記憶體的方式,讀寫之間有衝突)
               共享記憶體的效率是最高的,因為共享一塊都能看見的記憶體,不用多份拷貝,而且訪問共享記憶體相當於記憶體中區域一樣,
               不需要通過系統呼叫或者切入核心來完成。但是需要位元組提供同步措施。一般用訊號量解決讀寫衝突。
       socket: 可以跨越機器

77 型別轉換
  隱式型別轉換:int 型別 和float型別相加,會轉成float
  顯式型別轉換:static_cast /   dynamic_cast /  const_cast / reinterpret_cast
   static_cast: static_cast <type-id> (expression)主要用於非多型型別之間的轉化:
          用於類層次結構中,基類和子類之間指標和引用的轉換;
          當進行上行轉換,也就是把子類的指標或引用轉換成父類表示,這種轉換是安全的;
          當進行下行轉換,也就是把父類的指標或引用轉換成子類表示,這種轉換是不安全的,也需要程式設計師來保證;
          基本資料型別之間的轉換,如把int轉換成char,把int轉換成enum等等,這種轉換的安全性需要程式設計師來保證;
           把void指標轉換成目標型別的指標,是極其不安全的;

   dynamic_cast: dynamic_cast <type-id> (expression),因為是動態,主要是考慮多型的問題。
          type-id必須是類的指標,類的引用或者是void*, 如果是指標,expression也是指標;如果是引用,expression也是
          引用。主要用於類層次之間的上行/下行轉換,以及類之間的交叉轉。在類上行轉換的時候和static_cast一樣;下行
          轉換的時候,比static 安全。 多型型別之間轉換,主要使用dynamic_cast, 因為型別提供了執行時資訊。 
          class b: Public A
          {};

          B *pb = new B;
          A *Pa = dynamic_cast<A*>(pb); //安全的、


          下面是轉換成Void*,A和B沒有關係,但是都有虛擬函式
          void *pV = dynamic_cast<void *>(pA); // pV points to an object of A
          pV = dynamic_cast<void *>(pB); // pV points to an object of B

          // 因為向下轉換是不安全的,所以dynimac做檢查。這就是動態比靜態好的原因。
          如果expression是type-id的基類,使用dynamic_cast進行轉換時,在執行時就會檢查expression是否真正的指向一個type-id型別的物件,如

                                     果是,則能進行正確的轉換,獲得對應的值;否則返回NULL,如果是引用,則在執行時就會丟擲異常

    const_cast: const_cast <type-id> (expression)
            const_cast用來將型別的const、volatile和__unaligned屬性移除。常量指標被轉換成非常量指標,並且仍然指向原來的物件;
            常量引用被轉換成非常量引用,並且仍然引用原來的物件

    reinterpret_cast: reinterpret_cast <type-id> (expression):
          允許將任何指標型別轉換為其它的指標型別;聽起來很強大,但是也很不靠譜。它主要用於將一種資料型別從一種型別
          轉換為另一種型別。它可以將一個指標轉換成一個整數,也可以將一個整數轉換成一個指標,在實際開發中,
          先把一個指標轉換成一個整數,在把該整數轉換成原型別的指標,還可以得到原來的指標值;特別是開闢了系統全域性
          的記憶體空間,需要在多個應用程式之間使用時,需要彼此共享,傳遞這個記憶體空間的指標時,
          就可以將指標轉換成整數值,得到以後,再將整數值轉換成指標,進行對應的操作。

static_cast和reinterpret_cast的區別主要在於多重繼承,比如:

class A {
public:
int m_a;
};

class B {
public:
int m_b;
};

class C : public A, public B {};
那麼對於以下程式碼:

C c;
printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));
前兩個的輸出值是相同的,最後一個則會在原基礎上偏移4個位元組,這是因為static_cast計算了父子類指標轉換的偏移量,並將之轉換到正確的地址(c裡面有m_a,m_b,轉換為B*指標後指到m_b處),而reinterpret_cast卻不會做這一層轉換。
因此, 你需要謹慎使用 reinterpret_cast.

注意:reinterpret_cast, 操作符修改了運算元型別,但僅僅是重新解釋了給出的物件的位元模型而沒有進行二進位制轉換。

78 虛擬函式表
  注意: 如果沒有虛擬函式,那麼就沒有這個虛擬函式表的指標。虛擬函式表的指標(佔4位元組大小)影響sizeof的結果。

  v-Table: 虛擬函式的地址表。在有虛擬函式的類例項中,這個表被分配在了這個例項的記憶體中,當用父型別指標操作
      一個子類的時候,這張虛擬函式表像一個地圖一樣,指明瞭實際呼叫的函式。
    C++ 編譯器保證:虛擬函式表的指標存在於物件例項中最前面的位置。

  class Base {
  public:
  virtual void f() { cout << "Base::f" << endl; }
  virtual void g() { cout << "Base::g" << endl; }
  virtual void h() { cout << "Base::h" << endl; }

  };

  typedef void(*Fun)(void);

  Base base;
  Fun pFun = NULL;
  cout << "虛擬函式表地址" << (int*)(&base)<< endl; 
  cout << "虛擬函式表第一個函式地址:" << (int*)*(int*)(&base) << endl;
  pFun = (Fun)*((int*)*(int*)(&base));

  pFun();

  虛擬函式表最後有一個結尾標誌。

  一般繼承(無虛擬函式覆蓋):
  總結:A: 虛擬函式表按照其宣告的順序放在表中。
             B: 父類的虛擬函式在子類的虛擬函式前面。

  一般繼承(有虛擬函式覆蓋):
  總結:子類的覆蓋的函式放在原來虛擬函式的位置。
              沒有被覆蓋的函式依舊。

  多重繼承(無函式覆蓋):情況比較複雜(多張虛擬函式表,所以也有多個指向不同函式表的指標)
  總結: 每個父類都有自己的虛表;子類的成員函式放到了第一個父類的虛表中。(所謂的第一個父類是按照宣告順序來判斷的。)  

        多重繼承(有虛擬函式覆蓋) :
  多個父類虛擬函式表中的被覆蓋的函式都會替換成子類的函式指標。這樣我們就可以任一靜態型別的父類來指向子類。

  安全線: 用父類的指標訪問子類物件的非覆蓋函式,會報錯。
  虛擬函式如果是private的,但是可以通過虛擬函式表來訪問的到的。

79 多重繼承的二義性
  多個父類中有相同名稱的變數或者函式。子類中要指明是哪個父類的。
  子類中同名的函式會覆蓋父類的。

        子類如果和父類函式同名但是引數不同,子類會覆蓋父類,但是用using (using Base::fun;)是可以實現父類和子類過載的

80 菱形繼承
  N是基類(包含a成員和函式display),A和B分別繼承N,C繼承A和B。 
  A 和B 中都有a的儲存空間。可以通過A和B做限定: c.A::a 和 c.B::display();

81 為什麼用 exit()函式
  是歷史原因,雖然現在大多數平臺下,直接在 main() 函式裡面 return 可以退出程式。但是在某些平臺下,在 main() 函式裡面 return 會導致程式永遠不退出(因為程式碼已經執行完畢,程式卻還沒有收到要退出的指令)。換句話說,為了相容性考慮,在特定的平臺下,程式最後一行必須使用 exit() 才能正常退出,這是 exit() 存在的重要價值。

exit(1)表示程序非正常退出. 返回 1;
exit(0)表示程序正常退出. 返回 0.

在unix下的多程序中,n是該程序返回給父程序的值

在main函式中exit(0)等價於return 0.

82 廣義表
非線性的資料結構,是線性表的一種推廣。廣義表中放鬆對錶元素的原子限制,容許它們
具有自身的結構。人工智慧領域的表處理語言LISP語言中,廣義表是一種基本的資料結構,

廣義表是n(n≥0)個元素a1,a2,…,ai,…,an的有限序列。
其中:
①ai 或者是原子或者是一個廣義表。
②廣義表通常記作:
Ls=( a1,a2,…,ai,…,an)。
③Ls是廣義表的名字,n為它的長度。
④若ai是廣義表,則稱它為Ls的子表。
注意:
①廣義表通常用圓括號括起來,用逗號分隔其中的元素。
②為了區分原子和廣義表,書寫時用大寫字母表示廣義表,用小寫字母表示原子。
③若廣義表Ls非空(n≥1),則al是LS的表頭,其餘元素組成的表(a1,a2,…,an)稱為Ls的表尾。
④廣義表是遞迴定義的[1]

① E=()
  E是一個空表,其長度為0。
② L=(a,b)
  L是長度為2的廣義表,它的兩個元素都是原子,因此它是一個線性表
③ A=(x,L)=(x,(a,b))
  A是長度為2的廣義表,第一個元素是原子x,第二個元素是子表L。

一個表的"深度"是指表展開後所含括號的層數。

((a,b,(c,(d,e),f)),g) 的深度是4。

廣義表的儲存結構:
頭尾表示法: 表中的資料可能是列表,也可能是單元素,所以節點的結構有兩種:一種是表節點,表示列表;另外一種
是元素節點,用來表示單元素。
A:表節點:包括一個指向表頭的指標和指向表尾的指標。
B:元素節點:
C:還需要一個標誌位,0表示元素;1表示表節點。

孩子兄弟表示法:兩種節點形式,一種是有孩子節點,表示列表;另外一種是無孩子節點,用來表示單元素。
在有孩子節點中包括一個指向第一個孩子的指標,和一個指向兄弟節點的指標
無孩子節點中包括一個指向兄弟的指標和該元素的元素值。
為了能區分這兩類節點,在節點中還要設定一個標誌域:標誌1表示有孩子節點,標誌0,則
表示無孩子節點。

83 廣義表((a,b),c,d)表頭和表尾分別是?
頭(a,b) // 第一個
表尾(c,d) // 除了第一個剩下的加上括號就是表尾。

84 堆和棧區別

  A 管理方式: 棧:編譯器管理; 堆:程式釋放,容易洩露。
  B 空間大小: 棧:預設是1M, 堆:可以看做沒有限制。 
  C 是否產生碎片:棧:沒有碎片。 堆:產生碎片。
  D 生長方向:棧:向記憶體地址減小的方向; 堆: 地址增大的方向。
  E 分配方式: 棧:有靜態分配 堆:都是動態分配的。
  F 分配效率: 棧:暫存器存了棧的地址,壓棧/出棧有專門的指令,棧的效率很高。 
        堆:分配、管理記憶體的演算法複雜,空閒鏈塊查詢,合併,用了後,要更新
    空閒鏈塊的記錄。效率低。 如果碎片太多,可能還要像OS申請更多記憶體。