C语言笔记(四)--- 指针


第十章 指针

1. 地址指针的基本概念:

在计算机中,所有的数据都是存放在存储器中的。
一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型占2个单元,字符型占1个单元等。
为了正确访问这些内存单元,必须为每个内存单元编上号。
根据一个内存单元的编号即可准确地找到该内存单元。
内存单元的编号也叫做地址。
既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。
内存单元的指针和内存单元的内容是两个不同的概念。
在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。
因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。

2. 变量的指针和指向变量的指针变量:

变量的指针就是变量的地址。
存放变量地址的变量是指针变量。
即在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。
因此,一个指针变量的值就是某个变量的地址或称为某变量的指针。
为了表示指针变量和他所指向的变量之间的关系,在程序中用 "*" 符号表示“指向”,例如,i_pointer 代表指针变量,而 *i_pointeri_pointer 所指向的变量。
因此下面两个语句作用相同:

i=3;
*i_pointer=3;
// 第二个语句的含义是将3赋给指针变量i_pointer所指向的变量。

3. 定义一个指针变量:

对指针变量的定义包括三个内容:

  • 1)指针类型说明,即定义变量为一个指针变量;
  • 2)指针变量名;
  • 3)变量值(指针)所指向的变量的数据类型。

其一般形式为:
类型说明符 *变量名;

其中,* 表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。例如:
int *p1; 表示p1是一个指针变量,他的值是某个整型变量的地址。
或者说p1指向一个整型变量。
至于p1究竟指向哪一个整型变量, 应由向p1赋予的地址来决定。

注意:一个指针变量只能指向同类型的变量,如p3只能指向浮点变量,不能时而指向一个浮点变量,时而又指向一个字符变量。

4. 指针变量的引用:

指针变量在使用前,需要先声明,并且赋值,否则将造成系统混乱,甚至死机。
指针变量的赋值只能赋予地址,绝不能赋予任何其他数据,否则将引起错误。
在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。

两个有关的运算符:

  • 1)& : 取地址运算符。
  • 2)* : 指针运算符(或称“间接访问”运算符)

指针变量初始化的方式:

  • 1)
 int a;
 int *p=&a;
  • 2)
 int a;
 int *p;
 p=&a;

不允许把一个数赋予指针变量,故下面的赋值是错误的:

int *p;
p=1000;

被赋值的指针变量前不能再加 "*" 说明符,如 *p=&a 也是错误的。

请对下面的关于 “&" 和 "*" 的问题进行考虑:

    1. 如果已经执行了 pointr_1=&a; 语句,则 &*pointer_1 是什么含义? 指代 pointer_1 自己,即变量a的地址
  • 2)*&a 含义是什么? 指代a自己,即变量a的值。
  • 3)(*pointer_1)++*pointer_1++ 的区别?

5. 指针变量做为函数参数:

例题:输入的两个整数按大小顺序输出。

swap(int *p1,int *p2) 
{ int temp;
  temp=*p1;
  *p1=*p2;
  *p2=temp;
}
main(){
int a,b,*pointer_1,*pointer_2;
a=5;b=10;
pointer_1=&a;
pointer_2=&b;
if (a<b) swap(pointer_1,pointer_2);
}

对程序的说明:
swap是用户定义的函数,他的作用是交换两个变量(a和b)的值。
swap函数的形参p1和p2是指针变量。
注意实参pointer_1和pointer_2是指针变量,在函数调用时,将实参变量的值传递给形参变量。
采取的依然是“值传递”方式。
因此虚实结合后形参p1的值为&a,p2的值为&b。
这时p1和pointer_1指向变量a,p2和pointer_2指向变量b.
接着执行swap函数的函数体是 *p1*p2 的值互换,也就是使a和b的值互换。
函数调用结束后,p1和p2不复存在。
但是pointer_1和pointer_2所指向的变量a和b的值已经互换。

如果swap函数如下则达不到这个效果:

swap(int x,int y)
{ int temp;
  temp=x;
  x=y;
  y=temp;
}

请注意,不能企图通过改变指针形参的值而使指针实参的值改变。

swap(int *p1,int *p2){
 int *p;
 p=p1;
 p1=p2;
 p2=p;
}

上面说的,只要是理解了指针,都不算什么难理解的内容。

6. 指针变量几个问题的进一步说明:

指针变量可以进行某些运算,但其运算的种类是有限的。
他只能进行赋值运算和部分算数运算及关系运算。

  • 1)指针运算符:

    • a)取地址运算符 & : 取地址运算符 & 是单目运算符,其结合性为自右至左,其功能是取变量的地址。
      在scanf函数及前面介绍指针变量赋值中,我们已经了解并使用了 & 运算符。
    • b)取内容运算符 * : 取内容运算符 * 是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。
      * 运算符之后跟的变量必须是指针变量。
      需要注意的是,指针运算符*指针变量说明中的说明符* 不是一回事。
  • 2)指针变量的运算:

    • a)赋值运算:有以下几种形式:
      i)指针变量初始化赋值,前面已经作了介绍。
      ii)把一个变量的地址赋予指向相同数据类型的指针变量。例如:int a,*pa;pa=&a;
      iii)把一个指针变量的值赋予指向相同类型变量的另一个指针变量。如:int a*pa=&a,*pb; pb=pa;
      iv)把数组的首地址赋予指向数组的指针变量:例如:int a[5],*pa=a; 也可以写为 *pa=&a[0]; 因为a=&a[0]; 即数组名就是数组的首地址
      v)把字符串的首地址赋予指向字符类型的指针变量。例如:char *pc="C Language"; 这里应说明的是并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量
      vi)把函数的入口地址赋予指向函数的指针变量。例如:int (*pf)(); pf=f;
    • b)加减算数运算:对于指向数组的指针变量,可以加上或减去一个整数n。表示把指针向前或向后移动一个位置,和地址加1或减1在概念上是不同的。 指针变量的减运算只能对数组指针变量进行,对指向其他类型变量的指针变量做加减运算是毫无意义的。
    • c)两个指针变量之间的运算:
      只有指向同一数组的两个指针变量才能进行运算,否则运算毫无意义。
      i)两指针变量相减:所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减只差再除以该数组元素的长度(字节数)。两指针指针变量不能进行加法运算,因为没有实际含义。
      ii)两指针变量进行关系运算:指向同一数组的两指针变量的关系运算可表示他们所指数组元素之间的关系。
      例如:
      pf1==pf2表示pf1和pf2指向同一数组元素
      pf1>pf2表示pf1处于高地址位置
      指针变量还可以与0比较。
      设n为指针变量,则 n==0 表示n是空指针,他不指向任何变量。p!=0 表示p不是空指针。
      例如:
#define NULL 0
int *p=NULL;
  对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针赋0值后,则可以使用,只是他不指向任何变量而已。

7. 数组指针和指向数组的指针变量

(1)通过指针引用数组元素

如果p的初值为 &a[0] ,则:

  • a)p+ia+i 就是 a[i] 的地址,或者说他们指向a数组的第i个元素。
  • b)*(p+i)*(a+i) 就是 p+ia+i 所指向的数组元素,即 a[i]
  • c)指向数组的指针变量也可以带下标,如 p[i]*(p+i) 等价。类似于 a[i] ?

根据以上叙述,引用一个数组元素可以用:

  • a)下标法:a[i]p[i]
  • b) 指针法:即 *(a+i)*(p+i)

几个注意的问题:

  • a)指针变量可以实现本身的值的改变,如 p++ 是合法的,而 a++ 是错误的,因为a是地址常量。
  • b)要注意指针变量的当前值。
  • c)虽然定义数组时指定它包含10个元素,但指针变量可以指到数组以后的内存单元,系统并不认为违法。
  • d)*p++ ,由于 ++* 同优先级,结合方向自右至左,等价于 *(p++)
  • e)*(p++)*(++p) 作用不同。若p的初值为a,则 *(p++) 等价于 a[0]*(++p) 等价 a[1]
  • f)(*p)++ 表示p所指向的元素值加1.
  • g)如果p当前指向a数组中的第i个元素,则
    *(p--) 相当于 a[i--];
    *(++p) 相当于 a[++i];
    *(--p) 相当于 a[--i]

8. 数组名作为函数参数:

函数定义:

f(int arr[],int n){
...
}

函数调用:

int array[10];
f(array,10);

数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同一数组。
这就好像同一件物品有两个彼此不同的名称一样。
同样,指针变量的值也是地址,数组指针变量的值即为数组的首地址,当然也可以做为函数的参数使用。
例如:

float aver(float *pa);
main() {
 float sco[5],av,*sp;
 int i;
 sp=sco;
 ...
 av=aver(sp);
}

float aver(float *pa) {
 int i;
 float av,s=0;
 for (i=0;i<5;i++) s=s+*pa++;
 av=s/5;
 return av;
}

例题:将数组a中的n个整数按相反顺序存放算法为:将 a[0]a[n-1] 对换,再 a[1]a[n-2] 对换,直到将 a[(n-1)/2]a[n-int((n-1)/2)] 对换。
今用循环处理此问题,设两个“位置指示变量” i 和 j,i 的初值为 0,j 的初值为 n-1.将 a[i]a[j] 交换,然后是i的值加1,j的值减1,再将 a[i]a[j] 交换,直到 i=(n-1)/2 为止。
程序如下:

void inv(int x[],int n) {
 int temp,i,j,m=(n-1)/2;
 for (i=0;i<=m;i++)  {
   j=n-1-i;
   temp=x[i];x[i]=x[j];x[j]=temp;
 }
 return;
}

对此程序可以做一些改动,将 函数inv 中的 形参x 改成指针变量。

void inv(int *x,int n) {
 int *p,temp,*i,*j,m=(n-1)/2;
 i=x;j=x+n-1;p=x+m;
 for(;i<p;i++,j--) {
  temp=*i;*i=*j;*j=temp;
 }
 return;
}

从n个数中找出其中最大值和最小值
调用一个函数只能得到一个返回值,今用全局变量在函数之间“传递”数据。

int max,min;
void max_min_value(int array[],int n) {
 int *p,*array_end;
 array_end=array+n;
 max=min=*array;
 for(p=array+1;p<array_end;p++)
   if (*p>max) max=*p;
   else if (*p<min) min=*p;
 return;
}

main() {
 int i,number[10];
 printf("enter 10 integer numbers:\n");
 for (i=0;i<10;i++) {
  scanf("%d",&number[i]);
 }
 max_min_value(number,10);
 printf("\nmax=%d,min=%d\n",max,min);
}

9. 指向多维数组的指针和指针变量

(1)多维数组的地址

设有整型二维数组 a[3][4] 如下:

0 1 2 3
4 5 6 7
8 9 10 11

他的定义为

int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};

设数组a的首地址为1000,各下标变量的首地址为:

1000 1002 1004 1006
1008 1010 1012 1014
1016 1018 1020 1022

前面介绍过,C语言允许把一个二维数组分解为多个一维数组来处理。
因此数组a可分解为三个一维数组,即a[0],a[1],a[2]。
每一个一维数组又含有4个元素。
从二维数组的角度来看,a是二维数组名,a代表整个二维数组的首地址,也就是二维数组0行的首地址,等于1000。
a+1 代表第一行的首地址,等于1008。
a[0]是第一个一维数组的数组名和首地址,因此也为1000。*(a+0)*a 是与 a[0] 等效的,他表示一维数组a[0] 0号元素的首地址,也为1000.
&a[0][0] 是二维数组a的0行0列元素首地址,同样是1000.
因此,a a[0] *(a+0) &a[0][0] 是相等的。

同理,a+1 是二维数组1行的首地址,等于1008.
a[1] 是第二个一维数组的数组名和首地址,因此也是1008.
&a[1][0] 是二维数组a的1行0列元素地址,也是1008.
因此 a+1 a[1] *(a+1) &a[1][0] 是等同的。

由此可以得出:a+i , a[i] , *(a+i) , &a[i][0] 是等同的(这里注意,a+i= *(a+i)
另外,a[0] 也可以看成是 a[0]+0 ,是一维数组a[0]的0号元素的首地址,而 a[0]+1 则是a[0]的1号元素首地址,由此可得出 a[i]+j 则是一维数组 a[i] 的 j号元素 的首地址,他等于&a[i][j] 。(这里有疑问了,a+i是第i行的一维数组的首地址,a+i+j 呢?)
a[i] = *(a+i)a[i]+j=*(a+i)+j . 由于 *(a+i)+j 是 二维数组a 的 i行j列 元素的首地址,所以,该元素的值等于 *(*(a+i)+j)

(2)指向多维数组的指针变量

把二维数组a分解为一位数字 a[0],a[1],a[2] 之后,设p为指向二维数组的指针变量。
可定义为:

int (*p)[4];

他表示p是一个指针变量,他指向包含4个元素的一维数组。
若指向第一个一维数组a[0],其值等于a,a[0],或&a[0][0]等。
而p+i则指向一维数组a[i]。
从前面的分析可得出 *(p+i)+j 是二维数组i行j列的元素的地址,而 *(*(p+i)+j) 则是 i行j列 元素的值。

二维数组指针变量说明的一般形式为:
类型说明符 (*指针变量名)[长度]

其中,
"类型说明符"为所指数组的数据类型,
* 表示其后的变量是指针类型。
“长度”表示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。
应注意 () 不能少,如缺少括号则表示是指针数组,意义就完全不同了。

例子:

main() {
 int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
 int (*p)[4];
 int i,j;
 p=a;
 for (i=0;i<3;i++) {
    for (j=0;j<4;j++) printf("%2d  ",*(*(p+i)+j));
    printf("\n";
 }
}

10. 字符串的指针指向字符串的指针变量

(1)字符串的表示形式:

在C语言中,可以用两种方式访问一个字符串。

  • a)用字符数组存放一个字符串,然后输出该字符串。
main(){
 char string[]="I love China!";
 printf("%s\n",string);
}
  • b)用字符串指针指向一个字符串,
main()
{char *string="I love China!";
 printf("%s\n",string);
}

字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的。
只能按对指针变量的赋值不同来区别。
对指向字符变量的指针变量应赋予字符变量的地址。
例如:

char c,*p=&c;
// 表示p是一个指向字符变量c的指针变量。


char *s="C language";
// 则表示s是一个指向字符串的指针变量。把字符串的首地址赋予s。

例子:输出字符串中n个字符后的所有字符。

main() {
 char *ps="this is a book";
 int n=10;
 ps=ps+n;
 printf("%s\n",ps);
}

例子2:在输入的字符串中查找有无'k'字符

main() {
 char st[20],*ps;
 int i;
 printf("input a string:\n");
 ps=st;
 scanf("%s",ps);
 for(i=0;ps[i]!='\0';i++)
  if(ps[i]=='k') {
   printf("there is a 'k' in the string \n");
   break;
  }
 if (ps[i]=='\0') printf("There is no 'k' in the string\n");
}

例子3:本例是将指针变量指向一个格式字符串,用在printf函数中,用于输出二维数组的各种地址表示的值。但在printf语句中用指针变量PF代替了格式串。这也是程序中常用的方法。

main() {
 static int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
 char *PF;
 PF="%d,%d,%d,%d,%d\n";
 printf(PF,a+1,*(a+1),a[1],&a[1],&a[1][0]);
}

例子4:本例是把字符串指针做为函数参数的使用。
要求把一个字符串的内容复制到另一个字符串中,并且不能使用 strcpy函数
。函数cprstr的形参为两个字符指针变量。
pss指向源字符串,pds指向目标字符串。
注意表达式:(*pds=*pss)!='\0' 的用法。

cpystr(char *pss,char *pds) {
 while ((*pds=*pss)!='\0') {
   pds++;
   pss++;
 }
}

main() {
 char *pa="CHINA",b[10],*pb;
 pb=b;
 cpystr(pa,pb);
 printf("string a=%s\nstring b=%s\n",pa,pb);
}

函数还可以简写为:

cpystr(char *pss,char *pds) { 
  while ((*pds++=*pss++)!='\0') ; 
}

注意到 '\0' 的ASCII码为0,对于while语句只看表达式的值为非0就循环,为0则结束循环,因此也可省去 "!='\0'" 这一判断部分。而写为:

cpystr(char *pss,char *pds) { 
  while(*pds++=*pss++) ;
}

表达式的意义可解释为:源字符向目标字符赋值,移动指针,若所赋值为非0则循环,否则结束循环。

11. 使用字符串指针变量与字符数组的区别:

char *ps; ps="C Language"; 正确
char st[20];st={"C Language"}; 错误; 

即指针变量可以先声明再赋值,字符数组则不可以,只能逐个赋值。

12. 函数指针变量:

在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。
我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。
然后通过指针变量就可以找到并调用这个函数。
我们把这种指向函数的指针变量称为“函数指针变量”。
函数指针变量定义的一般形式为: 类型说明符 (*指针变量名)();
其中
“类型说明符”表示被指函数的返回值的类型。
(*指针变量名) 表示 * 后面的变量是定义的指针变量。
最后的空括号表示指针变量所指的是一个函数。

例如:int (*pf)(); 表示pf是一个指向函数入口的指针变量,该函数的返回值是整型。
例子:

int max(int a,int b) {
 if (a>b) return a;
 else return b;
}

main() {
 int max(int a,int b);
 int (*pmax)();
 int x,y,z;
 pmax=max;
 printf("input two numbers:\n");
 scanf("%d%d",&x,&y);
 z=(*pmax)(x,y);
 printf("maxmum=%d",z);
}

从上面的例子可以看出:

  • 1)先定义函数指针变量: int (*pmax)();
  • 2)赋值: pmax=max;
  • 3)调用:z=(*pmax)(x,y);

使用函数指针变量应注意以下两点:

  • 1)函数指针变量不能进行算数运算,这是与数组指针变量不同的。
  • 2)函数调用中 (*指针变量名) 的两边的括号不能少,其中的 * 不应该理解为求值运算,在此处他只是一种表示符号。

13. 指针型函数

在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。
定义指针型函数的一般形式为:

类型说明符 *函数名(形参表) {
  ...
}

其中函数名之前加了 * 号表明这是一个指针型函数,即返回值是一个指针。
如:int *ap(int x, int y) { ... }

例子:本程序是通过指针函数,输入一个1-7之间的整数,输出对应的星期名:

main() {
 int i;
 char *day_name(int n);
 printf("intput Day No:\n");
 scanf("%d",&i);
 if (i<0) exit(1);
 printf("Day No:%2d-->%s\n",i,day_name(i));
}

char *day_name(int n) {
 static char *name[]={"Illegal day","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
 return ((n<1)||n>7)?name[0]:name[n]);
}

应该特别注意的是函数指针变量和指针型函数这两者在写法和意义上的区别。
int (*p)()int *p() 是两个完全不同的量。
前者是函数指针变量说明,说明p是一个指向函数入口的指针变量,该函数的返回值是整型量, (*p) 的两边的括号不能少。
后者 int *p() 则不是变量说明,而是函数说明,说明p是一个指针型函数。

14. 指针数组和指向指针的指针:

(1)指针数组的概念

一个数组的元素值为指针则是指针数组。
指针数组是一组有序的指针的集合。
指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。
指针数组说明的一般形式为:

类型说明符 *数组名[数组长度]

例如: int *pa[3]; 表示pa是一个指针数组,他有3个数组元素,每个元素值都是一个指针,指向整型变量。
例子:通常可用一个指针数组来指向一个二维数组。
指针数组中的每个元素被赋予二维数组第一行的首地址,因此也可理解为指向

一个一维数组。

main() {
 int a[3][3]={1,2,3,4,5,6,7,8,9};
 int *pa[3]={a[0],a[1],a[2]};
 int *p=a[0];
 int i;
 for (i=0;i<3;i++) 
   printf("%d,%d,%d\n",a[i][2-i],*a[i],*(*(a+i)+i));

 for (i=0;i<3;i++)
   printf(%d,%d,%d\n",*pa[i],p[i],*(p+i));
}

应该注意到指针数组和二维数组指针变量的区别。
这两者虽然都可以用来表示二维数组,但是其表示方法和意义是不同的。
二维数组指针变量是单个的变量,其一般形式中 (*指针变量名) 两边的括号不能少。
而指针数组类型表示的是多个指针(一组有序指针)在一般形式中 *指针数组名 两边不能有括号。
例如:
int (*p)[3]; 表示一个指向二维数组的指针变量。该二维数组的列数为3或分解为一维数组的长度为3;
int *p[3] 表示p是一个指针数组,有三个下标变量p[0] p[1] p[2]均为指针变量。

指针数组也常用来表示一组字符串,这时指针数组的每个元素被赋予一个字符串的首地址。
指向字符串的指针数组的初始化更为简单,例如:

static char *name[]={"Illegal day","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};

指针数组也可以用作函数参数
例子:指针数组作指针型函数的参数。
在本例函数中,定义了一个指针数组name,并对name作了初始化赋值。
其每个元素都指向一个字符串。
然后又以name做为实参调用指针型函数day_name,在调用时把数组名name赋予形参变量name,输入的整数i做为第2个实参赋予形参n。
在day_name函数中定义了两个指针变量pp1和pp2,pp1被赋予name[0]的值(即 *name ),pp2被赋予name[n]的值,即*(name+n)
由条件表达式决定返回pp1或pp2指针给主函数中的指针变量ps。
最后输出i和ps的值。

main() {
 static char *name[]={"Illegal day","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
 char *ps;
 int i;
 char *day_name(char *name[],int n);
 printf("input Day No:\n");
 scanf("%d",&i);
 if (i<0) exit(1);
 ps=day_name(name,i);
 printf("Day No:%2d-->%s",i,ps);
}

char *day_name(char *name[],int n) {
 char *pp1,*pp2;
 pp1=*name;pp2=*(name+n);
 return ((n<1||n>7)?pp1:pp2);
}

例子:输入5个国名并按字母顺序排列后输出:
现编程如下:

#include "string.h"
main() {
 void sort(char *name[],int n);
 void print(char *name[],int n);
 static char *name[]={"CHINA","AMERICA","AUSTRALIA","FRANCE","GERMAN"};
 int n=5;
 sort(name,n);
 print(name,n);
}

void sort(char *name[],int n){
 char *pt;
 int i,j,k;
 for (i=0;i<n-1;i++) {
  k=i;
  for (j=i+1;j<n;j++)
   if(strcmp(name[k],name[j])>0) k=j;

  if (k!=i) {
   pt=name[i];
   name[i]=name[k];
   name[k]=pt;
  }
 }
}

void print(char *name[],int n){
 int i;
 for (i=0;i<n;i++) printf("%s \n",name[i]);
}

(2)指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
前面已经介绍过,通过指针访问变量称为间接访问。
由于指针变量直接指向变量,所以称为“单级间址”。
而如果通过指向指针的指针变量来访问变量则构成“二级间址”。

怎样定义一个指向指针型数据的指针变量呢?如下:

char **p;
// p前面有2个*号,相当于*(*p)。

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

如果有:

p=name+2;
printf("%o\n",*p);
printf("%s\n",*p);

则第一个printf函数输出name[2]的值(他是一个地址),
第二个printf函数语句子字符串形式(%s)输出字符串"Great WALL";

例子:使用指向指针的指针

main() {
 char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};
 char **p;
 int i;
 for (i=0;i<5;i++) {
  p=name+i;
  printf("%s\n",*p);
 }
}

13. main函数的参数

C语言规定main函数的参数只能有两个,习惯上把这两个参数写为 argcargv
因此,main函数的函数头可写为:main(argc,argv)

C语言还规定 argc(第一个形参)必须是整型变量,argv(第二个形参)必须是指向字符串的指针数组。
加上形参说明后,main函数的函数头应写为:

main(int argc,char *argv[])

main的参数通过命令行传入,但是应特别注意的是,main 的两个形参和命令行中的参数在位置上不是一一对应的。
因为,main的形参只有两个,而命令行中的参数个数原则上未加限制。
argc参数 表示了命令行中参数的个数(注意:文件名本身也算一个参数),argc 的值是在输入命令行时由系统按实际参数的个数自动赋予的。
例如:C:\E24 BASIC foxpro FORTRAN 连文件名本身共4个参数,所以 argc 的值是4.
argv参数 是字符串指针数组,其各元素值为命令行中各字符串的首地址。
指针数组的长度即为参数的个数。
其中 argv[0] 为文件名本身。
例子:

main(int argc,char *argv) {
 while(argc-->1)
  printf("%s\n",*++argv);
}

14. 有关指针的数据类型和指针运算的小结:

(1)有关指针的数据类型的小结:

int i; int *p; // 定义整型变量i和指向整型数据的指针变量
int a[n];int *p[n]; // 定义整型数组a;定义指针数组p
int (*p)[n]; // 定义p为指向含n个元素的一维数组的指针变量
int f();int *p();int (*p)(); // 定义函数;定义返回整型指针变量的函数;定义指向函数的指针。
int **p; // 定义指向指针的指针

(2)指针运算的小结:

  • a)指针变量加(减):将指针变量的原值(是一个地址)和他指向的变量所占用的内存单元字节数加(减)。

  • b)指针变量赋值:p=&a;p=array ; p=&array[i] ; p=array+i ; p=max (max为已定义的函数);p1=p2 ;
    注意:p=1000; //错误!

  • c)指针变量可以有空值。即该指针不指向任何变量:p=NULL (NULL为#define的值,为0);

  • d)两个指针变量可以相减:表示两个指针之间的元素个数。

  • e)两个指针变量比较:

(3)void指针类型

ANSI新标准增加了一种 void 指针类型,即可以定义一个指针变量,但不指定他是指向哪一种类型数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,430评论 3 44
  • 1. 变量 不同类型的变量在内存中占据不同的字节空间。 内存中存储数据的最小基本单位是字节,每一个字节都有一个内存...
    C语言学习阅读 1,282评论 0 4
  • C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程...
    小辰带你看世界阅读 939评论 0 6
  • 大学的每一年都会借着圣诞这个节日给舍友每人送上一个看去虽“粗制滥造”,实际却充斥着我“满满热血”的一个小礼...
    他特特key阅读 327评论 0 0