<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C/C++ 八股文(二)

          共 21175字,需瀏覽 43分鐘

           ·

          2021-09-14 19:11

          C++八股文(一)

          說一下static關(guān)鍵字的作用

          參考回答:

          1 全局靜態(tài)變量

          在全局變量前加上關(guān)鍵字 static,全局變量就定義成一個全局靜態(tài)變量。

          靜態(tài)存儲區(qū),在整個程序運(yùn)行期間一直存在。

          初始化:未經(jīng)初始化的全局靜態(tài)變量會被自動初始化為 0(自動對象的值是任意的,除非他被顯式初始化);

          作用域:全局靜態(tài)變量在聲明他的文件之外是不可見的,準(zhǔn)確地說是從定義之處開始,到文件結(jié)尾。

          2 局部靜態(tài)變量

          在局部變量之前加上關(guān)鍵字 static,局部變量就成為一個局部靜態(tài)變量。

          內(nèi)存中的位置:靜態(tài)存儲區(qū)

          初始化:未經(jīng)初始化的全局靜態(tài)變量會被自動初始化為 0(自動對象的值是任意的,除非他被顯式初始化);

          作用域:作用域仍為局部作用域,當(dāng)定義它的函數(shù)或者語句塊結(jié)束的時候,作用域結(jié)束。但是當(dāng)局部靜態(tài)變量離開作用域后,并沒有銷毀,而是仍然駐留在內(nèi)存當(dāng)中,只不過我們不能再對它進(jìn)行訪問,直到該函數(shù)再次被調(diào)用,并且值不變;

          3 靜態(tài)函數(shù)

          在函數(shù)返回類型前加 static,函數(shù)就定義為靜態(tài)函數(shù)。函數(shù)的定義和聲明在默認(rèn)情況下都是 extern 的,但靜態(tài)函數(shù)只是在聲明他的文件當(dāng)中可見,不能被其他文件所用。

          函數(shù)的實(shí)現(xiàn)使用 static 修飾,那么這個函數(shù)只可在本 cpp 內(nèi)使用,不會同其他 cpp 中的同名函數(shù)引起沖突;

          warning:不要再頭文件中聲明 static 的全局函數(shù),不要在 cpp 內(nèi)聲明非static 的全局函數(shù),如果你要在多個 cpp 中復(fù)用該函數(shù),就把它的聲明提到頭文件里去,否則 cpp 內(nèi)部聲明需加上 static 修飾;

          4 類的靜態(tài)成員

          在類中,靜態(tài)成員可以實(shí)現(xiàn)多個對象之間的數(shù)據(jù)共享,并且使用靜態(tài)數(shù)據(jù)成員還不會破壞隱藏的原則,即保證了安全性。因此,靜態(tài)成員是類的所有對象中共享的成員,而不是某個對象的成員。對多個對象來說,靜態(tài)數(shù)據(jù)成員只存儲一處,供所有對象共用

          5 類的靜態(tài)函數(shù)

          靜態(tài)成員函數(shù)和靜態(tài)數(shù)據(jù)成員一樣,它們都屬于類的靜態(tài)成員,它們都不是對象成員。因此,對靜態(tài)成員的引用不需要用對象名。

          在靜態(tài)成員函數(shù)的實(shí)現(xiàn)中不能直接引用類中說明的非靜態(tài)成員,可以引用類中說明的靜態(tài)成員(這點(diǎn)非常重要)。如果靜態(tài)成員函數(shù)中要引用非靜態(tài)成員時,可通過對象來引用。從中可看出,調(diào)用靜態(tài)成員函數(shù)使用如下格式:<類名>::<靜態(tài)成員函數(shù)名>(<參數(shù)表>);

          說一下C++和C的區(qū)別

          參考回答:

          設(shè)計(jì)思想上:

          C++ 是面向?qū)ο蟮恼Z言,而C是面向過程的結(jié)構(gòu)化編程語言

          語法上:

          • C++ 具有封裝、繼承和多態(tài)三種特性
          • C++ 相比 C,增加多許多類型安全的功能,比如強(qiáng)制類型轉(zhuǎn)換、
          • C++ 支持范式編程,比如模板類、函數(shù)模板等

          說一說c++中四種cast轉(zhuǎn)換

          參考回答: C++ 中四種類型轉(zhuǎn)換是:static_cast, dynamic_cast,const_cast, reinterpret_cast

          1、const_cast

          用于將 const 變量轉(zhuǎn)為非 const

          2、static_cast

          用于各種隱式轉(zhuǎn)換,比如非 const 轉(zhuǎn) const,void* 轉(zhuǎn)指針等, static_cast能用于多態(tài)向上轉(zhuǎn)化,如果向下轉(zhuǎn)能成功但是不安全,結(jié)果未知;

          3、dynamic_cast

          用于動態(tài)類型轉(zhuǎn)換。只能用于含有虛函數(shù)的類,用于類層次間的向上和向下轉(zhuǎn)化。只能轉(zhuǎn)指針或引用。向下轉(zhuǎn)化時,如果是非法的對于指針返回NULL,對于引用拋異常。要深入了解內(nèi)部轉(zhuǎn)換的原理。

          • 向上轉(zhuǎn)換:指的是子類向基類的轉(zhuǎn)換
          • 向下轉(zhuǎn)換:指的是基類向子類的轉(zhuǎn)換

          它通過判斷在執(zhí)行到該語句的時候變量的運(yùn)行時類型和要轉(zhuǎn)換的類型是否相同來判斷是否能夠進(jìn)行向下轉(zhuǎn)換。

          4、reinterpret_cast

          幾乎什么都可以轉(zhuǎn),比如將int轉(zhuǎn)指針,可能會出問題,盡量少用;

          5、為什么不使用 C 的強(qiáng)制轉(zhuǎn)換?

          C 的強(qiáng)制轉(zhuǎn)換表面上看起來功能強(qiáng)大什么都能轉(zhuǎn),但是轉(zhuǎn)化不夠明確,不能進(jìn)行錯誤檢查,容易出錯。

          請說一下C/C++ 中指針和引用的區(qū)別?

          參考回答

          1. 指針有自己的一塊空間,而引用只是一個別名;
          2. 使用 sizeof 看一個指針的大小是4,而引用則是被引用對象的大小;
          3. 指針可以被初始化為 NULL,而引用必須被初始化且必須是一個已有對象 的引用;
          4. 作為參數(shù)傳遞時,指針需要被解引用才可以對對象進(jìn)行操作,而直接對引 用的修改都會改變引用所指向的對象;
          5. 可以有 const 指針,但是沒有 const 引用;
          6. 指針在使用中可以指向其它對象,但是引用只能是一個對象的引用,不能 被改變;
          7. 指針可以有多級指針(**p),而引用止于一級;
          8. 指針和引用使用++運(yùn)算符的意義不一樣;
          9. 如果返回動態(tài)內(nèi)存分配的對象或者內(nèi)存,必須使用指針,引用可能引起內(nèi)存泄露。

          給定三角形ABC和一點(diǎn)P(x,y,z),判斷點(diǎn)P是否在ABC內(nèi),給出思路并手寫代碼

          參考回答:

          根據(jù)面積法,如果 P 在三角形 ABC 內(nèi),那么三角形 ABP 的面積+三角形 BCP 的面積+三角形 ACP 的面積應(yīng)該等于三角形 ABC 的面積。算法如下:

          #include <iostream>
          #include <math.h>
          using namespace std;
          #define ABS_FLOAT_0 0.0001
          struct point_float
          {
          float x;
          float y;
          };
          /**

          * @brief 計(jì)算三角形面積

          */

          float GetTriangleSquar(const point_float pt0, const point_float pt1, const point_float pt2)
          {
              point_float AB,   BC;
              AB.x = pt1.x - pt0.x;
              AB.y = pt1.y - pt0.y;
              BC.x = pt2.x - pt1.x;
              BC.y = pt2.y - pt1.y;
              return fabs((AB.x * BC.y - AB.y * BC.x)) / 2.0f;
          }
          /**

          * @brief 判斷給定一點(diǎn)是否在三角形內(nèi)或邊上

          */
          bool IsInTriangle(const point_float A, const point_float B, const point_float C, const point_float D)
          {
              float SABC, SADB, SBDC, SADC;
              SABC = GetTriangleSquar(A, B, C);
              SADB = GetTriangleSquar(A, D, B);
              SBDC = GetTriangleSquar(B, D, C);
              SADC = GetTriangleSquar(A, D, C);
              float SumSuqar = SADB + SBDC + SADC;
              if ((-ABS_FLOAT_0 < (SABC - SumSuqar)) && ((SABC - SumSuqar) < 
                  ABS_FLOAT_0))
              {
                  return true;
              }
              else
              {
                  return false;
              }
          }

          怎么判斷一個數(shù)是二的倍數(shù),怎么求一個數(shù)中有幾個1,說一下你的思路并手寫代碼

          參考回答:

          1. 判斷一個數(shù)是不是二的倍數(shù),即判斷該數(shù)二進(jìn)制末位是不是 0:a % 2 == 0 或者a & 0x0001 == 0
          2. 求一個數(shù)中 1 的位數(shù),可以直接逐位除十取余判斷:
          int fun(long x)
          {
          int _count = 0;
          while(x)
          {
              if(x % 10 == 1)
                   ++_count;
              x /= 10;
          }
          return _count;
          }
          int main()
          {
              cout << fun(123321) << endl;
              return 0;
          }

          請你回答一下野指針是什么?

          參考回答:

          野指針就是指向一個已刪除的對象或者未申請?jiān)L問受限內(nèi)存區(qū)域的指針

          請你介紹一下 C++ 中的智能指針

          參考回答:

          智能指針主要用于管理在堆上分配的內(nèi)存,它將普通的指針封裝為一個棧對象。當(dāng)棧對象的生存周期結(jié)束后,會在析構(gòu)函數(shù)中釋放掉申請的內(nèi)存,從而防止內(nèi)存泄漏。C++ 11 中最常用的智能指針類型為 shared_ptr,它采用引用計(jì)數(shù)的方法,記錄當(dāng)前內(nèi)存資源被多少個智能指針引用。該引用計(jì)數(shù)的內(nèi)存在堆上分配。當(dāng)新增一個時引用計(jì)數(shù)加1,當(dāng)過期時引用計(jì)數(shù)減一。只有引用計(jì)數(shù)為 0 時,智能指針才會自動釋放引用的內(nèi)存資源。對 shared_ptr 進(jìn)行初始化時不能將一個普通指針直接賦值給智能指針,因?yàn)橐粋€是指針,一個是類。可以通過 make_shared 函數(shù)或者通過構(gòu)造函數(shù)傳入普通指針。并可以通過 get 函數(shù)獲得普通指針。

          請你回答一下智能指針有沒有內(nèi)存泄露的情況

          參考回答:

          當(dāng)兩個對象相互使用一個 shared_ptr 成員變量指向?qū)Ψ剑瑫斐裳h(huán)引用,使引用計(jì)數(shù)失效,從而導(dǎo)致內(nèi)存泄漏。例如:

          請你來說一下智能指針的內(nèi)存泄漏如何解決

          參考回答:

          為了解決循環(huán)引用導(dǎo)致的內(nèi)存泄漏,引入了 weak_ptr 弱指針,weak_ptr 的構(gòu)造函數(shù)不會修改引用計(jì)數(shù)的值,從而不會對對象的內(nèi)存進(jìn)行管理,其類似一個普通指針,但不指向引用計(jì)數(shù)的共享內(nèi)存,但是其可以檢測到所管理的對象是否已經(jīng)被釋放,從而避免非法訪問。

          請你回答一下為什么析構(gòu)函數(shù)必須是虛函數(shù)?為什么C++默認(rèn)的析構(gòu)函數(shù)不是虛函數(shù) 考點(diǎn):虛函數(shù) 析構(gòu)函數(shù)

          參考回答:

          將可能會被繼承的父類的析構(gòu)函數(shù)設(shè)置為虛函數(shù),可以保證當(dāng)我們new一個子類,然后使用基類指針指向該子類對象,釋放基類指針時可以釋放掉子類的空間,防止內(nèi)存泄漏。

          C++ 默認(rèn)的析構(gòu)函數(shù)不是虛函數(shù)是因?yàn)樘摵瘮?shù)需要額外的虛函數(shù)表和虛表指針,占用額外的內(nèi)存。而對于不會被繼承的類來說,其析構(gòu)函數(shù)如果是虛函數(shù),就會浪費(fèi)內(nèi)存。因此 C++ 默認(rèn)的析構(gòu)函數(shù)不是虛函數(shù),而是只有當(dāng)需要當(dāng)作父類時,設(shè)置為虛函數(shù)。

          請你來說一下 C++ 中析構(gòu)函數(shù)的作用

          參考回答:

          析構(gòu)函數(shù)與構(gòu)造函數(shù)對應(yīng),當(dāng)對象結(jié)束其生命周期,如對象所在的函數(shù)已調(diào)用完畢時,系統(tǒng)會自動執(zhí)行析構(gòu)函數(shù)。

          析構(gòu)函數(shù)名也應(yīng)與類名相同,只是在函數(shù)名前面加一個位取反符~,例如 ~stud( ),以區(qū)別于構(gòu)造函數(shù)。它不能帶任何參數(shù),也沒有返回值(包括 void 類型)。只能有一個析構(gòu)函數(shù),不能重載。

          如果用戶沒有編寫析構(gòu)函數(shù),編譯系統(tǒng)會自動生成一個缺省的析構(gòu)函數(shù)(即使自定義了析構(gòu)函數(shù),編譯器也總是會為我們合成一個析構(gòu)函數(shù),并且如果自定義了析構(gòu)函數(shù),編譯器在執(zhí)行時會先調(diào)用自定義的析構(gòu)函數(shù)再調(diào)用合成的析構(gòu)函數(shù)),它也不進(jìn)行任何操作。所以許多簡單的類中沒有用顯式的析構(gòu)函數(shù)。

          如果一個類中有指針,且在使用的過程中動態(tài)的申請了內(nèi)存,那么最好顯示構(gòu)造析構(gòu)函數(shù)在銷毀類之前,釋放掉申請的內(nèi)存空間,避免內(nèi)存泄漏。

          類析構(gòu)順序:

          • 派生類本身的析構(gòu)函數(shù);
          • 對象成員析構(gòu)函數(shù);
          • 基類析構(gòu)函數(shù)。

          請你來說一下靜態(tài)函數(shù)和虛函數(shù)的區(qū)別

          參考回答:

          靜態(tài)函數(shù)在編譯的時候就已經(jīng)確定運(yùn)行時機(jī),虛函數(shù)在運(yùn)行的時候動態(tài)綁定。虛函數(shù)因?yàn)橛昧颂摵瘮?shù)表機(jī)制,調(diào)用的時候會增加一次內(nèi)存開銷

          請你來說一說重載和覆蓋

          參考回答:

          重載:兩個函數(shù)名相同,但是參數(shù)列表不同(個數(shù),類型),返回值類型沒有要求,在同一作用域中

          重寫:子類繼承了父類,父類中的函數(shù)是虛函數(shù),在子類中重新定義了這個虛函數(shù),這種情況是重寫

          請你說一說 strcpy 和 strlen

          參考回答:

          strcpy 是字符串拷貝函數(shù),原型:

          char strcpy(char dest, const char *src);

          從 src 逐字節(jié)拷貝到 dest,直到遇到 '\0' 結(jié)束,因?yàn)闆]有指定長度,可能會導(dǎo)致拷貝越界,造成緩沖區(qū)溢出漏洞,安全版本是 strncpy 函數(shù)。

          strlen 函數(shù)是計(jì)算字符串長度的函數(shù),返回從開始到 '\0' 之間的字符個數(shù)。

          請你說一說你理解的虛函數(shù)和多態(tài)

          參考回答:

          多態(tài)的實(shí)現(xiàn)主要分為靜態(tài)多態(tài)和動態(tài)多態(tài),靜態(tài)多態(tài)主要是重載,在編譯的時候就已經(jīng)確定;動態(tài)多態(tài)是用虛函數(shù)機(jī)制實(shí)現(xiàn)的,在運(yùn)行期間動態(tài)綁定。舉個例子:一個父類類型的指針指向一個子類對象時候,使用父類的指針去調(diào)用子類中重寫了的父類中的虛函數(shù)的時候,會調(diào)用子類重寫過后的函數(shù),在父類中聲明為加了 virtual 關(guān)鍵字的函數(shù),在子類中重寫時候不需要加 virtual 也是虛函數(shù)。

          虛函數(shù)的實(shí)現(xiàn):在有虛函數(shù)的類中,類的最開始部分是一個虛函數(shù)表的指針,這個指針指向一個虛函數(shù)表,表中放了虛函數(shù)的地址,實(shí)際的虛函數(shù)在代碼段(.text)中。當(dāng)子類繼承了父類的時候也會繼承其虛函數(shù)表,當(dāng)子類重寫父類中虛函數(shù)時候,會將其繼承到的虛函數(shù)表中的地址替換為重新寫的函數(shù)地址。使用了虛函數(shù),會增加訪問內(nèi)存開銷,降低效率。

          請你來回答一下 ++i 和 i++ 的區(qū)別

          參考回答:

          ++i 先自增 1,再返回,i++ 先返回 i,再自增 1

          請你來寫個函數(shù)在 main 函數(shù)執(zhí)行前先運(yùn)行

          參考回答:

          __attribute((constructor))void before()
          {
              printf("before main\n");
          }

          以下四行代碼的區(qū)別是什么?

          const char * arr = "123";
          char * brr = "123"
          const char crr[] = "123"
          char drr[] = "123";

          參考回答:

          const char * arr = "123";
          //字符串123保存在常量區(qū),const本來是修飾arr指向的值不能通過arr去修改,但是字符串“123”在常量區(qū),本來就不能改變,所以加不加const效果都一樣
          char * brr = "123";
          //字符串123保存在常量區(qū),這個arr指針指向的是同一個位置,同樣不能通過brr去修改"123"的值
          const char crr[] = "123";
          //這里123本來是在棧上的,但是編譯器可能會做某些優(yōu)化,將其放到常量區(qū)
          char drr[] = "123";
          //字符串123保存在棧區(qū),可以通過drr去修改

          請你來說一下 C++ 里是怎么定義常量的?常量存放在內(nèi)存的哪個位置?

          參考回答:

          常量在 C++ 里的定義就是一個 top-level const 加上對象類型,常量定義必須初始化。對于局部對象,常量存放在棧區(qū),對于全局對象,常量存放在全局/靜態(tài)存儲區(qū)。對于字面值常量,常量存放在常量存儲區(qū)。

          請你來回答一下 const 修飾成員函數(shù)的目的是什么?

          參考回答:

          const 修飾的成員函數(shù)表明函數(shù)調(diào)用不會對對象做出任何更改,事實(shí)上,如果確認(rèn)不會對對象做更改,就應(yīng)該為函數(shù)加上 const 限定,這樣無論 const 對象還是普通對象都可以調(diào)用該函數(shù)。

          如果同時定義了兩個函數(shù),一個帶 const,一個不帶,會有問題嗎?

          參考回答:

          不會,這相當(dāng)于函數(shù)的重載。

          請你來說一說隱式類型轉(zhuǎn)換

          參考回答:

          首先,對于內(nèi)置類型,低精度的變量給高精度變量賦值會發(fā)生隱式類型轉(zhuǎn)換,其次,對于只存在單個參數(shù)的構(gòu)造函數(shù)的對象構(gòu)造來說,函數(shù)調(diào)用可以直接使用該參數(shù)傳入,編譯器會自動調(diào)用其構(gòu)造函數(shù)生成臨時對象。

          請你來說一說 C++ 函數(shù)棧空間的最大值

          參考回答:

          默認(rèn)是 1M,不過可以調(diào)整

          請你回答一下 new/delete 與 malloc/free 的區(qū)別是什么

          參考回答:

          首先,new/delete 是 C++ 的關(guān)鍵字,而 malloc/free 是 C語言的庫函數(shù),后者使用必須指明申請內(nèi)存空間的大小,對于類類型的對象,后者不會調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)

          請你說說你了解的RTTI

          參考回答:

          運(yùn)行時類型檢查,在 C++ 層面主要體現(xiàn)在 dynamic_cast 和typeid,VS 中虛函數(shù)表的 -1 位置存放了指向 type_info 的指針。對于存在虛函數(shù)的類型,typeid 和 dynamic_cast 都會去查詢 type_info

          請你說說虛函數(shù)表具體是怎樣實(shí)現(xiàn)運(yùn)行時多態(tài)的?

          參考回答:

          子類若重寫父類虛函數(shù),虛函數(shù)表中,該函數(shù)的地址會被替換,對于存在虛函數(shù)的類的對象,在 VS 中,對象的對象模型的頭部存放指向虛函數(shù)表的指針,通過該機(jī)制實(shí)現(xiàn)多態(tài)。

          請你說說C語言是怎么進(jìn)行函數(shù)調(diào)用的?

          參考回答:

          每一個函數(shù)調(diào)用都會分配函數(shù)棧,在棧內(nèi)進(jìn)行函數(shù)執(zhí)行過程。調(diào)用前,先把返回地址壓棧,然后把當(dāng)前函數(shù)的 esp 指針壓棧。

          請你說說 C++ 如何處理返回值?

          參考回答:

          生成一個臨時變量,把它的引用作為函數(shù)參數(shù)傳入函數(shù)內(nèi)。

          請你回答一下 C+ +中拷貝賦值函數(shù)的形參能否進(jìn)行值傳遞?

          參考回答:

          不能。如果是這種情況下,調(diào)用拷貝構(gòu)造函數(shù)的時候,首先要將實(shí)參傳遞給形參,這個傳遞的時候又要調(diào)用拷貝構(gòu)造函數(shù)。。如此循環(huán),無法完成拷貝,棧也會滿。

          請你說一說 select

          參考回答:

          select 在使用前,先將需要監(jiān)控的描述符對應(yīng)的 bit 位置 1,然后將其傳給 select,當(dāng)有任何一個事件發(fā)生時,select 將會返回所有的描述符,需要在應(yīng)用程序自己遍歷去檢查哪個描述符上有事件發(fā)生,效率很低,并且其不斷在內(nèi)核態(tài)和用戶態(tài)進(jìn)行描述符的拷貝,開銷很大

          請你說說 fork,wait,exec 函數(shù)

          參考回答:

          父進(jìn)程產(chǎn)生子進(jìn)程使用 fork 拷貝出來一個父進(jìn)程的副本,此時只拷貝了父進(jìn)程的頁表,兩個進(jìn)程都讀同一塊內(nèi)存,當(dāng)有進(jìn)程寫的時候使用寫實(shí)拷貝機(jī)制分配內(nèi)存,exec 函數(shù)可以加載一個 elf 文件去替換父進(jìn)程,從此父進(jìn)程和子進(jìn)程就可以運(yùn)行不同的程序了。fork 從父進(jìn)程返回子進(jìn)程的 pid,從子進(jìn)程返回 0.調(diào)用了 wait 的父進(jìn)程將會發(fā)生阻塞,直到有子進(jìn)程狀態(tài)改變,執(zhí)行成功返回 0,錯誤返回 -1。exec 執(zhí)行成功則子進(jìn)程從新的程序開始運(yùn)行,無返回值,執(zhí)行失敗返回 -1

          請你回答一下靜態(tài)函數(shù)和虛函數(shù)的區(qū)別

          參考回答:

          靜態(tài)函數(shù)在編譯的時候就已經(jīng)確定運(yùn)行時機(jī),虛函數(shù)在運(yùn)行的時候動態(tài)綁定。虛函數(shù)因?yàn)橛昧颂摵瘮?shù)表機(jī)制,調(diào)用的時候會增加一次內(nèi)存開銷

          請你說一說重載和覆蓋

          參考回答:

          • 重載:兩個函數(shù)名相同,但是參數(shù)列表不同(個數(shù),類型),返回值類型沒有要求,在同一作用域中
          • 重寫:子類繼承了父類,父類中的函數(shù)是虛函數(shù),在子類中重新定義了這個虛函數(shù),這種情況是重寫

          請你來說一下map和set有什么區(qū)別,分別又是怎么實(shí)現(xiàn)的?

          參考回答:

          map 和 set 都是 C++ 的關(guān)聯(lián)容器,其底層實(shí)現(xiàn)都是紅黑樹(RB-Tree)。由于 map 和 set 所開放的各種操作接口,RB-tree 也都提供了,所以幾乎所有的 map 和set的操作行為,都只是轉(zhuǎn)調(diào) RB-tree 的操作行為。

          map 和 set 區(qū)別在于:

          1. map 中的元素是 key-value(關(guān)鍵字—值)對:關(guān)鍵字起到索引的作用,值則表示與索引相關(guān)聯(lián)的數(shù)據(jù);Set 與之相對就是關(guān)鍵字的簡單集合,set 中每個元素只包含一個關(guān)鍵字。

          2. set 的迭代器是 const 的,不允許修改元素的值;map 允許修改 value,但不允許修改 key。其原因是因?yàn)?map 和 set 是根據(jù)關(guān)鍵字排序來保證其有序性的,如果允許修改 key 的話,那么首先需要刪除該鍵,然后調(diào)節(jié)平衡,再插入修改后的鍵值,調(diào)節(jié)平衡,如此一來,嚴(yán)重破壞了 map 和 set 的結(jié)構(gòu),導(dǎo)致 iterator 失效,不知道應(yīng)該指向改變前的位置,還是指向改變后的位置。所以 STL 中將 set 的迭代器設(shè)置成 const,不允許修改迭代器的值;而 map 的迭代器則不允許修改 key 值,允許修改 value 值。

          3. map 支持下標(biāo)操作,set 不支持下標(biāo)操作。map 可以用 key 做下標(biāo),map 的下標(biāo)運(yùn)算符[ ]將關(guān)鍵碼作為下標(biāo)去執(zhí)行查找,如果關(guān)鍵碼不存在,則插入一個具有該關(guān)鍵碼和 mapped_type 類型默認(rèn)值的元素至 map 中,因此下標(biāo)運(yùn)算符[ ]在 map 應(yīng)用中需要慎用,const_map 不能用,只希望確定某一個關(guān)鍵值是否存在而不希望插入元素時也不應(yīng)該使用,mapped_type 類型沒有默認(rèn)值也不應(yīng)該使用。如果 find 能解決需要,盡可能用 find。

          請你來介紹一下 STL 的 allocaotr

          參考回答:

          STL 的分配器用于封裝 STL 容器在內(nèi)存管理上的底層細(xì)節(jié)。在 C++ 中,其內(nèi)存配置和釋放如下:

          • new 運(yùn)算分兩個階段:
          1. 調(diào)用 ::operator new 配置內(nèi)存;
          2. 調(diào)用對象構(gòu)造函數(shù)構(gòu)造對象內(nèi)容
          • delete 運(yùn)算分兩個階段:
          1. 調(diào)用對象希構(gòu)函數(shù);
          2. 掉員工::operator delete釋放內(nèi)存

          為了精密分工,STL allocator 將兩個階段操作區(qū)分開來:內(nèi)存配置有alloc::allocate()負(fù)責(zé),內(nèi)存釋放由alloc::deallocate()負(fù)責(zé);對象構(gòu)造由::construct()負(fù)責(zé),對象析構(gòu)由::destroy()負(fù)責(zé)。

          同時為了提升內(nèi)存管理的效率,減少申請小內(nèi)存造成的內(nèi)存碎片問題,SGI STL 采用了兩級配置器,當(dāng)分配的空間大小超過 128B 時,會使用第一級空間配置器;當(dāng)分配的空間大小小于 128B 時,將使用第二級空間配置器。第一級空間配置器直接使用malloc()、realloc()、free()函數(shù)進(jìn)行內(nèi)存空間的分配和釋放,而第二級空間配置器采用了內(nèi)存池技術(shù),通過空閑鏈表來管理內(nèi)存。

          請你來說一說 STL 迭代器刪除元素

          參考回答:

          這個主要考察的是迭代器失效的問題。

          1. 對于序列容器vector,deque來說,使用erase(itertor)后,后邊的每個元素的迭代器都會失效,但是后邊每個元素都會往前移動一個位置,但是erase會返回下一個有效的迭代器;
          2. 對于關(guān)聯(lián)容器map set來說,使用了erase(iterator)后,當(dāng)前元素的迭代器失效,但是其結(jié)構(gòu)是紅黑樹,刪除當(dāng)前元素的,不會影響到下一個元素的迭代器,所以在調(diào)用erase之前,記錄下一個元素的迭代器即可。
          3. 對于list來說,它使用了不連續(xù)分配的內(nèi)存,并且它的erase方法也會返回下一個有效的iterator,因此上面兩種正確的方法都可以使用。

          請你說一說 STL 中 MAP 數(shù)據(jù)存放形式

          參考回答: 紅黑樹。unordered map底層結(jié)構(gòu)是哈希表

          請你講講 STL 有什么基本組成

          參考回答:

          STL 主要由:以下幾部分組成:

          容器迭代器仿函數(shù)算法分配器配接器

          他們之間的關(guān)系:分配器給容器分配存儲空間,算法通過迭代器獲取容器中的內(nèi)容,仿函數(shù)可以協(xié)助算法完成各種操作,配接器用來套接適配仿函數(shù)

          請你說說 STL 中 map 與 unordered_map

          參考回答:

          1. Map映射,map 的所有元素都是 pair,同時擁有實(shí)值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實(shí)值。所有元素都會根據(jù)元素的鍵值自動被排序。不允許鍵值重復(fù)。

          底層實(shí)現(xiàn):紅黑樹

          適用場景:有序鍵值對不重復(fù)映射

          1. Multimap 多重映射。multimap 的所有元素都是 pair,同時擁有實(shí)值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實(shí)值。所有元素都會根據(jù)元素的鍵值自動被排序。允許鍵值重復(fù)。

          底層實(shí)現(xiàn):紅黑樹

          適用場景:有序鍵值對可重復(fù)映射

          請你說一說 vector 和 list 的區(qū)別,應(yīng)用,越詳細(xì)越好

          1. Vector 連續(xù)存儲的容器,動態(tài)數(shù)組,在堆上分配空間

          底層實(shí)現(xiàn):數(shù)組

          兩倍容量增長:

          vector 增加(插入)新元素時,如果未超過當(dāng)時的容量,則還有剩余空間,那么直接添加到最后(插入指定位置),然后調(diào)整迭代器。 如果沒有剩余空間了,則會重新配置原有元素個數(shù)的兩倍空間,然后將原空間元素通過復(fù)制的方式初始化新空間,再向新空間增加元素,最后析構(gòu)并釋放原空間,之前的迭代器會失效。

          性能:

          訪問:O(1)

          插入:在最后插入(空間夠):很快

          • 在最后插入(空間不夠):需要內(nèi)存申請和釋放,以及對之前數(shù)據(jù)進(jìn)行拷貝。
          • 在中間插入(空間夠):內(nèi)存拷貝
          • 在中間插入(空間不夠):需要內(nèi)存申請和釋放,以及對之前數(shù)據(jù)進(jìn)行拷貝。 刪除:
          • 在最后刪除:很快
          • 在中間刪除:內(nèi)存拷貝

          適用場景:經(jīng)常隨機(jī)訪問,且不經(jīng)常對非尾節(jié)點(diǎn)進(jìn)行插入刪除。 2. List 動態(tài)鏈表,在堆上分配空間,每插入一個元數(shù)都會分配空間,每刪除一個元素都會釋放空間。

          底層:雙向鏈表

          性能:

          訪問:隨機(jī)訪問性能很差,只能快速訪問頭尾節(jié)點(diǎn)。

          插入:很快,一般是常數(shù)開銷

          刪除:很快,一般是常數(shù)開銷

          適用場景:經(jīng)常插入刪除大量數(shù)據(jù)

          區(qū)別

          • vector 底層實(shí)現(xiàn)是數(shù)組;list 是雙向 鏈表。
          • vector 支持隨機(jī)訪問,list 不支持。
          • vector 是順序內(nèi)存,list 不是。
          • vector 在中間節(jié)點(diǎn)進(jìn)行插入刪除會導(dǎo)致內(nèi)存拷貝,list 不會。
          • vector 一次性分配好內(nèi)存,不夠時才進(jìn)行 2 倍擴(kuò)容;list 每次插入新節(jié)點(diǎn)都會進(jìn)行內(nèi)存申請。
          • vector 隨機(jī)訪問性能好,插入刪除性能差;list 隨機(jī)訪問性能差,插入刪除性能好。應(yīng)用vector 擁有一段連續(xù)的內(nèi)存空間,因此支持隨機(jī)訪問,如果需要高效的隨即訪問,而不在乎插入和刪除的效率,使用 vector。

          list 擁有一段不連續(xù)的內(nèi)存空間,如果需要高效的插入和刪除,而不關(guān)心隨機(jī)訪問,則應(yīng)使用 list。

          請你來說一下 STL 中迭代器的作用,有指針為何還要迭代器

          參考回答:

          1. 迭代器

          Iterator(迭代器)模式又稱 Cursor(游標(biāo))模式,用于提供一種方法順序訪問一個聚合對象中各個元素, 而又不需暴露該對象的內(nèi)部表示。或者這樣說可能更容易理解:Iterator 模式是運(yùn)用于聚合對象的一種模式,通過運(yùn)用該模式,使得我們可以在不知道對象內(nèi)部表示的情況下,按照一定順序(由 iterator 提供的方法)訪問聚合對象中的各個元素。

          由于 Iterator 模式的以上特性:與聚合對象耦合,在一定程度上限制了它的廣泛運(yùn)用,一般僅用于底層聚合支持類,如 STL 的 list、vector、stack 等容器類及 ostream_iterator 等擴(kuò)展 iterator。

          1. 迭代器和指針的區(qū)別

          迭代器不是指針,是類模板,表現(xiàn)的像指針。他只是模擬了指針的一些功能,通過重載了指針的一些操作符,->、*、++、--等。迭代器封裝了指針,是一個“可遍歷STL( Standard Template Library)容器內(nèi)全部或部分元素”的對象, 本質(zhì)是封裝了原生指針,是指針概念的一種提升(lift),提供了比指針更高級的行為,相當(dāng)于一種智能指針,他可以根據(jù)不同類型的數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)不同的 ++,--等操作。

          迭代器返回的是對象引用而不是對象的值,所以 cout 只能輸出迭代器使用 * 取值后的值而不能直接輸出其自身。

          1. 迭代器產(chǎn)生原因

          Iterator 類的訪問方式就是把不同集合類的訪問邏輯抽象出來,使得不用暴露集合內(nèi)部的結(jié)構(gòu)而達(dá)到循環(huán)遍歷集合的效果。

          請你說一說epoll原理

          參考回答:

          調(diào)用順序:

          int epoll_create(int size);
          int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
          int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

          首先創(chuàng)建一個 epoll 對象,然后使用 epoll_ctl 對這個對象進(jìn)行操作,把需要監(jiān)控的描述添加進(jìn)去,這些描述如將會以 epoll_event 結(jié)構(gòu)體的形式組成一顆紅黑樹,接著阻塞在 epoll_wait,進(jìn)入大循環(huán),當(dāng)某個 fd 上有事件發(fā)生時,內(nèi)核將會把其對應(yīng)的結(jié)構(gòu)體放入到一個鏈表中,返回有事件發(fā)生的鏈表。

          n 個整數(shù)的無序數(shù)組,找到每個元素后面比它大的第一個數(shù),要求時間復(fù)雜度為 O(N)

          參考回答:

          vector<int> findMax(vector<int>num)
          {
          if(num.size()==0)return num;
          vector<int>res(num.size());
          int i=0;
          stack<int>s;
          while(i<num.size())
          {
          if(s.empty()||num[s.top()]>=num[i])
          {
          s.push(i++);
          }
          else
          {
          res[s.top()]=num[i];
          s.pop();
          }
          }
          while(!s.empty())
          {
          res[s.top()]=INT_MAX;
          s.pop();
          }
          for(int i=0; i<res.size(); i++)
          cout<<res[i]<<endl;
          return res;
          }

          請你回答一下 STL 里 resize 和 reserve 的區(qū)別

          參考回答:

          resize():改變當(dāng)前容器內(nèi)含有元素的數(shù)量(size()),eg: vectorv; v.resize(len);v的size變?yōu)閘en,如果原來v的size小于len,那么容器新增(len-size)個元素,元素的值為默認(rèn)為0.當(dāng)v.push_back(3);之后,則是3是放在了v的末尾,即下標(biāo)為len,此時容器是size為len+1;

          reserve():改變當(dāng)前容器的最大容量(capacity),它不會生成元素,只是確定這個容器允許放入多少對象,如果reserve(len)的值大于當(dāng)前的capacity(),那么會重新分配一塊能存len個對象的空間,然后把之前v.size()個對象通過copy construtor復(fù)制過來,銷毀之前的內(nèi)存;

          測試代碼如下:

          #include <iostream>
          #include <vector>
          using namespace std;
          int main() {
              vector<int> a;
              a.reserve(100);
              a.resize(50);
              cout<<a.size()<<"  "<<a.capacity()<<endl;
                  //50  100
              a.resize(150);
              cout<<a.size()<<"  "<<a.capacity()<<endl;
                  //150  200
              a.reserve(50);
              cout<<a.size()<<"  "<<a.capacity()<<endl;
                  //150  200
              a.resize(50);
              cout<<a.size()<<"  "<<a.capacity()<<endl;
                  //50  200    
          }

          請你來說一下 C++ 中類成員的訪問權(quán)限

          參考回答:

          C++ 通過 public、protected、private 三個關(guān)鍵字來控制成員變量和成員函數(shù)的訪問權(quán)限,它們分別表示公有的、受保護(hù)的、私有的,被稱為成員訪問限定符。在類的內(nèi)部(定義類的代碼內(nèi)部),無論成員被聲明為 public、protected 還是 private,都是可以互相訪問的,沒有訪問權(quán)限的限制。在類的外部(定義類的代碼之外),只能通過對象訪問成員,并且通過對象只能訪問 public 屬性的成員,不能訪問 private、protected 屬性的成員

          請你來說一下 C++ 中 struct 和 class 的區(qū)別

          參考回答:

          在 C++ 中,可以用 struct 和 class 定義類,都可以繼承。區(qū)別在于:structural 的默認(rèn)繼承權(quán)限和默認(rèn)訪問權(quán)限是 public,而 class 的默認(rèn)繼承權(quán)限和默認(rèn)訪問權(quán)限是 private。 另外,class 還可以定義模板類形參,比如 template。

          請你回答一下C++類內(nèi)可以定義引用數(shù)據(jù)成員嗎?

          參考回答:

          可以,必須通過成員函數(shù)初始化列表初始化。

          請你回答一下 malloc 的原理,另外 brk 系統(tǒng)調(diào)用和 mmap 系統(tǒng)調(diào)用的作用分別是什么?

          參考回答:

          Malloc 函數(shù)用于動態(tài)分配內(nèi)存。為了減少內(nèi)存碎片和系統(tǒng)調(diào)用的開銷,malloc 其采用內(nèi)存池的方式,先申請大塊內(nèi)存作為堆區(qū),然后將堆區(qū)分為多個內(nèi)存塊,以塊作為內(nèi)存管理的基本單位。當(dāng)用戶申請內(nèi)存時,直接從堆區(qū)分配一塊合適的空閑塊。 Malloc 采用隱式鏈表結(jié)構(gòu)將堆區(qū)分成連續(xù)的、大小不一的塊,包含已分配塊和未分配塊;同時 malloc 采用顯示鏈表結(jié)構(gòu)來管理所有的空閑塊,即使用一個雙向鏈表將空閑塊連接起來,每一個空閑塊記錄了一個連續(xù)的、未分配的地址。

          當(dāng)進(jìn)行內(nèi)存分配時,Malloc 會通過隱式鏈表遍歷所有的空閑塊,選擇滿足要求的塊進(jìn)行分配;當(dāng)進(jìn)行內(nèi)存合并時,malloc 采用邊界標(biāo)記法,根據(jù)每個塊的前后塊是否已經(jīng)分配來決定是否進(jìn)行塊合并。

          Malloc 在申請內(nèi)存時,一般會通過 brk 或者 mmap 系統(tǒng)調(diào)用進(jìn)行申請。其中當(dāng)申請內(nèi)存小于 128K 時,會使用系統(tǒng)函數(shù) brk 在堆區(qū)中分配;而當(dāng)申請內(nèi)存大于 128K 時,會使用系統(tǒng)函數(shù) mmap 在映射區(qū)分配。

          請你說一說C++的內(nèi)存管理是怎樣的?

          參考回答:

          在 C++ 中,虛擬內(nèi)存分為代碼段、數(shù)據(jù)段、BSS段、堆區(qū)、文件映射區(qū)以及棧區(qū)六部分。

          • 代碼段:包括只讀存儲區(qū)和文本區(qū),其中只讀存儲區(qū)存儲字符串常量,文本區(qū)存儲程序的機(jī)器代碼。
          • 數(shù)據(jù)段:存儲程序中已初始化的全局變量和靜態(tài)變量
          • bss 段:存儲未初始化的全局變量和靜態(tài)變量(局部+全局),以及所有被初始化為0的全局變量和靜態(tài)變量。
          • 堆區(qū):調(diào)用new/malloc函數(shù)時在堆區(qū)動態(tài)分配內(nèi)存,同時需要調(diào)用delete/free來手動釋放申請的內(nèi)存。
          • 映射區(qū):存儲動態(tài)鏈接庫以及調(diào)用mmap函數(shù)進(jìn)行的文件映射
          • 棧:使用棧空間存儲函數(shù)的返回地址、參數(shù)、局部變量、返回值

          請你來說一下 C++/C 的內(nèi)存分配

          參考回答:

          32bitCPU 可尋址 4G 線性空間,每個進(jìn)程都有各自獨(dú)立的 4G 邏輯地址,其中 0~3G 是用戶態(tài)空間,3~4G 是內(nèi)核空間,不同進(jìn)程相同的邏輯地址會映射到不同的物理地址中。其邏輯地址其劃分如下:

          各個段說明如下:

          3G 用戶空間和 1G 內(nèi)核空間

          靜態(tài)區(qū)域:

          • text segment(代碼段):包括只讀存儲區(qū)和文本區(qū),其中只讀存儲區(qū)存儲字符串常量,文本區(qū)存儲程序的機(jī)器代碼。

          • data segment(數(shù)據(jù)段):存儲程序中已初始化的全局變量和靜態(tài)變量

          • bss segment:存儲未初始化的全局變量和靜態(tài)變量(局部+全局),以及所有被初始化為0的全局變量和靜態(tài)變量,對于未初始化的全局變量和靜態(tài)變量,程序運(yùn)行main之前時會統(tǒng)一清零。即未初始化的全局變量編譯器會初始化為 0 動態(tài)區(qū)域:

          • heap(堆):當(dāng)進(jìn)程未調(diào)用 malloc 時是沒有堆段的,只有調(diào)用 malloc 時采用分配一個堆,并且在程序運(yùn)行過程中可以動態(tài)增加堆大小(移動 break 指針),從低地址向高地址增長。分配小內(nèi)存時使用該區(qū)域。 堆的起始地址由 mm_struct 結(jié)構(gòu)體中的 start_brk 標(biāo)識,結(jié)束地址由 brk 標(biāo)識。

          • memory mapping segment(映射區(qū)):存儲動態(tài)鏈接庫等文件映射、申請大內(nèi)存( malloc 時調(diào)用 mmap 函數(shù))

          • stack(棧):使用棧空間存儲函數(shù)的返回地址、參數(shù)、局部變量、返回值,從高地址向低地址增長。在創(chuàng)建進(jìn)程時會有一個最大棧大小,Linux 可以通過 ulimi t命令指定。

          請你回答一下如何判斷內(nèi)存泄漏?

          參考回答:

          內(nèi)存泄漏通常是由于調(diào)用了 malloc/new 等內(nèi)存申請的操作,但是缺少了對應(yīng)的 free/delete。為了判斷內(nèi)存是否泄露,我們一方面可以使用 linux 環(huán)境下的內(nèi)存泄漏檢查工具 Valgrind,另一方面我們在寫代碼時可以添加內(nèi)存申請和釋放的統(tǒng)計(jì)功能,統(tǒng)計(jì)當(dāng)前申請和釋放的內(nèi)存是否一致,以此來判斷內(nèi)存是否泄露。

          請你來說一下什么時候會發(fā)生段錯誤

          參考回答:

          段錯誤通常發(fā)生在訪問非法內(nèi)存地址的時候,具體來說分為以下幾種情況:

          • 使用野指針
          • 試圖修改字符串常量的內(nèi)容

          請你來回答一下什么是 memory leak,也就是內(nèi)存泄漏

          參考回答:

          內(nèi)存泄漏(memory leak)是指由于疏忽或錯誤造成了程序未能釋放掉不再使用的內(nèi)存的情況。內(nèi)存泄漏并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)計(jì)錯誤,失去了對該段內(nèi)存的控制,因而造成了內(nèi)存的浪費(fèi)。

          內(nèi)存泄漏的分類:

          • 堆內(nèi)存泄漏 (Heap leak)。對內(nèi)存指的是程序運(yùn)行中根據(jù)需要分配通過 malloc,realloc new 等從堆中分配的一塊內(nèi)存,再是完成后必須通過調(diào)用對應(yīng)的 free或者delete 刪掉。如果程序的設(shè)計(jì)的錯誤導(dǎo)致這部分內(nèi)存沒有被釋放,那么此后這塊內(nèi)存將不會被使用,就會產(chǎn)生Heap Leak.
          • 系統(tǒng)資源泄露(Resource Leak)。主要指程序使用系統(tǒng)分配的資源比如 Bitmap,handle ,SOCKET 等沒有使用相應(yīng)的函數(shù)釋放掉,導(dǎo)致系統(tǒng)資源的浪費(fèi),嚴(yán)重可導(dǎo)致系統(tǒng)效能降低,系統(tǒng)運(yùn)行不穩(wěn)定。
          • 沒有將基類的析構(gòu)函數(shù)定義為虛函數(shù)。當(dāng)基類指針指向子類對象時,如果基類的析構(gòu)函數(shù)不是 virtual,那么子類的析構(gòu)函數(shù)將不會被調(diào)用,子類的資源沒有正確是釋放,因此造成內(nèi)存泄露。

          請你來說一下 reactor 模型組成

          參考回答:

          reactor 模型要求主線程只負(fù)責(zé)監(jiān)聽文件描述上是否有事件發(fā)生,有的話就立即將該事件通知工作線程,除此之外,主線程不做任何其他實(shí)質(zhì)性的工作,讀寫數(shù)據(jù)、接受新的連接以及處理客戶請求均在工作線程中完成。其模型組成如下:

          1. Handle:即操作系統(tǒng)中的句柄,是對資源在操作系統(tǒng)層面上的一種抽象,它可以是打開的文件、一個連接(Socket)、Timer等。由于Reactor模式一般使用在網(wǎng)絡(luò)編程中,因而這里一般指Socket Handle,即一個網(wǎng)絡(luò)連接。
          2. Synchronous Event Demultiplexer(同步事件復(fù)用器):阻塞等待一系列的Handle中的事件到來,如果阻塞等待返回,即表示在返回的 Handle中可以不阻塞的執(zhí)行返回的事件類型。這個模塊一般使用操作系統(tǒng)的 select 來實(shí)現(xiàn)。
          3. Initiation Dispatcher:用于管理 Event Handler,即 EventHandler 的容器,用以注冊、移除 EventHandler 等;另外,它還作為 Reactor 模式的入口調(diào)用 Synchronous Event Demultiplexerselect 方法以阻塞等待事件返回,當(dāng)阻塞等待返回時,根據(jù)事件發(fā)生的Handle將其分發(fā)給對應(yīng)的Event Handler 處理,即回調(diào) EventHandler中的 handle_event() 方法。 4.Event Handler:定義事件處理方法:handle_event(),以供InitiationDispatcher回調(diào)使用。
          4. Concrete Event Handler:事件 EventHandler 接口,實(shí)現(xiàn)特定事件處理邏輯。

          請自己設(shè)計(jì)一下如何采用單線程的方式處理高并發(fā)

          參考回答:

          在單線程模型中,可以采用 I/O 復(fù)用來提高單線程處理多個請求的能力,然后再采用事件驅(qū)動模型,基于異步回調(diào)來處理事件來

          請你說說 select,epoll 的區(qū)別,原理,性能,限制都說一說

          參考回答:

          1. IO 多路復(fù)用

          IO 復(fù)用模型在阻塞 IO 模型上多了一個 select 函數(shù),select 函數(shù)有一個參數(shù)是文件描述符集合,意思就是對這些的文件描述符進(jìn)行循環(huán)監(jiān)聽,當(dāng)某個文件描述符就緒的時候,就對這個文件描述符進(jìn)行處理。

          這種 IO 模型是屬于阻塞的 IO。但是由于它可以對多個文件描述符進(jìn)行阻塞監(jiān)聽,所以它的效率比阻塞 IO 模型高效。

          IO 多路復(fù)用就是我們說的select,poll,epoll。select/epoll的好處就在于單個 process 就可以同時處理多個網(wǎng)絡(luò)連接的 IO。它的基本原理就是 select,poll,epoll 這個 function 會不斷的輪詢所負(fù)責(zé)的所有 socket,當(dāng)某個 socket 有數(shù)據(jù)到達(dá)了,就通知用戶進(jìn)程。

          當(dāng)用戶進(jìn)程調(diào)用了 select,那么整個進(jìn)程會被 block,而同時,kernel 會“監(jiān)視”所有 select 負(fù)責(zé)的 socket,當(dāng)任何一個 socket 中的數(shù)據(jù)準(zhǔn)備好了,select 就會返回。這個時候用戶進(jìn)程再調(diào)用 read 操作,將數(shù)據(jù)從 kernel 拷貝到用戶進(jìn)程。

          所以,I/O 多路復(fù)用的特點(diǎn)是通過一種機(jī)制一個進(jìn)程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進(jìn)入讀就緒狀態(tài),select() 函數(shù)就可以返回。

          I/O 多路復(fù)用和阻塞I/O其實(shí)并沒有太大的不同,事實(shí)上,還更差一些。因?yàn)檫@里需要使用兩個system call (select 和 recvfrom),而blocking IO只調(diào)用了一個system call (recvfrom)。但是,用select的優(yōu)勢在于它可以同時處理多個connection

          所以,如果處理的連接數(shù)不是很高的話,使用select/epollweb server不一定比使用multi-threading + blocking IOweb server性能更好,可能延遲還更大。select/epoll的優(yōu)勢并不是對于單個連接能處理得更快,而是在于能處理更多的連接。)

          IO multiplexing Model中,實(shí)際中,對于每一個socket,一般都設(shè)置成為non-blocking,但是,如上圖所示,整個用戶的process其實(shí)是一直被block的。只不過process是被select這個函數(shù)block,而不是被socket IOblock

          2、select

          select:是最初解決 IO 阻塞問題的方法。用結(jié)構(gòu)體 fd_set 來告訴內(nèi)核監(jiān)聽多個文件描述符,該結(jié)構(gòu)體被稱為描述符集。由數(shù)組來維持哪些描述符被置位了。對結(jié)構(gòu)體的操作封裝在三個宏定義中。通過輪尋來查找是否有描述符要被處理。 存在的問題:

          • 內(nèi)置數(shù)組的形式使得 select 的最大文件數(shù)受限與 FD_SIZE;
          • 每次調(diào)用 select 前都要重新初始化描述符集,將 fd 從用戶態(tài)拷貝到內(nèi)核態(tài),每次調(diào)用 select 后,都需要將 fd 從內(nèi)核態(tài)拷貝到用戶態(tài);
          • 輪尋排查當(dāng)文件描述符個數(shù)很多時,效率很低;

          3、poll

          poll:通過一個可變長度的數(shù)組解決了select 文件描述符受限的問題。數(shù)組中元素是結(jié)構(gòu)體,該結(jié)構(gòu)體保存描述符的信息,每增加一個文件描述符就向數(shù)組中加入一個結(jié)構(gòu)體,結(jié)構(gòu)體只需要拷貝一次到內(nèi)核態(tài)。poll 解決了 select 重復(fù)初始化的問題。輪尋排查的問題未解決。

          4、epoll

          epoll:輪尋排查所有文件描述符的效率不高,使服務(wù)器并發(fā)能力受限。因此,epoll 采用只返回狀態(tài)發(fā)生變化的文件描述符,便解決了輪尋的瓶頸。

          epoll 對文件描述符的操作有兩種模式:LT(level trigger)ET(edge trigger)。LT 模式是默認(rèn)模式

          LT模式

          LT(level triggered)是缺省的工作方式,并且同時支持 block 和 no-block socket.在這種做法中,內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的 fd 進(jìn)行 IO 操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你的。

          ET模式

          ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時,內(nèi)核通過 epoll 告訴你。然后它會假設(shè)你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個文件描述符不再為就緒狀態(tài)了(比如,你在發(fā)送,接收或者接收請求,或者發(fā)送接收的數(shù)據(jù)少于一定量時導(dǎo)致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導(dǎo)致它再次變成未就緒),內(nèi)核不會發(fā)送更多的通知(only once)

          ET 模式在很大程度上減少了 epoll 事件被重復(fù)觸發(fā)的次數(shù),因此效率要比LT模式高。epoll 工作在 ET 模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務(wù)餓死。

          3、LT 模式與 ET 模式的區(qū)別如下:

          LT 模式:當(dāng)epoll_wait檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序可以不立即處理該事件。下次調(diào)用epoll_wait時,會再次響應(yīng)應(yīng)用程序并通知此事件。

          ET 模式:當(dāng)epoll_wait檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件。如果不處理,下次調(diào)用epoll_wait時,不會再次響應(yīng)應(yīng)用程序并通知此事件。

          請問 C++11 有哪些新特性?

          參考回答:

          C++11 最常用的新特性如下:

          • auto 關(guān)鍵字:編譯器可以根據(jù)初始值自動推導(dǎo)出類型。但是不能用于函數(shù)傳參以及數(shù)組類型的推導(dǎo)
          • nullptr 關(guān)鍵字:nullptr 是一種特殊類型的字面值,它可以被轉(zhuǎn)換成任意其它的指針類型;而 NULL 一般被宏定義為 0,在遇到重載時可能會出現(xiàn)問題。
          • 智能指針:C++11 新增了std::shared_ptrstd::weak_ptr等類型的智能指針,用于解決內(nèi)存管理的問題。
          • 初始化列表:使用初始化列表來對類進(jìn)行初始化
          • 右值引用:基于右值引用可以實(shí)現(xiàn)移動語義和完美轉(zhuǎn)發(fā),消除兩個對象交互時不必要的對象拷貝,節(jié)省運(yùn)算存儲資源,提高效率
          • atomic 原子操作用于多線程資源互斥操作
          • 新增 STL 容器 arra y以及 tuple

          文章鏈接:https://mp.weixin.qq.com/s/YRo5Lm9pbbZnjY1DQfW6yw

          編輯:公眾號【編程珠璣】

          瀏覽 72
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九九九亚洲 | 欧美一二| 国产亚洲色婷婷 | 一区二区三区无码区 | 俺去也在线播放 |