close


【不是惡夢的開始,而是再度的精進。掀開指標的神秘面紗】


指標之所以不好學,就是總讓初學者感覺上是隔著一層面紗在探索指標,那種似東非東,似西非西捉摸不定的飄渺感覺。


我們先從名詞上來說,指標和指標變數到底是什麼東西?


就我的解讀是:

變數是依變數型態在記憶體中所需要適當大小的記憶體空間,所以當我們宣告變數時,編譯程式依變數型態在記憶體裡配置一塊足以容納此一變數大小的記憶體空間給它。

指標是指向某個變數內容的記憶體位址,就稱為該變數的指標,所以指標是一個記憶體位址。

指標變數是編譯程式再配置一塊記憶體來存放某個變數的記憶體位址(指標)的一個變數,所以存放的是記憶體位址(指標)。


只是在口語上常常把指標變數就說成了指標,因此在使用指標時就東指西指,也不知道到底在指哪裡。

 

我們就用身分證例子來類比指標好了,當父母生下孩子到戶政事務所登記,這時候需要幫新生兒取的名字,如果第一胎生個女兒取名叫王美女,這時候戶政事務所就會給王美女一個身分證號碼A200000001,以後這個身分證號碼就指向王美女了,所以在程式碼中我們會這樣做


我們會宣告:

string name1 = "王美女";


如果又生個兒子,這時候戶政事務所就會給王帥哥一個身分證號碼A100000001

string name2 = "王帥哥";


我們就以字串變數來看美女帥哥好了,這時候name1就是字串變數名稱,"王美女"就是字串變數name1的值,name2也是字串變數名稱,"王帥哥"就是字串變數name2的值


就電腦內記憶體位址來看

字串變數在記憶體位址     字串變數值      字串變數名稱


     0x71fdf0              王美女             name1

     0x71fe00              王帥哥             name2


在將變數資料寫入的起始位址時,會根據變數型別所佔的byte數不同而位址不同


這時候身分證號碼A100000001就類比到字串變數在記憶體位址0x71fdf0,所以身分證號碼A100000001指向王美女,電腦可以根據變數在記憶體位址0x71fdf0查到變數值王美女,因此我們稱字串變數在記憶體位址為字串變數值的指標,因為0x71fdf0指向王美女。


在這個時候,如果我們又宣告一個變數來存放0x71fdf0(指標),這個變數就是指標變數了。意思就是說編譯程式又會依變數型態在記憶體裡配置一塊足以容納此一變數大小的記憶體空間給它。

 

我們從指標變數的宣告上來看,指標變數的宣告格是如下:
型態 *指標變數;

例:

int ai;

char ch;

int *ptr_i = &ai;          // 宣告一個名為ptr_i的整數型態的指標變數
char *ptr_ch = &ch;   // 宣告一個名為ptr_ch的字元型態的指標變數


但是,我們也可以這樣的宣告

型態* 指標變數;

例:

int ai;

char ac;

int* ptr_i = &ai;         // 宣告一個名為ptr_i的整數型態的指標變數
char* ptr_ch = &ch;   // 宣告一個名為ptr_ch的字元型態的指標變數

這兩種宣告是完全相同的宣告。


早期的書和早年我學習c++時,老師的教法也都使用第一種的方式來宣告,包括現在上網查到很多的程式也都這樣的宣告,但....我個人比較喜歡第二種的宣告方式。


在指標運算子裡,有一個叫做取址運算子 &,宣告格式如下:

&變數名稱;

例:(以第二種指標變數宣告方式為例)

int* ptr_i = NULL;    //宣告整數型態的指標變數ptr_i
int a = 10;               //宣告整數變數a並設值為10
ptr_i = &a;              //將a的記憶體位址的值設給指標變數ptr_i存放


另一個叫做依址取值運算子 *,宣告格式如下:

*變數名稱;

例:

int* ptr_i = NULL;    //宣告整數型態的指標變數ptr_i
int a;                        //宣告整數變數a並設值為10
ptr_i = &a;               //將a的記憶體位址的值設給指標變數ptr_i存放
*ptr_i = 100;            //將ptr_i所指向的記憶體內容設為100

這就是為什麼我喜歡第二種宣告的方式....原因如下


同樣是取址運算和依址取值運算,如果我們把它換成第一種的宣告方式,就變成:

例:

int *ptr_i = NULL;    //宣告整數型態的指標變數*ptr_i
int a = 10;               //宣告整數變數a並設值為10

ptr_i = &a;              //將a的記憶體位址的值設給指標變數ptr_i存放


這裡的問題點是:
既然int
*ptr_i;    //宣告整數型態的的指標變數ptr_i
那....ptr_i 不等於 *ptr_i呀!怎麼可以隨便把 * 拿掉

明明我們宣告*ptr_i才是整數型態的指標變數,為什麼整數型態的指標變數會變成ptr_i?


又例:

int *ptr_i = NULL;   //宣告整數型態的指標變數ptr_i
int a;                       //宣告整數變數a並設值為10
ptr_i = &a;              //將a的記憶體位址的值設給指標變數ptr_i存放
*ptr_i = 100;           //將ptr_i所指向的記憶體內容設為100

這裡的問題點是:

除了有取址運算子的問題外,又加了一條

明明..int *ptr_i;      //宣告整數型態的指標變數*ptr_i
                             // *ptr_i是存放記憶體位址的
卻..*ptr_i = 100;    //
怎麼能將*ptr_i所指向的記憶體位址設為100


所以使用第一種的宣告法,必須要在腦中自動置換ptr_i就是int *ptr_i,*ptr_i並不是int *ptr_i,而是依ptr_i位址來*取ptr_i位址的值。
這是早年我在學的時候的感覺。瘋了。繞口令了。鬼打牆了。所以不用別人教就自然會放手了,指標--掰掰。


綜合名稱上在口語的模糊和宣告上的模糊,很容易讓初學者有總是蒙上一層面紗的感覺,但或許說..第一種的宣告上有某種方便性,只是我還沒學到那麼深層吧...

在紛亂的口語中的指標,我們把目前姑且把指標先定義在指標變數吧!至於以後學到更深層的東西時,再依當時的情況來解釋指標。


指標變數所存放的內容,並不是一般的資料,而是存放記指定類型的憶體位址,也就是說指標變數所存放的是某個資料在記憶體中的位址,而不是數值或文字等資料內容,根據指標變數所指向的位址,就可以找到該記憶體位址所存放的內容。指標變數的宣告格式如下:

型態* 指標變數;            // 和前面所提到的形式有點不一樣,我喜歡這樣的款式

例:

int* ptr_i = NULL;        // 宣告一個名為ptr_i的整數型態的指標變數
char* ptr_ch = NULL;  // 宣告一個名為ptr_ch的字元型態的指標變數


只要在C++中的基本資料型態,如整數、浮點數、字元、字串等,都可以宣告成指標。


我們再回頭看看王帥哥王美女的例子


#include <iostream>
#include <string>

using namespace std;

int main(int argc, char** argv)
{
    
    string name1 = "王美女";
    string name2 = "王帥哥";
    string* p_str = &name1;
     
    cout << "\n長女 : " << name1 << "  name1 位址(從變數取址)           : " << &name1; 
    cout << "\n長女 : " << name1 << "  name1 位址(指標變數內的值)     : " << p_str;
    cout << "\n長女 : " << name1 << "  指標變數p_str所指向位址內的值  : " << *p_str;
    cout << "\n\n長男 : " << name2 << "  name2 位址 : " << &name2; 
 
    return 0;
 } 

 

執行結果:

長女 : 王美女  name1 位址(從變數取址)           : 0x71fdf0
長女 : 王美女  name1 位址(指標變數內的值)    : 0x71fdf0
長女 : 王美女  指標變數p_str所指向位址內的值 : 王美女

 

長男 : 王帥哥  name2 位址 : 0x71fe00


在程式碼中我們先宣告兩個字串變數,同時將字串變數賦值

string name1 = "王美女";
string name2 = "王帥哥";

再來宣告一個指標變數並賦值

string* p_str = &name1;   // 記得不要宣告未指定的指標  string* p_str; 
                                         // 沒初值化指標變數可能會指向程式所在的記憶體位址


在這裡我是這樣解讀的


string* 宣告一個字串的指標變數

p_str 指標變數的名稱是 p_str

= &name1 取變數name1的記憶體位址,設定給 p_str

*p_str  從指標變數p_str內的記憶體位址(指標)所指向該位址內的值


繞了一大圈,還這麼的拗口,與其要指標變數p_str內的指標,何不直接變數取址&name1?
與其要指標變數內的指標所指向的位址取值*p_str,何不直接丟出變數name1?
傻了嗎?C++!


你說這麼迷惑混亂的指標,為什麼我們還要入坑?
正因為指標可以進到記憶體位址(硬體)去存取資料,而且還有很多地方是非指標不可,
你說:可以不挑戰一下嗎?


 

 

 

 

 

.

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 伊蒙‧普羅客 的頭像
    伊蒙‧普羅客

    面向陽光,陰影就會在身後

    伊蒙‧普羅客 發表在 痞客邦 留言(2) 人氣()