面試題(C++方向)

第一篇

1、在函數內定義一個字符數組,用gets函數輸入字符串的時候,如果輸入越界,爲什麼程序會崩潰?

答:因爲gets無法截斷數組越界部分,會將所有輸入都寫入內存,這樣越界部分就可能覆蓋其他內容,造成程序崩潰。

 

2、C++中引用與指針的區別

答:聯繫:引用是變量的別名,可以將引用看做操作受限的指針;

區別:

1) 指針是一個實體,而引用僅是個別名;

2)引用只能在定義時必須初始化,指針可以不初始化爲空;

3)引用初始化之後其地址就不可改變(即始終作該變量的別名直至銷燬,即從一而終。注意:並不表示引用的值不可變,因爲只要所指向的變量值改變。引用的值也就改變了),但指針所指地址是不可變的;如下:

int m=23,n=13;

int& a=m;

a=12; //合法,相當於修改m=12

a=n;//合法,相當於修改m=13

3、C/C++程序的內存分區

答:其實C和C++的內存分區還是有一定區別的,但此處不作區分:

1)、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其
操作方式類似於數據結構中的棧。
2)、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回
收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。
3)、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的
全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另
一塊區域。 - 程序結束後由系統釋放。
4)、文字常量區 —常量字符串就是放在這裏的。 程序結束後由系統釋放
5)、程序代碼區—存放函數體的二進制代碼。

棧區與堆區的區別:

1)堆和棧中的存儲內容:棧存局部變量、函數參數等。堆存儲使用new、malloc申請的變量等;

2)申請方式:棧內存由系統分配,堆內存由自己申請;

3)申請後系統的響應:棧——只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,否則將報異常提示棧溢出。
堆——首先應該知道操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表 中刪除,並將該結點的空間分配給程序;

4)申請大小的限制:Windows下棧的大小一般是2M,堆的容量較大;

5)申請效率的比較:棧由系統自動分配,速度較快。堆使用new、malloc等分配,較慢;

總結:棧區優勢在處理效率,堆區優勢在於靈活;

內存模型:自由區、靜態區、動態區;

根據c/c++對象生命週期不同,c/c++的內存模型有三種不同的內存區域,即:自由存儲區,動態區、靜態區。

自由存儲區:局部非靜態變量的存儲區域,即平常所說的棧;

動態區: 用new ,malloc分配的內存,即平常所說的堆;

靜態區:全局變量,靜態變量,字符串常量存在的位置;

注:代碼雖然佔內存,但不屬於c/c++內存模型的一部分;

 

4、快速排序的思想、時間複雜度、實現以及優化方法

答:快速排序的三個步驟:
(1)選擇基準:在待排序列中,按照某種方式挑出一個元素,作爲 "基準"(pivot);
(2)分割操作:以該基準在序列中的實際位置,把序列分成兩個子序列。此時,在基準左邊的元素都比該基準小,在基準右邊的元素都比基準大;
(3)遞歸地對兩個序列進行快速排序,直到序列爲空或者只有一個元素。

基準的選擇:

對於分治算法,當每次劃分時,算法若都能分成兩個等長的子序列時,那麼分治算法效率會達到最大。

即:同一數組,時間複雜度最小的是每次選取的基準都可以將序列分爲兩個等長的;時間複雜度最大的是每次選擇的基準都是當前序列的最大或最小元素;

快排代碼實現:

我們一般選擇序列的第一個作爲基數,那麼快排代碼如下:

void quicksort(vector<int> &v,int left, int right)
{  
	if(left < right)//false則遞歸結束
	{    
		int key=v[left];//基數賦值
		int low = left;                
		int high = right;   
		while(low < high)	//當low=high時,表示一輪分割結束
		{                        
			while(low < high && v[high] >= key)//v[low]爲基數,從後向前與基數比較
			{                                
				high--;                        
			}
			swap(v[low],v[high]);

			while(low < high && v[low] <= key)//v[high]爲基數,從前向後與基數比較
			{                                
				low++;                        
			}      
			swap(v[low],v[high]);
		}                 
		//分割後,對每一分段重複上述操作
		quicksort(v,left,low-1);               
		quicksort(v,low+1,right);
	}
}

 

注:上述數組或序列v必須是引用類型的形參,因爲後續快排結果需要直接反映在原序列中;

優化:

上述快排的基數是序列的第一個元素,這樣的對於有序序列,快排時間複雜度會達到最差的o(n^2)。所以,優化方向就是合理的選擇基數

常見的做法“三數取中”法(序列太短還要結合其他排序法,如插入排序、選擇排序等),如下:

①當序列區間長度小於 7 時,採用插入排序;
②當序列區間長度小於 40 時,將區間分成2段,得到左端點、右端點和中點,我們對這三個點取中數作爲基數;
③當序列區間大於等於 40 時,將區間分成 8 段,得到左三點、中三點和右三點,分別再得到左三點中的中數、中三點中的中數和右三點中的中數,再將得到的三個中數取中數,然後將該值作爲基數。
具體代碼只是在上一份的代碼中將“基數賦值”改爲①②③對應的代碼即可:

		int key=v[left];//基數賦值
		if(right-left+1<=7){
			insertion_sort(v,left,right);//插入排序
			return;
		}else if(right-left+1<=8){
			key=SelectPivotOfThree(v,left,right);//三個取中
		}else{
			//三組三個取中,再三個取中(使用4次SelectPivotOfThree,此處不具體展示)
		}

需要調用的函數:

void insertion_sort(vector<int> &unsorted,int left, int right)  //插入排序算法      
{        
	for (int i = left+1; i <= right; i++)        
	{        
		if (unsorted[i - 1] > unsorted[i])        
		{       
			int temp = unsorted[i];       
			int j = i;       
			while (j > left && unsorted[j - 1] > temp)
         
			{         
				unsorted[j] = unsorted[j - 1];        
				j--;      	
			}   
			unsorted[j] = temp;    
		}    
	}   
}

int SelectPivotOfThree(vector<int> &arr,int low,int high)  //三數取中,同時將中值移到序列第一位
{  
    int mid = low + (high - low)/2;//計算數組中間的元素的下標  
  
    //使用三數取中法選擇樞軸
    if (arr[mid] > arr[high])//目標: arr[mid] <= arr[high]  
    {  
        swap(arr[mid],arr[high]);
    }  
    if (arr[low] > arr[high])//目標: arr[low] <= arr[high]  
    {  
        swap(arr[low],arr[high]);
    }  
    if (arr[mid] > arr[low]) //目標: arr[low] >= arr[mid]  
    {  
        swap(arr[mid],arr[low]);
    }  
    //此時,arr[mid] <= arr[low] <= arr[high]  
    return arr[low];  
    //low的位置上保存這三個位置中間的值  
    //分割時可以直接使用low位置的元素作爲樞軸,而不用改變分割函數了  
}  

 

這裏需要注意的有兩點:

 

①插入排序算法實現代碼;

②三數取中函數不僅僅要實現取中,還要將中值移到最低位,從而保證原分割函數依然可用。

 

5、 IO模型——IO多路複用機制

答:預備知識介紹:

IO模型有4中:同步阻塞IO、同步非阻塞IO、異步阻塞IO、異步非阻塞IO;IO多路複用屬於IO模型中的異步阻塞IO模型,在服務器高性能IO構建中常常用到。

上述幾個模型原理如下圖:

同步阻塞IO:                                                           同步非阻塞IO:                                            IO多路複用(異步阻塞IO):

如上:同步異步是表示服務端的,阻塞非阻塞是表示用戶端,所以可解釋爲什麼IO多路複用(異步阻塞)常用於服務器端的原因;

文件描述符(FD,又叫文件句柄):描述符就是一個數字,它指向內核中的一個結構體(文件路徑,數據區等屬性)。具體來源:Linux內核將所有外部設備都看作一個文件來操作,對文件的操作都會調用內核提供的系統命令,返回一個fd(文件描述符)。

 

下面開始介紹IO多路複用:

(1)I/O多路複用技術通過把多個I/O的阻塞複用到同一個select、poll或epoll的阻塞上,從而使得系統在單線程的情況下可以同時處理多個客戶端請求。與傳統的多線程/多進程模型比,I/O多路複用的最大優勢是系統開銷小,系統不需要創建新的額外進程或者線程。

(2)select,poll,epoll本質上都是同步I/O,因爲他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。

(3)I/O多路複用的主要應用場景如下:

服務器需要同時處理多個處於監聽狀態或者多個連接狀態的套接字;
服務器需要同時處理多種網絡協議的套接字;

(4)目前支持I/O多路複用的系統調用有 select,poll,epoll,epoll與select的原理比較類似,但epoll作了很多重大改進,現總結如下:

①支持一個進程打開的文件句柄FD個數不受限制(爲什麼select的句柄數量受限制:select使用位域的方式來傳遞關心的文件描述符,因爲位域就有最大長度,在Linux下是1024,所以有數量限制);

②I/O效率不會隨着FD數目的增加而線性下降;

③epoll的API更加簡單;

(5)三種接口調用介紹:

①select函數調用格式:

#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
//返回值:就緒描述符的數目,超時返回0,出錯返回-1

②poll函數調用格式:

# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

③epoll函數格式(操作過程包括三個函數):

#include <sys/epoll.h>
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);

(6)作用:一定程度上替代多線程/多進程,減少資源佔用,保證系統運行的高效率;

 

更多細節待續……

 

6、常用的Linux命令

答:(1)查看CPU利用率:top

(2)查看當前目錄:pwd和ls(ls -a可以查看隱藏目錄)

(3)切換目錄:cd

(4)查看文件佔用磁盤大小:du和df

(5)創建文件夾:mkdir

(6)新建文件:touch

(7)查看文件:cat

(8)拷貝:cp  移動:mv  刪除:rm

(9)查看進程:ps,如ps aux

(10)刪除進程:kill -9 PID,注-9是參數

(11)程序運行時間:time,使用時在命令前添加time即可,如:time ./test,可得到三個時間:real 0m0.020s,user 0m0.000s,sys 0m0.018s

grep命令(重要的常用命令之一):常用於打開文本修改保存,類似打windows開開TXT文本並修改;

sed命令(常用重要命令之一):主要用於對文件的增刪改查;

awk命令(重要常用命令之一):取列是其擅長的;

find 命令(常與xargs命令配合):查找 -type 文件類型-name 按名稱查找-exec執行命令;

xargs命令:配合find/ls查找,將查找結果一條條的交給後續命令處理;

gdb調試工具:

要調試C/C++的程序,一般有如下幾個步驟:

①首先在編譯時,我們必須要把調試信息加到可執行文件中,編譯生成可執行文件-------> g++  -g hello.cpp -o hello;

②啓動GDB編譯hello程序----------> gdb hello;

③顯示源碼------------> l;

④開始調試:break 16——設置斷點在16行,break func——設置斷點在函數func()入口處,info break——查看斷點信息,n——單步運行,c——繼續運行程序,r——運行程序;p i——打印i的值,finish——退出程序,q——退出gdb。

 

7、C中變量的存儲類型有哪些?

 

答:c語言中的存儲類型有auto, extern, register, static 四種;

 

8、動態規劃的本質

答:動歸,本質上是一種劃分子問題的算法,站在任何一個子問題的處理上看,當前子問題的提出都要依據現有的類似結論,而當前問題的結論是後面問題求解的鋪墊。任何DP都是基於存儲的算法,核心是狀態轉移方程。 

 

9、實踐中如何優化MySQL

答:四條從效果上第一條影響最大,後面越來越小。 

① SQL語句及索引的優化
② 數據庫表結構的優化
③ 系統配置的優化
④ 硬件的優化 

 

10、 什麼情況下設置了索引但無法使用

答:① LIKE語句,模糊匹配
② OR語句
③ 數據類型出現隱式轉化(如varchar不加單引號的話可能會自動轉換爲int型) 

 

11、 SQL語句的優化

答:alter儘量將多次合併爲一次;

insert和delete也需要合併;

儘量使用union而不是or;

 

12.、數據庫索引的底層實現原理和優化

答:B樹,經過優化的B+樹。主要是在所有的葉子結點中增加了指向下一個葉子節點的指針,因此InnoDB建議爲大部分表使用默認自增的主鍵作爲主索引。

 

13、HTTP和HTTPS的主要區別

答:見另一文章解析:http://blog.csdn.net/xiongchao99/article/details/73381280#t8

 

14、 如何設計一個高併發的系統

答:① 數據庫的優化,包括合理的事務隔離級別、SQL語句優化、索引的優化;

② 使用緩存,儘量減少數據庫 IO;

③ 分佈式數據庫、分佈式緩存;

④ 服務器的負載均衡;

 

15. 兩條相交的單向鏈表,如何求他們的第一個公共節點

答:思想:

①如果兩個鏈表相交,則從相交點開始,後面的節點都相同,即最後一個節點肯定相同;
②從頭到尾遍歷兩個鏈表,並記錄鏈表長度,當二者的尾節點不同,則二者肯定不相交;
③尾節點相同,如果A長爲LA,B爲LB,如果LA>LB,則A前LA-LB個先跳過;

——更多如鏈表相關經典問題:求單向局部循環鏈表的入、將兩個有序鏈表合併合成一個有序鏈表、鏈表逆序、求倒數第K個節點,判斷是否有環等。

 

16、求單向局部循環鏈表的環入口

答:思路:

假如有快慢指針判斷一個鏈表有局部環,鏈表起點是A,環的入口是B,快慢指針在環中的相遇點是C。那麼按照原來的運動方向,有AB=CB,這是可以證明的結論。具體如下圖說明:

 

17、IP地址如何在數據庫中存儲

答:常有以下幾種存儲方式:

說明一下:int類型的num存儲在解碼時是這樣做的:

65=num%256;num=num/256;

120=num%256;num=num/256;

……

 

18、new/delete和malloc/free的底層實現

答:malloc和new的區別:

1)malloc與free是C++/C語言的標準庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存;

2)new 返回指定類型的指針,並且可以自動計算所需要大小。而 malloc 則必須要由程序員計算字節數,並且在返回後強行轉換爲實際類型的指針;

3)new/delete在對象創建的同時可以自動執行構造函數初始化,在對象在消亡之前會自動執行析構函數。而malloc 只管分配內存,並不能對所得的內存進行初始化,所以得到的一片新內存中,其值將是隨機的;

既然new/delete的功能覆蓋了malloc/free,爲什麼C++還要保留malloc/free?因爲C++程序經常要調用C函數,而C程序只能用malloc/free管理動態內存。

new/delete、malloc/free底層實現原理:

概述:new/delete的底層實現是調用malloc/free函數實現的,而malloc/free的底層實現也不是直接操作內存而是調用系統API實現的。

new/delete的兩種分配方式原理圖如下:

注意,針對上圖最末尾所述的“new[]/delete[]時會多開闢4字節用於存儲對象個數”,作如下說明:
①對於內置類型:
new []不會在首地址前4個字節定義數組長度。
delete 和 delete[]是一樣的執行效果,都會刪除整個數組,要刪除的長度從new時即可知道。
②對於自定義類型:
new []會在首地址前4個字節定義數組長度。
當delete[]時,會根據前4個字節所定義的長度來執行析構函數刪除整個數組。
如果只是delete數組首地址,只會刪除第一個對象的值。 

 

19、overload、override、overwrite的介紹

答:(1)overload(重載),即函數重載:
①在同一個類中;
②函數名字相同;
③函數參數不同(類型不同、數量不同,兩者滿足其一即可);
④不以返回值類型不同作爲函數重載的條件。
(2)override(覆蓋,子類改寫父類的虛函數),用於實現C++中多態:
①分別位於父類和子類中;
②子類改寫父類中的virtual方法;
③與父類中的函數原型相同。
(3)overwrite(重寫或叫隱藏,子類改寫父類的非虛函數,從而屏蔽父類函數):
①與overload類似,但是範圍不同,是子類改寫父類;
②與override類似,但是父類中的方法不是虛函數。

 

20、小端/大端機器

答:小端/大端的區別是指低位數據存儲在內存低位還是高位的區別。其中小端機器指:數據低位存儲在內存地址低位,高位數據則在內存地址高位;大端機器正好相反。

當前絕大部分機器都是小端機器,就是比較符合人們邏輯思維的數據存儲方式,比如intel的機器基本就都是小端機器。

 

21、守護進程

答:(1)什麼是守護進程?
守護進程(Daemon Process),也就是通常說的 Daemon 進程(精靈進程),是 Linux 中的後臺服務進程。它是一個生存期較長的進程,通常獨立於

控制終端並且週期性地執行某種任務或等待處理某些發生的事件。

守護進程是個特殊的孤兒進程,這種進程脫離終端,爲什麼要脫離終端呢?之所以脫離於終端是爲了避免進程被任何終端所產生的信息所打斷,其在執

行過程中的信息也不在任何終端上顯示。

(2)如何查看守護進程?

在終端敲:ps axj

從上圖可以看出守護進行的一些特點:
守護進程基本上都是以超級用戶啓動( UID 爲 0 )
沒有控制終端( TTY 爲 ?)
終端進程組 ID 爲 -1 ( TPGID 表示終端進程組 ID)

更多守護進程相關參考:http://blog.csdn.net/lianghe_work/article/details/47659889

 

22、多線程

答:Java提供了3中多線程實現:Thread類、runable接口、使用ExecutorService、Callable、Future實現有返回結果的多線程。

而C++本身並沒有提高多線程編程功能的庫或接口,但Windows系統下的C++多線程編程還是可以通過<windows.h>庫中的相關多線程接口實現,具體見:http://blog.csdn.net/xiongchao99/article/details/64441017#t107

Linux寫的C++多線程可以用頭文件pthread.h,常用到其中兩個函數pthread_create和pthread_join。下面是一個Linux下的簡單C++多線程程序:

//Threads.cpp
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

void *thread(void *ptr)
{
    for(int i = 0;i < 3;i++) {
        sleep(1);
        cout << "This is a pthread." << endl;
    }
    return 0;
}

int main() {
    pthread_t id;
    int ret = pthread_create(&id, NULL, thread, NULL);//創建線程
    if(ret) {
        cout << "Create pthread error!" << endl;
        return 1;
    }
    for(int i = 0;i < 3;i++) {
        cout <<  "This is the main process." << endl;
        sleep(1);
    }
    pthread_join(id, NULL);//等待線程結束
    return 0;
}

 

 

 

多線程相關的同步的知識不再累述,見http://blog.csdn.net/xiongchao99/article/details/74858900,此處來說說多線程優缺點:

多線程的主要優點包括: 

(1)多線程技術使程序的響應速度更快 ,因爲用戶界面可以在進行其它工作的同時一直處於活動狀態;

(2)佔用大量處理時間的任務使用多線程可以提高CPU利用率,即佔用大量處理時間的任務可以定期將處理器時間讓給其它任務;

(3)多線程可以分別設置優先級以優化性能。

以下是最適合採用多線程處理:

(1)耗時或大量佔用處理器的任務阻塞用戶界面操作;

(2)各個任務必須等待外部資源 (如遠程文件或 Internet連接)。

多線程的主要缺點包括:

(1)等候使用共享資源時造成程序的運行速度變慢。這些共享資源主要是獨佔性的資源 ,如打印機等。

(2)對線程進行管理要求額外的 CPU開銷,線程的使用會給系統帶來上下文切換的額外負擔。

(3)線程的死鎖。即對共享資源加鎖實現同步的過程中可能會死鎖。

(4)對公有變量的同時讀或寫,可能對造成髒讀等;

 

23、長連接與短連接

答:(1)就是TCP長連接和TCP短連接:

①TCP長連接:TCP長連接指建立連接後保持連接而不斷開。若一段時間內沒有數據傳輸,服務器會發送心跳包給客戶端,判斷客戶端是否還在線,叫做TCP長連接中的keep alive。一般步驟:連接→數據傳輸→保持連接(心跳)→數據傳輸→保持連接(心跳)→……→關閉連接;

②TCP短連接:指連接建立並傳輸數據完成後,就斷開連接。一般步驟:連接→數據傳輸→關閉連接;

③使用場景:長連接適合單對單通信且連接數不太多的情況;短連接適合連接數多且經常更換連接對象的;

(2)HTTP是什麼連接:

①在HTTP/1.0中,默認使用的是短連接。但從 HTTP/1.1起,默認使用長連接,用以保持連接特性。使用長連接的HTTP協議,會在響應頭有加入這行代碼:

Connection:keep-alive

注意:此處的keep-alive和上述TCP長連接原理介紹中的keep alive不是一個意思:此處表示告知服務器本http請求是長連接模式,而TCP長連接中的keep alive表示對客戶端的保活檢測。

 

②http長連接並不是一直保持連接

http的長連接也不會是永久保持連接,它有一個保持時間如20s(從上一次數據傳輸完成開始計時),可以在不同的服務器軟件(如Apache)中設定這個時間,若超過該時間限制仍然無數據通信傳輸,服務器就主動關閉該連接。注:實現長連接要客戶端和服務端都支持長連接。

③http連接實質:http的長連接/短連接實質上就是TCP的長/短連接。

 

24、二分圖應用於最佳匹配問題(遊客對房間的滿意度之和最大問題)

答:題目:有n個遊客和n個客房,每個遊客對每間房有一個滿意度,現要求做出一個入住安排,使得所有遊客的滿意度最大。

思路:用二分圖解決,遊客作爲一邊的頂點,客房作爲另一邊的頂點,取出所有最大匹配中滿意度之和最大的方案。

實現:涉及匈牙利算法;

 

第二篇

1、class與struct的區別

答:C++中的struct對C中的struct進行了擴充,它已經不再只是一個包含不同數據類型的數據結構了,它已經獲取了太多的功能:
①struct能包含成員函數嗎? 能!
②struct能繼承嗎? 能!!
③struct能實現多態嗎? 能!!! 

既然這些它都能實現,那它和class還能有什麼區別?

最本質的一個區別就是成員默認屬性和默認繼承權限的不同:

①若不指明,struct成員的默認屬性是public的,class成員的默認屬性是private的;

②若不指明,struct成員的默認繼承權限是public的,class成員的默認繼承權限是private的;

 

2、虛函數和純虛函數

答:參考另一博文:http://blog.csdn.net/xiongchao99/article/details/64441017#t80

 

3、menset()函數

答:Memset用來將buffer開始的長爲size的內存空間全部設置爲字符c,一般用在對定義的字符串進行初始化爲''或'/0';這個函數在socket中多用於清空數組。參見:http://blog.csdn.net/xiongchao99/article/details/64441017#t124

 

發佈了38 篇原創文章 · 獲贊 202 · 訪問量 42萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章