指針的使用

1.指針與別名陷阱

若是有一塊內存區域,指向這塊內存區域的指針有多個,其中每個指針都是其餘指針的別名。數組

別名操做在優化程序是會形成不少麻煩,以下面的例子:函數

void f1(int *x, int *y)
{
    *x += 2 * *y;
}
void f2()
{
    *x += *y;
    *x += *y;
}

上面的兩段代碼,第一段訪問兩次寄存器,進行一次乘法計算,第二段代碼進行4次訪問寄存器,兩次乘法。這兩段代碼的效率明顯不同,優化的時候確定選擇效率高的代碼,可是兩段代碼的執行結果都是同樣的嗎?優化

當x和y都指向同一塊內存時,結果並非同樣的:spa

當x和y都指向變量a,且a = 3;指針

執行第一段代碼後a的值爲9,執行第二段代碼後a的值爲12 。緣由就是x和y都指向同一塊內存區域,每次操做都會改變這塊內存的值。code

2.數組的指針

在C語言中,一個指針變量一樣能夠指向一個數組:對象

int (*p)[10];

表示p是一個指針,指向一個數組對象,該數組是一個擁有10個整數的數組。內存

所以當p+1時,p移動的字節數應該是等於p所指向的數組對象的字節數。如上面的p+1,應該移動sizeof(int)*10也就是40個字節,以下圖所示編譯器

下面的程序演示了指向數組的指針:it

#include <stdio.h>

int main(void)
{
  int a[5] = {1, 2, 3, 4, 5};  //數組初始化
  int (*p)[5];  //數組指針
  int *ptr;
  p = &a;
  ptr = (int *)(p + 1);  //將數組指針轉換爲整型的指針
  printf("the result is : %d\n", *(ptr-1));  //輸出數組的最後一個元素
  
  return 0;
}

運行結果爲:

the result is : 5

p爲數組指針,當p+1後,p指向了數組後面的位置,並轉換爲整型指針,當整型指針前移4個字節時,就指向了數組的最後一個元素。

3.指針的指針

在C語言中,指針能夠指向指針,也就是說,指針變量的值能夠是另一個指針變量的地址。

定義一個指向指針的指針變量:

int **p;

以下程序:

#include <stdio.h>

int main()
{
  int a;
  int *p;
  int **q;
  a = 100;
  p = &a;  //p指向變量a
  q = &p;  //q指向指針p
  printf("var a : %d\n", a);  //輸出變量的值
  printf("pointer p : 0x%x\n", *p);  //輸出指針的值
  printf("pointer pointer q : 0x%x\n", *q); //輸出指向指針的指針的值
  return 0;
}

運行結果

4.指針與參數傳遞

使用傳遞變啦很指針的方法來改變參數自己的值。下面的例子使用了指針做爲餐胡傳遞,交換實現兩個變量的值。

#include <stdio.h>

void swap(int *a, int *b)
{
  int t;
  t = *a;
  *a = *b;
  *b = t;
}

int main(void)
{
  int a, b;
  a = 1;
  b = 2;
  printf("a, b : %d, %d\n", a, b);
  swap(&a, &b);
  printf("a, b : %d, %d\n", a, b);
  return 0;
}

運行結果:

函數將兩個指針做爲參數賦值到棧幀上,可是並無改變指針自己的值,而是經過指針修改所指向的內容,這時,修改是能夠被調用這函數看見的。

同理,若是須要修改指針自己,則需傳遞的參數應該是指針的指針。

#include <stdio.h>
#include <stdlib.h>

void alter(int **p)
{
  int *q;
  q = (int *)malloc(sizeof(int));//分配一塊存儲整型變量的內存
  *q = 100;  //將該變量的值設置爲100
  *p = q; //是指針的指針所指向的內容指向這塊新的內存
}

int main(void)
{
  int a;
  int *p;
  a = 10;
  p = &a;
  printf("p : 0x%x, *p %d\n", p, *p);
  alter(&p);  //更改指針變量自己的值
  printf("p : 0x%x, *p %d\n", p, *p);
  return 0;
}

運行結果:

5.指針類型的意義

指針的本質就是一個無符號的整型,表明一個內存單元的單元號,在定義一個指針變量的同時,每每會聲明該指針變所指向的數據的類型,以下:

int *p;

表示該指針變量p指向的數據是一個整型,其做用在於告訴編譯器須要從該地址處向後看多少個字節,把這些字節當作一個對象來看。

下面的程序演示指針所指向的數據類型的意義:

#include <stdio.h>

typedef struct{
  int array[2];
  char ch;
}Test;

int main(void)
{
  Test var = {0x12345678, 0x12345678, 0x30};  //初始化結構體
  char *p;
  Test *q;
  //將指針p轉換爲指向字符型變量,向後看一個字節
  p = (char *)&var;
  printf("1 byte : 0x%x\n", *p);
  //將指針p轉換爲指向短整型變量,向後看兩個字節
  printf("2 byte : 0x%x\n", *(short *)p);
  //將指針轉換爲指向整型變量,向後看4個字節
  printf("4 byte : 0x%x\n", *(int *)p);
  //將指針p轉換爲指向長整型,向後看8個字節
  printf("8 byte : 0x%lx\n", *(long *)p);

  //將指針p轉換爲Test結構類型變量
  q = (Test *)p;
  printf("whole bytes : 0x%x, 0x%x, %c\n", q->array[0], q->array[1], q->ch);
  
  return 0;
}

運行結果爲:

上面程序中的結構變量var的存儲結構爲:

4.void*型指針

void*指針表示一個任意類型的指針,能夠指向任意類型的內存單元。

C語言中定義一個指向任意類型對象的指針:

void *p;

該指針p表示指向任意類型,可是若是須要引用該指針所指向的數據時,結匯發生編譯錯誤。

下面的程序演示了引用一個任意類型指針所指向的數據:

#include <stdio.h>

int main(void)
{
  int a = 100;
  void *p;
  p = &a;
  printf("%d\n", *p);  //引用p所指向的數據
}

編譯該程序,發現出錯了:

指針類型的意義在於編譯器能夠知道從該指針所表示的地址開始,向後將多少字節當作一個總體對象來看。可是任意類型指針並不能告訴編譯器該對象的大小是多少,所以編譯器不知道向後看多少個字節,因此就會出現編譯錯誤。

    既然沒法引用任意類型指針所指向的數據,void*類型彷佛沒有存在的意義了,其實否則。有時編譯器不清楚用戶要吧指針所指向的內容作聲明用途,這時候void*類型的指針就派上用場了,編譯器認爲這塊內存用戶作什麼都是合法的,所以指向這塊內存的指針就是任意類型的。

    最典型的一個例子就是使用malloc()函數分配一塊內存後,獲得這塊內存的首地址。用戶分配了一塊內存,編譯器不知道用戶要作什麼,所以就返回一個void*類型的指針,這樣就能夠躲過沒必要要的編譯器類型檢查。以後怎麼使用該指針是用戶的事情,與malloc()函數無關了。

下面的程序演示使用malloc()函數獲得一個void*類型的指針:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  void *p;  //p是一個void*類型的指針
  int *q;
  p = malloc(sizeof(int));  //使用malloc()函數分配4個字節
  if(p == NULL) {
    perror("fail to malloc");
    exit(1);
  }
  q = (int *)p; //要將p轉換爲指向整型的指針後纔可使用其指向的數據
  *q = 100;
  printf("the value is : %d\n", *q);
  
  return 0;
}

運行結果爲:

C語言中對指針類型的檢查十分嚴格,參與運算或者比較的兩個指針指向的對象類型必須相同才被認爲是同一類型的指針。

指針的本質是一個無符號的整數,從理論上講,指針和一個整數的比較應該是沒有問題的,可是c語言編譯器不容許二者進行比較,所以在比較一個指針和一個整型數據時,首先要將整型數據轉換爲該指針類型,而後再進行比較。

#include <stdio.h>
int main(void)
{
  int *p;
  if(p == 1000){  //直接拿整型常量和指針進行比較
    printf("equal\n");
  } else {
    printf("not equal\n");
  }

  if(p == (int *)1000){ //將整型轉換爲指針變量後比較
    printf("equal\n");
  } else {
    printf("not equal\n");
  }
  return 0;
}

編譯時,編譯器會警告類型不匹配:

由此,爲了不編譯器的警告,須要將整型常量轉換爲指針。

一個典型的例子就是NULL常量,該常量用於表示空指針。其自己並非C語言中的一個關鍵字,而是一個定義在stdio.h文件中的宏:

#define NULL (void *)0

空指針實際上就是一個常數0,其表明0號內存單元。在全部的系統中,0號內存單元都是不容許進行讀寫操做的,所以指向該內存單元的指針做爲空指針使用。之因此須要將0轉換爲void*類型的指針,其目的是要避免編譯器作無用的類型檢查。

相關文章
相關標籤/搜索