指針表達式

一些聲明:

 char ch = 'a';
 char *cp = &ch;

現在有了兩個變量,一個ch(字符類型變量),一個cp(指向字符類型的指針變量)。

它們的初始化如下:

圖中還顯示出了ch後面的那個內存位置,由於並不知道它的初始值,所以用一個問號來代替。


下面來討論一些表達式的意義。

ch
&ch
cp
&cp
*cp
*cp+1
*(cp+1)
++cp
cp++
*++cp
*cp++
++*cp
(*cp)++
++*++cp
++*cp++

左值(L-value) 和 右值(R-value) 通俗的講,左值就是能夠出現在賦值符號左邊的東西,而右值就是那些可以出現在賦值符號右邊的東西了。較爲準確的定義:左值指的是如果一個表達式可以引用到某一個對象,並且這個對象是一塊內存空間且可以被檢查和存儲,那麼這個表達式就可以做爲一個左值。右值指的是引用了一個存儲在某個內存地址裏的數據。從上面的兩個定義可以看出,左值其實要引用一個對象,而一個對象在我們的程序中又肯定有一個名字或者可以通過一個名字訪問到,所以左值又可以歸納爲:左值表示程序中必須有一個特定的名字引用到這個值。而右值引用的是地址裏的內容,所以相反右值又可以歸納爲:右值表示程序中沒有一個特定的名字引用到這個值除了用地址。

ch

當它作爲右值使用時,表達式的值爲'a',如下圖所示:

那個粗橢圓提示變量ch的值就是表達式的值,即字符a。

但是,當這個表達式作爲左值使用時,它是指這個內存的地址,而不是這個該地址所包含的值。

所以它的圖示方式有所不同:

此時該位置用粗方框標記,提示這個位置就是表達式的結果。另外,它的值並未顯示,因爲它並不重要。事實上,這個值將被某個新值取代。

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch;
    printf("%c\n",ch);
    //作爲右值
    char name = ch;  //右值引用的是地址裏的內容,即字符a。把name變量初始化爲字符a。
    //作爲左值
    ch = 'b';     //左值其實要引用一個對象,指ch這個變量在內存中的地址。把這個地址裏面的內容修改爲'b'。
    printf("%c\n",name);
    printf("%c\n",ch);
    return 0;
}

&ch

作爲右值,這個表達式的值就是變量ch的地址。注意這個值同變量cp中所存儲的值一樣,但這個表達式並未提及cp,所以這個結果值並不是因爲它產生的。這樣,途中橢圓並不畫於cp的箭頭周圍。

爲什麼這個表達式不是一個合法的左值?當表達式&ch進行求值時,它的結果應該存儲於計算機的什麼地方呢?它肯定會位於某個地方,但你無法知道它位於何處。所以說它不是一個合法的左值。

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch;

    //ch的地址和變量cp中所存儲的值是一樣的。
    printf("%p\n",cp);
    printf("%p\n",&ch);

    //作爲右值
    char *name  = &ch;  
    printf("%p\n",name);

    return 0;
}


cp

它的右值就是cp的值,也就是ch的地址。它的左值就是cp所處的內存位置。

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch;

    //作爲右值
    char *cp1  = cp;  
    printf("%p\n",cp);
    printf("%p\n",cp1);

    //作爲左值
    cp = NULL;    //把指針變量cp設爲NULL指針,現在cp就不再指向ch了。
    printf("%p\n",cp);

    return 0;
}

&cp

這個與&ch類似。不過這次所取的是指針變量的地址。作爲右值時,這個結果的類型是指向字符的指針的指針。同樣,這個值的存儲位置並未清晰定義,所以這個表達式不是一個合法的左值

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch;

    //打印指針變量cp在內存中的地址。
    printf("%p\n",&cp);

    //作爲右值
    char **cp1 = &cp;   //cp1是一個指向“指向字符類型的指針”的指針。
    printf("%p\n",cp1); //打印出cp1所存儲的值,也就是cp的地址。

    return 0;

}


*cp

這個是間接訪問操作,也叫解引用指針。注意:*cp的類型是char

作爲右值和左值時表達式的結果和ch的一樣。

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch;

    printf("%c\n",*cp);
    printf("%c\n",ch);
    //作爲右值
    char name = *cp;  
    printf("%c\n",name);

    //作爲左值
    *cp = 'b';     
    printf("%c\n",*cp);
    printf("%c\n",ch);
    return 0;
}

接下來的幾個表達式會比較有趣。

*cp+1

這裏有兩個操作符。作爲右值時,*的優先級高於+,所以首先執行間接訪問操作(如圖中cp到ch的實線箭頭所示),可以得到它的值(如虛線橢圓所示)。取得這個值的一份拷貝並把它與1相加,表達式的最終結果爲字符'b'。圖中虛線表示表達式求值時數據的移動過程。這個表達式的最終結果的存儲位置並未清晰定義,所以它不是一個合法的左值。

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch;

    //作爲右值
    char name = *cp+1;  
    printf("%c\n",name);   // 將會輸出字符'b'
    printf("%c\n",*cp);   //還是字符'a'
  
    return 0;
}

*(cp+1)

在前面的表達式中增加了一個括號。這個括號使表達式先執行加法運算,就是把1和cp中所存儲的地址相加。此時的結果值是圖中虛線橢圓所示的指針。接下來的間接訪問操作隨着箭頭訪問緊隨ch之後的內存位置。這樣,這個表達式的右值就是這個位置的值,而它的左值就是這個位置本身。

注意指針加法運算的結果是個右值,因爲它的存儲位置並未清晰定義。如果沒有間接訪問操作,這個表達式將不是一個合法的左值。然而,間接訪問緊隨指針訪問一個特定的位置。這樣,*(cp+1)就可以作爲左值使用,儘管cp+1本身並不是左值。間接訪問操作符是少數幾個其結果爲左值的操作符之一。

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch;

    //作爲右值
    char name = *(cp+1);  
    printf("%c\n",name);   // 以字符的形式輸出,不知道會輸出什麼。
    printf("%p\n",name);   // 以十六進制的形式輸出

    //作爲左值
    *(cp+1) = 'b';
    name = *(cp+1); 
    printf("%c\n",name);   // 以字符的形式輸出,輸出‘b’。
    printf("%p\n",name);   // 以十六進制的形式輸出
  
    return 0;
}

注意:這個表達式所訪問的是ch後面的那個內存位置,我們如何知道原先存儲於那個地方的是什麼東西?一般而言,我們無法得知,所以像這樣的表達式是非法的。

一般來說,經常是在數組使用*(cp+1)。數組的儲存是一片連續的內存區域。


++cp

在這個表達式中,增加了指針變量cp的值。(爲了讓圖更清楚,我們省略了加法)。表達式的結果是增值後的指針的一份拷貝,因爲前綴++先增加它的操作數的值再返回這個結果。這份拷貝的存儲位置並沒有清晰定義,所以它不是一個合法的左值。(前一個例子也說到指針的加法不是一個左值)。

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch;

    //作爲右值
    char *cp1 = ++cp;
    printf("%c\n",*cp1);  //不知道會輸出什麼。
  
    return 0;
}

cp++

後綴++操作符同樣增加cp的值,但它先返回cp值的一份拷貝然後再增加cp的值。這樣,這個表達式的值就是cp原來的值的一份拷貝

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch; 

    //作爲右值
    char *cp1 = cp++;
    printf("%c\n",*cp1);  //輸出字符'a'。
  
    return 0;
}

前面兩個表達式的值都不是合法的左值。但如果我們在表達式中增加了間接訪問操作符,它們就可以成爲合法的左值,如下面的兩個表達式所示。

*++cp

間接操作符作用於增值後的指針的拷貝上,所以它的右值時ch後面那個內存地址的值,而它的左值就是那個位置本身。

驗證:(左值和右值的用法和*cp類似,由於這是非法訪問,就不舉例了。)


*cp++

使用後綴++操作符所產生的結果不同:它的右值和左值分別是變量ch的值和ch的內存位置,也就是cp原先所指。同樣,後綴++操作符在周圍的表達式中使用其原先操作數的值。後綴++操作符的優先級高於*操作符。事實上,這裏涉及3個步驟:(1)++操作符產生cp的一份拷貝,(2)然後++操作符增加cp的值,(3)最後,在cp的靠拷貝上執行間接訪問操作。

驗證:(右值和左值的用法也和*cp類似,只是這個表達式還有一個效果就是增加cp的值,使得cp不知指向ch,而是指向ch的下一個內存地址。)

這個表達式常常在循環中出現,首先用一個數組的地址初始化指針,然後使用這種表達式就可以依次訪問該數組的內容了。


++*cp

在這個表達式中,由於這兩個操作符的結合性都是從右向左,所以首先執行的是間接訪問操作。然後,cp所指向的位置的值增加1,表達式的結果就是這個增值後的值的一份拷貝。

驗證:

#include<stdio.h>
int main()
{
    char ch = 'a';
    char *cp = &ch; 

    //作爲右值
    char ch1 = ++*cp;
    printf("%c\n",ch1);  //打印出字符'b'
    printf("%c\n",*cp); //同時,ch存儲的內容也修改爲了字符'b'
  
    return 0;
}

最後3個表達式在實際中使用得很少。

(*cp)++

加上括號,使它首先執行間接訪問操作。這個表達式的計算過程與前一個表達式相似,但它的結果值是ch增值前的原先值,即'a'


++*++cp

這個表達式共有3個操作符。我們先前已經計算過*++cp了,所以現在我們需要做的只是增加它的結果值。但還是從頭開始分析吧,記住這些操作符的結合性都是從右向左的,所以首先執行的是++cp。cp下面的虛橢圓表示第1箇中間結果。接着,我們對這個拷貝值進行間接訪問,它使我們訪問ch後面的那個內存位置。第2箇中間結果用虛線方框表示,因爲下一個操作符把它當作一個左值使用。最後,我們在這個位置執行++操作,也就是增加它的值。

++*cp++

這個表達式和前一個表達式的區別在於這次第1個++操作符是後綴形式而不是前綴形式,由於它的優先級較高,所以先執行cp++。間接訪問操作所訪問的是cp所指的位置而不是cp所指向的位置後面的那一個位置。


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