C++面試寶典(整理版)6


101.用遞歸算法判斷數組a[N]是否爲一個遞增數組。

遞歸算法特徵:相同的處理或判斷邏輯,包括相同的輸入輸出參數。
遞歸算法注意:1.明確結束遞歸條件;2.遞歸趨近於結束條件;3.遞歸次數過多容易造成棧溢出
遞歸的方法,記錄當前最大的,並且判斷當前的是否比這個還大,大則繼續,否則返回false結束:
bool fun( int a[], int n )
{
  if( n= =1 )
  return true;
  if( n= =2 )
  return a[n-1] >= a[n-2];
  return fun( a,n-1) && ( a[n-1] >= a[n-2] );
}

102.編寫算法,從10億個浮點數當中,選出其中最大的10000個。

用外部排序,在《數據結構》書上有《計算方法導論》在找到第n大的數的算法上加工。
思路:先將數據進行分割成數據量小的一些文件,如1000000個數據爲一個文件,然後將每個文件數據進行排序,用快速排序法排序,然後使用K路合併法將其合併到一個文件下,取出排序好的最大的10000個數據

103.判斷字符串是否爲迴文

bool IsSymmetry(const char* p)
{
     assert(p!=NULL);
     const char* q=p;     
             int len=0;
     while(*q++!='\0')
     {
          len++;
     }      
             bool bSign=true;
     q=p+len-1;
     if (0<len)
     {
          for (int i=0;i<len/2;i++)
         {
               if(*p++!=*q--){ bSign=false;break;};
          }
     }
     if(bSign==true)
     {
          printf("Yes!\n");
     }
     else
     {
          printf("No!\n");
     }
     return bSign;
 }

104.Static 作用是什麼

首先static的最主要功能是隱藏,其次因爲static變量存放在靜態存儲區,所以它具備持久性和默認值0。

105.什麼是預編譯,何時需要預編譯?

預編譯又稱爲預處理,是做些代碼文本的替換工作。處理#開頭的指令,比如拷貝#include包含的文件代碼,#define宏定義的替換,條件編譯等,就是爲編譯做的預備工作的階段,主要處理#開始的預編譯指令,預編譯指令指示了在程序正式編譯前就由編譯器進行的操作,可以放在程序中的任何位置。
c編譯系統在對程序進行通常的編譯之前,先進行預處理。c提供的預處理功能主要有以下三種:1)宏定義 2)文件包含 3)條件編譯
1、總是使用不經常改動的大型代碼體。 
2、程序由多個模塊組成,所有模塊都使用一組標準的包含文件和相同的編譯選項。在這種情況下,可以將所有包含文件預編譯爲一個預編譯頭。

110.進程和線程的區別

什麼是進程(Process),普通的解釋就是,進程是程序的一次執行;
什麼是線程(Thread),線程可以理解爲進程中的執行的一段程序片段。
在一個多任務環境中下面的概念可以幫助我們理解兩者間的差別:
進程間是獨立的,這表現在內存空間,上下文環境;線程運行在進程空間內。 一般來講(不使用特殊技術)進程是無法突破進程邊界存取其他進程內的存儲空間;而線程由於處於進程空間內,所以同一進程所產生的線程共享同一內存空間。 同一進程中的兩段代碼不能夠同時執行,除非引入線程。線程是屬於進程的,當進程退出時該進程所產生的線程都會被強制退出並清除。線程佔用的資源要少於進程所佔用的資源。 進程和線程都可以有優先級。在線程系統中進程也是一個線程。可以將進程理解爲一個程序的第一個線程。
線程是指進程內的一個執行單元,也是進程內的可調度實體.與進程的區別:
(1)地址空間:進程內的一個執行單元;進程至少有一個線程;它們共享進程的地址空間;而進程有自己獨立的地址空間;
(2)進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資源
(3)線程是處理器調度的基本單位,但進程不是.
(4)二者均可併發執行.

111.插入排序和選擇排序

插入排序基本思想:(假定從大到小排序)依次從後面拿一個數和前面已經排好序的數進行比較,比較的過程是從已經排好序的數中最後一個數開始比較,如果比這個數,繼續往前面比較,直到找到比它大的數,然後就放在它的後面,如果一直沒有找到,肯定這個數已經比較到了第一個數,那就放到第一個數的前面。那麼一般情況下,對於採用插入排序法去排序的一組數,可以先選 取第一個數做爲已經排好序的一組數。然後把第二個放到正確位置。
選擇排序(Selection Sort)是一種簡單直觀的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小元素,然後放到排序序列末尾。以此類推,直到所有元素均排序完畢。

112.運算符優先級問題

能正確表示a和b同時爲正或同時爲負的邏輯表達式是(D )。
sssA、(a>=0||b>=0)&&(a<0||b<0) 
B、(a>=0&&b>=0)&&(a<0&&b<0) 
C、(a+b>0)&&(a+b<=0) 
D、a*b>0
以下關於運算符優先順序的描述中正確的是(C)。 
A、關係運算符<算術運算符<賦值運算符<邏輯與運算符 
B、邏輯與運算符<關係運算符<算術運算符<賦值運算符 
C、賦值運算符<邏輯與運算符<關係運算符<算術運算符 
D、算術運算符<關係運算符<賦值運算符<邏輯與運算符

113.字符串倒序

寫一個函數將"tom is cat" 倒序打印出來,即 "cat is tom"
//a.ch
#define SPACE ' '
#define ENDL '\0'
char* str = "Tom is cat"; // 字符串
char* p1 = str+strlen(str)-1;
char* p2 = p1; // 開始時,p1,p2都指向字符串結尾處
char t=0; // 臨時變量,用來保存被臨時替換爲ENDL的字符
while(str!=p1--)
{
  if(SPACE!=*p1)
 {
     for(p2=p1+1;SPACE!=*p1; p1--, t=*p2, *p2=ENDL);
     // p1+1指向單詞的第一個字母,p2指向單詞的結尾,此時輸出這個單詞
                printf("%s ",p1+1);
                *p2=t;
                p2=p1;
  }
}
Output:
cat is Tom
----------------------------------------------------------------------
1)寫一個遞歸函數將內存中的字符串翻轉"abc"->"cba"
2)寫一個函數將"tom is cat" 將內存中的字符串翻轉,即 "cat is tomm" 
#include <stdio.h>
#define SPACE ' '
#define ENDL '\0'
char* s = "The quick brown fox jumps over the lazy dog";
void str_reverse(char* p1,char* p2)
{
      if(p1==p2)return;
    *p1 = (*p1)+(*p2);
    *p2 = (*p1)-(*p2);
    *p1 = (*p1)-(*p2);
      if(p1==p2-1)return;
      else str_reverse(++p1,--p2);
      }
void str_word_reverse(char* str)
{
  char *q1=str, *q2=str, *t;
  while(*q1==SPACE)q1++;
  if(*q1==ENDL)return; //!
  else q2=q1+1;
  while( (*q2!=SPACE) && (*q2!=ENDL) )q2++;
         t=q2--;
    str_reverse(q1,q2);
  if(*t==ENDL)return;
  else str_word_reverse(t);
}

int main(int a ,char** b)
{
    printf("%s\n",s);
    str_reverse(s,s+strlen(s)-1);
    printf("%s\n",s);
    str_word_reverse(s);
    printf("%s\n",s);
           return 0;
}
Output:
The quick brown fox jumps over the lazy dog
god yzal eht revo spmuj xof nworb kciuq ehT
dog lazy the over jumps fox brown quick The
----------------------------------------------------------------------
今天同學又問一道題,和上面有些類似,但是要求更嚴格了一些:
寫一個遞歸函數將內存中的字符串翻轉"abc"->"cba",並且函數原型已確定:void reverse(char* p)
其實,要求越多,思路越確定,我的解如下:
#include <stdio.h>
#include <string.h>
char* s = "0123456789";
#define ENDL '\0'
void reverse(char* p){
       //這是這種方法的關鍵,使用static爲的是能用str_reverse的思路,但是不好
       static char* x=0;
       if(x==0)x=p;
       char* q = x+strlen(p)-1;
             if(p==q)return;
       *q=(*p)^(*q);
       *p=(*p)^(*q);
       *q =(*p)^(*q);
       if(q==p+1)return;
       reverse(++p);
       }
//這種方法就直觀多了,但是當字符串很長的時候就很低效
void reverse2(char* p){
       if(*(p+1)==ENDL)return;
       for(char* o=p+strlen(p)-1,char t=*o;o!=p;o--)
          *o=*(o-1);
       *p=t;
       reverse2(p+1);
       }
int main(int c,char** argv){
       reverse2(s);
       printf("%s\n",s);
       return 0;
       }

114.交換兩個數的宏定義

交換兩個參數值的宏定義爲:. #define SWAP(a,b) (a)=(a)+(b);(b)=(a)-(b);(a)=(a)-(b);

115.Itearator各指針的區別

       遊標和指針
我說過遊標是指針,但不僅僅是指針。遊標和指針很像,功能很像指針,但是實際上,遊標是通過重載一元的”*”和”->”來從容器中間接地返回一個值。將這些值存儲在容器中並不是一個好主意,因爲每當一個新值添加到容器中或者有一個值從容器中刪除,這些值就會失效。在某種程度上,遊標可以看作是句柄(handle)。通常情況下游標(iterator)的類型可以有所變化,這樣容器也會有幾種不同方式的轉變:
iterator——對於除了vector以外的其他任何容器,你可以通過這種遊標在一次操作中在容器中朝向前的方向走一步。這意味着對於這種遊標你只能使用“++”操作符。而不能使用“--”或“+=”操作符。而對於vector這一種容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一種操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比較運算符。

116. C++中的class和struct的區別

從語法上,在C++中(只討論C++中)。class和struct做類型定義時只有兩點區別:
(一)默認繼承權限。如果不明確指定,來自class的繼承按照private繼承處理,來自struct的繼承按照public繼承處理;
(二)成員的默認訪問權限。class的成員默認是private權限,struct默認是public權限。
除了這兩點,class和struct基本就是一個東西。語法上沒有任何其它區別。
不能因爲學過C就總覺得連C++中struct和class都區別很大,下面列舉的說明可能比較無聊,因爲struct和class本來就是基本一樣的東西,無需多說。但這些說明可能有助於澄清一些常見的關於struct和class的錯誤認識:
(1)都可以有成員函數;包括各類構造函數,析構函數,重載的運算符,友元類,友元結構,友元函數,虛函數,純虛函數,靜態函數;
(2)都可以有一大堆public/private/protected修飾符在裏邊;
(3)雖然這種風格不再被提倡,但語法上二者都可以使用大括號的方式初始化:
A a ={1, 2, 3};不管A是個struct還是個class,前提是這個類/結構足夠簡單,比如所有的成員都是public的,所有的成員都是簡單類型,沒有顯式聲明的構造函數。
(4)都可以進行復雜的繼承甚至多重繼承,一個struct可以繼承自一個class,反之亦可;一個struct可以同時繼承5個class和5個struct,雖然這樣做不太好。
(5)如果說class的設計需要注意OO的原則和風格,那麼沒任何理由說設計struct就不需要注意。
(6)再次說明,以上所有說法都是指在C++語言中,至於在C裏的情況,C裏是根本沒有“class”,而C的struct從根本上也只是個包裝數據的語法機制。
---------------------------------------------------------------
最後,作爲語言的兩個關鍵字,除去定義類型時有上述區別之外,另外還有一點點:“class”這個關鍵字還用於定義模板參數,就像“typename”。但關鍵字“struct”不用於定義模板參數。
關於使用大括號初始化
  class和struct如果定義了構造函數的話,都不能用大括號進行初始化
  如果沒有定義構造函數,struct可以用大括號初始化。
  如果沒有定義構造函數,且所有成員變量全是public的話,可以用大括號初始化。
  關於默認訪問權限
  class中默認的成員訪問權限是private的,而struct中則是public的。
  關於繼承方式
  class繼承默認是private繼承,而struct繼承默認是public繼承。
  關於模版
  在模版中,類型參數前面可以使用class或typename,如果使用struct,則含義不同,struct後面跟的是“non-type template parameter”,而class或typename後面跟的是類型參數。
class中有個默認的this指針,struct沒有
不同點:構造函數,析構函數 this 指針

117.有關重載函數

返回值類型不同構不成重載 
參數參數順序不同能構成重載
c++函數同名不同返回值不算重載!函數重載是忽略返回值類型的。 

成員函數被重載的特徵有: 
1) 相同的範圍(在同一個類中); 
2) 函數名字相同; 
3) 參數不同; 
4) virtual關鍵字可有可無。
5) 成員函數中 有無const (函數後面) 也可判斷是否重載

118.數據庫與T-SQL語言

關係數據庫是表的集合,它是由一個或多個關係模式定義。SQL語言中的數據定義功能包括對數據庫、基本表、視圖、索引的定義。

119.關係模型的基本概念

 關係數據庫以關係模型爲基礎,它有以下三部分組成:
    ●數據結構——模型所操作的對象、類型的集合
    ●完整性規則——保證數據有效、正確的約束條件
    ●數據操作——對模型對象所允許執行的操作方式
    關係(Relation)是一個由行和列組成的二維表格,表中的每一行是一條記錄(Record),每一列是記錄的一個字段(Field)。表中的每一條記錄必須是互斥的,字段的值必須具有原子性。
    120.SQL語言概述
    SQL(結構化查詢語言)是關係數據庫語言的一種國際標準,它是一種非過程化的語言。通過編寫SQL,我們可以實現對關係數據庫的全部操作。
    ●數據定義語言(DDL)——建立和管理數據庫對象
    ●數據操縱語言(DML)——用來查詢與更新數據
    ●數據控制語言(DCL)——控制數據的安全性
       起來是一個很簡單的問題,每一個使用過RDBMS的人都會有一個概念。
事務處理系統的典型特點是具備ACID特徵。ACID指的是Atomic(原子的)、Consistent(一致的)、Isolated(隔離的)以及Durable(持續的),它們代表着事務處理應該具備的四個特徵:
原子性:組成事務處理的語句形成了一個邏輯單元,不能只執行其中的一部分
一致性:在事務處理執行之前和之後,數據是一致的。
隔離性:一個事務處理對另一個事務處理沒有影響。
持續性:當事務處理成功執行到結束的時候,其效果在數據庫中被永久紀錄下來。

121.C語言中結構化程序設計的三種基本控制結構

順序結構、選擇結構、循環結構

123.三種基本的數據模型

按照數據結構類型的不同,將數據模型劃分爲層次模型、網狀模型和關係模型。

124.設計模式:工廠模式 和 單例模式 介紹一下?

工程模式即將對象創建過程封裝即爲工廠模式。
單例模式即整個類只有一個對象,並且不允許顯示創建。

125.vector 和 list的區別?

vector內部使用數組,訪問速度快,但是刪除數據比較耗性能
list內部使用鏈表,訪問速度慢,但是刪除數據比較快

126.純虛函數是怎樣實現的?在編譯原理上講一下?

在類內部添加一個虛擬函數表指針,該指針指向一個虛擬函數表,該虛擬函數表包含了所有的虛擬函數的入口地址,每個類的虛擬函數表都不
一樣,在運行階段可以循此脈絡找到自己的函數入口。
純虛函數相當於佔位符,先在虛函數表中佔一個位置由派生類實現後再把真正的函數指針填進去。除此之外和普通的虛函數沒什麼區別。

127.抽象類爲什麼不能實例化?

抽象類中的純虛函數沒有具體的實現,所以沒辦法實例化。

128.在函數後面加個const是怎麼理解的?

 在函數後面加個const一般在類的成員函數中使用,表示這個函數不修改數據成員的值。
另:void set_prt_val(int val)const{*ptr= val}理解:指針ptr指向的內容不是類的數據成員,所以這可這麼寫:*ptr = val;但這個指針在這個函數中不能修改。如果寫成這樣:ptr = &i(假設i是另外一個整形變量)就不對了,因爲改變了指針的內容。

129.進程間通信類型:

(1)環境變量、文件描述符 一般Unix環境下的父進程執行fork(),生成的子進程擁有了父進程當前設置的環境變量以及文件描述符;由於通信是一個單向的、一次性的通信,隨後的父進程以及子進程後續的內容不能再能共享;
(2)命令行參數 大多數用戶都使用過ShellExec相關的命令,此API可以打開新的進程,並可以通過接口裏的輸入參數進行信息共享;同樣,他也是一個單項、一次性的通信;
(3)管道 使用文件和寫方式訪問公用的數據結構;管道分爲匿名管道和命名管道,前者是用作關聯進程間用,後者爲無關聯的進程使用;前者通過文件描述符或文件句柄提供對命名管道的訪問,後者需要知道管道名稱才能讀寫管道;一般來講,讀寫的內容是字節流,需要轉換爲有意義的結構纔有意義;
(4)共享內存 進程需要可以被其他進程訪問瀏覽的進程塊;進程間共享內存的關係與函數間共享全局變量的關係類似
(5)DDE 動態數據交互
線程間通信類型:
(1)全局數據;
(2)全局變量;
(3)全局數據結構;
(4)線程間通信的參數:pThread_create這類API接口中的參數

130.關於內存對齊的問題以及sizof()的輸出

 答:編譯器自動對齊的原因:爲了提高程序的性能,數據結構(尤其是棧)應該儘可能地在自然邊界上對齊。原因在於,爲了訪問未對齊的內存,處理器需要作兩次內存訪問;然而,對齊的內存訪問僅需要一次訪問。

131.winsock建立連接的主要實現步驟?

答:
TCP:服務器端:1.socket()建立套接字,2將套接字綁定到本地地址和端口上,綁定(bind)3.將套接字設爲監聽模式,準備接收客戶端,監聽(listen);4.等待客戶端請求到來,請求到來後,連接請求,並返回一個新的對應此連接的套接字,accept()5.用返回的套接字和客戶端進行通訊(send/recv);6.返回並等待另一客戶請求。7.關閉套接字。
客戶端:1.socket()建立套接字2.向服務器發出連接請求,(connect)2。和服務器進行通信,send()和recv(),在套接字上寫讀數據,直至數據交換完畢;4closesocket()關閉套接字。
UDP:1服務器端:1.創建套接字(socekt)2.將套接字綁定到本地地址和端口上(bind);3.等待接收數據(recvfrom);4.closesocket()關閉套接字。
客戶端:1.創建套接字(socekt)2,向服務器端發送數據(sendto)3.closesocket()關閉套接字。

132.C++中爲什麼用模板類。

答:
(1)可用來創建動態增長和減小的數據結構
(2)它是類型無關的,因此具有很高的可複用性。
(3)它在編譯時而不是運行時檢查數據類型,保證了類型安全
(4)它是平臺無關的,可移植性
(5)可用於基本數據類型

133.動態連接庫的兩種方式?

答:調用一個DLL中的函數有兩種方法:
1.載入時動態鏈接(load-time dynamic linking),模塊非常明確調用某個導出函數,使得他們就像本地函數一樣。這需要鏈接時鏈接那些函數所在DLL的導入庫,導入庫向系統提供了載入DLL時所需的信息及DLL函數定位。
2.運行時動態鏈接(run-time dynamic linking),運行時可以通過LoadLibrary或LoadLibraryEx函數載入DLL。DLL載入後,模塊可以通過調用GetProcAddress獲取DLL函數的出口地址,然後就可以通過返回的函數指針調用DLL函數了。如此即可避免導入庫文件了。

 134..CSingleLock是幹什麼的。

 答:同步多個線程對一個數據類的同時訪問

 135.編寫strcat函數(6分)

 已知strcat函數的原型是char *strcat (char *strDest, const char *strSrc);
其中strDest 是目的字符串,strSrc 是源字符串。
(1)不調用C++/C 的字符串庫函數,請編寫函數 strcat
答:
VC源碼:
char * __cdecl strcat (char * dst, const char * src)
{
  char * cp = dst;
  while( *cp )
  cp++; /* find end of dst */
  while( *cp++ = *src++ ) ; /* Copy src to end of dst */
  return( dst ); /* return dst */
}
(2)strcat能把strSrc 的內容連接到strDest,爲什麼還要char * 類型的返回值?
答:方便賦值給其他變量

136.編寫類String 的構造函數、析構函數和賦值函數(25 分)

已知類String 的原型爲:
class String
{
  public:
    String(const char *str = NULL); // 普通構造函數
    String(const String &other); // 拷貝構造函數
    ~ String(void); // 析構函數
    String & operate =(const String &other); // 賦值函數
  private:
    char *m_data; // 用於保存字符串
};
請編寫String 的上述4 個函數。
標準答案:
// String 的析構函數
String::~String(void) // 3 分
{
  delete [] m_data;
  // 由於m_data 是內部數據類型,也可以寫成 delete m_data;
}
// String 的普通構造函數
String::String(const char *str) // 6 分
{
  if(str==NULL)
 {
    m_data = new char[1]; // 若能加 NULL 判斷則更好
    *m_data = ‘\0’;
  }
  else
 {
    int length = strlen(str);
    m_data = new char[length+1]; // 若能加 NULL 判斷則更好
    strcpy(m_data, str);
  }
}
// 拷貝構造函數
String::String(const String &other) // 3 分
{
  int length = strlen(other.m_data);
  m_data = new char[length+1]; // 若能加 NULL 判斷則更好
  strcpy(m_data, other.m_data);
}
// 賦值函數
String & String::operate =(const String &other) // 13 分
{
  // (1) 檢查自賦值 // 4 分
  if(this == &other)
  return *this;
  // (2) 釋放原有的內存資源 // 3 分
  delete [] m_data;
  // (3)分配新的內存資源,並複製內容 // 3 分
  int length = strlen(other.m_data);
  m_data = new char[length+1]; // 若能加 NULL 判斷則更好
  strcpy(m_data, other.m_data);
  // (4)返回本對象的引用 // 3 分
  return *this;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章