1,關於動態申請內存linux
答:內存分配方式三種:ios
(1)從靜態存儲區域分配:內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。c++
全局變量,static變量。程序員
(2)在棧上建立:在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,面試
函數執行結束時這些存儲單元自動被釋放。算法
棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。編程
(3)用malloc或new申請內存以後,應該當即檢查指針值是否爲NULL.防止使用指針值爲NULL的內存,windows
不要忘記爲數組和動態內存賦初值。防止將未被初始化的內存做爲右值使用。避免數組或指針的下標越界,數組
特別要小心發生「多1」或者「少1」操做。動態內存的申請與釋放必須配對,防止內存泄漏。安全
用free或delete釋放了內存以後,當即將指針設置爲NULL,防止產生「野指針」。從堆上分配,亦稱動態內存分配。
程序在運行的時候用malloc或new申請任意多少的內存,程序員本身負責在什麼時候用free或delete釋放內存。
動態內存的生存期由程序員決定,使用很是靈活。(int *pArray; int MyArray[6]; pArray = &MyArray[0];)
若是在申請動態內存時找不到足夠大的內存塊,malloc和new將返回NULL指針,
判斷指針是否爲NULL,若是是則立刻用return語句終止本函數,
或者立刻用exit(1)終止整個程序的運行,爲new和malloc設置異常處理函數。
2,C++指針攻破
答案:指針是一個變量,專門存放內存地址,特色是能訪問所指向的內存
指針自己佔據了4個字節的長度
int **ptr; //指針的類型是 int **
int (*ptr)[3]; //指針的類型是 int(*)[3]
int *(*ptr)[4]; //指針的類型是 int *(*)[4]
ptr++:指針ptr的值加上了sizeof(int)
ptr+=5:將指針ptr的值加上5*sizeof(int)
指針的賦值:
把一個變量的地址賦予指向相同數據類型的指針變量( int a; int *ip; ip=&a; )
把一個指針變量的值賦予指向相同類型變量的另外一個指針變量(int a; int *pa=&a; int *pb; pb=pa; )
把數組的首地址賦予指向數組的指針變量(int a[5],*pa; pa=a; 也可寫爲:pa=&a[0];)
若是給指針加1或減1 ,其實是加上或減去指針所指向的數據類型大小。
當給指針加上一個整數值或減去一個整數值時,表達式返回一個新地址。
相同類型的兩個指針能夠相減,減後返回的整數表明兩個地址間該類型的實例個數。
int ** cc=new (int*)[10]; 聲明一個10個元素的數組,數組每一個元素都是一個int *指針,
每一個元素還能夠單獨申請空間,由於cc的類型是int*型的指針,因此你要在堆裏申請的話就要用int *來申請;
int ** a= new int * [2]; //申請兩個int * 型的空間
a[0] = new int[4]; ////爲a的第一個元素申請了4個int 型空間,a[0] 指向了此空間的首地址處
a[1] = new int[3]; //爲a的第二個元素又申請了3個int 型空間,a[1]指向了此空間首地址處
指針數組初始化賦值:
一維指針開闢空間:char *str;int *arr; scanf("%d",&N);
str=(char*)malloc(sizeof(char)*N);
arr=(int*)malloc(sizeof(int)*N);
二維指針開闢空間:int **arr, i; scanf("%d%d",&row,&col);
arr=(int**)malloc(sizeof(int)*row);
for(i=0;i<row;i++)
arr[i]=(int*)malloc(sizeof(int)*col);
結構體指針數組,例如typedef struct{ char x; int y; }Quan,*QQuan;
定義一個結構體指針數組如:QQuan a[MAX]
for(i=0;i<MAX;i++)
{
a[i]=(QQuan)malloc(sizeof(Quan));
memset(a[i],0,sizeof(Quan));
}
指針數組賦值
float a[]={100,200,300,400,500};
float *p[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
char *units[1000];
char get_unit[250];
for(int i=0;i<get_unit_num;i++){ units[i]=(char*) malloc(60*sizeof(char*));
scanf("%s", get_unit); strcpy(units[i],get_unit);}
3,複雜指針解析:
(1)int (*func)(int *p);
(*func)()是一個函數,func是一個指向這類函數的指針,就是一個函數指針,這類函數具備int*類型的形參,返回值類型是 int。
(2)int (*func)(int *p, int (*f)(int*));
func是一個指向函數的指針,這類函數具備int *和int (*)(int*)這樣的形參。形參int (*f)(int*),f也是一個函數指針
(3)int (*func[5])(int *p);
func數組的元素是函數類型的指針,它所指向的函數具備int*類型的形參,返回值類型爲int。
(4)int (*(*func)[5])(int *p);
func是一個指向數組的指針,這個數組的元素是函數指針,這些指針指向具備int*形參,返回值爲int類型的函數。
(5)int (*(*func)(int *p))[5];
func是一個函數指針,這類函數具備int*類型的形參,返回值是指向數組的指針,所指向的數組的元素是具備5個int元素的數組。
注意:
須要聲明一個複雜指針時,若是把整個聲明寫成上面所示的形式,對程序可讀性是一大損害。
應該用typedef來對聲明逐層,分解,加強可讀性,例如對於聲明:int (*(*func)(int *p))[5];
這樣分解:typedef int (*PARA)[5]; typedef PARA (*func)(int *);
例如:int (*(*func)[5][6])[7][8];
func是一個指向數組的指針,這類數組的元素是一個具備5X6個int元素的二維數組,而這個二維數組的元素又是一個二維數組。
typedef int (*PARA)[7][8];
typedef PARA (*func)[5][6];
例如:int (*(*(*func)(int *))[5])(int *);
func是一個函數指針,這類函數的返回值是一個指向數組的指針,
所指向數組的元素也是函數指針,指向的函數具備int*形參,返回值爲int。
typedef int (*PARA1)(int*);
typedef PARA1 (*PARA2)[5];
typedef PARA2 (*func)(int*);
4,函數指針詳解
答:
函數指針是指向一個函數入口的指針
一個函數指針只能指向一種類型的函數,即具備相同的返回值和相同的參數的函數。
函數指針數組定義:void(*fun[3])(void*); 相應指向類A的成員函數的指針:void (A::*pmf)(char *, const char *);
指向外部函數的指針:void (*pf)(char *, const char *); void strcpy(char * dest, const char * source); pf=strcpy;
5,野指針
答:「野指針」是很危險的,if語句對它不起做用。「野指針」的成因主要有兩種:
(1)指針變量沒有被初始化。指針變量在建立的同時應當被初始化,要麼將指針設置爲NULL,要麼讓它指向合法的內存。
char *p = NULL; char *str = (char *) malloc(100);
(2)指針p被free或者delete以後,沒有置爲NULL
(3)指針操做超越了變量的做用範圍。所指向的內存值對象生命期已經被銷燬
6,引用和指針有什麼區別?
答:引用必須初始化,指針則沒必要;引用初始化之後不能改變,指針能夠改變其指向的對象;
不存在指向空值的引用,但存在指向控制的指針;
引用是某個對象的別名,主要用來描述函數和參數和返回值。而指針與通常的變量是同樣的,會在內存中開闢一塊內存。
若是函數的參數或返回值是類的對象的話,採用引用能夠提升程序的效率。
7,C++中的Const用法
答:char * const p; // 指針不可改,也就說指針只能指向一個地址,不能更改成其餘地址,修飾指針自己
char const * p; // 所指內容不可改,也就是說*p是常量字符串,修飾指針所指向的變量
const char * const p 和 char const * const p; // 內容和指針都不能改
const修飾函數參數是它最普遍的一種用途,它表示函數體中不能修改參數的值,
傳遞過來的參數在函數內不能夠改變,參數指針所指內容爲常量不可變,參數指針自己爲常量不可變
在引用或者指針參數的時候使用const限制是有意義的,而對於值傳遞的參數使用const則沒有意義
const修飾類對象表示該對象爲常量對象,其中的任何成員都不能被修改。
const修飾的對象,該對象的任何非const成員函數都不能被調用,由於任何非const成員函數會有修改爲員變量的企圖。
const修飾類的成員變量,表示成員常量,不能被修改,同時它只能在初始化列表中賦值。static const 的成員需在聲明的地方直接初始。
const修飾類的成員函數,則該成員函數不能修改類中任何非const成員。通常寫在函數的最後來修飾。
在函數實現部分也要帶const關鍵字.
對於const類對象/指針/引用,只能調用類的const成員函數,所以,const修飾成員函數的最重要做用就是限制對於const對象的使用
使用const的一些建議:在參數中使用const應該使用引用或指針,而不是通常的對象實例
const在成員函數中的三種用法(參數、返回值、函數)要很好的使用;
const在成員函數中的三種用法(參數、返回值、函數)要很好的使用;
不要輕易的將函數的返回值類型定爲const;除了重載操做符外通常不要將返回值類型定爲對某個對象的const引用;
8,const常量與define宏定義的區別
答:(1) 編譯器處理方式不一樣。define宏是在預處理階段展開,生命週期止於編譯期。
只是一個常數、一個命令中的參數,沒有實際的存在。
#define常量存在於程序的代碼段。const常量是編譯運行階段使用,const常量存在於程序的數據段.
(2)類型和安全檢查不一樣。define宏沒有類型,不作任何類型檢查,僅僅是展開。
const常量有具體的類型,在編譯階段會執行類型檢查。
(3) 存儲方式不一樣。define宏僅僅是展開,有多少地方使用,就展開多少次,不會分配內存。
const常量會在內存中分配(能夠是堆中也能夠是棧中)
9,解釋堆和棧的區別
答:一、棧區(stack)— 由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。
由系統自動分配。聲明在函數中一個局部變量 int b; 系統自動在棧中爲b開闢空間 。
只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域,棧的大小是2M。
若是申請的空間超過棧的剩餘空間時,將提示overflow。
棧由系統自動分配,速度較快。但程序員是沒法控制的。
函數調用時,第一個進棧的是主函數中後的下一條指令,的地址,而後是函數的各個參數。
在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。
堆區(heap) — 通常由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收 。
注意它與數據結構中的堆是兩回事,分配方式卻是相似於鏈表,須要程序員本身申請,並指明大小,在c中malloc函數
在C++中用new運算符。首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,
另外,因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中。
堆是向高地址擴展的數據結構,是不連續的內存區域。而鏈表的遍歷方向是由低地址向高地址。
堆的大小受限於計算機系統中有效的虛擬內存。
堆是由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便
通常是在堆的頭部用一個字節存放堆的大小。
10,論述含參數的宏和函數的優缺點
(1)函數調用時,先求出實參表達式的值,而後代入形參。而使用帶參的宏只是進行簡單的字符替換
(2)函數調用是在程序運行時處理的,分配臨時的內存單元;而宏展開是在編譯時進行的,在展開時不進行
內存分配,不進行值得傳遞處理,沒有「返回值」概念
(3)對函數中的形參和實參都要定義類型,類型要求一致,如不一致則進行類型轉換。而宏不存在類型問題
(4)調用函數只可獲得一個返回值,而用宏則能夠設法獲得幾個結果
(5)實用宏次數多時,宏展開後源程序變長,沒展開一次源程序增加,函數調用則不會
(6)宏替換不佔用運行時間,只佔編譯時間,而函數調用佔用運行時間
11,C++的空類,默認產生哪些類成員函數?
答:class Empty
{
public:
Empty(); //缺省構造函數
Empty(const Empty& ); //拷貝構造函數
~Empty(); //虛構函數
Empty& operator(const Empty& ) //賦值運算符
Empty& operator&(); //取址運算符
const Empty* operator&() const; // 取址運算符 const
}
12,談談類和結構體的區別
答:結構體在默認狀況下的成員都是public的,而類在默認狀況下的成員是private的。結構體和類都必須使用new建立,
struct保證成員按照聲明順序在內存在存儲,而類不保證。
13,C++四種強制類型轉換
答:(1)const_cast
字面上理解就是去const屬性,去掉類型的const或volatile屬性。
struct SA{ int k}; const SA ra;
ra.k = 10; //直接修改const類型,編譯錯誤 SA& rb = const_cast<SA&>(ra); rb.k = 10; //能夠修改
(2)static_cast
主要用於基本類型之間和具備繼承關係的類型之間的轉換。用於指針類型的轉換沒有太大的意義
static_cast是無條件和靜態類型轉換,可用於基類和子類的轉換,基本類型轉換,把空指針轉換爲目標類型的空指針,
把任何類型的表達式轉換成void類型,static_cast不能進行無關類型(如非基類和子類)指針之間的轉換。
int a; double d = static_cast<double>(a); //基本類型轉換
int &pn = &a; void *p = static_cast<void*>(pn); //任意類型轉換爲void
(3)dynamic_cast
你能夠用它把一個指向基類的指針或引用對象轉換成繼承類的對象
動態類型轉換,運行時類型安全檢查(轉換失敗返回NULL)
基類必須有虛函數,保持多態特性才能用dynamic_cast
只能在繼承類對象的指針之間或引用之間進行類型轉換
class BaseClass{public: int m_iNum; virtual void foo(){};};
class DerivedClass:BaseClass{public: char* szName[100]; void bar(){};};
BaseClass* pb = new DerivedClass();
DerivedClass *p2 = dynamic_cast<DerivedClass *>(pb);
BaseClass* pParent = dynamic_cast<BaseClass*>(p2);
//子類->父類,動態類型轉換,正確
(4)reinterpreter_cast
轉換的類型必須是一個指針、引用、算術類型、函數指針或者成員指針。
主要是將一個類型的指針,轉換爲另外一個類型的指針
不一樣類型的指針類型轉換用reinterpreter_cast
最普通的用途就是在函數指針類型之間進行轉換
int DoSomething(){return 0;};
typedef void(*FuncPtr)(){};
FuncPtr funcPtrArray[10];
funcPtrArray[0] = reinterpreter_cast<FuncPtr>(&DoSomething);
14,C++函數中值的傳遞方式有哪幾種?
答:函數的三種傳遞方式爲:值傳遞、指針傳遞和引用傳遞。
15,將「引用」做爲函數參數有哪些特色
答:(1)傳遞引用給函數與傳遞指針的效果是同樣的,這時,被調函數的形參就成爲原來主調函數的實參變量或者
對象的一個別名來使用,因此在被調函數中形參的操做就是對相應的目標對象的操做
(2)使用引用傳遞函數的參數,在內存中並無產生實參的副本,它是直接對實參操做,當參數數據較大時,引用
傳遞參數的效率和所佔空間都好
(3)若是使用指針要分配內存單元,須要重複使用「*指針變量名」形式進行計算,容易出錯且閱讀性較差。
16,簡單敘述面向對象的三個基本特徵
答:封裝性
把客觀事物封裝成抽象的類,對自身的數據和方法進行(public,private, protected)
繼承性
繼承概念的實現方式有三類:實現繼承、接口繼承和可視繼承。
實現繼承是指使用基類的屬性和方法而無需額外編碼的能力;
接口繼承是指僅使用屬性和方法的名稱、可是子類必須提供實現的能力;
可視繼承是指子窗體(類)使用基窗體(類)的外觀和實現代碼的能力。
抽象類僅定義將由子類建立的通常屬性和方法,建立抽象類時,請使用關鍵字 Interface 而不是 Class
多態性
多態性(polymorphisn)是容許你將父對象設置成爲和一個或更多的他的子對象相等的技術,賦值以後,
父對象就能夠根據當前賦值給它的子對象的特性以不一樣的方式運做。容許將子類類型的指針賦值給父類類型的指針。
實現多態,有二種方式,覆蓋(子類從新定義父類的虛函數),重載(容許存在多個同名函數,參數個數,類型不一樣)。
17,類成員函數的overload, override 和 隱藏的區別
答:
(1)成員函數被重載的特徵:相同的類範圍,函數名字相同,參數不一樣,virtual 關鍵字無關緊要。
(2)覆蓋指派生類的函數覆蓋基類函數,特徵是分別位於基類和派生類,函數名字相同,參數相同,基類函數必須有virtual關鍵字
(3)隱藏是指派生類的函數屏蔽了與其同名的基類函數。1,派生類的函數與基類的函數同名,可是參數不一樣,
不論有無virtual關鍵字,基類的函數將被隱藏 2,派生類的函數與基類的函數同名,而且參數也相同,
可是基類函數沒有virtual 關鍵字。此時,基類的函數被隱藏
3種狀況怎麼執行:重載:看參數;隱藏:用什麼就調用什麼;覆蓋:調用派生類 。
18,什麼是預編譯,什麼時候須要預編譯
答:就是指程序執行前的一些預處理工做,主要指#表示的.
須要預編譯的狀況:老是使用不常常改動的大型代碼體。全部模塊都使用一組標準的包含文件和相同的編譯選項。
19,memset ,memcpy 和strcpy 的根本區別?
答:memset用來對一段內存空間所有設置爲某個字符,通常用在對定義的字符串進行初始化爲' '或'';
它對較大的結構體或數組進行清零操做的一種最快方法。
char temp[30]; memset(temp,'\0',sizeof(temp));
char temp[30]只是分配了必定的內存空間給該字符數組,但並未初始化該內存空間,即數組。因此,須要使用memset()來進行初始化。
memcpy用來作內存拷貝,你能夠拿它拷貝任何數據類型的對象,能夠指定拷貝的數據長度;
strcpy就只能拷貝字符串了,它遇到'\0'就結束拷貝;例:char a[100],b[50];strcpy(a,b);
20,多態類中的虛函數表是Compile-Time,仍是Run-Time時創建的?
答:虛擬函數表是在編譯期就創建了,各個虛擬函數這時被組織成了一個虛擬函數的入口地址的數組.
而對象的隱藏成員--虛擬函數表指針是在運行期也就是構造函數被調用時進行初始化的,這是實現多態的關鍵.
21,Template有什麼特色?何時用?
答: Template能夠獨立於任何特定的類型編寫代碼,是泛型編程的基礎.
當咱們編寫的類和函數可以多態的用於跨越編譯時不相關的類型時,用Template.
模板主要用於STL中的容器,算法,迭代器等以及模板元編程.
C++的template是實如今庫設計和嵌入式設計中的關鍵,
能實現抽象和效率的結合;同時template還能有效地防止代碼膨脹
C++中爲何用模板類?
1)可用來建立動態增加和減少的數據結構
2)它是類型無關的,所以具備很高的可複用性
3)它在編譯時而不是運行時檢查數據類型,保證了類型安全
4)它是平臺無關的,可移植性
5)可用於基本數據類型
22,進程和線程的差異?
答:線程是指進程內的一個執行單元,也是進程內的可調度實體.區別:
(1)調度:線程做爲調度和分配的基本單位,進程做爲擁有資源的基本單位
(2)併發性:不只進程之間能夠併發執行,同一個進程的多個線程之間也可併發執行
(3)擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但能夠訪問隸屬於進程的資源.
(4)系統開銷:建立撤消進程,系統都要爲之分配和回收資源,系統的開銷明顯大於建立撤消線程
多進程與多線程,二者均可以提升程序的併發度,提升程序運行效率和響應時間。
23,請說出static關鍵字儘量多的做用
答:(1)函數體內做用範圍爲該函數體,該變量內存只被分配一次,具備記憶能力
(2)在模塊內的static全局變量能夠被模塊內全部函數訪問,但不能被模塊外其它函數訪問;
(3)在模塊內的static函數只可被這一模塊內的其它函數調用,這個函數的使用範圍被限制在聲明它的模塊內;
(4)在類中的static成員變量屬於整個類所擁有,對類的全部對象只有一份拷貝;
(5)在類中的static成員函數屬於整個類所擁有,這個函數不接收this指針,於是只能訪問類的static成員變量。
24,頭文件的做用是什麼?
答:一,經過頭文件來調用庫功能。在不少場合,源代碼不便(或不許)向用戶公佈,只要向用戶提供頭文件
和二進制的庫便可。用戶只須要按照頭文件中的接口聲明來調用庫功能,而沒必要關心接口怎麼實現的。
編譯器會從庫中提取相應的代碼。
二,頭文件能增強類型安全檢查。若是某個接口被實現或被使用時,其方式與頭文件中的聲明不一致,
編譯器就會指出錯誤,這一簡單的規則能大大減輕程序員調試、改錯的負擔。
25,在C++程序中調用C編譯後的函數,爲何要加extern C的聲明?
答:由於C++支持函數重載,而C不支持函數重載,函數被C++編譯後在庫中的名字與C語言的不一樣。
假設某個函數的原型爲:void foo(int x, int y);該函數被C編譯器編譯後在庫中的名字爲_foo,
而C++編譯器則產生像_foo_int_int之類的名字。 C++提供extern C來解決名字匹配問題
26,C++中哪些函數不能被聲明爲虛函數?
答:普通函數(非成員函數),構造函數,內聯成員函數、靜態成員函數、友元函數。
(1)虛函數用於基類和派生類,普通函數因此不能
(2)構造函數不能是由於虛函數採用的是虛調用的方法,容許在只知道部分信息的狀況的工做機制,
特別容許調用只知道接口而不知道對象的準確類型的方法,可是調用構造函數即便要建立一個對象,
那勢必要知道對象的準確類型。
(3)內聯成員函數的實質是在調用的地方直接將代碼擴展開
(4)繼承時,靜態成員函數是不能被繼承的,它只屬於一個類,由於也不存在動態聯編等
(5)友元函數不是類的成員函數,所以也不能被繼承
27, 數組int c[3][3]; 爲何c,*c的值相等,(c+1),(*c+1)的值不等, c,*c,**c,表明什麼意思?
答:c是第一個元素的地址,*c是第一行元素的首地址,其實第一行元素的地址就是第一個元素的地址,
**c是提領第一個元素。 爲何c,*c的值相等?
c: 數組名;是一個二維指針,它的值就是數組的首地址,也即第一行元素的首地址(等於 *c),
也等於第一行第一個元素的地址( & c[0][0]);能夠說成是二維數組的行指針。
*c: 第一行元素的首地址;是一個一維指針,能夠說成是二維數組的列指針。
**c:二維數組中的第一個元素的值;即:c[0][0]
因此:c 和 *c的值是相等的,但他們二者不能相互賦值,(類型不一樣)
(c + 1) :c是行指針,(c + 1)是在c的基礎上加上二維數組一行的地址長度,
即從&c[0][0]變到了&c[1][0];
(*c + 1):*c是列指針,(*c + 1)是在*c的基礎上加上二數組一個元素的所佔的長度,
&c[0][0]變到了&c[0][1],從而(c + 1)和(*c + 1)的值就不相等了。
28,定義 int **pa[4][3],則變量pa佔有的內存空間是多少?
答:int **p,在32位機器上 sizeof(p) = 4;
總共佔有4*3*sizeof(p) = 48.
29,拷貝構造函數相關問題,深拷貝,淺拷貝,臨時對象等
答:在C++中,三種對象須要拷貝的狀況:一個對象以值傳遞的方式傳入函數體,
一個對象以值傳遞的方式從函數返回,一個對象須要經過另一個對象進行初始化。
執行先父類後子類的構造,對類中每個數據成員遞歸地執行成員拷的動做.
深拷貝:若是一個類擁有資源,深拷貝意味着拷貝了資源和指針
淺拷貝:若是對象存在資源,而淺拷貝只是拷貝了指針,沒有拷貝資源,
這樣使得兩個指針指向同一份資源,形成對同一份析構兩次,程序崩潰。
臨時對象的開銷比局部對象小些。
臨時對象:輔助一個表達式的計算 a + b + c ,或者間接構造的實參,函數返回非引用的時候,
均可能產生臨時對象,臨時對象生命週期,是單個語句,是右值。
臨時對象的開銷比局部對象小些。
30,指針和引用有什麼分別;
答:引用必須初始化,即引用到一個有效的對象;而指針在定義的時候沒必要初始化,
能夠在定義後面的任何地方從新賦值。
引用初始化後不能改變,指針能夠改變所指的對象
不存在指向NULL的引用,但存在指向NULL的指針
引用的建立和銷燬並不會調用類的拷貝構造函數
語言層面,引用的用法和對象同樣;在二進制層面,引用通常都是經過指針來實現的,
只不過編譯器幫咱們完成了轉換.引用既具備指針的效率,又具備變量使用的方便性和直觀性.
31,寫一個"標準"宏MIN,這個宏輸入兩個參數並返回較小的一個
答:面試者注意謹慎將宏定義中的「參數」和整個宏用括號括起來
#define MIN(A, B) ((A) <= (B)? (A):(B))
32,用一個宏定義FIND求一個結構體struc中某個變量相對struc的偏移量
答: #define FIND(struc, e) (size_t)&( ((struc*)0)->e )
解析:其中(struc*)0表示將常量0轉化爲struc*類型指針所指向的地址。
&( ((struc*)0)->e )表示取結構體指針(struc*)0的成員e的地址,由於該結構體的首地址爲0,
因此其實就是獲得了成員e距離結構體首地址的偏移量,(size_t)是一種數據類型,爲了便於不一樣系統之間的移植,
最好定義爲一種無符號型數據,通常爲unsigned int
33,解析sizeof 以及 結構體的對齊問題
答:(1)sizeof(type),用於數據類型;
sizeof(var_name)或sizeof var_name用於變量
sizeof操做符不能用於函數類型,不徹底類型或位字段。
不徹底類型指具備未知存儲大小的數據類型,如未知存儲大小的數組類型、未知內容的結構或聯合類型、void類型等。
如int max(), char char_v [MAX]且MAX未知 , void類型
那麼sizeof(max),sizeof(char_v),sizeof(void)都是錯誤的
當sizeof的參數爲數組或者指針時
int a[50]; //sizeof(a)=4*50=200; 求數組所佔的空間大小
int *a=new int[50];// sizeof(a)=4; a爲一個指針,sizeof(a)是求指針
當sizeof的參數爲結構或類時候
結構或者類中的靜態成員不對結構或者類的大小產生影響,由於靜態變量的存儲位置 。
與結構或者類的實例地址無關。沒有成員變量的結構或類的大小爲1,
由於必須保證結構或類的每一 實例在內存中都有惟一的地址
(2)class MyStruct{ double ddal; char dda; int type;}
在VC中測試上面結構的大小時,你會發現sizeof(MyStruct)爲16。
其實,這是VC對變量存儲的一個特殊處理。爲了提升CPU的存儲速度,VC對一些變量的起始
地址作了「對齊」處理。在默認狀況下,VC規定各成員變量存放的起始地址相對於結構的
始地址偏移量必須爲該變量的類型佔用字節數的倍數,如Char偏移量爲sizeof(char)即1的倍數
先爲第一個成員dda1分配空間,其起始地址跟結構的起始地址相同,偏移量0恰好爲sizeof(double)的倍數,
該成員變量佔用sizeof(double)=8個字節;接下來爲第二個成員dda分配空間,這時
下一個能夠分配的地址對於結構的起始地址的偏移量爲8,是sizeof(char)的倍數,佔sizeof(char)=1字節
爲第三個成員type分配空間,這時下一個能夠分配的地址對於結構的起始地址的偏移量爲9
,不是sizeof(int)=4的倍數,爲了知足對齊方式對偏移量的約束問題,VC自動填充3個字節
這時下一個能夠分配的地址對於結構的起始地址的偏移量是12,恰好是sizeof(int)=4的倍數,
因此把type存放在偏移量爲12的地方,佔 用sizeof(int)=4個字節。總的佔用的空間大
小爲:8+1+3+4=16,恰好爲結構的字節邊界數(即結構中佔用最大空間的類型所佔用的字節
數sizeof(double)=8)的倍數,因此沒有空缺的字節須要填充。
34,在main函數執行以前,還會執行什麼代碼和工做
答:運行全局構造器,全局對象的構造函數會在main函數以前執行
設置棧指針,初始化static靜態和global全局變量,即數據段的內容
將未初始化部分的賦初值:數值型short,int,long等爲0,bool爲FALSE,指針爲NULL等
將main函數的參數,argc,argv等傳遞給main函數
35,如何判斷一段程序是由C 編譯程序仍是由C++ 編譯程序編譯的?
答:C++ 編譯時定義了 __cplusplus
C 編譯時定義了 _STDC_
36,分別寫出BOOL,int, float, 指針類型的變量 a 與 「零值」的比較語句
答:
BOOL: if(!a) or if(a)
int : if( 0 == a)
float : const EXPRESSION EXP = 0.000001;
if(a < EXP && a > -EXP)
pointer: if(a != NULL) or if(a == NULL)
37,已知String類定義以下,嘗試寫出類的成員函數實現
class{
public:
String(const char*str = NULL); //通用構造函數
String(const String& another); //拷貝構造函數
~String(); //析構函數
String& operator = = (const String& rhs); //賦值函數
private:
char* m_data; //用於保存字符串
};
答:
String::String(const char*str)
{
if(str == NULL)
{
m_data = new char[1];
m_data[0] = '\0';
}
else
{
m_data = new char[strlen(str)+1];
strcpy(m_data, str);
}
}
String::String(const String& another)
{
m_data = new char[strlen(another.m_data)+1];
strcpy(m_data, another.m_data);
}
String::String& operator = = (const String& rhs)
{
if(this == &rhs)
return &this;
delete[] m_data;
m_data = new char(strlen(rhs.m_data)+1); //刪除原來的數據,新開一塊內存
strcpy(m_data, rhs.m_data);
return *this;
}
~String()
{
delete[] m_data;
}
38,論述C++類繼承的優缺點
答:一,優勢:類繼承是在編譯時刻靜態定義的,能夠直接使用,類繼承能夠較方便的改變從父類繼承的實現
二,缺點:1,由於繼承在編譯時刻就定義了,因此沒法在運行時刻改變從父類繼承的實現
2,父類一般至少定義了子類的部分行爲,父類的任何改變均可能影響到子類的行爲
3,若是繼承下來的實現不適合解決新的問題,則父類必須重寫或被其餘更適合的類替換
這種依賴關係先限制了靈活性並最終限制了複用性
39,運算符重載的三種方式和不容許重載的5個運算符
答:運算符重載意義是爲了對用戶自定義數據的操做和內定義的數據類型的操做形式一致
(1)普通函數,友元函數,類成員函數
(2).*(成員指針訪問運算符)
::(域運算符)
sizeof 長度運算符
?:條件運算符
.(成員訪問運算符)
40,友元關係有什麼特性?
答:單向的,非傳遞的, 不能繼承的.
41,理解析構函數和虛函數的用法和做用?
答:析構函數也是特殊的類成員函數,它沒有返回類型,沒有參數,不能隨意調用,也沒有重載。
在類對象生命期結束的時候,由系統自動調用釋放在構造函數中分配的資源。
析構函數通常在對象撤消前作收尾工做,好比回收內存等工做。
虛函數的功能是使子類能夠用同名的函數對父類函數進行重載,而且在調用時自動調用子類重載函
數,在基類中經過使用關鍵字virtual來聲明一個函數爲虛函數,該函數的功能可能在未來的派生類
中定義或者在基類的基礎上擴展,系統只能在運行階段才能動態的決定調用哪個函數,動態的多態性,
若是是純虛函數,則純粹是爲了在子類重載時有個統一的命名而已。
42,關鍵字volatile有什麼含意?並給出三個不一樣的例子
答:一個定義爲volatile的變量是說這變量可能會被意想不到地改變,編譯器就不會去假設這個變量的值了。
精確地說就是,優化器在用到這個變量時必須每次都當心地從新讀取這個變量的值
而不是使用保存在寄存器裏的備份。下面是volatile變量的幾個例子:
1) 並行設備的硬件寄存器(如:狀態寄存器)
2) 一箇中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
3) 多線程應用中被幾個任務共享的變量
深究:一個參數既能夠是const還能夠是volatile,一個例子是隻讀的狀態寄存器,
它是volatile由於它可能被意想不到地改變,是const由於程序不該該試圖去修改它。
一個指針能夠是volatile,一個例子是當一箇中服務子程序修該一個指向一個buffer的指針時。
43,動態鏈接庫的兩種方式?
答:調用一個DLL中的函數有兩種方法:
1.載入時動態連接(load-time dynamic linking),模塊很是明確調用某個導出函數
,使得他們就像本地函數同樣。這須要連接時連接那些函數所在DLL的導入庫,導入庫向
系統提供了載入DLL時所需的信息及DLL函數定位。
2.運行時動態連接(run-time dynamic linking),運行時能夠經過LoadLibrary或Loa
dLibraryEx函數載入DLL。DLL載入後,模塊能夠經過調用GetProcAddress獲取DLL函數的
出口地址,而後就能夠經過返回的函數指針調用DLL函數了。如此便可避免導入庫文件了。
44,C和C++有什麼不一樣?
答:從機制上:c是面向過程的。c++是面向對象的,提供了類。c++編寫面向對象的程序比c容易。
從適用的方向:c適合要求代碼體積小的,效率高的場合,如嵌入式;c++適合更上層的,複雜的;
llinux核心大部分是c寫的,由於它是系統軟件,效率要求極高。
C語言是結構化編程語言,C++是面向對象編程語言
C++側重於對象而不是過程,側重於類的設計而不是邏輯的設計。
45,C++編譯器自動爲類產生的四個確缺省函數是什麼?
答:默認構造函數,拷貝構造函數,析構函數,賦值函數
46,簡單描述Windows內存管理的方法。
答:程序運行時須要從內存中讀出這段程序的代碼,代碼的位置必須在物理內存中才能被運行,
因爲如今的操做系統中有很是多的程序運行着,內存中不可以徹底放下,因此引出了虛擬內存的概念。
把哪些不經常使用的程序片段就放入虛擬內存,當須要用到它的時候在load入主存(物理內存)中。
內存管理也計算程序片斷在主存中的物理位置,以便CPU調度。
內存管理有塊式管理,頁式管理,段式和段頁式管理。如今經常使用段頁式管理
塊式管理:把主存分爲一大塊、一大塊的,當所需的程序片段不在主存時就分配一塊主存空間,
把程 序片段load入主存,就算所需的程序片度只有幾個字節也只能把這一塊分配給它。
這樣會形成很大的浪費,平均浪費了50%的內存空間,但時易於管理。
頁式管理:把主存分爲一頁一頁的,每一頁的空間要比一塊一塊的空間小不少,顯然這種方法
的空間利用率要比塊式管理高不少
段式管理:把主存分爲一段一段的,每一段的空間又要比一頁一頁的空間小不少,
這種方法在空間利用率上又比頁式管理高不少,可是也有另一個缺點。一個程序片段可能會被分爲幾十段,
這樣不少時間就會被浪費在計算每一段的物理地址上,計算機最耗時間的你們都知道是I/O吧
段頁式管理:結合了段式管理和頁式管理的優勢。把主存分爲若干頁,每一頁又分爲若干段,好處就很明顯
47,Linux有內核級線程嗎?
答:線程一般被定義爲一個進程中代碼的不一樣執行路線。從實現方式上劃分,線程有兩種類型:
「用戶級線程」和「內核級線程」。 用戶線程指不須要內核支持而在用戶程序中實現的線程,其不依賴於操做系統核心,
應用進程利用線程庫提供建立、同步、調度,和管理線程的函數來控制用戶線程。內核級線程須要內核的參與,
由內核完成線程的調度。其依賴於操做系統核心,由內核的內部需求進行建立和撤銷。
用戶線程不須要額外的內核開支,而且用戶態線程的實現方式能夠被定製或修改以適應特殊應用的要求,
可是當一個線程因 I/O 而處於等待狀態時,整個進程就會被調度程序切換爲等待狀態,其餘線程得不
到運行的機會;而內核線程則沒有這個個限制,有利於發揮多處理器的併發優點,但卻佔用了更多的系統開支。
48,main 主函數執行完畢後,是否可能會再執行一段代碼,給出說明?
答:能夠,能夠用_onexit 註冊一個函數,它會在main 以後執行int fn1(void), fn2(void), fn3(void), fn4 (void)
49, i++ 相比 ++i 哪一個更高效?爲何?
答:
(1)++i 比 i++效率高。
(2)i++要多調用一次類的構造和析夠函數
50,windows平臺下網絡編程有哪幾種網絡編程模型?
答:有阻塞,select,基於窗體的事件模型,事件模型,重疊模型,完成端口模型。
除了阻塞模型外,其餘都是非阻塞模型,其中效率最高的是完成端口模型,尤爲在windows下服務器最合適了。
作客戶端通常用事件模型了,select在window和類unix均可以使用。
51,什麼是函數模板
答:函數模板技術定義了參數化的非成員函數,使得程序可以使用不一樣的參數類型調用相同的函數,而至因而何種類型,
則是由編譯器肯定從模板中生成相應類型的代碼。編譯器肯定了模板函數的實際類型參數,稱之爲模板的實例化。
template<class T>定義模板標識
T Add(T a, T b) //函數模板
{
T result = a + b;
return a + b; //將兩個參數使用「+」運算符進行運算,這兩個參數並不知道是何種類型
}
該函數與通常函數的不一樣之處在於沒有明確指出使用何種數據類型和返回值又是哪種類型
如何在程序中調用該函數
#include<iostream> //包含標準輸入輸出頭文件
#include<string> //C++中的字符串處理頭文件
using namespace std;
template<class T>
T Add(T a, T b) //函數模板
{
T result = a + b;
return a + b; //將兩個參數使用「+」運算符進行運算,這兩個參數並不知道是何種類型
}
int main(int argc, char* argv[])
{
cout<<"2+3="<<Add(2,3)<<endl; //輸出整形的+運算結果
cout<<"sdf+123="<<Add(string("sdf"), string("123"))<<endl;
return 0;
}
52,什麼是類模板
答:描述了可以管理其餘數據類型的通用數據類型,一般用於創建包含其餘類型的容器類
對於這些容器,不管是哪種數據類型,其操做方式是同樣的,可是針對具體的類型又是專用的,
template<class T>
class TemplateSample
{
private:
T& emtity; //使用參數類型成員
public:
void F(T& arg); //使用參數類型定義成員函數
}
該示例定義了一個類模板,類模板中的模板形參T須要用戶在使用的時候進行定義
TemplateSample<int>demo; //針對該模板使用int類型
demo.F(123); //調用類模板中的成員函數
template<class T1, class T2, int num> //定義多個模板參數,且其中一個直接使用int類型
該示例的前兩個參數能夠是任何類型,可是第三個參數必定是int類型
TemplateSample<int , char, 12>demo; //使用非類類型的模板
#include<iostream>
template<class T, class T2, int num>
class CSampleTemplate
{
private:
T t1;
T2 t2;
public:
CSampleTemplate(T arg1, T2 arg2) //構造函數中使用模板參數
{
t1 = arg1 + num;
t2 = arg2 + num;
}
void Write()
{
std::cout<<"t1:"<<t1<<"t2"<<t2<<endl;
}
CSampleTemplate ()
{}
}
int main(int argc, char* argv[])
{
CSampleTemplate<int, int, 3>temp(1,2);
temp.Write();
return 0;
}
53,什麼是容器
答:STL是一個標準的C++庫,容器只是其中一個重要的組成部分,有順序容器和關聯容器
1)順序容器,指的是一組具備相同類型T的對象,以嚴格的線性形式組織在一塊兒
包括vector<T>, deque<T>, list<T>
2)關聯容器,提供一個key實現對對象的隨機訪問,其特色是key是有序的元素是按照預約義的鍵順序插入的i
set<Key> ,集合, 支持惟一鍵值,提供對鍵自己的快速檢索,例如set<long>:{學號}
set<Key>,多重集合,支持可重複鍵值,提供對鍵自己的快速檢索,例如multiset<string>:{姓名}
map<Key, T>,支持惟一Key類型的鍵值,提供對另外一個基於鍵的類型的快速檢索,例如map<long,string>:{學號,姓名}
multimap<Key, T>, 多重映射,支持可重複Key值,提供對另一個基於鍵類型T的快速檢索,例如map<string, string>:{姓名,地址}
54,介紹關聯容器
答:#include<vector> //包含頭文件
using std::vector //使用命名限定
vector<int>vInts;
建立一個Widget類型爲空的vector對象
vector<Widget>vWidgets; //空的vector對象
vector<Widget>vWidgets(500); //包含500個對象的vector
vector<Widget>vWidgets(500, Widget(0)); //包含500個對象的vector,而且初始化爲0
vector<Widget>vWidgetFromAnother(vWeigets); //利用現有的vector建立一個拷貝
向vector中添加一個數據,默認方式是push_back,表示將數據添加到vector的尾部,而且按照須要來分配內存,如
for(int i = 0 ; i < 10; i ++)
v.push_back(Widget(i));
若是想獲取vector v的大小,但不知道它是否爲空,或者已經包含了數據,可用以下代碼實現
int nSize = v.empty()? -1:static_cast<int>(v.size());
訪問vector中的數據有兩種方法 vector::at() 和 vector::operator[],其中vector::at()進行了邊界檢查
vector<int>v; //定義了vector對象
v.reserve(10); //分配空間可是沒有初始化
for(int i = 0 ; i < 7; i++)
{ v. push_back(i);}
int iVal1 = v[7]; //不進行邊界檢查
int iVal2 = v.at(7); //進行邊界檢查
deque容器是一個雙端隊列,存放的元素不是以連續的方式存放的
list容器是一種鏈表的實現,儲存的元素是經過使用雙向鏈表實現的
55,什麼是迭代器的範圍
答:迭代器是STL提供的對一個容器中的對象的訪問方法,定義了容器中對象的範圍,迭代器就如同一個指針。
vector<int>v; //聲明vector變量
v.push_back(2); //插入數據
v.push_back(1);
vector<int>::iterator first = v.begin(); //獲取vector<int>的一個元素的迭代器
while1(first != v.end()) //使用迭代器遍歷vector,一直到最後一個元素
{
int i = *first; //獲取迭代器指向的元素的值
first++;
}
55,C++如何實現泛型編程
答:泛型編程實現了於特定類型的操做算法,由編譯器根據泛型的調用所傳遞的類及模板生成該類型專用的代碼。
#include<iostream>
#include<string>
using namespaces std;
template<class T>
T Add(T a, T b)
{
T result; // 使用參數化的類型定義變量
result = a + b;
return result;
}
int main(int argc, char* argv[])
{
cout<<"2+3="<<Add(2,3)<<endl;
cout<<"sdf+123="<<Add(string("sdf"), string("123"));
return 0;
}
56,參數傳遞的方式和多態參數傳遞的實現
答:參數傳遞有傳值,傳指針,或者是引用等三種,下面作詳細的介紹
1)傳值方式適合通常的數值傳遞,而且不改變原數據,可是要消耗內存空間
2)傳遞指針方式適合傳遞數組和指針,因爲傳遞的是地址,因此直接操做會改變原數據
3)引用方式和指針方式比較相似,是相對比較新的一種方式,通常狀況下能用傳地址的就能用引用
並且使用引用更方便一些
實現多態主要是採用指針和引用,傳值方式是複製數據,其類型編譯器就已經決定,而多態是類型要等到執行器才能決定,
因此不適用傳值方式來實現多態參數傳遞
57,C++和C定義結構體區別是什麼?
答:C++中的結構和類其實具有幾乎同樣的功能,結構體內也是能夠聲明函數,C++的結構體和類默認具備不同的訪問屬性