面試學習總結

1.使用struct和class定義類以及類外的繼承有什麼區別?html

  1)使用struct定義結構體它的默認數據成員類型是public,而class關鍵字定義類時默認成員時private.react

  2) 在使用繼承的時候,struct定義類時它的派生類的默認具備public繼承,而class的派生類默認是private繼承。linux

 

2.派生類和虛函數概述?c++

1)基類中定義虛函數的目的是想在派生類中從新定義函數的新功能,若是派生類中沒有從新對虛函數定義,那麼使用派生類對象調用這個虛函數的時候,默認使用基類的虛函數。程序員

2)派生類中定義的函數必須和基類中的函數定義徹底相同。算法

3)基類中定義了虛函數,那麼在派生類中一樣也是。shell

 

3.虛函數和純虛函數的區別?數據庫

  1)虛函數和純虛函數都使用關鍵字virtual。若是在基類中定義一個純虛函數,那麼這個基類是一個抽象基類,也稱爲虛基類,不能定義對象,只能被繼承。當被繼承時,必須在派生類中從新定義純虛函數。編程

class Animal
{
     public:
         virtual void getColol() = 0; // 純虛函數
};

class Animal
{
    public:
         virtual void getColor(); // 虛函數
};

4.深拷貝和淺拷貝的區別網頁爬蟲

  舉個栗子~:

淺拷貝:

char p[] = "hello";
char *p1;
p1 = p;

深拷貝:

char p[] = "hello";
char *p1 = new char[];
p1 = p;

解釋:

   淺拷貝就是拷貝以後兩個指針指向同一個地址,只是對指針的拷貝。

   深拷貝是通過拷貝後,兩個指針指向兩個地址,且拷貝其內容。

2)淺拷貝可能出現的問題

   a. 由於兩個指針指向同一個地址,因此在一個指針改變其內容後,另外一個也可能會發生改變。

   b.淺拷貝只是拷貝了指針而已,使得兩個指針指向同一個地址,因此當對象結束其生命期調用析構函數時,可能會對同一個資源釋放兩次,形成程序奔潰。

 

4.STL中vector實現原理,是1.5倍仍是兩倍?各有什麼優缺點?

    vector是順序容器,他是一個動態數組。

    1.5倍優點:能夠重用以前分配但已經釋放的內存。

    2倍優點: 每次申請的內存都不能夠重用

5.STL中map,set的實現原理。

   map是一個關聯容器,它是以鍵值對的形式存儲的。它的底層是用紅黑樹實現。平均查找複雜度是O(logn)

   set也是一個關聯容器,它也是以鍵值對的形式存儲,但鍵值對都相同。底層也是以紅黑樹實現。平均查找複雜度O(logn)。

6. C++特色是什麼,多態實現機制?多態做用?兩個必須條件?

    C++中多態機制主要體如今兩個方面,一個是函數的重載,一個是接口的重寫。接口多態指的是「一個接口的多種形態」。每個對象內部都有一個虛表指針,該虛表指針被初始化爲本類的虛表。因此在程序中,無論對象類型如何轉換,但該對象內部的虛表指針始終不變,才能實現動態的對象函數調用,這是c++多態的實現原理。

   多態的基礎是繼承,須要虛函數的支持。子類繼承父類的大部分資源,不能繼承的有構造函數,析構函數,拷貝構造函數,operator = ,友元函數等。

做用:

     1.隱藏實現細節,代碼可以模塊化。 2.實現接口重用:爲了類在繼承和派生過程當中正確調用。

兩個必要條件:

     1.虛函數 2.一個基類的指針或者引用指向子類對象。

7.多重繼承有什麼問題?怎樣消除多重繼承中的二義性?

   1)增長程序的複雜性,使程序的編寫和維護都比較困難,容易出錯;

   2)繼承類和基類的同名函數產生了二義性,同名函數不知道調用基類仍是繼承類,c++ 中用虛函數解決這個問題。

   3)繼承過程當中可能會繼承一些沒必要要的數據,可能會產生數據很長。

可使用成員限定符和虛函數解決多重繼承中的函數的二義性問題。

8.什麼叫動態關聯,什麼叫靜態關聯?

   多態中,程序在編譯時肯定程序的執行動做叫作靜態關聯,在運行時才能肯定叫作動態關聯。

9.那些庫函數屬於高危函數?爲何?

   strcpy賦值到目標區間可能會形成緩衝區溢出!

10.求兩個數的乘積和商數,用宏定義來實現

#define Chen(a,b) ((a) * (b))
#define Divide(a,b) ((a) / (b))

11.枚舉和#define宏的區別

1)用#define定義的宏在預編譯的時候進行簡單替換。枚舉在編譯的時候肯定其值。

2)能夠調試枚舉常量,但不能夠調試宏常量。

3)枚舉能夠一次定義大量的常量,而宏只能定義一個。

12.介紹下函數的重載:

   重載是對不一樣類型的函數進行調用而使用相同的函數名。重載至少要在參數類型,參數個數,參數順序中有一個不一樣。

13.派生新類要通過三個步驟

   1)繼承基類成員 2)改造基類成員 3)添加新成員

14.面向對象的三個基本性質?並簡單描述

  1)封裝  : 將客觀事物抽象成類,每一個類對自身的數據和方法進行封裝。

   2)繼承

   3)多態  容許一個基類的指針或引用指向一個派生類的對象。

15.多態性體現都有那些?動態綁定怎麼實現?

  多態性是一個接口,多種實現,是面向對象的核心。

   動態綁定是指在編譯器在編譯階段不知道調用那個方法。

  編譯時多態性:經過重載函數實現。

  運行時多態性:經過虛函數實現,結合動態綁定。

16.動態聯編和靜態聯編

   靜態聯編是指程序在編譯連接時就知道程序的執行順序

   動態聯編是指在程序運行時才知道程序調用函數的順序。

17.爲何析構函數要定義成虛函數?

    若是不將析構函數定義成虛函數,那麼在釋放內存的時候,編譯器會使用靜態聯編,認爲p就是一個基類指針,調用基類析構函數,這樣子類對象的內存沒有釋放,形成內存泄露。定義成虛函數後,使用動態聯編,先調用子類析構函數,再調用基類析構函數。

18.那些函數不能被定義成虛函數?

1)構造函數不能被定義成虛函數,由於構造函數須要知道全部信息才能構造對象,而虛函數只容許知道部分信息。

2)內聯函數在編譯時被展開,而虛函數在運行時才能動態綁定。

3)友元函數 由於不能夠被繼承。

4)靜態成員函數 只有一個實體,不能被繼承,父子都有。

19.類型轉換有那些?各適用什麼環境?dynamic_cast轉換失敗時,會出現什麼狀況?(對指針,返回NULL,對引用拋出bad_cast異常)

   1)  static_cast靜態類型轉換,適用於基本類型之間和具備繼承關係的類型。

    i.e. 將double類型轉換爲int 類型。將子類對象轉換爲基類對象。

   2)常量類型轉換 const_cast, 適用於去除指針變量的常量屬性。沒法將非指針的常量轉換爲普一般量。

   3)動態類型轉換 dynamic_cast。運行時進行轉換分析的,並不是是在編譯時。dynamic_cast轉換符只能用於含有虛函數的類。

   dynamic_cast用於類層次間的向上轉換和向下轉換,還能夠用於類間的交叉轉換。在類層次間進行向上轉換,即子類轉換爲父類,此時完成的功能和static_cast相同,由於編譯器默認向上轉換老是安全的。向下轉換時,由於dynamic_cast具備類型檢查的功能,因此更加安全。類間的交叉轉換指的是子類的多個父類之間指針或引用的轉換。

20.爲何要用static_cast而不用c語言中的類型轉換?

    static_cast轉換,它會檢查看類型可否轉換,有類型安全檢查。

21.如何判斷一段程序是由c編譯的仍是由c++編譯的?

#ifdef _cplusplus
   cout << "c++" << endl;
#else
   cout << "c" << endl;
#endif

22.操做符重載,具體如何定義?

 除了類屬關係運算符 "." ,成員指針運算符 "*",做用域運算符 「'::」,sizeof運算符和三目運算符"?:"之外,C++中全部運算符均可以重載。

  <返回類型說明符>operator<運算符符號>(<參數表>){}

   重載爲類的成員函數或類的非成員函數時,參數個數會不一樣,應爲this指針。

23.內聯函數和宏定義的區別

    內聯函數是用來消除函數調用時的時間開銷的。頻繁被調用的短小函數很是受益。

  A.宏定義不檢查函數參數,返回值之類的,只是展開,相對來講,內聯函數會檢查參數類型,因此更安全。

 B.宏是由預處理器替換實現的,內聯函數是經過編譯器控制來實現的。

24.動態分配內存和靜態分配內存的區別?

   動態分配內存是使用運算符new來分配的,在堆上分配內存。

   靜態分配就是像A a;這樣的由編輯器來建立一個對象,在棧上分配內存。

25.explicit 是幹什麼用的?

  構造器,阻止不該該容許的由轉換構造函數進行的隱式類型轉換。explicit是用來阻止外部非正規的拷貝構造。

26.既然new/delete的功能徹底覆蓋了malloc()和free()那麼爲何還要保留malloc/free呢?

     由於C++程序要常常調用C程序,而C程序只能使用malloc/free來進行動態內存管理。

27.頭文件中#idfef/define/endif幹什麼用?

   預處理,防止頭文件被重複使用,包括program once都這樣。

28.預處理器標識#error的做用是什麼?

   拋出錯誤提示,標識預處理宏是否被定義。

29.怎樣用C編寫死循環?

while(1)
{
}

for(;;)
{
}

30.用變量a給出以下定義:

  一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數並返回一個整型數 int (*a[10])(int);

31.volatile是幹啥用的?(必須將cpu的寄存器緩存機制回答的很透徹)使用實例有哪些?(重點)

    1)訪問寄存器比訪問內存要快,編譯器會優化減小內存的讀取,可能會讀髒數據。聲明變量爲volatile,編譯器不在對訪問該變量的代碼進行優化,仍然從內存讀取,使訪問穩定。

總結:volatile聲明的變量會影響編譯器編譯的結果,用volatile聲明的變量表示這個變量隨時均可能發生改變,與該變量有關的運算,不在編譯優化,以避免出錯。

  2)使用實例:(區分c程序員和嵌入式系統程序員的基本問題)

   並行設備的硬件寄存器 (如:狀態寄存器)

   一箇中斷服務子程序會訪問到的非自動變量

   多線程應用中被幾個任務共享的變量

 3)一個參數既能夠是volatile又能夠是const嗎?

  能夠。好比狀態寄存器,聲明爲volatile表示它的值可能隨時改變,聲明爲const 表示它的值不該該被程序試圖修改。

  4) 一個指針能夠被聲明爲volatile嗎?解釋爲何?

    能夠。儘管這並非很常見。一個例子是當中斷服務子程序修改一個指向一個buffer的指針時。

   下面的函數有什麼錯誤:

   

int square(volatile int *ptr) {
return *ptr * *ptr;
}

下面是答案:
這段代碼有點變態。這段代碼的目的是用來返指針*ptr指向值的平方,可是,因爲*ptr指向一個volatile型參數,編譯器將產生相似下面的代碼:

int square(volatile int *ptr){
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

因爲*ptr的值可能被意想不到地該變,所以a和b多是不一樣的。結果,這段代碼可能返不是你所指望的平方值!正確的代碼以下

int square(volatile int *ptr){
int a;
a = *ptr;
return a * a;
}

 

32.引用和指針的區別

  1)引用是直接訪問,指針是間接訪問

  2)引用是變量的別名,自己不單獨分陪本身的內存空間。而指針有本身的內存空間。

  3)引用綁定內存空間必須賦初值。是一個變量別名,不能更改綁定,能夠改變對象的值

 

33.關於動態內存分配和靜態內存分配的區別:

  1)靜態內存分配是在編譯時完成的,不佔cpu資源;動態內存分配是在運行時完成的,分配和釋放佔用cpu資源。

   2)靜態內存分配是在棧上分配的,動態內存分配是在堆上分配的。

  3)動態內存分配須要指針或引用數據類型的支持,而靜態內存分配不須要。

  4)靜態內存分配是按計劃分配,在編譯前肯定內存塊的大小,而動態內存分配是按需分配

  5)靜態內存分配是把控制權交給編譯器,而動態內存分配是把控制權交給程序員

  6)靜態內存分配運行效率要比動態的高,由於動態內存分配在分配和釋放時都須要cpu資源

 

34.分別設置和清除一個整數的第三位?

#define BIT3 (0x1 << 3)

static int a;
 
void set_3()
{
    a |= BIT3;
}

void clear_3()
{
   a &= ~BIT3;
}

 

35.memcpy函數的實現

void* my_memcpy(void *dest , const void * src, size_t size)
{
      if(dest == NULL && src == NULL)
     {
            return NULL;
     }
     char* dest1 = dest;
     char* src1 = src;
     while(size-- >0)
     {
            *dest1 = *src1;
            dest1++;
            src1++;
     }
     return dest;
}

36.strcpy函數的實現

char* my_strcpy(char* dest, const char* src)
{
     assert(dest != NULL && src != NULL);
     char* dest1 = dest;
     while((*dest++ = *src++) != '\0');
     return dest1;
}

37.strcat函數的實現

char* strcat(char* dest, const char* src)
{
      assert(dest != NULL && src != NULL);
      char* dest1 = dest;
      while(*dest != '\0' )
           dest++;
       while((*dest++ = *src++) != '\0');
      return dest1;
}

38.strncat函數的實現

char* my_strncat(char* dest, const char * src, int n)
{
      assert(dest != NULL && src != NULL);
      char* dest1  = dest;
      while(*dest != '\0')
           dest++;
      while(n--)
      {
            *dest = *src;
            dest++;
            src++;
      }
      dest++;
      *dest = '\0';
      return dest1;
}

39.strcmp函數的實現

int my_strcmp(const char* str1,const char* str2)
{
      int ret = 0;
      while(!(ret =*(unsigned char*) str1 -*(unsigned char*) str2) && *str1)
      {
              str1++;
              str2++;
      }
      if(ret < 0)
           return -1;
      else if(ret >0)
           return 1;
      else
           return ret;
}

40.strncmp函數的實現

int my_strncmp(const char* str1, const char* str2,int n)
{
       assert(str1!= NULL && str2 != NULL);
       while(*str1 && *str2 && (*str1 == *str2) && n--)
       {
              str1++;
              str2++;
       }
       int ret = *str1 - *str2;
       if(ret >0)
            return 1;
       else if(ret < 0)
            return -1;
       else return ret;
}

41.strlen函數的實現

int my_strlen(const char *str)
{
       assert(str != NULL);
       int len = 0;
       while(*str++ != '\0')
       {
            len++;
       }
       return len;
}

42.strstr函數的實現

char* my_strstr(const char* str1, const char* str2)
{
       int len2;
       if(!(len2 = strlen(str2)))
             return (char *)str1;
       for(;*str1; str1++)
       {
              if(*str1 == *str2 && strncmp(str1,str2,len))
                    return (char *) str1;
       }
       return NULL;
}

43.String類的實現

class String
{
      public:
          String(const char *str = NULL);
          String(const String &other);
          ~String();
          String & operator = (const String &other);
          String & operator + (const String &other);
          bool operator == (const String &other);
          int getlength();
     private:
           char *m_data; 
};

String ::String(const char* str)
{
        if(str == NULL)
        {
                m_data = new char[1];
                m_data = '\0';
        }
        else
        {
               m_data = new char[strlen(str)+1];
               strcpy(m_data,str);
        }
}

String :: String(const String &other)
{
     if(!other.m_data)
    {
         m_data = 0;
    }
    else
    {
          m_data = new char[strlen(other.m_data)+1];
          strcpy(m_data,other.m_data);
    }  
}

String :: ~String()
{
      if(m_data)
      {
            delete[] m_data;
            m_data = 0;
      }
}

String & String :: operator = (const String & other)
{
       if(this != &other)
       {
             delete[] m_data;
             if(!other.m_data)
             {
                   m_data = 0;
             }
             else
             {
                   m_data = new char[strlen(other.m_data)+ 1];
                   strcpy(m_data, other.m_data);
             }
       }
       return *this;
}

String & String :: operator + (const String & other)
{
           String newString;
           if(!other)
           {
                 newString = *this;
           }
           else if(!m_data)
           {
                 newString = other;
           }
           else
           {
                   m_data = new char[strlen(m_data) + strlen(other.m_data) + 1];
                   strcpy(newString.m_data,m_data);
                   strcat(newString.m_data,other.m_data);
           }
           return newString;
}

bool String::operator == (const String &other)
{
          if(strlen(m_data) != strlen(other.m_data))
          {
                  return false;
          }
          else
          {
                  return strcmp(m_data,other.m_data) ? false : true;
          }
}

int String :: getlengrh()
{
          return strlen(m_data);
}

 

44.sizeof()的大小

  http://www.myexception.cn/program/1666528.html

 

45.do{}while(0);的用法有那些?

  1)能夠將語句做爲一個獨立的域  2)對於多語句能夠正常訪問  3)能夠有效的消除goto語句,達到跳轉語句的效果。

46.請用c/c++實現字符串反轉(不調用庫函數)「abc」類型的

char* reverse_str(char * str)
{
      if(str == NULL)
      {
             return NULL;
      }
      char* begin;
      char* end;
      begin = end = str;
      while(*end != '\0')
      {
            end++;
      }
      --end;
      while(begin < end)
      {
              char tmp = *begin;
              *begin = *end;
              *end = tmp;
              begin++;
              end--;
      }
      return str;
}

 二.服務器編程

   1.多線程和多進程的區別(重點必須從cpu調度,上下文切換,數據共享,多核cpu利用率,資源佔用等方面來回答。有一個問題必會問到,哪些東西是線程私有的,答案中必須包含寄存器!!)

  1)進程數據是分開的:共享複雜,須要用IPC,同步簡單; 多線程共享進程數據:共享簡單,同步複雜。

  2)進程建立銷燬,切換複雜。速度慢;線程建立銷燬,切換簡單,速度快。

  3)進程佔用內存多,cpu利用率低;線程佔用內存少,cpu利用率高;

  4)進程編程簡單,調試簡單;線程編程複雜,調試複雜;

  5)進程間不會相互影響,但當一個線程掛掉將致使整個進程死掉

  6)進程適用於多核,多機分佈;線程適用於多核

線程所私有的: 線程id,寄存器的值,線程的優先級和調度策略,棧,線程的私有數據,error變量,信號屏蔽字。

 

   2.多線程鎖的種類有那些?

    1)互斥鎖(mutex) 2)遞歸鎖 3)自旋鎖 4)讀寫鎖

   自旋鎖(spinlock):是指當一個線程在獲取鎖的時候,若是鎖已經被其它線程獲取,那麼該線程將循環等待,而後不斷的判斷鎖是否可以被成功獲取,直到獲取到鎖纔會退出循環。

   3.自旋鎖和互斥鎖的區別?

       自旋鎖:當鎖被其餘線程佔用時,其它線程並非睡眠狀態,而是不停地消耗cpu,獲取鎖;互斥鎖則否則,保持睡眠,直到互斥鎖被釋放喚醒。

        自旋鎖遞歸調用容易形成死鎖,對長時間才能得到鎖的狀況,容易形成cpu利用率低,只有內核可搶佔式或SMP狀況下才真正須要自旋鎖。

 

   4.進程間通訊:

     1)管道  2)命名管道 3)消息隊列  4)共享內存  5)信號量 6)套接字

 

    5.多線程程序架構,線程數量應該如何設置?

        應儘可能和cpu核數相等,或者爲cpu核數+1的個數。

    

    6.網絡編程設計模式,reactor/proactor/半同步半異步模式?

       reactor模式:同步阻塞I/O模式,註冊對應讀寫事件處理器,等待事件發生,進而調用事件處理器處理事件。

       proactor模式:異步I/O模式。

       reactor模式和proactor模式的主要區別是讀取和寫入是由誰來完成的。reactor模式須要應用程序本身讀取或者寫入數據,proactor模式中,應用程序不須要實際讀寫的過程。

        半同步半異步模式:

            上層的任務(如數據庫查詢,文件傳輸)經過同步I/O模型,簡化了編寫並行程序的難度。

            底層的任務(如網絡控制器的中斷處理)經過異步I/O模型,提供了更好的效率。

    

    7.有一個計數器,多個線程都須要更新,會遇到什麼問題,緣由是什麼應該如何作,怎樣優化?

         有可能一個線程更新的數據已經被另外一個線程更新了,讀髒數據/丟失修改,更新的數據出現異常。能夠經過加鎖來解決,保證數據更新只會被一個線程更新。

    

    8.若是select返回可讀,結果只讀到0字節,什麼狀況?

         某個套接字集合中沒有準備好,可能會select內存用FD_CLR清爲0。

 

    9.CONNECT可能會長時間阻塞,怎麼解決?

         1)使用定時器,最經常使用也是最有效的一種方法。

         2)使用非阻塞;設置非阻塞,返回以後使用select檢測狀態。

 

    10.keepalive是什麼東西?如何使用?

       keepalive,是在tcp中能夠檢測死鏈接的機制。

      1)若是主機可達,對方就會響應ACK應答,就認爲鏈接是存活的。

      2)若是可達,但應用程序退出,對方就發RST應答,發送TCP撤銷鏈接。

      3)若是可達,但應用程序崩潰,對方就發FIN應答。

      4)若是對方主機不響應ACK,RST,那麼繼續發送直到超時,就撤銷鏈接。默認2小時。

 

  11.udp調用connect有什麼做用?

    1)由於udp能夠是一對一,一對多,多對一,多對多的通訊,因此每次調用sendto/recvfrom都須要指定目標ip地址和端口號。經過調用connect函數創建一個端到端鏈接,就能夠和tcp同樣使用send()/recv()傳遞數據,而不須要每次都指定目標IP地址和端口號。可是它和TCP不一樣的是他不須要三次握手的過程。

    2)能夠經過在已經創建UDP鏈接的基礎上,調用connect()來指定新的目標IP地址和端口號以及斷開鏈接。

  

  12.socket編程,若是client斷電了,服務器如何快速知道?

    1)使用定時器(適合有數據流動的狀況)

    2)使用socket選項SO_KEEPALIVE(適合沒有數據流動的狀況)

       a. 本身編寫心跳包程序,簡單說就是給本身的程序加入一個線程,定時向對端發送數據包,查看是否有ACK,根據ACK的返回狀況來管理鏈接。這個方法比較通用,但改變了現有的協議;

       b.使用TCP的KEEPALIVE機制,UNIX網絡編程不推薦使用SO_KEEPALIVE來作心跳測試。

       keepalive原理:TCP內嵌心跳包,以服務端爲例,當server檢測到超過必定時間(2小時)沒有數據傳輸,就會向client傳送一個keepalive pocket

 

三.LINUX操做系統

    1.熟練netstat,tcpdump,ipcs,ipcrm

        netstat:檢查網絡狀態  tcpdump : 截取數據包  ipcs:檢查共享內存  ipcrm:解除共享內存

 

     2.共享內存段被映射進進程空間以後,存在於進程空間的什麼位置?共享內存段最大限制是多少?

        將一塊內存空間映射到兩個或者多個進程地址空間。經過指針訪問該共享內存區。通常經過mmap將文件映射到進程地址共享區。

        存在於進程數據段,最大限制是0x2000000Byte。

    

    3.寫一個C程序判斷是大端仍是小端字節序

        

union
{
    short value;
    char a[sizeof(short)];
}test;

int main()
{
    test.value = 0x0102;
    if(test.a[0] == 1 && test.a[1] == 2) 
cout << "big" << endl; else cout << "little" << endl; return 0; }

      4.i++操做是不是原子操做?並解釋爲何?

         確定不是,i++主要看三個步驟:

    首先把數據從內存放到寄存器,在寄存器上進行自增,而後返回到內存中,這三個操做中均可以被斷開。

     

       5.標準庫函數和系統調用?

         系統調用:是操做系統爲用戶態運行的進程和硬件設備(如cpu,磁盤,打印機等)進行交互提供的一組接口,即就是設置在應用程序和硬件設備之間的一個接口層。Linux內核是單內核,結構緊湊,執行速度快,各個模塊之間是直接調用的關係。Linux系統上到下依次是用戶進程->linux內核->硬件。其中系統調用接口是位於Linux內核中的,整個linux系統從上到下能夠是:用戶進程->系統調用接口->linux內核子系統->硬件,也就是說Linux內核包括了系統調用接口和內核子系統兩部分;或者從下到上能夠是:物理硬件->OS內核->OS服務->應用程序,操做系統起到「承上啓下」做用,向下管理物理硬件,向上爲操做系服務和應用程序提供接口,這裏的接口就是系統調用了。

          庫函數:把函數放到庫裏。把一些常用的函數編完放到一個一個lib文件裏,供別人用。別人用的時候把它所在的文件名用#include<>加到裏面就能夠了。一類是c語言標準規定的庫函數,一類是編譯器特定的庫函數。

          系統調用是爲了方便使用操做系統的接口。庫函數是爲了人們方便編程而使用。

 

    6.fork()一個子進程後,父進程的全局變量可否被子進程使用?

       fork()一個子進程後,子進程將會擁有父進程的幾乎一切資源,可是他們各自擁有各自的全局變量,不能通用,不像線程。對於線程,各個線程共享全局變量。

 

   7.線程同步幾種方式?

        互斥鎖,信號量,臨界區。

 

  8.爲何要字節對其?

    1)有些特殊的cpu只能處理4倍開始的內存地址。

    2)若是不是整倍數讀取會致使讀取屢次。

    3)數據總線爲讀取數據作了基礎。

 

  9.對一個數組而言,delete a和delete[] a有什麼區別?

    對於基礎數據類型沒有什麼區別,可是對於對象而言,delete a只調用一次析構函數,delete[] a纔會析構全部。

  10.什麼是協程?

    用戶態的輕量級線程,有本身的寄存器和棧。

  11.線程安全的幾種方式?

       1)原子操做 2)同步與鎖  3)可重入 4)阻止過分優化的volatile。

  

   12.int (*f(int,void(*)()))(int,int)是什麼意思?

    一個名爲f的參數爲int 和指向void的指針函數的指針函數,返回值爲int,參數是int和int

    (一個函數,參數爲int和指向返回值爲void的無參數的函數指針,返回值爲一個指向返回值爲int,參數爲int和int的函數指針)

   

    13.什麼是冪等性?舉個栗子?

      其任意屢次執行所產生的影響與一次執行的影響相同。

 

   14.當接收方的接收窗口爲0時還能接收數據嗎?爲何?還能接收什麼數據?那怎麼處理這些數據呢?

      能夠接收。

      數據:零窗口探測報文,確認報文段;攜帶緊急數據的報文段。

       可能會被拋棄。

   

     15.當接收方返回的接收窗口爲0時?發送方怎麼作?

        開啓計時器,發送零窗口探測報文。

 

    16.下面兩個函數早執行時有什麼區別?

         int f(string &str); f("abc"); //報錯

         int f(const string &str); f("abc"); // 正常

 

   17.char *const* (*next)()是什麼?

       next是一個指針,指向一個函數,這個函數返回一個指針,這個指針指向char類型的常量指針

 

   18.如何判斷一個數是2的次方?

         思想: 直接用這個數和這個數減一的數進行按位與操做就能夠。若是等於0則TRUE,不然false。

    

   19. 布隆過濾器 (Bloom Filter)

        Q:一個網站有 100 億 url 存在一個黑名單中,每條 url 平均 64 字節。這個黑名單要怎麼存?若此時隨便輸入一個 url,你如何快速判斷該 url 是否在這個黑名單中

        布隆過濾器:

它其實是一個很長的二進制矢量和一系列隨機映射函數

能夠用來判斷一個元素是否在一個集合中。它的優點是隻須要佔用很小的內存空間以及有着高效的查詢效率。

對於布隆過濾器而言,它的本質是一個位數組:位數組就是數組的每一個元素都只佔用 1 bit ,而且每一個元素只能是 0 或者 1。

一開始,布隆過濾器的位數組全部位都初始化爲 0。好比,數組長度爲 m ,那麼將長度爲 m 個位數組的全部的位都初始化爲 0。

0 0 0 0 0 0 0 0 0 0
0 0 1 m-2 m-1

在數組中的每一位都是二進制位。

布隆過濾器除了一個位數組,還有 K 個哈希函數。當一個元素加入布隆過濾器中的時候,會進行以下操做:

•使用 K 個哈希函數對元素值進行 K 次計算,獲得 K 個哈希值。•根據獲得的哈希值,在位數組中把對應下標的值置爲 1。

 

舉個例子,假設布隆過濾器有 3 個哈希函數:f1, f2, f3 和一個位數組 arr。如今要把 2333 插入布隆過濾器中:

•對值進行三次哈希計算,獲得三個值 n1, n2, n3。•把位數組中三個元素 arr[n1], arr[n2], arr[3] 都置爲 1。

當要判斷一個值是否在布隆過濾器中,對元素進行三次哈希計算,獲得值以後判斷位數組中的每一個元素是否都爲 1,若是值都爲 1,那麼說明這個值在布隆過濾器中,若是存在一個值不爲 1,說明該元素不在布隆過濾器中。

很明顯,數組的容量即便再大,也是有限的。那麼隨着元素的增長,插入的元素就會越多,位數組中被置爲 1 的位置所以也越多,這就會形成一種狀況:當一個不在布隆過濾器中的元素,通過一樣規則的哈希計算以後,獲得的值在位數組中查詢,有可能這些位置由於以前其它元素的操做先被置爲 1 了

如圖 1 所示,假設某個元素經過映射對應下標爲4,5,6這3個點。雖然這 3 個點都爲 1 ,可是很明顯這 3 個點是不一樣元素通過哈希獲得的位置,所以這種狀況說明這個元素雖然不在集合中,也可能對應的都是 1,這是誤判率存在的緣由。

因此,有可能一個不存在布隆過濾器中的會被誤判成在布隆過濾器中。

這就是布隆過濾器的一個缺陷:存在誤判。

可是,若是布隆過濾器判斷某個元素不在布隆過濾器中,那麼這個值就必定不在布隆過濾器中。總結就是:

•布隆過濾器說某個元素在,可能會被誤判•布隆過濾器說某個元素不在,那麼必定不在

用英文說就是:False is always false. True is maybe true。

    誤判率:

布隆過濾器能夠插入元素,但不能夠刪除已有元素。其中的元素越多,false positive rate(誤報率)越大,可是false negative (漏報)是不可能的。

 

假設布隆過濾器有m比特,裏面有n個元素,每一個元素對應k個指紋的Hash函數

      

補救方法

布隆過濾器存在必定的誤識別率。常見的補救辦法是在創建白名單,存儲那些可能被誤判的元素。 

 

使用場景

布隆過濾器的最大的用處就是,可以迅速判斷一個元素是否在一個集合中。所以它有以下三個使用場景:

•網頁爬蟲對 URL 的去重,避免爬取相同的 URL 地址•進行垃圾郵件過濾:反垃圾郵件,從數十億個垃圾郵件列表中判斷某郵箱是否垃圾郵箱(同理,垃圾短信)•有的黑客爲了讓服務宕機,他們會構建大量不存在於緩存中的 key 向服務器發起請求,在數據量足夠大的狀況下,頻繁的數據庫查詢可能致使 DB 掛掉。布隆過濾器很好的解決了緩存擊穿的問題。

以上來自公衆號:5分鐘學算法。

 

20. 排序算法

    1)冒泡  平均O(n^2),最好O(n)。穩定的排序算法。

void m_sort(vector<int> &a)
{
      int len = a.size();
      bool flag = true;
      for(int i = 0;  i <len; i++)
      {
             for(int j = i+1; j < len - i - 1; j++)
            {
                  if(a[j-1] > a[j])
                 {
                       swap(a[j-1],a[j]);
                       flag = false;
                 }
            }
            if(flag) break;
      }
}

 

      2)選擇排序 最好O(n^2) 平均O(n^2)

  

void select_sort(vector<int> &a)
{
       int len = a.size();
       for(int  i = 0;  i <len; i++)
      {
             int min = i;
             for(int j = i +1;  j < len; j++)
            {
                   if(a[j] < a[min])
                  {
                        min = j;
                  }
            }
            if(i != min)
           {
                swap(a[i],a[min]);
           }
      }
}

 

   3)插入排序 平均O(n^2) , 最好O(n)

      

void insert_sort(vector<int>& a)
{
       int len  = a.size();
       for(int i = 1; i < len; i++)
      {
             int tmp = a[i];
             int j = i;
             while(j > 0 && tmp < a[j-1])
             {
                   a[j] = a[j-1];
                   j--;
             }
             if(j != i)
            {
                   a[j] = tmp;
            }
      }
}

        4)希爾排序 平均O(nlogn) 最壞O(nlong^2 n)

    

void shell_select(vector<int>& a)
{
      int len = a.size();
      int gap = 1;
      while(gap < len)
     {
            gap = gap * 3 +1;
     }
     while(gap > 0)
    {
           for(int i = gap; i< len; i++)
          {
                  int tmp = a[i];
                  int j = i  - gap;
                  while(j >= 0 && tmp < a[j])
                 {
                          a[j+gap] = a[j];
                          j -= gap;
                 }
                 a[j + gap] = tmp;
          }
          gap = floor(gap/3);
    }
}

      5) 歸併排序  平均O(nlogn)   最壞O(nlogn)

vector<int> merge_sort(vector<int> &a,int left,int right)
{
       if(left < right)
      {
             int mid = (left + right) / 2;
             a = merge_sort(a,left,mid);
             a = merge_sort(a,mid+1,right);
             merge(a,left,mid,right);
      }
      return arr;
}

void merge(int[] &arr,int left,int mid,int right)
{
      int[] a = new int[right - left +1];
      int i = left;
      int j = mid+1;
      int k = 0;
      while(i <= mid && j <= right)
     {
           if(arr[i] < arr[j])
          {
               a[k++] = arr[i++];
          }
          else
         {
              a[k++] = arr[j++];
         }
     }
     while(i <= mid) a[k++] = arr[i++];
     while(j <= mid) a[k++] = arr[j++];
     for(i = 0; i < k; i++)
    {
          arr[left++] = a[i];
    }
}

 

  6)快速排序 平均O(nlogn)  最壞O(n^2)

  

void quick_sort(vector<int> &a,int left,int right)
{
       if(left < right)
      {
            int pre = site(a, 0,right);
            quick_sort(a,left,pre-1);
            quick_sort(a,pre+1,right);
      }
}

void my_swap(vector<int>& a,int i,int j)
{
       int tmp = a[i];
       a[i] = a[j];
       a[j] = tmp;
}

int site(vector<int>& a,int left,int right)
{
      int pivot = left;
      int index = pivot + 1;
      for(int i = index; i<= right; i++)
     {
           if(a[i] < a[pivot])
          {
                my_swap(a, i,index);
                index++;
          }
     }
     my_swap(a, pivot, index-1);
     return index-1;
}

 

    7) 堆排序 平均O(nlogn)  最好O(nlogn)

  

int heap[maxn],sz = 0;

void push(int x)
{
    int i = sz++;
    while(i > 0)
   {
        int p = (i-1)/2;
        while(heap[p] <= x) break;
        heap[i] = heap[p];
        i = p;     
   }
   heap[i] = x;
}

int pop()
{
     int ret = heap[0];
     int x = heap[--sz];
     int i = 0;
     while(i * 2 +1 < sz)
    {
           int a = i * 2+1, b = i * 2+ 2;
           if(b < sz && heap[b] < heap[a]) a = b;
           if(heap[a] >= x) break;
           heap[i] = heap[a];
           i = a;
    }
    heap[i] = x;
    return ret;
}
相關文章
相關標籤/搜索