说三道四技术文摘-感悟人生的经典句子
说三道四 > 文档快照

C语言指针(上)-C语言教程

HTML文档下载 WORD文档下载 PDF文档下载
指针是C语言中广泛使用的一种数据类型。运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构;能很方便地使用数组和字符串;并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。学习指针是学习C语言中最重要的一环,能否正确理解和使用指针是我们是否掌握C语言的一个标志。

 

   指针是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。

 

2) 最后一行的*pointer_1和*pointer_2就是变量a和b。最后两个printf函数作用是相同的。

3) 程序中有两处出现*pointer_1和*pointer_2,请区分它们的不同含义。

4) 程序第5、6行的“pointer_1=&a”和 “pointer_2=&b”不能写成“*pointer_1=&a”和 “*pointer_2=&b”。

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

1) 如果已经执行了“pointer_1=&a;”语句,则&*pointer_1是什么含义?

2) *&a含义是什么?

3) (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的值。

2. 指针变量的运算

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是什么意思呢?毫无实际意义。

两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。

例如:

pf1==pf2表示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个元素。

 

2) *(p+i)或*(a+i)就是p+i或a+i所指向的数组元素,即a[i]。例如,*(p+5)或*(a+5)就是a[5]。

3) 指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。

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

1) 下标法,即用a[i]形式访问数组元素。在前面介绍数组时都是采用这种方法。

2) 指针法,即采用*(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++);}

3) 从上例可以看出,虽然定义数组时指定它包含10个元素,但指针变量可以指到数组以后的内存单元,系统并不认为非法。

4) *p++,由于++和*同优先级,结合方向自右而左,等价于*(p++)。

5) *(p++)与*(++p)作用不同。若p的初值为a,则*(p++)等价a[0],*(++p)等价a[1]。

6) (*p)++表示p所指向的元素值加1。

7) 如果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。

 

4) 函数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)

{

 ……

}

 

}

3) 实参、型参都用指针变量。

4) 实参为指针变量,型参为数组名。

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

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列元素的值。

二维数组指针变量说明的一般形式为:

类型说明符  (*指针变量名)[长度]

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

【例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");}}

 

 

 

成为糟糕游戏设计师的五种打开方式 Stripe使用指南:Swift开发中的信用卡支付 将安全延伸至云端以及更远的地方 专访FreeWheel北京研发中心:一流技术型人才应具有国际化视野 iOS UIView动画实践(五):Keyframe Animation 【CTO讲堂】简说创业公司的技术选型,从BearyChat的开发说起 《近匠》Webix,跨浏览器的JavaScript UI组件库 微软正式推送Windows 10,你会升级最后一款Windows产品? Cinatra:高性能现代C++ Web框架 《移动App测试实战》作者邱鹏:我在测试过程中遇到的那些坑 Rust一周精彩回顾:《高级Rust编程语言》、新增13名贡献者 来自Sony Morpheus的开发灵感: 如何打造逼真的VR游戏? 如何像巫师那样隔空操作——聊聊迷你雷达的原理和应用 等你来战——IBM Cloudant数据库开发大赛火热开启! 《近匠》360加固保,应用安全攻防的核心实现 DEFCON黑客大会抢鲜看:你所不知道的黑客大对决 MDCC2015中国移动开发者大会启动 七场专题技术论坛公布 【CTO讲堂】如何构建高性能网络服务平台及生态系统? mysql备份数据库批处理 《近匠》Remix周哲,让Android运行在“电脑”上! TIOBE 2015年8月编程语言排行榜:Java第一,Objective-C跌出前五 Oculus PC SDK 0.7版即将发布,含新Direct Driver Mode 【CTO讲堂】NoSQL对未来大数据发展的意义何在? 从Java转向Go——AeroFS的一段奇妙之旅 谷歌重组建母公司Alphabet,新来的CEO Sandar Pichai是什么来头? 《近匠》专访AllSeen联盟主席薛国栋:从通讯层为物联网去中心化 Android性能优化典范(三) 杭州兆合陈洋:微气象大民生,气象微信平台研发探索 【探秘ES6】系列专栏(十):更深入了解生成器 【探秘ES6】系列专栏(十一):类语法 《近匠》专访Depth VR:浅析VR交互技术选型中的瓶颈与机遇 怎样用NMFTP象网络蚂蚁那样自动获得下载文件的地址 特酷的串口通讯调试工具,有其它工具... 有没有win32版的mysql JDBC驱动? 郁闷!菜鸟的“关于数据库订购与发布的问题” :( 如何使子类的属性不可见 我想画曲线,类似log曲线的样子,不知道vc里面怎样才能方便的实现,有源码最好 特酷的串口通讯调试工具,有其它工具... 请问如何用电话线将各分公司连网,并实现数据上传功能,以保证总公司的数据汇总,CSDN的高手到那里去了,我已经第二次提了,没有一个人回 请指教! 特酷的串口通讯调试工具,有其它工具... 菜得不得了的 问题,从SQL SERVER中导入一个表(如表名QK)到ORACLE后,不知表名变成什么? 怎样看到PowerBuilder User’s Guide使用手冊。Application Techniques使用手冊呀? 一直想看古天乐的《创世纪》,请哪位提供一下网上哪里有它下载或在线看网址,不甚感激! 为什么一执行文件ss.txt得内容就清空了??? 中文问题 特酷的串口通讯调试工具,有其它工具... sos sos 求,向access2000中写数据的asp代码?和用asp连接access有几种方法? 如何保存xml的网页,谢谢! 如何声明一个变量,可在整个程序内调用,并其生命周期和程序一样长??? 请推荐一本数据结构的书,最好是大学教材,c++描叙,写了 求助:vc++ 6.0中,用向导生成了一个windows程序, 我又新加了一个工具条,怎样让它和以前的工具条在同一行上? 请问VIA VT82C686A芯片的主板最大支持的CPU是多少?? 怎样看vb中控件的代码?如:from,command... 大家帮我看看这段代码哪里错了???? 在jbuilder6下如何存取中文数据库,内码的转换如何实现?高手请进,100分奉上 请问如何在dbgrid控件中 第3列=第2列×第1列 有什么方法可以获得页面信息 菜鸟请教一个复杂结构类型结构体成员变量初始化赋值出错的问题。 CDC实现图片运动问题,请大家多多帮忙?如何擦掉已经画出的图片 那里有Delphi函数表?API的个人理解,希望修正!(新人学习在过程中的问题) exchange的路由器问题 WinFax的烦事。 请问IsProxyClass函数在哪找 请问怎么才能实现将一个datawindow中的某一列拖放到另外一个同样的datawindw中 有报考今年3月份高教自考《计算机及应用》专业的朋友吗? 我要找C!各位帮忙! 为什么在MS_DOS下运行MySQL提示"This program cannot be run in DOS mode"? 关于 Execute 为什么在VS.net beta2开发的东西,在安装正式版后运行不了 请问 有谁知道《c++ primer》那里可以下载?? 怎么不能删除啊???????? 同居愉快! 请问:dos环境下c语言中有没有多线程?如果没有那么飞机游戏中不同的飞机同时运动怎么办. php的安装问题!!!! 为什么我安装PB8后只显示支持INFORMIX V9。0而不支持V7。0 pb怎么从数据库中取出text类型数据 服务器设置问题 请问哪里能下载到游戏开发的入门书籍,或者介绍经典的书 很久没有来这里了,散分道歉~~~ 声现象,初中物理一个人在窗前用望远镜观察到远处建筑架上工人以每秒1次的频率敲打钢钉,他听到打击声恰好与看到击锤的动作一致,当工人停止击锤后,他又听到了2次锤击声,工人离观察者的 实验室用加热无水醋酸钠和固体氢氧化钠混合物的方法制取甲烷,反应生成气体CH4CH4用什么方法收集?发生装置和制取哪种气体的装置相同? 喜怒哀乐中的喜和乐有什么区别? 有没有初中生关于保护校园环境的英语作文 喜马拉雅山脉是什么板块和什么板块碰撞挤压形成? 北京到上海的距离约为1000km,假设声音在空气中能传播这么远,那么声音从北京传到上海需要多长时间 靠空气柱振动发声的弦乐器有什么? 喜马拉雅山是哪俩个版块挤压形成的 北京到上海的距离约为1000km假设声音在空气中能传那么远那么从北京传到上海需要多长时间?火车从北京到上海需要多长时间?大型喷气式客车机呢?2丶用超声测位仪向海底垂直发射声波,经过4s 初二物理问题 空气柱四个玻璃杯,里面都有高度不同的水(从大到小ABCD排),用细棒敲打杯子,能发出1、2、3、4四个音阶,对应的杯子?如果是往里面吹气呢?敲打与什么有关?吹气又与什么有关 解释鲁迅《雪》中的词语1、【朔方】的雪花在纷飞之后,群却永远如粉,如沙.2、他也就【目光灼灼】地嘴唇通红地坐在雪地里.3、晴天又来【消逝】他皮肤. “雪是怎样形成的”故事内容讲解 小学三年级科学 如何比较水和油的轻重?水和油谁轻谁重? 鲁迅《雪》 第一段阅读 (最好有讲解,“江南的雪”有什么特点?你能看出 描写江南雪景的句子中倾注作者怎 样的情感? “雪是怎样形成的”故事 一个声源在震动,但 人听不到声音,关于产生这种现象的可能原因,有哪几种认真回答给悬赏 作文家乡的环境怎么写 故事:开国大典发生的时间、时期、人物、地点、讲得主要内容现在就要 镁在二氧化碳中燃烧是氧化反应吗? 家乡的环境的作文 我家发生了很恐怖的事情,在我读初三那年,我妈妈晚上睡觉时,听到床边有喘息声,把电灯开亮就小声点,把电灯关了又大声了,楼下有人拿着杯子涮牙刷,但又没听到倒水的声音,就只是牙刷碰杯 女同学说话的声音“尖细”,是指女同学发出的声音的____高,这是因为女生说话时声带振动比较____的缘故物理 喜马拉雅山不断( ),是由于( )和( )板块挤压形成 在初二物理书升华与凝华中有一个例子是沙漠中的雪瞬间不见,这里是中国最干旱的地区,年降水量只有13毫米,蒸发量高达4000毫米 .气温一直是零下14度 物质的量为0.10mol的镁条在只含有二氧化碳和氧气的混合气体的容器中燃烧(产物不含碳酸镁)反应后容器内固体的质量不可能为A3.2 B4.0 C4.2 D4.6答案是D哦 有人认为只有静止或做匀速直线运动的物体才具有惯性,做变速运动的物体不具有惯性,这种看法对吗? 如果让落叶归根为什么能使土壤里的水分蒸发变慢答案越快越好 下列各组混合物的物质的量均为a mol,组内物质按任意比例混合,完全燃烧时消耗O2的物质的量不变的是( ).A.甲烷、甲醇、甲醛    B.乙炔、苯、1,3-丁二烯C.丙烯、2-丁烯 D.乙醇、乙 喜马拉雅山是由( )挤压形成的 谁有 赵本山落叶归根 如果我的家乡的mp3?就那个如果我的家乡是一片大海,我就是一条小鱼,我游啊游,我多快乐!如果我的家乡是一条大路,我就是一辆汽车,我跑啊跑,我多快乐!如果我的家乡是 请观察黄土高原生态建设示意图,将措施与对应地貌类型连接起来,并说出生态建设的好处.建挡土坝,护坡被等.缓坡地(山顶地带)营造灌木丛,种植山杏等.陡坡地(山腰地带)建梯田,种植山 惯性可以保持物体做加速直线运动的状态么?做匀加速直线运动的物体,撤掉外力后的瞬间因为惯性不是可以继续做加速直线运动吗?为什么说惯性是物体保持匀速直线运动或静止状态的性质,而 对于落叶归根你有什么看法 喜马拉雅山脉是由 A印度洋板块与亚欧板块碰撞形成? 物体做变速运动就一定有外力作用 落叶归根怎么做 喜马拉雅山脉的形成因为()A.非洲板块与亚欧板块漂移分离B.亚欧板块与印度洋板块碰撞挤压C.亚欧板块与印度洋板块漂移分离 雪霁什么意思 制取甲烷的时候为什么要用高锰酸钾制取氧气的装置 为什么印度洋板块与亚欧板块碰撞,形成的喜马拉雅山脉以北是高原呢碰撞后形成喜马拉雅山脉,山脉南部是平原,北部却是海拔超过5000米的青藏高原呢 雪霁 青藏高原的隆起对我国东北地区气候的影响 喜马拉雅山脉是亚欧板块和印度洋板块碰撞形成的,但是又说是两个大陆板块碰撞形成的,那印度洋板块不是大洋板块吗?另一个问题,为什么北大西洋暖流不是终年受到西风控制的? 从板块构造理论看,与喜马拉雅山脉形成有关的两大板块是 A.亚欧板块和太平洋板块B.太平洋板块和印度洋板块C.印度洋板块和非洲板块D.印度洋板块和亚欧板块 设计两个关于“家乡环境建设过程中存在的问题“的调查问题 保护家乡的环境750字作文 急15号下午3点前 物体运动状态改变的难易程度取决于惯性的大小 对么? 北京到上海的距离为1000km假设声音能在空气中能够传这么远那么火车从北京到上海要多长时间,大型喷气飞机 初中物理的声现象声音的振动频率决定了______A声音的传播远近B声音的高低为什么写A错了,声音的振幅才是响度 惯性的大小决定其运动状态改变的难易程度?如题 不要太深奥啦! 喜怒哀乐中的喜和乐是不是重复了?我觉得喜和乐都是一个意思,人的情绪里还有一种叫恐惧.叫喜怒哀恐是不是更合适. 一个人在窗前用望远镜观察到远处建筑架上工人以每秒1次的频率敲打钢钉,他听到打击声恰好与看到击锤的动作一致,当工人停止击锤后,他又听到了2次锤击声,工人离观察者的距离约为_______米 尤袤的古诗雪的解释 “喜怒哀乐”中的喜和乐有什么区别? 1如何证明桌子发声时也在震动?2北京到上海的距离是1000km.假设声音在空气中能够传到那么远,声音从北京传到上海要多长时间?火车从北京到上海要多长时间?大型喷气式客机呢?自己查所需数 双鬓堆雪 喜怒哀乐这个词里,喜和乐的区别是什么 急救~谁能告诉我用物理的知识说明生活中的声现象(说一种声现象)可以不可以说点新鲜的~ 找几带雪的诗,找带雪的诗,越多越好,不带雪还是写雪,都行, “喜怒哀乐”中的喜和乐有何区别?求权威的解答.
备案号:鲁ICP备13029499号-2 说三道四 www.s3d4.cn 说三道四技术文摘