C++複習考點

1.聲明與定義的區別

int a定義變量須要爲變量在內存中分配存儲空間c++

extern int a聲明不須要分配存儲空間程序員

聲明的目的是爲了在定義以前使用,若是不須要在定義以前使用,那麼就沒有單獨聲明的必要算法

2.static來聲明一個變量的做用

局部變量用static聲明,變量由動態存儲方式改變爲靜態存儲方式。爲該變量分配的存儲空間會存在於整個程序運行過程當中。靜態局部變量做用域侷限於本函數內。數組

外部變量用static聲明,使變量局部化(局部於本文件),但仍爲靜態存儲方式。靜態外部變量做用域侷限於本文件內安全

static靜態變量雖然和整個程序共生存期,可是做用域仍是須要看其定義的地方,當你在某個函數中定義一個變量,該變量做用域僅在該函數中。但你在文件開頭定義一個全局變量,該變量做用域僅在該文件中。因此當你聲明一個變量調用另外一個文件靜態變量,編譯器會報錯的。數據結構

3.全局變量和局部變量在內存中是否有區別?若是有,是什麼區別?

全局變量儲存在靜態存儲區,局部變量存在於堆棧中。動態申請數據存在於(堆)中。函數

4.局部變量可否和全局變量重名?

能,局部會屏蔽全局。要用全局變量,須要使用"::"post

5.全局變量可不能夠定義在可被多個.C文件包含的頭文件中?爲何?

能夠,在不一樣的C文件中以static形式來聲明同名全局變量。前提是其中只能有一個C文件中對此變量賦初值,此時鏈接不會出錯。this

變量的定義只能出現一次,不然會致使重複定義。但卻能夠聲明屢次。全局變量定義在頭文件中。當該頭文件被多個c文件包含的話,就會致使重複定義。因此全局變量不能夠定義在頭文件中。spa

6.static全局變量與普通的全局變量有什麼區別?static局部變量和普通局部變量有什麼區別?static函數與普通函數有什麼區別?

  • static全局變量:靜態全局變量限制了其做用域,只在定義該變量的源文件內有效。在同一源程序的其它源文件中不能使用它。static全局變量只初始化一次,防止在其餘文件單元中被引用。

  • 普通全局變量:非靜態全局變量的做用域是整個源程序,當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。

  • static局部變量:變量由動態存儲方式改變爲靜態存儲方式。static局部變量只被初始化一次,下一次依據上一次結果值。

  • 普通局部變量:仍是動態存儲方式,存儲在堆棧中。

  • static函數:做用域僅在本文件中,只在當前源文件中使用的函數應該說明爲內部函數(static),內部函數應該在當前源文件中說明和定義。static函數在內存中只有一份。

  • 普通函數:可在當前源文件之外使用,需應該在一個頭文件中說明,要使用這些函數的源文件要包含這個頭文件。普通函數在每一個被調用中維持一份拷貝。

7.extern 和 static 的區別,什麼狀況用前者什麼狀況用後者

auto自動變量:代表變量自動具備本地範圍,在離開做用域,不管塊做用域,文件做用域仍是函數做用域,變量都會被程序隱藏或自動釋放。而後等你從新進入該做用域,變量又從新被定義和調用。使用auto變量優點是無需考慮變量是否被釋放。

static靜態變量:簡單說就是在函數等調用結束後,該變量也不會被釋放,保存的值還保留。即它的生存期是永久的,直到程序運行結束,系統纔會釋放,但也無需手動釋放。

extern外部變量:它屬於變量聲明,extern int a和int a的區別就是,前者告訴編譯器,有一個int類型的變量a定義在其餘地方,若是有調用請去其餘文件中查找定義。

關於extern變量聲明使用,例如一個工程中:

Test1.cpp文件開頭定義了int i =10; //定義了一個全局變量

Test2.cpp文件中定義:extern int i; //聲明在另外一個編譯單元有i變量

注意:不能夠寫成extern int i =10,由於變量已經存在,不能夠在聲明時候賦初始值。

8.x=x+1,x+=1,x++哪一個效率高

x++ > x+=1 > x=x+1

  • x++:讀取x的值->x自增1
  • x+=1:讀取右x的地址->x值+1->將獲得的值傳回給x(x地址已經讀出)
  • x=x+1:讀取右x的地址->x值+1->讀取左x的地址(並不知道左右是同一個x,因此要讀取兩遍)->將右值傳給左值

9.const 和#define 的優缺點

使用const關鍵字來聲明變量,代表,內存被初始化後,程序便不能再對它進行修改。 在默認的狀況下,全局變量的連接性爲外部的,但const全局變量的連接性爲內部的。也就是說,在C++看來,全局const定義就像使用了static說明符同樣。

const int Months = 12; 此時,應該注意的是應該在聲明中對const進行初始化,咱們應該避免以下的寫法: const int Months; Months = 12;

  • define由預處理程序處理,const由編譯程序處理。
  • #define不份內存,由於它是預編譯指令,編譯前進行了宏替換。
  • const定義常量是有數據類型的,這樣const定義的常量編譯器能夠對其進行數據靜態類型安全檢查,而#define宏定義的常量卻只是進行簡單的字符替換,沒有類型安全檢查,且有時還會產生邊際效應。
  • 有些調試程序可對const進行調試,但不對#define進行調試
  • const在編譯期間會計算其值,而define不會 -當定義局部變量時,const做用域僅限於定義局部變量的函數體內。但用#define時其做用域不只限於定義局部變量的函數體內,而是從定義點到整個程序的結束點。但也能夠用#undef取消其定義從而限定其做用域範圍。只用const定義常量,並不能起到其強大的做用。const還可修飾函數形式參數、返回值和類的成員函數等。從而提升函數的健壯性。由於const修飾的東西能受到c/c++的靜態類型安全檢查機制的強制保護,防止意外的修改。

10.strcpy和memcpy的區別

strcpy提供了字符串的複製。即strcpy只用於字符串複製,而且它不只複製字符串內容以外,還會複製字符串的結束符。 strcpy函數的原型是:char* strcpy(char* dest, const char* src);

strcpy的風險:(strcpy自己沒有什麼風險,風險來源於傳遞進去的兩個參數)

一、內存不夠:strcpy(x,y),字符串y比x大的話,就越界了

二、沒有結束符

三、拷貝自身

memcpy提供了通常內存的複製。即memcpy對於須要複製的內容沒有限制,所以用途更廣。

char *strcpy(char * dest, const char * src) // 實現src到dest的複製 {
    if ((src == NULL) || (dest == NULL)) { //判斷參數src和dest的有效性
        return NULL;
    }
    char *strdest = dest; //保存目標字符串的首地址
    while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的內容複製到dest下
    return strdest;
}
void *memcpy(void *memTo, const void *memFrom, size_t size) {
    if ((memTo == NULL) || (memFrom == NULL)) {//memTo和memFrom必須有效
        return NULL;
    }
    char *tempFrom = (char *)memFrom; //保存memFrom首地址
    char *tempTo = (char *)memTo; //保存memTo首地址
    while (size -- > 0) { //循環size次,複製memFrom的值到memTo中
        *tempTo++ = *tempFrom++ ;  
    }
    return memTo;
}
複製代碼

strcpy和memcpy主要有如下3方面的區別。

一、複製的內容不一樣。strcpy只能複製字符串,而memcpy能夠複製任意內容,例如字符數組、整型、結構體、類等。

二、複製的方法不一樣。strcpy不須要指定長度,它遇到被複制字符的串結束符"\0"才結束,因此容易溢出。memcpy則是根據其第3個參數決定複製的長度。

三、用途不一樣。一般在複製字符串時用strcpy,而須要複製其餘類型數據時則通常用memcpy

11.size_t

size_t類型是一個類型定義,一般將一些無符號的整形定義爲size_t,好比說unsigned int或者unsigned long,甚至unsigned long long。每個標準C實現應該選擇足夠大的無符號整形來表明該平臺上最大可能出現的對象大小。

size_t的定義在<stddef.h>, <stdio.h>,<stdlib.h>,<string.h>,<time.h>和<wchar.h>這些標準C頭文件中,也出如今相應的C++頭文件, 等等中,在使用size_t以前應該頭文件中至少包含一個這樣的頭文件。

包含以上任何C頭文件(由C或C++編譯的程序)代表將size_t做爲全局關鍵字。

根據定義,size_t是sizeof關鍵字(注:sizeof是關鍵字,並不是運算符)運算結果的類型。因此,應當經過適當的方式聲明n來完成賦值: n = sizeof(thing);

參數中帶有size_t的函數一般會含有局部變量用來對數組的大小或者索引進行計算,在這種狀況下,size_t是個不錯的選擇。適當地使用size_t還會使你的代碼變得如同自帶文檔。當你看到一個對象聲明爲size_t類型,你立刻就知道它表明字節大小或數組索引,而不是錯誤代碼或者是一個普通的算術值。

12.new 和malloc 的區別

相同點:均可用於申請動態內存和釋放內存

不一樣點:

  • 申請的內存所在位置不一樣:new操做符從堆上爲對象動態分配內存,malloc函數從自由存儲區上爲對象動態分配內存。
  • 內存分配失敗時的返回值不一樣:new內存分配失敗時,會拋出bac_alloc異常,它不會返回NULL;malloc分配內存失敗時返回NULL。 在使用C語言時,咱們習慣在malloc分配內存後判斷分配是否成功:
  • 操做對象有所不一樣:malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。對於非內部數據類的對象而言,光用maloc/free 沒法知足動態對象的要求。對象在建立的同時要自動執行構造函數,對象消亡以前要自動執行析構函數。因爲malloc/free 是庫函數而不是運算符,不在編譯器控制權限以內,不可以把執行構造函數和析構函數的任務強加malloc/free。
  • 用法不一樣:
    • 函數malloc 的原型以下: void * malloc(size_t size)

      用malloc 申請一塊長度爲length 的整數類型的內存,程序以下: int *p = (int *) malloc(sizeof(int) * length)

      一、malloc 返回值的類型是void *,因此在調用malloc 時要顯式地進行類型轉換,將void * 轉換成所須要的指針類型。

      二、 malloc 函數自己並不識別要申請的內存是什麼類型,它只關心內存的總字節數。

    • 函數free 的原型以下: void free( void * memblock )

      爲何free 函數不象malloc函數那樣複雜呢?這是由於指針p的類型以及它所指的內存的容量事先都是知道的,語句free(p)能正確地釋放內存。若是p 是NULL 指針,那麼free對p 不管操做多少次都不會出問題。若是p 不是NULL 指針,那麼free 對p連續操做兩次就會致使程序運行錯誤。

    • 運算符new 使用起來要比函數malloc 簡單得多,例如:

      int *p1 = (int *)malloc(sizeof(int) * length)

      int *p2 = new int[length]

      這是由於new 內置了sizeof、類型轉換和類型安全檢查功能。對於非內部數據類型的對象而言,new 在建立動態對象的同時完成了初始化工做。若是對象有多個構造函數,那麼new 的語句也能夠有多種形式。

      若是用new 建立對象數組,那麼只能使用對象的無參數構造函數。例如:

      Obj *objects = new Obj[100]// 建立100 個動態對象

      不能寫成

      Obj *objects = new Obj[100](1) // 建立100 個動態對象的同時賦初值1

    • 在用delete 釋放對象數組時,留意不要丟了符號‘[]’。例如:

      delete []objects // 正確的用法

      delete objects;// 錯誤的用法

      後者至關於delete objects[0],漏掉了另外99 個對象。

綜上:

一、new自動計算須要分配的空間,而malloc須要手工計算字節數

二、new是類型安全的,而malloc不是,好比:

int* p = new float[2] // 編譯時指出錯誤

int* p = malloc(2*sizeof(float)) // 編譯時沒法指出錯誤

new operator 由兩步構成,分別是 operator new 和 construct

三、operator new對應於malloc,但operator new能夠重載,能夠自定義內存分配策略,甚至不作內存分配,甚至分配到非內存設備上。而malloc無能爲力

四、new將調用constructor,而malloc不能;delete將調用destructor,而free不能。

五、malloc/free要庫文件支持,new/delete則不要。

13.C++中的new/delete與operator new/operator delete

new operator/delete operator就是new和delete操做符,而operator new/operator delete是函數。

new operator:

(1)調用operator new分配足夠的空間,並調用相關對象的構造函數

(2)不能夠被重載

operator new:

(1)只分配所要求的空間,不調用相關對象的構造函數。當沒法知足所要求分配的空間時,則

->若是有new_handler,則調用new_handler,不然
->若是沒要求不拋出異常(以nothrow參數表達),則執行bad_alloc異常,不然
->返回0
複製代碼

(2)能夠被重載

(3)重載時,返回類型必須聲明爲void*

(4)重載時,第一個參數類型必須爲表達要求分配空間的大小(字節),類型爲size_t

(5)重載時,能夠帶其它參數

14.C++的內存分配

在C++中,內存分紅5個區,他們分別是:

  • 堆:就是那些由new分配的內存塊,他們的釋放編譯器不去管,由咱們的應用程序去控制,通常一個new就要對應一個delete。若是程序員沒有釋放掉,那麼在程序結束後,操做系統會自動回收。

  • 棧:就是那些由編譯器在須要的時候分配,在不須要的時候自動清除的變量的存儲區。裏面的變量一般是局部變量、函數參數等。

  • 自由存儲區:就是那些由malloc等分配的內存塊,他和堆是十分類似的,不過它是用free來結束本身的生命的。

  • 全局/靜態存儲區:全局變量和靜態變量被分配到同一塊內存中,在之前的C++堆棧中,全局變量又分爲初始化的和未初始化的,在C++裏面沒有這個區分了,他們共同佔用同一塊內存區。

  • 常量存儲區:常量存儲區,這是一塊比較特殊的存儲區,他們裏面存放的是常量,不容許修改(固然,你要經過非正當手段也能夠修改,並且方法不少,取地址修改)

15.堆和棧的區別

  • 管理方式不一樣

    對於棧來說,是由編譯器自動管理,無需咱們手工控制;對於堆來講,釋放工做由程序員控制,容易產生memory leak

  • 空間大小不一樣

    通常來說在32位系統下,堆內存能夠達到4G的空間,從這個角度來看堆內存幾乎是沒有什麼限制的。可是對於棧來說,通常都是有必定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M。(這個值能夠經過編譯器修改)

  • 可否產生碎片不一樣

    對於堆來說,頻繁的new/delete勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降。對於棧來說,則不會存在這個問題,由於棧是先進後出的隊列,他們是如此的一一對應,以致於永遠都不可能有一個內存塊從棧中間彈出,在他彈出以前,在他上面的後進的棧內容已經被彈出,詳細的能夠參考數據結構。

  • 生長方向不一樣

    對於堆來說,生長方向是向上的,也就是向着內存地址增長的方向;對於棧來說,它的生長方向是向下的,是向着內存地址減少的方向增加。

  • 分配方式不一樣

    堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,好比局部變量的分配。動態分配由 malloc 函數進行分配,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行釋放,無需咱們手工實現。

  • 分配效率不一樣

    棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是 C/C++ 函數庫提供的,它的機制是很複雜的,例如爲了分配一塊內存,庫函數會按照必定的算法(具體的算法能夠參考數據結構/操做系統)在堆內存中搜索可用的足夠大小的空間,若是沒有足夠大小的空間(多是因爲內存碎片太多),就有可能調用系統功能去增長程序數據段的內存空間,這樣就有機會分到足夠大小的內存,而後進行返回。顯然,堆的效率比棧要低得多。

16. 構造函數和析構函數可不能夠爲虛函數,爲何?

構造函數不能爲虛函數,而析構函數能夠且經常是虛函數。

  • 構造函數不能爲虛函數

    1)從存儲空間角度:

    虛函數對應一個虛函數表vtable,這個vtable實際上是存儲在對象的內存空間的。可是,若是構造函數是虛的,就須要經過vtable來調用,但是對象尚未實例化,也就是內存空間尚未,沒法找到vtable,因此構造函數不能是虛函數。

    即vtable是在構造函數調用後才創建,於是構造函數不可能成爲虛函數。

    2)從使用角度:

    虛函數主要用於在信息不全的狀況下,能使重載的函數獲得對應的調用。構造函數自己就是要初始化實例,那使用虛函數也沒有實際意義,因此構造函數沒有必要是虛函數。

    虛函數的做用在於經過父類的指針或者引用來調用它的時候可以變成調用子類的那個成員函數。而構造函數是在建立對象時自動調用的,不可能經過父類的指針或者引用去調用,所以也就規定構造函數不能是虛函數。

  • 析構函數能夠是虛函數,且經常如此

    這個就好理解了,由於此時vtable已經初始化了;何況咱們一般經過基類的指針來銷燬對象,若是析構函數不爲虛的話,就不能正確識別對象類型,從而不能正確銷燬對象。

    在類的繼承中,若是有基類指針指向派生類,那麼用基類指針delete時,若是不定義成虛函數,派生類中派生的那部分沒法析構。在類的繼承體系中,基類的析構函數不聲明爲虛函數容易形成內存泄漏。因此若是你設計必定類多是基類的話,必需要聲明其爲虛函數。

17.如何限制一個類對象只能在堆(棧)上分配空間

在C++中,類的對象創建分爲兩種

一種是靜態創建,如A a

另外一種是動態創建,如A* ptr=new A

這兩種方式是有區別的:

靜態創建類對象: 是由編譯器爲對象在棧空間中分配內存,是經過直接移動棧頂指針,挪出適當的空間,而後在這片內存空間上調用構造函數造成一個棧對象。使用這種方法,直接調用類的構造函數。

動態創建類對象: 是使用new運算符將對象創建在堆空間中。這個過程分爲兩步,第一步是執行operator new()函數,在堆空間中搜索合適的內存並進行分配;第二步是調用構造函數構造對象,初始化這片內存空間。這種方法,間接調用類的構造函數。

  • 只能在堆上分配類對象——就是不能靜態創建類對象,即不能直接調用類的構造函數。 當對象創建在棧上面時,是由編譯器分配內存空間的,調用構造函數來構造棧對象。當對象使用完後,編譯器會調用析構函數來釋放棧對象所佔的空間。編譯器管理了對象的整個生命週期。若是編譯器沒法調用類的析構函數,狀況會是怎樣的呢?好比,類的析構函數是私有的,編譯器沒法調用析構函數來釋放內存。因此,編譯器在爲類對象分配棧空間時,會先檢查類的析構函數的訪問性,其實不光是析構函數,只要是非靜態的函數,編譯器都會進行檢查。若是類的析構函數是私有的,則編譯器不會在棧空間上爲類對象分配內存。

    所以,將析構函數設爲私有,類對象就沒法創建在棧上了。

    代碼以下:

    class A {  
        public:  
            A(){}  
            void destory(){delete this;}  
        private:  
            ~A(){}  
    };  
    複製代碼

    試着使用A a;來創建對象,編譯報錯,提示析構函數沒法訪問。這樣就只能使用new操做符來創建對象,構造函數是公有的,能夠直接調用。類中必須提供一個destory函數,來進行內存空間的釋放。類對象使用完成後,必須調用destory函數。

    上述方法的缺點:

    • 沒法解決繼承問題。

      若是A做爲其它類的基類,則析構函數一般要設爲virtual,而後在子類重寫,以實現多態。 所以析構函數不能設爲private。

      還好C++提供了第三種訪問控制,protected。 將析構函數設爲protected能夠有效解決這個問題,類外沒法訪問protected成員,子類則能夠訪問。

    • 類的使用很不方便

      使用new創建對象,卻使用destory函數釋放對象,而不是使用delete。 (使用delete會報錯,由於delete對象的指針,會調用對象的析構函數,而析構函數類外不可訪問。這種使用方式比較怪異。)

    爲了統一,能夠將構造函數設爲protected,而後提供一個public的static函數來完成構造,這樣不使用new,而是使用一個函數來構造,使用一個函數來析構。

    代碼以下,相似於單例模式:

    class A {  
        protected:  
            A(){}  
            ~A(){}  
        public:  
            static A* create() {  
                return new A();  
            }  
            void destory() {  
                delete this;  
            }  
    }; 
    複製代碼

    這樣,調用create()函數在堆上建立類A對象,調用destory()函數釋放內存。

  • 只能在棧上分配類對象

    只有使用new運算符,對象纔會創建在堆上,所以,只要禁用new運算符就能夠實現類對象只能創建在棧上。 雖然你不能影響new operator的能力(由於那是C++語言內建的),可是你能夠利用一個事實:new operator 老是先調用 operator new,然後者咱們是能夠自行聲明重寫的。

    所以,將operator new()設爲私有便可禁止對象被new在堆上。 代碼以下:

    class A {  
        private:  
            void* operator new(size_t t){}     // 注意函數的第一個參數和返回值都是固定的 
            void operator delete(void* ptr){} // 重載了new就須要重載delete 
        public:  
            A(){}  
            ~A(){}  
    }; 
    複製代碼

18.拷貝構造函數若是用值傳遞會有什麼影響?

參數爲引用,不爲值傳遞是爲了防止拷貝構造函數的無限遞歸,最終致使棧溢出。

若是拷貝構造函數中的參數不是一個引用,即形如CClass(const CClass c_class),那麼就至關於採用了傳值的方式(pass-by-value),而傳值的方式會調用該類的拷貝構造函數,從而形成無窮遞歸地調用拷貝構造函數。所以拷貝構造函數的參數必須是一個引用。

須要澄清的是,傳指針其實也是傳值,若是上面的拷貝構造函數寫成CClass(const CClass* c_class),也是不行的。事實上,只有傳引用不是傳值外,其餘全部的傳遞方式都是傳值。

19.stl 相關容器,底層用什麼實現的?

詳細見 juejin.im/post/5c9de9…

相關文章
相關標籤/搜索