WEB开发网
开发学院软件开发C++ C语言程序设计(第6章 指针)01 阅读

C语言程序设计(第6章 指针)01

 2008-03-08 12:27:26 来源:WEB开发网   
核心提示: 第6章 指 针 指针是C语言的精华部分,通过利用指针,C语言程序设计(第6章 指针)01,我们能很好地利用内存资源,使其发挥最大的效率,调用子程序并输出运行结果,sub_max( )函数完成对数组元素找最大的过程,有了指针技术,我们可以描述复杂的数据结构

                      第6章  指 针

   指针是C语言的精华部分,通过利用指针,我们能很好地利用内存资源,使其发挥最大的效率。有了指针技术,我们可以描述复杂的数据结构,对字符串的处理可以更灵活,对数组的处理更方便,使程序的书写简洁,高效,清爽。但由于指针对初学者来说,难于理解和把握,需要一定的计算机硬件的知识做基础,这就需要多做多练,多上机动手,才能在实践中尽快把握,成为C的高手。

6.1 指针与指针变量
6.2 指针变量的定义与引用
   6.2.1 指针变量的定义
   6.2.2 指针变量的引用
6.3 指针运算符与指针表达式
   6.3.1 指针运算符与指针表达式
   6.3.2 指针变量作函数的参数
6.4 指针与数组
   6.4.1 指针与一维数组
   6.4.2 指针与二维数组
   6.4.3 数组指针作函数的参数
  
  

6.1 指针与指针变量
   过去,我们在编程中定义或说明变量,编译系统就为已定义的变量分配相应的内存单元,也就是说,每个变量在内存会有固定的位置,有具体的地址。由于变量的数据类型不同,它所占的内存单元数也不相同。若我们在程序中做定义为:
int a=1, b=2;
float x=3.4, y = 4 . 5 ;
double m=3.124;
char ch1='a', ch2='b';
   让我们先看一下编译系统是怎样为变量分配内存的。变量a , b是整型变量,在内存各占2个字节;x , y是实型,各占4个字节;m是双精度实型,占8个字节; ch1 , ch2是字符型,各占1个字节。由于计算机内存是按字节编址的,设变量的存放从内存2000单元开始存放,则编译系统对变量在内存的安放情况为图6 - 1所示。
C语言程序设计(第6章 指针)01(图一)    变量在内存中按照数据类型的不同,占内存的大小也不同,都有具体的内存单元地址,如变量a 在内存的地址是200 0,占据两个字节后,变量b的内存地址就为2002,变量m的内存地址为2012等。对内存中变量的访问,过去用
scanf("%d%d%f",&a,&b,&x) 表示将数据输入变量的地址所指示的内存单元。那么,访问变量,首先应找到其在内存的地址,或者说,一个地址唯一指向一个内存变量,我们称这个地址为变量的指针。假如将变量的地址保存在内存的特定区域,用变量来存放这些地址,这样的变量就是指针变量,通过指针对所指向变量的访问,也就是一种对变量的“间接访问”。
设一组指针变量pa、pb、px、py、pm、pch1、pch2,分别指向上述的变量a、b、x、y、m、ch1、ch2,指针变量也同样被存放在内存,二者的关系如图6 - 2所示:在图6 - 2中,左部所示的内存存放了指针变量的值,该值给出的是所指变量的地址,通过该地址,就可以对右部描述的变量进行访问。如指针变量pa的值为2000,是变量a在内存的地址。因此, p a就指向变量a。变量的地址就是指针,存放指针的变量就是指针变量。
C语言程序设计(第6章 指针)01(图二)

6.2 指针变量的定义与引用
6.2.1 指针变量的定义
   在C程序中,存放地址的指针变量需专门定义;
int *ptr1;
float *ptr2;
char *ptr3;
   表示定义了三个指针变量ptr1、ptr2、ptr3。ptr1可以指向一个整型变量, ptr2可以指向一个实型变量,ptr3可以指向一个字符型变量,换句话说, ptr1、ptr2、ptr3可以分别存放整型变量的地址、实型变量的地址、字符型变量的地址。
   定义了指针变量,我们才可以写入指向某种数据类型的变量的地址,或者说是为指针变量赋初值:
int *ptr1,m= 3;
float *ptr2, f=4.5;
char *ptr3, ch='a';
ptr1 = &m ;
ptr2 = &f ;
ptr3 = &ch ;
   上述赋值语句ptr1 = &m表示将变量m的地址赋给指针变量ptr1,此时ptr1就指向m。三条赋值语句产生的效果是ptr1指向m;ptr2指向f;ptr3指向ch 。用示意图6 - 3描述如下:

C语言程序设计(第6章 指针)01(图三)    需要说明的是,指针变量可以指向任何类型的变量,当定义指针变量时,指针变量的值是随机的,不能确定它具体的指向,必须为其赋值,才有意义。

6.2.2 指针变量的引用
   利用指针变量,是提供对变量的一种间接访问形式。对指针变量的引用形式为:
*指针变量
其含义是指针变量所指向的值。
[例6-1] 用指针变量进行输入、输出。
main( )
{
   int *p,m;
   scanf("%d" , &m);
   p = &m ; / *指针p指向变量m * /
   PRintf("%d",*p);
/* p是对指针所指的变量的引用形式,与此m意义相同* /
}
运行程序:
3
3
上述程序可修改为:
main( )
{
   int *p,m;
   p = &m ;
   scanf("%d" , p); /* p是变量m的地址,可以替换& m * /
   printf("%d", m);
}
运行效果完全相同。请思考一下若将程序修改为如下形式:
main( )
{
   int *p,m;
   scanf("%d" , p);
   p = &m ;
   printf("%d", m);
}
会产生什么样的结果呢?事实上,若定义了变量以及指向该变量的指针为:
int a,*p;
若p=&a; 则称p指向变量a,或者说p具有了变量a的地址。在以后的程序处理中,凡是可
以写&a的地方,就可以替换成指针的表示p,a就可以替换成为*p。

6.3 指针运算符与指针表达式
6.3.1 指针运算符与指针表达式
   在C中有两个关于指针的运算符:
? &运算符: 取地址运算符,& m即是变量m的地址。
? *运算符:指针运算符, * ptr表示其所指向的变量。

[例6-2] 从键盘输入两个整数,按由大到小的顺序输出。
main( )
{
   int *p1,*p2,a,b,t; / * 定义指针变量与整型变量* /
   scanf("%d , %d" , &a , &b);
   p1 = &a; / *使指针变量指向整型变量* /
   p2 = &b;
   if (*p1 < *p2)
   { / *交换指针变量指向的整型变量* /
   t = *p1;
   *p1 = *p2;
   *p2 = t;
}
printf("%d, %d " , a , b);
}
   在程序中,当执行赋值操作p1 = &a和p2 = &b后,指针实实在在地指向了变量a与b,这时引用指针*p1与*p2,就代表了变量a与b。
运行程序:
3 , 4
   在程序运行过程中,指针与所指的变量之间的关系如图6 - 4所示:
C语言程序设计(第6章 指针)01(图四)

   当指针被赋值后,其在内存的安放如a ),当数据比较后进行交换,这时,指针变量与所指向的变量的关系如b )所示,在程序的运行过程中,指针变量与所指向的变量其指向始终没变。
下面对程序做修改。
[例6 - 3 ]
main( )
{
   int *p1,*p2,a,b,*t;
   scanf("%d , %d" , &a , &b);
   p1 = &a;
   p2 = &b;
   if(*p1 < *p2)
   { / *指针交换指向* /
     t = p1;
     p1 = p2;
     p2 = t;
   }
   printf("%d, %d ", *p1 , *p2);
}

   程序的运行结果完全相同,但程序在运行过程中,实际存放在内存中的数据没有移动,而是将指向该变量的指针交换了指向。其示意如图6 - 5:

C语言程序设计(第6章 指针)01(图五)
   当指针交换指向后, p 1和p 2由原来指向的变量a和b改变为指向变量b和a,这样一来, * p 1
就表示变量b,而* p 2就表示变量a。在上述程序中,无论在何时,只要指针与所指向的变量满
足p = & a;我们就可以对变量a 以指针的形式来表示。此时p等效于& a,* p等效于变量a 。

6.3.2 指针变量作函数的参数
   函数的参数可以是我们在前面学过的简单数据类型,也可以是指针类型。使用指针类型做函数的参数,实际向函数传递的是变量的地址。由于子程序中获得了所传递变量的地址,在该地址空间的数据当子程序调用结束后被物理地保留下来。
[例6-4] 利用指针变量作为函数的参数,用子程序的方法再次实现上述功能。
main( )
{
   void chang(); / *函数声明* /
   int *p1,*p2,a,b,*t;
   scanf("%d, %d", &a, &b);
   p1 = &a;
   p2 = &b;
   chang(p1 , p2); / *子程序调用* /
   printf("%d, %d " , *p1, *p2);
   return 0;
}
void chang(int *pt1,int *pt2)
{ / *子程序实现将两数值调整为由大到小* /
   int t;
   if (*pt1<*pt2) / *交换内存变量的值* /
   {
      t=*pt1; *pt1=*pt2; * pt2 = t;
   }
   return;
}
   由于在调用子程序时,实际参数是指针变量,形式参数也是指针变量,实参与形参相结合,传值调用将指针变量传递给形式参数pt1和pt2。但此时传值传递的是变量地址,使得在子程序中pt1和pt2具有了p1和p2的值,指向了与调用程序相同的内存变量,并对其在内存存放的数据进行了交换,其效果与[例6 - 2 ]相同。
思考下面的程序,是否也能达到相同的效果呢?
main( )
{
   void chang();
   int *p1,*p2,a,b,*t;
   scanf("%d,%d", &a, &b);
   p1 = &a;
   p2 = &b;
   chang(p1, p2);
   printf("%d, %d " , *p1, *p2);
}

void chang(int *pt1,int *pt2)
{
   int *t;
   if (*pt1<*pt2)
   {
     t=pt1; pt1=pt2; p t 2 = t ;
   }
   return;
}
   程序运行结束,并未达到预期的结果,输出与输入完全相同。其原因是对子程序来说,函数内部进行指针相互交换指向,而在内存存放的数据并未移动,子程序调用结束后,main( )函数中p 1和p 2保持原指向,结果与输入相同。

6.4 指针与数组
   变量在内存存放是有地址的,数组在内存存放也同样具有地址。对数组来说,数组名就是数组在内存安放的首地址。指针变量是用于存放变量的地址,可以指向变量,当然也可存放数组的首址或数组元素的地址,这就是说,指针变量可以指向数组或数组元素,对数组而言,数组和数组元素的引用,也同样可以使用指针变量。下面就分别介绍指针与不同类型的数组。
6.4.1 指针与一维数组
   假设我们定义一个一维数组,该数组在内存会有系统分配的一个存储空间,其数组的名字就是数组在内存的首地址。若再定义一个指针变量,并将数组的首址传给指针变量,则该指针就指向了这个一维数组。我们说数组名是数组的首地址,也就是数组的指针。而定义的指针变量就是指向该数组的指针变量。对一维数组的引用,既可以用传统的数组元素的下标法,也可使用指针的表示方法。
int a[10] , *ptr; /* 定义数组与指针变量* /
做赋值操作:ptr=a; 或ptr = &a[0];
则ptr就得到了数组的首址。其中, a是数组的首地址, &a[0]是数组元素a[0]的地址,由于a[0]的地址就是数组的首地址,所以,两条赋值操作效果完全相同。指针变量ptr就是指向数组a的指针变量。
   若ptr指向了一维数组,现在看一下C规定指针对数组的表示方法:
   1) ptr+n与a + n表示数组元素a[n]的地址,即&a[n] 。对整个a数组来说,共有10个元素, n的取值为0~9,则数组元素的地址就可以表示为ptr + 0~ptr + 9或a + 0~a + 9,与&a[0] ~&a[9]保持一致。
   2) 知道了数组元素的地址表示方法, *(ptr + n)和*(a+n)就表示为数组的各元素即等效于a[n]。
   3) 指向数组的指针变量也可用数组的下标形式表示为ptr[n],其效果相当于*(ptr + n)。

[例6-5] /*以下标法输入输出数组各元素。
下面从键盘输入10个数,以数组的不同引用形式输出数组各元素的值。
# include <stdio.h>
main( )
{
   int n,a[10],*ptr=a;
   for(n = 0; n<=9; n++)
   scanf("%d" , &a[n]);
   printf("1------output! ");
   for(n = 0; n<=9; n++)
   printf("%4d", a[n]);
   printf(" ");
}
运行程序:
1 2 3 4 5 6 7 8 9 0
1------output!
1 2 3 4 5 6 7 8 9 0

[例6-6] 采用指针变量表示的地址法输入输出数组各元素。
#include<stdio.h>
main( )
{
   int n,a[10],*ptr=a; / *定义时对指针变量初始化* /
   for(n = 0; n<=9; n++)
   scanf("%d", ptr + n);
   printf("2------output! ");
   for(n=0; n<=9; n++)
   printf("%4d", *(ptr+n));
   printf(" ");
}
运行程序:
1 2 3 4 5 6 7 8 9 0
2------output!
1 2 3 4 5 6 7 8 9 0

[例6-7] 采用数组名表示的地址法输入输出数组各元素。
main( )
{
   int n,a[10],*ptr=a;
   for(n = 0; n < = 9; n ++)
   scanf("%d", a+n);
   printf("3------output! ");
   for(n = 0; n<=9; n++)
   printf("%4d", *(a+n));
   printf(" ");
}
运行程序:
1 2 3 4 5 6 7 8 9 0
3------output!
1 2 3 4 5 6 7 8 9 0

[例6-8] 用指针表示的下标法输入输出数组各元素。
main( )
{
   int n,a[10],*ptr=a;
   for(n = 0; n<=9; n++)
   scanf("%d", &ptr[n]);
   printf("4------output! ");
   for(n = 0; n<=9; n++)
   printf("%4d", ptr[n]);
   printf(" ");
}
运行程序:
1 2 3 4 5 6 7 8 9 0
4----output!
1 2 3 4 5 6 7 8 9 0

[例6-9] 利用指针法输入输出数组各元素
main( )
{
   int n,a[10],*ptr=a;
   for(n = 0; n<=9; n++)
   scanf("%d", ptr++);
   printf("5------output! ");
   ptr = a; / *指针变量重新指向数组首址* /
   for(n = 0; n<=9; n++)
   printf("%4d", *ptr++);
   printf(" ");
}
运行程序:
1 2 3 4 5 6 7 8 9 0
5-----output!
1 2 3 4 5 6 7 8 9 0
   在程序中要注重*ptr++所表示的含义。*ptr表示指针所指向的变量; ptr++表示指针所指向的变量地址加1个变量所占字节数,具体地说,若指向整型变量,则指针值加2,若指向实型,则加4,依此类推。而printf(“%4d”, *ptr+ +)中,*ptr++所起作用为先输出指针指向的变量的值,然后指针变量加1。循环结束后,指针变量指向如图6 - 6所示:

C语言程序设计(第6章 指针)01(图六)
指针变量的值在循环结束后,指向数组的尾部的后面。假设元素a[9]的地址为1000,整型占2字节,则ptr的值就为100 2。请思考下面的程序段:
main( )
{
   int n,a[10],*ptr=a;
   for(n = 0; n<=9; n++)
   scanf("%d", ptr++);
   printf("4------output! ");
   for(n = 0; n<=9; n++)
   printf("%4d", *ptr++);
   printf(" ");
}
程序与例6 - 9相比,只少了赋值语句p t r = a;程序的运行结果还相同吗?

6.4.2 指针与二维数组
   定义一个二维数组:
   int a[3][4];
   表示二维数组有三行四列共1 2个元素,在内存中按行存放,存放形式为图6 - 7:
C语言程序设计(第6章 指针)01(图七)
   其中a是二维数组的首地址, &a[0][0]既可以看作数组0行0列的首地址,同样还可以看作是二维数组的首地址, a [0]是第0行的首地址,当然也是数组的首地址。同理a[n]就是第n行的首址;&a[n][m]就是数组元素a[n][m]的地址。
既然二维数组每行的首地址都可以用a[n]来表示,我们就可以把二维数组看成是由n行一维数组构成,将每行的首地址传递给指针变量,行中的其余元素均可以由指针来表示。图6 - 8给出了指针与二维数组的关系。
   我们定义的二维数组其元素类型为整型,每个元素在内存占两个字节,若假定二维数组从1000单元开始存放,则以按行存放的原则,数组元素在内存的存放地址为1000~1022。
用地址法来表示数组各元素的地址。对元素a[1][2],&a[1][2]是其地址, a[1] + 2也是其地址。分析a[1] + 1与a [1] + 2的地址关系,它们地址的差并非整数1,而是一个数组元素的所占位置2,原因是每个数组元素占两个字节。
对0行首地址与1行首地址a与a + 1来说,地址的差同样也并非整数1,是一行,四个元素占的字节数8。由于数组元素在内存的连续存放。给指向整型变量的指针传递数组的首地址,则该指针指向二维数组。
int *ptr, a[3][4];
若赋值: p t r = a;则用ptr++ 就能访问数组的各元素。

[例6-10] 用地址法输入输出二维数组各元素。
#include <stdio.h>
main( )
{
   int a[3][4];
   int i,j;
   for(i = 0; i<3; i++)
   for(j = 0; j<4; j++)
   scanf("%d",a[i]+j); / *地址法* /
   for(i = 0; i<3; i++)
   {
     for(j = 0; j<4; j++)
     printf("%4d",*(a[i]+j)); /* *(a[i]+j) 是地址法所表示的数组元素* /
     printf(" ");
   }
}
运行程序:
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4
5 6 7 8
9 1 0 1 1 1 2

[例6 - 11] 用指针法输入输出二维数组各元素。
#include<stdio.h>
main( )
{
   int a[3][4],*ptr;
   int i,j;
   ptr = a[0];
   for(i = 0; i<3; i++)
   for(j = 0; j< 4; j++)
   scanf("%d", ptr++); / *指针的表示方法* /
   ptr = a[0];
   for(i = 0; i<3; i++)
   {
     for(j = 0; j<4; j++)
     printf("%4d", *ptr++);
     printf(" ");
  }
}
运行程序:
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4
5 6 7 8
9 1 0 1 1 1 2
对指针法而言,程序可以把二维数组看作展开的一维数组:
main( )
{
   int a[3][4],*ptr;
   int i,j;
   ptr = a[0];
   for(i = 0; i< 3; i++)
     for(j = 0; j < 4; j++)
       scanf("%d", ptr++); / * 指针的表示方法* /
  ptr = a[0];
  for(i = 0; i < 12;i++)
  printf("%4d", *ptr++);
   printf(" ");
}
运行程序:
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 1 0 1 1 1 2

6.4.3 数组指针作函数的参数
   学习了指向一维和二维数组指针变量的定义和正确引用后,我们现在学习用指针变量作函数的参数。

[例6-12] 调用子程序,实现求解一维数组中的最大元素。
我们首先假设一维数组中下标为0的元素是最大和用指针变量指向该元素。后续元素与该元素一一比较,若找到更大的元素,就替换。子程序的形式参数为一维数组,实际参数是指向一维数组的指针。
#include <stdio.h>
main( )
{
   int sub_max(); / * 函数声明* /
   int n,a[10],*ptr=a; / *定义变量,并使指针指向数组* /
   int max;
   for(n = 0; n < = i - 1; n++) / *输入数据* /
   scanf("%d", &a[n]);
   max = sub_max(ptr, 10); / * 函数调用,其实参是指针* /
   printf("max = %d ", max);
}
int sub_max(b,i) / * 函数定义,其形参为数组* /
int b[],i;
{
   int temp,j;
   temp = b[0];
   for(j = 1; j < = 9; j++)
   if(temp<b[j]) temp=b[j];
     return temp;
}
   程序的main( )函数部分,定义数组a 共有1 0个元素,由于将其首地址传给了ptr,则指针变量ptr 就指向了数组,调用子程序,再将此地址传递给子程序的形式参数b,这样一来,b数组在内存与a 数组具有相同地址,即在内存完全重合。在子程序中对数组b 的操作,与操作数组a 意义相同。其内存中虚实结合的示意如图6 - 9所示。
main( )函数完成数据的输入,调用子程序并输出运行结果。sub_max( )函数完成对数组元素找最大的过程。在子程序内数组元素的表示采用下标法。运行程序:
1 3 5 7 9 2 4 6 8 0
max = 9

Tags:语言 程序设计 指针

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接