當前位置:網站首頁>打工人的面試題集錦

打工人的面試題集錦

2021-08-20 01:28:42 C語言與CPP編程


前言: 經常回顧C/C++面試題和筆試題,有助於我們對C/C++基礎有一個新的認識和記憶。在實際工作中,大部分人會被業務纏身,基礎慢慢的遺忘了,對很多基礎知識欲言又止!回顧筆試面試題目,會讓我們對基礎知識掌握更加牢固,同時筆試題也有助於我們開闊思路,提高編程能力,還有就是開源代碼閱讀,只有不斷的回顧和學習,才能不斷的進步。

加油吧!打工人!~


面試題 1:內存對齊的原則以及作用?

(1)結構體內的成員按自身長度自對齊(32比特機器上,如char=1,short=2,int=4,double=8),所謂自對齊是指該成員的起始地址必須是它自身長度的整數倍。如int只能以0,4,8這類地址開始。
(2)結構體的總大小為結構體的有效對齊值的整數倍(默認以結構體中最長的成員長度為有效值的整數倍,當用#pragrma pack(n)指定時,以n和結構體中最長的成員的長度中較小者為其值)。即sizeof的值,必須是其內部最大成員的整數倍,不足的要補齊。
例如:

class A
{
    char c;
    int a;
    char d;
};
 
cout << sizeof(A) << endl;
 
class B
{
    char c;
    char d;
    int a;
};
 
cout << sizeof(B) << endl;
sizeof(A)=12,sizeof(B)=8;

因為左邊是1+(3)+4+1+(3)=12,而右邊是1+1+(2)+4=8。括號中為補齊的字節。

內存對齊的作用:

1、平臺原因(移植原因):不是所有的硬件平臺都能訪問任意地址上的任意數據的;某些硬件平臺只能在某些地址處取某些特定類型的數據,否則拋出硬件异常。

2、性能原因:經過內存對齊後,CPU的內存訪問速度大大提昇。


面試題 2:變量的聲明和定義有什麼區別

為變量分配地址和存儲空間的稱為定義,不分配地址的稱為聲明。一個變量可以在多個地方聲明,但是只在一個地方定義。加入 extern 修飾的是變量的聲明,說明此變量將在文件以外或在文件後面部分定義。說明:很多時候一個變量,只是聲明不分配內存空間,直到具體使用時才初始化,分配內存空間, 如外部變量。


面試題 3:explicit關鍵字的作用

C++中, 一個參數的 構造函數(或者除了第一個參數外其餘參數都有默認值的多參構造函數), 承擔了兩個角色。1 是個 構造器 ,2 是個默認且隱含的類型轉換操作符。
所以, 有時候在我們寫下如 AAA = XXX, 這樣的代碼, 且恰好XXX的類型正好是AAA單參數構造器的參數類型, 這時候 編譯器就自動調用這個構造器, 創建一個AAA的對象。
這樣看起來好象很酷, 很方便。但在某些情况下(見下面權威的例子), 卻違背了我們(程序員)的本意。這時候就要在這個構造器前面加上explicit修飾, 指定這個構造器只能被明確的調用/使用, 不能作為類型轉換操作符被隱含的使用。

class Test1
{
public:
    Test1(int n)
    {
        num=n;
    }//普通構造函數
private:
    int num;
};
class Test2
{
public:
    explicit Test2(int n)
    {
        num=n;
    }//explicit(顯式)構造函數
private:
    int num;
};
int main()
{
    Test1 t1=12;//隱式調用其構造函數,成功
    Test2 t2=12;//編譯錯誤,不能隱式調用其構造函數
    Test2 t2(12);//顯式調用成功
    return 0;
}

Test1的 構造函數帶一個int型的參數,代碼23行會隱式轉換成調用Test1的這個構造函數。而Test2的構造函數被聲明為explicit(顯式),這錶示不能通過隱式轉換來調用這個構造函數,因此代碼24行會出現編譯錯誤。
普通構造函數能够被 隱式調用。而explicit構造函數只能被顯式調用。


面試題 4:寫出 bool 、int、 float、指針變量與“零值”比較的 if 語句

bool 型數據: 
if( flag ) 
{ 
  A; 
} 
else 
{ 
  B; 
} 
int 型數據: 
if( 0 != flag ) 
{ 
  A; 
} 
else { 
  B; 
} 
指針型數: 
if( NULL == flag ) 
{ 
  A; 
} 
else { 
  B; 
} 
float 型數據: 
if ( ( flag >= NORM ) && ( flag <= NORM ) ) 
{ 
  A; 
} 

注意:應特別注意在 int、指針型變量和“零值”比較的時候,把“零值”放在左邊,這樣當把“==” 誤寫成“=”時,編譯器可以報錯,否則這種邏輯錯誤不容易發現,並且可能導致很嚴重的後果。


面試題 5:內存溢出,內存泄漏的原因?

內存溢出是指程序在申請內存時,沒有足够的內存空間供其使用。原因可能如下:

內存中加載的數據量過於龐大,如一次從數據庫取出過多數據
代碼中存在死循環或循環產生過多重複的對象實體
遞歸調用太深,導致堆棧溢出等
內存泄漏最終導致內存溢出
內存泄漏是指向系統申請分配內存進行使用(new),但是用完後不歸還(delete),導致占用有效內存。常見的幾種情况:

(1) 在類的構造函數和析構函數中沒有匹配的調用new和delete函數

    兩種情况下會出現這種內存泄露:一是在堆裏創建了對象占用了內存,但是沒有顯示地釋放對象占用的內存;二是在類的構造函數中動態的分配了內存,但是在析構 函數中沒有釋放內存或者沒有正確的釋放內存</br>

(2) 在釋放對象數組時在delete中沒有使用方括號

  方括號是告訴編譯器這個指針指向的是一個對象數組,同時也告訴編譯器正確的對象地址值病調用對象的析構函數,如果沒有方括號,那麼這個指針就被默認為只指向一個對象,對象數組中的其他對象的析構函數就不會被調用,結果造成了內存泄露。</br>

(3)沒有將基類的析構函數定義為虛函數

   當基類指針指向子類對象時,如果基類的析構函數不是virtual,那麼子類的析構函數將不會被調用,子類的資源沒有正確是釋放,因此造成內存泄露</br>

 參考鏈接:https://blog.csdn.net/hyqwmxsh/article/details/52813307    

 緩沖區溢出(棧溢出)</br>
 程序為了臨時存取數據的需要,一般會分配一些內存空間稱為緩沖區。如果向緩沖區中寫入緩沖區無法容納的數據,機會造成緩沖區以外的存儲單元被改寫,稱為緩沖區溢出。而棧溢出是緩沖區溢出的一種,原理也是相同的。分為上溢出和下溢出。其中,上溢出是指棧滿而又向其增加新的數據,導致數據溢出;下溢出是指空棧而又進行删除操作等,導致空間溢出。</br>


面試題 6:sizeof 和 strlen 的區別

sizeof 和 strlen 有以下區別:
1 sizeof 是一個操作符,strlen 是庫函數。
2 sizeof 的參數可以是數據的類型,也可以是變量,而 strlen 只能以結尾為‘\0‘的字符串作參數。
3 編譯器在編譯時就計算出了 sizeof 的結果。而 strlen 函數必須在運行時才能計算出來。並且 sizeof 計算的是數據類型占內存的大小,而 strlen 計算的是字符串實際的長度。
4 數組做 sizeof 的參數不退化,傳遞給 strlen 就退化為指針了。
注意:有些是操作符看起來像是函數,而有些函數名看起來又像操作符,這類容易混淆的名稱一定要加以區分,否則遇到數組名這類特殊數據類型作參數時就很容易出錯。最容易混淆為函數的操作符就是 sizeof。



面試題 7:C中的 malloc 和C++中的 new 有什麼區別malloc 和 new 有以下不同:
(1) new、delete 是操作符,可以重載,只能在 C++中使用。
(2) malloc、free 是函數,可以覆蓋,C、C++中都可以使用。
(3) new 可以調用對象的構造函數,對應的 delete 調用相應的析構函數。
(4) malloc 僅僅分配內存,free 僅僅回收內存,並不執行構造和析構函數
(5) new、delete 返回的是某種數據類型指針,malloc、free 返回的是 void 指針。
注意:malloc 申請的內存空間要用 free 釋放,而 new 申請的內存空間要用 delete 釋放,不要混用。因為兩者實現的機理不同。


面試題 8:寫一個“標准”宏 MIN
#define min(a,b)((a)<=(b)?(a):(b))
注意:在調用時一定要注意這個宏定義的副作用,如下調用:
((++*p)<=(x)?(++*p):(x)。
p 指針就自加了兩次,違背了 MIN 的本意。


9:一個指針可以是 volatile 嗎
可以,因為指針和普通變量一樣,有時也有變化程序的不可控性。常見例:子中斷服務子程序修改一個指向一個 buffer 的指針時,必須用 volatile 來修飾這個指針。
說明:指針是一種普通的變量,從訪問上沒有什麼不同於其他變量的特性。其保存的數值是個整型數據,和整型變量不同的是,這個整型數據指向的是一段內存地址。


面試題 10:a 和&a 有什麼區別
請寫出以下代碼的打印結果,主要目的是考察 a 和&a 的區別。

#include<stdio.h> 
void main( void ) 
{ 
  int a[5]={1,2,3,4,5}; 
  int *ptr=(int *)(&a+1); 
 printf("%d,%d",*(a+1),*(ptr-1));   return; 
} 

輸出結果:2,5。
注意:數組名 a 可以作數組的首地址,而&a 是數組的指針。思考,將原式的 int *ptr=(int *)(&a+1); 改為 int *ptr=(int *)(a+1);時輸出結果將是什麼呢?


面試題 11:簡述 C、C++程序編譯的內存分配情况
C、C++中內存分配方式可以分為三種:
(1) 從靜態存儲區域分配:
內存在程序編譯時就已經分配好,這塊內存在程序的整個運行期間都存在。速度快、不容易出錯,因為有系統會善後。例如全局變量,static 變量等。
(2) 在棧上分配:
在執行函數時,函數內局部變量的存儲單元都在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
(3) 從堆上分配:
即動態內存分配。程序在運行的時候用 malloc 或 new 申請任意大小的內存,程序員自己負責在何時用 free 或 delete 釋放內存。動態內存的生存期由程序員决定,使用非常靈活。如果在堆上分配了空間,就有責任回收它,否則運行的程序會出現內存泄漏,另外頻繁地分配和釋放不同大小的堆空間將會產生堆內碎塊。
一個 C、C++程序編譯時內存分為 5 大存儲區:堆區、棧區、全局區、文字常量區、程序代碼區。


12:STL中map和set的原理(關聯式容器)
map和set的底層實現主要通過紅黑樹來實現

紅黑樹是一種特殊的二叉查找樹

1)每個節點或者是黑色,或者是紅色

2)根節點是黑色

3) 每個葉子節點(NIL)是黑色。[注意:這裏葉子節點,是指為空(NIL或NULL)的葉子節點!]

4)如果一個節點是紅色的,則它的子節點必須是黑色的

5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。

特性4)5)决定了沒有一條路徑會比其他路徑長出2倍,因此紅黑樹是接近平衡的二叉樹。


面試題 13:C++文件編譯與執行的四個階段

1)預處理:根據文件中的預處理指令來修改源文件的內容

2)編譯:編譯成匯編代碼

3)匯編:把匯編代碼翻譯成目標機器指令

4)鏈接:鏈接目標代碼生成可執行程序


面試題 14:面向對象的三大特征
面向對象的三大特征是封裝性、繼承性和多態性:
 封裝性:將客觀事物抽象成類,每個類對自身的數據和方法實行 protection(private, protected, public)。
 繼承性:廣義的繼承有三種實現形式:實現繼承(使用基類的屬性和方法而無需額外編碼的能力)、可視繼承(子窗體使用父窗體的外觀和實現代碼)、接口繼承(僅使用屬性和方法,實現滯後到子類實現)。
 多態性:是將父類對象設置成為和一個或更多它的子對象相等的技術。用子類對象給父類對象賦值之後,父類對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。
說明:面向對象的三個特征是實現面向對象技術的關鍵,每一個特征的相關技術都非常的複雜,程序員應該多看、多練。


面試題 15:C++的空類有哪些成員函數
 缺省構造函數。
 缺省拷貝構造函數。
 缺省析構函數。
 缺省賦值運算符。
 缺省取址運算符。
 缺省取址運算符 const。
注意:有些書上只是簡單的介紹了前四個函數。沒有提及後面這兩個函數。但後面這兩個函數也是空類的默認函數。另外需要注意的是,只有當實際使用這些函數的時候,編譯器才會去定義它們。


面試題 16:談談你對拷貝構造函數和賦值運算符的認識
拷貝構造函數和賦值運算符重載有以下兩個不同之處:
(1) 拷貝構造函數生成新的類對象,而賦值運算符不能。
(2) 由於拷貝構造函數是直接構造一個新的類對象,所以在初始化這個對象之前不用檢驗源對象是否和新建對象相同。而賦值運算符則需要這個操作,另外賦值運算中如果原來的對象中有內存分配要先把內存釋放掉
注意:當有類中有指針類型的成員變量時,一定要重寫拷貝構造函數和賦值運算符,不要使用默認的。


面試題 17:STL中的vector的實現,是怎麼擴容的?
vector使用的注意點及其原因,頻繁對vector調用push_back()對性能的影響和原因。vector就是一個動態增長的數組,裏面有一個指針指向一片連續的空間,當空間裝不下的時候,會申請一片更大的空間,將原來的數據拷貝過去,並釋放原來的舊空間。當删除的時候空間並不會被釋放,只是清空了裏面的數據。對比array是靜態空間一旦配置了就不能改變大小。

vector的動態增加大小的時候,並不是在原有的空間上持續新的空間(無法保證原空間的後面還有可供配置的空間),而是以原大小的兩倍另外配置一塊較大的空間,然後將原內容拷貝過來,並釋放原空間。在VS下是1.5倍擴容,在GCC下是2倍擴容。

在原來空間不够存儲新值時,每次調用push_back方法都會重新分配新的空間以滿足新數據的添加操作。如果在程序中頻繁進行這種操作,還是比較消耗性能的。


面試題 18:用 C++設計一個不能被繼承的類

template <typename T> class A 
{ 
  friend T; private: 
  A() {} 
  ~A() {} 
}; 
 
class B : virtual public A<B> 
{ public: 
  B() {} 
  ~B() {} 
}; 
class C : virtual public B 
{ public: 
  C() {} 
  ~C() {} 
}; 
void main( void ) 
{ 
 B b;  //C c; 
  return; 
} 

注意:構造函數是繼承實現的關鍵,每次子類對象構造時,首先調用的是父類的構造函數,然後才是自己的。


面試題 19:STL中unordered_map和map的區別
map是STL中的一個關聯容器,提供鍵值對的數據管理。底層通過紅黑樹來實現,實際上是二叉排序樹和非嚴格意義上的二叉平衡樹。所以在map內部所有的數據都是有序的,且map的查詢、插入、删除操作的時間複雜度都是O(logN)。

unordered_map和map類似,都是存儲key-value對,可以通過key快速索引到value,不同的是unordered_map不會根據key進行排序。unordered_map底層是一個防冗餘的哈希錶,存儲時根據key的hash值判斷元素是否相同,即unoredered_map內部是無序的。


面試題 20:簡述類成員函數的重寫、重載和隱藏的區別
(1)重寫和重載主要有以下幾點不同。
 範圍的區別:被重寫的和重寫的函數在兩個類中,而重載和被重載的函數在同
 參數的區別:被重寫函數和重寫函數的參數列錶一定相同,而被重載函數和重載函數的參數列錶一定不同。
 virtual 的區別:重寫的基類中被重寫的函數必須要有 virtual 修飾,而重載函數和被重載函數可以被 virtual 修飾,也可以沒有。
(2)隱藏和重寫、重載有以下幾點不同。
 與重載的範圍不同:和重寫一樣,隱藏函數和被隱藏函數不在同一個類中。
 參數的區別:隱藏函數和被隱藏的函數的參數列錶可以相同,也可不同,但是函數名肯定要相同。當參數不相同時,無論基類中的參數是否被 virtual 修飾,基類的函數都是被隱藏,而不是被重寫。
說明:雖然重載和覆蓋都是實現多態的基礎,但是兩者實現的技術完全不相同,達到的目的也是完全不同的,覆蓋是動態態綁定的多態,而重載是靜態綁定的多態。


面試題 21:簡述多態實現的原理
編譯器發現一個類中有虛函數,便會立即為此類生成虛函數錶 vtable。虛函數錶的各錶項為指向對應虛函數的指針。編譯器還會在此類中隱含插入一個指針 vptr(對 vc 編譯器來說,它插在類的第一個比特置上)指向虛函數錶。調用此類的構造函數時,在類的構造函數中,編譯器會隱含執行 vptr 與 vtable 的關聯代碼,將 vptr 指向對應的 vtable,將類與此類的 vtable 聯系了起來。另外在調用類的構造函數時,指向基礎類的指針此時已經變成指向具體的類的 this 指針,這樣依靠此 this 指針即可得到正確的 vtable,。
如此才能真正與函數體進行連接,這就是動態聯編,實現多態的基本原理。
注意:一定要區分虛函數,純虛函數、虛擬繼承的關系和區別。牢記虛函數實現原理,因為多態 C++面試的重要考點之一,而虛函數是實現多態的基礎。


面試題 22:C++的內存管理
在C++中,內存被分成五個區:棧、堆、自由存儲區、靜態存儲區、常量區

棧:存放函數的參數和局部變量,編譯器自動分配和釋放

堆:new關鍵字動態分配的內存,由程序員手動進行釋放,否則程序結束後,由操作系統自動進行回收

自由存儲區:由malloc分配的內存,和堆十分相似,由對應的free進行釋放

全局/靜態存儲區:存放全局變量和靜態變量

常量區:存放常量,不允許被修改


面試題 23:簡述一下C++編譯過程


面試題 24:構造函數為什麼一般不定義為虛函數?而析構函數一般寫成虛函數的原因 ?
隊列和棧都是線性存儲結構,但是兩者的插入和删除數據的操作不同,隊列是“先進先出”,棧是 “後進先出”。
注意:區別棧區和堆區。堆區的存取是“順序隨意”,而棧區是“後進先出”。棧由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧。堆一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由 OS 回收。分配方式類似於鏈錶。
1、構造函數不能聲明為虛函數

1)因為創建一個對象時需要確定對象的類型,而虛函數是在運行時確定其類型的。而在構造一個對象時,由於對象還未創建成功,編譯器無法知道對象的實際類型,是類本身還是類的派生類等等

2)虛函數的調用需要虛函數錶指針,而該指針存放在對象的內存空間中;若構造函數聲明為虛函數,那麼由於對象還未創建,還沒有內存空間,更沒有虛函數錶地址用來調用虛函數即構造函數了

2、析構函數最好聲明為虛函數

首先析構函數可以為虛函數,當析構一個指向派生類的基類指針時,最好將基類的析構函數聲明為虛函數,否則可以存在內存泄露的問題。

如果析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在删除指向派生類的基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會造成派生類對象析構不完全。1、構造函數不能聲明為虛函數

1)因為創建一個對象時需要確定對象的類型,而虛函數是在運行時確定其類型的。而在構造一個對象時,由於對象還未創建成功,編譯器無法知道對象的實際類型,是類本身還是類的派生類等等

2)虛函數的調用需要虛函數錶指針,而該指針存放在對象的內存空間中;若構造函數聲明為虛函數,那麼由於對象還未創建,還沒有內存空間,更沒有虛函數錶地址用來調用虛函數即構造函數了

2、析構函數最好聲明為虛函數

首先析構函數可以為虛函數,當析構一個指向派生類的基類指針時,最好將基類的析構函數聲明為虛函數,否則可以存在內存泄露的問題。

如果析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在删除指向派生類的基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會造成派生類對象析構不完全。它與本題中的堆和棧是兩回事。堆棧只是一種數據結構,而堆區和棧區是程序的不同內存存儲區域。


面試題 25:簡述大端、小端模式
大端模式,是指數據的高字節保存在內存的低地址。
小端模式,是指數據的高字節保存在內存的高地址中

使用程序判斷大小端:

    int a=1;
    char *p=(char *)&a;
    if(*p==1) printf("小端\n");
    else      printf("大端\n");



面試題 26:.i++是否為原子操作?

不是。操作系統原子操作是不可分割的,在執行完畢不會被任何其它任務或事件中斷,分為兩種情况(兩種都應該滿足)

(1) 在單線程中, 能够在單條指令中完成的操作都可以認為是" 原子操作",因為中斷只能發生於指令之間。

(2) 在多線程中,不能被其它進程(線程)打斷的操作就叫原子操作。i++分為三個階段:

內存到寄存器
寄存器自增
寫回內存
這三個階段中間都可以被中斷分離開.



面試題 27:指針與引用的區別
(1)指針只是一個變量,只不過這個變量存儲的是一個地址;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已,不占用內存空間。(2)引用必須在定義的時候初始化,而且初始化後就不能再改變;而指針不必在定義的時候初始化,初始化後可以改變。(3)指針可以為空,但引用不能為空(這就意味著我們拿到一個引用的時候,是不需要判斷引用是否為空的,而拿到一個指針的時候,我們則需要判斷它是否為空。這點經常在判斷函數參數是否有效的時候使用。) (4)“sizeof 引用" = 指向變量的大小 , "sizeof 指針"= 指針本身的大小 (5)指針可以有多級,而引用只能是一級


面試題 28:new與malloc的區別
(1)malloc與free是C++/C語言的標准庫函數,new/delete是C++的運算符。它們都可用於申請動態內存和釋放內存。
(2)對於非內部數據類型的對象而言,光用malloc/free無法滿足動態對象的要求。對象在創建的同時要自動執行構造函數,對象在消亡之前要自動執行析構函數。
(3)new可以認為是malloc加構造函數的執行。new出來的指針是直接帶類型信息的。而malloc返回的都是void指針。


面試題 29:寫一個“標准”宏 MIN
寫一個“標准”宏 MIN,這個宏輸入兩個參數並且返回較小的一個。
【答案】
#define min(a,b)((a)<=(b)?(a):(b))
注意:在調用時一定要注意這個宏定義的副作用,如下調用:
((++*p)<=(x)?(++*p):(x)。
p 指針就自加了兩次,違背了 MIN 的本意。


面試題 30:typedef 和 define 有什麼區別
(1) 用法不同:typedef 用來定義一種數據類型的別名,增强程序的可讀性。define 主要用來定義常量,以及書寫複雜使用頻繁的宏。
(2) 執行時間不同:typedef 是編譯過程的一部分,有類型檢查的功能。define 是宏定義,是預編譯的部分,其發生在編譯之前,只是簡單的進行字符串的替換,不進行類型的檢查。
(3) 作用域不同:typedef 有作用域限定。define 不受作用域約束,只要是在 define 聲明後的引用都是正確的。(4) 對指針的操作不同:typedef 和 define 定義的指針時有很大的區別。
注意:typedef 定義是語句,因為句尾要加上分號。而 define 不是語句,千萬不能在句尾加分號。


面試題 31:關鍵字 const的作用
(1)定義變量為只讀變量,不可修改
(2)修飾函數的參數和返回值(後者應用比較少,一般為值傳遞)
(3)const成員函數(只需要在成員函數參數列錶後加上關鍵字const,如char get() const;)可以訪問const成員變量和非const成員變量,但不能修改任何變量。在聲明一個成員函數時,若該成員函數並不對數據成員進行修改操作,應盡可能將該成員函數聲明為const成員函數。
(4)const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數.即對於class A,有const A a;那麼a只能訪問A的const成員函數。而對於:A b;b可以訪問任何成員函數。

(5)使用const關鍵字修飾的變量,一定要對變量進行初始化


面試題 32:關鍵字static的作用
1)函數體內:static 修飾的局部變量作用範圍為該函數體,不同於auto變量,其內存只被分配一次,因此其值在下次調用的時候維持了上次的值

2)模塊內:static修飾全局變量或全局函數,可以被模塊內的所有函數訪問,但是不能被模塊外的其他函數訪問,使用範圍限制在聲明它的模塊內

3)類中:修飾成員變量,錶示該變量屬於整個類所有,對類的所有對象只有一份拷貝

4)類中:修飾成員函數,錶示該函數屬於整個類所有,不接受this指針,只能訪問類中的static成員變量

注意和const的區別!!!const强調值不能被修改,而static强調唯一的拷貝,對所有類的對象


面試題 33:extern關鍵字的作用
extern置於變量或函數前,用於標示變量或函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。它只要有兩個作用:

(1)當它與“C”一起連用的時候,如:extern "C" void fun(int a,int b);則告訴編譯器在編譯fun這個函數時候按著C的規矩去翻譯,而不是C++的(這與C++的重載有關,C++語言支持函數重載,C語言不支持函數重載,函數被C++編譯器編譯後在庫中的名字與C語言的不同)
(2)當extern不與“C”在一起修飾變量或函數時,如:extern int g_Int;它的作用就是聲明函數或全局變量的作用範圍的關鍵字,其聲明的函數和變量可以在本模塊或其他模塊中使用。記住它是一個聲明不是定義!也就是說B模塊(編譯單元)要是引用模塊(編譯單元)A中定義的全局變量或函數時,它只要包含A模塊的頭文件即可,在編譯階段,模塊B雖然找不到該函數或變量,但它不會報錯,它會在連接時從模塊A生成的目標代碼中找到此函數。


面試題 34:流操作符重載為什麼返回引用
在程序中,流操作符>>和<<經常連續使用。因此這兩個操作符的返回值應該是一個仍舊支持這兩個操作符的流引用。其他的數據類型都無法做到這一點。
注意:除了在賦值操作符和流操作符之外的其他的一些操作符中,如+、-、*、/等卻千萬不能返回引用。因為這四個操作符的對象都是右值,因此,它們必須構造一個對象作為返回值。


面試題 35:什麼情况下會調用拷貝構造函數(三種情况)
系統自動生成的構造函數:普通構造函數和拷貝構造函數 (在沒有定義對應的構造函數的時候)

生成一個實例化的對象會調用一次普通構造函數,而用一個對象去實例化一個新的對象所調用的就是拷貝構造函數

調用拷貝構造函數的情形:

1)用類的一個對象去初始化另一個對象的時候

2)當函數的參數是類的對象時,就是值傳遞的時候,如果是引用傳遞則不會調用

3)當函數的返回值是類的對象或者引用的時候

舉例:

#include <iostream>
#include <string>
 
using namespace std;
 
class A{
 private:
  int data;
 public:
  A(int i){ data = i;}  //自定義的構造函數
  A(A && a);     //拷貝構造函數 
  int getdata(){return data;} 
};
//拷貝構造函數 
A::A(A && a){
 data = a.data;
 cout <<"拷貝構造函數執行完畢"<<endl;
}
//參數是對象,值傳遞,調用拷貝構造函數
int getdata1(A a){
 return a.getdata();
}
//參數是引用,引用傳遞,不調用拷貝構造函數 
int getdata2(A &a){
 return a.getdata();
} 
//返回值是對象類型,會調用拷貝構造函數
 A getA1(){
  A a(0);
  return a;
 } 
 //返回值是引用類型,會調用拷貝構造函數,因為函數體內生成的對象是臨時的,離開函數就消失
 A& getA2(){
  A a(0);
  return a;
 } 
 
 int main(){
    A a1(1);  
    A b1(a1);             //用a1初始化b1,調用拷貝構造函數  
    A c1=a1;              //用a1初始化c1,調用拷貝構造函數  
  
    int i=getdata1(a1);         //函數形參是類的對象,調用拷貝構造函數  
    int j=getdata2(a1);       //函數形參類型是引用,不調用拷貝構造函數  
  
    A d1=getA1();         //調用拷貝構造函數  
    A e1=getA2();        //調用拷貝構造函數  
  
    return 0;  
}  


面試題 36:類型安全以及C++中的類型轉換?
類型安全很大程度上可以等價於內存安全,類型安全的代碼不會試圖訪問自己沒被授權的內存區域。C只在局部上下文中錶現出類型安全,比如試圖從一種結構體的指針轉換成另一種結構體的指針時,編譯器將會報告錯誤,除非使用顯式類型轉換。然而,C中相當多的操作是不安全的。

四種類型轉換:

static_cast <T*> (content)  靜態轉換.在編譯期間處理,可以實現C++中內置基本數據類型之間的相互轉換。如果涉及到類的話,static_cast只能在有相互聯系的類型中進行相互轉換,不一定包含虛函數。
dynamic_cast<T*>(content) 動態類型轉換;也是向下安全轉型;是在運行的時候執行;基類中一定要有虛函數,否則編譯不通過。在類層次間進行上行轉換時(如派生類指針轉為基類指針),dynamic_cast和static_cast的效果是一樣的。在進行下行轉換時(如基類指針轉為派生類指針),dynamic_cast具有類型檢查的功能,比static_cast更安全。
const_cast<T*>(content) 去常轉換;編譯時執行;
reinterpret_cast<T*>(content) 重解釋類型轉換;


面試題 37:如何避免“野指針”
“野指針”產生原因及解决辦法如下:
(1) 指針變量聲明時沒有被初始化。解决辦法:指針聲明時初始化,可以是具體的地址值,也可讓它指向 NULL。
(2) 指針 p 被 free 或者 delete 之後,沒有置為 NULL。解决辦法:指針指向的內存空間被釋放後指針應該指向 NULL。
(3) 指針操作超越了變量的作用範圍。解决辦法:在變量的作用域結束前釋放掉變量的地址空間並且讓指針指向 NULL。注意:“野指針”的解决方法也是編程規範的基本原則,平時使用指針時一定要避免產生“野指針”,在使用指針前一定要檢驗指針的合法性。


面試題 38:typdef和define區別
#define是預處理命令,在預處理是執行簡單的替換,不做正確性的檢查

typedef是在編譯時處理的,它是在自己的作用域內給已經存在的類型一個別名

typedef    (int*)      pINT;
#define    pINT2   int*

效果相同?實則不同!實踐中見差別:pINT a,b;的效果同int *a; int *b;錶示定義了兩個整型指針變量。而pINT2 a,b;的效果同int *a, b;錶示定義了一個整型指針變量a和整型變量b。


面試題 39:動態綁定與靜態綁定

(1)靜態綁定發生在編譯期,動態綁定發生在運行期;(2)對象的動態類型可以更改,但是靜態類型無法更改;(3)要想實現動態,必須使用動態綁定;(4)在繼承體系中只有虛函數使用的是動態綁定,其他的全部是靜態綁定;(5)靜態多態是指通過模板技術或者函數重載技術實現的多態,其在編譯器確定行為。(6)動態多態是指通過虛函數技術實現在運行期動態綁定的技術

動態綁定:有一個基類,兩個派生類,基類有一個virtual函數,兩個派生類都覆蓋了這個虛函數。現在有一個基類的指針或者引用,當該基類指針或者引用指向不同的派生類對象時,調用該虛函數,那麼最終調用的是該被指向對象對應的派生類自己實現的虛函數。


面試題 40:智能指針怎麼實現?什麼時候改變引用計數?
(1)構造函數中計數初始化為1;
(2)拷貝構造函數中計數值加1;
(3)賦值運算符中,左邊的對象引用計數减一,右邊的對象引用計數加一;
(4)析構函數中引用計數减一;
(5)在賦值運算符和析構函數中,如果减一後為0,則調用delete釋放對象。


面試題 41:棧溢出的原因以及解决方法
1)函數調用層次過深,每調用一次,函數的參數、局部變量等信息就壓一次棧

2)局部變量體積太大。

解决辦法大致說來也有兩種:

1> 增加棧內存的數目;增加棧內存方法如下,在vc6種依次選擇Project->Setting->Link,在Category中選擇output,在Reserve中輸入16進制的棧內存大小如:0x10000000

2> 使用堆內存;具體實現由很多種方法可以直接把數組定義改成指針,然後動態申請內存;也可以把局部變量變成全局變量,一個偷懶的辦法是直接在定義前邊加個static,呵呵,直接變成靜態變量(實質就是全局變量)


面試題 42:構造函數能否為虛函數
構造函數不能是虛函數。而且不能在構造函數中調用虛函數,因為那樣實際執行的是父類的對應函數,因為自己還沒有構造好。析構函數可以是虛函數,而且,在一個複雜類結構中,這往往是必須的。
析構函數也可以是純虛函數,但純虛析構函數必須有定義體,因為析構函數的調用是在子類中隱含的。
說明:虛函數的動態綁定特性是實現重載的關鍵技術,動態綁定根據實際的調用情况查詢相應類的虛函數錶,調用相應的虛函數。


面試題 43:談談你對面向對象的認識
所謂的面向對象就是將我們的程序模塊化,對象化,把具體事物的特性屬性和通過這些屬性來實現一些動作的具體方法放到一個類裏面,這就是封裝。封裝是我們所說的面相對象編程的特征之一。除此之外還有繼承和多態。繼承有點類似與我們生物學上的遺傳,就是子類的一些特征是來源於父類的,兒子遺傳了父親或母親的一些性格,或者相貌,又或者是運動天賦。有點種瓜得瓜種豆得豆的意思。面向對象裏的繼承也就是父類的相關的屬性,可以被子類重複使用,子類不必再在自己的類裏面重新定義一回,父類裏有點我們只要拿過來用就好了。而對於自己類裏面需要用到的新的屬性和方法,子類就可以自己來擴展了。當然,會出現一些特殊情况,就是我們在有一些方法在父類已經定義好了,但是子類我們自己再用的時候,發現,其實,我們的雖然都是計算工資的,但是普通員工的工資計算方法跟經理的計算方法是不一樣的,所以這個時候,我們就不能直接調用父類的這個計算工資的方法了。這個時候我們就需要用到面向對象的另一個特性,多態。對,就是多態,我們要在子類裏面把父類裏面定義計算工資的方法在子類裏面重新實現一遍。多態包含了重載和重寫。重寫很簡單就是把子類從父親類裏繼承下來的方法重新寫一遍,這樣,父類裏相同的方法就被覆蓋了,當然啦,你還是可以通過super.CaculSalary方法來調用父類的工資計算方法。而重載就是類裏面相同方法名,不同形參的情况,可以是形參類型不同或者形參個數不同,或者形參順序不同,但是不能使返回值類型不同。


面試題 44:引用作為函數參數以及返回值的好處
對比值傳遞,引用傳參的好處:

1)在函數內部可以對此參數進行修改

2)提高函數調用和運行的效率(所以沒有了傳值和生成副本的時間和空間消耗)

如果函數的參數實質就是形參,不過這個形參的作用域只是在函數體內部,也就是說實參和形參是兩個不同的東西,要想形參代替實參,肯定有一個值的傳遞。函數調用時,值的傳遞機制是通過“形參=實參”來對形參賦值達到傳值目的,產生了一個實參的副本。即使函數內部有對參數的修改,也只是針對形參,也就是那個副本,實參不會有任何更改。函數一旦結束,形參生命也宣告終結,做出的修改一樣沒對任何變量產生影響。

用引用作為返回值最大的好處就是在內存中不產生被返回值的副本。

但是有以下的限制:

1)不能返回局部變量的引用。因為函數返回以後局部變量就會被銷毀

2)不能返回函數內部new分配的內存的引用。雖然不存在局部變量的被動銷毀問題,可對於這種情况(返回函數內部new分配內存的引用),又面臨其它尷尬局面。例如,被函數返回的引用只是作為一 個臨時變量出現,而沒有被賦予一個實際的變量,那麼這個引用所指向的空間(由new分配)就無法釋放,造成memory leak

3)可以返回類成員的引用,但是最好是const。因為如果其他對象可以獲得該屬性的非常量的引用,那麼對該屬性的單純賦值就會破壞業務規則的完整性。


版權聲明
本文為[C語言與CPP編程]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2021/08/20210820012841794y.html

隨機推薦