對於一些線程和進程的問題總結

線程之間的鎖有:互斥鎖、條件鎖、自旋鎖、讀寫鎖、遞歸鎖。一般而言,鎖的功能越強大,性能就會越低。

1、互斥鎖

互斥鎖用於控制多個線程對他們之間共享資源互斥訪問的一個信號量。也就是說是爲了避免多個線程在某一時刻同時操作一個共享資源。例如線程池中的有多個空閒線程和一個任務隊列。任何是一個線程都要使用互斥鎖互斥訪問任務隊列,以避免多個線程同時訪問任務隊列以發生錯亂。

在某一時刻,只有一個線程可以獲取互斥鎖,在釋放互斥鎖之前其他線程都不能獲取該互斥鎖。如果其他線程想要獲取這個互斥鎖,那麼這個線程只能以阻塞方式進行等待。

2、條件鎖

條件鎖就是所謂的條件變量,某一個線程因爲某個條件爲滿足時可以使用條件變量使改程序處於阻塞狀態。一旦條件滿足以“信號量”的方式喚醒一個因爲該條件而被阻塞的線程。最爲常見就是在線程池中,起初沒有任務時任務隊列爲空,此時線程池中的線程因爲“任務隊列爲空”這個條件處於阻塞狀態。一旦有任務進來,就會以信號量的方式喚醒一個線程來處理這個任務。這個過程中就使用到了條件變量pthread_cond_t。

3、自旋鎖

假設我們有一個兩個處理器core1和core2計算機,現在在這臺計算機上運行的程序中有兩個線程:T1和T2分別在處理器core1和core2上運行,兩個線程之間共享着一個資源。

首先我們說明互斥鎖的工作原理,互斥鎖是是一種sleep-waiting的鎖。假設線程T1獲取互斥鎖並且正在core1上運行時,此時線程T2也想要獲取互斥鎖(pthread_mutex_lock),但是由於T1正在使用互斥鎖使得T2被阻塞。當T2處於阻塞狀態時,T2被放入到等待隊列中去,處理器core2會去處理其他任務而不必一直等待(忙等)。也就是說處理器不會因爲線程阻塞而空閒着,它去處理其他事務去了。

而自旋鎖就不同了,自旋鎖是一種busy-waiting的鎖。也就是說,如果T1正在使用自旋鎖,而T2也去申請這個自旋鎖,此時T2肯定得不到這個自旋鎖。與互斥鎖相反的是,此時運行T2的處理器core2會一直不斷地循環檢查鎖是否可用(自旋鎖請求),直到獲取到這個自旋鎖爲止。

從“自旋鎖”的名字也可以看出來,如果一個線程想要獲取一個被使用的自旋鎖,那麼它會一致佔用CPU請求這個自旋鎖使得CPU不能去做其他的事情,直到獲取這個鎖爲止,這就是“自旋”的含義。當發生阻塞時,互斥鎖可以讓CPU去處理其他的任務;而自旋鎖讓CPU一直不斷循環請求獲取這個鎖。通過兩個含義的對比可以我們知道“自旋鎖”是比較耗費CPU的。

4、讀寫鎖

說到讀寫鎖我們可以藉助於“讀者-寫者”問題進行理解。

計算機中某些數據被多個進程共享,對數據庫的操作有兩種:一種是讀操作,就是從數據庫中讀取數據不會修改數據庫中內容;另一種就是寫操作,寫操作會修改數據庫中存放的數據。因此可以得到我們允許在數據庫上同時執行多個“讀”操作,但是某一時刻只能在數據庫上有一個“寫”操作來更新數據。這就是一個簡單的讀者-寫者模型。

 

子進程繼承父進程

  • 用戶號UIDs和用戶組號GIDs
  • 環境Environment
  • 堆棧
  • 共享內存
  • 打開文件的描述符
  • 執行時關閉(Close-on-exec)標誌
  • 信號(Signal)控制設定
  • 進程組號
  • 當前工作目錄
  • 根目錄
  • 文件方式創建屏蔽字
  • 資源限制
  • 控制終端

子進程獨有

  • 進程號PID
  • 不同的父進程號
  • 自己的文件描述符和目錄流的拷貝
  • 子進程不繼承父進程的進程正文(text),數據和其他鎖定內存(memory locks)
  • 不繼承異步輸入和輸出

父進程和子進程擁有獨立的地址空間和PID參數

子進程從父進程繼承了用戶號和用戶組號,用戶信息,目錄信息,環境(表),打開的文件描述符,堆棧,(共享)內存等。

經過fork()以後,父進程和子進程擁有相同內容的代碼段、數據段和用戶堆棧,就像父進程把自己克隆了一遍。事實上,父進程只複製了自己的PCB塊。而代碼段,數據段和用戶堆棧內存空間並沒有複製一份,而是與子進程共享。只有當子進程在運行中出現寫操作時,纔會產生中斷,併爲子進程分配內存空間。由於父進程的PCB和子進程的一樣,所以在PCB中斷中所記錄的父進程佔有的資源,也是與子進程共享使用的。這裏的“共享”一詞意味着“競爭”。

對C++ 瞭解的人都應該知道虛函數(Virtual Function)是通過一張虛函數表(Virtual Table)來實現的。簡稱爲V-Table。 在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,所以,當我們用父類的指針來操作一個子類的時候,這張虛函數表就顯得由爲重要了,它就像一個地圖一樣,指明瞭實際所應該調用的函數。
這裏我們着重看一下這張虛函數表。在C++的標準規格說明書中說到,編譯器必需要保證虛函數表的指針存在於對象實例中最前面的位置(這是爲了保證正確取到虛函數的偏移量)。 這意味着我們通過對象實例的地址得到這張虛函數表,然後就可以遍歷其中函數指針,並調用相應的函數。

.在C++中,內存分成5個區,他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區 
1.棧,
就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變量的存儲區。裏面的變量通常是局部變量、函數參數等。 
2.堆,就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那麼在程序結束後,操作系統會自動回收。 
3.自由存儲區,就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。 
4.全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分爲初始化的和未初始化的,在C++裏面沒有這個區分了,他們共同佔用同一塊內存區。 
5.常量存儲區,這是一塊比較特殊的存儲區,他們裏面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改) 
談談堆與棧的關係與區別 
現代計算機(串行執行機制),都直接在代碼底層支持棧的數據結構。體現在,有專門的寄存器指向棧所在的地址,有專門的機器指令完成數據入棧出棧的操作。這種機制的特點是效率高,支持的數據有限,一般是整數,指針,浮點數等系統直接支持的數據類型,並不直接支持其他的數據結構。因爲棧的這種特點,對棧的使用在程序中是非常頻繁的。對子程序的調用就是直接利用棧完成的。機器的call指令裏隱含了把返回地址推入棧,然後跳轉至子程序地址的操作,而子程序中的ret指令則隱含從堆棧中彈出返回地址並跳轉之的操作。C/C++中的自動變量是直接利用棧的例子,這也就是爲什麼當函數返回時,該函數的自動變量自動失效的原因。 
和棧不同,堆的數據結構並不是由系統(無論是機器系統還是操作系統)支持的,而是由函數庫提供的。基本的malloc/realloc/free 函數維護了一套內部的堆數據結構。當程序使用這些函數去獲得新的內存空間時,這套函數首先試圖從內部堆中尋找可用的內存空間,如果沒有可以使用的內存空間,則試圖利用系統調用來動態增加程序數據段的內存大小,新分配得到的空間首先被組織進內部堆中去,然後再以適當的形式返回給調用者。當程序釋放分配的內存空間時,這片內存空間被返回內部堆結構中,可能會被適當的處理(比如和其他空閒空間合併成更大的空閒空間),以更適合下一次內存分配申請。這套複雜的分配機制實際上相當於一個內存分配的緩衝池(Cache),使用這套機制有如下若干原因: 
1. 系統調用可能不支持任意大小的內存分配。有些系統的系統調用只支持固定大小及其倍數的內存請求(按頁分配);這樣的話對於大量的小內存分類來說會造成浪費。 
2. 系統調用申請內存可能是代價昂貴的。系統調用可能涉及用戶態和核心態的轉換。 
3. 沒有管理的內存分配在大量複雜內存的分配釋放操作下很容易造成內存碎片。

棧是系統數據結構,對於進程/線程是唯一的;堆是函數庫內部數據結構,不一定唯一。不同堆分配的內存無法互相操作。

static的內部機制: 
       靜態數據成員要在程序一開始運行時就必須存在。因爲函數在程序運行中被調用,所以靜態數據成員不能在任何函數內分配空間和初始化。 
       這樣,它的空間分配有三個可能的地方,一是作爲類的外部接口的頭文件,那裏有類聲明;二是類定義的內部實現,那裏有類的成員函數定義;三是應用程序的main()函數前的全局數據聲明和定義處。 
      靜態數據成員要實際地分配空間,故不能在類的聲明中定義(只能聲明數據成員)。類聲明只聲明一個類的“尺寸和規格”,並不進行實際的內存分配,所以在類聲 明中寫成定義是錯誤的。它也不能在頭文件中類聲明的外部定義,因爲那會造成在多個使用該類的源文件中,對其重複定義。 
      static被引入以告知編譯器,將變量存儲在程序的靜態存儲區而非棧上空間,靜態數據成員按定義出現的先後順序依次初始化,注意靜態成員嵌套時,要保證所嵌套的成員已經初始化了。消除時的順序是初始化的反順序。 
       static的優勢: 
       可以節省內存,因爲它是所有對象所公有的,因此,對多個對象來說,靜態數據成員只存儲一處,供所有對象共用。靜態數據成員的值對每個對象都是一樣,但它的 值是可以更新的。只要對靜態數據成員的值更新一次,保證所有對象存取更新後的相同的值,這樣可以提高時間效率。靜態數據成員是靜態存儲的,所以必須對它進行初始化。

 

 

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

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

總結:volatile關鍵詞影響編譯器編譯的結果,用volatile聲明的變量表示該變量隨時可能發生變化,與該變量有關的運算,不再編譯優化,以免出錯。

3)一個參數既可以是const還可以是volatile嗎?解釋爲什麼。

可以。一個例子是隻讀的狀態寄存器。它是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可能是不同的。結果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:
long square(volatile int *ptr){
int a;
a = *ptr;
return a * a;
}

首先說說const的用法(絕對不能說是常數)

1)在定義的時候必須進行初始化

2)指針可以是const  指針,也可以是指向const對象的指針

3)定義爲const的形參,即在函數內部是不能被修改的

4)類的成員函數可以被聲明爲常成員函數,不能修改類的成員變量

5)類的成員函數可以返回的是常對象,即被const聲明的對象

6)類的成員變量是常成員變量不能在聲明時初始化,必須在構造函數的列表裏進行初始化

下面的聲明都是什麼意思?
const int a; a是一個常整型數
int const a; a是一個常整型數
const int *a; a是一個指向常整型數的指針,整型數是不可修改的,但指針可以
int * const a; a爲指向整型數的常指針,指針指向的整型數可以修改,但指針是不可修改的
int const * a const; a是一個指向常整型數的常指針,指針指向的整型數是不可修改的,同時指針也是不可修改的
通過給優化器一些附加的信息,使用關鍵字const也許能產生更緊湊的代碼。合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。

static的用法(三個明顯的作用)

1)在函數體,一個被聲明爲靜態的變量在這一函數被調用過程中維持其值不變。
2)在模塊內(但在函數體外),一個被聲明爲靜態的變量可以被模塊內所用函數訪問,但不能被模塊外其它函數訪問。它是一個本地的全局變量。
3)在模塊內,一個被聲明爲靜態的函數只可被這一模塊內的其它函數調用。那就是,這個函數被限制在聲明它的模塊的本地範圍內使用

4)類內的static成員變量屬於整個類所擁有,不能在類內進行定義,只能在類的作用域內進行定義

5)類內的static成員函數屬於整個類所擁有,不能包含this指針,只能調用static成員函數

static全局變量與普通的全局變量有什麼區別?static局部變量和普通局部變量有什麼區別?static函數與普通函數有什麼區別?

static全局變量與普通的全局變量有什麼區別:static全局變量只初使化一次,防止在其他文件單元中被引用;
static局部變量和普通局部變量有什麼區別:static局部變量只被初始化一次,下一次依據上一次結果值;
static函數與普通函數有什麼區別:static函數在內存中只有一份,普通函數在每個被調用中維持一份拷貝

4.extern c 作用

告訴編譯器該段代碼以C語言進行編譯。

5.指針和引用的區別

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

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

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

總的來說:引用既具有指針的效率,又具有變量使用的方便性和直觀性

6. 關於靜態內存分配和動態內存分配的區別及過程

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

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

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

4)靜態內存分配是按計劃分配,在編譯前確定內存塊的大小,動態內存分配運行時按需分配。

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

6)靜態分配內存的運行效率要比動態分配內存的效率要高,因爲動態內存分配與釋放需要額外的開銷;動態內存管理水平嚴重依賴於程序員的水平,處理不當容易造成內存泄漏。

7. 頭文件中的ifndef/define/endif 幹什麼用

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

8. 宏定義求兩個元素的最小值

#define MIN(A,B) ((A) <= (B) ?(A) : (B))

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

 #define BIT3 (0x1<<3)

static int a;

void set_bit3(void){ 

    a |= BIT3;

} 

void clear_bit3(void){ 

    a &= ~BIT3;

} 
16. memcpy函數的實現

void *memcpy(void *dest, constvoid *src, size_t count) {
 char *tmp = dest;
 const char *s = src;
 
 while (count--)
  *tmp++ = *s++;
  return dest;
}

17. Strcpy函數實現

char *strcpy(char *dst,constchar *src) { 
      assert(dst != NULL&& src != NULL); 
      char *ret = dst; 
      while((* dst++ = * src++) != '\0') ; 
      return ret; 
 }
18. strcat函數的實現

char *strcat(char *strDes, constchar *strSrc){
assert((strDes != NULL) && (strSrc != NULL));
char *address = strDes;
while (*strDes != ‘\0′)
++ strDes;
while ((*strDes ++ = *strSrc ++) != ‘\0′)
return address;
}
19.strncat實現

char *strncat(char *strDes, const char *strSrc, int count){
  assert((strDes != NULL) && (strSrc != NULL));
  char *address = strDes;
  while (*strDes != ‘\0′)
    ++ strDes;
  while (count — && *strSrc != ‘\0′ )
    *strDes ++ = *strSrc ++;
    *strDes = ‘\0′;
 return address;
}
20. strcmp函數實現

int strcmp(const char *str1,const char *str2){
    /*不可用while(*str1++==*str2++)來比較,當不相等時仍會執行一次++,
    return返回的比較值實際上是下一個字符。應將++放到循環體中進行。*/
    while(*str1 == *str2){
        if(*str1 == '\0')
            return0;
         
        ++str1;
        ++str2;
    }
    return *str1 - *str2;
}
21. strncmp實現
int strncmp(constchar *s, const char *t, int count){
    assert((s != NULL) && (t != NULL));
    while (*s&& *t && *s == *t && count–) {
        ++ s;
        ++ t;
    }
    return (*s– *t);
}
22.strlen函數實現
int strlen(const char *str){
    assert(str != NULL);
    int len = 0;
    while (*str ++ != ‘\0′)
        ++ len;
    return len;
}
23. strpbrk函數實現
char * strpbrk(constchar * cs,const char * ct){
    constchar *sc1,*sc2;
    for( sc1 = cs; *sc1 != '\0'; ++sc1){
        for( sc2 = ct; *sc2 != '\0'; ++sc2){
            if (*sc1 == *sc2){
                return (char *) sc1;
            }
        }
    }
    return NULL;
}
24. strstr函數實現
char *strstr(const char *s1,constchar *s2){
 int len2;
 if(!(len2=strlen(s2)))//此種情況下s2不能指向空,否則strlen無法測出長度,這條語句錯誤
  return(char*)s1;
 for(;*s1;++s1)
 {
     if(*s1==*s2&& strncmp(s1,s2,len2)==0)
     return(char*)s1;
 }
 return NULL;
}
25. string實現(注意:賦值構造,operator=是關鍵)
class String{
public:
//普通構造函數
String(constchar *str = NULL);
//拷貝構造函數
String(const String &other);
//賦值函數
String & operator=(String&other) ;
//析構函數
~String(void);
private:
char* m_str;
};
 
分別實現以上四個函數
//普通構造函數
String::String(const char* str){
    if(str==NULL) //如果str爲NULL,存空字符串{
        m_str = newchar[1]; //分配一個字節
        *m_str = ‘\0′; //賦一個’\0′
}else{
       str = newchar[strlen(str) + 1];//分配空間容納str內容
        strcpy(m_str, str); //複製str到私有成員m_str中
    }
}
 
//析構函數
String::~String(){
 
    if(m_str!=NULL) //如果m_str不爲NULL,釋放堆內存{
        delete [] m_str;
        m_str = NULL;
}
}
 
//拷貝構造函數
String::String(const String &other){
    m_str = newchar[strlen(other.m_str)+1]; //分配空間容納str內容
    strcpy(m_str, other.m_str); //複製other.m_str到私有成員m_str中 
}
 
//賦值函數
String & String::operator=(String&other){
    if(this == &other) //若對象與other是同一個對象,直接返回本{
        return *this
}
    delete [] m_str; //否則,先釋放當前對象堆內存
    m_str = newchar[strlen(other.m_str)+1]; //分配空間容納str內容
    strcpy(m_str, other.m_str); //複製other.m_str到私有成員m_str中
    return *this;
}

用struct關鍵字與class關鍵定義類以及繼承的區別

   (1)定義類差別

         struct關鍵字也可以實現類,用class和struct關鍵字定義類的唯一差別在於默認訪問級別:默認情況下,struct成員的訪問級別爲public,而class成員的爲private。語法使用也相同,直接將class改爲struct即可。

   (2)繼承差別

使用class保留字的派生類默認具有private繼承,而用struct保留字定義的類某人具有public繼承。其它則沒有任何區別。

主要點就兩個:默認的訪問級別和默認的繼承級別 class都是private

8.派生類與虛函數概述

(1) 派生類繼承的函數不能定義爲虛函數。虛函數是希望派生類重新定義。如果派生類沒有重新定義某個虛函數,則在調用的時候會使用基類中定義的版本。

(2)派生類中函數的聲明必須與基類中定義的方式完全匹配。

(3) 基類中聲明爲虛函數,則派生類也爲虛函數。

29. 虛函數與純虛函數區別

1)虛函數在子類裏面也可以不重載的;但純虛必須在子類去實現

2)帶純虛函數的類叫虛基類也叫抽象類,這種基類不能直接生成對象,只能被繼承,重寫虛函數後才能使用,運行時動態綁定!

深拷貝與淺拷貝

 淺拷貝:

char ori[]=“hello”;char*copy=ori;

深拷貝:

char ori[]="hello";  char *copy=new char[];  copy=ori;

淺拷貝只是對指針的拷貝,拷貝後兩個指針指向同一個內存空間,深拷貝不但對指針進行拷貝,而且對指針指向的內容進行拷貝,經深拷貝後的指針是指向兩個不同地址的指針。

淺拷貝可能出現的問題:

1) 淺拷貝只是拷貝了指針,使得兩個指針指向同一個地址,這樣在對象塊結束,調用函數析構的時,會造成同一份資源析構2次,即delete同一塊內存2次,造成程序崩潰。

2) 淺拷貝使得兩個指針都指向同一塊內存,任何一方的變動都會影響到另一方。

3) 同一個空間,第二次釋放失敗,導致無法操作該空間,造成內存泄漏。

31.stl各容器的實現原理(必考)

1) Vector順序容器,是一個動態數組,支持隨機插入、刪除、查找等操作,在內存中是一塊連續的空間。在原有空間不夠情況下自動分配空間,增加爲原來的兩倍。vector隨機存取效率高,但是在vector插入元素,需要移動的數目多,效率低下。

注:vector動態增加大小時是以原大小的兩倍另外配置一塊較大的空間,然後將原內容拷貝過來,然後纔開始在原內容之後構造新元素,並釋放原空間。因此,對vector空間重新配置,指向原vector的所有迭代器就都失效了。

2) Map關聯容器,以鍵值對的形式進行存儲,方便進行查找。關鍵詞起到索引的作用,值則表示與索引相關聯的數據。紅黑樹的結構實現,插入刪除等操作都在O(logn)時間內完成。

3) Set是關聯容器,set每個元素只包含一個關鍵字。set支持高效的關鍵字檢查是否在set中。set也是以紅黑樹的結構實現,支持高效插入、刪除等操作。

32.哪些庫函數屬於高危函數,爲什麼?

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

33.STL有7種主要容器:vector,list,deque,map,multimap,set,multiset

34.你如何理解MVC。簡單舉例來說明其應用。

MVC模式是observer 模式的一個特例,現在很多都是java的一些框架,MFC的,PHP的。

35.C++特點是什麼,多態實現機制?多態作用?兩個必要條件?

C++中多態機制主要體現在兩個方面,一個是函數的重載,一個是接口的重寫。接口多態指的是“一個接口多種形態”。每一個對象內部都有一個虛表指針,該虛表指針被初始化爲本類的虛表。所以在程序中,不管你的對象類型如何轉換,但該對象內部的虛表指針是固定的,所以呢,才能實現動態的對象函數調用,這就是C++多態性實現的原理。

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

作用:

  1. 隱藏實現細節,代碼能夠模塊化;2. 接口重用:爲了類在繼承和派生的時候正確調用。

必要條件:

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

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

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

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

3)繼承過程中可能會繼承一些不必要的數據,對於多級繼承,可能會產生數據很長

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

37.求兩個數的乘積和商數,該作用由宏定義來實現

#define product(a,b) ((a)*(b))
#define divide(a,b)  ((a)/(b))

38.什麼叫靜態關聯,什麼叫動態關聯

多態中,靜態關聯是程序在編譯階段就能確定實際執行動作,程序運行才能確定叫動態關聯

39.什麼叫智能指針?常用的智能指針有哪些?智能指針的實現?

智能指針是一個存儲指向動態分配(堆)對象指針的類,構造函數傳入普通指針,析構函數釋放指針。棧上分配,函數或程序結束自動釋放,防止內存泄露。使用引用計數器,類與指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。創建類的新對象時,初始化指針並將引用計數置爲1;當對象作爲另一對象的副本而創建,增加引用計數;對一個對象進行賦值時,減少引用計數,並增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數,當引用計數減至0,則刪除基礎對象。

std::auto_ptr,不支持複製(拷貝構造函數)和賦值(operator=),編譯不會提示出錯。

C++11引入的unique_ptr, 也不支持複製和賦值,但比auto_ptr好,直接賦值會編譯出錯。

C++11或boost的shared_ptr,基於引用計數的智能指針。可隨意賦值,直到內存的引用計數爲0的時候這個內存會被釋放。還有Weak_ptr

40.枚舉與#define宏的區別

1)#define 宏常量是在預編譯階段進行簡單替換。枚舉常量則是在編譯的時候確定其值。
2)可以調試枚舉常量,但是不能調試宏常量。
3)枚舉可以一次定義大量相關的常量,而#define 宏一次只能定義一個。

42.派生新類的過程要經歷三個步驟

1.吸收基類成員   2.改造基類成員    3.添加新成員

43.面向對象的三個基本特徵,並簡單敘述之?

1)封裝:將客觀事物抽象成類,每個類對自身的數據和方法實行2)繼承3)多態:允許一個基類的指針或引用指向一個派生類對象

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

多態性是一個接口,多種實現,是面向對象的核心。編譯時多態性:通過重載函數實現。運行時多態性:通過虛函數實現,結合動態綁定。

45.虛函數,虛函數表裏面內存如何分配?

編譯時若基類中有虛函數,編譯器爲該的類創建一個一維數組的虛表,存放是每個虛函數的地址。基類和派生類都包含虛函數時,這兩個類都建立一個虛表。構造函數中進行虛表的創建和虛表指針的初始化。在構造子類對象時,要先調用父類的構造函數,初始化父類對象的虛表指針,該虛表指針指向父類的虛表。執行子類的構造函數時,子類對象的虛表指針被初始化,指向自身的虛表。每一個類都有虛表。虛表可以繼承,如果子類沒有重寫虛函數,那麼子類虛表中仍然會有該函數的地址,只不過這個地址指向的是基類的虛函數實現。派生類的虛表中虛函數地址的排列順序和基類的虛表中虛函數地址排列順序相同。當用一個指針/引用調用一個函數的時候,被調用的函數是取決於這個指針/引用的類型。即如果這個指針/引用是基類對象的指針/引用就調用基類的方法;如果指針/引用是派生類對象的指針/引用就調用派生類的方法,當然如果派生類中沒有此方法,就會向上到基類裏面去尋找相應的方法。這些調用在編譯階段就確定了。當涉及到多態性的時候,採用了虛函數和動態綁定,此時的調用就不會在編譯時候確定而是在運行時確定。不在單獨考慮指針/引用的類型而是看指針/引用的對象的類型來判斷函數的調用,根據對象中虛指針指向的虛表中的函數的地址來確定調用哪個函數。

46. 純虛函數如何定義?含有純虛函數的類稱爲什麼?爲什麼析構函數要定義成虛函數?

純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。純虛函數是虛函數再加上= 0。virtual void fun ()=0。含有純虛函數的類稱爲抽象類在很多情況下,基類本身生成對象是不合情理的。例如,動物作爲一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。同時含有純虛擬函數的類稱爲抽象類,它不能生成對象。如果析構函數不是虛函數,那麼釋放內存時候,編譯器會使用靜態聯編,認爲p就是一個基類指針,調用基類析構函數,這樣子類對象的內存沒有釋放,造成內存泄漏。定義成虛函數以後,就會動態聯編,先調用子類析構函數,再基類。
47. C++中哪些不能是虛函數?

1)普通函數只能重載,不能被重寫,因此編譯器會在編譯時綁定函數。
2)構造函數是知道全部信息才能創建對象,然而虛函數允許只知道部分信息。
3)內聯函數在編譯時被展開,虛函數在運行時才能動態綁定函數。
4)友元函數 因爲不可以被繼承。
5)靜態成員函數 只有一個實體,不能被繼承。父類和子類共有。
48. 類型轉換有哪些?各適用什麼環境?dynamic_cast轉換失敗時,會出現什麼情況(對指針,返回NULL.對引用,拋出bad_cast異常)?

 靜態類型轉換,static_cast,基本類型之間和具有繼承關係的類型。
例子A,double類型轉換成int。B,將子類對象轉換成基類對象。
常量類型轉換,const_cast, 去除指針變量的常量屬性。
無法將非指針的常量轉換爲普通變量。
動態類型轉換,dynamic_cast,運行時進行轉換分析的,並非在編譯時進行。dynamic_cast轉換符只能用於含有虛函數的類。dynamic_cast用於類層次間的向上轉換和向下轉換,還可以用於類間的交叉轉換。在類層次間進行向上轉換,即子類轉換爲父類,此時完成的功能和static_cast是相同的,因爲編譯器默認向上轉換總是安全的。向下轉換時,dynamic_cast具有類型檢查的功能,更加安全。類間的交叉轉換指的是子類的多個父類之間指針或引用的轉換。該函數只能在繼承類對象的指針之間或引用之間進行類型轉換,或者有虛函數的類。

 

49. 如何判斷一段程序是由C編譯程序還是由C++編譯程序編譯的?

#ifdef __cplusplus
cout<<"C++";
#else
cout<<"c";
#endif

50. 爲什麼要用static_cast轉換而不用c語言中的轉換?
Static_cast轉換,它會檢查類型看是否能轉換,有類型安全檢查。
比如,這個在C++中合法,但是確實錯誤的。
A* a= new A;
B* b = (B*)a;

51. 操作符重載(+操作符),具體如何去定義?
除了類屬關係運算符”.”、成員指針運算符”.*”、作用域運算符”::”、sizeof運算符和三目運算符”?:”以外,C++中的所有運算符都可以重載。
<返回類型說明符> operator <運算符符號>(<參數表>){}
重載爲類的成員函數和重載爲類的非成員函數。參數個數會不同,應爲this指針。

52. 內存對齊的原則?
A.結構體的大小爲最大成員的整數倍。
B.成員首地址的偏移量爲其類型大小整數倍。

53. 內聯函數與宏定義的區別?
內聯函數是用來消除函數調用時的時間開銷。頻繁被調用的短小函數非常受益。
A. 宏定義不檢查函數參數,返回值什麼的,只是展開,相對來說,內聯函數會檢查參數類型,所以更安全。
B. 宏是由預處理器對宏進行替代,而內聯函數是通過編譯器控制來實現的
54. 動態分配對象和靜態分配對象的區別?
動態分配就是用運算符new來創建一個類的對象,在堆上分配內存。
靜態分配就是A a;這樣來由編譯器來創建一個對象,在棧上分配內存。

55. explicit是幹什麼用的?
構造器,可以阻止不應該允許的經過轉換構造函數進行的隱式轉換的發生。explicit是用來防止外部非正規的拷貝構造的,要想不存在傳值的隱式轉換問題。

56. 內存溢出有那些因素?
(1) 使用非類型安全(non-type-safe)的語言如 C/C++ 等。
(2) 以不可靠的方式存取或者複製內存緩衝區。
(3) 編譯器設置的內存緩衝區太靠近關鍵數據結構。

57. new與malloc的區別,delete和free的區別?
1.malloc/free是C/C++語言的標準庫函數,new/delete是C++的運算符
2.new能夠自動分配空間大小,malloc傳入參數。
3. new/delete能進行對對象進行構造和析構函數的調用進而對內存進行更加詳細的工作,而malloc/free不能。
既然new/delete的功能完全覆蓋了malloc/free,爲什麼C++還保留malloc/free呢?因爲C++程序經常要調用C函數,而C程序只能用malloc/free管理動態內存。

58. 必須使用初始化列表初始化數據成員的情況
1.是對象的情況;
2.const修飾的類成員;
3.引用成員數據;

類成員變量的初始化不是按照初始化表順序被初始化,是按照在類中聲明的順序被初始化的。

59.深入談談堆和棧

1).分配和管理方式不同 :
       堆是動態分配的,其空間的分配和釋放都由程序員控制。
      棧由編譯器自動管理。棧有兩種分配方式:靜態分配和動態分配。靜態分配由編譯器完成,比如局部變量的分配。動態分配由alloca()函數進行分配,但是棧的動態分配和堆是不同的,它的動態分配是由編譯器進行釋放,無須手工控制。
2).產生碎片不同
        對堆來說,頻繁的new/delete或者malloc/free勢必會造成內存空間的不連續,造成大量的碎片,使程序效率降低。
        對棧而言,則不存在碎片問題,因爲棧是先進後出的隊列,永遠不可能有一個內存塊從棧中間彈出。
3).生長方向不同
      堆是向着內存地址增加的方向增長的,從內存的低地址向高地址方向增長。
     棧是向着內存地址減小的方向增長,由內存的高地址向低地址方向增長。
60.內存的靜態分配和動態分配的區別?
時間不同。靜態分配發生在程序編譯和連接時。動態分配則發生在程序調入和執行時。
空間不同。堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。alloca,可以從棧裏動態分配內存,不用擔心內存泄露問題,當函數返回時,通過alloca申請的內存就會被自動釋放掉。

61. 模版怎麼實現?模版作用?
實現:template void swap(T& a, T& b){}
作用:將算法與具體對象分離,與類型無關,通用,節省精力

62. 多重類構造和析構的順序

記住析構函數的調用順序與構造函數是相反的。
63. 迭代器刪除元素的會發生什麼?

迭代器失效
64. 靜態成員函數和數據成員有什麼意義?
1)非靜態數據成員,每個對象都有自己的拷貝。而靜態數據成員被當作是類的成員,是該類的所有對象所共有的,在程序中只分配一次內存只有一份拷貝,所以對象都共享,值對每個對象都是一樣的,它的值可以更新。

2)靜態數據成員存儲在全局數據區,所以不能在類聲明中定義,應該在類外定義。由於它不屬於特定的類對象,在沒有產生類對象時作用域就可見,即在沒有產生類的實例時,我們就可以操作它。

3)靜態成員函數與靜態數據成員一樣,都是在類的內部實現,屬於類定義的一部分。因爲普通成員函數總是具體的屬於具體對象的,每個有this指針。靜態成員函數沒有this指針,它無法訪問屬於類對象的非靜態數據成員,也無法訪問非靜態成員函數。靜態成員之間可以互相訪問,包括靜態成員函數訪問靜態數據成員和訪問靜態成員函數;

4)非靜態成員函數可以任意地訪問靜態成員函數和靜態數據成員;

5)沒有this指針的額外開銷,靜態成員函數與類的全局函數相比,速度上會有少許的增長;

6)調用靜態成員函數,可以用成員訪問操作符(.)和(->)爲一個類的對象或指向類對象的指調用靜態成員函數。

69.拷貝構造函數作用及用途?什麼時候需要自定義拷貝構造函數?

一般如果構造函數中存在動態內存分配,則必須定義拷貝構造函數。否則,可能會導致兩個對象成員指向同一地址,出現“指針懸掛問題”。

70. 100萬個32位整數,如何最快找到中位數。能保證每個數是唯一的,如何實現O(N)算法?

1).內存足夠時:快排

2).內存不足時:分桶法:化大爲小,把所有數劃分到各個小區間,把每個數映射到對應的區間裏,對每個區間中數的個數進行計數,數一遍各個區間,看看中位數落在哪個區間,若夠小,使用基於內存的算法,否則繼續劃分

72. C++虛函數是如何實現的?

使用虛函數表。 C++對象使用虛表, 如果是基類的實例,對應位置存放的是基類的函數指針;如果是繼承類,對應位置存放的是繼承類的函數指針(如果在繼承類有實現)。所以 ,當使用基類指針調用對象方法時,也會根據具體的實例,調用到繼承類的方法。 

73. C++的虛函數有什麼作用?

虛函數作用是實現多態,虛函數其實是實現封裝,使得使用者不需要關心實現的細節。在很多設計模式中都是這樣用法,例如Factory、Bridge、Strategy模式。

74.MFC中CString是類型安全類嗎,爲什麼?

不是,其他數據類型轉換到CString可以使用CString的成員函數Format來轉換

74.動態鏈接庫的兩種使用方法及特點?

1).載入時動態鏈接,模塊非常明確調用某個導出函數,使得他們就像本地函數一樣。這需要鏈接時鏈接那些函數所在DLL的導入庫,導入庫向系統提供了載入DLL時所需的信息及DLL函數定位。 

2)運行時動態鏈接。

二、服務器編程

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

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

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

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

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

5)進程間不會相互影響 ;線程一個線程掛掉將導致整個進程掛掉

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

線程所私有的:

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

二、服務器編程

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

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

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

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

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

5)進程間不會相互影響 ;線程一個線程掛掉將導致整個進程掛掉

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

線程所私有的:

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

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

a.互斥鎖(mutex)b.遞歸鎖 c.自旋鎖 d.讀寫鎖

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

當鎖被其他線程佔用時,其他線程並不是睡眠狀態,而是不停的消耗CPU,獲取鎖;互斥鎖則不然,保持睡眠,直到互斥鎖被釋放激活。

自旋鎖,遞歸調用容易造成死鎖,對長時間才能獲得到鎖的情況,使用自旋鎖容易造成CPU效率低,只有內核可搶佔式或SMP情況下才真正需要自旋鎖。

4.進程間通信和線程間通信

1).管道 2)消息隊列 3)共享內存 4)信號量 5)套接字 6)條件變量

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

 應儘量和CPU核數相等或者爲CPU核數+1的個數

6.什麼是原子操作,gcc提供的原子操作原語,使用這些原語如何實現讀寫鎖?

原子操作是指不會被線程調度機制打斷的操作;這種操作一旦開始,就一直運行到結束,中間不會有任何 context switch。

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

reactor模式:同步阻塞I/O模式,註冊對應讀寫事件處理器,等待事件發生進而調用事件處理器處理事件。 proactor模式:異步I/O模式。Reactor和Proactor模式的主要區別就是真正的讀取和寫入操作是有誰來完成的,Reactor中需要應用程序自己讀取或者寫入數據,Proactor模式中,應用程序不需要進行實際讀寫過程。

Reactor是:

主線程往epoll內核上註冊socket讀事件,主線程調用epoll_wait等待socket上有數據可讀,當socket上有數據可讀的時候,主線程把socket可讀事件放入請求隊列。睡眠在請求隊列上的某個工作線程被喚醒,處理客戶請求,然後往epoll內核上註冊socket寫請求事件。主線程調用epoll_wait等待寫請求事件,當有事件可寫的時候,主線程把socket可寫事件放入請求隊列。睡眠在請求隊列上的工作線程被喚醒,處理客戶請求。

Proactor:

主線程調用aio_read函數向內核註冊socket上的讀完成事件,並告訴內核用戶讀緩衝區的位置,以及讀完成後如何通知應用程序,主線程繼續處理其他邏輯,當socket上的數據被讀入用戶緩衝區後,通過信號告知應用程序數據已經可以使用。應用程序預先定義好的信號處理函數選擇一個工作線程來處理客戶請求。工作線程處理完客戶請求之後調用aio_write函數向內核註冊socket寫完成事件,並告訴內核寫緩衝區的位置,以及寫完成時如何通知應用程序。主線程處理其他邏輯。當用戶緩存區的數據被寫入socket之後內核嚮應用程序發送一個信號,以通知應用程序數據已經發送完畢。應用程序預先定義的數據處理函數就會完成工作。

半同步半異步模式:

上層的任務(如:數據庫查詢,文件傳輸)使用同步I/O模型,簡化了編寫並行程序的難度。
而底層的任務(如網絡控制器的中斷處理)使用異步I/O模型,提供了執行效率。

8.有一個計數器,多個線程都需要更新,會遇到什麼問題,原因是什麼,應該如何做?如何優化?

有可能一個線程更新的數據已經被另外一個線程更新了,更新的數據就會出現異常,可以加鎖,保證數據更新只會被一個線程完成。

9.如果select返回可讀,結果只讀到0字節,什麼情況?

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

10. connect可能會長時間阻塞,怎麼解決?

1.使用定時器;(最常用也最有效的一種方法)

2.採用非阻塞模式:設置非阻塞,返回之後用select檢測狀態。

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

keepalive,是在TCP中一個可以檢測死連接的機制。

1).如果主機可達,對方就會響應ACK應答,就認爲是存活的。

2).如果可達,但應用程序退出,對方就發RST應答,發送TCP撤消連接。

3).如果可達,但應用程序崩潰,對方就發FIN消息。

4).如果對方主機不響應ack,rst,繼續發送直到超時,就撤消連接。默認二個小時。

12.socket什麼情況下可讀?

 1.socket接收緩衝區中已經接收的數據的字節數大於等於socket接收緩衝區低潮限度的當前值;對這樣的socket的讀操作不會阻塞,並返回一個大於0的值(準備好讀入的數據的字節數).

 2.連接的讀一半關閉(即:接收到對方發過來的FIN的TCP連接),並且返回0; 
 3.socket收到了對方的connect請求已經完成的連接數爲非0.這樣的soocket處於可讀狀態;
 4.異常的情況下socket的讀操作將不會阻塞,並且返回一個錯誤(-1)。

13.udp調用connect有什麼作用?

1).因爲UDP可以是一對一,多對一,一對多,或者多對多的通信,所以每次調用sendto()/recvfrom()時都必須指定目標IP和端口號。通過調用connect()建立一個端到端的連接,就可以和TCP一樣使用send()/recv()傳遞數據,而不需要每次都指定目標IP和端口號。但是它和TCP不同的是它沒有三次握手的過程。

2).可以通過在已建立連接的UDP套接字上,調用connect()實現指定新的IP地址和端口號以及斷開連接。

14.socket編程,如果client斷電了,服務器如何快速知道?

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

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

1)、自己編寫心跳包程序,簡單的說就是自己的程序加入一條線程,定時向對端發送數據包,查看是否有ACK,根據ACK的返回情況來管理連接。此方法比較通用,一般使用業務層心跳處理,靈活可控,但改變了現有的協議;
2)、使用TCP的keepalive機制,UNIX網絡編程不推薦使用SO_KEEPALIVE來做心)跳檢測。
keepalive原理:TCP內嵌有心跳包,以服務端爲例,當server檢測到超過一定時間(/proc/sys/net/ipv4/tcp_keepalive_time 7200 即2小時)沒有數據傳輸,那麼會向client端發送一個keepalivepacket。

三、liunx操作系統

1.熟練netstattcpdump ipcs ipcrm

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

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

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

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

3.進程內存空間分佈情況

 

4.ELF是什麼?其大小與程序中全局變量的是否初始化有什麼關係(注意未初始化的數據放在bss段)

可執行連接格式。可以減少重新編程重新編譯的代碼。

5.動態鏈接和靜態鏈接的區別?

動態鏈接是隻建立一個引用的接口,而真正的代碼和數據存放在另外的可執行模塊中,在可執行文件運行時再裝入;而靜態鏈接是把所有的代碼和數據都複製到本模塊中,運行時就不再需要庫了

6.32位系統一個進程最多有多少堆內存

32位意味着4G的尋址空間,Linux把它分爲兩部分:最高的1G(虛擬地址從0xC0000000到0xffffffff)用做內核本身,成爲“系統空間”,而較低的3G字節(從0x00000000到0xbffffff)用作各進程的“用戶空間”。每個進程可以使用的用戶空間是3G。雖然各個進程擁有其自己的3G用戶空間,系統空間卻由所有的進程共享。從具體進程的角度看,則每個進程都擁有4G的虛擬空間,較低的3G爲自己的用戶空間,最高的1G爲所有進程以及內核共享的系統空間。實際上有人做過測試也就2G左右。

7.寫一個c程序辨別系統是64位 or 32位

 void* number =  0;      printf("%d\n",sizeof(&number));  

輸出8就是64位 輸出4就是32位的 根據邏輯地址判斷的

8.寫一個c程序辨別系統是大端or小端字節序

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

test.value= 0x0102;

if((test.a[0] == 1) && (test.a[1]== 2)) cout << "big"<<endl; else cout <<"little"  << endl;

9.信號:列出常見的信號,信號怎麼處理?

1).進程終止的信號 2).跟蹤進程的信號 3).與進程例外事件相關的信號等

對於信號的處理或者執行相關的操作進行處理或者直接忽略

10.i++ 是否原子操作?並解釋爲什麼?

答案肯定不是原子操作,i++主要看三個步驟

首先把數據從內存放到寄存器上,在寄存器上進行自增處理,放回到寄存器上,每個步驟都可能會被中斷分離開!

11.說出你所知道的各類linux系統的各類同步機制(重點),什麼是死鎖?如何避免死鎖(每個技術面試官必問)

1).原子操作 2).信號量(其實就是互斥鎖也就是鎖的機制)3).讀寫信號量(就是讀寫鎖)4).自旋鎖  5.內核鎖 6).順序鎖

死鎖就是幾個進程申請資源,出現了循環等待的情況!

避免死鎖的方法:

1).資源是互斥的 2).不可搶佔 3)佔有且申請 4).循環等待

12、exit()_exit()的區別?

 

13、如何實現守護進程?

1)創建子進程,父進程退出

2)在子進程中創建新會話

3)改變當前目錄爲根目

4)重設文件權限掩碼

5) 關閉文件描述符

6) 守護進程退出處理

當用戶需要外部停止守護進程運行時,往往會使用 kill命令停止該守護進程。所以,守護進程中需要編碼來實現kill發出的signal信號處理,達到進程的正常退出。

 

14、linux的任務調度機制是什麼?

Linux 分實時進程和普通進程,實時進程應該先於普通進程而運行。實時進程:

1) FIFO(先來先服務調度)

2) RR(時間片輪轉調度)。

每個進程有兩個優先級(動態優先級和實時優先級),實時優先級就是用來衡量實時進程是否值得運行的。 非實時進程有兩種優先級,一種是靜態優先級,另一種是動態優先級。實時進程又增加了第三種優先級,實時優先級。優先級越高,得到CPU時間的機會也就越大。

15、標準庫函數和系統調用的區別?
系統調用:是操作系統爲用戶態運行的進程和硬件設備(如CPU、磁盤、打印機等)進行交互提供的一組接口,即就是設置在應用程序和硬件設備之間的一個接口層。inux內核是單內核,結構緊湊,執行速度快,各個模塊之間是直接調用的關係。linux系統上到下依次是用戶進程->linux內核->硬件。其中系統調用接口是位於Linux內核中的,整個linux系統從上到下可以是:用戶進程->系統調用接口->linux內核子系統->硬件,也就是說Linux內核包括了系統調用接口和內核子系統兩部分;或者從下到上可以是:物理硬件->OS內核->OS服務->應用程序,操作系統起到“承上啓下”作用,向下管理物理硬件,向上爲操作系服務和應用程序提供接口,這裏的接口就是系統調用了。
庫函數:把函數放到庫裏。是把一些常用到的函數編完放到一個lib文件裏,供別人用。別人用的時候把它所在的文件名用#include<>加到裏面就可以了。一類是c語言標準規定的庫函數,一類是編譯器特定的庫函數。
系統調用是爲了方便使用操作系統的接口,而庫函數則是爲了人們編程的方便。

16、系統如何將一個信號通知到進程?

內核給進程發送信號,是在進程所在的進程表項的信號域設置對應的信號的位。進程處理信號的時機就是從內核態即將返回用戶態度的時候。執行用戶自定義的信號處理函數的方法很巧妙。把該函數的地址放在用戶棧棧頂,進程從內核返回到用戶態的時候,先彈出信號處理函數地址,於是就去執行信號處理函數了,然後再彈出,纔是返回進入內核時的狀態。

17. fork()一子進程程後父進程的全局變量能不能使用?

fork後子進程將會擁有父進程的幾乎一切資源,父子進程的都各自有自己的全局變量。不能通用,不同於線程。對於線程,各個線程共享全局變量。

18. 請畫出socket通信連接過程

 

19. 請用socket消息隊列實現“同步非阻塞”和“異步阻塞”兩種模式,並指出兩者的差別和優劣

http://blog.csdn.net/yongchurui/article/details/12780653

四、網絡編程

1. TCP頭大小,包含字段?三次握手,四次斷開描述過程,都有些什麼狀態。狀態變遷圖。TCP/IP收發緩衝區(2次)

頭部大小是20字節,包含數據如下:

 

三次握手:

 

四次釋放:

 

狀態變遷圖:

 

收發緩衝區:

 

2. 使用udp和tcp進程網絡傳輸,爲什麼tcp能保證包是發送順序,而udp無法保證?

因爲TCP發送的數據包是按序號發送,有確認機制和丟失重傳機制,而udp是不可靠的發送機制,發送的對應端口的數據包不是按順序發送的。

3. epoll哪些觸發模式,有啥區別?(必須非常詳盡的解釋水平觸發和邊緣觸發的區別,以及邊緣觸發在編程中要做哪些更多的確認)

epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的模式,ET是“高速”模式。LT模式下,只要這個fd還有數據可讀,每次epoll_wait都會返回它的事件,提醒用戶程序去操作,而在ET(邊緣觸發)模式中,它只會提示一次,直到下次再有數據流入之前都不會再提示了,無論fd中是否還有數據可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀光,也就是說一直讀到read的返回值小於請求值。

也就是說在LT模式的情況下一定要確認收發的數據包的buffer是不是足夠大如果收發數據包大小大於buffer的大小的時候就可能會出現數據丟失的情況。

4. tcp與udp的區別(必問)爲什麼TCP要叫做數據流?

1).基於連接與無連接

2).對系統資源的要求(TCP較多,UDP少)

3).UDP程序結構較簡單

4).流模式與數據報模式

5).TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證

6).TCP有擁塞控制和流量控制,UDP沒有

TCP提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之後才能傳輸數據。TCP提供超時重發,丟棄重複數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。

是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快

5.流量控制和擁塞控制的實現機制

網絡擁塞現象是指到達通信子網中某一部分的分組數量過多,使得該部分網絡來不及處理,以致引起這部分乃至整個網絡性能下降的現象,嚴重時甚至會導致網絡通信業務陷入停頓,即出現死鎖現象。擁塞控制是處理網絡擁塞現象的一種機制。數據的傳送與接收過程當中很可能出現收方來不及接收的情況,這時就需要對發方進行控制,以免數據丟失。

6. 滑動窗口的實現機制

滑動窗口機制,窗口的大小並不是固定的而是根據我們之間的鏈路的帶寬的大小,這個時候鏈路是否擁護塞。接受方是否能處理這麼多數據了。  滑動窗口協議,是TCP使用的一種流量控制方法。該協議允許發送方在停止並等待確認前可以連續發送多個分組。由於發送方不必每發一個分組就停下來等待確認,因此該協議可以加速數據的傳輸。 

7.epoll和select的區別?

1)select在一個進程中打開的最大fd是有限制的,由FD_SETSIZE設置,默認值是2048。不過 epoll則沒有這個限制,內存越大,fd上限越大,1G內存都能達到大約10w左右。

2)select的輪詢機制是系統會去查找每個fd是否數據已準備好,當fd很多的時候,效率當然就直線下降了,epoll採用基於事件的通知方式,一旦某個fd數據就緒時,內核會採用類似callback的回調機制,迅速激活這個文件描述符,高效。 

3)select還是epoll都需要內核把FD消息通知給用戶空間,epoll是通過內核於用戶空間mmap同一塊內存實現的,而select則做了不必要的拷貝

8. 網絡中,如果客戶端突然掉線或者重啓,服務器端怎麼樣才能立刻知道?

若客戶端掉線或者重新啓動,服務器端會收到復位信號,每一種tcp/ip得實現不一樣,控制機制也不一樣。

9. TTL是什麼?有什麼用處,通常那些工具會用到它?ping?traceroute? ifconfig? netstat?

TTL是Time To Live,每經過一個路由就會被減去一,如果它變成0,包會被丟掉。它的主要目的是防止包在有迴路的網絡上死轉,浪費網絡資源。ping和traceroute用到它。

10.linux的五種IO模式/異步模式.

1)同步阻塞I/O

2)同步非阻塞I/O

3)同步I/O複用模型

4) 同步信號驅動I/O

5) 異步I/O模型

 

11. 請說出http協議的優缺點.

1.支持客戶/服務器模式。2.簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑,通信速度很快。3.靈活:HTTP允許傳輸任意類型的數據對象。4.無連接:無連接的含義是限制每次連接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開連接。採用這種方式可以節省傳輸時間。5.無狀態:HTTP協議是無狀態協議。無狀態是指協議對於事務處理沒有記憶能力。缺少狀態意味着如果後續處理需要前面的信息,則它必須重傳,導致每次連接傳送的數據量增大。缺點就是不夠安全,可以使用https完成使用

12.NAT類型,UDP穿透原理。

1)Fullcone NAT (全克隆nat):一對一NAT一旦一個內部地址(iAddr:port1)映射到外部地址(eAddr:port2)。

2)Address-Restricted coneNAT(地址受限克隆nat):任意外部主機(hostAddr:any)都能通過給eAddr:port2發包到達iAddr:port1的前提是:iAddr:port1之前發送過包到hostAddr:any."any"也就是說端口不受限制

3). Port-Restricted cone NAT:內部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有發自iAddr:port1的包都經eAddr:port2向外發送。一個外部主機(hostAddr:port3)能夠發包到達iAddr:port1的前提是:iAddr:port1之前發送過包到hostAddr:port3.

4). Symmetric NAT(對稱NAT):同內部IP與port的請求到一個特定目的地的IP地址和端口,映射到一個獨特的外部來源的IP地址和端口。同一個內部主機發出一個信息包到不同的目的端,不同的映射使用外部主機收到了一封包從一個內部主機可以送一封包回來

 

13.大規模連接上來,併發模型怎麼設計

Epoll+線程池(epoll可以採用libevent處理)

14.tcp三次握手的,accept發生在三次握手哪個階段?

三次握手:C----->SYN K

              S------>ACK K+1 SYN J

              C------->ACK J+1   

              DONE!

client 的 connect  引起3次握手

server 在socket, bind, listen後,阻塞在accept,三次握手完成後,accept返回一個fd,

16.流量控制與擁塞控制的區別,節點計算機怎樣感知網絡擁塞了?

擁塞控制是把整體看成一個處理對象的,流量控制是對單個的節點。

感知的手段應該不少,比如在TCP協議裏,TCP報文的重傳本身就可以作爲擁塞的依據。依據這樣的原理, 應該可以設計出很多手段。

 

五、算法和數據結構

1.給定一個單向鏈表(長度未知),請設計一個既節省時間又節省空間的算法來找出該鏈表中的倒數第m個元素。實現這個算法,併爲可能出現的特例情況安排好處理措施。“倒數第m個元素”是這樣規定的:當m=0時,鏈表的最後一個元素將被返回。

解決問題方法思路如下:

方法一、如果我們知道鏈表的長度n,查找倒數第m個元素,也就是查找正序的第(n -  m)個元素(這裏的序號只是爲了分析,可能跟題目不一定正確的符合)。那麼這樣來說就簡單很多。首先遍歷鏈表得到鏈表長度,然後重新遍歷一次,查找正數第(n-m)個元素。時間複雜度大約是O(2n)。

方法二、我們是不是可以提供一個輔助存儲空間,是的我們在遍歷到鏈表結束的時候可以回溯到倒數第m個元素。比如用一個支持隨機訪問的容器記錄鏈表每一個節點的地址。那麼這樣的就可以只遍歷一次鏈表就能得到結果。時間複雜度大約是O(n),但是我們是用空間換取時間的,輔助存儲空間的大小由m決定,如果m過大也是不可取的。

方法三、頭結點指針爲當前指針,尾節點指針爲拖後指針。開始的時候當前指針和拖後指針初始化爲鏈表的頭結點,首先我們讓當前指針遍歷到第m個元素,拖後指針不變;然後同步更新當前指針和拖後指針;直到當前指針爲鏈表結尾。這樣我們就能保證當前指針和拖尾指針之間的距離是m。

代碼如下:

Node* FindMToLastNode(Node* pHead, int m)  {  

    // 查找到第m個元素  

    Node* pCurrent = pHead;  

    for (int i = 0; i < m; ++i)  

    {  

        if (pCurrent)  

        {  

            pCurrent = pCurrent->next;  

        }  

        else  

        {  

            return NULL;  

        }  

    }  

  

    Node* pFind = pHead;  

    while (pCurrent)   {  

        pFind        = pFind->next;  

        pCurrent    = pCurrent->next;  

    }   

    return pFind;  

2. 給定一個單向鏈表(長度未知),請遍歷一次就找到中間的指針,假設該鏈表存儲在只讀存儲器,不能被修改

設置兩個指針,一個每次移動兩個位置,一個每次移動一個位置,當第一個指針到達尾節點時,第二個指針就達到了中間節點的位置

處理鏈表問題時,”快行指針“是一種很常見的技巧,快行指針指的是同時用兩個指針來迭代訪問鏈表,只不過其中一個比另一個超前一些。快指針往往先行幾步,或與慢指針相差固定的步數。

node *create()  {  

    node *p1, *p2, *head;  

    int cycle = 1, x;  

    head = (node*)malloc(sizeof(node));  

    p1 = head;  

    while (cycle)  

    {         

        cout << "please input an integer: ";  

        cin >> x;  

        if (x != 0)  

        {  

            p2 = (node*)malloc(sizeof(node));  

            p2->data = x;  

            p1->next = p2;  

            p1 = p2;  

        }  

  

        else  

        {  

            cycle = 0;  

        }  

    }  

    head = head->next;  

    p1->next = NULL;  

    return head;  

}  

void findmid(node* head)  {  

    node *p1, *p2, *mid;  

    p1 = head;  

    p2 = head;  

  

    while (p1->next->next != NULL)  

    {     

        p1 = p1->next->next;  

        p2 = p2->next;  

        mid = p2;  

    }     

}

3. 將一個數組生成二叉排序樹

排序,選數組中間的一個元素作爲根節點,左邊的元素構造左子樹,右邊的節點構造有子樹。

4. 查找數組中第k大的數字?

因爲快排每次將數組劃分爲兩組加一個樞紐元素,每一趟劃分你只需要將k與樞紐元素的下標進行比較,如果比樞紐元素下標大就從右邊的子數組中找,如果比樞紐元素下標小從左邊的子數組中找,如果一樣則就是樞紐元素,找到,如果需要從左邊或者右邊的子數組中再查找的話,只需要遞歸一邊查找即可,無需像快排一樣兩邊都需要遞歸,所以複雜度必然降低。

最差情況如下:假設快排每次都平均劃分,但是都不在樞紐元素上找到第k大第一趟快排沒找到,時間複雜度爲O(n),第二趟也沒找到,時間複雜度爲O(n/2),第k趟找到,時間複雜度爲O(n/2k),所以總的時間複雜度爲O(n(1+1/2+....+1/2k))=O(n),明顯比冒泡快,雖然遞歸深度是一樣的,但是每一趟時間複雜度降低。

 5. 紅黑樹的定義和解釋?B樹的基本性質?

紅黑樹:

性質1. 節點是紅色或黑色。
性質2. 根節點是黑色。
性質3. 每個葉子結點都帶有兩個空的黑色結點(被稱爲黑哨兵),如果一個結點n的只有一個左孩子,那麼n的右孩子是一個黑哨兵;如果結點n只有一個右孩子,那麼n的左孩子是一個黑哨兵。
性質4 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
性質5. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。

B樹:

       1.所有非葉子結點至多擁有兩個兒子(Left和Right);

       2.所有結點存儲一個關鍵字;

       3.非葉子結點的左指針指向小於其關鍵字的子樹,右指針指向大於其關鍵字的子樹;

 

6. 常見的加密算法?

對稱式加密就是加密和解密使用同一個密鑰。
非對稱式加密就是加密和解密所使用的不是同一個密鑰,通常有兩個密鑰,稱爲“公鑰”和“私鑰”,它們兩個必需配對使用。
DES:對稱算法,數據加密標準,速度較快,適用於加密大量數據的場合;
MD5的典型應用是對一段Message產生fingerprint(指紋),以防止被“篡改”。
RSA是第一個既能用於數據加密也能用於數字簽名的算法。

7. https?

HTTP下加入SSL層,HTTPS的安全基礎是SSL。

8.有一個IP庫,給你一個IP,如何能夠快速的從中查找到對應的IP段?不用數據庫如何實現?要求省空間
9.簡述一致性hash算法。

1)首先求memcached服務器(節點)的哈希值,並將其配置到0~232的圓(continuum)。

2)然後採用同樣的方法求出存儲數據的鍵的哈希值,並映射到相同的圓上。

3)然後從數據映射到的位置開始順時針查找,將數據保存到找到的第一個服務器上。如果超過232仍然找不到服務器,就會保存到第一臺memcached服務器上。
11.描述一種hashtable的實現方法

1) 除法散列法: p ,令 h(k ) = kmod p ,這裏, p 如果選取的是比較大的素數,效果比較好。而且此法非常容易實現,因此是最常用的方法。最直觀的一種,上圖使用的就是這種散列法,公式: index = value % 16,求模數其實是通過一個除法運算得到的。

2) 平方散列法 :求index頻繁的操作,而乘法的運算要比除法來得省時。公式: index = (value * value) >> 28 (右移,除以2^28。記法:左移變大,是乘。右移變小,是除)

3) 數字選擇法:如果關鍵字的位數比較多,超過長整型範圍而無法直接運算,可以選擇其中數字分佈比較均勻的若干位,所組成的新的值作爲關鍵字或者直接作爲函數值。

4) 斐波那契(Fibonacci)散列法:平方散列法的缺點是顯而易見的,通過找到一個理想的乘數index = (value * 2654435769) >> 28

衝突處理:令數組元素個數爲 S ,則當 h(k) 已經存儲了元素的時候,依次探查 (h(k)+i) mod S , i=1,2,3…… ,直到找到空的存儲單元爲止(或者從頭到尾掃描一圈仍未發現空單元,這就是哈希表已經滿了,發生了錯誤。當然這是可以通過擴大數組範圍避免的)。

12、各類樹結構的實現和應用

13、hash,任何一個技術面試官必問(例如爲什麼一般hashtable的桶數會取一個素數?如何有效避免hash結果值的碰撞)

不選素數的話可能會造成hash出值的範圍和原定義的不一致

14.什麼是平衡二叉樹?

左右子樹都是平衡二叉樹,而且左右子樹的深度差值的約對值不大於1。

15.數組和鏈表的優缺點

數組,在內存上給出了連續的空間。鏈表,內存地址上可以是不連續的,每個鏈表的節點包括原來的內存和下一個節點的信息(單向的一個,雙向鏈表的話,會有兩個)。

數組優於鏈表的:

A. 內存空間佔用的少。

B. 數組內的數據可隨機訪問,但鏈表不具備隨機訪問性。

C. 查找速度快

鏈表優於數組的:

A. 插入與刪除的操作方便。

B. 內存地址的利用率方面鏈表好。

C. 方便內存地址擴展。

17.最小堆插入,刪除編程實現

18. 4G的long型整數中找到一個最大的,如何做?

每次從磁盤上儘量多讀一些數到內存區,然後處理完之後再讀入一批。減少IO次數,自然能夠提高效率。分批讀入選取最大數,再對緩存的最大數進行快排。 

19. 有千萬個string在內存怎麼高速查找,插入和刪除?

對千萬個string做hash,可以實現高速查找,找到了,插入和刪除就很方便了。關鍵是如何做hash,對string做hash,要減少碰撞頻率。

20.100億個數,求最大的1萬個數,並說出算法的時間複雜度

在內存中維護一個大小爲10000的最小堆,每次從文件讀一個數,與最小堆的堆頂元素比較,若比堆頂元素大,則替換掉堆頂元素,然後調整堆。最後剩下的堆內元素即爲最大的1萬個數,算法複雜度爲O(NlogN)

21.設計一個洗牌的算法,並說出算法的時間複雜度。

(1)全局洗牌法

    a)首先生成一個數組,大小爲54,初始化爲1~54

    b)按照索引1到54,逐步對每一張索引牌進行洗牌,首先生成一個餘數 value = rand %54,那麼我們的索引牌就和這個餘數牌進行交換處理

    c)等多索引到54結束後,一副牌就洗好了

 (2)局部洗牌法:索引牌從1開始,到54結束。這一次索引牌只和剩下還沒有洗的牌進行交換, value = index + rand() %(54 - index)

算法複雜度是O(n)

22.請分別用遞歸和非遞歸方法,先序遍歷二叉樹

http://blog.csdn.net/pi9nc/article/details/13008511

24.其他各種排序方法

http://blog.csdn.net/hguisu/article/details/7776068

25.哈希表衝突解決方法?

常見的hash算法如下:

  1. 1.      數字分析法 2.平方取中法 3.分段疊加法4.除留餘數法 5.僞隨機法

解決衝突的方法:

  1. 1.      開放地址法

也叫散列法,主要思想是當出現衝突的時候,以關鍵字的結果值作爲key值輸入,再進行處理,依次直到衝突解決

線性地址再散列法

當衝突發生時,找到一個空的單元或者全表

二次探測再散列

衝突發生時,在表的左右兩側做跳躍式的探測

僞隨機探測再散列

  1. 2.      再哈希法

同時構造不同的哈希函數

  1. 3.      鏈地址法

將同樣的哈希地址構造成一個同義詞的鏈表

  1. 4.      建立公共溢出區

建立一個基本表和溢出區,凡是和基本元素髮生衝突都填入溢出區

六、系統架構

1.設計一個服務,提供遞增的SessionID服務,要求保證服務的高可靠性,有哪些方案?集中式/非集中式/分佈式

2.多臺服務器要執行計劃任務,但只有拿到鎖的任務才能執行,有一箇中心服務器來負責分配鎖,但要保證服務的高可靠性。

3.如何有效的判斷服務器是否存活?服務器是否踢出集羣的決策如何產生?

4.兩個服務器如何在同一時刻獲取同一數據的時候保證只有一個服務器能訪問到數據?

可以採用隊列進行處理,寫一個隊列接口保證同一時間只有一個進程能夠訪問到數據,或者對於存取數據庫的來說,數據庫也是可以加鎖處理的

5.  編寫高效服務器程序,需要考慮的因素

性能對服務器程序來說是至關重要的了,畢竟每個客戶都期望自己的請求能夠快速的得到響應並處理。那麼影響服務器性能的首要因素應該是:

(1)系統的硬件資源,比如說CPU個數,速度,內存大小等。不過由於硬件技術的飛速發展,現代服務器都不缺乏硬件資源。因此,需要考慮的主要問題是如何從“軟環境”來提升服務器的性能。

服務器的”軟環境“

(2)一方面是指系統的軟件資源,比如操作系統允許用戶打開的最大文件描述符數量

(3)另一方面指的就是服務器程序本身,即如何從編程的角度來確保服務器的性能。

主要就要考慮大量併發的處理這涉及到使用進程池或線程池實現高效的併發模式(半同步/半異步和領導者/追隨者模式),以及高效的邏輯處理方式--有限狀態機內存的規劃使用比如使用內存池,以空間換時間,被事先創建好,避免動態分配,減少了服務器對內核的訪問頻率,數據的複製,服務器程序還應該避免不必要的數據複製,尤其是當數據複製發生在用戶空間和內核空間之間時。如果內核可以直接處理從socket或者文件讀入的數據,則應用程序就沒必要將這些數據從內核緩衝區拷貝到應用程序緩衝區中。這裏所謂的“直接處理”,是指應用程序不關心這些數據的具體內容是什麼,不需要對它們作任何分析。比如說ftp服務器,當客戶請求一個文件時,服務器只需要檢測目標文件是否存在,以及是否有權限讀取就可以了,不需要知道這個文件的具體內容,這樣的話ftp服務器就不需要把目標文件讀入應用程序緩衝區然後調用send函數來發送,而是直接使用“零拷貝”函數sendfile直接將其發送給客戶端。另外,用戶代碼空間的數據賦值也應該儘可能的避免複製。當兩個工作進程之間需要傳遞大量的數據時,我們就應該考慮使用共享內存來在他們直接直接共享這些數據,而不是使用管道或者消息隊列來傳遞。上下文切換和鎖:併發程序必須考慮上下文的切換問題,即進程切換或線程切換所導致的系統開銷。即時I/O密集型服務器也不應該使用過多的工作線程(或工作進程),否則進程間切換將佔用大量的CPU時間,服務器真正處理業務邏輯的CPU時間比重就下降了。因此爲每個客戶連接都創建一個工作線程是不可取的。應該使用某種高效的併發模式。(半同步半異步或者說領導者追隨者模式)另一個問題就是共享資源的加鎖保護。鎖通常被認爲是導致服務器效率低下的一個因素,因爲由他引入的代碼不僅不處理業務邏輯,而且需要訪問內核資源,因此如果服務器有更好的解決方案,應該儘量避免使用鎖。或者說服務器一定非要使用鎖的話,儘量使用細粒度的鎖,比如讀寫鎖,當工作線程都只讀一塊內存區域時,讀寫鎖不會增加系統開銷,而只有當需要寫時才真正需要鎖住這塊內存區域。對於高峯和低峯的伸縮處理,適度的緩存。

6. QQ飛車新用戶註冊時,如何判斷新註冊名字是否已存在?(數量級:幾億) 

可以試下先將用戶名通過編碼方式轉換,如轉換64位整型。然後設置N個區間,每個區間爲2^64/N的大小。對於新的用戶名,先通過2分尋找該用戶名屬於哪個區間,然後在在這個區間,做一個hash。對於不同的時間複雜度和內存要求可以設置不同N的大小~

 

加一些基礎的技術面試之外的職業素養的面試問題

1.你在工作中犯了個錯誤,有同事打你小報告,你如何處理? 

a.同事之間應該培養和形成良好的同事關係,就是要互相支持而不是互相拆臺,互相學習,互相幫助,共同進步。

b.如果小報告裏邊的事情都是事實也就是說確實是本人做的不好不對的方面,那麼自己應該有則改之,提高自己。如果小報告裏邊的事

情全部不是事實,就是說確實誣陷,那麼應該首先堅持日久見人心的態度,持之以恆的把本職工作做好,然後在必要的時候通過適當的

方式和領導溝通,相信領導會知道的。

2.你和同事合作完成一個任務,結果任務錯過了截止日期,你如何處理?

3.職業規劃? 

4.離職原因? 

5. 項目中遇到的難題,你是如何解決的?

A.時間 b要求 c.方法

 

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