指針可以指向基本類型,也可以指向復合類型,因此也可以指向另外一個指針變數,稱為指向指針的指針。
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]
來區分不同的命令。