天天看點

深入淺出C語言指針 全面!收藏!

作者:霸都嵌入式

C語言指針是一種特殊的變量,它可以存儲另一個變量的記憶體位址,進而間接地通路或修改該變量的值。指針是C語言中最強大也最複雜的特性之一,它可以實作動态記憶體配置設定、數組、字元串、函數參數傳遞、函數傳回值、資料結構等功能。

要了解指針的原理,我們需要先了解一些基本概念,如記憶體位址、變量、資料類型、運算符等。下面我将從這些方面逐一介紹。

## 記憶體位址

記憶體位址是計算機中每個位元組(byte)的唯一編号,它用來辨別記憶體中的位置。記憶體位址通常用十六進制數表示,如0x7ffeeaae08d8。每個記憶體位址都對應一個位元組的内容,也就是8位(bit)的二進制數,如01100010。

在C語言中,我們可以使用取位址運算符(&)來擷取一個變量的記憶體位址,也可以使用指針變量來存儲和操作一個變量的記憶體位址。例如:

#include <stdio.h>
int main () {
  int a = 10; // 定義一個整型變量a,并指派為10
  int *p; // 定義一個整型指針變量p
  p = &a; // 将a的記憶體位址指派給p
  printf("a的值為:%d\n", a); // 輸出a的值
  printf("a的記憶體位址為:%p\n", &a); // 輸出a的記憶體位址
  printf("p的值為:%p\n", p); // 輸出p的值,即a的記憶體位址
  printf("p所指向的記憶體中的值為:%d\n", *p); // 輸出p所指向的記憶體中的值,即a的值
  return 0;
}           

當上面的代碼被編譯和執行時,它會産生類似下面的結果:

a的值為:10
a的記憶體位址為:0x7ffeeaae08d8
p的值為:0x7ffeeaae08d8
p所指向的記憶體中的值為:10           

我們可以用文字來描述上述代碼在記憶體中的情況:

- 變量a占用了4個位元組(32位)的記憶體空間,其首位元組(最低位)的位址為0x7ffeeaae08d8,其内容為00001010(二進制表示),即10(十進制表示)。

- 變量p占用了8個位元組(64位)的記憶體空間,其首位元組(最低位)的位址為0x7ffeeaae08d0,其内容為0x7ffeeaae08d8(十六進制表示),即a的首位元組位址。

- 是以,我們說p指向了a,或者說p是a的指針。

## 變量

變量是程式中用來存儲資料的辨別符,它有一個名稱和一個資料類型。名稱用來區分不同的變量,資料類型用來确定變量占用多少記憶體空間以及如何解釋其内容。例如:

int a; // 定義一個整型變量a
char b; // 定義一個字元型變量b
float c; // 定義一個浮點型變量c           

上面定義了三個不同類型的變量,它們分别占用了不同大小的記憶體空間,并且有不同的取值範圍和精度。例如:

- 整型變量通常占用4個位元組(32位),其取值範圍是-2147483648到2147483647。

- 字元型變量通常占用1個位元組(8位),其取值範圍是0到255,或者-128到127,根據不同的字元編碼方式,可以表示不同的字元,如ASCII碼或Unicode碼。

- 浮點型變量通常占用4個位元組(32位),其取值範圍和精度取決于其符号位、指數位和尾數位的組合,一般可以表示大約6位有效數字的小數。

在C語言中,我們可以使用sizeof運算符來擷取一個變量或一個資料類型所占用的記憶體空間大小,機關是位元組。例如:

#include <stdio.h>
int main () {
  int a;
  char b;
  float c;
  printf("整型變量a占用了%d個位元組\n", sizeof(a)); // 輸出整型變量a占用了4個位元組
  printf("字元型變量b占用了%d個位元組\n", sizeof(b)); // 輸出字元型變量b占用了1個位元組
  printf("浮點型變量c占用了%d個位元組\n", sizeof(c)); // 輸出浮點型變量c占用了4個位元組
  printf("整型資料類型int占用了%d個位元組\n", sizeof(int)); // 輸出整型資料類型int占用了4個位元組
  printf("字元型資料類型char占用了%d個位元組\n", sizeof(char)); // 輸出字元型資料類型char占用了1個位元組
  printf("浮點型資料類型float占用了%d個位元組\n", sizeof(float)); // 輸出浮點型資料類型float占用了4個位元組
  return 0;
}           

當上面的代碼被編譯和執行時,它會産生類似下面的結果:

整型變量a占用了4個位元組
字元型變量b占用了1個位元組
浮點型變量c占用了4個位元組
整型資料類型int占用了4個位元組
字元型資料類型char占用了1個位元組
浮點型資料類型float占用了4個位元組           

## 資料類型

資料類型是程式中定義不同種類的資料的方式,它決定了資料的取值範圍、表示方式、運算規則等。C語言中有以下幾種基本的資料類型:

- 整型(int):表示整數,如10,-5,0等。

- 字元型(char):表示單個字元,如'a','B','9'等。

- 浮點型(float):表示帶有小數部分的數,如3.14,-0.5,0.0等。

- 雙精度浮點型(double):表示更大範圍和更高精度的帶有小數部分的數,如3.1415926,-1.23e10,0.0等。

- 無類型(void):表示沒有任何值的類型,通常用于函數的傳回值或參數。

除了這些基本的資料類型外,C語言還支援一些派生的資料類型,如指針、數組、結構體、聯合體、枚舉等。這些資料類型都是由基本資料類型組合而成的。例如:

int *p; // 定義一個指向整型的指針p
int a[10]; // 定義一個包含10個整型元素的數組a
struct student { // 定義一個結構體student
char name[20]; // 包含一個字元數組name
int age; // 包含一個整型age
};
union data { // 定義一個聯合體data
int i; // 包含一個整型i
float f; // 包含一個浮點型f
};
enum color { // 定義一個枚舉color
red, green, blue // 包含三個枚舉常量red, green, blue
};
           

## 指針

指針是一種派生的資料類型,它可以存儲另一個變量的記憶體位址,進而間接地通路或修改該變量的值。指針的定義格式如下:

資料類型 *指針名;           

其中,資料類型表示指針所指向的變量的類型,*表示這是一個指針,指針名是指針的辨別符。例如:

int *p; // 定義一個指向整型的指針p
char *q; // 定義一個指向字元型的指針q
float *r; // 定義一個指向浮點型的指針r           

上面定義了三個不同類型的指針,它們分别可以指向整型、字元型和浮點型的變量。注意,指針本身的大小和類型無關,隻取決于系統的位數,一般在32位系統中占用4個位元組,在64位系統中占用8個位元組。

在C語言中,我們可以使用以下幾種運算符來操作指針:

- 取位址運算符(&):用來擷取一個變量的記憶體位址,傳回一個指向該變量類型的指針。

- 間接通路運算符(*):用來擷取一個指針所指向的記憶體中的值,傳回一個該指針類型的變量。

- 指派運算符(=):用來給一個變量或一個指針指派,使其内容等于右邊的表達式。

- 自增自減運算符(++,--):用來對一個變量或一個指針進行加一或減一的操作,可以放在前面或後面,有不同的含義。

- 加減運算符(+,-):用來對一個變量或一個指針進行加法或減法的操作,可以與另一個變量或一個常數相加減。

- 關系運算符(==,!=,<,>,<=,>=):用來比較兩個變量或兩個指針的大小或相等性,傳回一個布爾值(0或1)。

- 邏輯運算符(&&,||,!):用來對兩個布爾值進行邏輯與、邏輯或或邏輯非的操作,傳回一個布爾值(0或1)。

下面我們來看一些使用指針的示例代碼和解釋:

#include <stdio.h>
int main () {
  int a = 10; // 定義一個整型變量a,并指派為10
  int *p; // 定義一個整型指針變量p
  p = &a; // 将a的記憶體位址指派給p
  printf("a的值為:%d\n", a); // 輸出a的值為10
  printf("a的記憶體位址為:%p\n", &a); // 輸出a的記憶體位址
  printf("p的值為:%p\n", p); // 輸出p的值,即a的記憶體位址
  printf("p所指向的記憶體中的值為:%d\n", *p); // 輸出p所指向的記憶體中的值,即a的值
  *p = 20; // 将20指派給p所指向的記憶體中的值,即修改了a的值
  printf("修改後,a的值為:%d\n", a); // 輸出修改後,a的值為20
  printf("修改後,p所指向的記憶體中的值為:%d\n", *p); // 輸出修改後,p所指向的記憶體中的值為20
  return 0;
}           

當上面的代碼被編譯和執行時,它會産生類似下面的結果:

a的值為:10
a的記憶體位址為:0x7ffeeaae08d8
p的值為:0x7ffeeaae08d8
p所指向的記憶體中的值為:10
修改後,a的值為:20
修改後,p所指向的記憶體中的值為:20           

從上面的代碼和結果可以看出,指針p可以通過取位址運算符(&)擷取a的記憶體位址,并通過間接通路運算符(*)擷取或修改a的值。這樣,我們就可以通過指針來操作變量,而不需要直接使用變量名。

下面我們來看另一個示例代碼,它展示了如何使用指針進行自增自減和加減運算:

#include <stdio.h>
int main () {
  int a[5] = {1, 2, 3, 4, 5}; // 定義一個包含5個整型元素的數組a,并初始化
  int *p; // 定義一個整型指針變量p
  p = a; // 将a的首元素位址指派給p,即p指向a[0]
  printf("p的值為:%p\n", p); // 輸出p的值,即a[0]的位址
  printf("p所指向的記憶體中的值為:%d\n", *p); // 輸出p所指向的記憶體中的值,即a[0]的值
  p++; // 将p自增1,即p指向a[1]
  printf("自增後,p的值為:%p\n", p); // 輸出自增後,p的值,即a[1]的位址
  printf("自增後,p所指向的記憶體中的值為:%d\n", *p); // 輸出自增後,p所指向的記憶體中的值,即a[1]的值
  (*p)++; // 将p所指向的記憶體中的值自增1,即修改了a[1]的值
  printf("修改後,a[1]的值為:%d\n", a[1]); // 輸出修改後,a[1]的值
  printf("修改後,p所指向的記憶體中的值為:%d\n", *p); // 輸出修改後,p所指向的記憶體中的值
  p = p + 2; // 将p加2,即p指向a[3]
  printf("加2後,p的值為:%p\n", p); // 輸出加2後,p的值,即a[3]的位址
  printf("加2後,p所指向的記憶體中的值為:%d\n", *p); // 輸出加2後,p所指向的記憶體中的值,即a[3]的值
  return 0;
}           

當上面的代碼被編譯和執行時,它會産生類似下面的結果:

p的值為:0x7ffeeaae08d8
p所指向的記憶體中的值為:1
自增後,p的值為:0x7ffeeaae08dc
自增後,p所指向的記憶體中的值為:2
修改後,a[1]的值為:3
修改後,p所指向的記憶體中的值為:3
加2後,p的值為:0x7ffeeaae08e4
加2後,p所指向的記憶體中的值為:4           

從上面的代碼和結果可以看出,指針p可以通過自增自減和加減運算來改變其指向的位置,進而通路或修改數組a中不同元素的值。這樣,我們就可以通過指針來周遊數組,而不需要使用下标。

下面我們來看最後一個示例代碼,它展示了如何使用指針作為函數的參數和傳回值:

#include <stdio.h>
// 定義一個函數swap,用來交換兩個整型變量的值,參數是兩個整型指針
void swap(int *x, int *y) {
  int temp; // 定義一個臨時變量temp
  temp = *x; // 将x所指向的記憶體中的值指派給temp
  *x = *y; // 将y所指向的記憶體中的值指派給x所指向的記憶體中的值
  *y = temp; // 将temp指派給y所指向的記憶體中的值
}
// 定義一個函數max,用來傳回兩個整型變量中較大的那個,參數是兩個整型變量,傳回值是一個整型指針
int *max(int a, int b) {
  if (a > b) { // 如果a大于b
	  return &a; // 傳回a的位址
  } else { // 否則
	  return &b; // 傳回b的位址
	}
}
int main () {
  int x = 10; // 定義一個整型變量x,并指派為10
  int y = 20; // 定義一個整型變量y,并指派為20
  int *p; // 定義一個整型指針變量p
  printf("交換前,x的值為:%d\n", x); // 輸出交換前,x的值為10
  printf("交換前,y的值為:%d\n", y); // 輸出交換前,y的值為20
  swap(&x, &y); // 調用swap函數,傳入x和y的位址作為參數
  printf("交換後,x的值為:%d\n", x); // 輸出交換後,x的值為20
  printf("交換後,y的值為:%d\n", y); // 輸出交換後,y的值為10
  p = max(x, y); // 調用max函數,傳入x和y作為參數,并将傳回值賦給p
  printf("較大者是:%d\n", *p); // 輸出較大者是10
  return 0;
}           

當上面的代碼被編譯和執行時,它會産生類似下面的結果:

交換前,x的值為:10
交換前,y的值為:20
交換後,x的值為:20
交換後,y的值為:10
較大者是:10           

從上面的代碼和結果可以看出,指針p可以作為函數swap和max的參數和傳回值來傳遞和傳回變量的位址。這樣,我們就可以通過指針來實作函數間變量的傳遞和傳回,而不需要複制變量。

總結

C語言指針是一種特殊的變量,它可以存儲另一個變量的記憶體位址,進而間接地通路或修改該變量的值。指針是C語言中最強大也最複雜的特性之一,它可以實作動态記憶體配置設定、數組、字元串、函數參數傳遞、函數傳回值、資料結構等功能。要了解指針的原理,我們需要先了解一些基本概念,如記憶體位址、變量、資料類型、運算符等。然後,我們可以使用不同的運算符來操作指針,如取位址、間接通路、指派、自增自減、加減、關系、邏輯等。最後,我們可以使用指針作為函數的參數和傳回值來傳遞和傳回變量的位址。通過這篇文章,希望你能夠掌握C語言指針的原理和用法,并能夠靈活地運用它們來編寫高效和優雅的代碼。

系列文章持續更新,如果覺得有幫助請點贊+關注!

繼續閱讀