中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當前位置: 首頁 > news >正文

wordpress 頁面二維碼徐州seo培訓

wordpress 頁面二維碼,徐州seo培訓,網(wǎng)站是怎么建成的,電商建網(wǎng)站運營51. 野指針和內存泄漏是什么?如何避免? ????  內存泄漏:是指程序中以動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果。 ????  避免&…

51. 野指針和內存泄漏是什么?如何避免?

????  內存泄漏:是指程序中以動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果。
????  避免:使用智能指針管理資源,在釋放對象數(shù)組時使用delete,盡量避免在堆上分配內存。

????  野指針指向一個已經(jīng)刪除的對象或未申請訪問受限內存區(qū)域的指針。
????  避免:對指針進行初始化。用已合法的可訪問內存地址對指針初始化,指針用完釋放內存,將指針賦值nullptr

解釋
????  內存泄露確實是編程中一個常見且嚴重的問題,特別是在使用如C++這樣的手動內存管理的語言中。內存泄漏發(fā)生在程序動態(tài)分配(通過new或malloc)給某個指針的內存,在使用完畢后未能釋放(通過delete或free)。如果未釋放的內存也無法被訪問,則會無法在程序的其余部分將其釋放。添加到此,程序再次請求內存,這會消耗更多的內存資源,最終可能導致資源耗盡甚至系統(tǒng)崩潰。

對于避免內存泄漏,你的建議是準確的:

  1. 使用智能指針管理資源:智能指針,如unique_ptr, shared_ptr, weak_ptr,被用作對象的守護者,在智能指針的生命周期結束時,它們自動刪除關聯(lián)的原始指針,防止內存泄漏。
  2. 不要忘記在釋放對象數(shù)組時使用delete []:delete和delete []不同,后者被設計用來釋放動態(tài)分配的數(shù)組。如果你有一個通過new []動態(tài)分配的數(shù)組,你必須使用delete []去釋放它。
  3. 盡量避免在堆上分配內存:雖然有時候你必須動態(tài)分配內存,在可能的情況下盡量減少使用。如果可以的話,試圖使用棧上的對象或集合類型,如std::array或std::vector,這些類型在超出范圍時自動清理他們的元素。

????  野指針是指向一個已經(jīng)釋放內存地址或一個未成功分配的內存地址的指針,這些指針的行為是未定義的,而且它們的使用常常會導致程序崩潰。

在使用指針期間,很重要的一點是盡量減少潛在的出錯點和程序的復雜性。避免野指針的一些常見方法包括:

  1. 對指針進行初始化:不初始化的指針變量會導致一個運行時錯誤。它會持有一個隨機的與機器環(huán)境有關的值,這意味著你不能預期它的行為。因此,應始終在聲明指針時對其進行初始化。
  2. 用已合法的可訪問內存地址對指針初始化:這確保你的指針在使用之前總是指向一個你可以訪問且由你管理的內存地址。
  3. 指針用完釋放內存:當你完成使用一個動態(tài)分配的內存塊后,應該立刻釋放它以防止內存泄漏。然而,重要的一點是,刪除內存區(qū)域之后不要再次使用原指針。
  4. 將指針賦值為nullptr:刪除一個指針后,立即將其設為nullptr是一個很好的實踐。這可以防止你不小心訪問之前釋放的內存區(qū)域。因為在嘗試訪問null指針時,大多數(shù)系統(tǒng)都會立即產(chǎn)生一個錯誤并終止程序,這快速的失敗可以讓你立刻發(fā)現(xiàn)問題。

52. malloc和new的區(qū)別是什么?

  1. malloc/free是標準庫函數(shù) , new/delete是C++運算符。
  2. malloc分配內存失敗返回空 , new失敗拋異常
  3. new/delete會調用構造析構函數(shù),malloc/free不會,所以他們無法滿足動態(tài)對象的要求
  4. new返回有類型的指針,malloc返回無類型的指針
  5. 分配內存的位置:malloc從堆上動態(tài)分配內存,new是從自由存儲區(qū)為對象動態(tài)分配內存(取決于operator new的實現(xiàn),可以為堆還可以是靜態(tài)存儲區(qū))

申請內存的步驟:

  1. new:調用oeprator new函數(shù),分配一塊足夠大的、且原始的、未命名的內存空間來存儲特定類型的對象。運行相應的構造函數(shù)來構造對象,并為其傳入初值,返回一個指向該對象的指針。
  2. delete:先調用對象的析構函數(shù),再調用operator delete函數(shù)釋放內存空間。

53. 多線程會發(fā)生什么問題?線程同步有哪些手段?

????  會引發(fā)資源競爭的問題,頻繁上鎖會導致程序運行效率低下,甚至會導致發(fā)生死鎖。

????  線程同步手段:使用atomic原子變量,使用互斥量也就是上鎖,使用條件變量信號量制約對共享資源的并發(fā)訪問。

解釋
????  當多個線程并發(fā)訪問同一資源時,無論是讀取還是寫入,如果沒有合適的同步手段,都可能引發(fā)數(shù)據(jù)的不一致性和競態(tài)條件。為了解決這個問題,我們通??梢允褂靡韵聨追N常見的線程同步機制,其中你提到的都包括在內:

  1. 原子操作:原子操作是以不可分割的方式執(zhí)行的操作。也就是說,執(zhí)行原子操作的線程在操作過程中不會被調度器打斷。在C++中,我們可以使用std::atomic類或者具有原子性的內置操作來實現(xiàn)原子操作。
  2. 互斥量(Mutex):互斥量是一種同步手段,它可以保證同一時刻只有一個線程能夠訪問特定的資源。在C++中,我們可以使用std::mutex類來創(chuàng)建互斥量。
  3. 條件變量:條件變量是另一種同步手段,它允許線程在滿足特定條件時才能訪問特定資源。這可以通過std::condition_variable類來實現(xiàn)。
  4. 信號量:信號量是一種計數(shù)的同步手段,它允許一定數(shù)量的線程同時訪問某個資源。信號量通常被用于保護線程池,連接池等資源,它可以限制同時訪問這些資源的最大線程數(shù)。

54. 什么是STL?

????  他是C++標準庫的重要組成部分,不僅是一個可復用的組件庫也是一個包含了數(shù)據(jù)結構與算法的軟件架構,它擁有六大組件分別是:仿函數(shù)、算法、迭代器、空間配置器、容器、配接器。

解釋

  1. 容器(Containers): STL中的容器是一種數(shù)據(jù)結構,可以存儲各種類型的數(shù)據(jù)。例如,數(shù)組,鏈表,棧,隊列,哈希表等。
  2. 算法(Algorithms): STL包含了許多通用的算法,例如,排序,查找,拷貝,修改等。這些算法被設計成可以在任何容器上工作。
  3. 迭代器(Iterators): 迭代器提供了一種統(tǒng)一的訪問容器元素的方法。你可以把迭代器看作一個指向容器元素的指針。
  4. 仿函數(shù)(Functors): 仿函數(shù)(也叫函數(shù)對象)是一個行為類似函數(shù)的對象。你可以像調用函數(shù)一樣來調用仿函數(shù)。仿函數(shù)通常與STL算法結合使用。
  5. 適配器(Adapters): STL適配器提供了一種方式來修改或擴展容器,算法或迭代器的行為。
  6. 內存分配器(Allocators): STL內存分配器定義了如何分配和回收STL容器使用的內存。

55. 對比迭代器和指針的區(qū)別?

????  迭代器不是指針,是一個模板類,通過重載了指針的一些操作符模擬了指針的一些功能迭代器返回的是對象引用而不是對象的值。

????  指針能夠指向函數(shù)而迭代器不行,迭代器只能指向容器

解釋

????  首先,如你所說,迭代器本質上是一個模板類,它比指針更加復雜。迭代器包含了更多的信息,例如迭代器類別(輸入迭代器、輸出迭代器、前向迭代器、雙向迭代器、隨機訪問迭代器)等,這些類別對其可用操作有所限制。

????  其次,迭代器通常返回對象引用,這意味著你可以修改它所指向的值,有些迭代器甚至可以運行在那些不支持原生指針操作的容器上,例如鏈表(list)和關聯(lián)容器(如set和map)。

????  最后,正如你所說,迭代器僅用于指向容器中的元素,它不可以指向函數(shù)或其他非容器類型。這是因為迭代器被設計為在容器中遍歷元素,而不是像指針那樣可以廣義地指向內存中的任意地址。

????  因此,盡管迭代器在表現(xiàn)上與指針類似,但更重要的是,它們?yōu)閿?shù)據(jù)的抽象和操作提供了一種一致且安全的接口。

56. 線程有哪些狀態(tài) , 線程鎖有哪些?

五種狀態(tài):創(chuàng)建,就緒,運行,阻塞,死亡
線程鎖的種類:互斥鎖、條件鎖、自旋鎖、讀寫鎖、遞歸鎖

解釋
線程的五種狀態(tài):

  1. 創(chuàng)建:線程資源被分配,但還沒有開始執(zhí)行。
  2. 就緒:線程被調度到了就緒隊列,等待系統(tǒng)分配處理器。
  3. 運行:線程獲得CPU,開始執(zhí)行代碼。
  4. 阻塞:線程在等待某些事件(例如I/O操作或者鎖)發(fā)生而暫停執(zhí)行。
  5. 結束:線程已完成任務并退出。

線程鎖,也就是用于同步多線程間對共享資源訪問的機制。你列舉的線程鎖的種類幾乎涵蓋了所有,這里我詳細點一下:

  1. 互斥鎖:用于保護共享資源,一次只允許一個線程訪問共享資源。

  2. 條件鎖(條件變量):允許線程在某個條件尚未滿足時候進入睡眠,滿足條件時候喚醒。

  3. 自旋鎖:如果無法立刻獲取鎖,線程會一直在一個循環(huán)中請求鎖,直到獲取為止。通常在鎖持有時間很短的情況下使用。

  4. 讀寫鎖:區(qū)分讀操作和寫操作。多個線程可以同時進行讀操作,但是寫操作會阻塞其他的所有操作(讀和寫)。

  5. 遞歸鎖:允許一個線程多次獲取同一個鎖,減少死鎖的可能性。

57. 解釋說明一下map和unordered_map

????  Map內部實現(xiàn)是一個紅黑樹,內部所有的元素都是有序的,而hashmap則是內部實現(xiàn)了一個哈希表,內部存儲元素是無序的。

Map優(yōu)點:有序性,其次是內部實現(xiàn)的是一個紅黑樹,使得很多操作都可以在logn的復雜度下可以實現(xiàn)效率較高。
Map缺點:空間占用率高。
unordered_map優(yōu)點:查找效率高。
unordered_map缺點:哈希表的建立比較費時間。

解釋
????  map是一種基于紅黑樹實現(xiàn)的關聯(lián)數(shù)組,它存儲的鍵值對是有序的。紅黑樹保證了每次插入和刪除都是對數(shù)時間復雜度,從而使整體性能很好。然而,由于紅黑樹是一種比較復雜的數(shù)據(jù)結構,所以map相比于unordered_map來說占用的空間會比較大。

????  另一方面,unordered_map是基于哈希表的。它的主要優(yōu)點是查找時間復雜度通??梢赃_到常數(shù)級別,這使得它在處理大量數(shù)據(jù)時非常高效。然而,哈希表的建立和維護需要花費一定的時間,特別是當發(fā)生哈希沖突時,可能需要更復雜的沖突解決策略。此外,由于unordered_map的元素是無序的,所以如果需要對數(shù)據(jù)進行排序或頻繁的范圍查詢,map可能是更好的選擇。

58. vector中的push_back()和emplace_back()的區(qū)別、以及使用場景

????  當使用push_back時會先調用類的有參構造函數(shù)創(chuàng)建一個臨時變量,再將這個元素拷貝或者移動到容器之中。
????  而emplace_back()則是直接在容器尾部進行構造,比push_back少一次構造函數(shù)的調用。

????  在大部分場景中emplace_back()可以替換push_back(),但是push_back()會比emplace_back()更加安全,emplace_back()只能用于直接在容器中構造新元素的情況,如果將現(xiàn)有的對象添加到容器中則需要使用push_back()。

解釋
????  就像解釋的那樣,push_back()和emplace_back()的主要區(qū)別在于它們處理對象構造的方式。push_back()通常在向容器添加對象時創(chuàng)建一個新的對象實例,然后把它復制或移動到容器中。相反,emplace_back()則嘗試在容器里直接構造對象,省去了創(chuàng)建臨時對象和復制/移動的過程。

????  因此,當處理那些構造、復制或者移動成本很高的對象時,在性能上,emplace_back()通常比push_back()更有優(yōu)勢。特別是當元素的構造函數(shù)接受多個參數(shù)的時候,使用emplace_back()可以避免寫出繁瑣的構造臨時對象的代碼。

????  然而,push_back()確實在某些情況下更安全。因為emplace_back()能隱式轉換類型,比如如果有一個容器是裝int的,如果你用emplace_back()向里面添加一個double類型的數(shù)據(jù),編譯器會隱式把這個double類型的數(shù)據(jù)轉換為int,可能會造成不可預測的錯誤。此外,push_back()也更適用于需要添加已存在的對象到容器中的場景。

59. 如何實現(xiàn)線程安全,除了加鎖還有沒有其他的方式?

????  除了鎖之外還可以使用互斥變量(防止多個線程來同時訪問共享資源,從而避免數(shù)據(jù)競爭問題),原子操作(原子操作是不可分割的,使用原子操作可以確保在多線程環(huán)境下操作是安全的),條件變量(協(xié)調線程之間的協(xié)作,用來在線程之間傳遞信號,從而控制線程的執(zhí)行流程)等方式。

60. 用vector擴容,resize和reserve的區(qū)別

????  使用resize改變的是vector的大小(size)可能會添加或刪除元素。
????  使用reserve改變的是vector的容量(capacity)不會改變當前元素的值僅僅是為了優(yōu)化內存使用和性能。

解釋
????  resize()和reserve()都是用于改變vector的大小,但它們的作用方式并不相同。

????  resize()函數(shù)會改變vector的size,也就是它的元素數(shù)量。如果新的size比當前大,那么在vector的尾部會添加上默認值或者指定的值。如果新的size小于當前size,那么vector尾部超出的元素會被刪除。

????  相反,reserve()函數(shù)并不改變vector的size,而是改變它的容量,即vector在必須分配更大的內存空間以前可容納的元素數(shù)量。當我們用reserve增加vector的容量時,其目的是為了防止在進行連續(xù)插入操作時出現(xiàn)多次內存的分配和拷貝。

????  舉例來說,如果你有一個空的vector,然后執(zhí)行reserve(100),那么這個vector的容量就是100,但它的大小仍然是0,因為實際上并沒有任何元素被添加進來。如果之后你再進行100次push_back操作,由于已經(jīng)預留了足夠的空間,所以這些操作都能立即完成,不需要做任何內存分配和元素拷貝。這使得程序的效率得以提高。

61. vector擴容為了避免重復擴容做了哪些機制?

????  當vector內存不夠時本身內存會以1.5或者2倍的增長,以減少擴容次數(shù)。
????  引入了reserve,自定義vector最大容量。

62. C++中空類的大小是多少?

????  1字節(jié)

63. weak_ptr是怎么實現(xiàn)的?

????  實現(xiàn)依賴于計數(shù)器寄存器實現(xiàn)的,計數(shù)器用來記錄弱引用的數(shù)量寄存器用來存儲shared_ptr

解釋
????  weak_ptr 的設計是以引用計數(shù)器和一個指向共享對象的寄存器為基礎的。

????  在 C++ 中,智能指針(如 shared_ptr 和 unique_ptr)負責自動管理對象的生命周期,以避免手動分配和釋放內存。然而,這種機制可能導致循環(huán)引用問題,尤其是當有兩個或多個對象互相引用時。為了解決這個問題,引入了 weak_ptr。
???? 
????  weak_ptr 是一個智能指針類型,它指向一個由 shared_ptr 所管理的對象,但是對于對象的引用計數(shù)不會增加。這意味著,即便所有的 shared_ptr 都已經(jīng)被銷毀,只要還有至少一個 weak_ptr,那么這個對象的內存地址不會被立刻回收,只是無法再被訪問。

具體來說,weak_ptr 的實現(xiàn)過程如下:

  1. 一個 weak_ptr 基于一個 shared_ptr 創(chuàng)建
  2. weak_ptr 將 shared_ptr 內的弱引用計數(shù)加一(weak reference count)
  3. 當最后一個 shared_ptr 被析構后,對象的數(shù)據(jù)部分被清理掉,但是控制塊(包括弱引用計數(shù)和對象自身)仍保留
  4. 當最后一個 weak_ptr 被析構,或者一個 weak_ptr 試圖訪問一個已經(jīng)被 shared_ptr 清理的對象時,weak_ptr 會將弱引用計數(shù)減一。如果弱引用計數(shù)降為 0,那么對象的控制塊也會被清理掉,對象徹底被清理干凈
  5. weak_ptr 不會直接提供對對象的訪問權,為了訪問對象,必須先將 weak_ptr 轉換為 shared_ptr,如果對象已經(jīng)被清理,轉換操作會得到一個空的 shared_ptr。

總的來說,weak_ptr 是一種弱引用,它不會影響對象的生命周期,可以用來解決循環(huán)引用的問題。它的實現(xiàn)依賴于引用計數(shù)器和一個與 shared_ptr 共享的寄存器。

64. 虛函數(shù)的底層原理是什么?

????  虛函數(shù)表和虛指針,詳細看第四問。[[#^9383bd|虛函數(shù)的實現(xiàn)]]

65. 一個函數(shù)f(int a , int b),其中a和b的地址關系是什么?

????  a和b的地址是相鄰的。

解釋:
????  在函數(shù)f(int a, int b)中,形式參數(shù)a和b在棧中的地址通常是相鄰的。然而,它們之間的具體地址關系(也就是說,哪個參數(shù)的地址更高或更低)可能取決于操作系統(tǒng)和編譯器的具體實現(xiàn)。

????  在許多主流的編譯器和操作系統(tǒng)中,如gcc和Windows,函數(shù)的參數(shù)在棧上以從右到左的順序被壓入。這應該意味著b的地址高于a的地址。

????  然而,這是一種實現(xiàn)細節(jié),可能會因不同的操作系統(tǒng)和編譯器而變化。除非你正在編寫依賴于特定平臺特性的低級代碼,否則通常不建議編寫依賴于此類內存布局細節(jié)的代碼。

66. 移動構造和拷貝構造的區(qū)別是什么?

????  移動構造函數(shù)本質上是基于指針的拷貝,實現(xiàn)對堆區(qū)內存所有權的移交,在一些特定場景下,可以減少不必要的拷貝。比如用一個臨時對象或者右值對象初始化類實例時,我們可以使用move函數(shù),將一個左值對象轉變?yōu)橛抑祵ο蟆?br /> ????  而拷貝構造則是將傳入對象復制一份然后放進新的內存中。

解釋

  1. 拷貝構造函數(shù):當我們創(chuàng)建一個新對象時,如果該對象是通過另一個已存在的對象初始化的,那么拷貝構造函數(shù)就會被調用??截悩嬙旌瘮?shù)會將原對象的所有屬性一一復制到新的對象。這種復制是值復制,也就是說新舊兩個對象雖然屬性值相同,但是它們在內存中的地址是不同的,互不影響。
  2. 移動構造函數(shù):和拷貝構造函數(shù)相比,移動構造函數(shù)不是復制原有對象的值,而是“竊取”原有對象的資源(例如內存等),而原對象則會被置入一個安全的、析構的狀態(tài)。例如,原對象是一個動態(tài)數(shù)組,則移動構造后,原數(shù)組將不再擁有這些動態(tài)內存,而新對象則接管了這部分內存的所有權。這樣就避免了不必要的復制操作,大大提高了效率。

????  需要注意的是,只有當源對象是一個右值,即臨時對象或者已經(jīng)被移動過的對象時,編譯器才會自動使用移動構造函數(shù)。如果源對象是一個左值,那么編譯器通常會使用拷貝構造函數(shù),這是為了保護數(shù)據(jù)的安全性。而我們可以通過 std::move 顯式告訴編譯器我們要使用移動構造函數(shù)轉移這個左值對象。

????  總而言之,拷貝構造和移動構造在語義上是不同的:拷貝構造的語義是復制,而移動構造的語義則是轉移。

67. lamda表達式捕獲列表,捕獲的方式有哪些?如果是引用捕獲需要注意什么?

????  按值捕獲引用捕獲,默認的引用捕獲可能會導致懸掛引用,引用捕獲會導致閉包包含一個局部變量的引用或者形參的引用,如果一個由lamda創(chuàng)建的閉包的生命周期超過了局部變量或者形參的生命期,那么閉包的引用將會空懸。解決方法是對個別參數(shù)使用值捕獲。

解釋
????  lambda 表達式的捕獲列表主要有兩種捕獲方式:按值捕獲按引用捕獲。我再來為你補充一些關于捕獲方式的細節(jié)和注意事項:

  1. 按值捕獲:這種方式會將外部變量在創(chuàng)建閉包(即 lambda)時進行拷貝,因此閉包對這些變量的使用實際上是對它們的拷貝進行操作。這樣得到的閉包內部的數(shù)據(jù)是獨立的,不會影響外部變量的狀態(tài)。使用方式是?[=]。

  2. 按引用捕獲:這種方式會將外部變量以引用的方式在創(chuàng)建閉包時進行捕獲,并且閉包對iil這些變量的使用實際上是直接對它們進行操作。這樣得到的閉包內部的數(shù)據(jù)和外部的數(shù)據(jù)是同一個,互相會進行影響。使用方式是?[&]。

????  關于按引用捕獲,最需要注意的就是你已經(jīng)提到的懸掛引用問題。正是由于閉包對變量的引用直接影響了變量本身,如果閉包的使用生命周期超過了其引用的外部變量的生命周期,那么當閉包在后續(xù)使用這個變量時,就會出現(xiàn)懸掛引用,引發(fā)未定義行為。這種情況常常出現(xiàn)在閉包被作為函數(shù)返回值返回,或者被長期存儲在某個數(shù)據(jù)結構中的場合。解決此類問題的最常見方法就是你提到的:僅對需要長期存儲的變量使用按值捕獲,對生命周期較短或者僅在創(chuàng)建閉包時使用的變量,仍然可以使用按引用捕獲。


#include <iostream> 
// 在這個函數(shù)中,我們創(chuàng)建了一個閉包,也就是一個lambda表達式。
// 這個閉包會捕獲變量x,并通過引用進行存儲和自增。
// 注意到這個函數(shù)返回的是一個函數(shù),即返回的是我們創(chuàng)建的lambda表達式函數(shù)對象 
std::function<int()> create_counter(int x) { return [&x]() { return ++x; }; }        //按值捕獲
//std::function<int()> create_counter(int &x) { return [&x]() { return ++x; }; }  //按引用捕獲int main() {int k = 2;auto counter = create_counter(k); std::cout << counter() << std::endl; // 輸出3std::cout << counter() << std::endl; // 輸出4 std::cout << counter() << std::endl; // 輸出5 cout << k << endl;                            // 輸出2return 0; 
}

????  在這個代碼中,create_counter函數(shù)返回了一個捕獲了變量x的lambda表達式。該lambda表達式定義了一個計數(shù)器,可以自增x并返回結果。

????  即使create_counter函數(shù)的調用已經(jīng)完成,閉包依然可以訪問并修改x。

????  這就是閉包的魔力–在函數(shù)作用域外部可以持久保存函數(shù)內部的狀態(tài)。在定義閉包的地方和使用閉包的地方,你都可以訪問和修改這些捕獲的變量。

68. 哈希碰撞的處理方法

  1. 開放地址法 : 當遇到哈希沖突時,去尋找一個新的空閑的哈希地址。
  2. 再哈希法 : 同時構造多個哈希函數(shù),等發(fā)生哈希沖突時就使用其他哈希函數(shù)直到不發(fā)生沖突為止,雖然不易發(fā)生聚集,但是增加了計算時間。
  3. 鏈地址法 : 將所有的哈希地址相同的記錄都鏈接在同一鏈表中。
  4. 建立公共溢出區(qū) : 將哈希表分為基本表和溢出表,將發(fā)生沖突的都存放在溢出表中。

解釋

  1. 開放地址法:這種方法具有直觀性和簡單性。一旦原本的哈希地址被占據(jù),就會去尋找下一個可用的地址。這種方法容易造成“聚集”,即連續(xù)的內存空間被連續(xù)的元素占據(jù),這會使得搜索時間增加。
  2. 再哈希法:當哈希沖突發(fā)生時,使用另一個哈希函數(shù)。這種方法可以減少聚集的發(fā)生,但如你所說的,多個哈希函數(shù)會帶來額外的計算成本。
  3. 鏈地址法:沖突的元素會被存在同一鏈表也是一種常見的處理沖突的方法。每個鏈表都對應一個哈希地址,這樣可以在一定程度上減少查找時間。
  4. 建立公共溢出區(qū):這種方法分離了主哈希區(qū)域和沖突區(qū)域,主哈希區(qū)域存放未沖突的元素,而沖突的元素存放在另一塊專門的區(qū)域里。這種方法的優(yōu)點是可以方便地處理沖突,并且插入和查找的速度相對較快。當然,它的缺點就是需要額外的空間來存儲沖突的元素。

69. unordered_map的擴容過程

????  當unordered_map中的元素數(shù)量達到了桶的負載因子(0.75) 時,會重新分配桶的數(shù)量(通常會按照原有桶的數(shù)量* 2 的方式進行擴容,但是具體的增長策略也可以通過修改容器中的max_load_factor成員變量來進行調整),并將所有的元素重新哈希到新的桶中。

解釋:
????  在C++中,無序映射(unordered_map)在插入新元素導致容器大小超過負載因子(默認為0.75)乘以桶數(shù)時,會進行重新哈希,這個過程也就是我們通常說的擴容。

????  在重新哈希過程中,容器會選擇一個新的更大的散列桶(通常是原來的兩倍),然后它會重新計算每個元素的散列值,并將它們放入新的散列桶中。從理論上講,這將保持數(shù)據(jù)的均勻分布,減少沖突,并使查找效率保持在常數(shù)時間復雜度。

????  但是,需要注意的是,重新哈希是一個代價較高的操作,因為它通常涉及具有大O(n)復雜度的動態(tài)內存分配和元素的復制。因此,在構造unordered_map時,如果你對將要插入的元素數(shù)量有預期,可以提前使用reserve函數(shù)來預留更多的空間,避免在插入過程中觸發(fā)多次重新哈希,從而提高性能。

這里有一段簡單的代碼演示了擴容過程:

#include <unordered_map>
#include <iostream>int main() {std::unordered_map<int, int> map;std::cout << "Initial bucket count: " << map.bucket_count() << "\n";for (int i = 0; i < 100; ++i)map[i] = i;std::cout << "Bucket count after insertion: " << map.bucket_count() << "\n";return 0;
}

????  這段代碼首先打印出初始的桶數(shù),然后在映射中插入100個元素,并再一次打印出桶數(shù)。在大多數(shù)平臺上,你會看到桶數(shù)在插入元素后有增長,這就是擴容過程。

70. vector如何判斷應該擴容?(size和capacity)

????  由當前容器內元素數(shù)量的大小容器最大大小進行比較,如果二者相等就會進行擴容,一般是1.5倍,部分的2倍。

71. 構造函數(shù)是否能聲明為虛函數(shù)?為什么?什么情況下為錯誤?

????  構造函數(shù)不能聲明為虛函數(shù),虛函數(shù)的調用是通過虛函數(shù)表來查找的,而虛函數(shù)表由類的實例化對象的vptr指針指向,該指針存在對象的內部空間里,需要調用構造函數(shù)完成初始化,如果構造函數(shù)為虛函數(shù),那么調用構造函數(shù)就需要去尋找vptr,但此時vptr還沒有完成初始化,導致無法構造對象。

72. 類中static函數(shù)是否能聲明為虛函數(shù)?

????  不能。因為類中的static函數(shù)是所有類實例化對象所共有的,沒有this指針,而虛函數(shù)依靠vptr和vtable來處理,vptr是一個指針,在類的構造函數(shù)中生成,并且只能通過this指針訪問,對于靜態(tài)成員函數(shù)來說,他沒有this指針,無法訪問vptr,因此static函數(shù)無法聲明為虛函數(shù)。

解釋
????  首先,我們要明白靜態(tài)成員函數(shù)和普通成員函數(shù)的差異。靜態(tài)成員函數(shù)是所有該類實例共享的,他們不操作類的實例變量,且能夠在沒有類實例的情況下調用。因此,靜態(tài)成員函數(shù)沒有this指針。

????  然而,虛函數(shù)被設計為通過對象實例的this指針訪問,它用于支持多態(tài),使得我們可以通過基類指針調用派生類的實現(xiàn)。它依賴于vptr(指向虛函數(shù)表vtable的指針)。vptr是在類的構造函數(shù)中創(chuàng)建的,并且只能通過this指針訪問。由于靜態(tài)成員函數(shù)沒有this指針,因此它無法訪問vptr,所以靜態(tài)成員函數(shù)不能是虛函數(shù)。

73. 哪些函數(shù)不能被聲明為虛函數(shù)?

????  構造函數(shù)、內聯(lián)函數(shù)(內聯(lián)函數(shù)有實體,在編譯時展開,沒有this指針)、靜態(tài)成員函數(shù)、友元函數(shù)(C++不支持友元函數(shù)繼承)、非類成員函數(shù)

解釋

  1. 構造函數(shù):虛函數(shù)依賴于對象,它們通過對象的虛表(vtable)來查找對應的函數(shù)實現(xiàn),而在構造函數(shù)中,對象還在初始化過程中,還沒有完全構建完成,虛表可能還沒有正確設置,因此構造函數(shù)不能為虛函數(shù)。
  2. 靜態(tài)成員函數(shù):靜態(tài)成員函數(shù)與特定的對象實例沒有關聯(lián),它屬于類而不是對象,因此無法使用虛函數(shù)的動態(tài)綁定特性。
  3. 內聯(lián)函數(shù):內聯(lián)函數(shù)在編譯的時候就已經(jīng)被展開,它們沒有像虛函數(shù)那樣的動態(tài)綁定過程。
  4. 友元函數(shù):友元函數(shù)不是類的成員,它們只是能夠訪問類的私有和保護成員,因此不能被聲明為虛函數(shù)。
  5. 非類成員函數(shù):既然不是類的成員,自然不能被聲明為虛函數(shù)。

類和對象的關系
????  當我們在編程語言中定義一個類時,我們實際上是在定義一個數(shù)據(jù)類型的藍圖或模板。類定義了數(shù)據(jù)類型的屬性(即數(shù)據(jù)成員)以及該數(shù)據(jù)類型可以執(zhí)行的操作(即成員函數(shù)或方法)。

????  然而,類本身并沒有占用任何內存空間。只有當我們根據(jù)類創(chuàng)建對象實例時,系統(tǒng)才會為對象分配內存,我們可以把對象實例視作是類的一個具體實例,它包含了由類定義的所有屬性和方法。

????  簡單來說,類就像是一個圖紙,而對象實例就像是根據(jù)這個圖紙制造出來的產(chǎn)品。就比如,如果我們有一個“汽車”類,它定義了汽車的各種屬性(如顏色、型號等)和操作(如開車、停車等)。然后,我們可以根據(jù)這個“汽車”類創(chuàng)建許多具體的汽車對象,比如一輛紅色的BMW,一輛藍色的奔馳等等,每一輛車就是一個對象實例。

????  以下是一段代碼,定義了一個名為Car的類,并創(chuàng)建了一個該類的對象實例:

class Car(object):def __init__(self, color, brand):self.color = colorself.brand = branddef drive(self):print(f"A {self.color} {self.brand} is driving.")# 創(chuàng)建一個對象實例
my_car = Car("red", "BMW")
my_car.drive()

????  在這段代碼中,我們首先定義了Car類,然后創(chuàng)建了一個Car類的對象實例my_car,然后調用了這個對象的drive()方法,輸出“A red BMW is driving.”。

????  所以,類是對一類對象的抽象定義,而對象實例則是這個定義的具體化。

為什么靜態(tài)成員函數(shù)不能使用虛函數(shù)的動態(tài)綁定特性?
????  當我們在類中聲明一個函數(shù)為靜態(tài)函數(shù)時,這個函數(shù)就成為了這個類的一部分,它和類的任何特定的對象實例都沒有關系。靜態(tài)函數(shù)是類的一個屬性,而不是對象的屬性。

????  靜態(tài)函數(shù)只能直接訪問類的靜態(tài)成員變量或靜態(tài)成員函數(shù),它不能訪問類的非靜態(tài)成員變量或非靜態(tài)成員函數(shù),也就是說,它不能使用類的this指針。

????  虛函數(shù)的動態(tài)綁定或者叫多態(tài)是面向對象編程的一種特性,它允許我們通過基類指針或引用來調用派生類中重寫的虛函數(shù)。這個特性主要用于實現(xiàn)不同的對象能以自己的方式來響應同一消息。但是,對于靜態(tài)成員函數(shù)來說,由于它不與任何的對象實例相關聯(lián),因此無法使用動態(tài)綁定。

????  這就意味著,你不能將靜態(tài)成員函數(shù)聲明為虛函數(shù)來實現(xiàn)多態(tài)性。因為虛函數(shù)依賴于具體的對象以實現(xiàn)運行時的多態(tài)性,而靜態(tài)成員函數(shù)并沒有與任何對象實例綁定,它只能通過類來調用,不能通過對象來調用。

????  對于靜態(tài)成員函數(shù)來說,它的調用通常都是已經(jīng)確定的,無法在運行時進行動態(tài)的更改。而對于虛函數(shù)來說,它的調用直到運行時才能確定具體的調用哪個版本的函數(shù)。因此,靜態(tài)成員函數(shù)無法使用虛函數(shù)的動態(tài)綁定特性。

74. 如何保證類的對象只能被開辟在堆上?(將構造函數(shù)聲明為私有、單例)

????  將構造函數(shù)設置為私有,這樣只有使用new運算符來建立對象,但是我們必須準備一個destory函數(shù)來進行內存的釋放,然后將析構函數(shù)設置為protected,提供一個public的static函數(shù)來完成構造,類似于單例模式。

????  如果在棧上分配呢?則是重載new操作符,使得new操作符的功能為空,這樣就使得外層程序無法在堆上分配對象,只可以在棧上分配。

解釋

  1. 在堆上
  • 將構造函數(shù)設為私有:這可以防止在棧上直接創(chuàng)建對象實例。

  • 提供一個public的靜態(tài)函數(shù)來完成構造:這個靜態(tài)方法可以調用私有構造函數(shù)在堆上創(chuàng)建對象,并返回對象的指針。

  • 將析構函數(shù)設置為protected:這一步一方面可以防止用戶直接delete對象,另一方面保證派生類的析構函數(shù)可以被正確地調用。

  • 提供一個public的destructor方法:在這個方法里可以正確刪除在堆上創(chuàng)建的對象。

class HeapOnly {
private:HeapOnly() {} // 構造函數(shù)私有~HeapOnly() {} // 析構函數(shù)保護
public:static HeapOnly* createInstance() { // 公有的創(chuàng)建對象方法return new HeapOnly();}static void destructor(HeapOnly* instance) { // 公有的析構對象方法delete instance;}
};
  1. 在棧上
    ????  關于只能在棧上創(chuàng)建對象,主要思路是禁止使用new操作符,可以通過以下方式實現(xiàn):
  • 將operator new重載為private:這樣就不能在堆上使用new創(chuàng)建對象。
  • 將析構函數(shù)設為私有:這樣不允許在棧以外的地方創(chuàng)建對象,因為只有在棧上創(chuàng)建的對象,才能保證在作用域結束后自動調用析構函數(shù)。
  • 提供一個public的destructor方法:在這個方法里可以正確刪除在棧上創(chuàng)建的對象.
class StackOnly {
private:void * operator new(size_t size) = delete; // 禁止使用newvoid operator delete(void *p) = delete; // 禁止使用delete~StackOnly() {} // 析構函數(shù)私有
public:static void destructor(StackOnly* instance) { //公有的析構對象方法instance->~StackOnly();}
};

????  以上兩種方法分別可以限制對象只能在堆上或者棧上創(chuàng)建,但是需要注意,這也帶來了很大的不便,使用這些方法的同時也需要在設計上謹慎考慮。

75. 講講你理解的虛基類

????  虛基類是C++中的一種特殊的類,用于解決繼承所帶來的“菱形繼承”問題。如果一個派生類同時從兩個基類派生,而這兩個基類又共同繼承自同一個虛基類,就會形成一個“菱形”繼承結構,導致派生類中存在兩份共同繼承的虛基類的實例,從而引發(fā)一系列問題。

????  為了解決這個問題,我們可以將虛基類作為共同基類,并在派生類中采用虛繼承的方式。

????  虛繼承會使得派生類中只存在一份共同繼承的虛基類的實例,從而避免了多個實例之間的沖突。

????  虛基類是可以被實例化的。

解釋
????  例如,假設我們向Animal類添加一個新的函數(shù)getCounter,這個函數(shù)返回一個counter實例變量。這個counter變量在每次Animal實例調用eat函數(shù)時加一。

class Animal {
private:int counter;public:Animal() : counter(0) {}void eat() { counter++; }int getCounter() { return counter; }
};class Dog : public Animal {
public:void bark() { }
};class Cat : public Animal {
public:void meow() { }
};class Chimera : public Dog, public Cat {
};

????  現(xiàn)在,當我們創(chuàng)建一個Chimera實例并調用eat函數(shù)時,我們可能期望getCounter返回1。但是實際上,因為Chimera包含兩個Animal實例(一個來自Dog, 另一個來自Cat),每個Animal實例都有自己的counter實例變量,所以會出現(xiàn)問題。更糟糕的是,當我們調用getCounter函數(shù)時,編譯器會因為存在歧義而報錯。

????  如果我們將Animal定義為虛基類,那么Chimera實例只會包含一個Animal實例,于是就不會有這個問題了。當你調用eat函數(shù)時,getCounter將正常返回1,符合我們的預期。這解釋了為什么我們在設計類的繼承關系時需要考慮虛基類。

class Animal {
private:int counter;public:Animal() : counter(0) {}void eat() { counter++; }int getCounter() { return counter; }
};class Dog : virtual public Animal {
public:void bark() { }
};class Cat : virtual public Animal {
public:void meow() { }
};class Chimera : public Dog, public Cat {
};

????  雖然虛基類可以被實例化,但如果一個類被聲明為虛類,那么這個類是不能被實例化的。

????  在C++中,抽象類(有時也被稱為虛類)是至少包含一個純虛函數(shù)的類。在C++中,我們可以這樣定義一個純虛函數(shù):virtual void func() = 0;。定義純虛函數(shù)的目的是為了讓派生類必須實現(xiàn)此函數(shù)

????  一旦類中包含了純虛函數(shù),這個類就不能被實例化成一個對象。這種類我們通常稱為抽象類。因為這個類通常定義了一種應該被其他類繼承并實現(xiàn)具體功能的接口,所以它自己并不能創(chuàng)建出具體的對象實例。

class Animal {
public:virtual void eat() = 0;   // 這是一個純虛函數(shù)
};

在這段代碼中,Animal就被定義為了抽象類(或虛類),我們不能直接實例化Animal類:

Animal animal;  // 錯誤:不能實例化抽象類

我們需要創(chuàng)建一個Animal的子類,并在子類中實現(xiàn)eat函數(shù),才能創(chuàng)建一個對象:

class Dog : public Animal {
public:void eat() override {// 實現(xiàn)eat函數(shù)}
};Dog dog;  // 正確:可以實例化Dog類

總結來說就是:

  • 虛基類(Virtual Base Class):當類被聲明為虛基類時,意味著在任何從該類進一步派生的類中,無論這個類在派生類中被繼承了多少次,只有一份基類的數(shù)據(jù)成員會被保留。虛基類的目的主要是解決多繼承情況下的菱形繼承問題。虛基類本身是可以實例化的,只要它沒有包含純虛函數(shù)。

  • 抽象類:抽象類是包含至少一個純虛函數(shù)的類。純虛函數(shù)就像是一個占位符,它在基類中并沒有具體的實現(xiàn),但它要求任何一個從基類派生的類必須提供這個函數(shù)的實現(xiàn)。因為抽象類包含了不完全定義的函數(shù),所以我們不能創(chuàng)建一個抽象類的實例,只能用抽象類作為基類,通過其派生類創(chuàng)建對象。

????  “虛基類可以被實例化”這句話指的應該是虛基類本身沒有純虛函數(shù),所以它可以被實例化;“如果一個類被聲明為虛類(抽象類),那么這個類是不能被實例化的”,這里的“虛類”應該指的是抽象類,它包含至少一個純虛函數(shù),因此不能被實例化。希望這能幫助你更好地理解。

76. C++哪些運算符不能被重載?

????  成員訪問操作符、域解析操作符、條件運算符之類的不能被重載。
????  其中并不推薦對逗號運算符、邏輯或、邏輯與之類的運算符進行重載,容易造成歧義。

解釋
????  首先,讓我們聊聊不能被重載的運算符。在C++中,.(成員訪問運算符)、.*(通過對象指針訪問成員的運算符)、::(域解析運算符)、 ?:(條件運算符)、sizeof(獲取數(shù)據(jù)類型或表達式結果大小的運算符)、typeid(獲取對象數(shù)據(jù)類型的運算符)以及#(預處理運算符)是不能被重載的。

????  至于你提到的逗號運算符(,)、邏輯與運算符(&&)以及邏輯或運算符(||),雖然這些運算符在C++中是可以被重載的,但實際上,我們并不推薦這樣做,原因如下:

  1. 對這些運算符重載可能會對代碼的可讀性造成破壞,使得代碼理解起來更加困難。
  2. 對這些運算符重載可能會混淆它們原有的邏輯(例如,邏輯與運算符本身意味著“而且”,邏輯或運算符代表“或者”,如果重載后邏輯完全改變,那么這無疑會使得代碼閱讀者十分困惑)。
  3. 如果對這些運算符進行重載,而重載的邏輯與原有的邏輯不一致,可能會產(chǎn)生錯誤的預期結果。

????  因此,對逗號運算符(,)、邏輯與運算符(&&)以及邏輯或運算符(||)進行重載,需要在確保代碼邏輯清晰明確,并且對項目沒有負面影響的前提下,并且一定要確保其他的代碼閱讀者也能很好的理解你的代碼。

77. 動態(tài)鏈接和靜態(tài)鏈接的區(qū)別?動態(tài)鏈接的原理是什么?

????  區(qū)別:他們最大的區(qū)別就是在于鏈接得時機不同,靜態(tài)鏈接是在形成可執(zhí)行程序前,而動態(tài)鏈接得進行則是程序進行時

????  靜態(tài)庫:就是將庫中的代碼包含到自己的程序中,每個程序鏈接靜態(tài)庫后,都會包含一份獨立的代碼,當程序運行起來時,所有這些重復的代碼都需要占用獨立的存儲空間,顯然很浪費計算機資源。

????  動態(tài)庫:不會將代碼直接復制到自己程序中,只會留下調用接口,程序運行時再去將動態(tài)庫加載到內存中,所有程序只會共享這一份動態(tài)庫,因此動態(tài)庫也被稱為共享庫。

????  動態(tài)鏈接的原理:是把程序按照模塊劃分成各個相對獨立部分,在程序運行時才將他們鏈接在一起形成一個完整的程序,而不是像靜態(tài)鏈接一樣把所有的程序模塊都鏈接成一個單獨的可執(zhí)行文件。

解釋
????  靜態(tài)鏈接指的是在編譯時將所有的模塊(包括主程序和所有庫)合并在一起,生成一個獨立的可執(zhí)行文件。這個文件可以在沒有任何外部依賴的情況下運行。

????  動態(tài)鏈接則是在程序運行時,才將鏈接到的庫添加到內存中。這樣的庫被稱為動態(tài)鏈接庫(在Windows中是.dll文件,在Unix-like系統(tǒng)是.so文件)。

????  現(xiàn)在,我給一個基本的代碼例子來解釋。假設有一個簡單的main函數(shù),它使用到了math庫中的sqrt函數(shù):

#include <math.h>
#include <iostream>int main() {double x = 9.0;double y = sqrt(x);std::cout << "The square root of " << x << " is " << y << std::endl;return 0;
}

????  對于靜態(tài)鏈接,你在編譯時可以指定鏈接器以靜態(tài)方式鏈接到math庫(這取決于系統(tǒng)和編譯器,很多情況下默認的是動態(tài)鏈接)。在這種情況下,sqrt函數(shù)的代碼會被包含在可執(zhí)行文件中,因此之后即使在沒有math庫的環(huán)境中也能運行。編譯命令可能如下:

g++ -o main main.cpp -static -lm

????  對于動態(tài)鏈接,編譯時只會存儲sqrt函數(shù)的引用,而不是函數(shù)本身的實現(xiàn)。當程序運行時,操作系統(tǒng)會找到相應的.so.dll文件,并將其加載到內存中。此時如果找不到對應的庫文件,則程序無法運行。編譯命令可能如下:

g++ -o main main.cpp -lm

????  總的來說,靜態(tài)鏈接產(chǎn)生的程序體積大,但在部署時簡單,因為沒有額外依賴;而動態(tài)鏈接產(chǎn)生的程序體積小,但運行時需要確保有相應的庫文件。

78. C++中怎么編譯C語言代碼?

????  使用extern"C"讓C++代碼按照C語言的方式去編譯。

解釋
????  在C++中,如果含有C語言編寫的文件或函數(shù),我們是需要使用"extern “C”"來告訴編譯器按照C語言的規(guī)則來編譯和鏈接這些代碼。

????  C++和C語言在函數(shù)名的處理上有不同。C++支持函數(shù)重載,所以編譯器會把函數(shù)的參數(shù)類型等信息添加到函數(shù)名中(這一過程我們通常稱之為"name mangling"或者"函數(shù)名改編"),以確保每個函數(shù)名都是唯一的。而C語言并不支持函數(shù)重載,所以它的函數(shù)名是不會被改編的。

????  當我們鏈接C和C++代碼時,就需要確保這些C函數(shù)的函數(shù)名不要被C++編譯器改編,以防止鏈接錯誤。"extern “C”"就是用于此目的的,它可以阻止C++編譯器對其之后的部分進行函數(shù)名改編。

????  一個常見的使用場景是在C++代碼中調用C庫,比如:

extern "C" {
#include "my_c_library.h"
}

????  在這段代碼中,"extern “C”"告訴編譯器,"my_c_library.h"中所有的代碼都不要進行name mangling,即按照C語言規(guī)則進行編譯和鏈接。

????  另一個常見的使用"extern “C”"的場景是C++代碼中定義了一個C函數(shù)

extern "C" void foo() {// 這個函數(shù)會按照C語言規(guī)則進行編譯和鏈接
}

????  在這段代碼中,盡管foo()函數(shù)是在C++代碼中定義的,但是由于"extern “C”"的存在,foo()函數(shù)的函數(shù)名不會被C++編譯器改編。

????  這就是C++中如何編譯C代碼,以及"extern “C”"的基本用法。

79. 未初始化的全局變量和初始化的全局變量放在哪里?

????  初始化的全局變量存放在數(shù)據(jù)段,數(shù)據(jù)段數(shù)據(jù)靜態(tài)分配

????  未初始化的全局變量存放在BSS(Block Started By Symbol)段,屬于靜態(tài)內存分配

解釋
????  在C和C++中,全局變量在內存中的存儲位置取決于它們是否被初始化。具體來說:

  • 初始化的全局變量:這些變量會被存放在數(shù)據(jù)段(Data Segment)。這部分內存用于存儲程序的全局變量和靜態(tài)變量,并在程序啟動時分配。數(shù)據(jù)段中的變量在程序運行期間的位置是固定的。
  • 未初始化的全局變量:這些變量會被存放在BSS段。BSS是Unix系統(tǒng)源代碼的一部分,指的是在程序執(zhí)行前被系統(tǒng)初始化為零或空指針的數(shù)據(jù)段。BSS段中的變量從概念上講是靜態(tài)分配內存的,它們在程序運行期間的位置也是固定的。
int g_var1;  // 未初始化的全局變量,將被放在BSS段
int g_var2 = 0;  // 初始化的全局變量,將被放在數(shù)據(jù)段

80. 說一下內聯(lián)函數(shù)及其優(yōu)缺點

????  內聯(lián)函數(shù)是在編譯期將函數(shù)體內嵌到程序中,以此來節(jié)省函數(shù)調用的開銷。

????  優(yōu)點:是節(jié)省了函數(shù)調用的開銷,讓程序運行的更加快速
????  缺點:是如果函數(shù)體過長,頻繁地使用內聯(lián)函數(shù)會導致代碼編譯膨脹問題,不能遞歸執(zhí)行

解釋
????  內聯(lián)函數(shù)雖然能提升程序的效率,但使用時確實需要一些注意。

優(yōu)點

  1. 提高執(zhí)行效率。無需函數(shù)調用過程,省去了參數(shù)傳遞、返回地址計算等操作開銷。
  2. 優(yōu)化了局部變量的處理。一般函數(shù)的局部變量在棧中,需要入棧出棧。內聯(lián)函數(shù)的局部變量直接 集成在原函數(shù)棧幀中,無需單獨處理。

缺點

  1. 內聯(lián)函數(shù)會被編譯器展開成了多份,如果函數(shù)體過大,使用過多內聯(lián)函數(shù)會導致編譯后的代碼膨脹,消耗更大的內存資源。
  2. 若在內聯(lián)函數(shù)中做遞歸操作,會無法進行展開,從而無法做內聯(lián)優(yōu)化。
  3. 編程復雜性增加。需要程序員明確指定內聯(lián),而且內聯(lián)過程還可能出現(xiàn)函數(shù)聲明和實現(xiàn)不一 致,導致編譯錯誤。

????  特別注意,C++中的inline只是建議編譯器進行內聯(lián),實際內聯(lián)與否還要看編譯器的決策,編譯器還會考慮函數(shù)體的復雜度和調用頻次等條件。如果函數(shù)體比較復雜,或者進行了遞歸調用等操作,編譯器可能無視inline指示而不進行內聯(lián)。對于簡單的函數(shù)進行內聯(lián)能夠明顯優(yōu)化性能,但對于復雜的、大型的、遞歸的函數(shù),內聯(lián)的效果可能并不會很好。

81. C++11中的auto是怎么實現(xiàn)自動識別類型的?模板是怎樣實現(xiàn)轉化成不同類型的?

????  auto僅僅只是一個占位符,在編譯期間它會被真正的類型替代,或者說C++中變量必須要有明確的類型的,只是這個類型是由編譯器自己推導出來的。

????  函數(shù)模板是個藍圖,她本身并不是函數(shù),是編譯器用使用方式具體類型函數(shù)的模具,所以模板其實就是將原本應該我們做重復的事交給了編譯器。

解釋
????  首先,我們定義一個函數(shù)模板,該模板實現(xiàn)了對任意類型的兩個值進行比較的操作:

template <typename T>
bool isEqual(T a, T b) {return a == b; 
}

????  在這段代碼中,template <typename T>這行表明我們正在定義一個模板,“T”則是我們的模板類型參數(shù)。

????  然后,我們在函數(shù)中使用這個模板參數(shù)T。C++編譯器就像一個“代碼生成機”,接下來自動會根據(jù)所提供的具體類型來生成代碼。

????  比如在主函數(shù)中實例化模板:

int main() {int a = 5;int b = 5;bool result = isEqual(a, b); // 使用int類型實例化模板cout << "Result is " << (result?"true":"false") << endl;string str1 = "Hello";string str2 = "Hello";result = isEqual(str1, str2); // 使用string類型實例化模板cout << "Result is " << (result?"true":"false") << endl;return 0;
}

????  當編譯器遇到isEqual(5, 5)這樣的實例化請求,它看到你傳入了兩個int類型的變量,于是它就會生成一個處理int類型參數(shù)的函數(shù)。同樣地,當它看到isEqual(“Hello”, “Hello”),它就會生成一個處理string類型參數(shù)的函數(shù)。

????  這是函數(shù)模板的一個基本示例,通過它,我們可以看到模板的工作方式:它們允許我們定義通用的模板,在實例化時根據(jù)提供的類型生成對應的代碼。

82. map和set的區(qū)別和底層實現(xiàn)是什么?map取值的find,[],at方法的區(qū)別(at有越界檢查的功能)

????  都是紅黑樹

????  find查找需要判斷返回的結果才知道有沒有成功。

????  []不管有沒有就是0,如果原先不存在該key,則插入,如果存在則覆蓋插入。

????  at方法則會進行越界檢查,這會損失性能,如果存在則返回它的值,如果不存在則拋出異常

解釋

#include <iostream>
#include <map>
#include <string>using namespace std;int main() {map<string, int> myMap; // 創(chuàng)建一個字典myMap["apple"] = 1; // 使用 [] 方法插入一個元素myMap["banana"] = 2;// 使用 Find 方法檢查元素是否存在if(myMap.find("apple") != myMap.end()) {cout << "Apple is in the map." << endl;} else {cout << "Apple is not in the map." << endl;}// 使用 [] 方法訪問一個存在的元素cout << "The value of apple is: " << myMap["apple"] << endl;// 使用 [] 方法訪問一個不存在的元素cout << "The value of cherry is: " << myMap["cherry"] << endl;// 使用 At 方法訪問一個存在的元素try {cout << "The value of banana is: " << myMap.at("banana") << endl;} catch (const out_of_range& oor) {cerr << "Out of Range error: " << oor.what() << '\n';}// 使用 At 方法訪問一個不存在的元素try {cout << "The value of orange is: " << myMap.at("orange") << endl;} catch (const out_of_range& oor) {cerr << "Out of Range error: " << oor.what() << '\n';}return 0;
}

????  在這段代碼中,當我們使用?operator[]?來訪問一個不存在的元素時,我們會看到這個元素將被添加到 map 中,并使用默認值進行初始化。而如果我們使用?at?方法來進行訪問,由于元素不存在,將會拋出一個 out_of_range 異常。

83. 詳細說一說fcntl的作用

作用:用于控制打開的文件描述符的一些屬性和行為

有五個功能:

  1. 復制一個現(xiàn)有的描述符(cmd = F_DUOFD)
  2. 獲得/設置文件描述符標記(cmd = F_GETFD或F_SETFD)
  3. 獲取/設置文件狀態(tài)標記(cmd=F——GETFL或F_SETFL)
  4. 獲取設置異步IO所有權(cmd=F_GETOWN或F_SETFL)
  5. 獲取設置記錄鎖(cmd=F_GETLK或F_SET)

84. C++的面向對象主要體現(xiàn)在哪些方面

????  體現(xiàn)在C++引入了面向對象的一些特性,例如加入了封裝繼承多態(tài)的特點。(然后介紹一下封裝繼承多態(tài))

解釋

  1. 封裝?:封裝是指將數(shù)據(jù)(變量)和操作數(shù)據(jù)的函數(shù)包裝到一個單元(即類)里。通過封裝,可以將具有共同屬性和功能的一組操作,從邏輯上劃分成一個個模塊(類)。例如,你可以創(chuàng)建一個名為“汽車”的類,并設置其變量如品牌、顏色、速度等,和控制這些變量的函數(shù),如駕駛和加速等。 通過設置私有成員私有方法,我們可以隱藏內部信息,保護數(shù)據(jù),只能通過公有的接口進行訪問。

  2. 繼承?:繼承是面向對象編程中從已有類(基類,父類或超類)中派生出新的類(派生類,子類或其子類)。這個新的類擁有基類的所有特性,而且還可以添加新的特性。例如,你可以從“汽車”類派生出一個叫“電動汽車”的類,它繼承了“汽車”的所有特性,并添加了新的特性,比如充電。繼承增加了代碼的復用性。

  3. 多態(tài)?:多態(tài)是指基類的指針或引用指向派生類對象,并能調用派生類的重寫(覆蓋)函數(shù),以實現(xiàn)不同的行為。例如,你可能有一個食物制作方法的類(例如“做飯”),并有一些其他派生類,如“炒飯” “燉飯”或“燒飯”。當你嘗試調用它們的“做”方法時,會執(zhí)行每個類特定的“做”方法。這意味著你可以對相同的函數(shù)進行不同的操作,這取決于你正在操作的對象。

????  通過結合這三個特性,C++ 程序員可以創(chuàng)建具有特定屬性和行為的復雜對象。這有助于提高代碼的可讀性和可維護性,并支持更復雜的編程概念,如抽象和接口。

85. 介紹一下extern C關鍵字,為什么會有這個關鍵字

????  是用來實現(xiàn)在C++代碼中用C語言的方式來編寫代碼,是C++為了兼容C語言所加入的關鍵字

86. 講一講迭代器失效及其解決方法

????  序列容器迭代器失效:當當前元素的迭代器被刪除后,后面所有元素的迭代器都會失效,他們都是一塊連續(xù)存儲的空間,所以當使用erase函數(shù)操作時,其后的每一個元素都會向前移動一個位置,此時可以使用erase函數(shù)操作可以返回下一個有效的迭代器

????  Vector迭代器失效問題總結

  1. 當執(zhí)行了erase方法時,指向刪除節(jié)點的迭代器全部失效,指向刪除節(jié)點之后的迭代器也全部失效。
  2. 當進行push_back方法時,end操作返回的迭代器肯定失效。
  3. 當插入一個元素后,capacity返回值與沒有插入元素之前相比有改變,則需要重新加載整個容器,此時first和end操作返回的迭代器失效。
  4. 當插入一個元素后,如果空間未重新分配,指向插入位置之前的元素的迭代器依然有效,但指向插入元素之后的迭代器全部失效。

????  Deque(雙端隊列)迭代器失效總結

  1. 對于deque,插入到除首尾位置之外的任何位置都會導致迭代器、指針和引用都會失效,如果在首尾位置添加元素,迭代器會失效,但是指針和引用不會失效。
  2. 如果在首尾之外的任何位置刪除元素,那么指向被刪除元素外其他元素的迭代器都會失效。
  3. 如果在其首部和尾部刪除元素則只會使被指向元素的迭代器失效。

關聯(lián)型容器迭代器失效
????  刪除當前的迭代器,僅僅會使當前的迭代器失效,只要erase時,遞增當前迭代器即可。

解釋

  1. 序列容器迭代器失效:當我們執(zhí)行刪除操作(比如使用erase)時,確實會導致當前元素之后所有元素的迭代器失效,因為它們所指向的內存區(qū)域已經(jīng)發(fā)生了改變。

????  下邊是一個使用C++ STL中erase方法的例子:

std::vector<int> vec = {10, 20, 30, 40, 50};// 建立一個指向第三個元素的迭代器
auto iter = vec.begin() + 2;// 刪除這個元素
iter = vec.erase(iter);// 現(xiàn)在iter指向刪除元素之后的元素(如果存在的話)cout  << *iter << endl; //輸出的是40

????  在這個例子中,erase刪除了vec中的30,并返回一個新的迭代器,它指向了40。這就是你所說的“erase函數(shù)操作可以返回下一個有效的迭代器”的效果。

????  所以,當你在操作底層實現(xiàn)為連續(xù)存儲區(qū)域的容器(例如vector、deque和string)時,要特別注意迭代器的有效性,尤其在插入、刪除操作之后。一般來說,如果你計劃在遍歷容器的過程中刪除元素,那么使用erase,并用返回的迭代器更新你正在使用的迭代器,是一個非常好的實踐。

  1. 對push_back后end迭代器失效的解釋
    1. 當我們對vector執(zhí)行push_back操作時,如果vector的容量(capacity)不足以容納新元素,vector容器會進行內存的重新分配以確保有足夠的空間來存儲新添加的元素。這種情況下,所有指向原來存儲區(qū)域的迭代器就會失效。
    2. 即使push_back操作沒有導致內存重新分配,end()方法返回的迭代器也會失效。因為end()方法返回的迭代器并不指向vector的任一元素,而是指向存儲區(qū)域“尾元素的下一位置”,這個位置是個標志位,被成為了"尾后迭代器"(past-the-end iterator),自然,當插入新元素后,end()方法返回的迭代器的位置改變,那么在插入新元素前獲取的尾后迭代器就失效了。

????  因此,無論是否發(fā)生了內存重新分配,都不能保證push_back后end()方法之前返回的迭代器依然有效。在進行了push_back操作后,最好的做法是重新獲取end迭代器。

  1. 插入元素后capacity發(fā)生改變,begin和end全部失效的解釋:
    1. 當我們在vector插入一個元素后,如果容量(capacity)不足以容納新元素,那么vector就需要進行內存的重新分配以確保有足夠的空間來存儲新添加的元素。在這種情況下,所有指向原來存儲區(qū)域的迭代器(包括begin和end返回的迭代器)都會失效。這是因為,這些迭代器是依賴于vector內部存儲元素的物理存儲區(qū)域的。當引發(fā)重新分配時(capacity發(fā)生改變時),元素會被移動到新的內存區(qū)域,而原來的迭代器依然指向舊的存儲區(qū)域,因此它們就會失效。因此,如果你早已獲取了begin和end返回的迭代器,然后又進行了可能引發(fā)重分配的插入操作(如push_back,insert等),那么應該在插入操作后重新獲取begin和end返回的迭代器。
  2. 對deque迭代器的解釋:
    1. 和 vector 不同,deque 是一個分段連續(xù)的容器,也就是說它的內存空間不是一塊連續(xù)的,而是由多塊連續(xù)內存組合而成的。這樣的設計使得在 deque 的首尾添加或刪除元素都有很好的性能。當我們在 deque 的除首尾之外的任何位置進行插入或刪除操作時,為了保持元素的連續(xù)性,這個操作可能會引發(fā)元素的移動,從而使所有迭代器、指針和引用都失效。而如果我們在 deque 的首尾位置進行添加或刪除元素,由于其特殊的數(shù)據(jù)組織方式(分段連續(xù)),此時只需要在相應端點增減一段內存,然后更新首尾指針即可,而不必移動已有元素。因此,在這種情況下,除了對應端點的迭代器會失效外,其他迭代器、指針和引用都不會失效。下面是一個簡單的代碼示例來解釋這一點:
#include <iostream>
#include <deque>int main() {std::deque<int> deq = {1, 2, 3, 4, 5};int* ptr = &deq[2];  // 指向元素 3std::cout << "*ptr = " << *ptr << std::endl;  // 輸出 3deq.push_front(0);  // 在首位置添加元素std::cout << "*ptr = " << *ptr << std::endl;  // 依然可以輸出 3,指針沒有失效deq.insert(deq.begin() + 2, -1);  // 在除首尾位置插入元素std::cout << "*ptr = " << *ptr << std::endl;  // 未定義的行為,因為 ptr 已經(jīng)失效return 0;
}

上面的代碼中,我們首先創(chuàng)建一個 deque?deq,然后獲取并打印了一個指向元素 3 的指針?ptr。接著我們在?deq?的首位置添加了一個新元素 0,然后再次打印?ptr,發(fā)現(xiàn)?ptr?并沒有失效。然后我們在?deq?的非首尾位置插入了一個新元素 -1,然后再次打印?ptr,此時?ptr?已經(jīng)失效,所以對?ptr?的解引用是未定義的行為。

  1. 對關聯(lián)型容器迭代器的解釋:對于關聯(lián)容器(如set,map,multiset,multimap等),刪除元素時只會使指向該元素的迭代器失效,其他迭代器并不會受到影響。在調用?erase()?方法刪除元素時,一個可行的做法是使用返回的迭代器更新當前迭代器,這樣可以保證當前迭代器始終有效。以下是一個例子:
#include <map>
#include <iostream>int main() {std::map<int, std::string> myMap;myMap[0] = "Zero";myMap[1] = "One";myMap[2] = "Two";for(auto it = myMap.begin(); it != myMap.end(); ) {if(it->first == 1) {it = myMap.erase(it);  // 刪除鍵為1的元素,并用返回的迭代器更新it} else {++it;}}// 打印剩余元素for(const auto& kv : myMap) {std::cout << kv.first << " : " << kv.second << '\n';}return 0;
}

????  在這段代碼中,我創(chuàng)建了一個包含三個鍵值對的 map。然后我迭代了整個 map,并在找到鍵為1的鍵值對時刪除了它。note:調用?erase?的同時也更新了?it,因此我們可以保證?it?始終是有效的。最后,我打印了 map 中剩余的鍵值對,可以看到鍵為1的鍵值對已經(jīng)被成功刪除。

http://www.risenshineclean.com/news/59650.html

相關文章:

  • wordpress案例分析梅州seo
  • 保險網(wǎng)站有哪些谷歌官方seo入門指南
  • 手機上如何做mv視頻網(wǎng)站網(wǎng)上接單平臺
  • WordPress重力表單注冊石家莊網(wǎng)絡seo推廣
  • 網(wǎng)站自動秒收錄工具網(wǎng)絡廣告案例以及分析
  • 佛山網(wǎng)站建設定制開發(fā)交換友情鏈接的方法
  • 外貿網(wǎng)站屏蔽國內ipseo報名在線咨詢
  • 做淘寶客沒有網(wǎng)站怎么做武漢seo優(yōu)化顧問
  • 個人域名用來做淘寶客網(wǎng)站推介網(wǎng)
  • wap企業(yè)網(wǎng)站模板什么是搜索引擎營銷?
  • 蘇州 網(wǎng)站建設 app移動網(wǎng)站優(yōu)化排名
  • 網(wǎng)站建設及推廣培訓關鍵詞搜索查找工具
  • 做微信的網(wǎng)站叫什么軟件網(wǎng)站快速上排名方法
  • 做醫(yī)院網(wǎng)站公司推廣app接單網(wǎng)
  • 門源網(wǎng)站建設公司網(wǎng)絡廣告形式
  • 網(wǎng)上有做口譯的網(wǎng)站么網(wǎng)站搜索排名優(yōu)化價格
  • 做網(wǎng)站工資年新多少在廣東互聯(lián)網(wǎng)營銷師證書騙局
  • 微應用和微網(wǎng)站的區(qū)別鹽城seo優(yōu)化
  • 專業(yè)做網(wǎng)文的網(wǎng)站網(wǎng)站建設制作專業(yè)
  • 國外建設工程招聘信息網(wǎng)站怎么寫網(wǎng)站
  • iis如何用ip地址做域名訪問網(wǎng)站汕頭seo快速排名
  • wordpress網(wǎng)站速度時快時慢seo網(wǎng)站優(yōu)化培訓廠家報價
  • 如何使用記事本做網(wǎng)站狼雨的seo教程
  • oa報表網(wǎng)站開發(fā)淘寶客推廣一天80單
  • 衡陽商城網(wǎng)站建設seo刷關鍵詞排名免費
  • 怎樣在手機做自己的網(wǎng)站企業(yè)培訓體系
  • 靈臺縣住房和城鄉(xiāng)建設局網(wǎng)站品牌推廣方案
  • wordpress子頁面密碼錯誤東營seo
  • 福建建設執(zhí)業(yè)資格網(wǎng)站報名系統(tǒng)百度推廣后臺登錄入口官網(wǎng)
  • 貴港網(wǎng)站建設西地那非片說明書