新手入門C語言第十三章:C指標
C 指標
學習 C 語言的指標既簡單又有趣。通過指標,可以簡化一些 C 程式設計任務的執行,還有一些任務,如動態記憶體分配,沒有指標是無法執行的。所以,想要成為一名優秀的 C 程式設計師,學習指標是很有必要的。
正如您所知道的,每一個變數都有一個記憶體位置,每一個記憶體位置都定義了可使用 & 運算子訪問的地址,它表示了在記憶體中的一個地址。
請看下面的例項,它將輸出定義的變數地址:
例項
#include <stdio.h> int main () { int var_runoob = 10; int *p; // 定義指標變數 p = &var_runoob; printf("var_runoob 變數的地址: %p\n", p); return 0; }
當上面的程式碼被編譯和執行時,它會產生下列結果:
var_runoob 變數的地址: 0x7ffeeaae08d8
通過上面的例項,我們瞭解了什麼是記憶體地址以及如何訪問它。接下來讓我們看看什麼是指標。
什麼是指標?
指標也就是記憶體地址,指標變數是用來存放記憶體地址的變數。就像其他變數或常量一樣,您必須在使用指標儲存其他變數地址之前,對其進行宣告。指標變數宣告的一般形式為:
type *var-name;
在這裡,type 是指標的基型別,它必須是一個有效的 C 資料型別,var-name 是指標變數的名稱。用來宣告指標的星號 * 與乘法中使用的星號是相同的。但是,在這個語句中,星號是用來指定一個變數是指標。以下是有效的指標宣告:
int *ip; /* 一個整型的指標 */
double *dp; /* 一個 double 型的指標 */
float *fp; /* 一個浮點型的指標 */
char *ch; /* 一個字元型的指標 */
所有實際資料型別,不管是整型、浮點型、字元型,還是其他的資料型別,對應指標的值的型別都是一樣的,都是一個代表記憶體地址的長的十六進位制數。
不同資料型別的指標之間唯一的不同是,指標所指向的變數或常量的資料型別不同。
如何使用指標?
使用指標時會頻繁進行以下幾個操作:定義一個指標變數、把變數地址賦值給指標、訪問指標變數中可用地址的值。這些是通過使用一元運算子 * 來返回位於運算元所指定地址的變數的值。下面的例項涉及到了這些操作:
例項
#include <stdio.h>
int main ()
{
int var = 20; /* 實際變數的宣告 */
int *ip; /* 指標變數的宣告 */
ip = &var; /* 在指標變數中儲存 var 的地址 */
printf("var 變數的地址: %p\n", &var );
/* 在指標變數中儲存的地址 */
printf("ip 變數儲存的地址: %p\n", ip );
/* 使用指標訪問值 */
printf("*ip 變數的值: %d\n", *ip );
return 0;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
var 變數的地址: 0x7ffeeef168d8
ip 變數儲存的地址: 0x7ffeeef168d8
*ip 變數的值: 20
C 中的 NULL 指標
在變數宣告的時候,如果沒有確切的地址可以賦值,為指標變數賦一個 NULL 值是一個良好的程式設計習慣。賦為 NULL 值的指標被稱為空指標。
NULL 指標是一個定義在標準庫中的值為零的常量。請看下面的程式:
例項
#include <stdio.h>
int main ()
{
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
ptr 的地址是 0x0
在大多數的作業系統上,程式不允許訪問地址為 0 的記憶體,因為該記憶體是作業系統保留的。然而,記憶體地址 0 有特別重要的意義,它表明該指標不指向一個可訪問的記憶體位置。但按照慣例,如果指標包含空值(零值),則假定它不指向任何東西。
如需檢查一個空指標,您可以使用 if 語句,如下所示:
if(ptr) /* 如果 p 非空,則完成 */
if(!ptr) /* 如果 p 為空,則完成 */
C 指標詳解
在 C 中,有很多指標相關的概念,這些概念都很簡單,但是都很重要。下面列出了 C 程式設計師必須清楚的一些與指標相關的重要概念:
一、指標的算術運算:可以對指標進行四種算術運算:++、--、+、-
C 指標是一個用數值表示的地址。因此,您可以對指標執行算術運算。可以對指標進行四種算術運算:++、--、+、-。
假設 ptr 是一個指向地址 1000 的整型指標,是一個 32 位的整數,讓我們對該指標執行下列的算術運算:
ptr++
在執行完上述的運算之後,ptr 將指向位置 1004,因為 ptr 每增加一次,它都將指向下一個整數位置,即當前位置往後移 4 位元組。這個運算會在不影響記憶體位置中實際值的情況下,移動指標到下一個記憶體位置。如果 ptr 指向一個地址為 1000 的字元,上面的運算會導致指標指向位置 1001,因為下一個字元位置是在 1001。
我們概括一下:
- 指標的每一次遞增,它其實會指向下一個元素的儲存單元。
- 指標的每一次遞減,它都會指向前一個元素的儲存單元。
- 指標在遞增和遞減時跳躍的位元組數取決於指標所指向變數資料型別長度,比如 int 就是 4 個位元組。
遞增一個指標
我們喜歡在程式中使用指標代替陣列,因為變數指標可以遞增,而陣列不能遞增,陣列可以看成一個指標常量。下面的程式遞增變數指標,以便順序訪問陣列中的每一個元素:
例項
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
/* 指標中的陣列地址 */
ptr = var;
for ( i = 0; i < MAX; i++)
{
printf("儲存地址:var[%d] = %p\n", i, ptr );
printf("儲存值:var[%d] = %d\n", i, *ptr );
/* 指向下一個位置 */
ptr++;
}
return 0;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
儲存地址:var[0] = e4a298cc
儲存值:var[0] = 10
儲存地址:var[1] = e4a298d0
儲存值:var[1] = 100
儲存地址:var[2] = e4a298d4
儲存值:var[2] = 200
遞減一個指標
同樣地,對指標進行遞減運算,即把值減去其資料型別的位元組數,如下所示:
例項
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i, *ptr;
/* 指標中最後一個元素的地址 */
ptr = &var[MAX-1];
for ( i = MAX; i > 0; i--)
{
printf("儲存地址:var[%d] = %p\n", i-1, ptr );
printf("儲存值:var[%d] = %d\n", i-1, *ptr );
/* 指向下一個位置 */
ptr--;
}
return 0;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
儲存地址:var[2] = 518a0ae4
儲存值:var[2] = 200
儲存地址:var[1] = 518a0ae0
儲存值:var[1] = 100
儲存地址:var[0] = 518a0adc
儲存值: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("儲存地址:var[%d] = %p\n", i, ptr );
printf("儲存值:var[%d] = %d\n", i, *ptr );
/* 指向上一個位置 */
ptr++;
i++;
}
return 0;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
儲存地址:var[0] = 0x7ffeee2368cc
儲存值:var[0] = 10
儲存地址:var[1] = 0x7ffeee2368d0
儲存值:var[1] = 100
儲存地址:var[2] = 0x7ffeee2368d4
儲存值:var[2] = 200
二、指標陣列:可以定義用來儲存指標的陣列。
在我們講解指標陣列的概念之前,先讓我們來看一個例項,它用到了一個由 3 個整陣列成的陣列:
例項
#include <stdio.h>
const int MAX = 3;
int main ()
{
int var[] = {10, 100, 200};
int i;
for (i = 0; i < MAX; i++)
{
printf("Value of var[%d] = %d\n", i, var[i] );
}
return 0;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
可能有一種情況,我們想要讓陣列儲存指向 int 或 char 或其他資料型別的指標。下面是一個指向整數的指標陣列的宣告:
int *ptr[MAX];
在這裡,把 ptr 宣告為一個數組,由 MAX 個整數指標組成。因此,ptr 中的每個元素,都是一個指向 int 值的指標。下面的例項用到了三個整數,它們將儲存在一個指標陣列中,如下所示:
例項
#include <stdio.h> const int MAX = 3; int main () { int var[] = {10, 100, 200}; int i, *ptr[MAX]; for ( i = 0; i < MAX; i++) { ptr[i] = &var[i]; /* 賦值為整數的地址 */ } for ( i = 0; i < MAX; i++) { printf("Value of var[%d] = %d\n", i, *ptr[i] ); } return 0; }
當上面的程式碼被編譯和執行時,它會產生下列結果:
Value of var[0] = 10
Value of var[1] = 100
Value of var[2] = 200
您也可以用一個指向字元的指標陣列來儲存一個字串列表,如下:
例項
#include <stdio.h>
const int MAX = 4;
int main ()
{
const char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
int i = 0;
for ( i = 0; i < MAX; i++)
{
printf("Value of names[%d] = %s\n", i, names[i] );
}
return 0;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
Value of names[0] = Zara Ali
Value of names[1] = Hina Ali
Value of names[2] = Nuha Ali
Value of names[3] = Sara Ali
三、指向指標的指標:C 允許指向指標的指標。
指向指標的指標是一種多級間接定址的形式,或者說是一個指標鏈。通常,一個指標包含一個變數的地址。當我們定義一個指向指標的指標時,第一個指標包含了第二個指標的地址,第二個指標指向包含實際值的位置。
一個指向指標的指標變數必須如下宣告,即在變數名前放置兩個星號。例如,下面聲明瞭一個指向 int 型別指標的指標:
int **var;
當一個目標值被一個指標間接指向到另一個指標時,訪問這個值需要使用兩個星號運算子,如下面例項所示:
例項
#include <stdio.h>
int main ()
{
int V;
int *Pt1;
int **Pt2;
V = 100;
/* 獲取 V 的地址 */
Pt1 = &V;
/* 使用運算子 & 獲取 Pt1 的地址 */
Pt2 = &Pt1;
/* 使用 pptr 獲取值 */
printf("var = %d\n", V );
printf("Pt1 = %p\n", Pt1 );
printf("*Pt1 = %d\n", *Pt1 );
printf("Pt2 = %p\n", Pt2 );
printf("**Pt2 = %d\n", **Pt2);
return 0;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
var = 100
Pt1 = 0x7ffee2d5e8d8
*Pt1 = 100
Pt2 = 0x7ffee2d5e8d0
**Pt2 = 100
四、傳遞指標給函式:通過引用或地址傳遞引數,使傳遞的引數在呼叫函式中被改變。
C 語言允許您傳遞指標給函式,只需要簡單地宣告函式引數為指標型別即可。
下面的例項中,我們傳遞一個無符號的 long 型指標給函式,並在函式內改變這個值:
例項
#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;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
Number of seconds :1294450468
能接受指標作為引數的函式,也能接受陣列作為引數,如下所示:
例項
#include <stdio.h>
/* 函式宣告 */
double getAverage(int *arr, int size);
int main ()
{
/* 帶有 5 個元素的整型陣列 */
int balance[5] = {1000, 2, 3, 17, 50};
double avg;
/* 傳遞一個指向陣列的指標作為引數 */
avg = getAverage( balance, 5 ) ;
/* 輸出返回值 */
printf("Average value is: %f\n", avg );
return 0;
}
double getAverage(int *arr, int size)
{
int i, sum = 0;
double avg;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = (double)sum / size;
return avg;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
Average value is: 214.40000
五、從函式返回指標:C 允許函式返回指標到區域性變數、靜態變數和動態記憶體分配。
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;
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
1523198053
1187214107
1108300978
430494959
1421301276
930971084
123250484
106932140
1604461820
149169022
*(p + [0]) : 1523198053
*(p + [1]) : 1187214107
*(p + [2]) : 1108300978
*(p + [3]) : 430494959
*(p + [4]) : 1421301276
*(p + [5]) : 930971084
*(p + [6]) : 123250484
*(p + [7]) : 106932140
*(p + [8]) : 1604461820
*(p + [9]) : 149169022
搜尋
複製