新手指南——指針篇(轉載)

 什麼是指針?
       和其它變量一樣,指針是基本的變量,所不同的是指針包含一個實際的數據,該數據代表一個可以找到實際信息的內存地址。這是一個非常重要的概念。許多程序和思想依靠指針作爲他們設計的基礎。
開始
       怎樣定義一個指針呢?除了你需要在變量的名稱前面加一個星號外,其它的和別的變量定義一樣。舉個例子,以下代碼定義了兩個指針變量,它們都指向一個整數。
int* pNumberOne;
int* pNumberTwo;
注意到兩個變量名稱前的前綴’p’了麼?這是一個慣例,用來表示這個變量是個指針。
       現在,讓我們將這些指針實際的指向某些東西:
pNumberOne = &some_number;
pNumberTwo = &some_other_number;
‘&’符號應該讀作什麼什麼的地址,它返回一個變量在內存中的地址,設置到左側的變量中。因此,在這個例子中,pNumberOne設置和some_number的地址相同,因此pNumberOne現在指向some_number
       現在,如果我們想訪問some_number的地址,可以使用pNumberOne。如果我們想通過pNumberOne訪問some_number的值,那麼應該用*pNumberOne。這個星號表示解除指針的參照,應該讀作“什麼什麼指向的內存區域”。
到現在我們學到了什麼?舉個例子
喲,有許多東西需要理解。我的建議是,如果你有哪個概念沒有弄清楚的話,那麼,不妨再看一遍。指針是個複雜的對象,可能需要花費一段時間來掌握它。
這兒有一個例子示範上面所將的概念。這是用C寫的,沒有C++擴展。
#include <stdio.h>
 
void main()
{
    // 申明變量
    int nNumber;
    int *pPointer;
 
    //賦值
    nNumber = 15;
    pPointer = &nNumber;
 
    // 輸出nNumber的值
    printf("nNumber is equal to : %d/n", nNumber);
 
    // 通過pPointer修改nNumber的值
    *pPointer = 25;
 
// 證明nNumber已經被改變了
// 再次打印nNumber的值
    printf("nNumber is equal to : %d/n", nNumber);
}
       通讀一遍,並且編譯樣例代碼,確信你理解了它爲什麼這樣工作。如果你準備好了,那麼繼續。
一個陷阱!
看看你能否發現下面這段程序的毛病:
#include <stdio.h>
 
int *pPointer;
 
void SomeFunction();
{
    int nNumber;
    nNumber = 25;   
 
    //pPointer指向nNumber
    pPointer = &nNumber;
}
 
void main()
{
    SomeFunction(); //pPointer做些事情
 
    // 爲什麼會失敗?
    printf("Value of *pPointer: %d/n", *pPointer);
}
這段程序先調用SomeFunction函數,該函數創建一個叫做nNumber的變量,並將pPointer指向它。那麼,問題是,當函數退出時,nNumber被刪除了,因爲它是一個局部變量。當程序執行到局部變量定義的程序塊以外時,局部變量總是被刪除了。這就意味着,當SomeFunction函數返回到main函數時,局部變量將被刪除,因此pPointer將指向原先nNumber的地址,但這個地址已經不再屬於這段程序了。如果你不理解這些,那麼重新閱讀一遍關於局部變量和全局變量的作用範圍是明智的選擇。這個概念也是非常重要的。
       那麼,我們如何解決這個問題呢?答案是使用大家都知道的一個方法:動態分配。請明白CC++的動態分配是不同的。既然現在大多數程序員都使用C++,那麼下面這段代碼就是常用的了。
動態分配
       動態分配可以說是指針的關鍵所在。不需要通過定義變量,就可以將指針指向分配的內存。也許這個概念看起來比較模糊,但是確實比較簡單。下面的代碼示範如何爲一個整數分配內存:
int *pNumber;
pNumber = new int;
       第一行申明瞭一個指針pNumber,第二行分配一個整數內存,並且將pNumber指向這個新內存。下面是另一個例子,這次用一個浮點數:
double *pDouble;
pDouble = new double;
       動態分配有什麼不同的呢?當函數返回或者程序運行到當前塊以外時,你動態分配的內存將不會被刪除。因此,如果我們用動態分配重寫上面的例子,可以看到現在能夠正常工作了。
#include <stdio.h>
 
int *pPointer;
 
void SomeFunction()
{
    // make pPointer point to a new integer
    pPointer = new int;
    *pPointer = 25;
}
 
void main()
{
    SomeFunction(); // make pPointer point to something
    printf("Value of *pPointer: %d/n", *pPointer);
}
       通讀一遍,編譯上面的代碼,確信你已經理解它是如何工作的。當調用SomeFunction時,分配了一些內存,並且用pPointer指向它。這次,當函數返回時,新內存就完整無缺了。因此pPointer仍舊指向有用的東西。這是因爲使用了動態分配。確信你已經理解它了。那麼繼續向下看,瞭解爲什麼上面的程序還會有一系列的錯誤。
內存分配和內存釋放
這裏有一個問題,可能會變得十分嚴重,雖然它很容易補救。這個問題就是,雖然你用動態分配可以方便的讓內存完整無缺,確實不會自動刪除,除非你告訴計算機,你不再需要這塊內存了,否則內存將一直被分配着。因此結果就是,如果你不告訴計算機你已經使用完這塊內存,那麼它將成爲被浪費的空間,因爲其它程序或者你的應用程序的其它部分不能使用這塊內存。最終將導致系統因爲內存耗盡而崩潰。因此這個問題相當重要。內存使用完後釋放非常容易:
delete pPointer;
       需要做的就是這些。但是你必須確定,你刪除的是一個指向你實際分配的內存的指針,而不是其它任何垃圾。嘗試用delete已經釋放的內存是危險的,並且可能導致程序崩潰。
       這裏再次舉個例子,這次修改以後就不會有內存浪費了。
#include <stdio.h>
 
int *pPointer;
 
void SomeFunction()
{
// make pPointer point to a new integer
    pPointer = new int;
    *pPointer = 25;
}
 
void main()
{
    SomeFunction(); // make pPointer point to something
    printf("Value of *pPointer: %d/n", *pPointer);
 
    delete pPointer;
}
只有一行不同,但這行是要點。如果你不刪除內存,就會導致“內存泄漏”,內存將逐漸減少,除非應用程序重新啓動,否則將不能再生。
向函數傳遞指針
傳遞指針給函數非常有用,但不容易掌握。如果我們寫一個程序,傳遞一個數值並且給它加上5,我們也許會寫出如下的程序:
#include <stdio.h>
 
void AddFive(int Number)
{
    Number = Number + 5;
}
 
void main()
{
    int nMyNumber = 18;
   
    printf("My original number is %d/n", nMyNumber);
    AddFive(nMyNumber);
    printf("My new number is %d/n", nMyNumber);
}
但是,程序中函數AddFive的參數Number只是變量nMyNumber的一個拷貝,而不是變量本身,因此,Number = Number + 5只是爲變量的拷貝增加了5,而不是最初的在main()函數中的變量。當然,你可以運行程序,以證明這一點。
       爲了將值傳遞出去,我們可以傳遞這個變量的指針到函數中,但我們需要修改一下函數,以便傳遞數值的指針而不是數值。因此將void AddFive(int Number)修改爲void AddFive(int *Number),增加了一個星號。下面是修改了的函數,注意,我們必須確認傳遞了nMyNumber的地址,而不是它本身。這通過增加&符號來完成,通常讀作“什麼什麼的地址”。
#include <stdio.h>
void AddFive(int* Number)
{
    *Number = *Number + 5;
}
 
void main()
{
    int nMyNumber = 18;
   
    printf("My original number is %d/n", nMyNumber);
    AddFive(&nMyNumber);
    printf("My new number is %d/n", nMyNumber);
}
大家可以試着自己做個例子來實驗一下。注意在AddFive函數中Number變量前那個重要的星號。只是必須的,用來告訴編譯器我們想將5加到變量Number指向的數值,而不是將5加到指針本身。
關於函數最後需要注意的是你也可以返回一個指針。比如:
int * MyFunction();
       在這個例子中,MyFunction函數返回一個指向整數的指針。
類的指針
關於指針還有兩個需要注意的問題。其中一個是結構或者類。你可以如下定義一個類:
class MyClass
{
public:
    int m_Number;
    char m_Character;
};
然後,你可以如下方式定義一個類變量:
MyClass thing;
       你應該已經知道這些了,如果還不知道的話,那麼再將上面的內容讀一遍。定義MyClass的指針應該這麼寫:
MyClass *thing;
       然後你需要分配內存,並將指針指向這個內存
       thing = new MyClass;
       問題來了,你如何使用這個指針呢?一般的,我們寫thing.m_Number,但你不能對指針用’.’操作,因爲thing不是一個MyClass對象。只是指向一個MyClass對象的指針。因此,指針thing不包含m_Number這個變量。只是它指向的結構中包含這個變量。因此,我們必須使用一個不同的協定,用->取代’.’。以下是一個例子:
class MyClass
{
public:
    int m_Number;
    char m_Character;
};
 
void main()
{
    MyClass *pPointer;
    pPointer = new MyClass;
 
    pPointer->m_Number = 10;
    pPointer->m_Character = 's';
 
    delete pPointer;
}
數組的指針
你也可以構造一個指向數組的指針,如下:
int *pArray;
int MyArray[6];
pArray = &MyArray[0];
將創建一個叫做pArray的指針,指向一個包含6個元素的數組。另一種構造的方法是使用動態分配,如下:
int *pArray;
pArray = new int[6];
       注意,你這裏也可以不用&MyArray[0],而直接使用&MyArray取代。當然,這僅僅適用於數組。
使用指向數組的指針
       一旦你有了指向數組的指針,那麼如何使用它呢?現在假設你有一個指向整數數組的指針,那麼指針開始時將指向第一個整數。舉例如下:
#include <stdio.h>
 
void main()
{
    int Array[3];
    Array[0] = 10;
    Array[1] = 20;
    Array[2] = 30;
 
    int *pArray;
    pArray = &Array[0];
 
    printf("pArray points to the value %d/n", *pArray);
}
將指針移到指向數組的下一個值,可以用pArray++。也許你也可以猜出來了,我們可以用pArray+2的方式將指針向後移動兩個位置。要注意的問題是,你自己必須知道數組的上限是多少(例子中是3),因爲編譯器不能檢查你是否將指針移到了數組以外,因此你可以很容易的將系統搞崩潰了。以下是個例子,顯示我們設置的三個值:
#include <stdio.h>
 
void main()
{
    int Array[3];
    Array[0] = 10;
    Array[1] = 20;
    Array[2] = 30;
 
    int *pArray;
    pArray = &Array[0];
 
    printf("pArray points to the value %d/n", *pArray);
    pArray++;
    printf("pArray points to the value %d/n", *pArray);
    pArray++;
    printf("pArray points to the value %d/n", *pArray);
}
你也可以使用pArray-2這樣的方式來向前移動2個位置。不管是加或者減,你必須保證不是對指針所指向的數據的操作。這種操作指針和數組的方式在循環中是最常用的。例如forwhile循環。
       另外要提的是,如果你有一個指針比如int pNumberSet,你也可以把它當成數組。例如pNumberSet[0]等於*pNumberSet,並且pNumberSet[1]等於*(pNumberSet+1)
       對於數組,還有一點要注意的,如果你用new爲數組分配內存,比如:
int *pArray;
pArray = new int[6];
你必須用以下方式進行刪除:
delete[] pArray;
注意delete後的[],它告訴編譯器,這是刪除整個數組,而不僅僅是第一個元素。對於數組你必須使用這種方法,否則就會有內存泄漏。
總結
一條要注意的:你不能刪除不是用new分配的內存。比如以下例子:
void main()
{
    int number;
    int *pNumber = number;
   
    delete pNumber; // wrong - *pNumber wasn't allocated using new.
}
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章