天天看點

C++筆試面試常考知識點彙總(一)

1:大端與小端?與尋常習慣的差別?

大端:高位址存儲低位位元組。

小端:低位址存儲低位位元組。

如對于資料0x1234,其32位為0x00001234。

對于大端來說:(位址由低到高存儲)00 00 12 34

對于小端來說:(位址由低到高存儲)34 12 00 00

由此可以得出結論:大端的存儲方式與尋常習慣相一緻,而小端的存儲方式為按照位元組倒排。

2:#include< >與#include” “的差別?

優先搜尋路徑不同。

前者優先搜尋标準庫檔案目論,是以對于标準庫檔案搜尋效率高。

後者優先搜尋自定義檔案目錄,然後搜尋整個磁盤,對于自定義檔案的搜尋比較快。

3:C++将基類的析構函數定義為虛函數的目的?

若将基類的析構函數定義為虛函數,當删除一個指向派生類的基類指針時,首先會調用派生類的析構函數,然後再調用基類的析構函數;否則隻會調用基類的析構函數。

4:mutable定義資料成員的意義?

mutable定義的資料成員,可以在const成員函數中進行修改。

5:怎樣定義一個純虛函數?純虛函數與抽象類的關系?抽象類的特點?

将虛函數指派為0即可得到一個純虛函數;包含純虛函數的類是抽象類。抽象類有以下特點:不能執行個體化;不能作為函數參數及函數傳回類型;可以定義抽象類類型的指針。

抽象類一般作為基類使用。對于一個抽象基類,如果其派生類沒有重新定義基類的虛函數,則派生類也還是抽象類,隻有派生類重新定義了基類的虛函數,派生類才不再是抽象類,才是一個可以建立對象的具體類。

6:malloc與new的差別?

1)malloc是函數,而new是操作符

2)malloc申請記憶體時,需要我們指定申請的空間大小,且傳回的類型為void*,需要将其強制轉換為所需類型指針;new申請記憶體時,會根據所申請的類型自動計算申請空間的大小,且可直接傳回指定類型的指針

3)malloc釋放記憶體時,用free函數,而new删除對象時,用的是delete操作符

4)malloc/free申請釋放記憶體時,不需要需要調用析構函數,而new/delete申請釋放記憶體時需要調用析構函數

7:STL算法中的next_permutation()算法的用法?

如果序列已經是最後一個排列,則next_permutation将序列重排為最小的序列,并傳回false。否則,它将輸入序列轉換為字典序中下一個排列,并傳回true。

8:用位邏輯運算實作位向量的set、clr、test?

#define BITSPERWORD 32 //表示一個整型含有32個bit
#define SHIFT 5 //機關位移量
#define MASK 0x1F   //掩碼
#define N 10000000  //表示有1000萬個數
int a[+N/BITSPERWORD]; //使用整型數組模拟定義1000萬個位的數組

//i>>SHIFT指的是右移5位,也就是除以32,指的是該位存在于哪個數組中
//i&MASK指的是i%32,剩下的數字為多少,1<<(i&MASK))表示1左移i&MASK位
void set(int i){a[i>>SHIFT]|=(<<(i&MASK));}
void clr(int i){a[i>>SHIFT]&=~(<<(i&MASK));}
int test(int i){return a[i>>SHIFT]&(<<(i&MASK));}
           

解析:拿set函數為例:對于形參i,首先将i除以32(i>>SHIFT)以找到i位于數組中哪一位置;i&MASK指的是i%32後剩下的數字為多少,然後進行左移(因為對于任意一個a[m],隻有32位,現其已經限定到具體的某個a[m],是以其移位範圍為0~32);最後通過或運算将a[m]中的0變為1。

clr函數通過非運算(該位置1取非成為0)與與運算(該位置1與0相與後成為0),将相應位置的1轉化為0。test函數探測i位置上資料到底是1還是0(i位置上的資料與1相與,傳回其真實值)。

9:二維數組作為參數傳遞的注意事項?

需要指明數組的第二維,第一維不是必須要顯式指定。

10:建立動态二維數組的兩種方法?在删除用new建立的二維數組時注意事項?

法1://申請空間

int ** a = new int *[row];//比一維的等号左右多了2個*而已
    for(int i = ;i < row;i++)
        a[i] = new int[column];
           
該方法定義的動态二維數組的釋放需先釋放指針數組的每個元素指向的數組,然後再釋放該指針數組:
           
for(int i = ;i < row;i++)
    {
        delete a[i];
        a[i] = NULL;
    }
    delete [row]a;
    a = NULL;
           

11:sizeof運算符在編譯階段處理的特性?

sizeof操作符在其作用範圍内,内容不會被編譯,而隻是簡單替換成其類型。

12:sizeof(字元串序列)和sizeof(字元串數組)的差別?

sizeof(字元串序列)=字元串長度+1;(算上最後面的’\0’)

sizeof(字元串數組)=字元串長度;(用{}表示時)

13:sizeof(動态數組)?

動态數組實質上是一個指針,其占用空間為4/8(32位/64位系統)。

14:用strlen得到字元串的長度?

對于字元串序列,到第一個’\0’為止(’\0’自動添加且不計算在内);對于字元串數組,必須要顯示指定’\0’。對于string型字元串,不能直接使用strlen,而需要使用strlen(s.c_str())計算。

char p[]="abcdefg";
    char a[]={'a','b','c','\0'};
    cout<<strlen(p)<<endl;//輸出7
    cout<<strlen(a)<<endl;//輸出3
           

15:sizeof(聯合)需要注意的占用空間最大的成員及對齊方式問題?

聯合與結構的差別:結構中各成員有各自的記憶體空間,結構變量所占用記憶體空間是各成員所占用記憶體空間之和;聯合中,各成員共享一段記憶體空間,一個聯合變量所占用記憶體空間等于各成員中所占用記憶體空間最大的變量所占用記憶體空間(需考慮記憶體對齊問題),此時共享意味着每次隻能賦一種值,賦入新值則沖去舊值。

16:sizeof(class)需注意情況?

sizeof隻計算資料成員的大小;不計算static資料成員的大小;繼承時需要考慮基類資料成員問題,考慮虛函數問題,無論有多少個虛函數,計算空間時虛函數表隻計算一次。

補充:當派生類存在多重繼承時,sizeof運算結果?

class A
{
    int a;
    int b;
    virtual void fun()
    {
    }
};
class B
{
    int c;
    virtual void fun1()
    {
    }
    virtual void fun2()
    {
    }
};
class C:public A,public B
{
    int d;
};

int main( ) 
{    
    cout<<sizeof(A)<<endl;//輸出12
    cout<<sizeof(B)<<endl;//輸出8
    cout<<sizeof(C)<<endl;//輸出24
    return ;    
}
           
對于同一個類的不同虛函數,隻需考慮一次即可,而對于不同類的虛函數,派生類需要都考慮(每一個類虛函數隻需考慮一次)。可參考虛函數表裡的記憶體配置設定原則。
           

17:rand()函數的用法?srand函數的用法?

rand();
    srand((unsigned int)(time NULL));
           

18:++a與a++的問題

++a得到左值,而a++得到右值。

19:靜态配置設定與動态配置設定的差別?

靜态配置設定是指在編譯期間就能确定記憶體的大小,由編譯器配置設定記憶體。動态配置設定是指在程式運作期間,由程式員申請的記憶體空間。堆和棧都可以動态配置設定,但靜态配置設定隻能是棧。

20:構造函數的清單初始化的初始化順序?哪三種情況是必須的?

1:const資料成員;2:引用資料成員;3:沒有預設構造函數的成員對象

對于static成員:若是static const成員,可以在類内初始化,也可以在類外初始化;若是普通的static成員,必須在類外初始化。
           

21:C++的轉義序列?\abc與\xabc分别表示什麼?

\abc表示8進制轉義序列(\後最多跟三位數字);\xabc表示16進制的轉義序列。

補充:\\也是一個轉義字元

22:當編譯器處理一個const時會将其轉化成一個立即數。

23:為什麼不能重新定義一個繼承而來的預設參數?

因為從基類繼承來的預設參數值都是靜态綁定的,而virtual函數——你唯一應該覆寫的東西,卻是動态綁定。

24:為什麼不能重新定義繼承而來的非虛函數?

因為非虛函數是靜态綁定的,如果重新定義繼承而來的非虛函數,則指向派生類的基類指針在調用該非虛函數時,将可能會産生行為異常。

25:(做題經驗)短路求值問題一定要細心運算。

一定要細心,判斷是否一定會對&&或||後面的操作進行運算

26:深拷貝與淺拷貝的差別?

深拷貝是指源對象與拷貝對象互相獨立,其中任何一個對象的改動都不會對另外一個對象造成影響。淺拷貝是指源對象與拷貝對象共用一份實體,僅僅是引用的變量不同(名稱不同),對其中任何一個對象的改動都會影響另外一個對象。

換種解釋:淺拷貝,隻是對指針的拷貝,拷貝後兩個指針指向同一個記憶體空間,深拷貝不但對指針進行拷貝,而且對指針指向的内容進行拷貝,經深拷貝後的指針是指向兩個不同位址的指針。

淺拷貝會出現什麼問題呢?

其一,淺拷貝隻是拷貝了指針,使得兩個指針指向同一個位址,這樣在對象塊結束,調用函數析構時,會造成同一份資源析構2次,即delete同一塊記憶體2次,造成程式崩潰。

其二,淺拷貝使得源對象和拷貝對象指向同一塊記憶體,任何一方的變動都會影響到另一方。

其三,在釋放記憶體的時候,會造成拷貝對象原有的記憶體沒有被釋放造成記憶體洩露。(源對象記憶體被釋放後,由于源對象和拷貝對象指向同一個記憶體空間,拷貝對象的空間不能再被利用了,删除拷貝對象不會成功,無法操作該空間,是以導緻記憶體洩露)

類的靜态成員是所有類的執行個體共有的,存儲在全局(靜态)區,隻此一份,不管繼承、執行個體化還是拷貝都是一份。是以類的靜态成員不允許深拷貝。

27:如何隻讓類對象隻在棧(堆)上配置設定對象?

1:隻在堆上配置設定對象

動态建立對象,是使用new運算符将對象建立在堆空間中。過程分為2步,第一步是執行operator new()函數,在堆空間中搜尋适合的記憶體并進行配置設定;第二步是調用構造函數構造對象,初始化這片記憶體空間。這種方法,間接調用類的構造函數。

将構造函數設為私有,将會阻止在堆上配置設定對象,是不行的。

将析構函數設為私有(為了考慮繼承,可将析構函數設為受保護的)。當對象建立在棧上面時,是由編譯器配置設定記憶體空間的,調用構造函數來構造棧對象。當對象使用完後,編譯器會調用析構函數來釋放棧對象所占的空間。編譯器管理了對象的整個生命周期。編譯器在為類對象配置設定棧空間時,會先檢查類的析構函數的通路性,其實不光是析構函數,隻要是非靜态的函數,編譯器都會進行檢查。如果類的析構函數是私有的,則編譯器不會在棧空間上為類對象配置設定記憶體。是以,将析構函數設為私有,類對象就無法建立在棧上了。

2:隻在棧上配置設定對象

隻要禁用new運算符(設為私有)就可以實作類對象隻能建立在棧上。

28:char擴充為int的兩種情況及注意事項?

将char轉換為int時關鍵看char是unsigned還是signed,如果是unsigned就執行0擴充,如果是signed就執行符号位擴充。跟int本身是signed還是unsiged無關。

而同等位數的類型之間的指派表達式不會改變其在記憶體之中的表現形式,即如果signed char a = 0xe0; unsigned char c = a;則c=0xe0;

29:面向過程static全局變量與全局變量的差別?

1)全局變量預設是有外部連結性的,作用域是整個工程,在一個檔案内定義的全局變量,在另一個檔案中,通過extern 全局變量名的聲明,就可以使用全局變量。

2)靜态全局變量是顯式用static修飾的全局變量,作用域是聲明此變量所在的檔案,其他的檔案即使用extern聲明也不能使用。

30:面向過程靜态局部變量的特點?

1)在全局資料區配置設定記憶體;

2)在程式執行到該對象的聲明處時被首次初始化,且以後不再初始化;

3)一般在聲明處初始化,如果沒有顯式初始化,會被程式自動初始化為0;

4)它始終駐留在全局資料區,直到程式運作結束。但其作用域為局部作用域,當定義它的函數或語句塊結束時,其作用域随之結束;

31:面向對象靜态資料成員的特點?

靜态資料成員被當作是類的成員。隻配置設定一次記憶體,供所有對象共用。

靜态資料成員存儲在全局資料區。靜态資料成員定義時要配置設定空間,是以不能在類聲明中定義(const static成員可以在類中定義)。

不屬于特定的類對象,在沒有産生類對象時其作用域就可見,即在沒有産生類的執行個體時,我們就可以操作它。

靜态資料成員初始化與一般資料成員初始化不同。靜态資料成員初始化的格式為:

資料類型 類名:: 靜态資料成員名=值

類的靜态資料成員有兩種通路形式:

類對象名.靜态資料成員名 或 類類型名::靜态資料成員名

靜态資料成員主要用在各個對象都有相同的某項屬性的時候。比如對于一個存款類,每個執行個體的利息都是相同的。是以,應該把利息設為存款類的靜态資料成員。這有兩個好處,第一,不管定義多少個存款類對象,利息資料成員都共享配置設定在全局資料區的記憶體,是以節省存儲空間。第二,一旦利息需要改變時,隻要改變一次,則所有存款類對象的利息全改變過來了;

同全局變量相比,使用靜态資料成員有兩個優勢:

不存在與程式中其它全局名字沖突的可能性;

可以實作資訊隐藏。靜态資料成員可以是private成員,而全局變量不能;

32:面向對象靜态成員函數的特點?

普通的成員函數一般都隐含了一個this指針,this指針指向類的對象本身,因為普通成員函數總是具體的屬于某個類的具體對象的。但是與普通函數相比,靜态成員函數由于不與任何的對象相聯系,是以它不具有this指針。從這個意義上講,它無法通路屬于類對象的非靜态資料成員,也無法通路非靜态成員函數,它隻能調用其餘的靜态成員。

注意:出現在類體外的函數定義不能指定關鍵字static;

使用方法:可以用成員通路操作符(.)和(->;)為一個類的對象或指向類對象的指針調用靜态成員函數,也可以直接:類名::靜态成員函數名(參數表)

33:靜态成員與非靜态成員之間的可通路性?

靜态成員之間可以互相通路;非靜态成員可以通路靜态成員及非靜态成員,而靜态成員不能通路非靜态成員。

34:memcpy的用法與strcpy之間的差別?

memcpy函數的功能是從源指針所指的記憶體位址的起始位置開始拷貝n個位元組到目标指針所指的記憶體位址的起始位置中。

void *memcpy(void *dest, const void *src, size_t n); //函數傳回指向dest的指針

strcpy和memcpy主要有以下3方面的差別。

1)複制的内容不同。strcpy隻能複制字元串,而memcpy可以複制任意内容,例如字元數組、整型、結構體、類等。

2)複制的方法不同。strcpy不需要指定長度,它遇到被複制字元的串結束符”\0”才結束,是以容易溢出。memcpy則是根據其第3個參數決定複制的長度。

3)用途不同。通常在複制字元串時用strcpy,而需要複制其他類型資料時則一般用memcpy。

35:前置遞增遞減運算符的運算對象必須是左值。

36:不能聲明為虛函數的幾種情況?

1)普通函數(非類成員函數)(不能被覆寫)

2)友元函數(C++不支援友元函數繼承)

3)内聯函數(編譯期間展開,虛函數是在運作期間綁定)

4)構造函數(沒有對象不能使用虛函數,先有構造函數後有虛函數,虛函數是對對象的動作(構造函數不能繼承))

5)靜态成員函數(隻有一份大家共享)

37:虛函數的定義及功能?

定義:在某基類中聲明為 virtual 并在一個或多個派生類中被重新定義的非static成員函數。

功能:實作多态性,通過指向派生類的基類指針或引用,通路派生類中同名覆寫成員函數。

38:抽象類的特點?

含有純虛拟函數的類稱為抽象類,它不能生成對象。

在面向對象的概念中,所有的對象都是通過類來描繪的,但是反過來,并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的資訊來描繪一個具體的對象,這樣的類就是抽象類。如動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。

抽象類隻能作為基類來使用,其純虛函數的實作由派生類給出。如果派生類中沒有重新定義純虛函數,而隻是繼承基類的純虛函數,則這個派生類仍然還是一個抽象類。如果派生類中給出了基類純虛函數的實作,則該派生類就不再是抽象類了,它是一個可以建立對象的具體的類。

39:malloc、alloc、calloc、realloc的差別?

alloc:唯一在棧上申請記憶體的,無需釋放;

malloc:在堆上申請記憶體,最常用;

calloc:malloc+初始化為0;

realloc:将原本申請的記憶體區域擴容,參數size大小即為擴容後大小,是以此函數要求size大小必須大于ptr記憶體大小。

40:STL算法中partition算法的用法?

對指定範圍内的元素重新排序,使用輸入的函數,把結果為true的元素放在結果為false的元素之前。stable_partition版本保留原始容器中的相對順序。如使數組中奇數位于偶數前面。

41:什麼是多态?

多态:同一操作作用于不同的對象,可以有不同的解釋,産生不同的執行結果。在運作時,可以通過指向派生類的基類的指針或引用,來調用實作派生類中的方法。

42:dynamic_cast的作用?

dynamic_cast将一個基類對象指針(或引用)(需指向派生類或為派生類的引用)cast到派生類指針。dynamic_cast會根據基類指針是否真正指向繼承類對象來做相應處理,即會作一定的判斷。對指針進行dynamic_cast,失敗傳回null,成功傳回正常cast後的對象指針; 對引用進行dynamic_cast,失敗抛出一個異常,成功傳回正常cast後的對象引用。

43:在32位系統中和64為中long分别占用多少位元組?

32位:4位元組;64位:8位元組。

44:#define與typedef的差別?

1) #define是預處理指令,在編譯預處理時進行簡單的替換,不作正确性檢查。

2)typedef是在編譯時處理的。它在自己的作用域内給一個已經存在的類型一個别名。

3)typedef int * int_ptr;與#define int_ptr int * (注意沒有分号,否則連同分号一起替換)

作用都是用int_ptr代表 int * ,但是二者不同,正如前面所說 ,#define在預處理時進行簡單的替換,而typedef不是簡單替換 ,而是采用如同定義變量的方法那樣來聲明一種類型。

這也說明了為什麼下面觀點成立

typedef int * pint ;
#define PINT int *
           

那麼:

const pint p ;//p不可更改,但p指向的内容可更改
const PINT p ;//p可更改,但是p指向的内容不可更改。
           

pint是一種指針類型 const pint p 就是把指針給鎖住了 p不可更改

而const PINT p 是const int * p 鎖的是指針p所指的對象。

45:堆和棧的差別?

1)棧,由編譯器自動管理,無需我們手工控制;堆,申請釋放工作由程式員控制

2)堆的生長方向是向上的,也就是向着記憶體位址增加的方向;棧的生長方向是向下的,是向着記憶體位址減小的方向增長。

3)對于堆來講,頻繁的 new/delete 勢必會造成記憶體空間的不連續,進而造成大量的碎片,使程式效率降低。對于棧來講,則不會存在這個問題

4)一般來講在 32 位系統下,堆記憶體可以達到4G的空間,但是對于棧來講,一般都是有一定的空間大小的(2M)。

46:C、C++源代碼要經過哪些步驟才生成可執行檔案?各步驟的作用?

C/C++源代碼要經過:預處理、編譯、連接配接三步才能變成相應平台下的可執行檔案。

預處理:主要是編譯器對各種預處理指令進行處理,包括頭檔案的包含、宏定義的擴充、條件編譯的選擇等。

編譯:進行詞法與文法分析,首先編譯成純彙編語句,再将之彙編成跟CPU相關的二進制碼,生成各個目标檔案。

連接配接:連結是處理可重定位檔案,把它們的各種符号引用和符号定義轉換為可執行檔案中的合适資訊(一般是虛拟記憶體位址) 的過程。

47:公有繼承、私有繼承後基類public、protected、private成員在派生類中的通路權限?

有如下表格解釋

基類中的public成 public繼承 public
基類中的protected成員 public繼承 protected
基類中的private成員 public繼承 不可通路
基類中的public成員 protected繼承 protected
基類中的protected成員 protected繼承 protected
基類中的private成員 protected繼承 不可通路
基類中的public成員 private繼承 private
基類中的protected成員 private繼承 private
基類中的private成員 private繼承 不可通路

注意:派生類的對象隻可通路public繼承的基類中的public成員。

48:指針和引用的相同點與差別?

相同點:

1. 都是位址的概念;

指針指向一塊記憶體,它的内容是所指記憶體的位址;引用是某塊記憶體的别名。

差別:

1. 指針是一個實體,而引用僅是個别名;

2. 引用使用時無需解引用(*),指針需要解引用;

3. 引用隻能在定義時被初始化一次,之後不可變;指針可變;

4. 引用不能為空,指針可以為空;

5. “sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身(所指向的變量或對象的位址)的大小;

6. 指針和引用的自增(++)運算意義不一樣;引用的++實際上是綁定引用的對象的++,而指針的++,将指向指針之後的記憶體

7.從記憶體配置設定上看:程式為指針變量配置設定記憶體區域,而引用不需要配置設定記憶體區域。

49:初始化與指派的差別?

初始化不是指派,初始化的含義是建立變量時賦予其一個初始值,而指派的含義是把對象的目前值擦除,而以一個新值來替代。

50:聲明與定義的差別?

為了支援分離式編譯,C++将聲明和定義區分開來。聲明使得名字為程式所知,一個檔案如果想使用别處定義的名字則必須包含對那個名字的聲明。定義負責建立與名字關聯的實體。

簡單來說,聲明規定了變量的類型和名字,在這一點上與定義相同。但定義還申請了記憶體空間,也可能會為變量賦一個初始值。

繼續閱讀