C++常考面試題


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申請更多內存。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM