常見c語言面試題及答案?應該使用inline内聯函數,即編譯器将inline内聯函數内的代碼替換到函數被調用的地方,我來為大家講解一下關于常見c語言面試題及答案?跟着小編一起來看一看吧!
常見c語言面試題及答案
第一節 語法基礎 C和C 有什麼區别?- C 是面向對象的語言,而C是面向過程的語言;
- C 引入new/delete運算符,取代了C中的malloc/free庫函數;
- C 引入引用的概念,而C中沒有;
- C 引入類的概念,而C中沒有;
- C 引入函數重載的特性,而C中沒有。
#01 C 11有哪些新特性?- Lambda表達式用于創建匿名函數:
- [capture](parameters)->return-type {body}
- 自動類型推導auto和decltype;
- 列表初始化;
- =default生成默認構造函數,=delete禁止使用拷貝構造函數;
- nullptr關鍵字,用于解決NULL的二義性;
- 基于RAII原則,引入shared_ptr、unique_ptr等智能指針;
- 右值引用,将引用綁定到右值,如臨時對象或字面量;
- 引入線程庫;
- 範圍for循環。
#02 struct和class有什麼區别?- 成員的默認訪問權限:
- struct的成員默認為public權限,class的成員默認為private權限;
- 默認繼承權限:
- struct的繼承按照public處理,class的繼承按照private處理。
#03 對于一個頻繁使用的短小函數,應該使用什麼來實現?有什麼優缺點?
應該使用inline内聯函數,即編譯器将inline内聯函數内的代碼替換到函數被調用的地方。
優點:
- 在内聯函數被調用的地方進行代碼展開,省去函數調用的時間,從而提高程序運行效率;
- 相比于宏函數,内聯函數在代碼展開時,編譯器會進行語法安全檢查或數據類型轉換,使用更加安全;
缺點:
- 代碼膨脹,會産生更多的開銷;
- 如果内聯函數内代碼塊的執行時間比調用時間長得多,那麼效率的提升并沒有那麼大;
- 如果修改内聯函數,那麼所有調用該函數的代碼文件都需要重新編譯;
- 内聯聲明隻是建議,是否内聯由編譯器決定,所以實際并不可控。
#04 #define和inline有什麼區别?- #define宏函數在預處理階段展開,而inline内聯函數在編譯階段展開;
- #define宏函數不檢查參數類型,而inline内聯函數檢查參數類型,使用更加安全。
#05 const關鍵字有什麼作用?- 修飾變量時,表示該變量的值在其生命周期内隻讀,不能被改變;
- 修飾指針:int * const;
- 修飾指針所指向的對象:const int *;
- 修飾引用所綁定的對象:const int &;
- 修飾函數的引用形參時,可以保護實參不被函數修改;
- 修飾非靜态成員變量時,不能在類定義處初始化,必須通過構造函數初始化列表進行初始化;
- 修飾靜态成員變量時,不能在類内部初始化,一般在類外部進行初始化;
- 修飾成員函數時,表示該函數不應修改非靜态成員,但并不可靠,因為指針所指對象可能會被修改。
#06 #define和const有什麼區别?- 編譯器處理方式不同:#define宏是在預處理階段展開,不能對宏定義進行調試,而const常量是在編譯階段使用;
- 類型和安全檢查不同:#define宏沒有類型,不做任何類型檢查,僅僅是代碼展開,可能産生邊際效應等錯誤,而const常量有具體類型,在編譯階段會執行類型檢查;
- 存儲方式不同:#define宏僅僅是代碼展開,在多個地方進行字符串替換,不會分配内存,存儲于程序的代碼段中,而const常量會分配内存,但隻維持一份拷貝,存儲于程序的數據段中。
- 定義域不同:#define宏不受定義域限制,而const常量隻在定義域内有效。
#07 explicit關鍵字有什麼作用?
首先,可以用單個實參來調用的構造函數都定義了從形參類型到實參類型的隐式轉換,這種轉換往往都是非預期的,所以使用explicit關鍵字對構造函數進行修飾,從而避免由構造函數定義的隐式轉換。
#08 extern關鍵字有什麼作用?- 當extern修飾變量或函數時,表示變量或函數的定義在其他文件中,提示編譯器在其他模塊中尋找其定義;
- 當extern C時,提示編譯器在編譯函數時按照C的規則去翻譯相應的函數名,如果按照C 的規則,函數名會被翻譯得變得面目全非,因為C 支持函數的重載。
#09 static關鍵字有什麼作用?- 修飾局部變量時,使得該變量在靜态存儲區分配内存;隻能在首次函數調用中進行首次初始化,之後的函數調用不再進行初始化;其生命周期與程序相同,但其作用域為局部作用域,并不能一直被訪問;
- 修飾全局變量時,使得該變量在靜态存儲區分配内存;在聲明該變量的整個文件中都是可見的,而在文件外是不可見的;
- 修飾函數時,在聲明該函數的整個文件中都是可見的,而在文件外是不可見的,從而可以在多人協作時避免同名的函數沖突;
- 修飾成員變量時,所有的對象都隻維持一份拷貝,可以實現不同對象間的數據共享;不需要實例化對象即可訪問;不能在類内部初始化,一般在類外部初始化,并且初始化時不加static;
- 修飾成員函數時,該函數不接受this指針,隻能訪問類的靜态成員;不需要實例化對象即可訪問。
#10 volatile關鍵字有什麼作用?可以同時是const和volatile嗎?指針可以是volatile嗎?
訪問寄存器比訪問内存要快,所以CPU會優先訪問該數據在寄存器中的存儲結果,但内存中的數據可能已經被意想不到地改變,而寄存器中還保留着原來的結果,所以為了避免這種情況,可以将變量聲明為volatile,不進行編譯優化,使得CPU每次都從内存中讀取數據。
例子是并行設備的硬件寄存器、中斷服務子程序會訪問到的非自動變量、多線程應用中被幾個任務共享的變量。
一個變量可以同時是const和volatile,一個例子是隻讀狀态寄存器,是const表示程序不應該試圖修改變量的值,是volatile表示變量的值可能被操作系統、硬件、其他線程等改變。
指針可以是volatile,一個例子是中斷服務子程序修改一個指向buffer的指針。
補充:下面這段代碼有什麼錯誤?
int square(volatile int *ptr) {
return (*ptr * *ptr);
}
因為*ptr的值可能被意想不到地改變,所以兩次解引用得到的值不一定相同,因此應該如下:
int square(volatile int *ptr) {
int a = *ptr;
return a * a;
}
#11 sizeof和strlen之間有什麼區别?- sizeof屬于運算符,不是庫函數,其結果在編譯時期計算得到,因此不能用來得到動态分配的内存大小,而strlen屬于庫函數,其結果在運行期間計算得到;
- sizeof參數可以是任何數據或數據類型,而strlen的參數隻能是字符指針,且該指針指向結尾為\0的字符串。
#12 assert有什麼用處?
assert是一種僅在debug版本中使用的宏函數,用于檢查不該發生的情況,可以看作是一種在任何系統狀态下都可以安全使用的無害測試手段。
另外,可以通過#define NDEBUG來關閉assert(需要在<cassert>頭文件之前)。
#13 變量的聲明和定義有什麼區别?- 聲明僅僅是把變量類型等信息提供給編譯器,并不為其分配内存空間,而定義需要為變量分配内存空間;
- 變量可以在多處聲明,如外部變量extern,但隻能在一處定義。
#14 指針和引用有什麼區别?- 指針是一種對象,用來存放某個對象的地址,占用内存空間,而引用是一種别名,不占用内存空間;
- 指針可以聲明為空,之後進行初始化,普通指針可以随時更換所指對象,而引用必須在聲明的時候初始化,而且初始化後不可改變;
- 指針包含指向常量的指針和常量的指針,而引用不包含常量引用,但包含對常量的引用。
#15 [識别指針、函數和數組] 以下聲明語句分别代表什麼意思?
void *(*(*fp1)(int))[10];
fp1是一個指針,指向一個函數,這個函數的參數為整型,返回一個指針,這個指針指向一個數組,數組中有10個元素,每個元素都是一個void *指針。
float (*(*fp2)(int, int, int))(int);
fp2是一個指針,指向一個函數,這個函數的參數是三個整型,返回一個指針,這個指針指向一個函數,這個函數的參數是整型,返回是浮點型;
int (*(*fp3)())[10]();
fp3是一個指針,指向一個函數,這個函數返回一個指針,這個指針指向一個數組,數組中有10個元素,每個元素都是一個指針,指向一個函數,這個函數的參數為空,返回整型。
第二節 内存管理#00 常用的數據類型各自占用多大的内存空間?
數據類型 |
32位編譯器 |
64位編譯器 |
bool |
1 |
1 |
char |
1 |
1 |
short (int) |
2 |
2 |
int |
4 |
4 |
unsigned int |
4 |
4 |
long |
4 |
8 |
long long |
8 |
8 |
float |
4 |
4 |
double |
8 |
8 |
pointer |
4 |
8 |
#01 new/delete和malloc/free之間有什麼關系?- 相同點:對于内部數據類型來說,沒有構造與析構的過程,所以兩者是等價的,都可以用于申請動态内存和釋放内存;
- 不同點:new/delete可以調用對象的構造函數和析構函數,屬于運算符,在編譯器權限之内;malloc/free僅用于内存分配和釋放,屬于庫函數,不在編譯器權限之内;new是類型安全的,而malloc返回的數據類型是void *,所以要顯式地進行類型轉換;new可以自動計算所需字節數,而malloc需要手動計算;new申請内存失敗時抛出bad_malloc異常,而malloc返回空指針。
#02 delete與delete []有什麼區别?- 對于簡單類型來說,使用new分配後,不管是數組數組還是非數組形式,兩種方式都可以釋放内存:
int *a = new int(1);
delete a;
int *b = new int(2);
delete [] b;
int *c = new int[11];
delete c;
int *d = new int[12];
delete [] d;
- 對于自定義類型來說,就需要對于單個對象使用delete,對于對象數組使用delete [],逐個調用數組中對象的析構函數,從而釋放所有内存;
- 如果反過來使用,即對于單個對象使用delete [],對于對象數組使用delete,其行為是未定義的;
- 所以,最恰當的方式就是如果用了new,就用delete;如果用了new [],就用delete []。
#03 如果在申請動态内存時找不到足夠大的内存塊,即malloc和new返回空指針,那麼應該如何處理這種情況?- 對于malloc來說,需要判斷其是否返回空指針,如果是則馬上用return語句終止該函數或者exit終止該程序;
- 對于new來說,默認抛出異常,所以可以使用try...catch...代碼塊的方式:
try {
int *ptr = new int[10000000];
} catch(bad_alloc &memExp) {
cerr << memExp.what() << endl;
}
- 還可以使用set_new_handler函數的方式:
void no_more_memory() {
cerr << "Unable to satisfy request for memory" << endl;
abort();
}
int main() {
set_new_handler(no_more_memory);
int *ptr = new int[10000000];
}
- 在這種方式裡,如果new不能滿足内存分配請求,no_more_memory會被反複調用,所以new_handler函數必須完成以下事情:讓更多内存可被使用:可以在程序一開始執行就分配一大塊内存,之後當new_handler第一次被調用,就将這些内存釋放還給程序使用;使用另一個new_handler;卸除new_handler:返回空指針,這樣new就會抛出異常;直接抛出bad_alloc異常;調用abort或exit。
#04 内存洩漏的場景有哪些?如何判斷内存洩漏?如何定位内存洩漏?
内存洩漏的場景:
- malloc和free未成對出現;new/new []和delete/delete []未成對出現;在堆中創建對象分配内存,但未顯式釋放内存;比如,通過局部分配的内存,未在調用者函數體内釋放:
char* getMemory() {
char *p = (char *)malloc(30);
return p;
}
int main() {
char *p = getMemory();
return 0;
}
在構造函數中動态分配内存,但未在析構函數中正确釋放内存;
- 未定義拷貝構造函數或未重載賦值運算符,從而造成兩次釋放相同内存的做法;比如,類中包含指針成員變量,在未定義拷貝構造函數或未重載賦值運算符的情況下,編譯器會調用默認的拷貝構造函數或賦值運算符,以逐個成員拷貝的方式來複制指針成員變量,使得兩個對象包含指向同一内存空間的指針,那麼在釋放第一個對象時,析構函數釋放該指針指向的内存空間,在釋放第二個對象時,析構函數就會釋放同一内存空間,這樣的行為是錯誤的;
- 沒有将基類的析構函數定義為虛函數。
判斷和定位内存洩漏的方法:
在Linux系統下,可以使用valgrind、mtrace等内存洩漏檢測工具。
#05 内存的分配方式有幾種?- 在棧上分配:在執行函數時,局部變量的内存都可以在棧上分配,函數結束時會自動釋放;棧内存的分配運算内置于處理器的指令集中,效率很高,但分配的内存容量有限;
- 從堆上分配:由new分配/delete釋放的内存塊,也稱為動态内存分配,程序員自行申請和釋放内存,使用靈活;
- 從自由存儲區分配:由malloc分配/free釋放的内存塊,與堆類似;
- 從常量存儲區分配:特殊的存儲區,存放的是常量,不可修改;
- 從全局/靜态存儲區分配:編譯期間分配内存,整個程序運行期間都存在,如全局變量、靜态變量等。
#06 堆和棧有什麼區别?- 分配和管理方式不同:堆是動态分配的,其空間的分配和釋放都由程序員控制;棧是由編譯器自動管理的,其分配方式有兩種:靜态分配由編譯器完成,比如局部變量的分配;動态分配由alloca()函數進行分配,但是會由編譯器釋放;
- 産生碎片不同:對堆來說,頻繁使用new/delete或者malloc/free會造成内存空間的不連續,産生大量碎片,是程序效率降低;對棧來說,不存在碎片問題,因為棧具有先進後出的特性;
- 生長方向不同:堆是向着内存地址增加的方向增長的,從内存的低地址向高地址方向增長;棧是向着内存地址減小的方向增長的,從内存的高地址向低地址方向增長;
- 申請大小限制不同:棧頂和棧底是預設好的,大小固定;堆是不連續的内存區域,其大小可以靈活調整。
#07 靜态内存分配和動态内存分配有什麼區别?- 靜态内存分配是在編譯時期完成的,不占用CPU資源;動态内存分配是在運行時期完成的,分配和釋放需要占用CPU資源;
- 靜态内存分配是在棧上分配的;動态内存分配是在堆上分配的;
- 靜态内存分配不需要指針或引用類型的支持;動态内存分配需要;
- 靜态内存分配是按計劃分配的,在編譯前确定内存塊的大小;動态内存分配是按需要分配的;
- 靜态内存分配是把内存的控制權交給了編譯器;動态内存分配是把内存的控制權給了程序員;
- 靜态内存分配的運行效率比動态内存分配高,動态内存分配不當可能造成内存洩漏。
#08 如何構造一個類,使得隻能在堆上或隻能在棧上分配内存?- 隻能在堆上分配内存:将析構函數聲明為private;
- 隻能在棧上生成對象:将new和delete重載為private。
#09 淺拷貝和深拷貝有什麼區别?
淺拷貝隻複制指向某個對象的指針,而不複制對象本身,新舊對象還是共享一塊内存;而深拷貝會創造一個相同的對象,新對象與原對象不共享内存,修改新對象不會影響原對象。
#10 字節對齊的原則是什麼?- 從偏移為0的位置開始存儲;
- 如果沒有定義#pragma pack(n):sizeof的最終結果必然是結構内部最大成員的整數倍,不夠補齊;結構内部各個成員的首地址必然是自身大小的整數倍;
- 如果定義了#pragma pack(n):sizeof的最終結果必然必然是min[n,結構内部最大成員]的整數倍,不夠補齊;結構内部各個成員的首地址必然是min[n,自身大小]的整數倍。
第三節 面向對象#00 面向對象的三大特征是哪些?各自有什麼樣的特點?- 封裝:将客觀事物封裝成抽象的類,而類可以把自己的數據和方法暴露給可信的類或者對象,對不可信的類或對象則進行信息隐藏。
- 繼承:可以使用現有類的所有功能,并且無需重新編寫原來的類即可對功能進行拓展;
- 多态:一個類實例的相同方法在不同情形下有不同的表現形式,使不同内部結構的對象可以共享相同的外部接口。
#01 多态的實現有哪幾種?
多态分為靜态多态和動态多态。其中,靜态多态是通過重載和模闆技術實現的,在編譯期間确定;動态多态是通過虛函數和繼承關系實現的,執行動态綁定,在運行期間确定。
#02 動态多态有什麼作用?有哪些必要條件?
動态多态的作用:
- 隐藏實現細節,使代碼模塊化,提高代碼的可複用性;
- 接口重用,使派生類的功能可以被基類的指針/引用所調用,即向後兼容,提高代碼的可擴充性和可維護性。
動态多态的必要條件:
- 需要有繼承;
- 需要有虛函數覆蓋;
- 需要有基類指針/引用指向子類對象。
#03 動态綁定是如何實現的?
當編譯器發現類中有虛函數時,會創建一張虛函數表,把虛函數的函數入口地址放到虛函數表中,并且在對象中增加一個指針vptr,用于指向類的虛函數表。當派生類覆蓋基類的虛函數時,會将虛函數表中對應的指針進行替換,從而調用派生類中覆蓋後的虛函數,從而實現動态綁定。
#04 純虛函數有什麼作用?如何實現?
定義純虛函數是為了實現一個接口,起到規範的作用,想要繼承這個類就必須覆蓋該函數。
實現方式是在虛函數聲明的結尾加上= 0即可。
#05 虛函數表是針對類的還是針對對象的?同一個類的兩個對象的虛函數表是怎麼維護的?
虛函數表是針對類的,類的所有對象共享這個類的虛函數表,因為每個對象内部都保存一個指向該類虛函數表的指針vptr,每個對象的vptr的存放地址都不同,但都指向同一虛函數表。
#06 為什麼基類的構造函數不能定義為虛函數?
虛函數的調用依賴于虛函數表,而指向虛函數表的指針vptr需要在構造函數中進行初始化,所以無法調用定義為虛函數的構造函數。
#07 為什麼基類的析構函數需要定義為虛函數?
為了實現動态綁定,基類指針指向派生類對象,如果析構函數不是虛函數,那麼在對象銷毀時,就會調用基類的析構函數,隻能銷毀派生類對象中的部分數據,所以必須将析構函數定義為虛函數,從而在對象銷毀時,調用派生類的析構函數,從而銷毀派生類對象中的所有數據。
#08 構造函數和析構函數能抛出異常嗎?- 從語法的角度來說,構造函數可以抛出異常,但從邏輯和風險控制的角度來說,盡量不要抛出異常,否則可能導緻内存洩漏。
- 析構函數不可以抛出異常,如果析構函數抛出異常,則異常點之後的程序,比如釋放内存等操作,就不會被執行,從而造成内存洩露的問題;而且當異常發生時,C 通常會調用對象的析構函數來釋放資源,如果此時析構函數也抛出異常,即前一個異常未處理又出現了新的異常,從而造成程序崩潰的問題。
#09 如何讓一個類不能實例化?
将類定義為抽象類(也就是存在純虛函數)或者将構造函數聲明為private。
#10 多繼承存在什麼問題?如何消除多繼承中的二義性?- 增加程序的複雜度,使得程序的編寫和維護比較困難,容易出錯;
- 在繼承時,基類之間或基類與派生類之間發生成員同名時,将出現對成員訪問的不确定性,即同名二義性;
- 消除同名二義性的方法:利用作用域運算符::,用于限定派生類使用的是哪個基類的成員;在派生類中定義同名成員,覆蓋基類中的相關成員;
- 當派生類從多個基類派生,而這些基類又從同一個基類派生,則在訪問此共同基類的成員時,将産生另一種不确定性,即路徑二義性;
- 消除路徑二義性的方法:消除同名二義性的兩種方法都可以;使用虛繼承,使得不同路徑繼承來的同名成員在内存中隻有一份拷貝。
#11 如果類A是一個空類,那麼sizeof(A)的值為多少?為什麼?
sizeof(A)的值為1,因為編譯器需要區分這個空類的不同實例,分配一個字節,可以使這個空類的不同實例擁有獨一無二的地址。
第四節 高級特性#00 什麼是智能指針?智能指針有什麼作用?分為哪幾種?各自有什麼樣的特點?
智能指針是一個RAII類模型,用于動态分配内存,其設計思想是将基本類型指針封裝為(模闆)類對象指針,并在離開作用域時調用析構函數,使用delete删除指針所指向的内存空間。
智能指針的作用是,能夠處理内存洩漏問題和空懸指針問題。
分為auto_ptr、unique_ptr、shared_ptr和weak_ptr四種,各自的特點:
- 對于auto_ptr,實現獨占式擁有的概念,同一時間隻能有一個智能指針可以指向該對象;但auto_ptr在C 11中被摒棄,其主要問題在于:對象所有權的轉移,比如在函數傳參過程中,對象所有權不會返還,從而存在潛在的内存崩潰問題;不能指向數組,也不能作為STL容器的成員。
- 對于unique_ptr,實現獨占式擁有的概念,同一時間隻能有一個智能指針可以指向該對象,因為無法進行拷貝構造和拷貝賦值,但是可以進行移動構造和移動賦值;
- 對于shared_ptr,實現共享式擁有的概念,即多個智能指針可以指向相同的對象,該對象及相關資源會在其所指對象不再使用之後,自動釋放與對象相關的資源;
- 對于weak_ptr,解決shared_ptr相互引用時,兩個指針的引用計數永遠不會下降為0,從而導緻死鎖問題。而weak_ptr是對對象的一種弱引用,可以綁定到shared_ptr,但不會增加對象的引用計數。
#01 shared_ptr是如何實現的?- 構造函數中計數初始化為1;
- 拷貝構造函數中計數值加1;
- 賦值運算符中,左邊的對象引用計數減1,右邊的對象引用計數加1;
- 析構函數中引用計數減1;
- 在賦值運算符和析構函數中,如果減1後為0,則調用delete釋放對象。
#02 類型轉換分為哪幾種?各自有什麼樣的特點?- static_cast:用于基本數據類型之間的轉換、子類向父類的安全轉換、void*和其他類型指針之間的轉換;
- const_cast:用于去除const或volatile屬性;
- dynamic_cast:用于子類和父類之間的安全轉換,可以實現向上向下轉換,因為編譯器默認向上轉換總是安全的,而向下轉換時,dynamic_cast具有類型檢查的功能;
- dynamic_cast轉換失敗時,對于指針會返回目标類型的nullptr,對于引用會返回bad_cast異常;
- reinterpret_cast:用于不同類型指針之間、不同類型引用之間、指針和能容納指針的整數類型之間的轉換。
#03 RTTI是什麼?其原理是什麼?
RTTI即運行時類型識别,其功能由兩個運算符實現:
- typeid運算符,用于返回表達式的類型,可以通過基類的指針獲取派生類的數據類型;
- dynamic_cast運算符,具有類型檢查的功能,用于将基類的指針或引用安全地轉換成派生類的指針或引用。
#04 右值引用有什麼作用?
右值引用的主要目的是為了實現轉移語義和完美轉發,消除兩個對象交互時不必要的對象拷貝,也能夠更加簡潔明确地定義泛型函數。
#05 标準庫中有哪些容器?分别有什麼特點?
标準庫中的容器主要分為三類:順序容器、關聯容器、容器适配器。
順序容器包括五種類型:
- array<T, N>數組:固定大小數組,支持快速随機訪問,但不能插入或删除元素;
- vector<T>動态數組:支持快速随機訪問,尾位插入和删除的速度很快;
- deque<T>雙向隊列:支持快速随機訪問,首尾位置插入和删除的速度很快;(可以看作是vector的增強版,與vector相比,可以快速地在首位插入和删除元素)
- list<T>雙向鍊表:隻支持雙向順序訪問,任何位置插入和删除的速度都很快;
- forward_list<T>單向鍊表:隻支持單向順序訪問,任何位置插入和删除的速度都很快。
關聯容器包含兩種類型:
- map容器:map<K, T>關聯數組:用于保存關鍵字-值對;
- multimap<K, T>:關鍵字可重複出現的map;
- unordered_map<K, T>:用哈希函數組織的map;
- unordered_multimap<K, T>:關鍵詞可重複出現的unordered_map;
- set容器:set<T>:隻保存關鍵字;
- multiset<T>:關鍵字可重複出現的set;
- unordered_set<T>:用哈希函數組織的set;
- unordered_multiset<T>:關鍵詞可重複出現的unordered_set;
容器适配器包含三種類型:
- stack<T>棧、
- queue<T>隊列、
- priority_queue<T>優先隊列。
#06 vector的reserve()和resize()方法之間有什麼區别?
首先,vector的容量capacity()是指在不分配更多内存的情況下可以保存的最多元素個數,而vector的大小size()是指實際包含的元素個數;
其次,vector的reserve(n)方法隻改變vector的容量,如果當前容量小于n,則重新分配内存空間,調整容量為n;如果當前容量大于等于n,則無操作;
最後,vector的resize(n)方法改變vector的大小,如果當前容量小于n,則調整容量為n,同時将其全部元素填充為初始值;如果當前容量大于等于n,則不調整容量,隻将其前n個元素填充為初始值。
#07 靜态鍊接和動态鍊接有什麼區别?- 靜态鍊接是在編譯鍊接時直接将需要的執行代碼拷貝到調用處;
- 優點在于程序在發布時不需要依賴庫,可以獨立執行,缺點在于程序的體積會相對較大,而且如果靜态庫更新之後,所有可執行文件需要重新鍊接;
- 動态鍊接是在編譯時不直接拷貝執行代碼,而是通過記錄一系列符号和參數,在程序運行或加載時将這些信息傳遞給操作系統,操作系統負責将需要的動态庫加載到内存中,然後程序在運行到指定代碼時,在共享執行内存中尋找已經加載的動态庫可執行代碼,實現運行時鍊接;
- 優點在于多個程序可以共享同一個動态庫,節省資源;
- 缺點在于由于運行時加載,可能影響程序的前期執行性能。
#08 懸挂指針與野指針有什麼區别?- 懸挂指針:當指針所指向的對象被釋放,但是該指針沒有任何改變,以至于其仍然指向已經被回收的内存地址,這種情況下該指針被稱為懸挂指針;
- 野指針:未初始化的指針被稱為野指針。
#09 拷貝構造函數和賦值運算符重載之間有什麼區别?
Student s;
Student s1 = s; // 隐式調用拷貝構造函數
Student s2(s); // 顯式調用拷貝構造函數
- 賦值運算符重載用于将源對象的内容拷貝到目标對象中,而且若源對象中包含未釋放的内存需要先将其釋放;
Student s;
Student s1;
s1 = s; // 使用賦值運算符
- 一般情況下,類中包含指針變量時需要重載拷貝構造函數、賦值運算符和析構函數。
#10 覆蓋和重載之間有什麼區别?- 覆蓋是指派生類中重新定義的函數,其函數名、參數列表、返回類型與父類完全相同,隻是函數體存在區别;覆蓋隻發生在類的成員函數中;
- 重載是指兩個函數具有相同的函數名,不同的參數列表,不關心返回值;當調用函數時,根據傳遞的參數列表來判斷調用哪個函數;重載可以是類的成員函數,也可以是普通函數。
第五節 代碼實現#00 對于float類型的變量,如何比較零值?
if ((val >= -0.000001) && (val <= 0.000001))
#01 如何判斷一段程序是C編譯程序還是C 編譯程序?
#ifdef __cplusplus
cout << "C " << endl;
#else
cout << "C" << endl;
#endif
#02 如何使用宏定義求兩個元素中的最小值?
#define MIN(A, B) ((A) <= (B) ? (A) : (B))
但此處應注意防範宏的副作用,比如對于MIN(*p , b)來說:
((*p ) <= (b) ? (*p ) : (b))
因此,對于指針p來說,會産生兩次自增操作。
#03 如何實現strcpy函數?
char *strcpy(char *dst, char *src) {
assert(dst != nullptr && src != nullptr);
char *res = dst;
while ((*dst = *src ) != '\0');
return res;
}
#04 如何實現strcat函數?
char *strcat(char *dst, char *src) {
assert(dst != nullptr && src != nullptr);
char *res = dst;
while (*dst != '\0')
dst;
while ((*dst = *src ) != '\0');
return res;
}
#05 如何實現strcmp函數?
int strcmp(const char *str1, const char *str2) {
assert(str1 != nullptr && str2 != nullptr);
while (*str1 == *str2 && *str1 != '\0' && *str2 != '\0') {
str1;
str2;
}
return *str1 - *str2;
#06 下面這段代碼有什麼問題?
void GetMemory(char *p) {
p = (char *)malloc(100);
}
void Test(void) {
char *str = nullptr;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
- 修改形參的值并不會改變實參的值,所以調用GetMemory之後,str依然為空指針;
- 在GetMemory和Test中,沒有malloc對應的free,造成内存洩漏。
#07 下面這段代碼有什麼問題?
char *GetMemory(void) {
char p[] = "hello world";
return p;
}
void Test(void) {
char *str = nullptr;
str = GetMemory();
printf(str);
}
GetMemory中的數組p[]是局部變量,所開辟的内存将在函數調用結束後被回收,雖然返回指向該内存的指針,但是内存中的數據可能已經發生了改變,從而形成懸挂指針。
可以使用如下兩種方法進行改進:
char *p = "hello world";
static char *p = "hello world";
#08 如何實現一個string類?
class String {
public:
String(const char *str = nullptr);
String(const String &other);
~ String(void);
String & operator =(const String &other);
private:
char *m_data;
};
String::String(const char *str) {
if (str == nullptr) {
m_data = new char[1]{'\0'};
} else {
int length = strlen(str);
m_data = new char[length 1];
strcpy(m_data, str);
}
}
String::String(const String &other) {
int length = strlen(other.m_data);
m_data = new char[length 1];
// 此處可以訪問private成員,是因為此處還算是在類内,隻要是在類内就可以訪問類成員
strcpy(m_data, other.m_data);
}
String::~String(void) {
delete [] m_data;
}
String & String::operator =(const String &other) {
if (this == &other) return *this; // 檢查自賦值
delete [] m_data; // 釋放原來的内存
int length = strlen(other.m_data);
m_data = new char[length 1];
strcpy(m_data, other.m_data);
return *this;
}
, 更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!