整理的C/C++相關的面經知識點

自己在找工作的過程中,參考了一些博客並自己整理的有關C/C++面經知識點。

C語言中 new和malloc的區別

  • **申請內存所在位置:**new/delete是操作符,malloc/free是函數;new操作符從自由存儲區(free store)上位對象動態分配內存空間,而malloc函數從從堆上動態分配內存。而且new在申請對象時會調用對象的構造函數和析構函數
    **自由存儲區是C++基於new操作符的一個抽象概念,凡是通過new操作符進行內存申請該內存
  • **返回類型安全性:*new操作符內存分配成功時,返回的是對象類型的指針。類型嚴格與對象匹配,無須進行類型轉換,而malloc內存分配成功則是返回void,需要通過強制類型轉換轉換成我們需要的類型。
  • **內存分配失敗時的返回值:**new內存分配失敗時,會拋出bad_alloc異常,不會返回NULL;malloc內存分配失敗時返回NULL。
  • 使用new操作符申請內存時無需指定內存大小,編譯器會根據類型信息自動進行計算;而malloc需要顯式的指定所需內存的大小

C++中有了maoolc/free, 爲什麼還需要new/delete?

malloc與free是c++/C語言的標準庫函數,new/delete是C++的運算符,他們都可以用於申請動態內存和釋放內存
對於非內部數據類型的對象而言,只用malloc/delete無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。由於malloc/free是庫函數而不是運算符,不在編譯器控制權限範圍內,不能夠把執行構造函數和析構函數的任務強加於malloc/delete。
因此C++語言需要一個能完成動態內存分配和初始化工作的運算符new,以及一個能完成清理與釋放內存工作的運算符delete。

malloc內存分配機制是,在哪裏分配內存,最大可以申請多大的內存

  • 它是將可用的內存塊連接爲一個長長的列表,即所謂的空閒鏈表,調用malloc函數時,它沿連接表尋找一個大到足以滿足用戶請求所需要的內存塊,將該內存塊一分爲二(一塊大小與用戶請求的大小相等,另一塊大小就是剩下的字節)
    調用free函數時,它將用戶釋放的內存塊連接到空閒鏈上,到最後,空閒鏈會被切成很多的小內存片段,如果這時用戶申請一個大的內存片段,那麼空閒鏈上可能沒有可以滿足用戶要求的片段了,於是,malloc函數請求延時,並開始在空閒鏈上檢查各內存片段,並進行整理,將相鄰的小空閒塊合併成較大的內存塊。
  • malloc函數是在堆上面分配內存
  • Linux下虛擬地址空間分配給進程的是3GB,Windows下默認的是2GB,真正能夠使用多少內存跟你的機器和編譯器決定了,
    在Linux系統能申請到的內存地址空間在2.8GB左右,而在windows下面(32位)得到的地址空間在1.2GB左右

malloc(0)的返回值問題

  1. malloc(0)在我的系統裏是可以正常返回一個非NULL值的。返回了一個正常的地址。

  2. malloc(0)申請的空間到底有多大不是用strlen或者sizeof來看的,而是通過malloc_usable_size這個函數來看的。—當然這個函數並不能完全正確的反映出申請內存的範圍。strlen和sizeof都是計算爲0

  3. malloc(0)申請的空間長度不是0,在我的系統裏它是12,也就是你使用malloc申請內存空間的話,正常情況下系統會返回給你一個至少12B的空間。這個可以從malloc(0)和malloc(5)的返回值都是12,而malloc(20)的返回值是20得到。—其實,如果你真的調用了這個程序的話,會發現,這個12確實是”至少12“的。

  4. malloc(0)申請的空間是可以被使用的。

C語言程序中爲什麼要從main函數開始

main只是開發工具所規定的一個特殊函數名稱而已,它既不是程序的入口,也不是必須要有的函數
如:

  • 一.在Linux C中,使用attribute關鍵字,聲明constructor和destructor,可以自定義程序入口點,不一定是在main函數開始執行
  • 二.在標準C中,編譯器在編譯的時候把你的程序開始執行的地址設爲main函數的地址,彙編中可以自由通過end僞指令制定
    在VS中給你可以通過這麼設置:
    項目->屬性->配置屬性->鏈接器->高級->入口點,改爲你想入口點的函數名
  • 三.在單片機C中,在C語言運行前都有彙編程序編寫的啓動程序,裏面對單片機進行的初始化,同時設置了C程序的入口main函數

new運算符的原理(底層使用了operator new(),最終調用了malloc),new運算符重載,怎麼寫重載函數,重載的定義

//++++++++++++++++++++++++++++++++++++++++++++//
首先區分new operator和operator new這兩個概念,前者new operator就是new操作符,一般,在new一個對象的時候,底層做了如下兩步:

  1. 爲對象分配內存

  2. 調用構造函數初始化這塊內存
    在第一步中分配內存時就使用了operator new,其實現機制

    void *operator new(std::size_t size) throw(std::bac_alloc)
    {
    if(size==0)
    size=1;
    void *p;
    while((p=::malloc(size))==0)
    {
    std::new_handler nh=std::get_new_handler();
    if(nh)
    nh();
    else
    throw std::bad_alloc();
    }
    return p;
    }
    void operator delete(void *ptr)
    {
    if(ptr)
    ::free(ptr);
    }
    在第二步中,調用構造函數初始化內存,如果不涉及繼承,這一步非常簡單,就是給內存賦值,重點是operator new分配內存上,其處理流程如下:

  3. 分配內存如果成功則直接返回,否則;

  4. 查看new_handler是否可用,new handler的作用用於再釋放一部分內存

  5. 如果可用則調用之並跳轉到1,否則;

  6. 拋出bad_alloc異常
    //++++++++++++++++++++++++++++++++++++++//

new,delete在C++中被歸爲運算符,所以可以重載他們
new的行爲:

  • 先開闢內存空間

  • 再調用類的構造函數
    在開闢內存空間的部分,可以被重載
    delete的行爲:

  • 先調用類的析構函數

  • 再釋放內存空間
    釋放內存空間的部分,可以被重載
    爲什麼要重載?
    有時候需要實現內存池的時候需要重載它們,頻繁的new和delete對象,會造成內存碎片,內存不足等問題,調用構造函數placement new。

memset函數的作用,有哪些參數

函數原型:

extern void *memset(void *buffer, int c, int count)

說明:把buffer所指內存區域的前count個字節設置成字符c,返回指向buffer的指針

void *memset(void *dest, int val, size_t count)
{
	if(dest == NULL || count < 0)
		return NULL;
	char *pdest = (char *)dest;
	while(count-- > 0)
	{
		*pdest++ = val;
	}
	return dest;
}

linux系統應用程序的內存空間分配(內核空間和進程空間),一般進程空間多大,內核空間多大,進程空間分佈是什麼樣的,堆區最大空間是多少

  • 在Linux系統中內核空間爲1GB(0xC0000000到0xFFFFFFFF),用戶進程空間爲3GB(0x00000000到0xBFFFFFFF)
  • 在Windows系統中默認的內存分配是內核空間2GB,用戶進程空間爲2GB
    進程空間分佈:
  • stack(棧):存儲局部,臨時變量,在函數調用時,存儲函數的返回指針,用於控制函數的調用和返回,在程序開始時自動分配內存,結束時自動釋放內存,(Linux系統通常在8MB左右)
  • Heap(堆):存儲動態內存分配,需要程序員手工分配,手工釋放
  • uninitialized data(BSS):在程序運行初未對變量進行初始化的數據
  • initialized data(Data):在程序運行初已經對變量進行初始化的數據
  • Text(程序段):程序代碼在內存中的映射,存放函數體的二進制代碼
    堆區的大小受限於計算機系統中有效的虛擬內存

C++中的模板機制了,原理,類模板和函數模板在定義時的區別

函數模板機制結論:

  • 編譯器並不是把函數模板處理成能夠處理任意類的函數
  • 編譯器從函數模板通過具體類型產生不同的函數
  • 編譯器會對函數模板進行兩次編譯
  • 在聲明的地方對模板代碼本身進行編譯;在調用的地方的地方對參數替換後的代碼進行編譯

模板就是泛型編程的基礎,泛型編程即以一種獨立於任何特定類型的方式編寫代碼,可以實現算法與數據結構的分離。
函數重載不是一種面向對象的編程,而只是一種語法規則,重載跟多態沒有什麼直接關係

二叉樹的原理,平衡二叉樹的原理,二叉樹的遍歷方式,二叉樹的最大節點數,二叉樹插入刪除的時間複雜度,二叉樹插入的時間複雜度與樹的節點數和樹深度的關係,二叉樹的優點

數組和鏈表的區別,它們的應用場景

  • 鏈表是鏈式的存儲結構,數組是順序的存儲結構,在內存中,數組是一塊連續的區域
  • 鏈表的插入與刪除元素相對數組較爲簡單,不需要移動元素,但查找某個元素較爲複雜
  • 鏈表查找某個元素較爲簡單,但插入與刪除元素較爲複雜,且最大長度在編程一開始時就指定,擴充長度不如鏈表方便
  • 數組在內存中連續,在棧區,鏈表在內存中不連續,一般在堆區

**應用場景:**需要快速訪問數據,很少或不插入或者刪除元素,用數組
需要經常插入或者刪除元素用鏈表數組結構

C++繼承機制,虛繼承原理

繼承是使代碼可以複用的重要手段,也是面向對象程序設計的核心思想之一。簡單地說,繼承是指一個對象直接使用另一個對象的屬性和方法。繼承呈現了面向對象程序設計的層次結構,原始類稱爲基類,繼承類稱爲派生類,也分別叫做父類和子類,子類也可以爲父類,被另外的類繼承。繼承的方式有三種分別爲公有繼承(public)、保護繼承(protect)、私有繼承(private)

  • **公有繼承:**基類的公有成員和保護成員作爲派生類的成員時,它們都保持原有的狀態,而基類的私有成員對派生類是不可見的,也不能被這個派生類的子類所訪問
  • **私有繼承:**基類的公有成員和保護成員作爲派生類的私有成員,並且不能被這個派生類的子類所訪問
  • **保護繼承:**基類的所有公有成員和保護成員都成爲派生類的保護成員,並且只能被它的派生類成員函數或友元訪問,基類的私有成員仍然私有,且對派生類不可見
    public繼承是一個接口繼承,保持is-a原則,每個父類可用的成員對子類也可用,因爲每個子類對象也都是一個父類對象

private/protected繼承是一個實現繼承,基類的部分成員並非完全成爲子類接口的一部分,是has-a的關係原則
一個類有多個基類,這樣的繼承關係稱爲多繼承

=====class 派生名:訪問控制符 基類名1,訪問控制符 基類名2

虛繼承的作用是減少了對基類的重複,代價是增加了虛表指針的負擔(更多的虛表指針)

虛繼承:

虛擬繼承是多重繼承中特有的概念。虛擬基類是爲解決多重繼承(菱形繼承)而出現的。如:類D繼承自類B1、B2,而類B1、B2都繼承自類A,因此在類D中兩次出現類A中的變量和函數。爲了節省內存空間,可以將B1、B2對A的繼承定義爲虛擬繼承,而A就成了虛擬基類。實現的代碼如下:

class A

class B1:public virtual A;

class B2:public virtual A;

class D:public B1,public B2;

虛擬繼承在一般的應用中很少用到,所以也往往被忽視,這也主要是因爲在C++中,多重繼承是不推薦的,也並不常用,而一旦離開了多重繼承,虛擬繼承就完全失去了存在的必要因爲這樣只會降低效率和佔用更多的空間。

虛函數機制,一個類有虛函數,有成員變量,求所佔的內存大小

**虛函數:**虛函數的調用是通過虛函數表指針和虛函數表來實現的

  • 虛函數表和類綁定,只此一份。虛函數表指針每個類對象都有,指針值都是同一個虛函數表的地址
  • 虛函數指針vfptr不屬於數據成員,vfptr的值是在構造函數內容執行之前進行初始化的,構造函數是根據本類的類型進行初始化的。所以拷貝構造函數,或者賦值函數都不會影響到vfptr的值
  • 對虛函數的調用,都是通過實體的vfptr所指的虛函數表來進行調用的
    內存排布:子類繼承父類的成員變量,內存上會先排布父類的成員變量,接着排布子類的成員變量,其中成員函數不佔字節
  • 每個類都有虛指針和虛表
  • 如果不是虛繼承,那麼子類將父類的虛指針繼承下來,並指向自身的虛表(發生在對象構造時),有多少個函數,虛表裏面的項就會有多少,多重繼承時,可能存在多個的基類虛表和虛指針
  • 如果是虛繼承,那麼子類會有兩份虛指針,一份指向自己的虛表,另一份指向虛基表,多重繼承時虛基表與虛基表指針有且只有一份

總結如下:

  • **無虛函數覆蓋的繼承:**虛函數按照其聲明順序放在虛函數表中,父類的虛函數在其子類的虛函數的前面
  • **有虛函數覆蓋的繼承:**派生類中起覆蓋作用的虛函數放在原基類虛函數的位置,沒有被覆蓋的虛函數依舊
  • **無虛函數覆蓋的多重繼承:**每個父類都有自己的虛函數表,派生類的虛函數被放在了第一個父類的虛函數表中(按照聲明順序排序)
  • **有虛函數覆蓋的多重繼承:**派生類中起覆蓋作用的虛函數放在原基類虛函數的位置,沒有被覆蓋的虛函數依舊
  • **無虛函數覆蓋的虛繼承:**派生類有單獨的虛函數表,另外也單獨保存一份父類的虛函數表
  • **有虛函數覆蓋的虛繼承:**派生類單獨的虛函數表,另外也單獨保存一份父類的虛函數表,派生類中起覆蓋作用的虛函數放在原基類虛函數的位置,沒有被覆蓋的虛函數依舊

C++中的多態

多態主要是有靜態多態和動態多態

  • **靜態多態:**主要是函數重載和泛型編程
  • **動態多態:**虛函數

靜態多態:也稱爲靜態綁定或早綁,編譯器在編譯期間完成的,編譯器根據函數實參的類型(可能會進行隱式類型轉換),可推斷出要調用的那個函數,如果有對應的函數就調用該函數,否則出現編譯錯誤。

動態多態:在程序執行期間判斷所引用對象的實際類型,根據實際類型調用相應的方法,使用virtual關鍵字修飾類的成員函數時,指明該函數爲虛函數,派生類需要重新實現,編譯器實現動態綁定

總結:

  • 1.在編譯過程中的聯編的被稱爲靜態聯編(static binding)
  • 2.在運行時選擇正確的虛方法,被稱爲動態聯編
  • 3.編譯器對非虛方法使用靜態聯編,編譯器對虛方法使用動態聯編

說說引用,什麼時候用引用好,什麼時候用指針好?

指針和引用的區別:

  • 非空區別:指針可以爲空,而引用不能爲空
  • 可修改區別:如果指針不是常指針,那麼就可以修改指向,而引用不能
  • 初始化區別:指針在定義時可以不用初始化,而引用在定義的同時必須初始化
  • 從內存上來講,系統爲指針分配內存空間,而引用與綁定的對象共享內存空間,系統不爲引用變量分配內存空間

使用引用參數的主要原因有兩個:
•程序員能修改調用函數中的數據對象
•通過傳遞引用而不是整個數據–對象,可以提高程序的運行速度

一般的原則:
對於使用引用的值而不做修改的函數:
•如果數據對象很小,如內置數據類型或者小型結構,則按照值傳遞
•如果數據對象是數組,則使用指針(唯一的選擇),並且指針聲明爲指向const的指針
•如果數據對象是較大的結構,則使用const指針或者引用,已提高程序的效率。這樣可以節省結構所需的時間和空間
•如果數據對象是類對象,則使用const引用(傳遞類對象參數的標準方式是按照引用傳遞)

對於修改函數中數據的函數:
•如果數據是內置數據類型,則使用指針
•如果數據對象是數組,則只能使用指針
•如果數據對象是結構,則使用引用或者指針
•如果數據是類對象,則使用引用

關鍵字const是什麼含義

const是C++中常用的類型修飾符,常類型使指使用類型修飾符const說明的類型,意味着“只讀”

const的作用:
  • **可以定義const常量:**如const int max = 100;

  • **便於進行類型檢查:**const常量有數據類型,而#define宏常量沒有數據類型,編譯器可以對前者進行類型安全檢查,而對後者只進行字符替換,沒有類型安全檢查,並且在字符替換時可能產生意想不到的錯誤

  • **可以保護被修飾的東西:**防止意外的修改,增強程序的健壯性

  • 爲函數重載提供了一個參考:

  • **可以節省空間,避免不必要的內存分配:**const定義常量從彙編的角度來看,只是給出了對應的內存地址,而不是像#define一樣給出的是立即數,所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝

    #define PI 3.14159 //常量宏
    const double Pi = 3.14159 //此時並未將Pi放入ROM中
    double i=Pi; //此時爲Pi分配內存,以後不在分配
    double I=PI; //編譯期間進行宏替換,分配內存
    double j=Pi; //沒有內存分配
    double J=PI; //再進行宏替換,有一次分配內存

**提高了效率:**編譯器通常不爲普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成爲一個編譯期間的常量,沒有了存儲與讀內存的操作,使得它的效率很高

const的使用
const int a;  
int const a;   //前兩個作用一樣,a是一個常整型數
const int *a;  //a是一個指向常整型數的指針(整型數是不可修改的,指針是可以修改的)---底層const
int *const a;  //a是一個指向整型數的常指針(指針指向的整型數是可以修改的,但指針是不可修改的)---頂層const
int const *a const;   //a是一個指向常整型的常指針(指針指向的整型數是不可以修改的,同時指針也是不可以修改的)

即指針使用const有:

  • 指針本身是常量不可變: char * const pContent;
  • **指針所指向的內容是常量不可變:**const char *pContent;
  • 兩者都不可變: const char * const pContent;

**區別方法:**沿着*號劃一條線:

  • **底層const:**如果const位於*的左側,則const就是用來修飾指針所指向的變量,即指針指向爲常量;常量指針:指向的對象是個常量,不能修改其內容,只能更改指針指向的地址
  • **頂層const:**如果const位於*的右側,const就是修飾指針本身,即指針本身是常量,指針常量,不能修改指針指向的地址,只能修改其內容

函數中使用const

(1) const修飾函數參數

a.傳遞過來的參數在函數內不可以改變(無意義,因爲Var本身就是形參)

void function(const int Var);

b.參數指針所指向內容爲常量不可變

void function(const char *Var);

c.參數指針本身爲常量不可變(無意義,因爲char *Var也是形參)

void function(char *const Var);

d.參數爲引用,爲了增加效率同時防止修改,修飾引用參數時:

void function(const Class& Var);  //引用參數在函數內不可以改變
void function(const TYPE& Var);   //引用參數在函數內爲常量不可變

實現庫函數strcpy的功能,不能使用任何庫函數:

char *strcpy(char *strDest, const char *strSrc)
{
	if(strDest == NULL || strSrc == NULL)
		return NULL;
	if(strDest == strSrc)
		return strDest;
	char *temptr = strDest;
	while((*temptr++ = *strSrc++) != '\0');
	return temptr;
}

實現庫函數strcmp函數的功能

int strcmp(const char *strDest, const char *strSrc)
{
	assert(strDest != NULL && strSrc != NULL);
	while(*strDest && *strSrc && *strDest == *strSrc)
	{
		strDest++;
		strSrc++;
	}
	return *strDest - *strSrc;
}

C語言中static有什麼作用

  • 函數體內static變量的作用範圍爲該函數體,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值
  • 模塊體內static全局變量可以被模塊體內所有函數訪問,但不能被模塊外其他函數訪問
  • 模塊體內static函數只可被這一模塊內的其他函數調用,這個函數的使用範圍被限制在聲明它的模塊內
  • 類中的static成員變量屬於這個類所有,對類的所有對象只有一份拷貝
  • 類中的static成員函數屬於整個類所有,這個函數不接收this指針,因而只能訪問類的static成員變量。

C++中的struct和class的全部區別

  • C++中的struct具有了"類"的功能,與關鍵字class的區別在於struct中的成員變量和函數的默認訪問權限爲public,而class的爲private
  • struct可以在定義的時候直接以{}對其成員變量賦初值,而class不能

C++內存管理

在C++中,內存分成5個區,他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區

  • ,在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
  • ,就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那麼在程序結束後,操作系統會自動回收。
  • 自由存儲區,就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。
  • 全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分爲初始化的和未初始化的,在C++裏面沒有這個區分了,他們共同佔用同一塊內存區。
  • 常量存儲區,這是一塊比較特殊的存儲區,他們裏面存放的是常量,不允許修改。

C編譯的程序佔用存儲

  • **棧區(stack):**由編譯器自動分配釋放,存放函數的參數值,局部變量值等
  • **堆區(heap):**一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收
  • **全局/靜態存儲區(static):**全局變量和靜態變量的存儲是放在一起的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和靜態變量在相鄰的另一塊區域,程序結束後有系統釋放
  • **文字常量區:**常量字符串存放在這裏
  • **程序代碼區:**存放函數體的二進制代碼

內存泄漏的情況

  • 緩衝區溢出
  • new/delete函數沒有匹配
  • 淺拷貝
  • 野指針/空懸指針
  • 重複釋放
  • 內存碎片

C++中成員變量的列表初始化

class A
{
private:
    int n1;
    int n2;

public:
    A():n2(0),n1(n2+2){}

    void Print(){
        cout << "n1:" << n1 << ", n2: " << n2 <<endl;  
    }
};

int main()
{

    A a;
    a.Print();

    return 0;
}

輸出結果爲n1:隨機, n2:0

  • 成員變量在使用初始化列表初始化時,與構造函數中初始化成員列表的順序無關,只與定義成員變量的順序有關。因爲成員變量的初始化次序是根據變量在內存中次序有關,而內存中的排列順序早在編譯期就根據變量的定義次序決定了。
  • 如果不使用初始化列表初始化,在構造函數內初始化時,此時與成員變量在構造函數中的位置有關。
  • 注意:類成員在定義時,是不能初始化的
  • 注意:類中const成員常量必須在構造函數初始化列表中初始化。
  • 注意:類中static成員變量,必須在類外初始化。

一個C++源文件從文本到可執行文件經歷的過程

對於c/c++編寫的程序,從源代碼到可執行文件,一般經過下面四個步驟:

  • 預處理, 產生.ii文件 gcc -E hello.c -o hello.i
  • 編譯, 產生彙編文件.S文件 gcc -S hello.i -o hello.S
  • 彙編,產生目標文件.o或.obj文件 gcc -C hello.S -o hello.o
  • 鏈接,產生可執行文件.out或.exe文件 gcc hello.o -o hello

gcc -Wall 允許發出GCC提供的所有有用的警告信息
gcc -w 關閉所有警告信息
使用GDB調試可執行文件之前,必須使用帶-g或者-gdb編譯選項的gcc命令來編譯源程序
編譯:gcc test.c -o test -g
GDB調試: gdb test
GDB調試當前正在運行的程序:
1.編譯時帶-g選項
2.運行程序
3.ps找到進程號
4.啓動gdb, 使用attach選項,這時gdb會停止在程序的某處
5.按照GDB調式方法調式,當程序退出之後,依然可以使用run命令重啓程序

C++的新特性

  • 關鍵字及新語法:auto, nullptr, for
  • STL容器:std::array, std::forward_list, std::unordered_map, std::unordered_set
  • 多線程:std::thread, std::atomic, std::condition_variable
  • 智能指針內存管理:std::shared_ptr, std::weak_ptr
  • 其他:std::function, std::bind和lamda表達式

C++中的虛類

C++中的虛類相當於模型類,定義虛類的方式是在:在類中創建純虛函數,這就是多態的一種方式
純虛類是不能創建對象的,但是它能創建指針對象,用他存儲其派生類的對象,但是存儲了派生類的對象也只能訪問其有的成員

C++中的析構函數爲什麼是虛函數

如果基類的析構函數不是虛函數,在特定情況下會導致派生類無法被析構
情況一:用派生類類型指針綁定派生類實例,析構的時候,不管基類析構函數是不是虛函數,都會正常析構
情況二:用基類類型指針綁定派生類實例,析構的時候,如果基類析構函數不是虛函數,則只會析構基類,不會析構派生類對象,從而造成內存泄漏

帶參數的宏定義

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章