C语言课程回顾:十、C语言之 指针

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

C语言之 指针

  • 10 指针
    • 10.1 地址指针的基本概念
    • 10.2 变量的指针和指向变量的指针变量
      • 10.2.1 定义一个指针变量
      • 10.2.2 指针变量的引用
      • 10.2.3 指针变量作为函数参数
      • 10.2.4 指针变量几个问题的进一步说明
    • 10.3 数组指针和指向数组的指针变量
      • 10.3.1 指向数组元素的指针
      • 10.3.2 通过指针引用数组元素
      • 10.3.3 数组名作函数参数
      • 10.3.4 指向多维数组的指针和指针变量
    • 10.4 字符串的指针指向字符串的针指变量
      • 10.4.1 字符串的表示形式
      • 10.4.2 使用字符串指针变量与字符数组的区别
    • 10.5 函数指针变量
    • 10.6 指针型函数
    • 10.7 指针数组和指向指针的指针
      • 10.7.1 指针数组的概念
      • 10.7.2 指向指针的指针
      • 10.7.3 main函数的参数
    • 10.8 有关指针的数据类型和指针运算的小结
      • 10.8.1 有关指针的数据类型的小结
      • 10.8.2 指针运算的小结
      • 10.8.3 void指针类型


10 指针

指针是C语言中广泛使用的一种数据类型。运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构;能很方便地使用数组和字符串;并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。学习指针是学习C语言中最重要的一环,能否正确理解和使用指针是我们是否掌握C语言的一个标志。同时,指针也是C语言中最为困难的一部分,在学习中除了要正确理解基本概念,还必须要多编程,上机调试。只要作到这些,指针也是不难掌握的。

10.1 地址指针的基本概念

在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等,在前面已有详细的介绍。为了正确地访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 找到之后在存单上写入存款、取款的金额。在这里,帐号就是存单的指针, 存款数是存单的内容。对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。

在这里插入图片描述

图中,设有字符变量C,其内容为“K”(ASCII码为十进制数 75),C占用了011A号单元(地址用十六进数表示)。设有指针变量P,内容为011A,这种情况我们称为P指向变量C,或说P是指向变量C的指针。
严格地说,一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址,是常量,“指针变量”是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。
既然指针变量的值是一个地址,那么这个地址不仅可以是变量的地址,也可以是其它数据结构的地址。在一个指针变量中存放一个数组或一个函数的首地址有何意义呢? 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址,也就找到了该数组或函数。这样一来,凡是出现数组,函数的地方都可以用一个指针变量来表示,只要该指针变量中赋予数组或函数的首地址即可。这样做,将会使程序的概念十分清楚,程序本身也精练,高效。在C语言中,一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构,而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址,它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。 这也是引入“指针”概念的一个重要原因。

10.2 变量的指针和指向变量的指针变量

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

在这里插入图片描述

因此,下面两个语句作用相同:
i=3;
*i_pointer=3;
第二个语句的含义是将3赋给指针变量i_pointer所指向的变量。

10.2.1 定义一个指针变量

对指针变量的定义包括三个内容:
(1) 指针类型说明,即定义变量为一个指针变量;
(2) 指针变量名;
(3) 变量值(指针)所指向的变量的数据类型。
其一般形式为:
类型说明符 *变量名;
其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。
例如: int *p1;
表示p1是一个指针变量,它的值是某个整型变量的地址。或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量,应由向p1赋予的地址来决定。
再如:
int *p2; /p2是指向整型变量的指针变量/
float *p3; /p3是指向浮点变量的指针变量/
char *p4; /p4是指向字符变量的指针变量/
应该注意的是,一个指针变量只能指向同类型的变量,如P3 只能指向浮点变量,不能时而指向一个浮点变量,时而又指向一个字符变量。

10.2.2 指针变量的引用

指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值。未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址, 决不能赋予任何其它数据,否则将引起错误。在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。
两个有关的运算符:

  1. &:取地址运算符。
  2. *:指针运算符(或称“间接访问” 运算符)。
    C语言中提供了地址运算符&来表示变量的地址。
    其一般形式为:
    &变量名;
    如&a表示变量a的地址,&b表示变量b的地址。变量本身必须预先说明。
    设有指向整型变量的指针变量p,如要把整型变量a 的地址赋予p可以有以下两种方式:
    (1) 指针变量初始化的方法
    int a;
    int *p=&a;
    (2) 赋值语句的方法
    int a;
    int p;
    p=&a;
    不允许把一个数赋予指针变量,故下面的赋值是错误的:
    int p;
    p=1000;
    被赋值的指针变量前不能再加“
    ”说明符,如写为
    p=&a 也是错误的。
    假设:
    int i=200, x;
    int *ip;
    我们定义了两个整型变量i,x,还定义了一个指向整型数的指针变量ip。i,x中可存放整数,而ip中只能存放整型变量的地址。我们可以把i的地址赋给ip:
    ip=&i;
    此时指针变量ip指向整型变量i,假设变量i的地址为1800,这个赋值可形象理解为下图所示的联系。

在这里插入图片描述

以后我们便可以通过指针变量ip间接访问变量i,例如:
x=ip;
运算符
访问以ip为地址的存贮区域,而ip中存放的是变量i的地址,因此,*ip访问的是地址为1800的存贮区域(因为是整数,实际上是从1800开始的两个字节),它就是i所占用的存贮区域, 所以上面的赋值表达式等价于
x=i;
另外,指针变量和一般变量一样,存放在它们之中的值是可以改变的,也就是说可以改变它们的指向,假设
int i,j,*p1,*p2;
i=‘a’;
j=‘b’;
p1=&i;
p2=&j;
则建立如下图所示的联系:

在这里插入图片描述

这时赋值表达式:
p2=p1
就使p2与p1指向同一对象i,此时*p2就等价于i,而不是j,图所示:
在这里插入图片描述

如果执行如下表达式:
*p2=*p1;
则表示把p1指向的内容赋给p2所指的区域, 此时就变成图所示

在这里插入图片描述

通过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,而且不直观,因为通过指针要访问哪一个变量,取决于指针的值(即指向),例如"*p2=*p1;“实际上就是"j=i;”,前者不仅速度慢而且目的不明。但由于指针是变量,我们可以通过改变它们的指向,以间接访问不同的变量,这给程序员带来灵活性,也使程序代码编写得更为简洁和有效。
指针变量可出现在表达式中, 设
int x,y,px=&x;
指针变量px指向整数x,则
px可出现在x能出现的任何地方。例如:
y=*px+5; /表示把x的内容加5并赋给y/
y=++*px; /*px的内容加上1之后赋给y,++*px相当于++(px)/
y=*px++; /*相当于y=px; px++/
【例10.1】

main()
{ int a,b;int *pointer_1, *pointer_2;a=100;b=10;pointer_1=&a;
pointer_2=&b;printf("%d,%d\n",a,b);printf("%d,%d\n",*pointer_1, *pointer_2);
}

对程序的说明:

  1. 在开头处虽然定义了两个指针变量pointer_1和pointer_2,担它们并未指向任何一个整型变量。只是提供两个指针变量,规定它们可以指向整型变量。程序第5、6行的作用就是使pointer_1指向a,pointer_2指向b。

在这里插入图片描述

  1. 最后一行的pointer_1和pointer_2就是变量a和b。最后两个printf函数作用是相同的。
  2. 程序中有两处出现pointer_1和pointer_2,请区分它们的不同含义。
  3. 程序第5、6行的“pointer_1=&a”和 “pointer_2=&b”不能写成“*pointer_1=&a”和 “pointer_2=&b”。
    请对下面再的关于“&”和“
    ”的问题进行考虑:
  4. 如果已经执行了“pointer_1=&a;”语句,则&*pointer_1是什么含义?
  5. *&a含义是什么?
  6. (pointer_1)++和pointer_1++的区别?
    【例10.2】输入a和b两个整数,按先大后小的顺序输出a和b。
main()
{ int *p1,*p2,*p,a,b;scanf("%d,%d",&a,&b);p1=&a;p2=&b;if(a<b){p=p1;p1=p2;p2=p;}printf("\na=%d,b=%d\n",a,b);printf("max=%d,min=%d\n",*p1, *p2);
}

10.2.3 指针变量作为函数参数

函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。
【例10.3】题目同例10.2,即输入的两个整数按大小顺序输出。今用函数处理,而且用指针类型的数据作函数参数。

swap(int *p1,int *p2)
{int temp;temp=*p1;*p1=*p2;*p2=temp;
}
main()
{ 
int a,b;
int *pointer_1,*pointer_2;scanf("%d,%d",&a,&b);pointer_1=&a;pointer_2=&b;if(a<b) swap(pointer_1,pointer_2);printf("\n%d,%d\n",a,b);}

对程序的说明:
swap是用户定义的函数,它的作用是交换两个变量(a和b)的值。swap函数的形参p1、p2是指针变量。程序运行时,先执行main函数,输入a和b的值。然后将a和b的地址分别赋给指针变量pointer_1和pointer_2,使pointer_1指向a,pointer_2指向b。

在这里插入图片描述

接着执行if语句,由于a〈b,因此执行swap函数。注意实参pointer_1和pointer_2是指针变量,在函数调用时,将实参变量的值传递给形参变量。采取的依然是“值传递”方式。因此虚实结合后形参p1的值为&a,p2的值为&b。这时p1和pointer_1指向变量a,p2和pointer_2指向变量b。

在这里插入图片描述

接着执行执行swap函数的函数体使p1和p2的值互换,也就是使a和b的值互换。

在这里插入图片描述

函数调用结束后,p1和p2不复存在(已释放)如图。

在这里插入图片描述

最后在main函数中输出的a和b的值是已经过交换的值。
请注意交换p1和p2的值是如何实现的。请找出下列程序段的错误:

swap(int *p1,int *p2)
{int *temp;*temp=*p1;      /*此语句有问题*/*p1=*p2;*p2=temp;
}

请考虑下面的函数能否实现实现a和b互换。

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

如果在main函数中用“swap(a,b);”调用swap函数,会有什么结果呢?请看下图所示。

在这里插入图片描述

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

swap(int *p1,int *p2)
{int *p;p=p1;p1=p2;p2=p;
}
main()
{ 
int a,b;
int *pointer_1,*pointer_2;scanf("%d,%d",&a,&b);pointer_1=&a;pointer_2=&b;if(a<b) swap(pointer_1,pointer_2);printf("\n%d,%d\n",*pointer_1,*pointer_2);}

其中的问题在于不能实现如图所示的第四步(d)。

在这里插入图片描述

【例10.5】输入a、b、c3个整数,按大小顺序输出。

swap(int *pt1,int *pt2)
{int temp;temp=*pt1;*pt1=*pt2;*pt2=temp;
}
exchange(int *q1,int *q2,int *q3)
{ if(*q1<*q2)swap(q1,q2);
if(*q1<*q3)swap(q1,q3);
if(*q2<*q3)swap(q2,q3);
}
main()
{ 
int a,b,c,*p1,*p2,*p3;scanf("%d,%d,%d",&a,&b,&c);p1=&a;p2=&b; p3=&c;exchange(p1,p2,p3);printf("\n%d,%d,%d \n",a,b,c);}

10.2.4 指针变量几个问题的进一步说明

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

  1. 指针运算符
  1. 取地址运算符&:取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。在scanf函数及前面介绍指针变量赋值中,我们已经了解并使用了&运算符。
  2. 取内容运算符*:取内容运算符是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在运算符之后跟的变量必须是指针变量。
    需要注意的是指针运算符和指针变量说明中的指针说明符不是一回事。在指针变量说明中,“”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“”则是一个运算符用以表示指针变量所指的变量。
    【例10.6】
main(){int a=5,*p=&a;printf ("%d",*p);
}
表示指针变量p取得了整型变量a的地址。printf("%d",*p)语句表示输出变量a的值。
  1. 指针变量的运算
  1. 赋值运算:指针变量的赋值运算有以下几种形式。
    ① 指针变量初始化赋值,前面已作介绍。
    ② 把一个变量的地址赋予指向相同数据类型的指针变量。
    例如:
    int a,*pa;
    pa=&a; /把整型变量a的地址赋予整型指针变量pa/
    ③ 把一个指针变量的值赋予指向相同类型变量的另一个指针变量。
    如:
    int a,*pa=&a,*pb;
    pb=pa; /把a的地址赋予指针变量pb/
    由于pa,pb均为指向整型变量的指针变量,因此可以相互赋值。
    ④ 把数组的首地址赋予指向数组的指针变量。
    例如:
    int a[5],*pa;
    pa=a;
    (数组名表示数组的首地址,故可赋予指向数组的指针变量pa)
    也可写为:
    pa=&a[0]; /数组第一个元素的地址也是整个数组的首地址, 也可赋予pa/
    当然也可采取初始化赋值的方法:
    int a[5],*pa=a;
    ⑤ 把字符串的首地址赋予指向字符类型的指针变量。
    例如:
    char *pc;
    pc=“C Language”;
    或用初始化赋值的方法写为:
    char *pc=“C Language”;
    这里应说明的是并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量。在后面还将详细介绍。
    ⑥ 把函数的入口地址赋予指向函数的指针变量。
    例如:
    int (*pf)();
    pf=f; /f为函数名/
  2. 加减算术运算
    对于指向数组的指针变量,可以加上或减去一个整数n。设pa是指向数组a的指针变量,则pa+n,pa-n,pa++,++pa,pa–,–pa运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意,数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。例如:
    int a[5],*pa;
    pa=a; /pa指向数组a,也是指向a[0]/
    pa=pa+2; /pa指向a[2],即pa的值为&pa[2]/
    指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。
  3. 两个指针变量之间的运算:只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。
    ① 两指针变量相减:两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。例如pf1和pf2是指向同一浮点数组的两个指针变量,设pf1的值为2010H,pf2的值为2000H,而浮点数组每个元素占4个字节,所以pf1-pf2的结果为(2000H-2010H)/4=4,表示pf1和 pf2之间相差4个元素。两个指针变量不能进行加法运算。 例如,pf1+pf2是什么意思呢?毫无实际意义。
    ② 两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。
    例如:
    pf1pf2表示pf1和pf2指向同一数组元素;
    pf1>pf2表示pf1处于高地址位置;
    pf1<pf2表示pf2处于低地址位置。
    指针变量还可以与0比较。
    设p为指针变量,则p
    0表明p是空指针,它不指向任何变量;
    p!=0表示p不是空指针。
    空指针是由对指针变量赋予0值而得到的。
    例如:
    #define NULL 0
    int *p=NULL;
    对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。
    【例10.7】
main(){int a=10,b=20,s,t,*pa,*pb; /*说明pa,pb为整型指针变量*/pa=&a;                     /*给指针变量pa赋值,pa指向变量a*/pb=&b;                     /*给指针变量pb赋值,pb指向变量b*/s=*pa+*pb;                 /*求a+b之和,(*pa就是a,*pb就是b)*/t=*pa**pb;                 /*本行是求a*b之积*/printf("a=%d\nb=%d\na+b=%d\na*b=%d\n",a,b,a+b,a*b);printf("s=%d\nt=%d\n",s,t);
}

【例10.8】

main(){int a,b,c,*pmax,*pmin;             /*pmax,pmin为整型指针变量*/printf("input three numbers:\n");  /*输入提示*/scanf("%d%d%d",&a,&b,&c);         /*输入三个数字*/if(a>b){                          /*如果第一个数字大于第二个数字...*/pmax=&a;                        /*指针变量赋值*/pmin=&b;}                       /*指针变量赋值*/else{pmax=&b;                        /*指针变量赋值*/pmin=&a;}                       /*指针变量赋值*/if(c>*pmax) pmax=&c;              /*判断并赋值*/if(c<*pmin) pmin=&c;              /*判断并赋值*/printf("max=%d\nmin=%d\n",*pmax,*pmin); /*输出结果*/
}

10.3 数组指针和指向数组的指针变量

一个变量有一个地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。

10.3.1 指向数组元素的指针

一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的内存单元。一个数组元素的首地址也是指它所占有的几个内存单元的首地址。
定义一个指向数组元素的指针变量的方法,与以前介绍的指针变量相同。
例如:
int a[10]; /定义a为包含10个整型数据的数组/
int *p; /定义p为指向整型变量的指针/
应当注意,因为数组为int型,所以指针变量也应为指向int型的指针变量。下面是对指针变量赋值:
p=&a[0];
把a[0]元素的地址赋给指针变量p。也就是说,p指向a数组的第0号元素。

在这里插入图片描述

C语言规定,数组名代表数组的首地址,也就是第0号元素的地址。因此,下面两个语句等价:
p=&a[0];
p=a;
在定义指针变量时可以赋给初值:
int *p=&a[0];
它等效于:
int *p;
p=&a[0];
当然定义时也可以写成:
int *p=a;
从图中我们可以看出有以下关系:
p,a,&a[0]均指向同一单元,它们是数组a的首地址,也是0 号元素a[0]的首地址。应该说明的是p是变量,而a,&a[0]都是常量。在编程时应予以注意。
数组指针变量说明的一般形式为:
类型说明符 *指针变量名;
其中类型说明符表示所指数组的类型。从一般形式可以看出指向数组的指针变量和指向普通变量的指针变量的说明是相同的。

10.3.2 通过指针引用数组元素

C语言规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。
引入指针变量后,就可以用两种方法来访问数组元素了。
如果p的初值为&a[0],则:

  1. p+i和a+i就是a[i]的地址,或者说它们指向a数组的第i个元素。

在这里插入图片描述

  1. (p+i)或(a+i)就是p+i或a+i所指向的数组元素,即a[i]。例如,(p+5)或(a+5)就是a[5]。
  2. 指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。
    根据以上叙述,引用一个数组元素可以用:
  3. 下标法,即用a[i]形式访问数组元素。在前面介绍数组时都是采用这种方法。
  4. 指针法,即采用*(a+i)或*(p+i)形式,用间接访问的方法来访问数组元素,其中a是数组名,p是指向数组的指针变量,其处值p=a。
    【例10.9】输出数组中的全部元素。(下标法)
main(){int a[10],i;for(i=0;i<10;i++)a[i]=i;for(i=0;i<5;i++)printf("a[%d]=%d\n",i,a[i]);
}

【例10.10】输出数组中的全部元素。(通过数组名计算元素的地址,找出元素的值)

main(){int a[10],i;for(i=0;i<10;i++)*(a+i)=i;for(i=0;i<10;i++)printf("a[%d]=%d\n",i,*(a+i));
}

【例10.11】输出数组中的全部元素。(用指针变量指向元素)

main(){int a[10],I,*p;p=a;for(i=0;i<10;i++)*(p+i)=i;for(i=0;i<10;i++)printf("a[%d]=%d\n",i,*(p+i));
}

【例10.12】

main(){int a[10],i,*p=a;for(i=0;i<10;){*p=i;printf("a[%d]=%d\n",i++,*p++);}
}

几个注意的问题:

  1. 指针变量可以实现本身的值的改变。如p++是合法的;而a++是错误的。因为a是数组名,它是数组的首地址,是常量。
  2. 要注意指针变量的当前值。请看下面的程序。
    【例10.13】找出错误。
main(){int *p,i,a[10];p=a;
for(i=0;i<10;i++)*p++=i;for(i=0;i<10;i++)printf("a[%d]=%d\n",i,*p++);
}

【例10.14】改正。

main(){int *p,i,a[10];p=a;
for(i=0;i<10;i++)
*p++=i;p=a;for(i=0;i<10;i++)printf("a[%d]=%d\n",i,*p++);
}
  1. 从上例可以看出,虽然定义数组时指定它包含10个元素,但指针变量可以指到数组以后的内存单元,系统并不认为非法。
  2. p++,由于++和同优先级,结合方向自右而左,等价于*(p++)。
  3. (p++)与(++p)作用不同。若p的初值为a,则*(p++)等价a[0],*(++p)等价a[1]。
  4. (*p)++表示p所指向的元素值加1。
  5. 如果p当前指向a数组中的第i个元素,则
    *(p–)相当于a[i–];
    *(++p)相当于a[++i];
    *(–p)相当于a[–i]。

10.3.3 数组名作函数参数

数组名可以作函数的实参和形参。如:

main()
{int array[10];…………f(array,10);
…………
}f(int arr[],int n);{
…………
}

array为实参数组名,arr为形参数组名。在学习指针变量之后就更容易理解这个问题了。数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同一数组。这就好象同一件物品有两个彼此不同的名称一样。

在这里插入图片描述

同样,指针变量的值也是地址,数组指针变量的值即为数组的首地址,当然也可作为函数的参数使用。

【例10.15】

float aver(float *pa);
main(){float sco[5],av,*sp;int i;sp=sco;printf("\ninput 5 scores:\n");for(i=0;i<5;i++) scanf("%f",&sco[i]);av=aver(sp);printf("average score is %5.2f",av);
}
float aver(float *pa)
{int i;float av,s=0;for(i=0;i<5;i++) s=s+*pa++;av=s/5;return av;
}

【例10.16】将数组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)   /*形参x是数组名*/
{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;
}
main()
{int i,a[10]={3,7,9,11,0,6,7,5,4,2};printf("The original array:\n");for(i=0;i<10;i++)printf("%d,",a[i]);printf("\n");inv(a,10);printf("The array has benn inverted:\n");for(i=0;i<10;i++)printf("%d,",a[i]);printf("\n");
}

对此程序可以作一些改动。将函数inv中的形参x改成指针变量。
【例10.17】对例10.16可以作一些改动。将函数inv中的形参x改成指针变量。
程序如下:

void inv(int *x,int n)   /*形参x为指针变量*/
{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;
}
main()
{int i,a[10]={3,7,9,11,0,6,7,5,4,2};printf("The original array:\n");for(i=0;i<10;i++)printf("%d,",a[i]);printf("\n");inv(a,10);printf("The array has benn inverted:\n");for(i=0;i<10;i++)printf("%d,",a[i]);printf("\n");
}

运行情况与前一程序相同。
【例10.18】从0个数中找出其中最大值和最小值。
调用一个函数只能得到一个返回值,今用全局变量在函数之间“传递”数据。程序如下:

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 umbers:\n");for(i=0;i<10;i++)scanf("%d",&number[i]);max_min_value(number,10);printf("\nmax=%d,min=%d\n",max,min);}

说明:

  1. 在函数max_min_value中求出的最大值和最小值放在max和min中。由于它们是全局,因此在主函数中可以直接使用。
  2. 函数max_min_value中的语句:
    max=min=*array;
    array是数组名,它接收从实参传来的数组numuber的首地址。
    array相当于(&array[0])。上述语句与 max=min=array[0];等价。
  3. 在执行for循环时,p的初值为array+1,也就是使p指向array[1]。以后每次执行p++,使p指向下一个元素。每次将*p和max与min比较。将大者放入max,小者放min。

在这里插入图片描述

  1. 函数max_min_value的形参array可以改为指针变量类型。实参也可以不用数组名,而用指针变量传递地址。
    【例10.19】程序可改为:
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],*p;p=number;             /*使p指向number数组*/printf("enter 10 integer umbers:\n");for(i=0;i<10;i++,p++)scanf("%d",p);p=number;max_min_value(p,10);printf("\nmax=%d,min=%d\n",max,min);}

归纳起来,如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的对应关系有以下4种:

  1. 形参和实参都是数组名。
 
main()
{int a[10];……f(a,10)……
f(int x[],int n)
{……
}}

a和x指的是同一组数组。
2) 实用数组,形参用指针变量。

main()
{int a[10];……f(a,10)……
f(int *x,int n)
{……
}}
  1. 实参、型参都用指针变量。
  2. 实参为指针变量,型参为数组名。
    【例10.20】用实参指针变量改写将n个整数按相反顺序存放。
void inv(int *x,int n)
{int *p,m,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;
}
main()
{int i,arr[10]={3,7,9,11,0,6,7,5,4,2},*p;p=arr;printf("The original array:\n");for(i=0;i<10;i++,p++)printf("%d,",*p);printf("\n");p=arr;inv(p,10);printf("The array has benn inverted:\n");for(p=arr;p<arr+10;p++)printf("%d,",*p);printf("\n");
}

注意:main函数中的指针变量p是有确定值的。即如果用指针变作实参,必须现使指针变量有确定值,指向一个已定义的数组。
【例10.21】用选择法对10个整数排序。

main()
{int *p,i,a[10]={3,7,9,11,0,6,7,5,4,2};printf("The original array:\n");for(i=0;i<10;i++)printf("%d,",a[i]);printf("\n");p=a;sort(p,10);for(p=a,i=0;i<10;i++){printf("%d  ",*p);p++;}printf("\n");
}
sort(int x[],int n)
{int i,j,k,t;for(i=0;i<n-1;i++){k=i;for(j=i+1;j<n;j++)if(x[j]>x[k])k=j;if(k!=i){t=x[i];x[i]=x[k];x[k]=t;}}
}

说明:函数sort用数组名作为形参,也可改为用指针变量,这时函数的首部可以改为:
sort(int *x,int n) 其他可一律不改。

10.3.4 指向多维数组的指针和指针变量

本小节以二维数组为例介绍多维数组的指针变量。

  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,各下标变量的首地址及其值如图所示。

在这里插入图片描述

前面介绍过,C语言允许把一个二维数组分解为多个一维数组来处理。因此数组a可分解为三个一维数组,即a[0],a[1],a[2]。每一个一维数组又含有四个元素。

在这里插入图片描述

例如a[0]数组,含有a[0][0],a[0][1],a[0][2],a[0][3]四个元素。
数组及数组元素的地址表示如下:
从二维数组的角度来看,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,&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[i]理解为元素a[i]的地址,不存在元素a[i]。C语言规定,它是一种地址计算方法,表示数组a第i行首地址。由此,我们得出:a[i],&a[i],*(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]=(a+i)得a[i]+j=(a+i)+j。由于*(a+i)+j是二维数组a的i行j列元素的首地址,所以,该元素的值等于*(*(a+i)+j)。
【例10.22】

main(){int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};printf("%d,",a);printf("%d,",*a);printf("%d,",a[0]);printf("%d,",&a[0]);printf("%d\n",&a[0][0]);printf("%d,",a+1);printf("%d,",*(a+1));printf("%d,",a[1]);printf("%d,",&a[1]);printf("%d\n",&a[1][0]);printf("%d,",a+2);printf("%d,",*(a+2));printf("%d,",a[2]);printf("%d,",&a[2]);printf("%d\n",&a[2][0]);printf("%d,",a[1]+1);printf("%d\n",*(a+1)+1);printf("%d,%d\n",*(a[1]+1),*(*(a+1)+1));
}
  1. 指向多维数组的指针变量
    把二维数组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列元素的值。
    二维数组指针变量说明的一般形式为:
    类型说明符 (*指针变量名)[长度]
    其中“类型说明符”为所指数组的数据类型。“
    ”表示其后的变量是指针类型。“长度”表示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。应注意“(*指针变量名)”两边的括号不可少,如缺少括号则表示是指针数组(本章后面介绍),意义就完全不同了。
    【例10.23】
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.4 字符串的指针指向字符串的针指变量

10.4.1 字符串的表示形式

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

  1. 用字符数组存放一个字符串,然后输出该字符串。
    【例10.24】
main(){char string[]=”I love China!;printf("%s\n",string);
}

说明:和前面介绍的数组属性一样,string是数组名,它代表字符数组的首地址。

在这里插入图片描述

  1. 用字符串指针指向一个字符串。
    【例10.25】
main(){char *string=”I love China!;printf("%s\n",string);
}

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

char c,*p=&c;

表示p是一个指向字符变量c的指针变量。
而:

char *s=“C Language”;

则表示s是一个指向字符串的指针变量。把字符串的首地址赋予s。
上例中,首先定义string是一个字符指针变量,然后把字符串的首地址赋予string(应写出整个字符串,以便编译系统把该串装入连续的一块内存单元),并把首地址送入string。程序中的:
char *ps=“C Language”;
等效于:
char *ps;
ps=“C Language”;
【例10.26】输出字符串中n个字符后的所有字符。

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

运行结果为:
book
在程序中对ps初始化时,即把字符串首地址赋予ps,当ps= ps+10之后,ps指向字符“b”,因此输出为"book"。
【例10.27】在输入的字符串中查找有无‘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");
}

【例10.28】本例是将指针变量指向一个格式字符串,用在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,*a,a[0],&a[0],&a[0][0]);printf(PF,a+1,*(a+1),a[1],&a[1],&a[1][0]);printf(PF,a+2,*(a+2),a[2],&a[2],&a[2][0]);printf("%d,%d\n",a[1]+1,*(a+1)+1);printf("%d,%d\n",*(a[1]+1),*(*(a+1)+1));
}

【例10.29】本例是把字符串指针作为函数参数的使用。要求把一个字符串的内容复制到另一个字符串中,并且不能使用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);
}

在本例中,程序完成了两项工作:一是把pss指向的源字符串复制到pds所指向的目标字符串中,二是判断所复制的字符是否为\0',若是则表明源字符串结束,不再循环。否则,pds和pss都加1,指向下一字符。在主函数中,以指针变量pa,pb为实参,分别取得确定值后调用cprstr函数。由于采用的指针变量pa和pss,pb和pds均指向同一字符串,因此在主函数和cprstr函数中均可使用这些字符串。也可以把cprstr函数简化为以下形式: cprstr(char *pss,char*pds) {while ((*pds++=*pss++)!=\0’);}
即把指针的移动和赋值合并在一个语句中。 进一步分析还可发现\0'的ASCⅡ码为0,对于while语句只看表达式的值为非0就循环,为0则结束循环,因此也可省去“!=\0’”这一判断部分,而写为以下形式:
cprstr (char *pss,char *pds)
{while (*pdss++=*pss++);}
表达式的意义可解释为,源字符向目标字符赋值,移动指针,若所赋值为非0则循环,否则结束循环。这样使程序更加简洁。
【例10.30】简化后的程序如下所示。

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

10.4.2 使用字符串指针变量与字符数组的区别

用字符数组和字符指针变量都可实现字符串的存储和运算。但是两者是有区别的。在使用时应注意以下几个问题:
  1. 字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以‘\0’作为串的结束。字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。
  2. 对字符串指针方式
char *ps="C Language";
可以写为:char *ps;
ps="C Language";
而对数组方式:static char st[]={"C Language"};
不能写为:char st[20];st={"C Language"};
而只能对字符数组的各元素逐个赋值。从以上几点可以看出字符串指针变量与字符数组在使用时的区别,同时也可看出使用指针变量更加方便。
前面说过,当一个指针变量在未取得确定地址前使用是危险的,容易引起错误。但是对指针变量直接赋值是可以的。因为C系统对指针变量赋值时要给以确定的地址。
因此,char *ps="C Langage";
或者char *ps;ps="C Language";
都是合法的。

10.5 函数指针变量

在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为“函数指针变量”。

函数指针变量定义的一般形式为:
类型说明符 (指针变量名)();
其中“类型说明符”表示被指函数的返回值的类型。“(
指针变量名)”表示“*”后面的变量是定义的指针变量。最后的空括号表示指针变量所指的是一个函数。
例如:
int (*pf)();
表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
【例10.31】本例用来说明用指针形式实现对函数调用的方法。

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. 先定义函数指针变量,如后一程序中第9行 int (*pmax)();定义 pmax为函数指针变量。
  2. 把被调函数的入口地址(函数名)赋予该函数指针变量,如程序中第11行 pmax=max;
  3. 用函数指针变量形式调用函数,如程序第14行 z=(*pmax)(x,y);
  4. 调用函数的一般形式为:
    (*指针变量名) (实参表)
    使用函数指针变量还应注意以下两点:
    a) 函数指针变量不能进行算术运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。
    b) 函数调用中"(指针变量名)"的两边的括号不可少,其中的不应该理解为求值运算,在此处它只是一种表示符号。

10.6 指针型函数

前面我们介绍过,所谓函数类型是指函数返回值的类型。在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。

定义指针型函数的一般形式为:

    类型说明符 *函数名(形参表)  {  ……          /*函数体*/}  
其中函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。
如:int *ap(int x,int y){......       /*函数体*/
}

表示ap是一个返回指针值的指针型函数,它返回的指针指向一个整型变量。
【例10.32】本程序是通过指针函数,输入一个1~7之间的整数,输出对应的星期名。

main(){int i;char *day_name(int n);   printf("input 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]);
}

本例中定义了一个指针型函数day_name,它的返回值指向一个字符串。该函数中定义了一个静态指针数组name。name数组初始化赋值为八个字符串,分别表示各个星期名及出错提示。形参n表示与星期名所对应的整数。在主函数中,把输入的整数i作为实参,在printf语句中调用day_name函数并把i值传送给形参n。day_name函数中的return语句包含一个条件表达式,n值若大于7或小于1则把name[0]指针返回主函数输出出错提示字符串“Illegal day”。否则返回主函数输出对应的星期名。主函数中的第7行是个条件语句,其语义是,如输入为负数(i<0)则中止程序运行退出程序。exit是一个库函数,exit(1)表示发生错误后退出程序,exit(0)表示正常退出。
应该特别注意的是函数指针变量和指针型函数这两者在写法和意义上的区别。如int(*p)()和int *p()是两个完全不同的量。
int (*p)()是一个变量说明,说明p是一个指向函数入口的指针变量,该函数的返回值是整型量,(*p)的两边的括号不能少。
int *p()则不是变量说明而是函数说明,说明p是一个指针型函数,其返回值是一个指向整型量的指针,*p两边没有括号。作为函数说明,在括号内最好写入形式参数,这样便于与变量说明区别。
对于指针型函数定义,int *p()只是函数头部分,一般还应该有函数体部分。

10.7 指针数组和指向指针的指针

10.7.1 指针数组的概念

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

指针数组说明的一般形式为:
类型说明符 *数组名[数组长度]
其中类型说明符为指针值所指向的变量的类型。
例如:
int *pa[3]
表示pa是一个指针数组,它有三个数组元素,每个元素值都是一个指针,指向整型变量。
【例10.33】通常可用一个指针数组来指向一个二维数组。指针数组中的每个元素被赋予二维数组每一行的首地址,因此也可理解为指向一个一维数组。

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));
}

本例程序中,pa是一个指针数组,三个元素分别指向二维数组a的各行。然后用循环语句输出指定的数组元素。其中a[i]表示i行0列元素值;(*(a+i)+i)表示i行i列的元素值;pa[i]表示i行0列元素值;由于p与a[0]相同,故p[i]表示0行i列的值;(p+i)表示0行i列的值。读者可仔细领会元素值的各种不同的表示方法。
应该注意指针数组和二维数组指针变量的区别。这两者虽然都可用来表示二维数组,但是其表示方法和意义是不同的。
二维数组指针变量是单个的变量,其一般形式中"(*指针变量名)“两边的括号不可少。而指针数组类型表示的是多个指针(一组有序指针)在一般形式中”*指针数组名"两边不能有括号。
例如:
int (*p)[3];
表示一个指向二维数组的指针变量。该二维数组的列数为3或分解为一维数组的长度为3。
int *p[3]
表示p是一个指针数组,有三个下标变量p[0],p[1],p[2]均为指针变量。
指针数组也常用来表示一组字符串,这时指针数组的每个元素被赋予一个字符串的首地址。指向字符串的指针数组的初始化更为简单。例如在例10.32中即采用指针数组来表示一组字符串。其初始化赋值为:

char name[]={“Illagal day”,
“Monday”,
“Tuesday”,
“Wednesday”,
“Thursday”,
“Friday”,
“Saturday”,
“Sunday”};
完成这个初始化赋值之后,name[0]即指向字符串"Illegal day",name[1]指向"Monday"…。
指针数组也可以用作函数参数。
【例10.34】指针数组作指针型函数的参数。在本例主函数中,定义了一个指针数组name,并对name 作了初始化赋值。其每个元素都指向一个字符串。然后又以name作为实参调用指针型函数day_name,在调用时把数组名name赋予形参变量name,输入的整数i作为第二个实参赋予形参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\n",i,ps);
}
char *day_name(char *name[],int n)
{char *pp1,*pp2;pp1=*name;pp2=*(name+n);return((n<1||n>7)? pp1:pp2);
}

【例10.35】输入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]);
}

说明:
在以前的例子中采用了普通的排序方法,逐个比较之后交换字符串的位置。交换字符串的物理位置是通过字符串复制函数完成的。反复的交换将使程序执行的速度很慢,同时由于各字符串(国名)的长度不同,又增加了存储管理的负担。用指针数组能很好地解决这些问题。把所有的字符串存放在一个数组中,把这些字符数组的首地址放在一个指针数组中,当需要交换两个字符串时,只须交换指针数组相应两元素的内容(地址)即可,而不必交换字符串本身。
本程序定义了两个函数,一个名为sort完成排序,其形参为指针数组name,即为待排序的各字符串数组的指针。形参n为字符串的个数。另一个函数名为print,用于排序后字符串的输出,其形参与sort的形参相同。主函数main中,定义了指针数组name 并作了初始化赋值。然后分别调用sort函数和print函数完成排序和输出。值得说明的是在sort函数中,对两个字符串比较,采用了strcmp函数,strcmp函数允许参与比较的字符串以指针方式出现。name[k]和name[j]均为指针,因此是合法的。字符串比较后需要交换时,只交换指针数组元素的值,而不交换具体的字符串,这样将大大减少时间的开销,提高了运行效率。

10.7.2 指向指针的指针

如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。

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

在这里插入图片描述

从下图可以看到,name是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。Name是一个数据,它的每一个元素都有相应的地址。数组名name代表该指针数组的首地址。name+1是mane[i]的地址。name+1就是指向指针型数据的指针(地址)。还可以设置一个指针变量p,使它指向指针数组元素。P就是指向指针型数据的指针变量。
怎样定义一个指向指针型数据的指针变量呢?如下:
char *p;
p前面有两个号,相当于(p)。显然p是指针变量的定义形式,如果没有最前面的
,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个*号,表示指针变量p是指向一个字符指针型变量的。*p就是p所指向的另一个指针变量。
从下图可以看到,name是一个指针数组,它的每一个元素是一个指针型数据,其值为地址。name是一个数组,它的每一个元素都有相应的地址。数组名name代表该指针数组的首地址。name+1是mane[i]的地址。name+1就是指向指针型数据的指针(地址)。还可以设置一个指针变量p,使它指向指针数组元素。P就是指向指针型数据的指针变量。

在这里插入图片描述

如果有:

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

则,第一个printf函数语句输出name[2]的值(它是一个地址),第二个printf函数语句以字符串形式(%s)输出字符串“Great Wall”。
【例10.36】使用指向指针的指针。

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

说明:
p是指向指针的指针变量。
【例10.37】一个指针数组的元素指向数据的简单例子。

main()
{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++;}
}

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

10.7.3 main函数的参数

前面介绍的main函数都是不带参数的。因此main 后的括号都是空括号。实际上,main函数可以带参数,这个参数可以认为是 main函数的形式参数。C语言规定main函数的参数只能有两个,习惯上这两个参数写为argc和argv。因此,main函数的函数头可写为:
main (argc,argv)
C语言还规定argc(第一个形参)必须是整型变量,argv( 第二个形参)必须是指向字符串的指针数组。加上形参说明后,main函数的函数头应写为:
main (int argc,char *argv[])
由于main函数不能被其它函数调用,因此不可能在程序内部取得实际值。那么,在何处把实参值赋予main函数的形参呢? 实际上,main函数的参数值是从操作系统命令行上获得的。当我们要运行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数即可把这些实参传送到main的形参中去。
DOS提示符下命令行的一般形式为:
C:>可执行文件名 参数 参数……;
但是应该特别注意的是,main 的两个形参和命令行中的参数在位置上不是一一对应的。因为,main的形参只有二个,而命令行中的参数个数原则上未加限制。argc参数表示了命令行中参数的个数(注意:文件名本身也算一个参数),argc的值是在输入命令行时由系统按实际参数的个数自动赋予的。
例如有命令行为:
C:>E24 BASIC foxpro FORTRAN
由于文件名E24本身也算一个参数,所以共有4个参数,因此argc取得的值为4。argv参数是字符串指针数组,其各元素值为命令行中各字符串(参数均按字符串处理)的首地址。 指针数组的长度即为参数个数。数组元素初值由系统自动赋予。其表示如图所示:

在这里插入图片描述

【例10.38】
main(int argc,char argv){
while(argc–>1)
printf(“%s\n”,
++argv);
}

本例是显示命令行中输入的参数。如果上例的可执行文件名为e24.exe,存放在A驱动器的盘内。因此输入的命令行为:
C:>a:e24 BASIC foxpro FORTRAN
则运行结果为:
BASIC
foxpro
FORTRAN
该行共有4个参数,执行main时,argc的初值即为4。argv的4个元素分为4个字符串的首地址。执行while语句,每循环一次argv值减1,当argv等于1时停止循环,共循环三次,因此共可输出三个参数。在printf函数中,由于打印项*++argv是先加1再打印, 故第一次打印的是argv[1]所指的字符串BASIC。第二、三次循环分别打印后二个字符串。而参数e24是文件名,不必输出。

10.8 有关指针的数据类型和指针运算的小结

10.8.1 有关指针的数据类型的小结

定义 含 义
int i; 定义整型变量i
int *p p为指向整型数据的指针变量
int a[n]; 定义整型数组a,它有n个元素
int *p[n]; 定义指针数组p,它由n个指向整型数据的指针元素组成
int (*p)[n]; p为指向含n个元素的一维数组的指针变量
int f(); f为带回整型函数值的函数
int *p(); p为带回一个指针的函数,该指针指向整型数据
int (*p)(); p为指向函数的指针,该函数返回一个整型值
int **p; P是一个指针变量,它指向一个指向整型数据的指针变量

10.8.2 指针运算的小结

现把全部指针运算列出如下:

  1. 指针变量加(减)一个整数:
    例如:p++、p–、p+i、p-i、p+=i、p-=i
    一个指针变量加(减)一个整数并不是简单地将原值加(减)一个整数,而是将该指针变量的原值(是一个地址)和它指向的变量所占用的内存单元字节数加(减)。
  2. 指针变量赋值:将一个变量的地址赋给一个指针变量。
    p=&a; (将变量a的地址赋给p)
    p=array; (将数组array的首地址赋给p)
    p=&array[i]; (将数组array第i个元素的地址赋给p)
    p=max; (max为已定义的函数,将max的入口地址赋给p)
    p1=p2; (p1和p2都是指针变量,将p2的值赋给p1)
    注意:不能如下:
    p=1000;
  3. 指针变量可以有空值,即该指针变量不指向任何变量:
    p=NULL;
  4. 两个指针变量可以相减:如果两个指针变量指向同一个数组的元素,则两个指针变量值之差是两个指针之间的元素个数。
  5. 两个指针变量比较:如果两个指针变量指向同一个数组的元素,则两个指针变量可以进行比较。指向前面的元素的指针变量“小于” 指向后面的元素的指针变量。

10.8.3 void指针类型

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/46822.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑远程开关机

1. 远程开机 参考&#xff1a;https://post.smzdm.com/p/664774/ 1.1 Wake On LAN - 局域网唤醒&#xff08;需要主板支持&#xff0c;一般都支持&#xff09; 要使用远程唤醒&#xff0c;有几种方式&#xff1a;使用类似向日葵开机棒&#xff08;很贵&#xff09;、公网ip&…

MongoDB教程(六):mongoDB复制副本集

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、MongoD…

使用Bind提供的域名解析服务

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、DNS域名解析服务 二、安装Bind服务程序 1、正向解析 2、反向解析 三、部署从服务器 四、安全的加密传输 五、部署缓存服务器 六、分…

浅析班组建设在企业发展中的重要作用

众所周知&#xff0c;企业的成功与否往往取决于其内部管理的精细化和团队的高效协作。而班组作为企业最基层的管理单元&#xff0c;其建设质量直接关系到企业的整体运营效率和竞争力。今天&#xff0c;深圳天行健企业管理咨询公司将从多个维度分析班组建设在企业发展中的重要作…

【Python】深入了解 defaultdict:轻松处理默认值与复杂数据结构

文章目录 1. 深入理解 Python 中的 defaultdict&#xff1a;简化数据结构处理的利器2. defaultdict 基础概念3. 创建 defaultdict 实例3.1 基本用法3.2 使用其他工厂函数 4. defaultdict 的应用场景4.1 计数器4.2 分组数据 5. defaultdict 的高级用法5.1 嵌套 defaultdict5.2 自…

为什么流程图在项目管理中如此重要?

在我们的日常学习生活中&#xff0c;是不是感觉工作复杂繁琐&#xff0c;知识杂乱无章呢&#xff1f;那么流程图能够完美的解决这个问题&#xff0c;本文将会用一篇文章告诉你什么是流程图&#xff0c;流程图简单来说就是一种以图形方式表示算法、工作流程或过程的图表&#xf…

云服务器重置密码后,xshell远程连接不上,重新启用密码登录方式

云服务器重置密码后 &#xff0c;xshell连接出现不能使用密码登录 解决方案&#xff1a;以下来自阿里云重新启用密码登录方式帮助文档 为轻量应用服务器创建密钥且重启服务器使密钥生效后&#xff0c;服务器会自动禁止使用root用户及密码登录。如果您需要重新启用密码登录方式&…

数据结构-java中链表的存储原理及使用方式

目录 链表&#xff08;线性表的链式存储&#xff09; 代码实例&#xff1a;&#xff08;链表构建&#xff0c;头插尾插&#xff09; LinkedList LinkedList的使用&#xff1a; 1、构造方法 2、操作方法 LinkedList 和 ArrayList 的区别 链表&#xff08;线性表的链式存储…

基于python的图像去水印

1 代码 import cv2 import numpy as npdef remove_watermark(image_path, output_path):# 读取图片image cv2.imread(image_path)# 转换为灰度图gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 使用中值滤波去除噪声median_filtered cv2.medianBlur(gray, 5)# 计算图像的梯…

【Python学习笔记】:Python爬取音频

【Python学习笔记】&#xff1a;Python爬取音频 背景前摇&#xff08;省流可以不看&#xff09;&#xff1a; 人工智能公司实习&#xff0c;好奇技术老师训练语音模型的过程&#xff0c;遂请教&#xff0c;得知训练数据集来源于爬取某网页的音频。 很久以前看B站同济子豪兄的《…

实验三:图像的平滑滤波

目录 一、实验目的 二、实验原理 1. 空域平滑滤波 2. 椒盐噪声的处理 三、实验内容 四、源程序和结果 (1) 主程序&#xff08;matlab&#xff09; (2) 函数GrayscaleFilter (3) 函数MeanKernel (4) 函数MedFilter 五、结果分析 1. 空域平滑滤波 2. 椒盐噪声的处理…

【数据结构与算法 经典例题】判断二叉树是否对称

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法 经典例题》C语言 期待您的关注 目录 一、问题描述 二、解题思路 三、C语言实现代码 一、问题描述 给你一个二…

AI论文精读笔记-MAE

1. 论文基本信息 论文标题&#xff1a;Masked Autoencoders Are Scalable Vision Learners 作者&#xff1a;Kaiming He∗,† Xinlei Chen∗ Saining Xie Yanghao Li Piotr Doll ́ ar Ross Girshick 发表时间和期刊&#xff1a;19 Dec 2021; arxiv 论文链接&#xff1a;Mas…

【大模型书籍】从零开始大模型开发与微调:基于PyTorch与ChatGLM(附PDF)

哈喽各位&#xff0c;今天又来给大家分享大模型学习书籍了&#xff0c;今天是这本<从零开始大模型开发与微调&#xff1a;基于PyTorch与ChatGLM 书籍PDF分享>&#xff0c;大模型是深度学习自然语言处理皇冠上的一颗明珠&#xff0c;也是当前AI和NLP研究与产业中最重要的方…

HTML+CSS+JS用户管理(可储存用户数据)

使用cookies记录账号密码信息&#xff0c;可以注册、登录、注销账号。 点赞❤️收藏⭐️关注&#x1f60d; 效果图 源代码在效果图后面 源代码 HTML <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <…

docker desktop历史版本安装

1.安装choco Windows安装 choco包管理工具-CSDN博客 2.通过choco安装 下面例子为安装旧版2.3.0.2,其它版本类似 Chocolatey Software | Docker Desktop 2.3.0.2 https://download.docker.com/win/stable/45183/Docker%20Desktop%20Installer.exe choco install docker-des…

前端报错adding CSS “touch-action: none“ to this element解决方案

目录 如图所示控制台出现报错&#xff1a; 原因&#xff1a; touch-action 介绍&#xff1a; 解决方案&#xff1a; 1.手动设置touch-action&#xff1a; 2.使用条件渲染&#xff1a; 3.CSS样式隔离&#xff1a; 4.浏览器兼容性&#xff1a; 5. 忽略警告 如图所示控制台…

Windows虚拟摄像头有哪些?分享5个方法,手机一键替换电脑摄像头!

Windows虚拟摄像头怎么用&#xff1f;当我们的电脑缺少摄像头时&#xff0c;我们可以借助虚拟摄像头实现。手机作为电脑摄像头的需求日益增长&#xff0c;本文就来给大家详细介绍&#xff0c;关于电脑虚拟摄像头的使用和说明&#xff0c;一起来看看吧&#xff01; &#xff08;…

微服务实战系列之玩转Docker(二)

前言 上一篇&#xff0c;博主对Docker的背景、理念和实现路径进行了简单的阐述。作为云原生技术的核心之一&#xff0c;轻量级的容器Docker&#xff0c;受到业界追捧。因为它抛弃了笨重的OS&#xff0c;也不带Data&#xff0c;可以说&#xff0c;能够留下来的都是打仗的“精锐…

Vue3 前置知识

1. Vue3 简介 2020年9月18日&#xff0c;Vue.js发布版3.8版本&#xff0c;代号&#xff1a;one Piece(海贼王)经历了&#xff1a;4800次提交、40个RFC、600次PR、300贡献者官方发版地址&#xff1a;Release v3.0.0 One Piecevuejs/,core截止2023年10月&#xff0c;最新的公开版…