C/C++指針的理解和解析

C/C++中的關鍵知識點就是指針的用法,在代碼開發過程中涉及到各種各樣的指針。我結合自己的應用經驗和查詢相關說得比較清楚的資料信息,總結了一些基本的指針用法和理解。希望看完之後對指針的用法和理解會有更深入的理解。

1、指針變量

int *p1;

p1 是一個指向 int 類型數據的指針變量,至於 p1 究竟指向哪一份數據,應該由賦予它的值決定。再如:

int a = 10;

int *p_a = &a;

在定義指針變量 p_a 的同時對它進行初始化,並將變量 a 的地址賦予它,此時 p_a 就指向了 a。值得注意的是,p_a 需要的一個地址,a 前面必須要加取地址符&,否則是不對的。

//定義普通變量
float a = 100.5, b = 100.6;
char c = 'a', d = 'as';
//定義指針變量
float *p1 = &a;
char *p2 = &c;
//修改指針變量的值
p1 = &b;
p2 = &d;

*是一個特殊符號,表明一個變量是指針變量,定義 p1、p2 時必須帶*。而給 p1、p2 賦值時,因爲已經知道了它是一個指針變量,就沒必要多此一舉再帶上*,後邊可以像使用普通變量一樣來使用指針變量。也就是說,定義指針變量時必須帶*,給指針變量賦值時不能帶*

通過指針變量獲取數據實例:

#include <stdio.h>
int main(){
    int a = 15;
    int *p = &a;
    printf("%d, %d\n", a, *p);  //兩種方式都可以輸出a的值
    return 0;
}

打印輸出的都是15,如果直接打印p,則輸出的是15這個數據存儲的地址

2、關於 * 和 & 的理解

假如int a =10;int *pa = &a。

假設有一個 int 類型的變量 a,pa 是指向它的指針,那麼*&a&*pa分別是什麼意思呢?

*&a可以理解爲*(&a)&a表示取變量 a 的地址(等價於 pa),*(&a)表示取這個地址上的數據(等價於 *pa),繞來繞去,又回到了原點,*&a仍然等價於 a。

&*pa可以理解爲&(*pa)*pa表示取得 pa 指向的數據(等價於 a),&(*pa)表示數據的地址(等價於 &a),所以&*pa等價於 pa

3、星號*主要有三種用途

  • 表示乘法,例如int a = 3, b = 5, c;  c = a * b;,這是最容易理解的。
  • 表示定義一個指針變量,以和普通變量區分開,例如int a = 100;  int *p = &a;
  • 表示獲取指針指向的數據,是一種間接操作,例如int a, b, *p = &a;  *p = 100;  b = *p;

4、數組指針

假設 p 是指向數組 arr 中第 n 個元素的指針,

即是:int arr[3] = {99,12,34}; int *p = arr表示p指向arr數組的第一個元素的地址

那麼 *p++、*++p、(*p)++ 分別是什麼意思呢?

*p++ 等價於 *(p++),表示先取得第 n 個元素的值,再將 p 指向下一個元素,上面已經進行了詳細講解。

*++p 等價於 *(++p),會先進行 ++p 運算,使得 p 的值增加,指向下一個元素,整體上相當於 *(p+1),所以會獲得第 n+1 個數組元素的值。

(*p)++ 就非常簡單了,會先取得第 n 個元素的值,再對該元素的值加 1。假設 p 指向第 0  個元素,並且第 0 個元素的值爲 99,執行完該語句後,第 0  個元素的值就會變爲 100。

 

5、字符串數組和字符串常量

定義一個字符串有兩種方法,見下面兩種方法

char str[] = "this is a test str"
//char *pstr = "this is a test str"

//print mes
char *pstr = str;
int len = strlen(str), i;
//使用*(pstr+i)
for(i=0; i<len; i++){
    printf("%c", *(pstr+i));
}
printf("\n");

//使用pstr[i]
for(i=0; i<len; i++){
    printf("%c", pstr[i]);
}

//接輸出字符串
printf("%s\n", pstr);

前面那種表示字符串的方法就是字符數據的表示方法

後面那隻表示字符串的方法就是字符串常量表示方法

C語言有兩種表示字符串的方法,一種是字符數組,另一種是字符串常量,它們在內存中的存儲位置不同,使得字符數組可以讀取和修改,而字符串常量只能讀取不能修改。

6、指針作爲函數參數使用

函數的參數不僅可以是整數、小數、字符等具體的數據,還可以是指向它們的指針。用指針變量作函數參數可以將函數外部的地址傳遞到函數內部,使得在函數內部可以操作函數外部的數據,並且這些數據不會隨着函數的結束而被銷燬。就像數組、字符串、動態分配的內存等都是一系列數據的集合,沒有辦法通過一個參數全部傳入函數內部,只能傳遞它們的指針,在函數內部通過指針來影響這些數據集合。

定義一個含有數組參數的函數

int max(int intArr[], int len){}

或者這樣定義

int max(int intArr[6], int len){}

int intArr[]雖然定義了一個數組,但沒有指定數組長度,好像可以接受任意長度的數組。實際上這兩種形式的數組定義都是假象,不管是int intArr[6]還是int intArr[]都不會創建一個數組出來,編譯器也不會爲它們分配內存,實際的數組是不存在的,它們最終還是會轉換爲int *intArr這樣的指針。這就意味着,兩種形式都不能將數組的所有元素“一股腦”傳遞進來,大家還得規規矩矩使用數組指針。int intArr[6]這種形式只能說明函數期望用戶傳遞的數組有 6 個元素,並不意味着數組只能有 6 個元素,真正傳遞的數組可以有少於或多於 6 個的元素。需要強調的是,不管使用哪種方式傳遞數組,都不能在函數內部求得數組長度,因爲 intArr 僅僅是一個指針,而不是真正的數組,所以必須要額外增加一個參數來傳遞數組長度。

C語言爲什麼不允許直接傳遞數組的所有元素,而必須傳遞數組指針呢?參數的傳遞本質上是一次賦值的過程,賦值就是對內存進行拷貝。所謂內存拷貝,是指將一塊內存上的數據複製到另一塊內存上。對於像 int、float、char 等基本類型的數據,它們佔用的內存往往只有幾個字節,對它們進行內存拷貝非常快速。而數組是一系列數據的集合,數據的數量沒有限制,可能很少,也可能成千上萬,對它們進行內存拷貝有可能是一個漫長的過程,會嚴重拖慢程序的效率,爲了防止技藝不佳的程序員寫出低效的代碼,C語言沒有從語法上支持數據集合的直接賦值。

 

7、指針函數

C語言允許函數的返回值是一個指針地址),我們將這樣的函數稱爲指針函數。下面的例子定義了一個函數 strlong(),用來返回兩個字符串中較長的一個

#include <stdio.h>
#include <string.h>

char *strlong(char *str1, char *str2){
    if(strlen(str1) >= strlen(str2)){ //不是說不能在函數裏面求傳進來的數組長度麼?怎麼理解?
        return str1;
    }else{
        return str2;
    }
}

int main(){
    char str1[30] = "qqqqwwww", str2[30] = "qqqqwwwweeee", *str;
    gets(str1);
    gets(str2);
    str = strlong(str1, str2);
    printf("Longer string: %s\n", str);

    return 0;
}

當然打印的值肯定就是qqqqwwwweeee

用指針作爲函數返回值時需要注意的一點是,函數運行結束後會銷燬在它內部定義的所有局部數據,包括局部變量、局部數組和形式參數,函數返回的指針請儘量不要指向這些數據,C語言沒有任何機制來保證這些數據會一直有效,它們在後續使用過程中可能會引發運行時錯誤。

爲什麼說可能會出錯呢?原因:前面我們說函數運行結束後會銷燬所有的局部數據,這個觀點並沒錯,大部分C語言教材也都強調了這一點。但是,這裏所謂的銷燬並不是將局部數據所佔用的內存全部抹掉,而是程序放棄對它的使用權限,棄之不理,後面的代碼可以隨意使用這塊內存。對於局部變量的指針地址和值如果在調用函數出來馬上使用,可能還會存在而且數組正確,如果有其它函數被調用佔用了該部分內存就會覆蓋這塊內存,得到的數據就失去了意義,會得到錯誤的數據。

8、函數指針(指向函數的指針)

一個函數總是佔用一段連續的內存區域,函數名在表達式中有時也會被轉換爲該函數所在內存區域的首地址,這和數組名非常類似。我們可以把函數的這個首地址(或稱入口地址)賦予一個指針變量,使指針變量指向函數所在的內存區域,然後通過指針變量就可以找到並調用該函數。這種指針就是函數指針。

實例

#include <stdio.h>

//返回兩個數中較大的一個
int max(int a, int b){
    return a>b ? a : b;
}

int main(){
    int x, y, maxval;
    //定義函數指針
    int (*pmax)(int, int) = max;  //也可以寫作int (*pmax)(int a, int b)
    printf("Input two numbers:");
    scanf("%d %d", &x, &y);
    maxval = (*pmax)(x, y);
    printf("Max value: %d\n", maxval);

    return 0;
}

運行結果:
Input two numbers:10 50↙
Max value: 50

第 14 行代碼對函數進行了調用。pmax 是一個函數指針,在前面加 * 就表示對它指向的函數進行調用。注意( )的優先級高於*,第一個括號不能省略。

 

9、總結

指針(Pointer)就是內存的地址,C語言允許用一個變量來存放指針,這種變量稱爲指針變量。指針變量可以存放基本類型數據的地址,也可以存放數組、函數以及其他指針變量的地址。

程序在運行過程中需要的是數據和指令的地址,變量名、函數名、字符串名和數組名在本質上是一樣的,它們都是地址的助記符:在編寫代碼的過程中,我們認爲變量名錶示的是數據本身,而函數名、字符串名和數組名錶示的是代碼塊或數據塊的首地址;程序被編譯和鏈接後,這些名字都會消失,取而代之的是它們對應的地址。
 

常見指針變量的定義
定  義 含  義
int *p; p 可以指向 int 類型的數據,也可以指向類似 int arr[n] 的數組。
int *p[n]; p 爲指針數組。[ ] 的優先級高於 *,所以應該理解爲 int *(p[n]);,數組裏面存的值都是地址
int *p(void); p 是一個函數,它的返回值類型爲 int *,這個函數叫指針函數
int (*p)(); p 是一個函數指針,指向原型爲 int func() 的函數。級int (*p) ()= func() 


1) 指針變量可以進行加減運算,例如p++p+ip-=i。指針變量的加減運算並不是簡單的加上或減去一個整數,而是跟指針指向的數據類型有關。例如類型爲int,那麼++則增加4個字節,如果是char的話++就增加1個字節

2) 給指針變量賦值時,要將一份數據的地址賦給它,不能直接賦給一個整數,例如int *p = 1000;是沒有意義的,使用過程中一般會導致程序崩潰,不能是1000,必須是指向一個值的地址

3) 使用指針變量之前一定要初始化,否則就不能確定指針指向哪裏,如果它指向的內存沒有使用權限,程序就崩潰了。對於暫時沒有指向的指針,建議賦值NULL

4) 兩個指針變量可以相減。如果兩個指針變量指向同一個數組中的某個元素,那麼相減的結果就是兩個指針之間相差的元素個數,更確切的說是相差的字節數,一個字節8位。

5) 數組也是有類型的,數組名的本意是表示一組類型相同的數據。在定義數組時,或者和 sizeof、& 運算符一起使用時數組名才表示整個數組,表達式中的數組名會被轉換爲一個指向數組的指針。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章