指針可以指向基本類型,也可以指向復合類型,因此也可以指向另外一個指針變數,稱為指向指針的指針。
int i; int *pi = &i; int **ppi = π
這樣定義之後,表達式*ppi取pi的值,表達式**ppi取i的值。請讀者自己畫圖理解i、pi、ppi這三個變數之間的關係。
很自然地,也可以定義指向“指向指針的指針”的指針,但是很少用到:
int ***p;
數組中的每個元素可以是基本類型,也可以復合類型,因此也可以是指針類型。例如定義一個數組a由10個元素組成,每個元素都是int *指針:
int *a[10];
這稱為指針數組。int *a[10];和int **pa;之間的關係類似於int a[10];和int *pa;之間的關係:a是由一種元素組成的數組,pa則是指向這種元素的指針。所以,如果pa指向a的首元素:
int *a[10]; int **pa = &a[0];
則pa[0]和a[0]取的是同一個元素,唯一比原來複雜的地方在於這個元素是一個int *指針,而不是基本類型。
我們知道main函數的標準原型應該是int main(int argc, char *argv[]);。argc是命令行參數的個數。而argv是一個指向指針的指針,為什麼不是指針數組呢?因為前面講過,函數原型中的[]表示指針而不表示數組,等價于char **argv。那為什麼要寫成char *argv[]而不寫成char **argv呢?這樣寫給讀代碼的人提供了有用信息,argv不是指向單個指針,而是指向一個指針數組的首元素。數組中每個元素都是char *指針,指向一個命令行參數字元串。
例 23.2. 打印命令行參數
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
for(i = 0; i < argc; i++)
printf("argv[%d]=%s\n", i, argv[i]);
return 0;
}編譯執行:
$ gcc main.c $ ./a.out a b c argv[0]=./a.out argv[1]=a argv[2]=b argv[3]=c $ ln -s a.out printargv $ ./printargv d e argv[0]=./printargv argv[1]=d argv[2]=e
注意程序名也算一個命令行參數,所以執行./a.out a b c這個命令時,argc是4,argv如下圖所示:
由於argv[4]是NULL,我們也可以這樣循環遍歷argv:
for(i=0; argv[i] != NULL; i++)
NULL標識着argv的結尾,這個循環碰到NULL就結束,因而不會訪問越界,這種用法很形象地稱為Sentinel,NULL就像一個哨兵守衛着數組的邊界。
在這個例子中我們還看到,如果給程序建立符號連結,然後通過符號連結運行這個程序,就可以得到不同的argv[0]。通常,程序會根據不同的命令行參數做不同的事情,例如ls -l和ls -R打印不同的檔案列表,而有些程序會根據不同的argv[0]做不同的事情,例如專門針對嵌入式系統的開源項目Busybox,將各種Linux命令裁剪後集於一身,編譯成一個執行檔busybox,安裝時將busybox程序拷到嵌入式系統的/bin目錄下,同時在/bin、/sbin、/usr/bin、/usr/sbin等目錄下創建很多指向/bin/busybox的符號連結,命名為cp、ls、mv、ifconfig等等,不管執行哪個命令其實最終都是在執行/bin/busybox,它會根據argv[0]來區分不同的命令。