二十万字C/C++、嵌入式软开面试题全集宝典五
目錄
81、 vector越界訪問下標,map越界訪問下標?vector刪除元素時會不會釋放空間?
82、 map[]與find的區別?
83、 STL中list與queue之間的區別
84、 STL中的allocator,deallocator
85、 STL中hash_map擴容發生什么?
86、 map如何創建?
87、 vector的增加刪除都是怎么做的?為什么是1.5倍?
88、 C++ sort排序算法底層
89、 函數指針?
90、 C與C++的聯系與區別
91、 C++中NULL和nullptr的區別
92、 C/C++內存分配
93、 c/c++的內存分配模型,詳細說一下棧、堆、靜態存儲區?
94、 野指針是什么?如何檢測內存泄漏?
95、 懸空指針和野指針有什么區別?
96、 內存泄漏
97、 內存溢出定位
98、 什么是內存對齊
99、 為什么內存對齊
100、 內存對齊規則
?
81、 vector越界訪問下標,map越界訪問下標?vector刪除元素時會不會釋放空間?
1.通過下標訪問vector中的元素時不會做邊界檢查,即便下標越界。也就是說,下標與first迭代器相加的結果超過了finish迭代器的位置,程序也不會報錯,而是返回這個地址中存儲的值。如果想在訪問vector中的元素時首先進行邊界檢查,可以使用vector中的at函數。通過使用at函數不但可以通過下標訪問vector中的元素,而且在at函數內部會對下標進行邊界檢查。
2.map的下標運算符[]的作用是:將key作為下標去執行查找,并返回相應的值;如果不存在這個key,就將一個具有該key和value的某人值插入這個map。
3.erase()函數,只能刪除內容,不能改變容量大小; erase成員函數,它刪除了itVect迭代器指向的元素,并且返回要被刪除的itVect之后的迭代器,迭代器相當于一個智能指針;clear()函數,只能清空內容,不能改變容量大小;如果要想在刪除內容的同時釋放內存,那么你可以選擇deque容器。
82、 map[]與find的區別?
1.map的下標運算符[]的作用是:將關鍵碼作為下標去執行查找,并返回對應的值;如果不存在這個關鍵碼,就將一個具有該關鍵碼和值類型的默認值的項插入這個map。
2.map的find函數:用關鍵碼執行查找,找到了返回該位置的迭代器;如果不存在這個關鍵碼,就返回尾迭代器。
83、 STL中list與queue之間的區別
1.list不再能夠像vector一樣以普通指針作為迭代器,因為其節點不保證在存儲空間中連續存在;
2.list插入操作和結合才做都不會造成原有的list迭代器失效;
3.list不僅是一個雙向鏈表,而且還是一個環狀雙向鏈表,所以它只需要一個指針;
4.list不像vector那樣有可能在空間不足時做重新配置、數據移動的操作,所以插入前的所有迭代器在插入操作之后都仍然有效;
5.deque是一種雙向開口的連續線性空間,所謂雙向開口,意思是可以在頭尾兩端分別做元素的插入和刪除操作;可以在頭尾兩端分別做元素的插入和刪除操作;
6.deque和vector最大的差異,一在于deque允許常數時間內對起頭端進行元素的插入或移除操作,二在于deque沒有所謂容量概念,因為它是動態地以分段連續空間組合而成,隨時可以增加一段新的空間并鏈接起來,deque沒有所謂的空間保留功能。
84、 STL中的allocator,deallocator
1.第一級配置器直接使用malloc()、free()和relloc(),第二級配置器視情況采用不同的策略:當配置區塊超過128bytes時,視之為足夠大,便調用第一級配置器;當配置器區塊小于128bytes時,為了降低額外負擔,使用復雜的內存池整理方式,而不再用一級配置器;
2.第二級配置器主動將任何小額區塊的內存需求量上調至8的倍數,并維護16個free-list,各自管理大小為8~128bytes的小額區塊;
3.空間配置函數allocate(),首先判斷區塊大小,大于128就直接調用第一級配置器,小于128時就檢查對應的free-list。如果free-list之內有可用區塊,就直接拿來用,如果沒有可用區塊,就將區塊大小調整至8的倍數,然后調用refill(),為free-list重新分配空間;
4.空間釋放函數deallocate(),該函數首先判斷區塊大小,大于128bytes時,直接調用一級配置器,小于128bytes就找到對應的free-list然后釋放內存。
85、 STL中hash_map擴容發生什么?
1.hash table表格內的元素稱為桶(bucket),而由桶所鏈接的元素稱為節點(node),其中存入桶元素的容器為stl本身很重要的一種序列式容器——vector容器。之所以選擇vector為存放桶元素的基礎容器,主要是因為vector容器本身具有動態擴容能力,無需人工干預。
2.向前操作:首先嘗試從目前所指的節點出發,前進一個位置(節點),由于節點被安置于list內,所以利用節點的next指針即可輕易完成前進操作,如果目前正巧是list的尾端,就跳至下一個bucket身上,那正是指向下一個list的頭部節點。
86、 map如何創建?
1.vector 底層數據結構為數組 ,支持快速隨機訪問
2.list底層數據結構為雙向鏈表,支持快速增刪
3.deque底層數據結構為一個中央控制器和多個緩沖區,詳細見STL源碼剖析P146,支持首尾(中間不能)快速增刪,也支持隨機訪問
deque是一個雙端隊列(double-ended queue),也是在堆中保存內容的。它的保存形式如下:
[堆1] --> [堆2] -->[堆3] --> ...
每個堆保存好幾個元素,然后堆和堆之間有指針指向,看起來像是list和vector的結合品.
4.stack 底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時
5.queue 底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時(stack和queue其實是適配器,而不叫容器,因為是對容器的再封裝)
6.priority_queue 的底層數據結構一般為vector為底層容器,堆heap為處理規則來管理底層容器實現
7.set 底層數據結構為紅黑樹,有序,不重復
8.multiset 底層數據結構為紅黑樹,有序,可重復
9.map底層數據結構為紅黑樹,有序,不重復
10.multimap 底層數據結構為紅黑樹,有序,可重復
11.hash_set 底層數據結構為hash表,無序,不重復
12.hash_multiset 底層數據結構為hash表,無序,可重復
13.hash_map 底層數據結構為hash表,無序,不重復
14.hash_multimap 底層數據結構為hash表,無序,可重復
87、 vector的增加刪除都是怎么做的?為什么是1.5倍?
1.新增元素:vector通過一個連續的數組存放元素,如果集合已滿,在新增數據的時候,就要分配一塊更大的內存,將原來的數據復制過來,釋放之前的內存,在插入新增的元素;
2.對vector的任何操作,一旦引起空間重新配置,指向原vector的所有迭代器就都失效了;
3.初始時刻vector的capacity為0,塞入第一個元素后capacity增加為1;
4.不同的編譯器實現的擴容方式不一樣,VS2015中以1.5倍擴容,GCC以2倍擴容。
對比可以發現采用采用成倍方式擴容,可以保證常數的時間復雜度,而增加指定大小的容量只能達到O(n)的時間復雜度,因此,使用成倍的方式擴容。
擴容倍數
考慮可能產生的堆空間浪費,成倍增長倍數不能太大,使用較為廣泛的擴容方式有兩種,以2二倍的方式擴容,或者以1.5倍的方式擴容。
1.以2倍的方式擴容,導致下一次申請的內存必然大于之前分配內存的總和,導致之前分配的內存不能再被使用,所以最好倍增長因子設置為(1,2)之間;
2.向量容器vector的成員函數pop_back()可以刪除最后一個元素;
3.而函數erase()可以刪除由一個iterator指出的元素,也可以刪除一個指定范圍的元素。
4.還可以采用通用算法remove()來刪除vector容器中的元素.
5.不同的是:采用remove一般情況下不會改變容器的大小,而pop_back()與erase()等成員函數會改變容器的大小。
88、 C++ sort排序算法底層
sort作為一個內置的排序方法,可以被vector等直接調用。
對于STL中的sort()算法:
1.當數據量大時,將會采用Quick Sort(快排),分段遞歸進行排序。
2.一旦分段后的數據量小于某個閾值,為了避免快排的遞歸帶來過大的額外的開銷,sort()算法就自動改為Insertion Sort(插入排序)。
3.如果遞歸的層次過深,還會改用Heap Sort(堆排序)。
簡單來說,sort并非只是普通的快速排序,除了對普通的快排進行優化,它還結合了插入排序和堆排序。根據不同的數量級以及不同的情況,能夠自動選擇合適的排序算法。
89、 函數指針?
1.什么是函數指針?
函數指針指向的是特殊的數據類型,函數的類型是由其返回的數據類型和其參數列表共同決定的,而函數的名稱則不是其類型的一部分。
一個具體函數的名字,如果后面不跟調用符號(即括號),則該名字就是該函數的指針(注意:大部分情況下,可以這么認為,但這種說法并不很嚴格)。
2.函數指針的聲明方法
int (*pf)(const int&, const int&); (1)
上面的pf就是一個函數指針,指向所有返回類型為int,并帶有兩個const int&參數的函數。注意*pf兩邊的括號是必須的,否則上面的定義就變成了:
int*pf(const int&, const int&); (2)
而這聲明了一個函數pf,其返回類型為int *, 帶有兩個const int&參數。
3.為什么有函數指針
○1函數與數據項相似,函數也有地址(函數名就是指針)。我們希望在同一個函數中通過使用相同的形參在不同的時間使用產生不同的效果。
○2一個函數名就是一個指針,它指向函數的代碼。一個函數地址是該函數的進入點,也就是調用函數的地址。函數的調用可以通過函數名,也可以通過指向函數的指針來調用。函數指針還允許將函數作為變元傳遞給其他函數;
○3兩種方法賦值:
指針名 = 函數名;指針名 = &函數名
90、 C與C++的聯系與區別
一、C++與C的聯系:
1、C++是在C語言的基礎上開發的一種面向對象編程語言,應用廣泛。C++支持多種編程范式 --面向對象編程、泛型編程和過程化編程。 其編程領域眾廣,常用于系統開發,引擎開發等應用領域,是最受廣大程序員受用的最強大編程語言之一,支持類:類、封裝、重載等特性!
2、C++在C的基礎上增添類,C是一個結構化語言,它的重點在于算法和數據結構。C程序的設計首要考慮的是如何通過一個過程,對輸入(或環境條件)進行運算處理得到輸出(或實現過程(事務)控制),而對于C++,首要考慮的是如何構造一個對象模型,讓這個模型能夠契合與之對應的問題域,這樣就可以通過獲取對象的狀態信息得到輸出或實現過程(事務)控制。
二、C++與C的區別
1、C是面向過程的語言,而C++是面向對象的語言,那么什么是面向對象?
面向對象:面向對象是一種對現實世界的理解和抽象的方法、思想,通過將需求要素轉化為對象進行問題處理的一種思想。
2、C和C++動態管理內存的方法不一樣,C是使用malloc、free函數,而C++不僅有malloc/free,還有new/delete關鍵字。那malloc/free和new/delete差別?
malloc/free和new/delete差別:
①malloc/free是C和C++語言的標準庫函數,需要頭文件包含,new/delete是C++的運算符,關鍵字,需要編譯器支持。它們都可用于申請動態內存和釋放內存。
②由于malloc/free是庫函數不是運算符,不在編譯器范圍之內,不能夠把執行構造函數和析構函數的任務強加入malloc/free。因此C++需要一個能完成動態內存分配和初始化工作的運算符new,一個能完成清理與釋放內存工作的運算符delete。
③new可以認為是malloc加構造函數的執行。new出來的指針是直接帶類型信息的。而malloc返回的都是void指針。
④malloc是從堆上開辟空間,而new是從自由存儲區開辟(自由存儲區是從C++抽象出來的概念,不僅可以是堆,還可以是靜態存儲區)。
⑤malloc對開辟的空間大小有嚴格指定,而new只需要對象名。
⑥malloc開辟的內存如果太小,想要換一塊大一點的,可以調用relloc實現,但是new沒有直觀的方法來改變。
3、C中的struct和C++的類,C++的類是C中沒有的,C中的struct可以在C++中等同類來使用,struct和類的差別是,struct的成員默認訪問修飾符是public,而類默認是private。
4、C++支持重載,而C不支持重載,C++支持重載在于C++名字的修飾符與C不同,例如在C++中函數 int f(int) 經過名字修飾之后變為_f_int,而C是_f,所以C++才會支持不同的參數調用不同的函數。
5、C++中有引用,而C沒有。那指針和引用有什么差別?
指針和引用的區別:
①指針有自己的一塊空間,而引用只是一個別名。
②使用sizeof查看一個指針大小為4(32位),而引用的大小是被引用對象的大小。
③指針可以是NULL,而引用必須被初始化且必須是對一個以初始化對象的引用。
④作為參數傳遞時,指針需要被解引用才可以對對象進行操作,而直接對引用的修改都會改變引用所指向的對象。
⑤指針在使用中可以指向其它對象,但是引用只能是一個對象的引用,不能被修改。
⑥指針可以有多級指針(**p),而引用只有一級。
⑦指針和引用使用++運算符的意義不一樣。
6、C++全部變量的默認連接屬性是外連接,而C是內連接。
7、C中用const修飾的變量不可以用在定義數組時的大小,但是C++用const修飾的變量可以。(如果不進行&,解引用的操作的話,是存放在符號表的,不開辟內存)
8、C++有很多特有的輸入輸出流。
?
91、 C++中NULL和nullptr的區別
1.在C語言中,NULL通常被定義為:
#define NULL ((void *)0)
所以說NULL實際上是一個空指針。
2.C++是強類型語言,void*是不能隱式轉換成其他類型的指針的,所以實際上編譯器提供的頭文件做了相應的處理:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
可見,在C++中,NULL實際上是0。
3.C++中的nullptr
就是空指針常量,C++11之前并沒有所謂的“空指針類型”,C++11設計了nullptr_t,它唯一接受的值就是nullptr,專門代表空指針。
92、 C/C++內存分配
1、靜態存儲區分配
內存分配在程序編譯之前完成,且在程序的整個運行期間都存在,例如全局變量、靜態變量等。
2、棧上分配
在函數執行時,函數內的局部變量的存儲單元在棧上創建,函數執行結束時這些存儲單元自動釋放。
3、堆上分配
堆分配(又稱動態內存分配)。程序在運行時用malloc或者new申請內存,程序員自己用free或者delete釋放,動態內存的生存期由我們自己決定。
93、 c/c++的內存分配模型,詳細說一下棧、堆、靜態存儲區?
1、棧區(stack),由編譯器自動分配釋放,存放函數的參數值,局部變量的值等其操作方式類似于數據結構中的棧。
2、堆區(heap),一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS(操作系統)回收。注意它與數據結構中的堆是兩回事,分配方式倒是類似于鏈表。
3、全局區(靜態區)(static),全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束后由系統釋放。
4、常量存儲區,常量字符串就是放在這里的。程序結束后由系統釋放。
5、程序代碼區,存放函數體的二進制代碼。
94、 野指針是什么?如何檢測內存泄漏?
1.野指針:指向內存被釋放的內存或者沒有訪問權限的內存的指針。
2.“野指針”的成因主要有3種:
○1指針變量沒有被初始化。任何指針變量剛被創建時不會自動成為NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要么將指針設置為NULL,要么讓它指向合法的內存。例如char *p = NULL;char *str = new char(100);
○2指針p被free或者delete之后,沒有置為NULL;
○3指針操作超越了變量的作用范圍。
3.如何避免野指針:
○1對指針進行初始化
①將指針初始化為NULL。
char *p = NULL;
②用malloc分配內存
char * p = (char * )malloc(sizeof(char));
③用已有合法的可訪問的內存地址對指針初始化
char num[ 30] = {0};
char *p = num;
○2指針用完后釋放內存,將指針賦NULL。
delete(p);
p = NULL;
95、 懸空指針和野指針有什么區別?
1.野指針:野指針指,訪問一個已刪除或訪問受限的內存區域的指針,野指針不能判斷是否為NULL來避免。指針沒有初始化,釋放后沒有置空,越界
2.懸空指針:一個指針的指向對象已被刪除,那么就成了懸空指針。野指針是那些未初始化的指針。
96、 內存泄漏
1.內存泄漏
內存泄漏是指由于疏忽或錯誤造成了程序未能釋放掉不再使用的內存的情況。內存泄漏并非指內存在物理上消失,而是應用程序分配某塊內存后,由于設計錯誤,失去了對該段內存的控制;或者申請了?塊內存空間,使?完畢后沒有釋放掉,或者由程序申請的?塊內存,且沒有任何?個指針指向它,那么這塊內存就泄漏了。?般表現?式是程序運?時間越?,占?內存越多,最終?盡全部內存,整個系統崩潰。
2.內存泄漏的后果
只發生一次小的內存泄漏可能不被注意,但泄漏大量內存的程序將會出現各種征兆:性能下降到內存逐漸用完,導致另一個程序失敗,而使用戶無從查找問題的真正根源。
3.內存泄漏類型
(1)堆內存泄漏 (Heap leak)。對內存指的是程序運行中根據需要分配通過malloc,realloc new等從堆中分配的一塊內存,再是完成后必須通過調用對應的 free或者delete 刪掉。如果程序的設計的錯誤導致這部分內存沒有被釋放,那么此后這塊內存將不會被使用,就會產生Heap Leak.
(2)系統資源泄露(Resource Leak)。主要指程序使用系統分配的資源比如 Bitmap,handle ,SOCKET等沒有使用相應的函數釋放掉,導致系統資源的浪費,嚴重可導致系統效能降低,系統運行不穩定。
4.如何排除
使用工具軟件BoundsChecker,BoundsChecker是一個運行時錯誤檢測工具,它主要定位程序運行時期發生的各種錯誤;
調試運行DEBUG版程序,運用以下技術:CRT(C run-time libraries)、運行時函數調用堆棧、內存泄漏時提示的內存分配序號(集成開發環境OUTPUT窗口),綜合分析內存泄漏的原因,排除內存泄漏。
5.解決方法
智能指針。
6.檢查、定位內存泄漏
1.window上檢查方法:在main函數最后面一行,加上一句_CrtDumpMemoryLeaks()。調試程序,自然關閉程序讓其退出,查看輸出:
輸出這樣的格式{453}normal block at 0x02432CA8,868 bytes long
被{}包圍的453就是我們需要的內存泄漏定位值,868 bytes long就是說這個地方有868比特內存沒有釋放。
定位代碼位置
在main函數第一行加上_CrtSetBreakAlloc(453);意思就是在申請453這塊內存的位置中斷。然后調試程序,程序中斷了,查看調用堆棧。加上頭文件#include <crtdbg.h>
2.linux:?先可以通過觀察猜測是否可能發?內存泄漏,Linux中使? swap 命令觀察還有多少可?的交換空間,在?兩分鐘內鍵?該命令三到四次,看看可?的交換區是否在減少。
3.還可以使?其他?些 /usr/bin/stat ?具如 netstat、 vmstat 等。如發現波段有內存被分配且從不釋放,?個可能的解釋就是有個進程出現了內存泄漏。
3.?于內存調試,內存泄漏檢測以及性能分析的軟件開發?具 valgrind 這樣的?具來進?內存泄漏的檢測。
97、 內存溢出定位
如果是Windows,考慮下Visual Leak Detector,這個對泄露點信息定位相當不錯;如果是Linux,考慮下Valgrind,檢測內存使用以及線程方面的Bug功能非常強大
98、 什么是內存對齊
現在計算機內存空間都是按照byte字節劃分的,理論上講對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經常在特定的內存地址上訪問,這就需要各種數據類型按照一定的規則在空間上排列,而不是一個接一個的排放,這就是內存對齊。
99、 為什么內存對齊
1.平臺原因(移植原因)
2.不是所有的硬件平臺都能訪問任意地址上的任意數據的;
3.某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件異
4.性能原因:
○1數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。
○2原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。
100、 內存對齊規則
在不用#pagrama pack()包裹的情況下,結構體或聯合體按照編譯器默認的對齊方式有以下三個對其原則:
1.數據成員對齊原則:結構(struct或union)的數據成員,第一個數據成員存放在offset為0的地方,以后每個數據成員存儲的起始位置都要從其占用內存大小的整數倍開始。
2.結構體作為成員的原則:如果一個結構中有某些結構體成員,則結構的成員要從其內部最大元素大小的整數倍地址開始存儲。(struct a里有struct b,b里有char,int,double等元素,那b應該從8的整數倍開始,當然了,如果struct b在結構體a開始位置,則直接考慮其大小)
3.結構(或聯合)的整體對齊原則:在數據成員各自對齊后,結構(或聯合)本身也要進行對齊,即以結構體內部占用內存空間最大的數據類型進行對齊。(等同于sizeof該結構體的結果必須是其內部最大成員占用內存的整數倍)。
示例1:
示例2:
struct B { int a; char b; int c; }; struct A { char x1; B b; short x2; float x3; char x4; }aa; //#pragma pack() int main() { printf("%d\n", sizeof(struct A)); //printf("%x,%x,%x\n", aa.a, *(&aa.b + 1), aa.c); //printf("%p,%p,%p\n", &aa.a, &aa.b, &aa.c); return 0; }添加了#pragma pack(n)后規則就變成了下面這樣:
1、 偏移量要是n和當前變量大小中較小值的整數倍
2、 整體大小要是n和最大變量大小中較小值的整數倍
3、 n值必須為1,2,4,8…,為其他值時就按照默認的分配規則
總結
以上是生活随笔為你收集整理的二十万字C/C++、嵌入式软开面试题全集宝典五的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ftpclient读取服务器文件能获得文
- 下一篇: 输入文字加下划线_微信昵称这样设置,文字