1. 程式人生 > 其它 >C語言指標

C語言指標

1 指標

指標是什麼?與記憶體地址的關係是什麼?

下圖是 4G 記憶體中每個位元組單元和它的編號:

  • 每個位元組單元可以儲存8個二進位制位,即1個位元組大小的內容;
  • 位元組單元也被稱為記憶體單元;
  • 為了方便定址,每個記憶體單元都有唯一的編號,這個編號就是記憶體地址(Address)或者指標(Address)
  • 地址從 0 開始依次增加,對於 32 位環境,程式能夠使用的記憶體為 4GB,最小的地址為 0x00000000,最大的地址為 0xFFFFFFFF

【答】

  • 指標是記憶體單元的編號;
  • 指標變數的值是一個記憶體地址。用*取指標變數的值得到的就是該指標變數指向的記憶體地址儲存的值。

1.1 如何建立指標

#include <stdio.h>

int main() {
  int a = 10;
  int* p1 = &a;
  int *p2 = &a;
  int** pp = &p1;

  printf("%p\n", &a);
  printf("%p\n", p1);
  printf("%p\n", p2);
  printf("%p\n", pp);

  system("pause");
  return 0;
}

一次執行中,執行結果如下:

我們畫圖來表示:

  • 但在編寫程式碼的過程中,我們認為變數名錶示的是資料本身;例如,a 是 int 型,表示的是 4位元組的整數。

  • a 是一個變數,用來存放整數,需要在前面加&來獲得它的地址;&a 表示就是該變數的首地址。

  • 指標可以認為是一種資料型別,int*就可以視為一個整體。p1 就是一個指標,這個指標指向變數 a 的首地址。

  • int* p = &a;int *p = &a; 寫法略有差異,但是實際功能是相同的。因此, &a,p1,p2 表示的是同一個記憶體單元。

  • int** pp 就等同於 (int*)* pp

    ,pp 是指標,指向一個記憶體單元,在32位系統中,int*佔4個位元組,所以 pp 儲存的值等於 0x0133F7D4,這個值又是一個記憶體地址,或者說指標。

【答】:定義指標用 int* p; 或者 int *p,個人更推薦前者。

1.2 指標佔多少位元組

我們先來看一個程式:

#include <stdio.h>

int main() {
  char* p1;
  short* p2;
  int* p3;
  long* p4;
  float* p5;
  double* p6;

  printf("%d\n", sizeof(p1));
  printf("%d\n", sizeof(p2));
  printf("%d\n", sizeof(p3));
  printf("%d\n", sizeof(p4));
  printf("%d\n", sizeof(p5));
  printf("%d\n", sizeof(p6));	 
  system("pause");
  return 0;
}

我們來看一下執行結果:

  • 我們發現任何型別的指標變數都是佔用4個位元組。
  • 因為我們的程式是用32位編譯的,所以總是4個位元組

【答】
指標的大小是固定的;指標所佔的位元組數跟編譯的程式位數有關。如果是32位程式,指標的寬度就是4位元組,如果是64位程式,指標的寬度就是8位元組。

1.3 指標變數的運算

我們再來看一段程式:

#include <stdio.h>

int main()
{
  char c = 'C';
  short s = 1;
  int i = 100;
  double d = 99.9;
  char* p1 = &c;
  short* p2 = &s;
  int* p3 = &i;
  double* p4 = &d;
  printf("size char=%6d short=%5d int=%7d double=%4d\n", sizeof(char), sizeof(short), sizeof(int), sizeof(double));
  printf("Init p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);
  // 加法運算
  p1++;p2++;p3++;p4++;
  printf("p++  p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);
  // 減法運算
  p1-=2;p2-=2;p3-=2;p4-=2;
  printf("p-=2 p1=%8p p2=%8p p3=%8p p4=%8p\n", p1, p2, p3, p4);

  system("pause");
  return 0;
}

執行結果如下:

  • 自增:p1,p2,p3,p4每次加1,他們的地址分別增加1、2、4、8個位元組,這個增量正好等於 char,short,int,double 型別的長度。
  • 減法:p1,p2,p3,p4每次減2,他們的地址分別減少2、4、8、16個位元組,這個減少量正好等於 char,short,int,double 型別的長度的2倍。

1.4 單目操作符&*

首先,定義指標變數void* p 或者 void *p,以及定義引用變數 int& a 或者 int &a;在定義變數時,會比 &* 單純作為單目運算子要多一個型別宣告。

首先,作為單目運算子,它的特點就是隻有一個運算元:

int a = 1;
int* p = &a;
*p = 10;
  • 首先定義了一個變數a
  • 接著,定義了指標變數p,併為它賦值。賦值時,用上了單目運算子&來取得變數a的首地址,並賦值給指標p。
  • 最後,再次使用了單目運算子*來獲取指標p指向的資料,並修改了資料內容。

& 作為 單目運算子 使用時,是取地址符
* 作為 單目運算子 使用時,是解引用符

2 指標常量和常量指標

我之前在學習指標的時候,總是會把指標常量和常量指標混淆起來。最主要的原因是,按照一般的習慣,我們是從左向右讀,所以我們會理所當然地認為const int* p常量指標。但是實際上你倒過來念就是正確的。

2.1 指標常量

int a = 0;
int b = 1;

// 指標常量
const int* p = &a;

// *p = 10; // 錯誤,內容不可以修改
p = &b;     // 正確,指標可以修改

記憶方法:按照英文反著念 int*,const: 指標常量。中文的習慣,最終定性為“常量”,所以指標指向的資料是個常量,不可以被修改。

2.2 常量指標

int a = 0;
int b = 1;

// 常量指標
int* const p = &a;

*p = 10;   // 正確,指標指向的資料可以修改
// p = &b; // 錯誤,指標地址不可以修改

記憶方法:反著念定義關鍵字 const,int*:常量指標。中文的習慣,最終定性為“指標”,常量修飾“指標”一詞,所以指標地址不可以被修改。但是,指標指向的資料可以被修改。

2.3 地址和資料都不許改

int a = 0;
int b = 1;

const int* const p = &a;

// *p = 10;  // 錯誤,指標指向的資料不可以修改
// p = &b;   // 錯誤,指標地址不可以修改

3 指標與陣列

3.1 訪問陣列中的元素

int arr[] = {1,2,3,4,5};
int* start = arr;
  • 首先,初始化了整型陣列 arr;
  • 接著,把陣列名賦值給了指標start;

函式名、字串名和陣列名錶示的是程式碼塊或資料塊的首地址

我們想要遍歷陣列,首先需要計算陣列的元素個數:

int len = sizeof(arr) / 4;

然後,不使用指標時,我們訪問陣列的是這樣:

for (int i = 0; i < len; i++) {
  printf("%d\n", arr[i]);
}

使用指標訪問陣列是這樣的:

for (int i = 0; i < len; i++) {
  printf("%d\n", *(start+i));
}

3.2 通過char型指標訪問short陣列會發生什麼?

short arr[] = {1, 2, 3};
char* p = reinterpret_cast<char *>(arr);

for (int i = 0; i < 3; i++) {
  printf("%d\n", *(p+i));
}

執行結果:

參考文件

《C語言指標是什麼?1分鐘徹底理解C語言指標的概念》閱讀