C++面試八股文:override和finial關鍵字有什麼作用?

来源:https://www.cnblogs.com/binarch/archive/2023/06/22/17498594.html
-Advertisement-
Play Games

某日二師兄參加XXX科技公司的C++工程師開發崗位第22面: (二師兄好苦逼,節假日還在面試。。。) > 面試官:C++的繼承瞭解嗎? > > 二師兄:(不好意思,你面到我的強項了。。)瞭解一些。 > > 面試官:什麼是虛函數,為什麼需要虛函數? > > 二師兄:虛函數允許在基類中定義一個函數,然後 ...


某日二師兄參加XXX科技公司的C++工程師開發崗位第22面: (二師兄好苦逼,節假日還在面試。。。)

面試官:C++的繼承瞭解嗎?

二師兄:(不好意思,你面到我的強項了。。)瞭解一些。

面試官:什麼是虛函數,為什麼需要虛函數?

二師兄:虛函數允許在基類中定義一個函數,然後在派生類中進行重寫(override)。

二師兄:主要是為了實現面向對象中的三大特性之一多態。多態允許在子類中重寫父類的虛函數,同樣的函數在子類和父類實現不同的形態,簡稱為多態。

面試官:你知道overridefinial關鍵字的作用嗎?

二師兄:override關鍵字告訴編譯器,這個函數一定會重寫父類的虛函數,如果父類沒有這個虛函數,則無法通過編譯。此關鍵字可省略,但不建議省略。

二師兄:finial關鍵字告訴編譯器,這個函數到此為止,如果後續有類繼承當前類,也不能再重寫此函數。

二師兄:這兩個關鍵字都是C++11引入的,為了提升C++面向對象編碼的安全性。

面試官:你知道多態是怎麼實現的嗎?

二師兄:(起開,我要開始裝逼了!)C++主要使用了虛指針和虛表來實現多態。在擁有虛函數的對象中,包含一個虛指針(virtual pointer)(一般位於對象所在記憶體的起始位置),這個虛指針指向一個虛表(virtual table),虛表中記錄了虛函數的真實地址。

#include <iostream>
struct Foo
{
    size_t a = 42;
    virtual void fun1() {std::cout <<"Foo::fun1" << std::endl;}
    virtual void fun2() {std::cout <<"Foo::fun2" << std::endl;}
    virtual void fun3() {std::cout <<"Foo::fun3" << std::endl;}
};

struct Goo: Foo{
    size_t b = 1024;
    virtual void fun1() override {std::cout <<"Goo::fun1" << std::endl;}
    virtual void fun3() override {std::cout <<"Goo::fun3" << std::endl;}
};

using PF = void(*)();

void test(Foo* pf)
{
    size_t* virtual_point = (size_t*)pf;

    PF* pf1 = (PF*)*virtual_point;  
    PF* pf2 = pf1 + 1;  //偏移8位元組 到下一個指針 fun2
    PF* pf3 = pf1 + 2;  //偏移16位元組 到下下一個指針 fun3

    (*pf1)();   //Foo::fun1 or Goo::fun1 取決於pf的真實類型
    (*pf2)();   //Foo::fun2
    (*pf3)();   //Foo::fun3 or Goo::fun3 取決於pf的真實類型
}

int main(int argc, char const *argv[])
{
    Foo* fp = new Foo;
    test(fp);
    
    fp = new Goo;
    test(fp);

    size_t* virtual_point = (size_t*)fp;
    size_t* ap = virtual_point + 1;
    size_t* bp = virtual_point + 2;
    
    std::cout << *ap << std::endl;  //42
    std::cout << *bp << std::endl;  //1024
}

file

二師兄:當初始化虛表時,會把當前類override的函數地址寫到虛表中(Goo::fun1Goo::fun3),對於基類中的虛函數但是派生類中沒有override,則會把基類的函數地址寫到虛表中(Foo::fun2),在調用函數的時候,會通過虛指針轉到虛表,並根據虛函數的偏移得到真實函數地址,從而實現多態。

面試官:不錯。上圖你畫出了單一繼承的記憶體佈局,那多繼承呢?

二師兄:多繼承記憶體佈局類似,只不過會多幾個virtual pointer

#include <iostream>
struct Foo1
{
    size_t a = 42;
    virtual void fun1() {std::cout <<"Foo1::fun1" << std::endl;}
    virtual void fun2() {std::cout <<"Foo1::fun2" << std::endl;}
    virtual void fun3() {std::cout <<"Foo1::fun3" << std::endl;}
};

struct Foo2{
    size_t b = 1024;
    virtual void fun4()  {std::cout <<"Foo2::fun4" << std::endl;}
    virtual void fun5()  {std::cout <<"Foo2::fun5" << std::endl;}
};

struct Foo3{
    size_t c = 0;
    virtual void fun6()  {std::cout <<"Foo3::fun1" << std::endl;}
    virtual void fun7()  {std::cout <<"Foo3::fun3" << std::endl;}
};

struct Goo: public Foo1, public Foo2, public Foo3
{
    virtual void fun2() override {std::cout <<"Goo::fun2" << std::endl;}
    virtual void fun6() override {std::cout <<"Goo::fun6" << std::endl;}
};

int main(int argc, char const *argv[])
{
    Goo g;
    g.fun1();   //Foo1::fun1
    g.fun2();   //Goo::fun2
    g.fun3();   //Foo1::fun3
    g.fun4();   //Foo2::fun4
    g.fun5();   //Foo2::fun5
    g.fun6();   //Goo::fun6
    g.fun7();   //Foo3::fun7
}

file

面試官:你知道什麼是菱形繼承嗎?菱形繼承會引發什麼問題?如何解決?

二師兄:菱形繼承(Diamond Inheritance)是指在繼承層次結構中,如果兩個不同的子類B和C繼承自同一個父類A,而又有一個類D同時繼承B和C,這種繼承關係被稱為菱形繼承。

file

二師兄:因為B和C各繼承了一份A,當D繼承B和C的時候就會有2份A;

#include <iostream>
struct A
{
    int val = 42;
    virtual void fun(){std::cout <<"A::fun" << std::endl;}
};
struct B: public A{ void fun() override{std::cout <<"B::fun" << std::endl;}};
struct C: public A{ void fun() override{std::cout <<"C::fun" << std::endl;}};
struct D: public B, public C{void fun() override{std::cout <<"D::fun" << std::endl;}};
int main(int argc, char const *argv[])
{
   	D d;
    std::cout << d.val << std::endl;	//編譯失敗,不知道調用從哪個類中繼承的val變數
    d.fun(); 	//編譯失敗,不知道調用從哪個類中繼承的fun函數
}

二師兄:解決的辦法有兩種,一種是在調用符之前加上父類限定符:

std::cout << d.B::val << std::endl; //42
d.C::fun();     //C::fun

二師兄:但這裡並沒有解決數據冗餘的問題,因為D中有B和C,而B和C各有一個虛表和一個int類型的成員變數,所以sizeof(D)的大小是32(x86_64架構,考慮到記憶體對齊)。

二師兄:所幸在C++11引入了虛繼承(Virtual Inheritance)機制,從源頭上解決了這個問題:

#include <iostream>
struct A
{
    int val = 42;
    virtual void fun(){std::cout <<"A::fun" << std::endl;}
};
struct B: virtual public A{ void fun() override{std::cout <<"B::fun" << std::endl;}};
struct C: virtual public A{ void fun() override{std::cout <<"C::fun" << std::endl;}};
struct D: public B, public C{void fun() override{std::cout <<"D::fun" << std::endl;}};
int main(int argc, char const *argv[])
{
    D d;
    std::cout << d.val << std::endl; //42
    d.fun();     //D::fun
}

二師兄:此時在對象d中,只包含了一個val和兩個虛指針,成員變數的冗餘問題得到解決。

面試官:一般我們認為多態會影響性能,你舉得為什麼影響性能?

二師兄:大多數人認為,虛函數的調用會先通過虛指針跳到虛函數表,然後通過偏移確定函數真實地址,再跳轉到地址執行,是間接調用導致了性能損失。

二師兄:但實際上無法內聯才是虛函數性能低於正常函數的主要原因。由於多態是運行時特征,在編譯時編譯器並不知道指針指向的函數地址,所以無法被內聯。同時跳轉到特定地址執行函數可能引發的L1 cache miss(空間局部性不好),這也會影響性能。

面試官:虛函數的調用一定是非內聯的嗎?

二師兄:不是。現代編譯器很聰明,如果編譯器能夠在編譯時推斷出真實的函數,可能會直接內聯這個虛函數。虛函數的調用是否內聯取決於編譯器的實現和上下文。

面試官:你覺得多態在安全性上有沒有什麼問題?

二師兄:的確是有的。當我們把類中的虛函數定義為private的時候,雖然我們不能通過類的對象去訪問這個函數,但我們知道這個函數就在虛函數表中,可以通過特殊的方法(上文中已經給出示例)訪問它:

#include <iostream>
struct Foo
{
private:
    virtual void fun() {std::cout << "Foo::fun" << std::endl;}
};

int main(int argc, char const *argv[])
{
    Foo f;
    //f.fun();  //編譯錯誤
    using Fun = void(*)();
    size_t* virtual_point = (size_t*)&f;
    Fun* fun = (Fun*)*virtual_point;
    (*fun)();
}

面試官:好的,今天的面試到這裡就結束了,請回去等通知吧。

今天二師兄表現很不錯,加個肉粽。感謝小伙伴的耐心閱讀,祝各位小伙伴端午節牛逼(端午快樂->沒文化,端午安康->跟風狗,好吧我祝各位端午牛逼)。二師兄的C++面試之旅,明天不見不散

關註我,帶你21天“精通”C++!(狗頭)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 經過前幾篇文章的講解,初步瞭解ASP.NET Core MVC項目創建,啟動運行,以及命名約定,創建控制器,視圖,模型,接收參數,傳遞數據ViewData,ViewBag,路由,頁面佈局,wwwroot和客戶端庫,Razor語法,EnityFrameworkCore與資料庫,HttpContext,... ...
  • 好久沒寫博客了,今天給大家分享一個圖片轉PDF的相關操作,也算是一次總結吧。 首先需要準備動態庫itextsharp.dll,這個dll去網上下載,都可以下載到,C#對PDF的操作都是基於這個類庫來實現的。話不多說,直接上代碼。 /// <summary> /// 導出PDF /// </summa ...
  • # 個人博客-推薦文章載入優化 # 前言 隨著博客文章越來越多,那麼推薦的文章也是越來越多,之前推薦文章是只推薦8篇,但是我感覺有點少,然後也是決定加一個載入按鈕,也是類似與分頁的效果,點擊按鈕可以繼續載入8篇文章。 # 我的實現思路 同樣使用`X.PagedList`組件去實現分頁效果,通過Nug ...
  • 博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202306/3076680-20230622230643001-398516589.png) # 1. 停電事故後電力恢復的方式 ## 1.1. 停電後常見的情形是,送電幾秒鐘後又再次斷電 ## 1.2. 數 ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202306/3076680-20230621151546278-1606324122.png) # 1. 自黑式攻擊 ## 1.1. 自黑只會偶爾成為人類的美德 ## 1.2. 對系統來說,絕對不會推崇自黑 ...
  • 環境:CentOS 7.6_x64 Python版本 :3.9.12 pjsip版本:2.13 之前寫過一篇CentOS7環境編譯python3.9版本pjsua的文章: https://www.cnblogs.com/MikeZhang/p/centos7py39pjsua20230608.htm ...
  • java之父:高斯林 官方文檔: https://docs.oracle.com/javase/specs/index.html 官方線上PDF:https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf >深入理解java虛擬機 javaSE8: ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...