C語言中文網 目錄
首頁 > 編程筆記 > C語言筆記 > 指針 閱讀:4,456

C語言指針的運算

本文討論使用指針進行的運算,最重要的運算是獲取指針所引用的對象或函數。也可以比較指針,使用指針來遍歷一個內存區域。

使用指針讀取與修改對象

間接運算符 * 生成內存中的位置,該位置的地址存儲在一個指針中。如果 ptr 是一個指針,那么 *ptr 就是 ptr 所指向的對象(或函數)。

使用間接運算符有時候被稱為解引用(dereferencing)一個指針。指針指向的內存位置被認為存儲有一個對象,指針的類型決定了該對象的類型。例如,當用 int 指針獲取一個特定內存位置,讀出或寫入的也是 int 類型的對象。

與乘法運算符 * 不同,間接運算符 * 是一元運算符,也就是說,間接運算符只有一個操作數。在例 1 中,ptr 指向變量 x。因此,表達式 *ptr 等效于變量 x 本身。

【例1】解引用一個指針
double x, y, *ptr;    // 兩個double變量和一個double指針
ptr = &x;                   // 使得ptr指向x
*ptr = 7.8;             // 對變量x賦值7.8
*ptr *= 2.5;            // 將x乘以2.5
y = *ptr + 0.5;                 // 將加法x+0.5的結果賦值給y

不要混淆指針聲明中的星號(*)和間接運算符。聲明中的語法可以被視為展示了如何使用指針。如下例所示:
double *ptr;

如上述聲明,ptr 具有 double* 類型(讀為:“指向double的指針”)。因此,表達式 *ptr 類型是 double。

當然,間接運算符 * 必須匹配一個具有有效地址的指針。這個用法要求在編寫程序時需要特別小心!例 1 中,如果沒有 ptr=&x 為 ptr 分配有效地址,那么所有包含 *ptr 的語句都是沒有意義的(解引用一個沒有定義的指針),有可能會造成程序崩潰。

一個指針變量,其本身也是內存中的一個對象,也就是說,其他指針可以指向該指針。若想創建指針的指針,必須使用兩個星號,如下例所示:
char c = 'A', *cPtr = &c, **cPtrPtr = &cPtr;

表達式 *cPtrPtr 當前生成 char 指針 cPtr,而 **cPtrPtr 的值是 char 變量 c。圖 1 展示了這樣的關系。


圖1

指針的指針不僅限于兩個層次的間接運算。也可以根據自己的需要定義多個層次的間接運算。然而,不能通過多次使用地址運算符 & 來取得指針的值:
char c = 'A', **cPtrPtr = &(&c);  // 錯誤!

上例中的第二個初始化語句是非法的:表達式(&c)不可以作為 & 的操作數,因為它不是一個左值。換句話說,在本例中,不存在可以讓 cPtrPtr 指向的 char 指針。

如果將一個指針采用引用方式傳入函數,以讓函數可以修改該指針的值,那么該函數的參數就是指針的指針。下面簡單的例子是一個函數,動態地創建一個新的記錄,將其地址存儲在一個指針變量中:
#include <stdlib.h>
// 記錄的類型:
typedef struct { long key; /* ... */ } Record;
_Bool newRecord( Record **ppRecord )
{
  *ppRecord = malloc( sizeof(Record) );
  if ( *ppRecord != NULL )
  {
    /* ...初始化新記錄的成員... */
    return 1;
  }
  else
    return 0;
}

下列語句是調用函數 newRecord()的一種可能方式:
Record *pRecord = NULL;
if ( newRecord( &pRecord) )
{
  /* ...pRecord現在指向了一個新的Record對象... */
}

表達式 *pRecord 生成新的記錄,并且(*pRecord).key 是該記錄中的 key 成員。表達式(*pRecord).key 中的括號是有必要的,因為點運算符(.)比間接運算符(*)具有更高的優先級。

不用上面運算符與括號結合的方式,也可以使用箭頭運算符 -> 來獲取結構或聯合的成員。如果 p 是一個指向結構或聯合的指針,并且該結構或聯合具有成員 m,那么表達式 p->m 等效于(*P).m。因此,下面的語句將一個值賦值給 pRecord 所指的 key 成員:
pRecord->key = 123456L;

修改和比較指針

除了使用賦值操作讓一個指針引用一個給定的對象或函數,也可以使用算術運算來修改一個對象指針。當進行指針算術運算(pointer arithmetic)時,編譯器會自動在運算中采用指針所指類型的空間大小。

對于指向對象的指針,可以進行下列的運算:
(1) 對一個指針執行整數加法和減法操作。
(2) 兩個指針相減。
(3) 比較兩個指針。

當將兩個指針相減時,這兩個指針必須具有相同的基本類型,但是類型限定符則不需要一樣。而且,可以使用相等運算符(==和!=)來將任何指針與空指針常量比較,也可以將對象指針與 void 指針比較。

這里所描述的三種指針運算,通常只針對指向數組元素的指針時才有用。為了展示這些運算的作用,假設有兩個指針 p1 和 p2,它們都指向數組 a 內的元素:

(1) 如果 p1 指向數組元素 a[i],并且 n 是一個整數,那么表達式 p2=p1+n 的使得 p2 指向數組元素 a[i+n](假設 i+n 仍在數組 a 的索引范圍內)。

(2) 減法 p2-p1 的結果是獲得兩個指針之間數組元素的數量,結果的類型是 ptrdiff_t。該類型定義在頭文件 stddef.h 中,通常定義成 int。在賦值運算 p2=p1+n 之后,表達式 p2-p1 的值是 n。

(3) 如果 p2 所引用的元素比 p1 所引用的元素具有更大的索引值,則 p1<li="">。

因為一個數組的名稱會隱式地轉換為指向數組第一個元素的指針,所以可以把數組的下標表示法替換為指針算術
(1) 表達式 a+i 是指向 a[i] 的指針,而 *(a+i)的值是元素 a[i]。
(2) 表達式 p1-a 的結果是 p1 指向元素的索引值 i。

在例 2 中,函數 selection_sortf()對 float 元素數組進行排序,使用選擇性排序算法。這個函數使用指針而非索引完成對 float 元素數組的排序,函數swapf()維持不變。

【例2】指針版本的函數 selection_sortf()
// 函數swapf()交換兩個float變量的值
// 參數:兩個指向float的指針
inline void swapf( float *p1, float *p2 )
{
   float tmp = *p1; *p1 = *p2; *p2 = tmp;       // 交換*p1和*p2
}
// 函數selection_sortf()使用選擇性排序算法,對float元素數組排序
// 參數:一個float元素數組,以及它的長度
void selection_sortf( float a[], int n )        // 對有n個float元素的數組a進行排序
{
  if ( n <= 1 ) return;                              // 不進行排序
  register float *last = a + n-1,               // 指向最后一個元素的指針
                 *p,                            // 指向一個選定元素的指針
                 *minPtr;                       // 指向當前最小值元素的指針
  for ( ; a < last; ++a )                    // 將指針遍歷整個數組
  {
    minPtr = a;                                         // 在a所指的元素與最后一個元素之間
    for ( p = a+1; p <= last; ++p )          // 找到最小值元素
      if ( *p < *minPtr )
        minPtr = p;
    swapf( a, minPtr );                                 // 將最小值元素與a所指的元素交換
  }
}

通常來講,指針版本的函數比使用索引版本的函數具有更高效率,因為使用索引 i 獲取數組 a 的元素,表達式為 a[i] 或 *(a+i),它涉及將 a 的地址加上 i*sizeof(元素類型)的值,以獲得對應的數組元素地址。指針版本相比之下需要的運算就少得多了,因為指針本身可遞增,不需要索引,并且指針直接指向所需的元素。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo