序言
學習 C 語言的指針既簡單又有趣。通過指針,可以簡化一些 C 編程任務的執行,還有一些任務,如動態內存分配,沒有指針是無法執行的。所以,想要成爲一名優秀的 C 程序員,學習指針是很有必要的。
內存地址
在瞭解c指針之前,我們先來了解下內存地址。計算機程序運行時的數據一般保存在內存(ram)中,爲了存取內存中的數據,系統會對內存進行編址,然後程序就可以按地址存取數據。c語言中,每一個變量都有一個內存位置,每一個內存位置都定義了可使用連字號(&)運算符訪問的地址,它表示了在內存中的一個地址。請看下面的實例,它將輸出定義的變量地址:
#include <stdio.h>
int main ()
{
int var1;
printf("var1 變量的地址: %p\n", &var1 );
return 0;
}
運行結果是:var1 變量的地址: 0x7fff5cc109d4
什麼是指針?
指針是一個變量,其值爲另一個變量的地址,即指針變量的值就是上面說的內存地址。所有指針的值的實際數據類型,不管是整型、浮點型、字符型,還是其他的數據類型,都是一樣的,都是一個代表內存地址的長的十六進制數。訪問指針變量中可用地址的值是通過使用一元運算符 *來返回位於操作數所指定地址的變量的值。
#include <stdio.h>
int main ()
{
int var = 20; /* 實際變量的聲明 */
int *ip; /* 指針變量的聲明 */
ip = &var; /* 在指針變量中存儲 var 的地址 */
printf("Address of var variable: %p\n", &var );
/* 在指針變量中存儲的地址 */
printf("Address stored in ip variable: %p\n", ip );
/* 使用指針訪問值 */
printf("Value of *ip variable: %d\n", *ip );
return 0;
}
運行結果是:
Address of var variable: bffd8b3c
Address stored in ip variable: bffd8b3c
Value of *ip variable: 20
指向指針的指針
現在我們瞭解了內存地址、指針以及它們之間的相互關係之後,下面看看指針當中的一些難點:指向指針的指針。指向指針的指針是一種多級間接尋址的形式,或者說是一個指針鏈。通常,一個指針包含一個變量的地址。當我們定義一個指向指針的指針時,第一個指針包含了第二個指針的地址,第二個指針指向包含實際值的地址。
示例如下:
#include <stdio.h>
int main ()
{
int var;
int *ptr;
int **pptr;
var = 3000;
/* 獲取 var 的地址 */
ptr = &var;
/* 使用運算符 & 獲取 ptr 的地址 */
pptr = &ptr;
/* 使用 pptr 獲取值 */
printf("Value of var = %d\n", var );
printf("Value available at *ptr = %d\n", *ptr );
printf("Value available at **pptr = %d\n", **pptr);
return 0;
}
運行結果是:
Value of var = 3000
Value available at *ptr = 3000
Value available at **pptr = 3000
傳遞指針給函數
C 語言允許傳遞指針給函數,只需要簡單地聲明函數參數爲指針類型即可。例如:
#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 :129445046
從函數返回指針
C 允許您從函數返回指針。爲了做到這點,您必須聲明一個返回指針的函數。另外,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
函數指針
函數指針是指向函數的指針變量。通常我們說的指針變量是指向一個整型、字符型或數組等變量,而函數指針是指向函數。函數指針可以像一般函數一樣,用於調用函數、傳遞參數。
函數指針變量的聲明:
typedef int (*fun_ptr)(int,int); // 聲明一個指向參數和返回值都爲int的函數指針類型
以下實例聲明瞭函數指針變量 p,指向函數 max:
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
/* p 是函數指針 */
int (* p)(int, int) = & max; // &可以省略,這裏和一般的整型指針有點不一樣,賦值整型指針必須有&符號
int a, b, c, d;
printf("請輸入三個數字:");
scanf("%d %d %d", & a, & b, & c);
/* 與直接調用函數等價,d = max(max(a, b), c) */
d = p(p(a, b), c);
printf("最大的數字是: %d\n", d);
return 0;
}
運行結果是:
請輸入三個數字:1 2 3
最大的數字是: 3
函數指針變量可以作爲某個函數的參數來使用的,回調函數就是一個通過函數指針調用的函數。簡單講:回調函數是由別人的函數執行時調用你實現的函數。示例:
實例中 populate_array 函數定義了三個參數,其中第三個參數是函數的指針,通過該函數來設置數組的值。實例中我們定義了回調函數 getNextRandomValue,它返回一個隨機值,它作爲一個函數指針傳遞給 populate_array 函數。populate_array 將調用 10 次回調函數,並將回調函數的返回值賦值給數組。
#include <stdlib.h>
#include <stdio.h>
// 回調函數
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}
// 獲取隨機值
int getNextRandomValue(void)
{
return rand();
}
int main(void)
{
int myarray[10];
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
運行結果是:
16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709
總結:
- 指針指向變量的地址,即指針的值是另一個變量的地址。
- 指針也是變量,所以指針它也有自己的地址,指針自己的地址和它指向的地址是兩個不一樣的地址。
- 獲取指針指向的地址不需要帶*號。
- 獲取指針指向的地址所存取的值需要帶*號。
- 可以有多級指針。
- 調用函數指針不需要帶*號,和一般的基類指針操作不一樣。
- 函數指針是指向函數的指針變量,函數指針可以作爲某個函數的參數。