天天看點

C語言基礎 — ( C語言的炁體源流——指針)前言一、指針是什麼?二、指針變量三、總結

歡迎小夥伴的點評✨✨ 本篇章系列是對C語言的深度思考和總結、關于C語言内容會持續更新

文章目錄

  • 前言
  • 一、指針是什麼?
  • 二、指針變量
    • 2.1、怎樣定義指針變量
    • 2.2、怎樣引用指針變量
    • 2.3、指針變量作為函數參數
    • 2.4、數組元素的指針
  • 三、總結

前言

指針是C語言中的一個重要概念,也是C語言的一個重要特色。正确而靈活地運用它,可以使程式簡潔、緊湊、高效。每一個學習和使用C語言的人,都應當深入地學習和掌握指針。可以說,不掌握指針就是沒有掌握C的精華。

指針的概念比較複雜,使用也比較靈活,是以初學時常會出錯,務請在學習本章内容時十分小心,多思考,多比較,多上機,在實踐中掌握它。

一、指針是什麼?

為了說清楚什麼是指針,必須先弄清楚資料在記憶體中是如何存儲的,又是如何讀取的。

如果在程式中定義了一個變量,在對程式進行編譯時,系統就會給這變量配置設定記憶體單元。

編譯系統根據程式中定義的變量類型,配置設定一定長度的空間。記憶體區的每一個位元組有一個編号 ,這就是 位址 ,它相當于旅館中的房間号。在位址所标志的記憶體單元中存放的資料則相當于旅館房間中居住的旅客。

由于通過位址能夠找到所需的變量單元,可以說, 位址指向該變量單元 。 打個比方,一個房間的門口挂了一個房間号 2008 ,這個2008 就是房間的位址,或者說,2008 指向 該房間。 是以 ,将 位址形象化地稱為指針 。 意思是通過它能找到以它為位址的記憶體單元。

說明: 對計算機存儲單元的通路比旅館要複雜一些,在C語言中,資料是分類型的,對不同類型的資料,在記憶體中配置設定的存儲單元大小(位元組數)和存儲方式是不同的(如整數以補碼形式存放,實數以指數形式存放)。如果隻是指定了位址1010,希望從該單元中調出資料,這是做不到的,雖然能找到所指定的存儲單元,但是,無法确定是從1個位元組中取資訊(字元資料),還是從2個位元組取資訊(短整型),抑或是從4個位元組取資訊(整型)。也沒有說明按何種存儲方式存取資料(整數和單精度實數都是4個位元組,但存儲方式是不同的)。是以,為了有效地存取一個資料,除了需要位置資訊外,還需要有該資料的類型資訊(如果沒有該資料的類型資訊,隻有位置資訊是無法對該資料進行存取的)。C語言中的位址包括位置資訊(記憶體編号,或稱純位址)和它所指向的資料的類型資訊,或者說它是 帶類型的位址 如 &a ,一般稱它為 變量a的位址 ,确切地說, 它是 整型變量a的位址 。後面提到的 位址 ,都是這個意思。

請務必弄清楚存儲單元的 位址 和存儲單元的 内容 這兩個概念的差別 ,假設程式已定義了3個整型變量 i , j , k , 在程式編譯時,系統可能配置設定位址為 2000 ~ 2003 的4個位元組給變量 i , 2004 ~ 2007 , 的4個位元組給 j ,2008 ~2011 的4個位元組給k (不同的編譯系統在不同次的編譯中,配置設定給變量的存儲單元的位址是不相同的),在程式中一般是通過變量名來引用變量的值,例如:

printf(" %d\n",i);

由于在編譯時,系統已為變量 i 配置設定了按整型存儲方式的4個位元組,并建立了變量名和位址的對應表,是以在執行上面語句時,首先通過變量名找到相應的位址,從4個位元組中按照整型資料的存儲方式讀出整型變量 i 的值,然後按十進制整數格式輸出。

注意: 對變量的通路都是通過位址進行的。

假如有輸入語句

scanf(" %d",&i);

在執行時,把鍵盤輸入的值送到位址為2000 開始的整型存儲單元中。如果有語句

k = i + j;

則從2000 ~ 2003 位元組取出 i 的值 (3) ,再從2004 ~2007 位元組取出 j 的值(6) ,将它們相加後再将其和(9) 送到 k 所占用的2008 ~ 2011 位元組單元中。

這種直接按變量名進行的通路,稱為 直接通路 方式。

還可以采用另一種稱為 間接通路 的方式,即将變量 i 的位址存放在另一變量中,然後通過該變量來找到變量 i 的位址 ,進而通路 i 變量。

在C語言程式中,可以定義整型變量、浮點型(實型)變量、字元變量等,也可以定義一種特殊的變量,用它存放位址。假如定義了一個變量 i_pointer (變量名可任意取),用來存放整型變量的位址。可以通過下面語句将 i 的位址(2000)存放到 i_pointer 中。

i_pointer = &i ; //将 i 的位址存放到 i_pointer中

這時,i_pointer 的值就是2000(即變量 i 所占用單元的起始位址)。

要存取 變量 i 的值,即可以用直接通路的方式,也可以采用間接通路的方式:先找到存放 變量 i 的位址 的變量 i_pointer , 從中取出 i

的位址(2000) ,然後到2000 位元組開始的存儲單元中存取 i 的值。

指針的 指向 是通過 位址 來展現的。假設 i_pointer 中的值是變量 i 的位址 (2000) ,這樣就在 i_pointer 和變量 i 之間建立起一種聯系,即通過 i_pointer 能知道 i 的位址,進而找到變量 i 的記憶體單元。

由于通過位址能找到所需的變量單元,是以說,位址 指向 該變量單元(如同說,一個房間号 指向 某一房間一樣)。将位址形象化地稱為 指針 。意思是通過它能找到以它為位址的記憶體單元(如同根據位址 2000 就能找到變量 i 的存儲單元一樣)。

如果有一個變量專門用來存放另一變量的位址(即指針),則它稱為 指針變量 。上述的 i_pointer 就是一個指針變量。指針變量就是位址變量,用來存放位址,指針變量的值是位址 (即指針)。

注意: 區分 指針 和 指針變量 這兩個概念。例如 ,可以說變量 i 的指針是 2000 ,而不能說 i 的指針變量是 2000 ,

指針是一個位址 ,而指針變量是存放位址的變量。

二、指針變量

2.1、怎樣定義指針變量

定義指針變量的一般形式為

類型名 *指針變量名;

如:

int *pointer_1 , *pointer_2;

左端的 int 是在定義指針變量時必須指定的 基類型 。 指針變量的基類型用來指定此指針變量可以指向的變量的類型。

說明: 前面介紹過基本的資料類型(如 int , char , float 等),既然有這些類型的變量,就可以有指向這些類型變量的指針,是以,指針變量是基本資料類型派生出來的類型,它不能離開基本資料類型而獨立存在。

下面都是合法的定義:

float *pointer_3; // pointer_3 是指向float 型變量的指針變量,簡稱 float 指針

char *pointer_4; // pointer_4 是指向字元型變量的指針變量,簡稱 char 指針

可以在定義指針變量時,同時對它初始化,如:

int *pointer_1 = &a , *pointer_2 = &b ; // 定義指針變量 pointer_1 , pointer_2 , 并分别指向 a , b

說明: 在定義指針變量時要注意:

(1) 指針變量前面的 ✳ 表示該變量為指針型變量。指針變量名是 pointer_1 和 pointer_2 ,而不是 ✳pointer_1 , ✳pointer_2 。

這是與定義整型或實型變量的形式不同的。

(2) 在定義指針變量時必須 指定基類型 ,要知道不同類型的資料在記憶體中所占的位元組數和存放方式是不同的。指向一個整型變量和指向一個實型變量,其實體上的含義是不同的。從另一角度分析,指針變量是用來存放位址的,C的位址資訊包括存儲單元的位置(記憶體編号)和類型資訊。指針變量的屬性應與之比對。例如:

int a , ✳p ;

p = &a ;

&a不僅包含變量a的位置(如編号為2000的存儲單元),還包括 存儲的資料是整型 的資訊。現在定義指針變量 p 的基類型為 int ,即它所指向的隻能是整型資料。這時 p 能接收 &a 的資訊。如果改為

float ✳p ;

p = &a ;

&a 是 整型變量 a的位址 在編譯時就會出現一個警告(warning):

把一個 int * 型資料轉換為 float * 資料 。在指派時,編譯系統會把&a 的基類型自動改換為float 型,然後賦給p 。 但是p 不能用這個位址指向整型變量。

從以上可以知道指針或位址是包含有類型資訊的。應該使指派号兩側的類型一緻,以避免出現意外結果。

一個變量的指針的含義包括兩個方面,一是以存儲單元編号表示的純位址(如編号為2000 ),一是它指向存儲單元的資料類型(如int , char , float 等)。

(3) 如何表示指針類型。 指向整型資料的指針類型表示為 int* ,讀作 指向 int 的指針 或 簡稱 int 指針 。

(4) 指針變量中隻能存放位址(指針),不要将一個整數賦給一個指針變量。如:

pointer_1 = 100; // pointer_1 是指針變量,100是整數,不合法

原意是想将位址100 賦給指針變量 pointer_1 , 但是系統無法辨識它是位址,從形式上看100是整常數,而整常數隻能賦給整型變量,而不能賦給指針變量,判為非法。在程式中是不能用一個數值代表位址的,位址隻能用位址符 & 得到并賦給一個指針變量,如 p = &a ;

2.2、怎樣引用指針變量

在引用指針變量時,可能有3種情況:

(1) 給指針變量指派。如:

p = &a ; // 把 a 的位址賦給指針變量p

指針變量 p 的值是變量 a 的位址,p 指向 a 。

(2) 引用指針變量指向的變量。

如果已執行 p = &a ; ,即指針變量 p 指向了整型變量 a ,則

printf(" %d " , *p);

其作用是以整數形式輸出指針變量 p 所指向的變量的值,即變量 a 的值。

如果有以下指派語句:

*p = 1;

表示将整數 1 賦給 p 目前所指向的變量,如果 p 指向變量 a ,則相當于把 1 賦給 a ,即 a = 1 ; 。

(3) 引用指針變量的值。如:

printf(“%o”, p);

作用是以八進制數形式輸出指針變量 p 的值,如果p指向了 a ,就是輸出了 a 的位址,即&a 。

注意: 要熟練掌握兩個有關的運算符。

(1) & 取位址運算符。 &a 是變量 a 的位址 。

(2) * 指針運算符(或稱 間接通路 運算符) ,*p 代表指針變量 p 指向的對象。

2.3、指針變量作為函數參數

函數的參數不僅可以是整型、浮點型、字元型等資料,還可以是指針類型。它的作用是将一個變量的位址傳送到另一個函數中。

為了使在函數中改變了的變量值能被主調函數 main 所用,應該用指針變量作為函數參數,在函數執行過程中使用指針變量所指向的變量值發生變化,函數調用結束後,這些變量值的變化依然保留下來,這樣就實作了 通過調用函數使變量的值發生變化,在主調函數 (如 main 函數)中可以使用這些改變了的值的目的。

如果想通過函數調用得到n個要改變的值,可以這樣做:

① 在主調函數中設 n 個變量,用 n 個指針變量指向它們;

② 設計一個函數,有 n 個指針形參。在這個函數中改變這 n 個形參的值;

③ 在主調函數中調用這個函數,在調用時将這 n 個指針變量作實參,将它們的值,也就是相關變量的位址傳給該函數的形參;

④ 在執行該函數的過程中,通過形參指針變量,改變它們所指向的 n 個 變量的值;

⑤ 主調函數中就可以使用這些改變了值的變量。

注意: 不能企圖通過改變指針形參的值而使指針實參的值改變。

注意: 函數的調用可以(而且隻可以) 得到一個傳回值(即函數值),而使用指針變量作參數,可以得到多個變化了的值。如果不用指針變量是難以做到這一點的。要善于利用指針法。

2.4、數組元素的指針

一個變量有位址,一個數組包含若幹元素,每個數組元素都在記憶體中占用存儲單元,它們都有相應的位址。指針變量既然可以指向變量,當然也可以指向數組元素(把某一進制素的位址放到一個指針變量中)。 所謂數組元素的指針就是數組元素的位址。

可以用一個指針變量指向一個數組元素 。例如:

int a[10] = {0,1,2,3,4,5,6,7,8,9}; // 定義a 為包含10個整型資料的數組

int *p ; // 定義p為指向整型變量的指針變量

p = &a[0]; // 把a[0] 元素的位址賦給指針變量 p

printf(“%d\n”,p[3]) ; // 使用指針通路數組元素,把a[3] 元素的數組列印出來

以上是使指針變量 p 指向 a 數組的第 0 号元素

引用數組元素可以用 下标法 (如 a[3]) ,也可以用 指針法 ,即通過指向數組元素的指針找到所需的元素。使用指針法能使目标程式品質高(占記憶體少,運作速度快)。

在C語言中,數組名(不包括形參組名)代表數組中首元素(即序号為0的元素)的位址。是以,下面兩個語句等價:

p = &a[0]; // p的值是 a[0] 的位址

p = a; // p的值是數組a 首元素(即 a[0])的位址

注意: 程式中的數組名不代表整個數組,隻代表數組首元素的位址。 上述 p = a ; 的作用是 把a數組的首元素的位址賦給指針變量 p ,而不是 把數組 a 各元素的值指派給 p 。

在定義指針變量時可以對它初始化,如:

int *p = &a[0];

它等效于下面兩行:

int *p ;

p = &a[0]; // 不應寫成 *p = &a[0];

當然定義時也可以寫成

int *p = a;

它的作用是将 a 數組首元素(即 a[0])的位址賦給指針變量 p(而不是賦給 *p) 。

三、總結

指針是位址。

通過位址能找到所需的變量單元,可以說, 位址指向該變量單元。

是以,将位址形象化地稱為指針。

繼續閱讀