C和指針


指針的魅力


指針說:love me,love me!

但是他對指針說:I hate u,I hate u!

……

 

指針僅僅是作爲指針,我們可以把它當做有用的工具,爲我們提供便利與好處。說起工具不得不讓我想起一樣東西——鋤頭,因爲原人類有了鋤頭才使人類文明進入了開荒造田的農業時代,解決了溫飽,開啓了人類新紀元。可以這麼說吧鋤頭使人類文明得到進步,沒有鋤頭也就沒有今天的我們,其地位與重要性可想而知。那麼我們的指針何以能發揮像鋤頭那樣驚人的魅力呢?

 

魅力1 算法之找我的名字——簡單,靈活,快捷

以下算法功能是在一個字符串中查找長度爲8的一個字符子串,比如我的名字“ZhanHang”就是一個8長度的字串。算法解釋:因爲字串myname的長度爲8,也就是它是一個8字節的內存連續的數組,而myname指向這一段內存。又Long long 指針類型是一個指向8字節內存的類型,因此就可以將myname轉換成long long 類型指針,如此在進行子串的比較時,就可以直接比較兩個long long 類型的變量即可,免去了對子串進行遍歷的麻煩。詳情請看代碼。

  1. #include <stdio.h>  
  2.   
  3. int find_my_name(const char* str, int str_len, const char* myname)  
  4. {  
  5.     long long *pkey = (long long*)myname;  
  6.     long long *curr_str = NULL;  
  7.     str_len = str_len - sizeof(long long); //此處是爲了for循環作的優化處理,因爲後面7個字符不需要遍歷檢測了  
  8.     for(int i=0; i<=str_len; i++)  
  9.     {  
  10.         curr_str = (long long*)&str[i]; //得到一個新的子串  
  11.         if( *pkey ==  *curr_str ) //判斷兩個字串是否相等  
  12.             return i+1;  //返回子串在字串中的位置  
  13.     }  
  14. }  
  15. /** 
  16. Author:花心龜 
  17. Blog:http://blog.csdn.net/zhanxinhang 
  18. **/   
  19. int main()  
  20. {  
  21.     char str[]="alZhanhangadf";  
  22.     char myname[]="Zhanhang";  
  23.     printf("my name at %d\n",find_my_name(str,sizeof(str)/sizeof(char),myname));//輸出結果是3  
  24.       
  25.     return 0;  
  26. }  
小結:指針實際上指向一內存空間,其內存空間的大小由指針類型而定。靈活的應用指針是能夠爲編程帶來便利的。

(題外語:作者使用此實例旨在通過靈活應用指針的例子,幫助理解指針,並通過理解指針作爲本文介紹指針魅力的起點,實際工程中並不贊同此方法,因爲本方法存在擴展性差,移植性差等問題。)



魅力2 迭代器——高效

“迭代”一詞在漢語裏面是“更替”的意思,也就是說“迭代器”的意思就是指用來做更替操作的工具。實例:實現算法使一個字符串顛倒順序,實現步驟:構建兩個迭代器p 和 q ,在一次遍歷中,p的位置從字串開頭向中間不斷更替,q從字串末尾向中間不斷更替,

然後每次交換p和q所指向的內容即可,直到p和q在中間相遇,這時循環次數剛好等於字串的長度_l/2。

實現代碼:

  1. void reverse(char *_str,int _l) //反轉函數,_l指要反轉字串的長度  
  2. {  
  3.  char *p=_str,*q=_str+_l-1,temp;  
  4.   
  5.  while(q>p)  
  6.    {   
  7.      temp=*p;  
  8.      *p=*q;  
  9.      *q=temp;  
  10.    
  11.      p++;  
  12.      q--;  
  13.    }  
  14. }  

我若不用迭代器p和q呢?用兩個變量i和j作爲str的下標,也就是說訪問元素的方式變爲:str[i],str[j],如下

  1. void reverse(char *_str,int _l) //反轉函數,_l指要反轉字串的長度  
  2. {  
  3.   int i=0,j=_l-1,temp;  
  4.    
  5.  while(j>i)  
  6.    {   
  7.      temp=str[i];  
  8.      str[i]=str[j];  
  9.      str[j]=str[i];  
  10.    
  11.      i++;  
  12.      j--;      
  13.    }  
  14. }  
這樣並不比上面用迭代器的情況好,而且要糟很多,因爲這樣用str[i],str[j]的下標的方式訪問元素時,需要先對str所存的數組首地址進行一次加減運算才能正確得到第i個、第j個值(讀者可在任何一款編譯器上進行反彙編查看),上面一共出現了5次下標訪問str元素,情況可想而知。

小結:迭代器p和q在這裏起到了不可更替的作用,正是因爲p和q使算法效率得到了提升。

 

 

魅力3 函數指針——高擴展性

何爲函數指針:函數指針就是指向某個函數的指針,指針變量存儲的是函數的地址。

如何定義一個函數指針:定義爲 [ 返回類型 (*pfun) (形參,…) ]。

如何使用函數指針:pfun(實參...) 或者(*pfun)(實參...)。

現看一個實例:

  1. #include <stdio.h>  
  2. int fun0(const int &a)//定義謂詞1 (我們把作爲函數參數的函數稱爲謂詞)  
  3. {  
  4.     if(a==1)  
  5.         return 1;  
  6.     else  
  7.         return 0;  
  8. }  
  9. int fun1(const int &a)//定義謂詞2  
  10. {  
  11.     if(a<10)  
  12.         return 1;  
  13.     else  
  14.         return 0;  
  15. }  
  16. /*根據whatCondition函數指針所指的函數(謂詞)設置的條件打印數組*/  
  17. void print_arr_if(const int *arr, int alen, int(*whatCondition)(const int&))  
  18. {  
  19.     for(inti=0; i<alen; i++)  
  20.     {  
  21.         if( whatCondition(arr[i]) )  
  22.             printf("%d ",arr[i]);  
  23.     }  
  24.     printf("\n");  
  25. }  
  26. /** 
  27. Author:花心龜 
  28. Blog:http://blog.csdn.net/zhanxinhang 
  29. **/  
  30. int main()  
  31. {    
  32.     int arr[]={1,1,2,1,11,12,3,10};  
  33.     print_arr_if(arr,sizeof(arr)/sizeof(int),fun0); //打印結果爲1 1 1  
  34.     print_arr_if(arr,sizeof(arr)/sizeof(int),fun1); //打印結果爲1,1,2,1,3  
  35.    
  36.       
  37.     return 0;  
  38. }  

小結:以上利用whatCondition函數指針大大提高了print_arr_if函數的擴展性,通過對謂詞的定義幾乎可打印你想要的任何數組元素。

 

魅力4 函數傳遞——高效,實用

c/c++有三種函數傳遞方式,它們分別是值傳遞,指針傳遞和引用傳遞,其中指針傳遞與引用傳遞都是將地址傳進函數,基本上沒什麼區別。

指針及引用作爲函數傳遞類型,與值傳遞相比,是高效的。爲什麼高效呢?請看如下:

現有如下結構體:

  1. typedef struct structType  
  2. {  
  3. int i;  
  4. char arr[100];  
  5. }structType;  
  6. //一個print函數的定義:  
  7. void print0(const structType data)  
  8. {  
  9.        //printf something about data  
  10. }  
  11. //另一個print函數的定義:  
  12. void print1(const structType *pdata)  
  13. {  
  14.        //printf something about data  
  15. }  

通過比較發現,print1比print0有明顯的效率優勢,因爲print0是值傳遞,當值傳進去時,必須要開闢一個structType那麼大的內存空間來乘裝這些值,這就要相當大的一部分資源消耗,而print1是指針傳遞,傳進去的是地址,一個地址只需4字節內存空間,使用時解析其指針即可,因此它比print0更高效更實用。

再看我們如何用一個函數交換兩個變量的值:

  1. swap(int &a, int &b)  
  2. {  
  3.      int temp = a;  
  4.      a= b ;  
  5.      a = temp;  
  6. }  

交換函數只有使用引用傳遞或指針傳遞才能改變形參a和b所指向的內存的值。

小結:函數的指針傳遞與引用傳遞提供了函數傳遞的一個高效的途徑,另外,若要使用某個函數來改變某一變量的值唯有使用指針傳遞或引用傳遞給該函數,這體現了實用性。



魅力5 鏈——實用

在鏈表,二叉樹等數據結構中,指針作爲鏈接兩個節點的“鏈”,時刻牽絆着它們的邏輯關係。指針爲這些數據結構提供了高效實現的可能,因此使我們能夠在內存世界裏完成對自然事物的邏輯構造,使我們的計算機更具實用價值。

 


魅力6 動態內存分配——實用

動態內存分配離不開指針,假如把內存空間當做容器,每個程序都有它自己的容器,它如同一個容器補給器,可隨時搭載從外界而來的一個個容器,隨時爲您排憂解難,爲程序分憂。不僅如此,你可以通過該補給器將申請而來的容器送回去,讓它爲別地應用程序所利用。

 

……


另:如何善用指針

首先看什麼是野指針和空指針

空指針: 如 int *p = NULL 這就定義了一個指針,通常NULL是一個零值,操作系統定義內存64kb以下的內存單元是不可訪問的,所以像如 *p = 9 這樣給他賦值是系統不允許的,將會發生內存報錯。

野指針: 如 int *p就是一個野指針,可以看到它在創建時沒有賦初值,所以它的值是一個隨機數,也就是亂指一氣,通常都是指向了不合法的內存段,所以使用它也會內存報錯。還有指針p被free或者delete之後也會成爲野指針,因爲它所指的內存空間被釋放之後,變成了一個不合法內存段。野指針,它顧名思義它就是一個野指針,它是沒有主人領養的野獸,兇猛殘暴,用它你就得自食其果。

指針防災措施:一,養成好習慣,在每聲明一個指針時便對它賦初值NULL。二、養成好習慣,在指針被free或者delete之後,對其賦值爲NULL。三、在一定做好一和二的基礎下,就可以在使用指針之前只用if語句判斷指針是否爲空,以做到防錯。四、避免強制類型轉換指針,除非能確保轉換前後類型所佔用的空間大小是一致的。詳情可看此文:http://blog.csdn.net/zhanxinhang/article/details/6719387。另外 、無論何時,只要有幾個指針同時指向了同一內存空間,就必須考慮在什麼時候刪除該對象。因爲如果刪除得太早,就會有某個仍然指向它的指針存在,再去使用這個指針就會發生未定義行爲。如果刪除得太晚,又會佔用本來可以被其他程序應用的內存空間。


總結:指針可以當做一很好的工具,只要我們好好理解它善用它,它就能發揮它所具有的魅力。也許要真正理解指針需要些許時間去磨練去思考,但是有付出總有回報,回報過後都是值得的。現如今由於java,c#等語言的盛行,似乎指針的用武之地變少了。那麼是不是有了java,c#等理解指針就不重要了呢?非也,非也。我從高中就開始接觸編程,接觸指針,上到大學纔開始接觸java,c#等,從中我發現這些語言沒了指針確實是會少了些煩惱,尤其是對初手。但,對指針的理解(注意指針與內存息息相關)有助於您瞭解java等的底層世界,如垃圾回收機制,java虛擬機等,我認爲了解這些對一個走專業化道路的java程序員是必須的。多瞭解點底層,多一點自由,少一點束縛。雖然要說讓指針發揮得像鋤頭那樣的驚人魅力,有點大,但至少在計算機世界裏,它是的,因爲有了它纔有了像java這樣神奇的東西。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章