【不是惡夢的開始,而是再度的精進。指標】
在談指標之前,我們先回顧一下變數。
我們都知道要使用某個變數之前都要先宣告變數,讓編譯程式來分配某個記憶體位置來存放變數。
當我們這麼宣告 int a = 7; 時,程式本身做了這樣的事情
記憶體位址 變數值 變數名稱
0x70fe14 7 a
程式會分配一塊記憶體 0x70fe1c 這個位址來存放變數值 7。
而所謂的一塊記憶體位址,並不是只有 0x70fe1c 這一個位址,而是根據作業系統與變數型態來決定需要多大的記憶體,以我的電腦來說,int 整數型態的變數佔4個位元組,所以變數a就佔0x70fe1c、0x70fe1d、0x70fe1e、0x70fe1f這四個位元組,而以0x70fe1c為標記位址。
所以用四個位元組來存放7這個數值。
但是變數名稱a又是存到哪兒呢?其實記憶體內的這四個位元組的名字就是a,a是給我們看的,四個位元組是給機器(電腦)看的。
在談指標之前,先介紹一下這個符號 '&'。
& 是取指運算子,宣告格式如下:
&變數名稱;
&a 就是取出變數a在記憶體中的位址
什麼是指標呢?
因為這個"記憶體位址0x70fe1c"是指向變數值7,所以我們就稱"記憶體位址0x70fe1c"是變數 a 的指標。
什麼是指標變數呢?
存放指標(記憶體位址)的變數就是指標變數,所以指標變數並不是存放數值、字元、字串...,而是存放記憶體位址。
指標變數的宣告也很類似各種變數的宣告,格式如下:
int a; //宣告整數變數
int* pi = &a; // 把指標變數設定出指向特定的地方
或
int* pi = NULL; // 空指標變數
我們來看宣告指標變數的宣告
int* 就是宣告一個整數型的指標變數
pi 就是這個整數型的指標變數名稱,我的習慣p代表指標,i代表整數,這不是硬性規定
= &a 就是取出變數a的記憶體位址設定給等號左邊的指標變數pi
千萬避免這樣宣告 int* pi;
這是避免指標指向記憶體的其他資料區、指向程式區或系統區,而我們冒然的使用這樣的指標,就有可能改變到其他區域記憶體內的資料,後果難料。
在我的測試 int* pi; 居然編譯成功,完全沒有編譯錯誤的警告,所以真的要很小心使用指標。
我們再依上面的例子來說明:
當我們這麼宣告 int a = 7; int* pi =&a; 時,程式本身做了這樣的事情
記憶體位址 變數值 變數名稱
0x70fe18 0x70fe14 pi (指標變數)
0x70fe14 7 a (數值變數)
當我們宣告 int* pi = &a; 就是把變數a的位址設定給(存入)指標變數
這時候指標變數 pi 內存的值是變數a的位址 0x70fe14
而程式也同樣會分配一個位址 0x70fe18 來儲存指標變數 pi(整數變數 a 的位址)
各種基本資料型態的變數都可以設定各自類型的指標
例:
int* pi = &a;
char* pch = &b;
double* pd = &c;
float* pf = &d;
*** 不 要 把 不 同 類 型 的 指 標 存 入 你 指 定 類 型 的 指 標 變 數 ,否 則 編 譯 時 會 產 生 無 法 轉 換 類 型 的 錯 誤 ***
指標中有了取指運算子&,還有一個叫依址取值運算子*。
這個星號 "*" 到目前為止有三種可能
1.它是乘號運算子,例: a = b * 3;
2.它是宣告指標,例: int* pi = &a;
3.就是我們現在要說的依址取值運算子
* 是依址取指運算子,宣告格式如下:
*變數名稱;
例:
int a = 5;
int* pi = &a;
*pi = 100; // 這樣就把變數 a 的值設為100
我們就來段小程式碼
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
int a = 5;
int* pi = &a;
*pi = 100; // 這樣就把變數a的值設為100
cout << "\na = " << *pi;
return 0;
}
執行結果:
a = 100
我們可以透過&a取得變數a的位址(指標),再將這位址(指標)存到指標變數pi,最後將100存入依指標變數內的位址(指標)找到的變數a,這樣我們就可以透過指標來設定或操作變數a的值了(*pi = 100;)。
在指標的設定運算與設值中,C++允許我們把多個同資料型態的指標指向單一個變數
例:
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
int a = 100, b = 200;
int* pi1 = &a;
int* pi2 = &b;
cout << "原本的設值...\n";
cout << "\na = " << a << " pi1 = " << pi1 << " *pi1 = " << *pi1;
cout << "\nb = " << b << " pi2 = " << pi2 << " *pi2 = " << *pi2;
pi2 = pi1;
cout << "\n\n重新設值後....\n";
cout << "\na = " << a << " pi1 = " << pi1 << " *pi1 = " << *pi1;
cout << "\nb = " << b << " pi2 = " << pi2 << " *pi2 = " << *pi2;
return 0;
}
執行結果:
原本的設值...
a = 100 pi1 = 0x70fe08 *pi1 = 100
b = 200 pi2 = 0x70fe0c *pi2 = 200
重新設值後....
a = 100 pi1 = 0x70fe08 *pi1 = 100
b = 200 pi2 = 0x70fe08 *pi2 = 100
pi2 = pi1;
這是把 pi1 指標變數設定給 pi2,讓 pi2 指標變數內的指標同樣指向 pi1 指標變數內的指標所指的地方,所以口語上就會省略的說把指標pi1設給指標pi2,使兩個指標都指向變數a,就是這樣省略式的口語讓指標和指標變數就說不清了。
或許就當是滑稽ㄏㄨㄚˊ ㄐㄧ 一樣吧,忘了是幾年前誰說了滑稽ㄏㄨㄚˊ ㄐㄧ 該唸成 滑稽ㄍㄨˇ ㄐㄧ,可是在他說之前,明明大家都唸成 ㄏㄨㄚˊ ㄐㄧ 好幾十年了。
以此類推,pi1、pi2 就說成指標吧(反正也說了好幾十年了,只要心裡明白這是指標變數),免得在看其它的書籍或與人討論時,又牛頭對不上馬嘴。
反正都在談指標了,就多看一眼記憶體。
例:
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
int a = 7, b = 12;
int* pia = &a;
int* pib = &b;
cout << "\npia = " << pia << " a = " << a << " sizeof(pia) = " << sizeof(pia);
cout << "\n&pia = " << &pia << " pia = " << pia;
cout << "\n";
cout << "\npib = " << pib << " b = " << b;
cout << "\n&pib = " << &pib << " pib = " << pib;
int c = 2;
int* pic =&c;
cout << "\n";
cout << "\npic = " << pic << " c = " << c;
cout << "\n&pic = " << &pic << " pic = " << pic;
cout << "\n\n";
cout << "\npic = " << pic << " *pic = " << *pic;
pic -= 1;
cout << "\npic = " << pic << " *pic = " << *pic;
return 0;
}
執行結果:
pia = 0x70fdec a = 7 sizeof(pia) = 8
&pia = 0x70fdf8 pia = 0x70fdec
pib = 0x70fdf0 b = 12
&pib = 0x70fe00 pib = 0x70fdf0
pic = 0x70fdf4 c = 2
&pic = 0x70fe08 pic = 0x70fdf4
pic = 0x70fdf4 *pic = 2
pic = 0x70fdf0 *pic = 12
把記憶體位置排序一下
&pic = 0x70fe08
&pib = 0x70fe00
&pia = 0x70fdf8
pic = 0x70fdf4
pib = 0x70fdf0
pia = 0x70fdec
在我的電腦裡,整數佔四個位元組,但整數指標佔八個位元組(好像和書說的不一樣(需要確認一下))。
我的程式碼故意先輸出變數a,b和a,b的指標,再宣告變數c和c的指標,但這並不影響編譯程式安排變數和指標的位置,依然變數放在一起,接著才一起放指標。
指標除了前面所談的,可以設定運算 pi2 = pi1; 外,指標也可以作加法與減法運算,當然啦!這是指對於指標內的位址所作的運算。
當我們把指標減一的時候 pic -= 1;,pic指標從0x70fdf4變成0x70fdf0,相差四個位元組,正好就是整數變數所佔記憶體的空間大小,這也恰巧地指向變數b的位置,變成重疊變數b的指標了,也就取到變數b的值了。這個例子並不是要表達指標可以用加減法運算來取得另一個變數值,而是要表達在指標的使用上必須小心謹慎,避免誤取他值而不自知。
回頭來看,如果我們真的想用指標pic來取變數b的值,我們可以
1. pic = &b; // 直接把變數b的位址設定給指標pic
或
2. pic = pib; // 把指標pib 設定給pic;
然後依址取值...*pic;
這樣才是正確的指標操作....
.