〈 C++學習日記 #1〉指標 Pointer (Part.1)


1. 什麼是指標

指標是一種特別的變數,用來存放其他變數在記憶體中的位址(adress)。當我們宣告一個變數時,編譯器會配置一塊記憶體給他。每個記憶體空間均有它獨一無二的編號,即為位址。

當指標ptr指向變數a,代表ptr存放變數a的位址,要存取變數a時,可以利用ptr先找到a的位址,再取出所存取的變數值。

2. 為什麼要用指標

  • 提高效率
  • 處理不同資料結構,如linked list或binary tree
  • 傳達記憶體訊息,如記憶體配置函數malloc()

3. 指標變數

先來看看基本指標變數的宣告

int a = 10;
int *ptr = &a; //將ptr指向變數a

指標符號「*」可將變數宣告為指標變數,而前面則是代表所指向的資料型態。另外,位址運算子「&」可用來存取變數的記憶體位址,因此:

cout << 'ptr = ' << ptr << endl;
cout << '*ptr =' << *ptr << endl;
cout << '&ptr =' << &ptr << endl;

輸出結果為

ptr = 0x6ffe14 //變數a的位址
*ptr = 10 //用a的位址存取值
&ptr = 0x6ffe10 //指標變數ptr的位址

另外,編譯器配置給整數型態的空間是4個位元組,而給字元的空間則是1個位元組,但是指向整數與字元的指標變數所佔的位元組都是4個位元組。

4. 指標與函數

介紹完指標,接下來開始涉及指標的實際應用。這個段落主要介紹指標在函數方面的四種用途:

  • 傳遞指標到函數中
  • 傳回值為指標的函數
  • 指標函數
  • 函數之間的傳遞

傳遞指標到函數中

首先是如何將指標傳遞到函數當中,可以用以下語法:

傳回值型態 函數名稱(資料型態 *指標變數(宣告時可省略)){
    函數本體
}

例如:

void getAddress(int *);

int main(void){

    int a = 10;
    int *ptr = &a;
    getAddress(ptr); //也可以寫成getAddress(&a);

    system("pause");
    return 0;
}

void getAddress(int *pl){
    cout << "address is " << pl << endl;
    return;
}

會得到以下輸出結果:

address is 0x6ffe14

使用指標傳遞有個很大的好處,就是即便沒有回傳值,依舊可以更改傳入的引述值,是個十分實用的特性。例如想將上方程式碼中的變數a做運算,就可以這麼寫:

void addNum(int *, int);

int main(void){

    int a = 10;
    int *ptr = &a;
    addNum(ptr, 5); //把ptr指向的整數變數加上5

    cout << "main函數內的a值: " << a << endl;

    system("pause");
    return 0;
}

void addNum(int *pl, int m){
    cout << "original: " << *pl << endl;
    *pl += m;
    cout << "after addition: " << *pl << endl;
    cout << endl;
    return;
}

會得到以下輸出結果:

original: 10
after addition: 15

main函數內的a值: 15

可以看到main中變數a的值也被修改了。

傳回值為指標的函數

語法為

傳回值型態 *函數名稱(資料型態 引數){
    函數本體
}

這邊給使用個範例就好

int *addNum(int *, int);

int main(void){

    int a = 10;
    int *ptr;
    ptr = addNum(&a, 5);

    cout << "*ptr = " << *ptr << endl;

    system("pause");
    return 0;
}

int *addNum(int *pl, int m){
    cout << "original: " << *pl << endl;
    *pl += m;
    cout << "after addition: " << *pl << endl;
    cout << endl;
    return pl;
}

輸出結果:

original: 10
after addition: 15

*ptr = 15

指標函數

指標也可以指向函數,函數的位址和陣列類似,函數名稱本身記錄著函數的經過編譯後機器碼的起始位址。

指向函數的指標被稱為函數指標(function pointer),定義格式如下:

欲指向函數的傳回值型態 (*指標變數名稱)(引數型態)

用將兩個整數相加的函數作為範例:

int addNum(int,int);


int main(void){

    int (*pF)(int,int);
    pF = addNum;

    cout << "pF(10,5) = " << pF(10,5) << endl;

    system("pause");
    return 0;
}

int addNum(int m, int n){
    return m+n;
}

輸出結果:
pF(10,5) = 15

指標函數在函數之間的傳遞扮演著重要的角色,在下個段落會解釋如何使用。

傳遞函數到其他函數中

上一段提到,函數名稱本身記錄著函數的起始位址,因此可以看到在下方的範例的第7、8行,函數名稱可以當作位址引數。

範例:

int addNum(int,int);
int mulNum(int,int);
int pF(int,int,int (*pf)(int,int));

int main(void){

    cout << "pF(10,5,addNum) = " << pF(10,5,addNum)<< endl;
    cout << "pF(10,5,mulNum) = " << pF(10,5,mulNum)<< endl;
    system("pause");
    return 0;
}

int addNum(int m, int n){
    return m+n;
}
int mulNum(int m, int n){
    return m*n;
}
int pF(int m, int n, int (*pf)(int,int)){
    return (*pf)(m,n);
}

輸出結果:

pF(10,5,addNum) = 15
pF(10,5,mulNum) = 50

5. 指標與陣列、字串

指標的算術運算是根據所指向的資料型態對於位址的調整。比如對於整數指標iptr,iptr+1代表將原位址加上四個位元組,而對於字元指標cptr,cptr+1則是加上一個位元組。

這個特性可以用在存取陣列元素上,前面提過函數的名稱即包含了本身的起始位址,而陣列也是一樣的道理。更精準地來說,陣列名稱本身是一個存放位址的「指標常數(pointer constant)」,它指向陣列的位址,而常數代表這種指標不能夠更改所指的對象。

存取一維陣列元素:

int a[3] = {10,56,21};
int sum=0;
for(int i = 0;i<3;i++){
    sum+=*(a+i);
}
cout << "sum=" << sum << endl;

輸出結果:

sum=87

傳遞一維陣列到函數:

void replace(int *,int,int);

int main(void){
    int arr[5]={1,2,3,4,5};
    replace(arr,2,6);
    cout << "arr after replace(arr,2,6) = ";
    for(int i=0;i<5;i++)cout<<arr[i]<<',';
    system("pause");
    return 0;
}
//將索引值為m的元素值換成n 
void replace(int *ptr, int m, int n){
    *(ptr+m)=n;
    return;
}

輸出結果:

arr after replace(arr,2,6) = 1,2,6,4,5,

也可以將指標指向字串,存取的方法有以下兩種:

  1. 利用字元陣列
    char str[] = "How are you?"; //str是一個指標常數
    
  2. 利用字元指標
    char *ptr = "How are you?";
    
    第一種方法str會成為指標常數,無法修改,但第二種方法的ptr是指標變數,其值可以進行修改。因此可以透過ptr = ptr+4;來存取字串的值。
    char *ptr="How are you?";
    cout<<"*ptr = "<<*ptr<<endl;
    cout<<"ptr = "<<ptr<<endl;
    cout<<"*(ptr+4) = " <<*(ptr+4)<<endl;
    cout<<"ptr+4 = " << ptr+4 << endl;
    
    輸出結果:
    *ptr = H
    ptr = How are you?
    *(ptr+4) = a
    ptr+4 = are you?
    

6. 指標陣列

就像其他資料型態一樣,指標也可以宣告為陣列。比如宣告一維的整數指標陣列:

int *ptrArr[3];

就有三個整數指標ptrArr[0],ptrArr[1],ptrArr[2]可以使用

還有將二維的字元陣列轉為指標的寫法:

char chrArr[3][10] = {"Tom","Lily","James"};
char *ptrArr[3] = {"Tom","Lily","James"};

注意用指標的寫法在記憶體配置的效率上會高於用字元陣列的寫法,因為用指標陣列存取字串時,編譯器會自動配置可容納該字串的記憶體空間,避免字串的長短不一導致的空間浪費。

7. 結語

關於指標還可以講很多,有夠難的操

#algorithm #data structure #C++







你可能感興趣的文章

【單元測試的藝術】Chap 7: 測試階層和組織

【單元測試的藝術】Chap 7: 測試階層和組織

Web開發學習筆記12 — 如何用JavaScript設置CSS樣式?、document.style與window.getComputedStyle()的差異

Web開發學習筆記12 — 如何用JavaScript設置CSS樣式?、document.style與window.getComputedStyle()的差異

day_00 = '__init__'

day_00 = '__init__'






留言討論