釋出
2011年11月12日 (最後更新:2011年11月12日)

指標

評分:4.2/5 (437 票)
*****
你熟悉 int 嗎? int 是一個物件,它佔用一定的記憶體空間(通常是 4 位元組,有時是 8 位元組),並存儲一個數值。

指標看起來也一樣。它佔用與 int 相同的記憶體空間,並存儲一個數值。所有指標看起來都一樣,無論是指向 int 的指標還是指向 double 的指標,或是其他型別的指標。它們佔用相同的空間,並存儲一個數值。

到目前為止,你明白了嗎?

現在,你知道如何建立 int 了嗎?像這樣

 
int a;


你建立指標的方式完全一樣;

1
2
int* a; // This create a pointer to an int
double* b; // This creates a pointer to a double  


就像任何物件一樣,在使用指標之前必須先建立它。

你可以使用這種語法建立指向任何你能想到的物件的指標,但別被愚弄了——所有指標都佔用 4 (有時是 8) 位元組,並存儲一個數值。

你知道,如果你建立一個 int 而不給它賦值,它就會包含一些垃圾值嗎?只是當時記憶體中碰巧有的任何值。

 
int b; // This int could have any value at all  


指標也是一樣

 
int* b; // This int pointer could have any value at all  


當然,給事物命名的相同規則也適用,所以這個

1
2
int a;
int* a;


將無法編譯,因為我有兩個同名的物件。

到目前為止,很簡單,對吧?

指標有一個特殊的功能。你還記得指標儲存一個數值嗎?我可以對指標執行一個稱為“解引用”的操作,這僅僅意味著我返回儲存在那個數值所指向的記憶體位置的那個物件。所以,如果我的指標儲存的值是 0x22334455(我這裡使用了十六進位制,但它只是一個數字),當我解引用指標時,我將獲得記憶體位置 0x22334455 處的任何內容。我們有時說指標指向記憶體位置 0x22334455 中的物件。

我們如何知道要將什麼數值放入指標(或者換句話說,我們如何知道物件的記憶體地址,以便我們可以將該記憶體地址儲存在指標中)?幸運的是,存在一個運算子 "&",它將返回任何物件的地址(即該物件在記憶體中的位置的數值)。

1
2
int a; // an integer
&a; // the address of that integer 


所以,這是我們如何使用它

1
2
int* p; // a new pointer, with some garbage value - could be pointing anywhere
p = &a; // now we've stored the address of a in the pointer, so now the pointer is pointing at the object a  


在某些情況下,你可能需要手動輸入地址位置,你可以這樣做。假設你知道某個感興趣的值確實在地址 0x2323FFFF。你可以手動設定它,我們稍後會講到。

如果所有指標的大小都相同並且儲存一個數值,那麼為什麼我們有 int* 和 double* 和 string* 以及其他所有型別?因為當我們解引用它們時,我們需要知道我們會得到什麼型別的物件。僅此而已。如果我們想要麻煩一點,我們可以建立一個 int*,然後強制它指向一個 double,因為指標只儲存一個數字——它不儲存任何關於它指向的內容型別的資訊。不過,這有點高階。

我們使用 "*" 來執行這個解引用操作。所以,如果我有一個名為 MrPointer 的指標,並且我想獲取它指向的內容,我會這樣做

 
*MrPointer;


假設它指向一個 int。我可以這樣做

 
int someInt = *MrPointer;


或者我也可以在表示式中使用它

 
int anotherInt = 4 + *MrPointer;


我將詳細說明。
1
2
3
4
5
6
7
8
9
10
11
12
int a = 7; // Make an int, give it the value 7
int* MrPointer; // Make a pointer. Don't give it a value - it's got some garbage value
            //      if I try to use it, I deserve everything I get!

MrPointer = &a; // Now, MrPointer contains the memory address of the int a

*MrPointer; // This means "get me the int at the memory address you're pointing to" - we know the value
          //    of that int will be 7. I'm not doing anything with it, so this is a bit of a waste of code!

int b = *MrPointer; // Now I've made a new int, and set it to the value of whatever MrPointer points at. 
                   //    We know MrPointer holds the memory address of a, so the value returned is 7,
                   //     so the value of b will be 7.  


那麼,接下來是指標算術。

你對普通算術很熟悉嗎?讓我們用 int 來回顧一下。

1
2
3
int a = 7;
a++; // Now, a holds the value 8
a = a+4; // Now, a holds the value 12  


很簡單。指標呢?首先,後退一步,回想一下指標的意義。它們的目的是儲存記憶體地址,而記憶體地址是記憶體中某個物件的地址。所以 b 將有一個值,比如 0x22334455。

 
int* b = &a; // the pointer b holds the memory address of our int a, from above  


所以 b 將有一個值,比如 0x22334455。

指標算術就是對指標進行加(和減)值的算術。回想一下,指標儲存一個記憶體地址,所以指標算術就是對記憶體地址進行加(和減)值的算術。如果我們給指標加 1,我們不是說“將 1 加到你正在持有的記憶體地址”。我們的意思是“前進足夠的記憶體,以便指向記憶體中的下一個物件”。

int 通常是 4 位元組。如果 b 的地址是 0x22334455,那麼 int 將佔用位元組 0x22334455、0x22334456、0x22334457 和 0x22334458(在某些系統上,它將是 0x22334455、0x22334454、0x22334453 和 0x22334452——哪個都無所謂,因為你的編譯器會為你跟蹤所有這些事情,並正確調整指標)。所以,如果我們有一個指向 int 的指標,比如上面的 b,當我們給那個指標加 1 時,記憶體地址將改變 4 個位元組,以便它現在持有記憶體中下一個 int 的記憶體地址。我們需要確保那裡確實有一個 int!編譯器會信任我們做得對。

所以,假設我們有兩個 int 值在記憶體中是相鄰的。我們可以透過建立一個數組來實現

1
2
int c[2]; // This will make two int values in memory, right next to each other
int* p = &(c[0]); // This makes a pointer p, and gives it the value of the address of c[0], which is the first int  


請注意,這也將起作用;

1
2
int*p = c; // This makes a pointer p, and gives it the value of the address of c, which is itself a pointer,
            // pointing at the start of the array.  


現在,讓我們給指標加 1。

 
p = p+1; // Now, the pointer holds the value of the next int along, c[1].  


很巧妙,不是嗎?當你給指標加 1 時,它持有的值不會改變 1;它會改變正確的量,以便指向下一個物件。它知道那有多遠,因為當你建立它時,你指定了它將指向的物件型別。如果它是一個 int 指標,它持有的值將改變 4 個(有時是 8 個)位元組。如果它指向一個佔 40 位元組的奇怪物件,那麼值將改變 40 個位元組。你不需要擔心記憶體地址實際改變了多少;你可以相信編譯器會為你處理。

如果你給指標加 2,指標的值將改變正確的量以指向記憶體中靠後的第 2 個物件,依此類推。如果你從指標減 1,它將指向記憶體中靠前的第 1 個物件。

所以,檢查一下這個

1
2
3
4
5
6
7
double x[10]; // an array of 10 doubles
double* p = x; // p is a pointer, now pointing at the first double
for (int i=0;i<10;i++)
{
  std::cout << *p << std::endl;  // output the double value
  p++; // make the pointer point to the next one
}


此程式碼輸出了一個 double 陣列的(垃圾)值,並演示了指標算術。

你會意識到,這開啟了各種巧妙的功能。如果你有一個 int,僅僅為了好玩,你想逐個檢視每個位元組,那該怎麼辦?char 定義為一位元組長,所以如果你有一個 char 指標,讓它指向那個 int,然後你給 char 指標加 1,你就會看到下一個位元組——在 int 裡面!

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main()
{

  int a = 34;
  char* p = (char*)&a; // Make a char*, and force the compiler to treat the address of a as a char pointer.
  for (int i =0;i<4;i++)
    {std::cout << (int)(*p) << std::endl; p++;} // Cycle through the 4 bytes of the int, outputting each byte as if it were an int
  return 0;
}


這個會稍微有趣一點

1
2
3
4
5
6
7
8
9
#include <iostream>
int main()
{
  int a = 256;
  char* p = (char*)&a; // Make a char*, and force the compiler to treat the address of a as a char pointer.
  for (int i =0;i<4;i++)
    {std::cout << (int)(*p) << std::endl; p++;} // Cycle through the 4 bytes of the int, outputting each byte as if it were an int
  return 0;
}


你可以深入瞭解記憶體中的確切數字,你應該能夠弄清楚數字 256 是如何用這 4 個位元組表示的。

正如你所猜到的,我們可以用這種直接干預單個位元組的強大能力做很多瘋狂的事情。歡迎來到 C 和 C++ 的底層威力!


使用 "*" 處理指標時的不同用法
"*" 有兩種含義。


讓我們先講第一種含義
1
2
int b; // Make a new object. The new object is to be an int.
int* a; // Make a new object. The new object is to be an int-pointer  


當我們建立物件時,"*" 只是描述我們想要建立的物件型別的一部分。一旦我們建立了物件,我們只需使用物件的名稱。

這是我們如何傳遞我們剛剛建立的物件 b

 
someFunction(b);


當我們實際使用 b 時,我們不需要指定它是一個 int。

所以,這是我們如何傳遞我們剛剛建立的指標

 
someOtherFunction(a);


當我們實際使用 a 時,我們不需要指定它是一個 int 指標。

"*" 的另一種用途是作為解引用運算子。我們在指標上使用它,當我們不想要實際的指標時;我們想要它指向的東西。

1
2
int* c; // Here, "*" is part of the kind of object we want to make
*c; // Here, "*" means dereference the pointer and give us what it points at  


這是兩種不同的用法,具有兩種不同的含義。如果你看到 int* 或 double* 或 string* 或任何其他這樣的 <object-type>*,它是在描述一種物件型別。如果你看到 *pointer,它的意思是解引用。

你將在建立指標時以及在指定函式接收(和返回)的物件型別時看到“描述一種物件型別”的用法,例如在這個函式原型中

 
void DealDamage(int *EnemyHpPointer, int damage, string EnemyName);


DealDamage 接收一個指向 int 的指標、一個 int 和一個字串。它可以這樣使用

1
2
3
4
5
6
int* somePointer;
int someValue;
string someString;

// and then, after these have been given values, use the function
DealDamage(somePointer, someValue, someString);