linux C/C++伺服器後臺開發面試題總結
一、程式語言
1.根據熟悉的語言,談談兩種語言的區別?
主要淺談下C/C++和PHP語言的區別:
1)PHP弱型別語言,一種指令碼語言,對資料的型別不要求過多,較多的應用於Web應用開發,現在好多網際網路開發公司的主流web後臺開發語言,主要框架為mvc模型,如smarty,yaf,升級的PHP7速度較快,對伺服器的壓力要小很多,在新浪微博已經有應用,對比很明顯。
2)C/C++開發語言,C語言更偏向硬體底層開發,C++語言是目前為止我認為語法內容最多的一種語言。C/C++在執行速度上要快很多,畢竟其他型別的語言大都是C開發的,更多應用於網路程式設計和嵌入式程式設計。
2.volatile是幹啥用的,(必須將
1)訪問暫存器比訪問記憶體單元要快,編譯器會優化減少記憶體的讀取,可能會讀髒資料。宣告變數為volatile,編譯器不再對訪問該變數的程式碼優化,仍然從記憶體讀取,使訪問穩定。
總結:volatile關鍵詞影響編譯器編譯的結果,用volatile宣告的變量表示該變數隨時可能發生變化,與該變數有關的運算,不再編譯優化,以免出錯。
2)使用例項如下(區分C程式設計師和嵌入式系統程式設計師的最基本的問題。):
並行裝置的硬體暫存器(如:狀態暫存器)
一箇中斷服務子程式中會訪問到的非自動變數(Non-automatic variables)
多執行緒應用中被幾個任務共享的變數
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;
}
3.static const等等的用法,(能說出越多越好)(重點)
² 首先說說const的用法(絕對不能說是常數)
1)在定義的時候必須進行初始化
2)指標可以是const 指標,也可以是指向const物件的指標
3)定義為const的形參,即在函式內部是不能被修改的
4)類的成員函式可以被宣告為常成員函式,不能修改類的成員變數
5)類的成員函式可以返回的是常物件,即被const宣告的物件
6)類的成員變數是常成員變數不能在宣告時初始化,必須在建構函式的列表裡進行初始化
(注:千萬不要說const是個常數,會被認為是外行人的!!!!哪怕說個只讀也行)
下面的宣告都是什麼意思?
const int a; a是一個常整型數
int const a; a是一個常整型數
const int *a; a是一個指向常整型數的指標,整型數是不可修改的,但指標可以
int * const a; a為指向整型數的常指標,指標指向的整型數可以修改,但指標是不可修改的
int const * a const; a是一個指向常整型數的常指標,指標指向的整型數是不可修改的,同時指標也是不可修改的
通過給優化器一些附加的資訊,使用關鍵字const也許能產生更緊湊的程式碼。合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的引數,防止其被無意的程式碼修改。簡而言之,這樣可以減少bug的出現。
Const如何做到只讀?
這些在編譯期間完成,對於內建型別,如int,編譯器可能使用常數直接替換掉對此變數的引用。而對於結構體不一定。
² 再說說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;
}
10. 用預處理指令#define宣告一個常數,用以表明1年中有多少秒
#define SECONDS_PER_YEAR (60 * 60 * 24 *365)UL
11. 前處理器標識#error的目的是什麼?
丟擲錯誤提示,標識外部巨集是否被定義!
12. 嵌入式系統中經常要用到無限迴圈,你怎麼樣用C編寫死迴圈呢?
記住這是第一方案!!!!
while(1)
{
}
一些程式設計師更喜歡如下方案:
for(;;){
}
組合語言的無線迴圈是:
Loop:
...
goto Loop;
13. 用變數a給出下面的定義
一個有10個指標的陣列,該指標指向一個函式,該函式有一個整型引數並返回一個整型數int (*a[10])(int);
14. 中斷是嵌入式系統中重要的組成部分,這導致了很多編譯開發商提供一種擴充套件—讓標準C支援中斷。具代表事實是,產生了一個新的關鍵字__interrupt
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;
}
26. C語言同意一些令人震驚的結構,下面的結構是合法的嗎,如果是它做些什麼?
int a = 5, b = 7, c;
c = a+++b; 等同於c = a++ + b;
因此, 這段程式碼持行後a = 6, b = 7, c = 12。
27. 用struct關鍵字與class關鍵定義類以及繼承的區別
(1)定義類差別
struct關鍵字也可以實現類,用class和struct關鍵字定義類的唯一差別在於預設訪問級別:預設情況下,struct成員的訪問級別為public,而class成員的為private。語法使用也相同,直接將class改為struct即可。
(2)繼承差別
使用class保留字的派生類預設具有private繼承,而用struct保留字定義的類某人具有public繼承。其它則沒有任何區別。
主要點就兩個:預設的訪問級別和預設的繼承級別 class都是private
28.派生類與虛擬函式概述
(1) 派生類繼承的函式不能定義為虛擬函式。虛擬函式是希望派生類重新定義。如果派生類沒有重新定義某個虛擬函式,則在呼叫的時候會使用基類中定義的版本。
(2)派生類中函式的宣告必須與基類中定義的方式完全匹配。
(3) 基類中宣告為虛擬函式,則派生類也為虛擬函式。
29. 虛擬函式與純虛擬函式區別
1)虛擬函式在子類裡面也可以不過載的;但純虛必須在子類去實現
2)帶純虛擬函式的類叫虛基類也叫抽象類,這種基類不能直接生成物件,只能被繼承,重寫虛擬函式後才能使用,執行時動態動態繫結!
30.深拷貝與淺拷貝
淺拷貝:
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=函式,友元函式等等
作用:
- 隱藏實現細節,程式碼能夠模組化;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 巨集一次只能定義一個。
41.介紹一下函式的過載
過載是在不同型別上作不同運算而又用同樣的名字的函式。過載函式至少在引數個數,引數型別,或引數順序上有所不同。
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)呼叫靜態成員函式,可以用成員訪問操作符(.)和(->)為一個類的物件或指向類物件的指呼叫靜態成員函式。
65.sizeof一個類求大小(注意成員變數,函式,虛擬函式,繼承等等對大小的影響)
http://www.cnblogs.com/BeyondTechnology/archive/2010/09/21/1832369.html
66請用C/C++實現字串反轉(不呼叫庫函式)”abc”型別的
char *reverse_str(char *str) {
if(NULL == str) { //字串為空直接返回
return str;
}
char *begin;
char *end;
begin = end = str;
while(*end != '\0') { //end指向字串的末尾
end++;
}
--end;
char temp;
while(begin < end) { //交換兩個字元
temp = *begin;
*begin = *end;
*end = temp;
begin++;
end--;
}
return str; //返回結果
}
67.寫一個函式,將字串翻轉,翻轉方式如下:“I am astudent”反轉成“student a am I”,不借助任何庫函式
1 #include "stdio.h"
2 #include <iostream>
3 using namespace std;
4
5 void revesal(char * start, char* end){
6 char *temp_s = start;
7 char *temp_e = end;
8 while(temp_s < temp_e){
9 char temp= *temp_s;
10 *temp_s= *temp_e;
11 *temp_e = temp;
12 ++temp_s;
13 --temp_e;
14 }
15 return;
16 }
17
18 void revesal_str(char *str){
19 if(str == NULL){
20 return;
21 }
22
23 char *start = str;
24 char *end = str;
25
26 while(*++end !='\0');
27 revesal(start, end-1);
28 cout << str << endl;
29 char *sub_start = str;
30 while(start < end + 1 ){
31 if(*start == ' ' || *start == '\0'){
32 char *temp = start - 1;
33 revesal(sub_start,temp);
34 while(*++start ==' ');
35 sub_start = start;
36 continue;
37 }
38 ++start;
39 }
40 }
68.解構函式可以丟擲異常嗎?為什麼不能丟擲異常?除了資源洩露,還有其他需考慮的因素嗎?
C++標準指明解構函式不能、也不應該丟擲異常。C++異常處理模型最大的特點和優勢就是對C++中的面向物件提供了最強大的無縫支援。那麼如果物件在執行期間出現了異常,C++異常處理模型有責任清除那些由於出現異常所導致的已經失效了的物件(也即物件超出了它原來的作用域),並釋放物件原來所分配的資源,這就是呼叫這些物件的解構函式來完成釋放資源的任務,所以從這個意義上說,解構函式已經變成了異常處理的一部分。
1)如果解構函式丟擲異常,則異常點之後的程式不會執行,如果解構函式在異常點之後執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源洩漏的問題。
2)通常異常發生時,c++的機制會呼叫已經構造物件的解構函式來釋放資源,此時若解構函式本身也丟擲異常,則前一個異常尚未處理,又有新的異常,會造成程式崩潰的問題。
69.拷貝建構函式作用及用途?什麼時候需要自定義拷貝建構函式?
一般如果建構函式中存在動態記憶體分配,則必須定義拷貝建構函式。否則,可能會導致兩個物件成員指向同一地址,出現“指標懸掛問題”。
70. 100萬個32位整數,如何最快找到中位數。能保證每個數是唯一的,如何實現O(N)演算法?
1).記憶體足夠時:快排
2).記憶體不足時:分桶法:化大為小,把所有數劃分到各個小區間,把每個數對映到對應的區間裡,對每個區間中數的個數進行計數,數一遍各個區間,看看中位數落在哪個區間,若夠小,使用基於記憶體的演算法,否則繼續劃分
71. OFFSETOF(s, m)的巨集定義,s是結構型別,m是s的成員,求m在s中的偏移量。
#define OFFSETOF(s, m) size_t(&((s*)0)->m)
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變數、
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把它分為兩部分: