答:引用就是某個目標變量的別名(alias)
,對應用的操做與對變量直接操做效果徹底相同。申明一個引用的時候,切記要對其進行初始化。引用聲明完畢後,至關於目標變量名有兩個名稱,即該目標原名稱
和引用名
,不能再把該引用名做爲其餘變量名的別名。聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標變量名的一個別名,它自己不是一種數據類型,所以引用自己不佔存儲單元
,系統也不給引用分配存儲單元。不能創建數組的引用。ios
(1)傳遞引用給函數與傳遞指針的效果是同樣的。這時,被調函數的形參就成爲原來主調函數中的實參變量或對象的一個別名來使用,因此在被調函數中對形參變量的操做就是對其相應的目標對象(在主調函數中)的操做。c++
(2)使用引用傳遞函數的參數,在內存中並無產生實參的副本,它是直接對實參操做;而使用通常變量傳遞函數的參數,當發生函數調用時,須要給形參分配存儲單元,形參變量是實參變量的副本;若是傳遞的是對象,還將調用拷貝構造函數。所以,當參數傳遞的數據較大時,用引用比用通常變量傳遞參數的效率和所佔空間都好。程序員
(3)使用指針做爲函數的參數雖然也能達到與使用引用的效果,可是,在被調函數中一樣要給形參分配存儲單元,且須要重複使用"*指針變量名"的形式進行運算,這很容易產生錯誤且程序的閱讀性較差;另外一方面,在主調函數的調用點處,必須用變量的地址做爲實參。而引用更容易使用,更清晰。編程
若是既要利用引用提升程序的效率,又要保護傳遞給函數的數據不在函數中被改變,就應使用常引用。常引用聲明方式:const 類型標識符 &引用名=目標變量名
;數組
int a; const int &ra = a; ra = 1; // 錯誤 a = 1; // 正確
string foo( ); void bar(string&s) //下面的表達式將是非法的: bar(foo()); bar("hello world");
緣由在於foo( )
和"hello world"
串都會產生一個臨時對象,而在C++中,這些臨時對象都是const
類型的。所以上面的表達式就是試圖將一個const
類型的對象轉換爲非const
類型,這是非法的。引用型參數應該在能被定義爲const
的狀況下,儘可能定義爲`const 。
類型標識符 &函數名(形參列表及類型說明) { //函數體 }
在內存中不產生被返回值的副本;(注意:正是由於這點緣由,因此返回一個局部變量的引用是不可取的。由於隨着該局部變量生存期的結束,相應的引用也會失效,產生
runtime error
!
(1)不能返回局部變量的引用。這條能夠參照Effective C++[1]的Item 31
。主要緣由是局部變量會在函數返回後被銷燬,所以被返回的引用就成爲了"無所指"
的引用,程序會進入未知狀態。安全
(2)不能返回函數內部new
分配的內存的引用。 這條能夠參照Effective C++[1]的Item 31
。雖然不存在局部變量的被動銷燬問題,可對於這種狀況(返回函數內部new
分配內存的引用),又面臨其它尷尬局面。例如,被函數返回的引用只是做爲一個臨時變量出現,而沒有被賦予一個實際的變量,那麼這個引用所指向的空間(由new
分配)就沒法釋放,形成memory leak
。架構
(3)能夠返回類成員的引用,但最好是const
。 這條原則能夠參照Effective C++[1]的Item 30
。主要緣由是當對象的屬性是與某種業務規則(business rule
)相關聯的時候,其賦值經常與某些其它屬性或者對象的狀態有關,所以有必要將賦值操做封裝在一個業務規則當中。若是其它對象能夠得到該屬性的很是量引用(或指針)
,那麼對該屬性的單純賦值就會破壞業務規則的完整性。ide
(4)流操做符重載返回值申明爲「引用」
的做用:模塊化
流操做符<<
和>>
,這兩個操做符經常但願被連續使用,例如:cout <<"hello" << endl
; 所以這兩個操做符的返回值應該是一個仍然支持這兩個操做符的流引用。可選的其它方案包括:返回一個流對象和返回一個流對象指針。可是對於返回一個流對象,程序必須從新(拷貝)構造一個新的流對象,也就是說,連續的兩個<<
操做符其實是針對不一樣對象的!這沒法讓人接受。對於返回一個流指針則不能連續使用<<
操做符。 所以,返回一個流對象引用是唯一選擇。這個惟一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是C++語言中引入引用
這個概念的緣由吧。 賦值操做符=
。這個操做符象流操做符同樣,是能夠連續使用的,例如:x = j = 10
;或者(x=10)=100
;賦值操做符的返回值必須是一個左值,以即可以被繼續賦值。所以引用成了這個操做符的唯一返回值選擇。
#include <iostream.h> int &put(int n); int vals[10]; int error = -1; void main(){ put(0) = 10; // 以put(0)函數值做爲左值,等價於vals[0]=10; put(9) = 20; // 以put(9)函數值做爲左值,等價於vals[9]=20; cout << vals[0]; cout << vals[9]; } int &put(int n){ if (n>=0 && n<=9) { return vals[n]; }else { cout << "subscript error"; return error; } }
(5)在另外的一些操做符中,卻千萬不能返回引用:+-*/
四則運算符。它們不能返回引用,Effective C++[1]的Item23
詳細的討論了這個問題。主要緣由是這四個操做符沒有side effect
,所以,它們必須構造一個對象做爲返回值,可選的方案包括:返回一個對象、返回一個局部變量的引用,返回一個new
分配的對象的引用、返回一個靜態對象引用。根據前面提到的引用做爲返回值的三個規則,第二、3兩個方案都被否決了。靜態對象的引用又由於((a+b) == (c+d))
會永遠爲true
而致使錯誤。因此可選的只剩下返回一個對象了。函數
引用是除指針外另外一個能夠產生多態效果的手段。這意味着,一個基類的引用能夠指向它的派生類實例(見:C++中類的多態與虛函數的使用)。
Class A; Class B : Class A{ // ... }; B b; A &ref= b;
指針經過某個指針變量指向一個對象後,對它所指向的變量間接操做。程序中使用指針,程序的可讀性差;而引用自己就是目標變量的別名,對引用的操做就是對目標變量的操做。此外,就是上面提到的對函數傳ref
和pointer
的區別。
流操做符<<
和>>
、賦值操做符=的返回值
、拷貝構造函數的參數、賦值操做符=的參數
、其它狀況都推薦使用引用。
結構
和聯合
都是由多個不一樣的數據類型成員組成, 但在任何同一時刻, 聯合中只存放了一個被選中的成員(全部成員共用一塊地址空間), 而結構的全部成員都存在(不一樣成員的存放地址不一樣)。class String{ public: String(const char *str = NULL); // 通用構造函數 String(const String &another); // 拷貝構造函數 ~String(); // 析構函數 String& operater =(const String &rhs); // 賦值函數 private: char* m_data; // 用於保存字符串 };
嘗試寫出類的成員函數實現。
String::String(const char *str){ if ( str == NULL ){ // strlen在參數爲NULL時會拋異常纔會有這步判斷 m_data =newchar[1] ; m_data[0] ='\0' ; }else{ m_data =newchar[strlen(str) +1]; strcpy(m_data,str); } } String::String(const String &another){ m_data =newchar[strlen(another.m_data) +1]; strcpy(m_data,other.m_data); } String& String::operator=(const String &rhs){ if ( this==&rhs) return*this ; delete []m_data; //刪除原來的數據,新開一塊內存 m_data =newchar[strlen(rhs.m_data) +1]; strcpy(m_data,rhs.m_data); return*this ; } String::~String(){ delete []m_data ; }
.h
頭文件中的ifndef
/define
/endif
的做用?防止該頭文件被重複引用。
#include <file.h>
與 #include "file.h"
的區別?前者是從Standard Library
的路徑尋找和引用file.h
,然後者是從當前工做路徑
搜尋並引用file.h
。
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語言的不一樣。例如,假設某個函數的原型爲: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"
聲明後,模塊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 )
,其結果是:
foo
的目標代碼時,沒有對其名字進行特殊處理,採用了C語言的方式;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"
的慣用法在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 extern int 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" { }
。
在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 /* 這樣會編譯出錯:#include "cExample.h" */ externint add( int x, int y ); int main( int argc, char* argv[] ){ add( 2, 3 ); return0; }
關聯
、聚合
(Aggregation
)以及組合
(Composition
)的區別?涉及到UML中的一些概念:
關聯
是表示兩個類的通常性聯繫,好比「學生」和「老師」就是一種關聯關係;聚合
表示has-a
的關係,是一種相對鬆散的關係,聚合類不須要對被聚合類負責,以下圖所示,用空的菱形表示聚合關係:從實現的角度講,聚合能夠表示爲:
class A {...} class B { A* a; .....}
組合
表示contains-a
的關係,關聯性強於聚合:組合類與被組合類有相同的生命週期,組合類要對被組合類負責,採用實心的菱形表示組合關係:實現的形式是:
class A{...} class B{ A a; ...}
面向對象
的三個基本特徵,並簡單敘述之?封裝
:將客觀事物抽象成類,每一個類對自身的數據和方法實行protection
(private
, protected
,public
)繼承
:廣義的繼承有三種實現形式:實現繼承
(指使用基類的屬性和方法而無需額外編碼的能力)、可視繼承
(子窗體使用父窗體的外觀和實現代碼)、接口繼承
(僅使用屬性和方法,實現滯後到子類實現)。前兩種(類繼承)和後一種(對象組合=>接口繼承以及純虛函數)構成了功能複用的兩種方式。多態
:系統可以在運行時,可以根據其類型肯定調用哪一個重載的成員函數的能力,稱爲多態性。(見:C++中類的多態與虛函數的使用)重載
(overload
)和重寫
(override
,有的書也叫作「覆蓋overwrite」
)的區別?重載
:是指容許存在多個同名函數,而這些函數的參數表不一樣(或許參數個數不一樣,或許參數類型不一樣,或許二者都不一樣)。重寫
:是指子類從新定義父類虛函數的方法。重載
:編譯器根據函數不一樣的參數表,對同名函數的名稱作修飾,而後這些同名函數就成了不一樣的函數(至少對於編譯器來講是這樣的)。如,有兩個同名函數:function func(p:integer):integer;和function func(p:string):integer;
。那麼編譯器作過修飾後的函數名稱多是這樣的:int_func、str_func
。對於這兩個函數的調用,在編譯器間就已經肯定了,是靜態的。也就是說,它們的地址在編譯期就綁定了(早綁定),所以,重載和多態無關!重寫
:和多態真正相關。當子類從新定義了父類的虛函數後,父類指針根據賦給它的不一樣的子類指針,動態的調用屬於子類的該函數,這樣的函數調用在編譯期間是沒法肯定的(調用的子類的虛函數的地址沒法給出)。所以,這樣的函數地址是在運行期綁定的(晚綁定)。多態
的做用?隱藏實現細節
,使得代碼可以模塊化;擴展代碼模塊,實現代碼重用;接口重用
:爲了類在繼承和派生的時候,保證使用家族中任一類的實例的某一屬性時的正確調用。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
的支持。
New
delete
與malloc
free
的聯繫與區別?都是在堆
(heap
)上進行動態的內存操做。用malloc
函數須要指定內存分配的字節數而且不能初始化對象,new
會自動調用對象的構造函數。delete
會調用對象的destructor
,而free
不會調用對象的destructor
.
intializationlist
而不能用assignment
?當類中含有const
、reference
成員變量;基類的構造函數都須要初始化表。
不是。兩個不一樣類型的指針之間能夠強制轉換(用reinterpret cast
)。C#是類型安全的。
main
函數執行之前,還會執行什麼代碼?全局對象的構造函數會在main
函數以前執行,爲malloc
分配必要的資源,等等。
靜態存儲區域
分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量
,static
變量。棧
上建立。在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集。堆
上分配,亦稱動態內存分配
。程序在運行的時候用malloc
或new
申請任意多少的內存,程序員本身負責在什麼時候用free
或delete
釋放內存。動態內存的生存期由程序員決定,使用很是靈活,但問題也最多。struct
和 class
的區別struct
的成員默認是公有的,而類的成員默認是私有的。struct
和 class
在其餘方面是功能至關的。從感情上講,大多數的開發者感到類
和結構
有很大的差異。感受上結構僅僅象一堆缺少封裝和功能的開放的內存位,而類就象活的而且可靠的社會成員,它有智能服 務,有牢固的封裝屏障和一個良好定義的接口。既然大多數人都這麼認爲,那麼只有在你的類有不多的方法而且有公有數據(這種事情在良好設計的系統中是存在的!)時,你也許應該使用 struct
關鍵字,不然,你應該使用 class
關鍵字。
sizeof(A)
的值是多少,若是不是零,請解釋一下編譯器爲何沒有讓它爲零。(Autodesk
)確定不是零。舉個反例,若是是零的話,聲明一個class A[10]
對象數組,而每個對象佔用的空間是零,這時就沒辦法區分A[0]
,A[1]…
了。
8086
彙編下,邏輯地址
和物理地址
是怎樣轉換的?(Intel
)通用寄存器給出的地址,是段內偏移地址,相應段寄存器地址*10H+通用寄存器內地址,就獲得了真正要訪問的地址。
重點是static_cast
, dynamic_cast
和reinterpret_cast
的區別和應用。
BOOL
,int
,float
,指針類型的變量a
與「零」
的比較語句。if ( !a ) or if(a)
if ( a ==0)
const EXPRESSION EXP = 0.000001; if ( a < EXP&& a >-EXP)
if ( a != NULL) or if(a == NULL)
const
與#define
相比,有何優勢?const
常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換可能會產生意料不到的錯誤。數組
與指針
的區別?數組要麼在靜態存儲區被建立(如全局數組),要麼在棧上被建立。指針能夠隨時指向任意類型的內存塊。
char a[] = 「hello」; a[0] = ‘X’; char *p = 「world」; // 注意p 指向常量字符串 p[0] = ‘X’; // 編譯器不能發現該錯誤,運行時錯誤
sizeof
能夠計算出數組的容量(字節數
)。sizeof(p)
,p
爲指針獲得的是一個指針變量的字節數,而不是p
所指的內存容量。C++/C 語言沒有辦法知道指針所指的內存容量,除非在申請內存時記住它。注意當數組做爲函數的參數進行傳遞時,該數組自動退化爲同類型的指針。char a[] ="hello world"; char*p = a; cout<<sizeof(a) << endl; // 12 字節 cout<<sizeof(p) << endl; // 4 字節
void Func(char a[100]){ cout<<sizeof(a) << endl; // 4 字節而不是100 字節 }
重載
、覆蓋
和隱藏
區別?virtual
關鍵字無關緊要。virtual
關鍵字。virtual
關鍵字,基類的函數將被隱藏(注意別與重載混淆)。virtual
關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)