C语言的本质(13)——指向指针的指针


指针可以指向基本类型,也可以指向复合类型,因此一个指针变量存放的可以是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。由于指针变量直接指向变量,所以称为“单级间址”。而如果通过指向指针的指针变量来访问变量则构成“二级间址”。

 


int i;
int *pi = &i;
int **ppi = π

这样定义之后,表达式*ppi取pi的值,表达式**ppi取i的值。

 

 

使用指向指针的指针。

int main(void){
   char *strs[]={"Hello World!","itcast","CPrograme","Linux","Computer desighn"};
   char **p;
   int i;
   for(i=0;i<5;i++){
       p=strs+i;
       printf("%s\n",*p);
    }
}


strs是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。strs是一个数组,它的每一个元素都有相应的地址。数组名name代表该指针数组的首地址。strs +1是strs [i]的地址。strs +1就是指向指针型数据的指针(地址)。还可以设置一个指针变量p,使它指向指针数组元素。P就是指向指针型数据的指针变量。

 

 

之所以定义二级指针**P,不是为了使用**P表示指向的值,而是为了使用*P来存储存储值的那个单元的地址。

 

 

Void get_memory(char **p_str, int num) 
{ 
	*p_str = (char *)malloc(num);
} 
void Test(void) 
{ 
	char *str = NULL; 
	get_memory (&str, 100);
	strcpy(str, "hello world!");
	printf(str);
	free(str);
}

一开始传参数时,存储str的单元地址赋值给P,这样P中单元存储的就是STR的地址,从而,*P 表示的就是STR的值。

 

我们也可以定义指向“指向指针的指针”的指针,但是很少用到:

int ***p;
 

数组中的每个元素可以是基本类型,也可以复合类型,因此也可以是指针类型。例如定义一个数组a由10个元素组成,每个元素都是int *指针:

 

int *a[10];这称为指针数组。int *a[10];和int **pa;之间的关系类似于inta[10];和int *pa;之间的关系:a是由一种元素组成的数组,pa则是指向这种元素的指针。所以,如果pa指向a的首元素:

 

int *a[10];
int **pa = &a[0];

则pa[0]和a[0]取的是同一个元素,唯一比原来复杂的地方在于这个元素是一个int *指针,而不是基本类型。

 

一个指针数组的元素指向数据的简单例子。

int main(void)
{
   static int a[5]={1,3,5,7,9};
   int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
   int **p,i;
   p=num;
for(i=0;i<5;i++)
{
       printf("%d\t",**p);
       p++;
    }
}

说明:指针数组的元素只能存放地址。

 

我们知道main函数的标准原型应该是int main(int argc, char *argv[]);。argc是命令行参数的个数。而argv是一个指向指针的指针,为什么不是指针数组呢?因为前面讲过,函数原型中的[]表示指针而不表示数组,等价于char **argv。那为什么要写成char *argv[]而不写成char **argv呢?这样写给读代码的人提供了有用信息,argv不是指向单个指针,而是指向一个指针数组的首元素。数组中每个元素都是char *指针,指向一个命令行参数字符串。

 

打印命令行参数: 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main(int argc, char *argv[])
{
         inti;
         for(i= 0; i < argc; i++)
                   printf("argv[%d]=%s\n",i, argv[i]);
         return0;
}
 


使用gcc编译并执行:

 

注意程序名也算一个命令行参数,所以执行./a.outa 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]来区分不同的命令。

 

 

 

 

posted on 2014-07-17 15:32  三少爷的剑123  阅读(174)  评论(0编辑  收藏  举报

导航