華為C面試寶典_第1頁
華為C面試寶典_第2頁
華為C面試寶典_第3頁
華為C面試寶典_第4頁
華為C面試寶典_第5頁
已閱讀5頁,還剩26頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

1、變量static和const關(guān)鍵字的作用static關(guān)鍵字至少有下列n個作用:(1)函數(shù)體內(nèi)static變量的作用范圍為該函數(shù)體,不同于auto變量,該變量的內(nèi)存只被分配一次,因此其值在下次調(diào)用時仍維持上次的值;(2)在模塊內(nèi)的static全局變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問;(3)在模塊內(nèi)的static函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用,這個函數(shù)的使用范圍被限制在聲明它的模塊內(nèi);(4)在類中的static成員變量屬于整個類所擁有,對類的所有對象只有一份拷貝;(5)在類中的static成員函數(shù)屬于整個類所擁有,這個函數(shù)不接收this指針,因而只能訪問類的static成員

2、變量。 const關(guān)鍵字至少有下列n個作用:(1)欲阻止一個變量被改變,可以使用const關(guān)鍵字。在定義該const變量時,通常需要對它進(jìn)行初始化,因為以后就沒有機會再去改變它了;(2)對指針來說,可以指定指針本身為const,也可以指定指針?biāo)傅臄?shù)據(jù)為const,或二者同時指定為const;(3)在一個函數(shù)聲明中,const可以修飾形參,表明它是一個輸入?yún)?shù),在函數(shù)內(nèi)部不能改變其值;(4)對于類的成員函數(shù),若指定其為const類型,則表明其是一個常函數(shù),不能修改類的成員變量;(5)對于類的成員函數(shù),有時候必須指定其返回值為const類型,以使得其返回值不為“左值”。例如:const clas

3、sA operator*(const classA& a1,const classA& a2);operator*的返回結(jié)果必須是一個const對象。如果不是,這樣的變態(tài)代碼也不會編譯出錯:classA a, b, c;(a * b) = c; / 對a*b的結(jié)果賦值操作(a * b) = c顯然不符合編程者的初衷,也沒有任何意義。函數(shù)體中的指針或引用常量不能被返回Char *func(void)char str=”Hello Word”;/這個是不能被返回的,因為str是個指定變量,不是一般的值,函數(shù)結(jié)束后會被注銷掉return str; 函數(shù)體內(nèi)的指針變量并不會隨著函數(shù)的消

4、亡而自動釋放函數(shù)一個內(nèi)存拷貝函數(shù)的實現(xiàn)體void *memcpy(void *pvTo,const void *pvFrom,size_t size)assert(pvTo!=NULL)&&(pvFrom!=NULL);byte *pbTo=(byte*)pvTo; /防止地址被改變byte *pbFrom=(byte*)pvFrom;while (size- >0) pbTo+ = pbForm+;return pvTo;C+函數(shù)中值的傳遞方式有三種方式:值傳遞(Pass by value)、指針傳遞(Pass by pointer)、引用傳遞(Pass by refe

5、rence)void fun(char c) /pass by valuevoid fun(char *str) /pass by pointervoid fun(char &str) /pass by reference如果輸入?yún)?shù)是以值傳遞的話,最好使用引用傳遞代替,因為引用傳遞省去了臨時對象的構(gòu)造和析構(gòu)函數(shù)的類型不能省略,就算沒有也要加個void判斷處理器存放數(shù)據(jù)的類型請寫一個C函數(shù),若處理器是Big_endian的,則返回0;若是Little_endian的,則返回1解答:int checkCPU()union w int a;char b; c;c.a = 1;return

6、(c.b = 1);剖析:嵌入式系統(tǒng)開發(fā)者應(yīng)該對Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU對操作數(shù)的存放方式是從低字節(jié)到高字節(jié),而Big-endian模式對操作數(shù)的存放方式是從高字節(jié)到低字節(jié)。例如,16bit寬的數(shù)0x1234在Little-endian模式CPU內(nèi)存中的存放方式(假設(shè)從地址0x4000開始存放)為:內(nèi)存地址存放內(nèi)容0x40000x340x40010x12而在Big-endian模式CPU內(nèi)存中的存放方式則為:內(nèi)存地址存放內(nèi)容0x40000x120x40010x3432bit寬的數(shù)0x12345678在Little-

7、endian模式CPU內(nèi)存中的存放方式(假設(shè)從地址0x4000開始存放)為:內(nèi)存地址存放內(nèi)容0x40000x780x40010x560x40020x340x40030x12而在Big-endian模式CPU內(nèi)存中的存放方式則為:內(nèi)存地址存放內(nèi)容0x40000x120x40010x340x40020x560x40030x78聯(lián)合體union的存放順序是所有成員都從低地址開始存放,面試者的解答利用該特性,輕松地獲得了CPU對內(nèi)存采用Little-endian還是Big-endian模式讀寫。如果誰能當(dāng)場給出這個解答,那簡直就是一個天才的程序員。設(shè)計atoi函數(shù) int atoi(char *s)&

8、#160;int atoi(const char *nptr); 函數(shù)說明 atoi()會掃描參數(shù)nptr字符串,跳過前面的空格字符,直到遇上數(shù)字或正負(fù)符號才開始做轉(zhuǎn)換,而再 遇到非數(shù)字或字符串結(jié)束時('0')才結(jié)束轉(zhuǎn)換,并將結(jié)果返回。返回值 返回轉(zhuǎn)換后的整型數(shù)。#include <stdio.h>#include <ctype.h>int myAtoi(const char* s) int result = 0; int flag = 1; int i = 0; while(isspace(s

9、i)  i+; if(si = '-')  flag = -1;  i+;  if(si = '+')  i+; while(si != '0')  if(si > '9') | (si < '0')   break;  int j = si - '0'  result = 10

10、* result + j;  i+;  result = result * flag; return result;int main() char* a = "   -1234def" char* b = "+1234" int i = myAtoi(a); int j = myAtoi(b); printf("%d n",i); printf("%d",j); return

11、0;String類基本函數(shù)的實現(xiàn)已知String類定義如下:class Stringpublic:String(const char *str = NULL); / 通用構(gòu)造函數(shù)String(const String &another); / 拷貝構(gòu)造函數(shù) String(); / 析構(gòu)函數(shù)String & operater =(const String &rhs); / 賦值函數(shù)private:char *m_data; / 用于保存字符串;嘗試寫出類的成員函數(shù)實現(xiàn)。答案:String:String(const char *str)if ( str = NULL ) /

12、strlen在參數(shù)為NULL時會拋異常才會有這步判斷m_data = new char1 ;m_data0 = '0' ;elsem_data = new charstrlen(str) + 1;strcpy(m_data,str); String:String(const String &another)m_data = new charstrlen(another.m_data) + 1;strcpy(m_data,other.m_data);String& String:operator =(const String &rhs)if ( this

13、= &rhs)return *this ;delete m_data; /刪除原來的數(shù)據(jù),新開一塊內(nèi)存m_data = new charstrlen(rhs.m_data) + 1;strcpy(m_data,rhs.m_data);return *this ;String:String()delete m_data ;1. 算法鏈表反轉(zhuǎn)單向鏈表的反轉(zhuǎn)是一個經(jīng)常被問到的一個面試題,也是一個非?;A(chǔ)的問題。比如一個鏈表是這樣的: 1->2->3->4->5 通過反轉(zhuǎn)后成為5->4->3->2->1。最容易想到的方法遍歷一遍鏈表,利用一個輔助

14、指針,存儲遍歷過程中當(dāng)前指針指向的下一個元素,然后將當(dāng)前節(jié)點元素的指針反轉(zhuǎn)后,利用已經(jīng)存儲的指針往后面繼續(xù)遍歷。源代碼如下:struct linka int data; linka* next; ; void reverse(linka*& head) if(head =NULL)                   return; linka *pre, *cur, *ne; pre=head; cur=h

15、ead->next; while(cur)    ne = cur->next;    cur->next = pre;    pre = cur;    cur = ne; head->next = NULL; head = pre; 還有一種利用遞歸的方法。這種方法的基本思想是在反轉(zhuǎn)當(dāng)前節(jié)點之前先調(diào)用遞歸函數(shù)反轉(zhuǎn)后續(xù)節(jié)點。源代碼如下。不過這個方法有一個缺點,就是在反轉(zhuǎn)后的最后一個結(jié)點會形成一個環(huán),所以必須將函數(shù)的返回的節(jié)點的next域置為NULL。因為要改變head指針,所以我用了引用。

16、算法的源代碼如下:linka* reverse(linka* p,linka*& head) if(p = NULL | p->next = NULL)    head=p;    return p; else    linka* tmp = reverse(p->next,head);    tmp->next = p;    return p; 雙向鏈表有雙向循環(huán)鏈表結(jié)點定義為: struct node int data; struct node *front,*n

17、ext; ; 有兩個雙向循環(huán)鏈表A,B,知道其頭指針為:pHeadA,pHeadB,請寫一函數(shù)將兩鏈表中data值相同的結(jié)點刪除 BOOL DeteleNode(Node *pHeader, DataType Value) if (pHeader = NULL) return; BOOL bRet = FALSE; Node *pNode = pHead; while (pNode != NULL) if (pNode->data = Value) if (pNode->front = NULL) pHeader = pNode->next; pHeader->fron

18、t = NULL; else if (pNode->next != NULL) pNode->next->front = pNode->front; pNode->front->next = pNode->next; Node *pNextNode = pNode->next; delete pNode; pNode = pNextNode; bRet = TRUE; /不要break或return, 刪除所有 else pNode = pNode->next; return bRet; void DE(Node *pHeadA, Node

19、 *pHeadB) if (pHeadA = NULL | pHeadB = NULL) return; Node *pNode = pHeadA; while (pNode != NULL) if (DeteleNode(pHeadB, pNode->data) if (pNode->front = NULL) pHeadA = pNode->next; pHeadA->front = NULL; else pNode->front->next = pNode->next; if (pNode->next != NULL) pNode->

20、next->front = pNode->front; Node *pNextNode = pNode->next; delete pNode; pNode = pNextNode; else pNode = pNode->next; 循環(huán)鏈表怎么判斷鏈表中是否有環(huán)? bool CircleInList(Link* pHead) if(pHead = = NULL | pHead->next = = NULL)/無節(jié)點或只有一個節(jié)點并且無自環(huán) return (false); if(pHead->next = = pHead)/自環(huán) return (true)

21、; Link *pTemp1 = pHead;/step 1 Link *pTemp = pHead->next;/step 2 while(pTemp != pTemp1 && pTemp != NULL && pTemp->next != NULL) pTemp1 = pTemp1->next; pTemp = pTemp->next->next; if(pTemp = = pTemp1) return (true); return (false); 2. 類構(gòu)造函數(shù)與析構(gòu)函數(shù)派生類的構(gòu)造函數(shù)應(yīng)在初始化表里調(diào)用基類的構(gòu)造函數(shù);派

22、生類和基類的析構(gòu)函數(shù)應(yīng)加Virtual關(guān)鍵字。不要小看構(gòu)造函數(shù)和析構(gòu)函數(shù),其實編起來還是不容易。#include <iostream.h>class Base public: virtual Base() cout<< "Base" << endl ; ;class Derived : public Base public: virtual Derived() cout<< "Derived" << endl ; ;void main(void) Base * pB = new Derived;

23、 / upcast delete pB;輸出結(jié)果為: Derived Base如果析構(gòu)函數(shù)不為虛,那么輸出結(jié)果為 Base3. 指針和引用什么是“引用”?申明和使用“引用”要注意哪些問題?答:引用就是某個目標(biāo)變量的“別名”(alias),對應(yīng)用的操作與對變量直接操作效果完全相同。申明一個引用的時候,切記要對其進(jìn)行初始化。引用聲明完畢后,相當(dāng)于目標(biāo)變量名有兩個名稱,即該目標(biāo)原名稱和引用名,不能再把該引用名作為其他變量名的別名。聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標(biāo)變量名的一個別名,它本身不是一種數(shù)據(jù)類型,因此引用本身不占存儲單元,系統(tǒng)也不給引用分配存儲單元。不能建立數(shù)組的引用

24、。將“引用”作為函數(shù)參數(shù)有哪些特點?傳遞引用給函數(shù)與傳遞指針的效果是一樣的。這時,被調(diào)函數(shù)的形參就成為原來主調(diào)函數(shù)中的實參變量或?qū)ο蟮囊粋€別名來使用,所以在被調(diào)函數(shù)中對形參變量的操作就是對其相應(yīng)的目標(biāo)對象(在主調(diào)函數(shù)中)的操作。(2)使用引用傳遞函數(shù)的參數(shù),在內(nèi)存中并沒有產(chǎn)生實參的副本,它是直接對實參操作;而使用一般變量傳遞函數(shù)的參數(shù),當(dāng)發(fā)生函數(shù)調(diào)用時,需要給形參分配存儲單元,形參變量是實參變量的副本;如果傳遞的是對象,還將調(diào)用拷貝構(gòu)造函數(shù)。因此,當(dāng)參數(shù)傳遞的數(shù)據(jù)較大時,用引用比用一般變量傳遞參數(shù)的效率和所占空間都好。(3)使用指針作為函數(shù)的參數(shù)雖然也能達(dá)到與使用引用的效果,但是,在被調(diào)函數(shù)中

25、同樣要給形參分配存儲單元,且需要重復(fù)使用"*指針變量名"的形式進(jìn)行運算,這很容易產(chǎn)生錯誤且程序的閱讀性較差;另一方面,在主調(diào)函數(shù)的調(diào)用點處,必須用變量的地址作為實參。而引用更容易使用,更清晰。在什么時候需要使用“常引用”?如果既要利用引用提高程序的效率,又要保護(hù)傳遞給函數(shù)的數(shù)據(jù)不在函數(shù)中被改變,就應(yīng)使用常引用。常引用聲明方式:const 類型標(biāo)識符 &引用名=目標(biāo)變量名;例1int a ;const int &ra=a;ra=1; /錯誤a=1; /正確例2string foo( );void bar(string & s);那么下面的表達(dá)式將是非法

26、的:bar(foo( );bar("hello world");原因在于foo( )和"hello world"串都會產(chǎn)生一個臨時對象,而在C+中,這些臨時對象都是const類型的。因此上面的表達(dá)式就是試圖將一個const類型的對象轉(zhuǎn)換為非const類型,這是非法的。引用型參數(shù)應(yīng)該在能被定義為const的情況下,盡量定義為const 。將“引用”作為函數(shù)返回值類型的格式、好處和需要遵守的規(guī)則?格式:類型標(biāo)識符 &函數(shù)名(形參列表及類型說明) /函數(shù)體 好處:在內(nèi)存中不產(chǎn)生被返回值的副本;(注意:正是因為這點原因,所以返回一個局部變量的引用是不可取

27、的。因為隨著該局部變量生存期的結(jié)束,相應(yīng)的引用也會失效,產(chǎn)生runtime error!注意事項:(1)不能返回局部變量的引用。這條可以參照Effective C+1的Item 31。主要原因是局部變量會在函數(shù)返回后被銷毀,因此被返回的引用就成為了"無所指"的引用,程序會進(jìn)入未知狀態(tài)。(2)不能返回函數(shù)內(nèi)部new分配的內(nèi)存的引用。這條可以參照Effective C+1的Item 31。雖然不存在局部變量的被動銷毀問題,可對于這種情況(返回函數(shù)內(nèi)部new分配內(nèi)存的引用),又面臨其它尷尬局面。例如,被函數(shù)返回的引用只是作為一個臨時變量出現(xiàn),而沒有被賦予一個實際的變量,那么這個引

28、用所指向的空間(由new分配)就無法釋放,造成memory leak。(3)可以返回類成員的引用,但最好是const。這條原則可以參照Effective C+1的Item 30。主要原因是當(dāng)對象的屬性是與某種業(yè)務(wù)規(guī)則(business rule)相關(guān)聯(lián)的時候,其賦值常常與某些其它屬性或者對象的狀態(tài)有關(guān),因此有必要將賦值操作封裝在一個業(yè)務(wù)規(guī)則當(dāng)中。如果其它對象可以獲得該屬性的非常量引用(或指針),那么對該屬性的單純賦值就會破壞業(yè)務(wù)規(guī)則的完整性。(4)流操作符重載返回值申明為“引用”的作用:流操作符<<和>>,這兩個操作符常常希望被連續(xù)使用,例如:cout <<

29、 "hello" << endl;因此這兩個操作符的返回值應(yīng)該是一個仍然支持這兩個操作符的流引用??蛇x的其它方案包括:返回一個流對象和返回一個流對象指針。但是對于返回一個流對象,程序必須重新(拷貝)構(gòu)造一個新的流對象,也就是說,連續(xù)的兩個<<操作符實際上是針對不同對象的!這無法讓人接受。對于返回一個流指針則不能連續(xù)使用<<操作符。因此,返回一個流對象引用是惟一選擇。這個唯一選擇很關(guān)鍵,它說明了引用的重要性以及無可替代性,也許這就是C+語言中引入引用這個概念的原因吧。賦值操作符=。這個操作符象流操作符一樣,是可以連續(xù)使用的,例如:x = j

30、 = 10;或者(x=10)=100;賦值操作符的返回值必須是一個左值,以便可以被繼續(xù)賦值。因此引用成了這個操作符的惟一返回值選擇。例3i nclude <iostream.h>int &put(int n);int vals10;int error=-1;void main()put(0)=10; /以put(0)函數(shù)值作為左值,等價于vals0=10;put(9)=20; /以put(9)函數(shù)值作為左值,等價于vals9=20;cout<<vals0;cout<<vals9;int &put(int n)if (n>=0 &

31、& n<=9 ) return valsn;else cout<<"subscript error" return error; (5)在另外的一些操作符中,卻千萬不能返回引用:+-*/ 四則運算符。它們不能返回引用,Effective C+1的Item23詳細(xì)的討論了這個問題。主要原因是這四個操作符沒有side effect,因此,它們必須構(gòu)造一個對象作為返回值,可選的方案包括:返回一個對象、返回一個局部變量的引用,返回一個new分配的對象的引用、返回一個靜態(tài)對象引用。根據(jù)前面提到的引用作為返回值的三個規(guī)則,第2、3兩個方案都被否決了。靜態(tài)對象的

32、引用又因為(a+b) = (c+d)會永遠(yuǎn)為true而導(dǎo)致錯誤。所以可選的只剩下返回一個對象了?!耙谩迸c多態(tài)的關(guān)系?引用是除指針外另一個可以產(chǎn)生多態(tài)效果的手段。這意味著,一個基類的引用可以指向它的派生類實例。例4Class A; Class B : Class A.; B b; A& ref = b;“引用”與指針的區(qū)別是什么?指針通過某個指針變量指向一個對象后,對它所指向的變量間接操作。程序中使用指針,程序的可讀性差;而引用本身就是目標(biāo)變量的別名,對引用的操作就是對目標(biāo)變量的操作。此外,就是上面提到的對函數(shù)傳ref和pointer的區(qū)別。什么時候需要“引用”?流操作符<<

33、;和>>、賦值操作符=的返回值、拷貝構(gòu)造函數(shù)的參數(shù)、賦值操作符=的參數(shù)、其它情況都推薦使用引用。指針運算寫一個函數(shù),功能:完成內(nèi)存之間的拷貝 memcpy source code:     270 void* memcpy( void *dst, const void *src, unsigned int len )     271     272    register char *d;     273    register char *s;   &

34、#160; 27     275    if (len = 0)     276       return dst;     277     278    if (is_overlap(dst, src, len, len)     279       complain3("memcpy", dst, src, len);     280   &#

35、160; 281    if ( dst > src )     282       d = (char *)dst + len - 1;     283       s = (char *)src + len - 1;     284       while ( len >= 4 )     285          *d- = *s-; &#

36、160;   286          *d- = *s-;     287          *d- = *s-;     288          *d- = *s-;     289          len -= 4;     290          

37、; 291       while ( len- )     292          *d- = *s-;     293           294     else if ( dst < src )     295       d = (char *)dst;     296       s =

38、(char *)src;     297       while ( len >= 4 )     298          *d+ = *s+;     299          *d+ = *s+;     300          *d+ = *s+;     301      

39、;    *d+ = *s+;     302          len -= 4;     303           304       while ( len- )     305          *d+ = *s+;     306           307

40、         308    return dst;     309 4. 內(nèi)存管理內(nèi)存分配方式內(nèi)存分配方式有三種:(1)從靜態(tài)存儲區(qū)域分配。內(nèi)存在程序編譯的時候就已經(jīng)分配好,這塊內(nèi)存在程序的整個運行期間都存在。例如全局變量,static變量。(2)在棧上創(chuàng)建。在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放。棧內(nèi)存分配運算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。(3) 從堆上分配,亦稱動態(tài)內(nèi)存分配。程序在運行的時候用malloc或new申請任意多少的

41、內(nèi)存,程序員自己負(fù)責(zé)在何時用free或delete釋放內(nèi)存。動態(tài)內(nèi)存的生存期由我們決定,使用非常靈活,但問題也最多。常見的內(nèi)存錯誤及其對策發(fā)生內(nèi)存錯誤是件非常麻煩的事情。編譯器不能自動發(fā)現(xiàn)這些錯誤,通常是在程序運行時才能捕捉到。而這些錯誤大多沒有明顯的癥狀,時隱時現(xiàn),增加了改錯的難度。有時用戶怒氣沖沖地把你找來,程序卻沒有發(fā)生任何問題,你一走,錯誤又發(fā)作了。 常見的內(nèi)存錯誤及其對策如下:* 內(nèi)存分配未成功,卻使用了它。編程新手常犯這種錯誤,因為他們沒有意識到內(nèi)存分配會不成功。常用解決辦法是,在使用內(nèi)存之前檢查指針是否為NULL。如果指針p是函數(shù)的參數(shù),那么在函數(shù)的入口處用assert(p!=N

42、ULL)進(jìn)行檢查。如果是用malloc或new來申請內(nèi)存,應(yīng)該用if(p=NULL) 或if(p!=NULL)進(jìn)行防錯處理。* 內(nèi)存分配雖然成功,但是尚未初始化就引用它。犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為內(nèi)存的缺省初值全為零,導(dǎo)致引用初值錯誤(例如數(shù)組)。 內(nèi)存的缺省初值究竟是什么并沒有統(tǒng)一的標(biāo)準(zhǔn),盡管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式創(chuàng)建數(shù)組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。* 內(nèi)存分配成功并且已經(jīng)初始化,但操作越過了內(nèi)存的邊界。例如在使用數(shù)組時經(jīng)常發(fā)生下標(biāo)“多1”或者“少1”的操作。特別是在for循環(huán)語句中,循環(huán)次數(shù)很容

43、易搞錯,導(dǎo)致數(shù)組操作越界。* 忘記了釋放內(nèi)存,造成內(nèi)存泄露。含有這種錯誤的函數(shù)每被調(diào)用一次就丟失一塊內(nèi)存。剛開始時系統(tǒng)的內(nèi)存充足,你看不到錯誤。終有一次程序突然死掉,系統(tǒng)出現(xiàn)提示:內(nèi)存耗盡。動態(tài)內(nèi)存的申請與釋放必須配對,程序中malloc與free的使用次數(shù)一定要相同,否則肯定有錯誤(new/delete同理)。* 釋放了內(nèi)存卻繼續(xù)使用它。有三種情況:(1)程序中的對象調(diào)用關(guān)系過于復(fù)雜,實在難以搞清楚某個對象究竟是否已經(jīng)釋放了內(nèi)存,此時應(yīng)該重新設(shè)計數(shù)據(jù)結(jié)構(gòu),從根本上解決對象管理的混亂局面。(2)函數(shù)的return語句寫錯了,注意不要返回指向“棧內(nèi)存”的“指針”或者“引用”,因為該內(nèi)存在函數(shù)體結(jié)

44、束時被自動銷毀。(3)使用free或delete釋放了內(nèi)存后,沒有將指針設(shè)置為NULL。導(dǎo)致產(chǎn)生“野指針”。【規(guī)則1】用malloc或new申請內(nèi)存之后,應(yīng)該立即檢查指針值是否為NULL。防止使用指針值為NULL的內(nèi)存?!疽?guī)則2】不要忘記為數(shù)組和動態(tài)內(nèi)存賦初值。防止將未被初始化的內(nèi)存作為右值使用?!疽?guī)則3】避免數(shù)組或指針的下標(biāo)越界,特別要當(dāng)心發(fā)生“多1”或者“少1”操作?!疽?guī)則4】動態(tài)內(nèi)存的申請與釋放必須配對,防止內(nèi)存泄漏?!疽?guī)則5】用free或delete釋放了內(nèi)存之后,立即將指針設(shè)置為NULL,防止產(chǎn)生“野指針”。指針與數(shù)組的對比C+/C程序中,指針和數(shù)組在不少地方可以相互替換著用,讓人產(chǎn)

45、生一種錯覺,以為兩者是等價的。數(shù)組要么在靜態(tài)存儲區(qū)被創(chuàng)建(如全局?jǐn)?shù)組),要么在棧上被創(chuàng)建。數(shù)組名對應(yīng)著(而不是指向)一塊內(nèi)存,其地址與容量在生命期內(nèi)保持不變,只有數(shù)組的內(nèi)容可以改變。指針可以隨時指向任意類型的內(nèi)存塊,它的特征是“可變”,所以我們常用指針來操作動態(tài)內(nèi)存。指針遠(yuǎn)比數(shù)組靈活,但也更危險。下面以字符串為例比較指針與數(shù)組的特性。3.1 修改內(nèi)容示例3-1中,字符數(shù)組a的容量是6個字符,其內(nèi)容為hello。a的內(nèi)容可以改變,如a0= 'X。指針p指向常量字符串“world”(位于靜態(tài)存儲區(qū),內(nèi)容為world),常量字符串的內(nèi)容是不可以被修改的。從語法上看,編譯器并不覺得語句p0=

46、'X有什么不妥,但是該語句企圖修改常量字符串的內(nèi)容而導(dǎo)致運行錯誤。char a = “hello”;a0 = 'X;cout << a << endl;char *p = “world”; / 注意p指向常量字符串p0 = 'X; / 編譯器不能發(fā)現(xiàn)該錯誤cout << p << endl;示例3.1 修改數(shù)組和指針的內(nèi)容3.2 內(nèi)容復(fù)制與比較不能對數(shù)組名進(jìn)行直接復(fù)制與比較。示例7-3-2中,若想把數(shù)組a的內(nèi)容復(fù)制給數(shù)組b,不能用語句 b = a ,否則將產(chǎn)生編譯錯誤。應(yīng)該用標(biāo)準(zhǔn)庫函數(shù)strcpy進(jìn)行復(fù)制。同理,比較b和a

47、的內(nèi)容是否相同,不能用if(b=a) 來判斷,應(yīng)該用標(biāo)準(zhǔn)庫函數(shù)strcmp進(jìn)行比較。語句p = a 并不能把a的內(nèi)容復(fù)制指針p,而是把a的地址賦給了p。要想復(fù)制a的內(nèi)容,可以先用庫函數(shù)malloc為p申請一塊容量為strlen(a)+1個字符的內(nèi)存,再用strcpy進(jìn)行字符串復(fù)制。同理,語句if(p=a) 比較的不是內(nèi)容而是地址,應(yīng)該用庫函數(shù)strcmp來比較。/ 數(shù)組char a = "hello"char b10;strcpy(b, a); / 不能用 b = a;if(strcmp(b, a) = 0) / 不能用 if (b = a)/ 指針int len = st

48、rlen(a);char *p = (char *)malloc(sizeof(char)*(len+1);strcpy(p,a); / 不要用 p = a;if(strcmp(p, a) = 0) / 不要用 if (p = a)示例3.2 數(shù)組和指針的內(nèi)容復(fù)制與比較3.3 計算內(nèi)存容量用運算符sizeof可以計算出數(shù)組的容量(字節(jié)數(shù))。示例7-3-3(a)中,sizeof(a)的值是12(注意別忘了)。指針p指向a,但是sizeof(p)的值卻是4。這是因為sizeof(p)得到的是一個指針變量的字節(jié)數(shù),相當(dāng)于sizeof(char*),而不是p所指的內(nèi)存容量。C+/C語言沒有辦法知道指針

49、所指的內(nèi)存容量,除非在申請內(nèi)存時記住它。注意當(dāng)數(shù)組作為函數(shù)的參數(shù)進(jìn)行傳遞時,該數(shù)組自動退化為同類型的指針。示例7-3-3(b)中,不論數(shù)組a的容量是多少,sizeof(a)始終等于sizeof(char *)。char a = "hello world"char *p = a;cout<< sizeof(a) << endl; / 12字節(jié)cout<< sizeof(p) << endl; / 4字節(jié)示例3.3(a) 計算數(shù)組和指針的內(nèi)存容量void Func(char a100)cout<< sizeof(a)

50、<< endl; / 4字節(jié)而不是100字節(jié)示例3.3(b) 數(shù)組退化為指針指針參數(shù)是如何傳遞內(nèi)存的?如果函數(shù)的參數(shù)是一個指針,不要指望用該指針去申請動態(tài)內(nèi)存。示例7-4-1中,Test函數(shù)的語句GetMemory(str, 200)并沒有使str獲得期望的內(nèi)存,str依舊是NULL,為什么?void GetMemory(char *p, int num)p = (char *)malloc(sizeof(char) * num);void Test(void)char *str = NULL;GetMemory(str, 100); / str 仍然為 NULL strcpy(s

51、tr, "hello"); / 運行錯誤示例4.1 試圖用指針參數(shù)申請動態(tài)內(nèi)存毛病出在函數(shù)GetMemory中。編譯器總是要為函數(shù)的每個參數(shù)制作臨時副本,指針參數(shù)p的副本是 _p,編譯器使 _p = p。如果函數(shù)體內(nèi)的程序修改了_p的內(nèi)容,就導(dǎo)致參數(shù)p的內(nèi)容作相應(yīng)的修改。這就是指針可以用作輸出參數(shù)的原因。在本例中,_p申請了新的內(nèi)存,只是把_p所指的內(nèi)存地址改變了,但是p絲毫未變。所以函數(shù)GetMemory并不能輸出任何東西。事實上,每執(zhí)行一次GetMemory就會泄露一塊內(nèi)存,因為沒有用free釋放內(nèi)存。如果非得要用指針參數(shù)去申請內(nèi)存,那么應(yīng)該改用“指向指針的指針”,見示

52、例4.2。void GetMemory2(char *p, int num)*p = (char *)malloc(sizeof(char) * num);void Test2(void)char *str = NULL;GetMemory2(&str, 100); / 注意參數(shù)是 &str,而不是strstrcpy(str, "hello"); cout<< str << endl;free(str); 示例4.2用指向指針的指針申請動態(tài)內(nèi)存由于“指向指針的指針”這個概念不容易理解,我們可以用函數(shù)返回值來傳遞動態(tài)內(nèi)存。這種方法更加簡

53、單,見示例4.3。char *GetMemory3(int num)char *p = (char *)malloc(sizeof(char) * num);return p;void Test3(void)char *str = NULL;str = GetMemory3(100); strcpy(str, "hello");cout<< str << endl;free(str); 示例4.3 用函數(shù)返回值來傳遞動態(tài)內(nèi)存用函數(shù)返回值來傳遞動態(tài)內(nèi)存這種方法雖然好用,但是常常有人把return語句用錯了。這里強調(diào)不要用return語句返回指向“棧內(nèi)存

54、”的指針,因為該內(nèi)存在函數(shù)結(jié)束時自動消亡,見示例4.4。char *GetString(void)char p = "hello world"return p; / 編譯器將提出警告void Test4(void)char *str = NULL;str = GetString(); / str 的內(nèi)容是垃圾cout<< str << endl;示例4.4 return語句返回指向“棧內(nèi)存”的指針用調(diào)試器逐步跟蹤Test4,發(fā)現(xiàn)執(zhí)行str = GetString語句后str不再是NULL指針,但是str的內(nèi)容不是“hello world”而是垃圾。如

55、果把示例4.4改寫成示例4.5,會怎么樣?char *GetString2(void)char *p = "hello world"return p;void Test5(void)char *str = NULL;str = GetString2();cout<< str << endl;示例4.5 return語句返回常量字符串函數(shù)Test5運行雖然不會出錯,但是函數(shù)GetString2的設(shè)計概念卻是錯誤的。因為GetString2內(nèi)的“hello world”是常量字符串,位于靜態(tài)存儲區(qū),它在程序生命期內(nèi)恒定不變。無論什么時候調(diào)用GetStri

56、ng2,它返回的始終是同一個“只讀”的內(nèi)存塊。杜絕“野指針”“野指針”不是NULL指針,是指向“垃圾”內(nèi)存的指針。人們一般不會錯用NULL指針,因為用if語句很容易判斷。但是“野指針”是很危險的,if語句對它不起作用。 “野指針”的成因主要有兩種:(1)指針變量沒有被初始化。任何指針變量剛被創(chuàng)建時不會自動成為NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創(chuàng)建的同時應(yīng)當(dāng)被初始化,要么將指針設(shè)置為NULL,要么讓它指向合法的內(nèi)存。例如char *p = NULL;char *str = (char *) malloc(100);(2)指針p被free或者delete之后,沒有置為

57、NULL,讓人誤以為p是個合法的指針。(3)指針操作超越了變量的作用范圍。這種情況讓人防不勝防,示例程序如下:class A public:void Func(void) cout << “Func of class A” << endl; ;void Test(void)A *p;A a;p = &a; / 注意 a 的生命期p->Func(); / p是“野指針”函數(shù)Test在執(zhí)行語句p->Func()時,對象a已經(jīng)消失,而p是指向a的,所以p就成了“野指針”。但奇怪的是我運行這個程序時居然沒有出錯,這可能與編譯器有關(guān)。有了malloc/free

58、為什么還要new/delete?malloc與free是C+/C語言的標(biāo)準(zhǔn)庫函數(shù),new/delete是C+的運算符。它們都可用于申請動態(tài)內(nèi)存和釋放內(nèi)存。對于非內(nèi)部數(shù)據(jù)類型的對象而言,光用maloc/free無法滿足動態(tài)對象的要求。對象在創(chuàng)建的同時要自動執(zhí)行構(gòu)造函數(shù),對象在消亡之前要自動執(zhí)行析構(gòu)函數(shù)。由于malloc/free是庫函數(shù)而不是運算符,不在編譯器控制權(quán)限之內(nèi),不能夠把執(zhí)行構(gòu)造函數(shù)和析構(gòu)函數(shù)的任務(wù)強加于malloc/free。 因此C+語言需要一個能完成動態(tài)內(nèi)存分配和初始化工作的運算符new,以及一個能完成清理與釋放內(nèi)存工作的運算符delete。注意new/delete不是庫函數(shù)。我

59、們先看一看malloc/free和new/delete如何實現(xiàn)對象的動態(tài)內(nèi)存管理,見示例6。class Objpublic :Obj(void) cout << “Initialization” << endl; Obj(void) cout << “Destroy” << endl; void Initialize(void) cout << “Initialization” << endl; void Destroy(void) cout << “Destroy” << endl; ;void UseMallocFree(void)Obj *a = (

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論