C 語言編程 — 高級數據類型 — 指針

目錄

前文列表

程序編譯流程與 GCC 編譯器
C 語言編程 — 基本語法
C 語言編程 — 基本數據類型
C 語言編程 — 變量與常量
C 語言編程 — 運算符
C 語言編程 — 邏輯控制語句
C 語言編程 — 函數

派生類型

包括:指針類型、數組類型、結構類型、共用體類型和函數類型。

指針

通過指針,可以簡化一些 C 編程任務的執行,還有一些任務,如動態內存分配,沒有指針是無法執行的。

前門的文章中提到過,變量 = 變量名 + 變量值,而且 C 語言是值語義的,有別於 Python 的引用語義。所以變量名就是變量在內存中的入口地址,變量值就是變量在內存空間中實際的數值。在程序中可以使用地址運算符 & 來獲取變量的入口地址。如下:

#include <stdio.h>

int main(){
    int var1;
    char var2[10] = {10, 9, 8, 7};

    printf("var1: %p\n", &var1);
    printf("var2-0: %p\n", &var2[0]);
    printf("var2-1: %p\n", &var2[1]);
    printf("var2-2: %p\n", &var2[2]);

    return 0;
}

運行:

$ ./main
var1: 0x7ffc857d59bc
var2-0: 0x7ffc857d59b0
var2-1: 0x7ffc857d59b1
var2-2: 0x7ffc857d59b2

可見,不同變量之間的內存空間很可能不是連續的,但同一數值內的順序元素的空間是連續的。

指針的本質也是一個變量,其變量值是另一個變量的入口地址,即一個變量存儲了另一個變量的內存地址,是爲指針。數組名本質上也是一個指針,並且是常量指針,記錄了數組的入口地址,且不能夠被修改。

聲明指針

type *var-name;
  • type 是指針的基類型,是一個有效的 C 數據類型
  • var-name 是指針變量的名稱
  • * 用來聲明指針類型變量
int    *ip;    /* 一個整型的指針 */
double *dp;    /* 一個 double 型的指針 */
float  *fp;    /* 一個浮點型的指針 */
char   *ch;     /* 一個字符型的指針 */

需要注意的是,不管指針的基類型是什麼,指針變量的數值的類型都是一個代表內存地址的十六進制數。指針的基類表示了指針所指向的變量或常量的數據類型。

使用指針

使用指針時會頻繁進行以下幾個操作:

  1. 定義一個指針變量
  2. 把變量的內存地址賦值給指針
  3. 訪問指針變量存儲的數值(內存地址)
#include <stdio.h>
 
int main ()
{
   int  var = 20;   /* 實際變量的聲明 */
   int  *ip;        /* 指針變量的聲明 */
 
   ip = &var;  /* 在指針變量中存儲 var 的地址 */
 
   printf("Address of var variable: %p\n", &var  );
 
   /* 在指針變量中存儲的地址 */
   printf("Address stored in ip variable: %p\n", ip );
 
   /* 使用指針訪問值 */
   printf("Value of *ip variable: %d\n", *ip );
 
   return 0;
}

運行:

Address of var variable: bffd8b3c
Address stored in ip variable: bffd8b3c
Value of *ip variable: 20

NULL 指針

在聲明指令變量的時候,如果沒有確切的內存地址可以賦值,那麼爲指針變量賦一個 NULL 值是一個良好的編程習慣,稱爲空指針。NULL 指針是一個定義在標準庫中的值爲零的常量。

#include <stdio.h>
 
int main ()
{
   int  *ptr = NULL;
   printf("ptr 的地址是 %p\n", ptr);
   return 0;
}

運行

ptr 的地址是 0x0

在大多數的操作系統上,不允許程序訪問地址爲 0x0 的內存,因爲該內存是操作系統保留的。但按照慣例,如果指針變量的數值爲 NULL 時,則假定它不指向任何東西。

判斷一個空指針的方式:

if(ptr)     /* 如果 p 非空,則完成 */
if(!ptr)    /* 如果 p 爲空,則完成 */

指針的算術運算

C 指針的本質是一個十六進制數值,所以可以對指針執行算術運算,可以對指針進行四種算術運算:++--+-

  • 指針的每一次遞增,它會指向下一個元素的存儲單元。
  • 指針的每一次遞減,它會指向前一個元素的存儲單元。
  • 指針在遞增和遞減時的步進(跳躍的字節數)取決於指針所指向的變量的數據類型,比如 int 就是 4 個字節。

我們喜歡在程序中使用指針代替數組,因爲變量指針可以遞增,而數組不能遞增,數組可以看成一個指針常量。下面的程序遞增變量指針,以便順序訪問數組中的每一個元素:

#include <stdio.h>


const int MAX = 3;


int main(){
    int var[] = {10, 100, 200};
    int i;
    int *ptr;

    /* 數組名就是一個指針,直接複製給指針類型變量 */
    ptr = var;
    for(i = 0; i < MAX; i++){
        printf("Address: var[%d] = %p\n", i, ptr);
        printf("Value: var[%d] = %d\n", i, *ptr);
        /* 移動到下一個位置 */
        ptr++;
    }
    return 0;
}

運行:

./main
Address: var[0] = 0x7ffe48f272d0
Value: var[0] = 10
Address: var[1] = 0x7ffe48f272d4
Value: var[1] = 100
Address: var[2] = 0x7ffe48f272d8
Value: var[2] = 200

可見,每遞增一次,移動了 4 Byte。

同樣地,對指針進行遞減運算,即把值減去其數據類型的字節數,如下所示:

#include <stdio.h>

const int MAX = 3;

int main(){
    int var[] = {10, 100, 200};
    int i;
    int *ptr;

    /* 獲得數組最後一個元素的指針,再複製給指令類型變量 */
    ptr = &var[MAX - 1];
    for(i = MAX; i > 0; i--){
        printf("Address: var[%d] = %p\n", i - 1, ptr);
        printf("Value: var[%d] = %d\n", i - 1, *ptr);

        /* 移動到下一個位置 */
        ptr--;
    }
    return 0;
}

運行:

./main
Address: var[2] = 0x7ffdbab78f88
Value: var[2] = 200
Address: var[1] = 0x7ffdbab78f84
Value: var[1] = 100
Address: var[0] = 0x7ffdbab78f80
Value: var[0] = 10

指針可以時要關係運算符進行比較,如 ==<>。如果 p1 和 p2 指向兩個相關的變量,比如同一個數組中的不同元素,則可對 p1 和 p2 進行大小比較。下面的程序修改了上面的實例,只要變量指針所指向的地址小於或等於數組的最後一個元素的地址 &var[MAX - 1],則把變量指針進行遞增:

#include <stdio.h>
 
const int MAX = 3;
 
int main ()
{
   int  var[] = {10, 100, 200};
   int  i, *ptr;
 
   /* 指針中第一個元素的地址 */
   ptr = var;
   i = 0;
   while ( ptr <= &var[MAX - 1] )
   {
 
      printf("Address of var[%d] = %x\n", i, ptr );
      printf("Value of var[%d] = %d\n", i, *ptr );
 
      /* 指向上一個位置 */
      ptr++;
      i++;
   }
   return 0;
}

指向指針的指針

指向指針的指針是一種多級間接尋址的實現,或者說是一個指針鏈。通常,一個指針包含一個變量的地址。當我們定義一個指向指針的指針時,第一個指針包含了第二個指針的地址,第二個指針指向包含實際數值的內存位置。

在這裏插入圖片描述

一個指向指針的指針變量必須如下聲明,在變量名前放置兩個 * 號。例如,下面聲明瞭一個指向 int 類型指針的指針:

int **var;

當一個目標值被一個指針間接指向到另一個指針時,訪問這個值需要使用兩個星號運算符,如下面實例所示:

#include <stdio.h>
 
int main ()
{
   int  var;
   int  *ptr;
   int  **pptr;

   var = 3000;

   /* 獲取整型變量 var 的地址 */
   ptr = &var;

   /* 獲取指向整型變量的指針變量 ptr 的地址 */
   pptr = &ptr;

   printf("Value of var = %d\n", var );
   printf("Value available at *ptr = %d\n", *ptr );
   printf("Value available at **pptr = %d\n", **pptr);

   return 0;
}

將指針作爲實際參數傳入函數

C 語言允許您傳遞指針給函數,只需要簡單地聲明函數參數爲指針類型即可。

#include <stdio.h>
#include <time.h>
 
void getSeconds(unsigned long *par);

int main ()
{
   unsigned long sec;

   getSeconds(&sec);
   
   /* 輸出實際值 */
   printf("Number of seconds: %ld\n", sec);
   return 0;
}

void getSeconds(unsigned long *par)
{
   /* 獲取當前的秒數 */
   *par = time(NULL);
   return;
}

從函數返回指針

類似地,C 語言允許從函數返回指針類型。只需要一個簡單的函數聲明:

int * myFunction(){}

需要注意的是,C 語言不支持在調用函數時返回局部變量的地址,除非定義局部變量爲 static 變量。下面的函數,它會生成 10 個隨機數,並使用表示指針的數組名(即第一個數組元素的地址)來返回它們:

#include <stdio.h>
#include <time.h>
#include <stdlib.h> 
 
/* 要生成和返回隨機數的函數 */
int * getRandom( )
{
   static int r[10];
   int i;
 
   /* 設置種子 */
   srand((unsigned)time(NULL));
   for ( i = 0; i < 10; ++i)
   {
      r[i] = rand();
      printf("%d\n", r[i] );
   }
 
   return r;
}


int main ()
{
   /* 一個指向整數的指針 */
   int *p;
   int i;
 
   p = getRandom();
   for ( i = 0; i < 10; i++ )
   {
       printf("*(p + [%d]) : %d\n", i, *(p + i) );
   }
   return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章