C++ 筆試、面試題目大匯總二

12. 已知String類定義如下

class String
{
public:
String(const char *str = NULL); // 通用構造函數
String(const String &another); // 拷貝構造函數
~String(); // 析構函數
String& operater =(const String &rhs); // 賦值函數
private:
char* m_data; // 用於保存字符串
};

嘗試寫出類的成員函數實現。

答案:


13. .h頭文件中的ifndef/define/endif 的作用?

答:防止該頭文件被重複引用。

14. #include 與#include "file.h"的區別?

答:前者是從Standard Library的路徑尋找和引用file.h,而後者是從當前工作路徑搜尋並引用file.h。

15.在C++程序中調用被C 編譯器編譯後的函數,為什麼要加extern “C”?

首先,作為extern是C/C++語言中表明函數和全局變量作用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量可以在本模塊或其它模塊中使用

通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變量和函數時只需包含模塊A的頭文件即可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,但是並不會報錯;它會在連接階段中從模塊A編譯生成的目標代碼中找到此函數

extern "C"是連接申明(linkage declaration),被extern "C"修飾的變量和函數是按照C語言方式編譯和連接的,來看看C++中對類似。

C的函數是怎樣編譯的:

作為一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯後在符號庫中的名字與C語言的不同。例如,假設某個函數的原型為:

void foo( int x, int y );

該函數被C編譯器編譯後在符號庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都採用了相同的機制,生成的新名字稱為“mangled name”)。

_foo_int_int 這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者為_foo_int_float。

同 樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,我們以"."來區分。而本質上,編譯器在進行編譯時,與函數的處理相似,也為類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不同。

未加extern "C"聲明時的連接方式

假設在C++中,模塊A的頭文件如下:

// 模塊A頭文件 moduleA.h

#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif

在模塊B中引用該函數:

// 模塊B實現文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);

實際上,在連接階段,連接器會從模塊A生成的目標文件moduleA.obj中尋找_foo_int_int這樣的符號!

加extern "C"聲明後的編譯和連接方式

加extern "C"聲明後,模塊A的頭文件變為:

// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern"C"int foo( int x, int y );
#endif 

在模塊B的實現文件中仍然調用foo( 2,3 ),其結果是:

(1)模塊A編譯生成foo的目標代碼時,沒有對其名字進行特殊處理,採用了C語言的方式;

(2)連接器在為模塊B的目標代碼尋找foo(2,3)調用時,尋找的是未經修改的符號名_foo。

如果在模塊A中函數聲明瞭foo為extern "C"類型,而模塊B中包含的是extern int foo( int x, int y ) ,則模塊B找不到模塊A中的函數;反之亦然。

所以,可以用一句話概括extern “C”這個聲明的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源於真實世界的需求驅動。我們在思考問題時,不能只停留在這個語言是怎麼做的,還要問一問它為什麼要這麼做,動機是什麼,這樣我們可以更深入地理解許多問題):實現C++與C及其它語言的混合編程。

明白了C++中extern "C"的設立動機,我們下面來具體分析extern "C"通常的使用技巧:

extern "C"的慣用法

(1)在C++中引用C語言中的函數和變量,在包含C語言頭文件(假設為cExample.h)時,需進行下列處理:

extern"C"
{
#include"cExample.h"
}

而在C語言的頭文件中,對其外部函數只能指定為extern類型C語言中不支持extern "C"聲明

,在.c文件中包含了extern"C"時會出現編譯語法錯誤。

C++引用C函數例子工程中包含的三個文件的源代碼如下:

/* c語言頭文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
externint add(int x, inty);
#endif
/* c語言實現文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++實現文件,調用add:cppFile.cpp
extern"C"
{
#include"cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return0;

如果C++調用一個C語言編寫的.DLL時,當包括.DLL的頭文件或聲明接口函數時,應加extern "C" { }。

(2)在C中引用C++語言中的函數和變量時,C++的頭文件需添加extern "C",但是在C語言中不能直接引用聲明瞭extern "C"的該頭文件,應該僅將C文件中將C++中定義的extern"C"函數聲明為extern類型。

C引用C++函數例子工程中包含的三個文件的源代碼如下:

//C++頭文件cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern"C"int add( int x, int y );
#endif
//C++實現文件 cppExample.cpp
#include"cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C實現文件 cFile.c
/* 這樣會編譯出錯:#i nclude "cExample.h" */
externint add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return0;
}

16. 關聯、聚合(Aggregation)以及組合(Composition)的區別?

涉及到UML中的一些概念:

關聯是表示兩個類的一般性聯繫,比如“學生”和“老師”就是一種關聯關係;

聚合表示has-a的關係,是一種相對鬆散的關係,聚合類不需要對被聚合類負責,如下圖所示,用空的菱形表示聚合關係:

從實現的角度講,聚合可以表示為:

class A {...} class B { A* a; .....}

組合表示contains-a的關係,關聯性強於聚合:組合類與被組合類有相同的生命週期,組合類要對被組合類負責,採用實心的菱形表示組合關係:

實現的形式是:

class A{...} class B{ A a; ...}

17.面向對象的三個基本特徵,並簡單敘述之?

1. 封裝:將客觀事物抽象成類,每個類對自身的數據和方法實行protection(private, protected,public)

2. 繼承:廣義的繼承有三種實現形式:實現繼承(指使用基類的屬性和方法而無需額外編碼的能力)、可視繼承(子窗體使用父窗體的外觀和實現代碼)、接口繼承(僅使用屬性和方法,實現滯後到子類實現)。前兩種(類繼承)和後一種(對象組合=>接口繼承以及純虛函數)構成了功能複用的兩種方式。

3. 多態:系統能夠在運行時,能夠根據其類型確定調用哪個重載的成員函數的能力,稱為多態性。(見:C++中類的多態與虛函數的使用)

18. 重載(overload)和重寫(overried,有的書也叫做“覆蓋”)的區別?

常考的題目。

從定義上來說:

重載:是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。

重寫:是指子類重新定義父類虛函數的方法。

從實現原理上來說:

重載:編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然後這些同名函數就成了不同的函數(至少對於編譯器來說是這樣的)。如,有兩個同名函數:function func(p:integer):integer;和function func(p:string):integer;。那麼編譯器做過修飾後的函數名稱可能是這樣的:int_func、str_func。對於這兩個函數的調用,在編譯器間就已經確定了,是

靜態的。也就是說,它們的地址在編譯期就綁定了(早綁定),因此,重載和多態無關

重寫:和多態真正相關。當子類重新定義了父類的虛函數後,父類指針根據賦給它的不同的子類指針,動態的調用屬於子類的該函數,這樣的函數調用在編譯期間是無法確定的(調用的子類的虛函數的地址無法給出)。因此,這樣的函數地址是在運行期綁定的(晚綁定)。

19. 多態的作用?

主要是兩個:

1. 隱藏實現細節,使得代碼能夠模塊化;擴展代碼模塊,實現代碼重用;

2. 接口重用:為了類在繼承和派生的時候,保證使用家族中任一類的實例的某一屬性時的正確調用

20. Ado與Ado.net的相同與不同?

除了“能夠讓應用程序處理存儲於DBMS 中的數據“這一基本相似點外,兩者沒有太多共同之處。但是Ado使用OLE DB 接口並基於微軟的COM 技術,而ADO.NET 擁有自己的ADO.NET 接口並且基於微軟的.NET 體系架構。眾所周知.NET 體系不同於COM 體系,ADO.NET 接口也就完全不同於ADO和OLE DB 接口,這也就是說ADO.NET 和ADO是兩種數據訪問方式。ADO.net 提供對XML 的支持。


分享到:


相關文章: