www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > > C語言與CPP編程
[導讀]從存儲空間角度,虛函數(shù)對應一個虛函數(shù)表,而指向虛函數(shù)表的虛函數(shù)指針是存儲區(qū)對象內存內的。如果構造函數(shù)是虛函數(shù),則需要通過虛函數(shù)表來調用,而對象還沒有構造出來,無法找到虛函數(shù)表。

shared_ptr 指針的實現(xiàn)

template<typename T> class Shared_ptr {private: T *ptr; int *use_count; // 用指針保證指向同一塊地址public: Shared_ptr() { ptr = nullptr; use_count = nullptr; cout << "Created" << endl; } Shared_ptr(T *p){ ptr = p; use_count = new int(1); cout << "Created" << endl; } Shared_ptr(const Shared_ptr &other) { ptr = other.ptr; ++(*other.use_count); use_count = other.use_count; cout << "Created" << endl; } Shared_ptr& operator=(const Shared_ptr &other) { if(this == &other) return *this; (*other.use_count)++; if(ptr && --(*use_count) == 0) { delete ptr; delete use_count; } ptr = other.ptr; use_count = other.use_count; return *this; } T& operator*() { // 返回指針的引用 return *ptr; } T* operator->() { return ptr; } int get_use_count() { if(use_count == nullptr) return 0; return *use_count; } ~Shared_ptr() { if(ptr && --(*use_count) == 0) { delete ptr; delete use_count; cout << "Destroy" << endl; } }};

構造函數(shù)不可以是虛函數(shù)

  1. 從存儲空間角度,虛函數(shù)對應一個虛函數(shù)表,而指向虛函數(shù)表的虛函數(shù)指針是存儲區(qū)對象內存內的。如果構造函數(shù)是虛函數(shù),則需要通過虛函數(shù)表來調用,而對象還沒有構造出來,無法找到虛函數(shù)表。
  2. 從使用角度,虛函數(shù)主要用于信息不全的情況下,使子類重寫的函數(shù)能得到對應的調用。構造函數(shù)本身就是要初始化對象,所以用虛函數(shù)沒有意義。

平衡樹(AVL)、伸展樹(Splay)、紅黑樹

  1. 平衡樹:
    1. 優(yōu)點:查找、插入、刪除最壞復雜度都是 O(logN)。操作簡單。
    2. 缺點:插入、刪除的旋轉成本不菲。刪除操作后,最多旋轉 O(logN) 次,若頻繁進行插入、刪除操作,得不償失
  2. 伸展樹
    1. 優(yōu)點:局部性強: 1)剛剛訪問過的節(jié)點,可能在不久后再次被訪問到。2) 將要訪問的節(jié)點,可能在不久之前剛剛被訪問的節(jié)點附近。
    2. 缺點:不能保證單次最壞情況的出現(xiàn),不適用于敏感場合。復雜度不好分析,均攤 O(logN)。
  3. 紅黑樹
    1. 優(yōu)點:查找、插入、刪除的復雜度都是 O(logN)。插入之后最多旋轉 2 次,刪除之后最多旋轉 1 次能夠再次達到平衡狀態(tài)。
    2. 缺點:左右子樹高度相差比 AVL 大。

AVL 和 紅黑樹比較

AVL 是嚴格的平衡樹,因此在插入/刪除節(jié)點時,根據(jù)不同的情況,旋轉的次數(shù)比紅黑樹多。

紅黑樹是弱平衡的,用非嚴格的平衡換取插入/刪除節(jié)點時旋轉次數(shù)的降低。

兩者都是平衡樹,那么查找的時候,查找效率就會和樹的高度有關,所以 AVL 會比紅黑樹更優(yōu)。

有了 AVL 為什么還要紅黑樹

雖然 AVL 解決的二叉查找樹退化成鏈表的情況,但是平衡樹要求每個節(jié)點左子樹和右子樹高度相差最多為 1。這個要求太嚴格了,導致插入/刪除的時候需要不斷的旋轉來達到這個要求。而紅黑樹不會頻繁的破壞規(guī)則,不用頻繁的調整,紅黑樹是一種不大嚴格的平衡樹,但是換來了效率的提高,是一種折中的方案。

紅黑樹

面試常問:什么是紅黑樹?

wiki紅黑樹

紅黑樹性質

  1. 節(jié)點一定是紅色或黑色。
  2. 根節(jié)點是黑色。
  3. 每個葉子節(jié)點都是黑色的空節(jié)點(NIL)。
  4. 每個紅色節(jié)點的兩個子節(jié)點都是黑色。
  5. 從任一節(jié)點到其每個葉子節(jié)點的所有路徑上都包含相同的黑色節(jié)點數(shù)。

這些性質強制了紅黑樹從根到葉子的最長路徑不會超過最短路徑的兩倍。性質 4 導致了路徑不能有兩個相連的紅色節(jié)點,最短的可能路徑都是黑色節(jié)點,最長的可能路徑有交替的紅色和黑色節(jié)點。因為根據(jù)性質 5 所有最長的路徑都有相同數(shù)目的黑色節(jié)點,這就表明了沒有路徑能多于任何其他路徑的兩倍長。

紅黑樹的擴展

可以給每個階段加上 size 域,表示以節(jié)點 x 為根的子樹的節(jié)點數(shù)。

size[x] = size[x->left]+size[x->right]

通過 size 就可以獲得比 x 小的節(jié)點數(shù)目和第 x 小的節(jié)點。

i++ 和 ++i 的區(qū)別

++i 返回對象的引用,i++ 必須產生一個臨時對象保存更改前對象的值并返回,所以導致在大對象的時候產生的比較大的復制開銷,效率較低。

// ++iint& int::operator++() { *this += 1; return *this;}// i++const int int::operator++(int) { int old_value = *this; ++(*this); return old_value;}

拷貝構造函數(shù)

Node a;Node b(a);Node c = a;

這里的 b 和 c 都是一開始是不存在的,是通過 a 對象來構造和初始化的。

拷貝構造函數(shù)重載形式:

Node (const Node &other) { 
}

如果用戶沒有自定義拷貝構造函數(shù)并且用到了拷貝構造函數(shù),則編譯器會生成默認的拷貝構造函數(shù)。

在 C++ 中,這三種情況下拷貝構造函數(shù)會被使用:

  1. 一個對象以值傳遞的形式傳入函數(shù)內。
  2. 一個對象以值傳遞的形式從函數(shù)返回。
  3. 一個對象通過另一個對象初始化。

優(yōu)點:可以很容易的復制對象。

缺點:對象的隱式拷貝是 C++ 中是錯誤和性能問題的來源之一。它也降低了代碼的可讀性,并使得對象子程序中的傳遞和改變變得難以跟蹤。

賦值函數(shù)

Node a;Node b;b = a;

這里的 b 已經存在的,在通過 a 賦值給 b。

賦值函數(shù)重載形式:

Node& operator=(const Node &other) {
}

拷貝構造函數(shù)和賦值函數(shù)區(qū)別

  1. 拷貝構造函數(shù)是在對象初始化時,分配一塊空間并初始化,而賦值函數(shù)是對已經分配空間的對象進行賦值操作。
  2. 實現(xiàn)上,拷貝構造函數(shù)是構造函數(shù),通過參數(shù)的對象初始化產生一個對象。賦值函數(shù)則是把一個對象賦值給另一個對象,需要先判斷兩個對象是否是同一個對象,若是則什么都不做,直接返回,若不是則需要先釋放原對象內存,在賦值。(可以參考 shared_ptr 實現(xiàn))

總結:

  • 對象不存在,沒有通過別的對象來初始化,就是構造函數(shù)。
  • 對象不存在,通過別的對象來初始化,就是拷貝構造函數(shù)。
  • 對象存在,通過別的對象來初始化,就是賦值函數(shù)。

虛函數(shù)和內聯(lián)函數(shù)

內聯(lián)函數(shù)通常就是將它在調用處 "內斂的" 展開,消除反復調用的額外開銷,但是如果函數(shù)太長,會使代碼臃腫,反而降低效率。

虛函數(shù)可以是內聯(lián)函數(shù),無論是顯式還是隱式,inline 都只是一個申請,最終由編譯器決定是否內聯(lián)。所以可以用 inline 修飾虛函數(shù),但虛函數(shù)表現(xiàn)多態(tài)性時,不可以內聯(lián),只有當知道調用哪個類的函數(shù)時,才可以是內聯(lián)。

空類的大小

在 C++ 中規(guī)定類的大小不為 0,空類大小為 1,當類不包含虛函數(shù)和非靜態(tài)成員時,其對象大小也為 1。若存在虛函數(shù),則需要存儲一個虛函數(shù)指針大小,在 32 位上為 4 字節(jié)。

結構體字節(jié)對齊

class A {};class B{public: A x;};sizeof(B) = 1
class B{public: inline virtual fun() { }};sizeof(B) = 4
class B{public: A x; inline virtual fun() { }};sizeof(B) = 8

可以發(fā)現(xiàn)最后一個的 sizeof 并不是單純的 1+4=5,而直接變成了 8,因為存在結構體的字節(jié)對齊規(guī)則。

結構體字節(jié)對齊的根本原因:1) 移植性更好,某些平臺只能在特定的地址訪問特定的數(shù)據(jù)。2) 提高存取數(shù)據(jù)的速度,CPU 通常按塊讀取數(shù)據(jù)會更加快速。

結構體字節(jié)對齊原則:

  1. #pragma pack
    1. 結構內部各成員首地址必然是自身大小的整數(shù)倍。
    2. sizeof 最終結果必然是結構內部最大成員的整數(shù)倍,不夠補齊
  2. #pragma pack(n)
    1. 結構內部各成員首地址必然是 min(n, 自身大小) 的整數(shù)倍。
    2. sizeof 最終結果必然是 min(n, 結構內部最大成員) 的整數(shù)倍,不夠補齊。
#includeusing namespace std;
class A { char a; int b; short c;};class B { int a; char b; short c;};
int main() { cout << sizeof(A) << endl; // 12 cout << sizeof(B) << endl; // 8 return 0;}

造成不同結果的原理在于:

  • 對于 class A 來說,內部字節(jié)為
a b c
1*** 1111 11**
  • 對于 class B 來說,內部字節(jié)為
a b c
1111 1* 11

有 static、virtual 之類的一個類的內存分布

  • static 修飾成員變量
    • 靜態(tài)成員變量在 全局存儲區(qū) 分配內存,本類的所有對象共享,在還沒生成類對象之前也可以使用。
  • static 修飾成員函數(shù)
    • 靜態(tài)成員函數(shù)在 代碼區(qū) 分配內存。靜態(tài)成員函數(shù)和非靜態(tài)成員函數(shù)的區(qū)別在于非靜態(tài)成員函數(shù)存在 this 指針,而靜態(tài)成員函數(shù)不存在,所以靜態(tài)成員函數(shù)沒有類對象也可以調用。
  • virtual
    • 虛函數(shù)表存儲在常量區(qū),也就是只讀數(shù)據(jù)段
    • 虛函數(shù)指針存儲在對象內,如果對象是局部變量,則存儲在棧區(qū)內。

使用宏定義求結構體成員偏移量

#include#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER)/*(TYPE*)0 將零轉型成 TYPE 類型指針((TYPE*)0->MEMBER) 訪問結構體中的成員&((TYPE*)0->MEMBER) 取出數(shù)據(jù)成員地址,也就是相對于零的偏移量(size_t) & ((TYPE*)0)->MEMBER) 將結果轉換成 size_t 類型。*/
struct Node { char a; short b; double c; int d;};
int main() { printf("%d\n", offsetof(Node, a)); printf("%d\n", offsetof(Node, b)); printf("%d\n", offsetof(Node, c)); printf("%d\n", offsetof(Node, d)); return 0;}

size_t 在可以理解成 unsigned\ \ int,在不同平臺下被 typedef 成不同類型。

C++ 中哪些函數(shù)不可以是虛函數(shù)

  1. 普通函數(shù)(非成員函數(shù)):普通函數(shù)不屬于類成員,不能被繼承。普通函數(shù)只能被重載,不能被重寫,因此聲明成虛函數(shù)沒有意義。
  2. 構造函數(shù):構造函數(shù)本來就是為了初始化對象而存在的,沒有定義為虛函數(shù)的必要。而且對象還沒構造出來,不存在虛函數(shù)指針,也無法成為虛函數(shù)。
  3. 內聯(lián)成員函數(shù):內聯(lián)函數(shù)是為了在代碼中直接展開,減少調用函數(shù)的代價,是在編譯的時候展開。而虛函數(shù)需要動態(tài)綁定,這不可能統(tǒng)一。只有當編譯器知道調用的是哪個類的函數(shù)時,才可以展開。
  4. 靜態(tài)成員函數(shù):對于所有對象都共享一個函數(shù),沒有動態(tài)綁定的必要。
  5. 友元函數(shù):C++ 不支持友元函數(shù)的繼承,自然不能是虛函數(shù)。

手撕堆排序

[堆排序的思路以及代碼的實現(xiàn)

](https://blog.csdn.net/qq_36573828/article/details/80261541)

令 k = log_2n

初始化堆復雜度 O(N) 。分析:假設每一次都到葉子的父節(jié)點


排序重建堆復雜度 。分析:假設每一次都到葉子節(jié)點。


void adjust(vector<int>& nums, int i, int n) { while(2*i+1 < n) { int j = 2*i+1; if(j+11]) j++; if(nums[i] > nums[j]) break; swap(nums[i], nums[j]); i = j; }}
vector<int> sortArray(vector<int>& nums) { if(nums.size() <= 1) return nums; int n = nums.size(); for(int i=n/2-1; i>=0; i--) adjust(nums, i, n); // 初始化堆 for(int i=n-1; i>0; i--) { // 排序重建堆 swap(nums[i], nums[0]); adjust(nums, 0, i); } return nums;}

main 函數(shù)前后還會執(zhí)行什么

全局對象的構造在 main 函數(shù)之前完成,全局對象的析構在 main 函數(shù)之后完成。

const 和 define

  1. define 在預編譯階段起作用,const 在編譯、運行的時候起作用。
  2. define 只是簡單的替換功能,沒有類型檢查功能,const 有類型檢查功能,可以避免一些錯誤。
  3. define 在預編譯階段就替換掉了,無法調試,const 可以通過集成化工具調試。

inline 和 define

  1. inline 在編譯時展開,define 在預編譯時展開。
  2. inline 可以進行類型安全檢查,define 只是簡單的替換。
  3. inline 是函數(shù),define 不是函數(shù)。
  4. define 最好用括號括起來,不然會產生二義性,inline 不會。
  5. inline 是一個建議,可以不展開,define 一定要展開。

inline 函數(shù)的要求

  1. 含有遞歸調用的函數(shù)不能設置為 inline
  2. 循環(huán)語句和 switch 語句,無法設置為 inline
  3. inline 函數(shù)內的代碼應很短小。最好不超過5行

聲明和定義

  • 變量定義:為變量分配空間,還可以為變量指定初始值。
  • 變量聲明:向程序表明變量的類型和名字,但不分配空間??梢酝ㄟ^ extern 關鍵字來聲明而不定義,extern 告訴編譯器變量在別的地方定義了。
  1. 定義也是聲明,聲明不是定義。例如:
  • extern int i 聲明且不定義 i 變量, int i 聲明且定義了 i 變量。
  • 聲明有初始值時,為當成定義
    • extern int i=10 此時看成定義了 i 變量。
  • 函數(shù)的聲明和定義,區(qū)別在于是否帶有花括號。
  • 面向過程和面向對象

    面向過程就是分析出解決問題所需要的步驟,然后用函數(shù)把步驟一步一步的實現(xiàn)。優(yōu)點在于性能高。

    面向對象就是構成問題事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描述某個對象在整個解決問題的步驟中的行為。優(yōu)點在于易維護、易復用、易擴展,使系統(tǒng)更加靈活。

    堆區(qū)和自由存儲區(qū)

    基本上所有的 C++ 編譯器默認使用堆來實現(xiàn)自由存儲,也就是缺省的 new/delete 是通過 malloc/free 方式來實現(xiàn)的,這時候可以說他從堆上分配內存,也可以說他從自由存儲區(qū)分配內存。但程序員可以通過重載操作符,改用其他內存實現(xiàn)自由存儲。

    堆區(qū)是操作系統(tǒng)維護的一塊內存,而自由存儲區(qū)是 C++ 中用于 new/delete 動態(tài)分配和釋放對象的 抽象概念。

    堆區(qū)和棧區(qū)


    堆區(qū) 棧區(qū)
    管理方式 由程序員控制 系統(tǒng)主動分配
    空間大小 空間很大,可以達到4G 默認1M
    碎片問題 頻繁的 malloc/free 會造成大量碎片 不會存在這個問題
    生長方向 向著內存地址增加的方向 向著內存地址減小的方向
    分配效率 速度比較慢 速度比較快

    #ifndef 和 #endif 的作用

    在大的軟件工程中,可能多個文件包含同一個頭文件,當這些文件鏈接成可執(zhí)行文件的時候,就會造成大量的 "重定義" 錯誤,可以通過 #ifndef 來避免這個錯誤。

    #ifndef _A_H#define _A_H
    #endif

    這樣就可以保證 a.h 被定義一次。

    explicit 關鍵字

    類的構造函數(shù)存在隱式轉換,如果想要避免這個功能,就可以通過 explicit 關鍵字來將構造函數(shù)聲明成顯式的。

    菱形繼承

    class A { public: void fun() { cout << "!!!" << endl; }};class B : public A{};class C : public A{};class D : public B, public C {};

    int main() { // freopen("in", "r", stdin); D d; d.fun();; return 0;}

    像上面的繼承關系,當繼承關系形成菱形時,D 中保存了兩份 A,在調用 d.fun() 時就會出現(xiàn)調用不明確的問題。

    這種情況由兩種解決方法:

    1. 使用域限定訪問的函數(shù)。也就是將 d.fun 修改成 d.B::fun 或者 d.C::fun。
    2. 第二種方法就是使用虛繼承。虛繼承解決了從不同途徑繼承來的同名數(shù)據(jù)成員在內存中有不同的拷貝造成數(shù)據(jù)不一致的問題,將共同基類設置成虛基類,這時從不同路徑繼承過來的同名數(shù)據(jù)成員在內存中就只有一個拷貝。操作方法就是在 B 和 C 的繼承處加上 virtual 修飾。

    虛繼承底層實現(xiàn)一般通過虛基類指針和虛基類表實現(xiàn)。每個虛繼承的子類都有一個虛基類指針和一個虛基類表,虛表中記錄了虛基類與本類的偏移地址。通過偏移地址,這樣就找到了虛基類成員,節(jié)省了存儲空間。

    weak_ptr 指向的對象被銷毀

    首先 weak_ptr 是一種不控制對象生存周期的智能指針,它指向一個 shared_ptr 管理的對象。一旦最后一個指向對象的 shared_ptr 被銷毀,那么無論是否有 weak_ptr 指向該對象,都會釋放資源。

    weak_ptr 不能直接指向對象,需要先調用 lock,而 lock 會先判斷該對象是否還存在。

    如果存在 lock 就會返回一個指向該對象的 shared_ptr,并且該對象的 shared_ptr 的引用計數(shù)加一。

    如果在 weak_ptr 獲得過程中,原本的所有 shared_ptr 被銷毀,那么該對象的生命周期會延長到這個臨時 shared_ptr 銷毀為止。

    lambda 表達式

    lambda 表達式定義一個匿名函數(shù),并且可以捕獲一定范圍內的變量,基本格式如下:

    [capture](params) mutable ->ReturnType {statement}
    • [capture]:捕獲列表,可以捕獲上下文的變量來供 lambda 函數(shù)使用
      • [var]:值傳遞的方式捕獲 var
      • [&var]:引用傳遞的方式捕獲 var
      • [=]:值傳遞的方式捕獲父作用域的所有變量。
      • [&]:引用傳遞的方式捕獲父作用域的所有變量。
      • [this]:值傳遞的方式捕獲當前 this 指針。
    • (params):參數(shù)列表,和普通函數(shù)參數(shù)列表一致,如果不傳參數(shù)可以和 () 一起忽略。
    • mutable 修飾符號,默認情況下,lambda 表達式是一個 const 函數(shù),可以用 mutable 取消他的常量性。若使用 mutable 修飾,參數(shù)列表不可省略。
    • **->**ReturnType:返回值類型。若是 void,可以省略。
    • {statement}:函數(shù)主體,和普通函數(shù)一樣。

    lambda 表達式優(yōu)點在于代碼簡潔,容易進行并行計算。缺點在于在非并行計算中,效率未必有 for 循環(huán)快,并且不容易調試,對于沒學過的程序員來說可讀性差。

    #includeusing namespace std;
    int main() { vector<int> g{9, 2, 1, 2, 5, 6, 2}; int ans = 1; sort(g.begin(), g.end(), [](int a, int b) ->bool{return a>b;}); for_each(g.begin(), g.end(), [&ans](int x){cout << x << " ", ans = ans*x;}); cout << "\nmul = " << ans << endl; return 0;}/*9 6 5 2 2 2 1mul = 2160*/

    存在全局變量和局部變量時,訪問全局變量

    可以通過全局作用符號 :: 來完成

    int a = 10;
    int main() { // freopen("in", "r", stdin); int a = 20; cout << a << endl; cout << ::a << endl; return 0;}

    全局變量的初始化的順序

    同一文件中的全局變量按照聲明順序,不同文件之間的全局變量初始化順序不確定。

    如果要保證初始化次序的話,需要通過在函數(shù)使用靜態(tài)局部變量并返回來實現(xiàn)。

    class FileSystem{...};FileSystem & tfs(){ static FileSystem fs;//定義并初始化一個static對象 return fs;}

    淺拷貝和深拷貝

    • 淺拷貝:源對象和拷貝對象共用一份實體,僅僅是引用的變量名不同。對其中任意一個修改,都會影響另一個對象。
    • 深拷貝:源對象和拷貝對象相互獨立。對其中一個對象修改,不會影響另一個對象。
    • 兩個對象指向同塊內存,當析構函數(shù)釋放內存時,會引起錯誤。

    從這個例子可以看出,b 通過默認拷貝函數(shù)進行初始化,然而進行的是淺拷貝,導致對 a 進行修改的時候,b 的存儲值也被修改。

    struct Node { char* s; Node(char *str) { s = new char[100]; strcpy(s, str); } /* 手動實現(xiàn)拷貝構造函數(shù) Node(const Node &other) { s = new char[100]; strcpy(s, other.s); } */};
    int main() { // freopen("in", "r", stdin); Node a("hello"); Node b(a); cout << b.s << endl; strcpy(a.s, "bad copy"); cout << b.s << endl; return 0;}

    正確的寫法應該自己寫一個拷貝函數(shù),而不是用默認的,應該盡量的避免淺拷貝。

    如何控制一個類只能在堆或棧上創(chuàng)建對象

    在 C++ 中創(chuàng)建對象的方法有兩種,一種是靜態(tài)建立,一個是動態(tài)建立。

    • 靜態(tài)建立由編譯器為對象分配內存,通過調用構造函數(shù)實現(xiàn)。這種方法創(chuàng)建的對象會在棧上。
    • 靜態(tài)建立由用戶為對象分配內存,通過 new 來實現(xiàn),間接調用構造函數(shù)。這種方法創(chuàng)建的對象會在堆上。

    只能從堆上分配對象:

    當建立的對象在棧上時,由編譯器分配內存,因此會涉及到構造函數(shù)和析構函數(shù)。那么如果無法調用析構函數(shù)呢?也就是說析構函數(shù)是 private 的,編譯器會先檢查析構函數(shù)的訪問性,由于無法訪問,也就防止了靜態(tài)建立。

    但這種方法存在一種缺點,就是把析構函數(shù)設成 private 后,如果這個類要作為基類的話,析構函數(shù)應該設成虛函數(shù),而設成 private 后子類就無法重寫析構函數(shù),所以應該把析構函數(shù)設成 protected。然后額外設置一個接口來 delete。

    class Node {public: Node(){}; void Destroy() { delete this; }protected: ~Node(){};};

    此時解決了靜態(tài)建立的過程,但使用時,通過 new 創(chuàng)建對象,通過 Destroy 函數(shù)釋放對象,為了統(tǒng)一,可以把構造函數(shù)和析構函數(shù)都設成 protected,重寫函數(shù)完成構造和析構過程。

    class Node {public: static Node* Create() { return new Node(); } void Destroy() { delete this; }protected: Node(){}; ~Node(){};};

    只能從棧上分配對象:

    同樣的道理,只需要禁止通過動態(tài)建立對象就可以實現(xiàn)在棧上分配對象,所以可以重載 new 和 delete 并設為 private,使用戶只能靜態(tài)建立對象。

    class Node {public: Node(){}; ~Node(){};private: void* operator new(size_t t){} void operator delete(void* p){}};

    memcpy 和 memmove 的實現(xiàn)

    memcpy 可以直接通過指針自增賦值,但要求源地址和目的地址無重合。

    void mymemmove1(void* s, const void* t, size_t n) {char *ps = static_cast<char*>(s);const char *pt = static_cast<const char*>(t);while(n--) {*ps++ = *pt++;}}

    如果源地址和目的地址存在重合,會因為地址的重合導致數(shù)據(jù)被覆蓋,所以要通過 memmove 來實現(xiàn),需要從末尾往前自減賦值。

    為了加快速度還可以使用 4 字節(jié)賦值的方式

    // 直接按字節(jié)進行 copyvoid mymemmove1(void* s, const void* t, size_t n) { char *ps = static_cast<char*>(s); const char *pt = static_cast<const char*>(t); if(ps<=pt && pt<=ps+n-1) { ps = ps+n-1; pt = pt+n-1; while(n--) { *ps-- = *pt--; } } else { while(n--) { *ps++ = *pt++; } }}
    // 加快速度,每次按 4 字節(jié)進行 copyvoid mymemmove2(void *s, const void *t, size_t n) { int *ts = static_cast<int*>(s); const int *tt = static_cast<const int*>(t); char *ps = static_cast<char*>(s); const char *pt = static_cast<const char*>(t); int x = n/4, y = n%4; if(ps<=pt && pt<=ps+n-1) { ps = ps+n-1; pt = pt+n-1; while(y--) { *ps-- = *pt--; } ps++, pt++; ts = reinterpret_cast<int*>(ps); tt = reinterpret_cast<const int*>(pt); ts--, tt--; while(x--) { *ts-- = *tt--; } } else { while(y--) { *ps++ = *pt++; } ts = reinterpret_cast<int*>(ps); tt = reinterpret_cast<const int*>(pt); while(x--) { *ts++ = *tt++; } }}

    通過重載 new/delete 來檢測內存泄漏的簡易實現(xiàn)

    講每次 new 產生的內存記錄,并在 delete 的時候刪去記錄,那么最后剩下的就是發(fā)生內存泄漏的代碼。

    #include using namespace std;
    class TraceNew {public: class TraceInfo { private: const char* file; size_t line; public: TraceInfo(); TraceInfo(const char *File, size_t Line); ~TraceInfo(); const char* File() const; size_t Line(); }; TraceNew(); ~TraceNew(); void Add(void*, const char*, size_t); void Remove(void*); void Dump();private: map<void*, TraceInfo> mp;} trace;
    TraceNew::TraceInfo::TraceInfo() {}
    TraceNew::TraceInfo::TraceInfo(const char *File, size_t Line) : file(File), line(Line) {}
    TraceNew::TraceInfo::~TraceInfo() { delete file;}
    const char* TraceNew::TraceInfo::File() const { return file;}
    size_t TraceNew::TraceInfo::Line() { return line;}
    TraceNew::TraceNew() { mp.clear();}
    TraceNew::~TraceNew() { Dump(); mp.clear();}
    void TraceNew::Add(void *p, const char *file, size_t line) { mp[p] = TraceInfo(file, line);}
    void TraceNew::Remove(void *p) { auto it = mp.find(p); if(it != mp.end()) mp.erase(it);}
    void TraceNew::Dump() { for(auto it : mp) { cout << it.first << " " << "memory leak on file: " << it.second.File() << " line: " << it.second.Line() << endl; }}
    void* operator new(size_t size, const char *file, size_t line) { void* p = malloc(size); trace.Add(p, file, line); return p;}
    void* operator new[](size_t size, const char *file, size_t line) { return operator new(size, file, line);}
    void operator delete(void *p) { trace.Remove(p); free(p);}
    void operator delete[](void *p) { operator delete(p); }
    #define new new(__FILE__,__LINE__)
    int main() { int *p = new int; int *q = new int[10]; return 0;}
    /*0xa71850 memory leak on file: a.cpp line: 900xa719b8 memory leak on file: a.cpp line: 91*/

    垃圾回收機制

    之前使用過,但現(xiàn)在不再使用或者沒有任何指針再指向的內存空間就稱為 "垃圾"。而將這些 "垃圾" 收集起來以便再次利用的機制,就被稱為“垃圾回收”。

    垃圾回收機制可以分為兩大類:

    1. 基于引用計數(shù)的垃圾回收器
    • 系統(tǒng)記錄對象被引用的次數(shù)。當對象被引用的次數(shù)變?yōu)?0 時,該對象即可被視作 "垃圾" 而回收。但難以處理循環(huán)引用的情況。
  • 基于跟蹤處理的垃圾回收器
    • 標記-清除:對所有存活對象進行一次全局遍歷來確定哪些對象可以回收。從根出發(fā)遍歷一遍找到所有可達對象(活對象),其它不可達的對象就是垃圾對象,可被回收。
    • 標記-縮并:直接清除對象會造成大量的內存碎片,所以調整所有活的對象縮并到一起,所有垃圾縮并到一起,然后一次清除。
    • 標記-拷貝:堆空間分為兩個部分 From 和 To。剛開始系統(tǒng)只從 From 的堆空間里面分配內存,當 From 分配滿的時候系統(tǒng)就開始垃圾回收:從 From 堆空間找出所有的活對象,拷貝到 To 的堆空間里。這樣一來,F(xiàn)rom 的堆空間里面就全剩下垃圾了。而對象被拷貝到 To 里之后,在 To 里是緊湊排列的。接下來是需要將 From 和 To 交換一下角色,接著從新的 From 里面開始分配。

    通過位運算實現(xiàn)加減乘除取模

    • 加法操作

    對于每一位而言,在不考慮進位的情況下,可以得到

    0+0 = 0\\
    0+1 = 1\\
    1+0 = 1\\
    1+1 = 0

    顯然,上面的情況符合 異或 操作且只有第四種情況發(fā)生了進位,進位情況符合 操作。在所有發(fā)生進位處,應該在更高的一位處加一,這個值可以通過 左移 操作實現(xiàn)。那么就可以得到

    x+y = x \oplus y + (x \& y)<<1

    可以發(fā)現(xiàn),后面的式子變成了一個新的加法式,那么只要遞歸計算即可。當 (x & y)<<1 == 0 時,就消除了加法式。

    ll add(ll x, ll y) { ll newx = x^y; ll newy = (x&y)<<1; if(newy == 0) return newx; return add(newx, newy);}
    • 減法操作

    減法操作可以看成


    同樣可以通過加法式得到

    ll sub(ll x, ll y) { return add(x, add(~y, 1));}
    • 乘法操作

    假設 y=1010,則可以關注于二進制上的 1 位,那么可以將 x*y 做出拆解

    \begin{aligned} xy &= x1010\ &= x1000 + x0010
    \end{aligned}

    而這個當乘數(shù)只有一個 1 時,可以通過二進制的左移操作實現(xiàn)。

    ll mul(ll x, ll y) { ll ans = 0; int flag = x^y; x = x<0 ? add(~x, 1) : x; y = y<0 ? add(~y, 1) : y; for(int i=0; (1ll< if(y&(1ll< ans = add(ans, x< } } return flag<0 ? add(~ans, 1) : ans;}
    • 除法操作

    和乘法操作思想一樣,枚舉答案每一位是否為 1,通過左移來得到乘積并減去。先從大的開始找,如果有一位是 1,那么就在答案將這一位設成 1。

    ll divide(ll x, ll y) { ll ans = 0; int flag = x^y; x = x<0 ? add(~x, 1) : x; y = y<0 ? add(~y, 1) : y; for(int i=31; i>=0; i--) { if(x >= (1ll*y< ans |= (1ll< x = sub(x, 1ll*y< } } return flag<0 ? add(~ans, 1) : ans;}
    • 取模操作

    已經得到了除法的結果,那么取模操作也很容易實現(xiàn)了

    ll mod(ll x, ll y) { x = x<0 ? add(~x, 1) : x; y = y<0 ? add(~y, 1) : y; return sub(x, mul(y, divide(x, y)));}

    為什么子類對象可以賦值給父類對象而反過來卻不行

    • 子類繼承于父類,它含有父類的部分,又做了擴充。如果子類對象賦值給父類變量,則使用該變量只能訪問子類的父類部分。
    • 如果反過來,這個子類變量如果去訪問它的擴充成員變量,就會訪問不到,造成內存越界。

    為什么 free 時不需要傳指針大小

    free 要做的事是歸還 malloc 申請的內存空間,而在 malloc 的時候已經記錄了申請空間的大小,所以不需要傳大小,直接傳指針就可以。

    手寫鏈表實現(xiàn) LRU

    class LRU {private: struct Node { int val; Node *pre, *suf; Node() { pre = suf = nullptr; } Node(int _val) { val = _val; pre = suf = nullptr; } }; int size; int capacity; Node *head; unordered_map<int, Node*> mp; Node* find(int val) { if(mp.count(val)) { return mp[val]; } else { return nullptr; } } void del(Node *node) { if(node == nullptr) return ; node->pre->suf = node->suf; node->suf->pre = node->pre; mp.erase(node->val); if(node == head) head = head->suf; size--; } void add(Node *node) { if(head == nullptr) { head = node; head->suf = head; head->pre = head; mp[node->val] = node; } else { node->suf = head; node->pre = head->pre; head->pre = node; node->pre->suf = node; mp[node->val] = node; head = node; } size++; }public: LRU() { mp.clear(); head = nullptr; size = capacity = 0; } void reverse(int _capacity) { capacity = _capacity; } void insert(int val) { Node *node = new Node(val); Node *selectnode = find(val); del(selectnode); if(size == capacity) { del(head->pre); } add(node); } void view() { Node *p = head; do { cout << p->val << " "; p = p->suf; } while(p != head); cout << endl; }}lru;

    推薦閱讀:

    C++ 萬字長文第一篇---拿下字節(jié)面試

    免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

    本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
    換一批
    延伸閱讀

    LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

    關鍵字: 驅動電源

    在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設備,其驅動電源的性能直接關系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅動電源設計中至關重要的兩個環(huán)節(jié),集成化方案的設計成為提升電機驅動性能的關鍵。

    關鍵字: 工業(yè)電機 驅動電源

    LED 驅動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設備的使用壽命。然而,在實際應用中,LED 驅動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設計、生...

    關鍵字: 驅動電源 照明系統(tǒng) 散熱

    根據(jù)LED驅動電源的公式,電感內電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

    關鍵字: LED 設計 驅動電源

    電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產業(yè)的重要發(fā)展方向。電動汽車的核心技術之一是電機驅動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅動系統(tǒng)中的關鍵元件,其性能直接影響到電動汽車的動力性能和...

    關鍵字: 電動汽車 新能源 驅動電源

    在現(xiàn)代城市建設中,街道及停車場照明作為基礎設施的重要組成部分,其質量和效率直接關系到城市的公共安全、居民生活質量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

    關鍵字: 發(fā)光二極管 驅動電源 LED

    LED通用照明設計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

    關鍵字: LED 驅動電源 功率因數(shù)校正

    在LED照明技術日益普及的今天,LED驅動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

    關鍵字: LED照明技術 電磁干擾 驅動電源

    開關電源具有效率高的特性,而且開關電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅動電源

    關鍵字: LED 驅動電源 開關電源

    LED驅動電源是把電源供應轉換為特定的電壓電流以驅動LED發(fā)光的電壓轉換器,通常情況下:LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

    關鍵字: LED 隧道燈 驅動電源
    關閉