第八章指针
【本章要求】
1、指针的声明、定义方法以及通过指针变量引用存储单元中的内容
2、指针作为函数参数,地址传递情况下,函数实参的变化
3、用指针指向函数,并通过指针调用该函数
4、一维数组和字符串中元素地址的计算方法,以及通过指针引用这些元素
5、二维数组中元素的计算方法,以及通过指针数组引用其中元素的方法
6、通过命令行方式调用main函数
7、内存的动态分配和释放
8.1 指针的基本概念
如果在程序中定义了一个变量,在编译时就给这个变量分配内存单元。系统根据程序中定义的变量类型,分配一定长度的空间。内存区的每一个字节有—个编号,这就是“地址”,它相当于旅馆中的房间号。
在程序中一般是通过变量名来对内存单元进行存取操作的。程序经过编译以后己经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。
按变量地址存取变量值的方式称为“直接访问”方式。
还可以采用另一种称之为“间接访问”的方式,将变量i的地址存放在另一个变量中。按C 语言的规定,可以在程序中定义整型变量、实型变量、字符变量等,也可以定义这样一种特殊的变量,它是存放地址的。
假设我们定义了一个变量i_pointer,用来存放整型变量的地址,它被分配为3010、3011字节。可以通过下面语句将i的地址(2000)存放到i_pointer中。
i_pointer = &i;
这时,i_pointer的值就是2000,即变量i所占用单元的起始地址。要存取变量i的值,也可以采用间接方式;先找到存放“i的地址”的变量,从中取出i的地址(2000),然后到2000、2001字节取出i的值(3)。
所谓“指向”就是通过地址来体现的。i_pointer中的值为2000,它是变量i的地址,这样就在i_pointer和变量i之间建立起一种联系,即通过i_pointer能知道i的地址,从而找到变量i的内存单元。
在C语言中,将地址形象化地称为“指针”。意思是通过它能找到以它为地址的内存单元(例如根据地址2000就能找到变量i的存储单元,从而读取其中的值)。一个变量的地址称为该变量的“指针”。例如,地址2000是变量i的指针。如果有一个变量专门用来存放另一变量的地址(即指针),则它称为“指针变量”。指针变量的值(即指针变量中存放的值)是指针(地址)。
8.2 指针变量
变量的指针就是变量的地址。存放变量地址的变量是指针变量,用来指向另一个变量。为了表示指针变量和它所指向的变量之间的联系,在程序中用“*”符号表示“指向”。
- 93 -
《C语言程序设计》
8.2.1 指针变量的定义
C语言规定所有变量在使用前必须定义,指定其类型,并按此分配内存单元。指针变量不同于整型变量和其他类型的变量,它是用来专门存放地址的。必须将它定义为“指针类型”。先看一个具体例子:
int a, b;
int c1, c2;
第1行定义了两个整型变量a和b,第2行定义了两个指针变量: c1和c2,它们是指向整型变量的指针变量。左端的int是在定义指针变量时必须指定的“基类型”。指针变量的基类型用来指定该指针变量可以指向的变量的类型。
定义指针变量的一般形式为:
基类型*指针变量名
在定义指针变量时要注意两点:
1、指针变量前面的“*”,表示该变量的类型为指针型变量。注意:指针变量名足c1、c2,而不是*c1、*c2。
2、在定义指针变量时必须指定基类型。一个指针变量只能指向同—个类型的变量。
8.2.2 指针变量的赋值
可用运算符“&”求变量的地址。
可以用赋值语句使一个指针变量指向一个变量,例如:
a1=&x;
b2=&y;
也可以在定义指针变量的同时对其赋值,例如:
int x=3,y=4,*c1=&i,*c2=&j;
等价于
int x,y,*c1,*c2;
x=3;y=4;
c1=&x;
c2=&y;
在定义指针变量时应注意:
1、在定义指针变量时,指针变量的值是随机的,不能确定它具体的指向,必须为某赋值,才有意义。
2、指针变量的类型必须与其存放的变量类型一致,即只有变量的地址才能放到指向整型变量的指针变量中。
8.2.3指针的两个运算符
在C语言中有两个关于指针的运算符:
&-------取地址运算符;
*--------指针运算符
取地址运算符“&”可以加在变量和数组元素的前面,其意义是取出变量或数组元素的地址。
- 94 -
第八章指针
因为指针变量也是变量,所以取地址运算符也可以加在指针变量的前面,其含义是取出指针变量的地址。
【例8.1】输入a和b两个整数,按先大后小的顺序输出a和b。
main ( )
{
int *c1,*c2,*c,x,y;
scanf(“%d,%d”,&x,&y);
cl = &x;c2 = &y;
if(x printf(“\nx = %d,y = %d\n\n”,x,y); printf(“max = %d,min = %d\n”,*cl,*c2); } 运行情况如下: 5,9 x = 5,y = 9 max = 9,min = 5 当输入x=5,b=9时,由于x 指针运算符“*”可以加在指针或指针变量的前面,其意义是指针或指针变量所指向的内存单元。 8.2.4 指针变量的引用 指针变量中只能存放地址(指针).不要将一个整型量(或任何其他非地址类型的数据)赋给一个指针变量。 a_1= 100;(a_1为指针变量,100为整数) 有两个有关的运算符: 1、&:取地址运算符。 2、*:指针运算符(或称“间接访问”运算符)。 &x为变量x的地址,*c为指针变量c所指向的存储单元。 例如:通过指针变量访问整型变量。 main ( ) {int x,y; int *a_1,*a_2; x = 100;y = 10; a_1 = &x; a_2 = &y; printf(“%d,%d\n”,x,y); printf(“%d,%d\n”,*a_1,*a_2);} 运行结果为: 100,10 100,10 【说明】 - 95 - 《C语言程序设计》 1、在开头处虽然定义了两个指针变量a_l和a_2,但它们并未指向任何一个整型变量。只是提供两个指针变量,规定它们可以指向整型变量。 2、最后一行的*a_1和*a_2就是变量x和y。 3、程序中有两处出现*a_1和*a_2,它们有不同的含义。程序第3行的*a_l和*a_2表示定义两个指针变量a_1、a_2。它们前面的“*”只是表示该变量是指针变量。程序最后—行printf函数中的*a_1和*a_2则代表变量。 4、第 5、6行“a_1=&x;”和“a_2=&y;”是将x和y的地址分别赋给a_1和a_2。注意不应写成:“*a_1= &x;”和“a_2 = &y;”。 8.2.5 将指针变量作为参数时的传递 函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。 【例8.2】输入a和b两个整数,按先大后小的顺序输出a和b。 swap(int *cl,int *c2) { int temp; temp = *c1; *cl = *c2; *c2 = t; } main ( ) { int x,y; int *a_1,*a_2; scanf(“%d,%d”,&x,&y); a_1 = &x;a_2 =&y; if(x printf(“\n%d,%d\n”,x,y); ) 运行结果: 5,9 9,5 对程序的说明:swap是用户定义的函数,它的作用是交换两个变量(x和y)的值。swap函数的两个形参c1、c2是指针变量。程序运行时,先执行main函数,输入x和y的值(今输入5和9)。然后将x和y的地址分别赋给指针变量a_1和a_2,使a_1指向x,a_2指向y。接着执行if语句,由于x < y,因此执行swap函数。在函数调用时,将实参变量的值传送给形参变量。采取的依然是“值传递”方式。因此虚实结合后形参c1的值为&x,c2 请注意交换*cl和*c2的值是如何实现的。如果写成以下这样就有问题了: swap(int *c1,int *c2) {int *temp; *temp = *c1; *c1 =*c2; *c2=*temp; } - 96 - 第八章指针 *c1就是x,是整型变量。而*temp是指针变量temp所指向的变量。但temp中并无确定的地址值,它的值是不可预见的。*temp所指向的单元也是不可预见的。因此,对*temp赋值可能会破坏系统的正常工作状况。可以看到,在执行swap函数后,变量x和y的值改变了。 swap(int a,int b) {int temp; temp = a; a = b; b = temp; } 在函数调用时,x的值传送给a,y的值传送给b。执行完swap函数后,a和b的值是互换了,但main函数中的x和y并未互换。也就是说由于“单向传送”的“值传递”方式,形参值的改变无法传给实参。 如果想通过函数调用得到n个要改变的值,可以: (1)在主调函数中设n个变量,用n个指针变量指向它们; (2)然后将指针变量作实参,将这n个变量的地址传给所调用的函数的形参; (3)通过形参指针变量,改变该n个变量的值; (4)主调函数中就可以使用这些改变了值的变量。 请注意,不能企图通过改变指针形参的值而使指针实参的值改变。 swap(int *cl,int *c2) {int *c; c = cl; c1= c2; c2= c; } main( ) { int x,y; int *a_1,*a_2; scanf(“%d,%d”,&x,&y); a_1 = &x; a_2 = &y; if(x printf(“\n %d,%d\n”,*a_l,*a_2); } 此程序的意图是:交换a_1和a_2的值,使a_1指向值大的变量。但是这是办不到的,程序实际输出为“5,9”。C语言中实参变量和形参变量之间的数据传递是单向的“值传递”方式。指针变量作函数参数也要遵循这—规则。调用函数不可能改变实参指针变量的值,但可以改变实参指针变量所指变量的值。 【例8.3】输入a、b、c3个整数,按大小顺序输出。 swap(int *pc1,int *pc2) { int temp; temp =* pcl; *pt1 =*pc2; *pc2 = temp; - 97 - 《C语言程序设计》 - 98 - } exchange(int *al,int *a2,int *a3) { if(*al<*a2)swap(al,a2); if(*al<*a3)swap(al,a3); if(*a2<*a3)swap(a2,a3); } main ( ) { int x,y,z,*p1,*p2,*p3; scanf(“%d,%d,%d”,&x,&y,&z); pl = &a ;p2=&y;p3 = &z; exchange(p1,p2,p3); printf(“\n%d,%d,%d\n”,x,y,z); } 运行结果: 9,0,10 10,9,0 8.3 数组与指针 一个变量有地址,—个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组和数组元素。所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。 引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高。 8.3.1 指向数组元素的指针 定义一个指向数组元素的指针变量的方法,与指向变量的指针变量相同。 例如: int x[10]; int *a; a =&x[0]; 把a[0]元素的地址赋给指针变量a。也就是说,a指向x数组的第0号元素。 C语言规定数组名代表数组的首地址,也就是第0号元素的地址。因此,下面两个语句等价: a = &x[0]; a = x; a = x的作用是“把x数组的首地址赋给指针变量a”,而不是“把数组x各元素的值赋给p”。 在定义指针变量时可以赋给初值: int *a = &x[0]; 它等效于 int *a; 第八章指针 a = &x[0]; 当然定义时也可以写成 int *a = x; 它的作用是将x的首地址(即x[0]的地址)赋给指针变量a(而不是赋给*a)。 8.3.2 通过指针引用数组元素 按C的规定:如果指针变量a已指向数组中的一个元素,则a+l指向同一数组中的下一个元素(而不是将a值简单地加1)。 如果a的初值为&x[0],则: 1、a+i和x+i就是x[i]的地址,或者说,它们指向x数组的第i个元素。 2、*(a+i)或*(x+i)是a+i或x+i所指向的数组元素,即x[i]。 3、指向数组的指针变量也可以带下标,如a[i]与*(a+i)等价。 引用—个数组元素,可以用: (1)下标法,如x[i]形式; (2)指针法,如*(x+i)或*(a+i)。其中a是数组名,a是指向数组的指针变量,其初值a = x。 【例8.4】输出数组中的全部元素。假没有—个x数组,整型,有10个元素。要输出各元素的值有三种方法: (一)下标法。 main ( ) { int x[10]; int i; for(i = 0;i<10;i++) scanf(“%d”,&x[i]); printf(“\n”); for(i = 0:i<10;i++) printf(“%d”,x[i]); } (二)通过数组名计算数组元素地址,找出元素的值。 main ( ) { int x[10]; int i; for(i = 0;i<10;i++) scanf(“%d”,&x[i]); printf(“\n”); for(i = 0:i<10;i++) printf(“%d”,*(x+i)); } (三)用指针变量指向数组元素。 main ( ) { - 99 - 《C语言程序设计》 - 100 - int x[10]; int a,i; for(i = 0;i<10;i++) scanf(“%d”,&x[i]); printf(“\n”); for(a = x:a<(x+10);a++)printf(“%d”,*a); } 以上3个程序的运行情况均如下:1 2 3 4 5 6 7 8 9 0(输入) 1 2 3 4 5 6 7 8 9 0 对三种方法的比较: 1、第(一)和(二)种方法执行效率是相同的。 2、第(三)种方法比(一)(二)法快,用指针变量直接指向元素,不必每次都重新计算地址,像a++这样的自加操作是比较快的。 3、用下标法比较直观,能直接知道是第几个元素。 在使用指针变量时,有以下几个问题要注意: 1、指针变量可以实现使本身的值改变。 2、要注意指针变量的当前值。 【例8.5】通过指针变量输出x组的10个元素。 main ( ) { int *a,i,x10]; a = x for(i=0;i<10;i++) scanf(“%d”,a++); printf(“\n”); for(i=0;i printf(“%d”,*a); } 显然这个程序输出的数值并不是x数组中各元素的值。原因是指针变量的初始值为x数组首地址。 解决这个问题的办法,只要在第二个for循环之前加一个赋值语句: a=x; 即 main ( ) { int *a,i,x[10]; a = x; for(i=0;i<10;i++) scanf(“%d”,a++); printf(“\n”); a = x; for(i=0;i 第八章指针 printf(“%d”,*a);} 3、从上例可以看到,虽然定义数组时指定它包含10个元素,用a指向数组元素,但指针变量可以指到数组似后的内存单元。 4、注意指针变量的运算。如果先使a指向数组x(即a=x),则: (1)a++(或a+ = 1),使a指向下一元素,即x[i]。若再执行*a,取出下一个元素x[1]值。 (2)a++,由于x++和*同优先级,结合方向为自右而左,因此它等价于*(a++)。作用是先得到a指向的变量的值(即‘x),然后再使a+1=>a。 for(i=0;i printf(“%d”,*a); 可以改写为 for(i=0;i printf(“%d”,*a++); (3)*(a++)与*(++a)作用不同。前者是先取*a值,后使a加1。后者是先使a加l,再取*a。 (4)(*a)++表示a所指向的元素值加1,即(x[0])++,如果x[0]=3,则(x[0])++的值为4。注意:是元素值加l,而不是指针值加1。 (5)如果a当前指向x数组中第i个元素,则: *(a++)相当于x[i--],先对a进行“*”运算,再使a自减。 *(++a)相当于x[++i],先使a自加,再作*运算。 *(--a)相当于x[- -i],先使a自减,再作*运算。 8.3.3 数组名作函数参数 数组名可以用作函数的形参和实参。如: main( ) f(int arr[ ],in(n) {int array[10];{ …… f(array,10);} … ) array为实参数组名,arr为形参数组名。当用数组名作参数时,如果形参数组中各元素的值发生变化,实参数组元素的值随之变化。 用数组元素作实参的情况与用变量作实参时一样,是“值传递”方式,单向传递数据。如果用数组名作实参,在调用函数时是把数组的首地址传送给形参(注意:不是把数组的值传给形参)。以接受实参传过来的数组首地址。这样实参数组与形参数组共占同一段内存。 需要说明的是:C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组起始地址,因此传递的值是数组首地址,所以要求形参为指针变量。不要错认为用数组名作函数参数时不采用“值传递”方式。 【例8.6】将数组a中n个整数按相反顺序存放。 void inv(int a[ ],int n) { int temp,i,j,m = (n一1)/2; - 101 - 《C语言程序设计》 - 102 - for (i =0;i<=m;i++) {j = n – 1- i; temp=a[i];a[i]=a[j];a[j]=temp;} return; } main ( ) {int i,x[10]={3,7,9,11,0,6,7,5,4,2};printf(”The original array:\n”); for(i=0;i<10;i++) printf(“%d,”,x[i]); printf (”\n”); inv(a,10); printf(“the array has been inverted:\n”); for(i=0;i<10;i++) printf(“%d,”,x[i]); } 对这个程序可以作一些改动。将函数inv中的形参a改成指针变量。实参为数组名x,即数组a的首地址,将它传给形参指针变量a,这时a指向x[0]。 void inv(int x,int n) { int *c1,temp,*a,*b,m = (n - 1)/2; a = x; b = x+n-1;c1 = x+m; for(;a<=c1;a++,b--) {temp = *a;*a=*b;*b=temp;} return; } main ( ) {int i,x[10]={3,7,9,11,0,6,7,5,4,2}; printf(”The original array:\n”); for(i=0;i<10;i++) printf(“%d,”,x[i]); printf (”\n”); inv(x,10); printf(“the array has been inverted:\n”); for(i=0;i<10;i++) printf(“%d,”,x[i]); } 【例8.7】从10个数中找出其中最大值和最小值。 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 第八章指针 if (*p>max) max=*p; else if(*p return; } main ( ) {int i,number[10]; printf(“enter 10 integer numbers:\n”); for(i=0;i scanf(“%d”,&number[i]); max_min_value(number,10); printf(“\nmax—%d,min=%d\n”,max,min);} 实参也可以不用数组名,而用指针变量传递地址,形参仍用指针变量。程序可改为:int max,min; void max_min_value(int *array,int n) {int *c,*array_end; array_end = array + n; max=min=*array; for(p=array+1;c if (*c>max) max=*c; else if(*c return; } main ( ) {int i,number[10],*c; c = number; printf(“enter 10 integer numbers:\n”); for(i =0;i<10;i++,c++) scanf (“%d”,c); printf(“the 10 integer numbers:\n”); for(c = number,i =0;i<10;i++,p++) printf(“%d”,*c); c = number; max_min_value(c,10); printf(“\n max = %d,rain = %d\n”,max,min); } 如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的对应关系有以下4种情况: (1)形参和实参都用数组名,如: main ( ) f(int x[ ],int n) {int a[10];{ …… f ( a,10 );} … } - 103 - 《C语言程序设计》 程序中的实参a和形参x都已定义为数组。如前所述,传递的是a数组首地址。 (2)实参用数组名,形参用指针变量。 main ( ) f (int * x,int n) {int a[10];{ …… f (a,10);} … } (3)实参形参都用指针变量。例如: main ( ) f (int *x,int n) {int a[10],*c1;{ c1 = a;… …} f (c1,10); … } (4)实参为指针变量,形参为数组名。如: main ( ) f (int x [ ],int n) { int a[10],*c1;{…} c1 = a; … f(c1,10); } 【例8.8】将数组a中n个整数按相反顺序存放。 void inv (int *x,int n) { int *c1,m,temp,*a,*b; m = (n - 1) / 2; a = x; b = x + n -1;c1 = x + m; for(;a<= c1;a++,b--) {temp = *a,*a =*b;*b = temp;} return; } main ( ) {int i,arr[10],*p1 = arr; printf(“The original array:\n”); for(i =0;i<10;i++,p++) scanf(“%d”,p1); printf(“\n”); p1 = arr; inv (p1,10); printf(“The array has been inverted:\n”); for(p1 =arr;p1 printf(“%d”,*p1 ); printf(“\n”); - 104 - 第八章指针 } 注意,main函数中的指针变量p1是有确定值的。如果在main函数中不设数组,只设指针变量,程序如下: main ( ) { int i,*art; printf(“The original array:\n”); for ( i =0;i<=10;i++) scanf(“%d”,arr+i); printf(“\n”); inv(arr,10); printf(“The array has been inverted:\n”); for (i=0;i printf(“%d”,*(arr+i)); printf(“\n”); } 【例8.9】用选择法对10个整数排序。 main ( ) { int *p1,i,x[10]; p1 = x; for(i =0;i scanf(“%d”,p1++); p1=x; sort(p1,10); for( p1 = x,i=0;i {printf(“%d”,*p1);p1++;} } sort ( int a[ ],int n) {int x,y,k,t; for (x=0;x {k = x; for(y = x+1;y if (a [x]>a[x]) k = y; if ( k! = y ) {t = a[x];a[x] = a[k];a[k]=t;} } } 8.3.4 指针与字符串 一、字符串的表示形式 在C程序中,可以用两种方法访问一个字符串。 (一)用字符数组存放一个字符串,然后输出该字符串。 例如: main ( ) - 105 - 《C语言程序设计》 - 106 - {char string[ ] = “I lovc China!”; printf(“%s\n”,string); 运行时输出: I love China! (二)用字符指针指向一个字符串。 可以不定义字符数组,而定义一个字符指针。用指针指向字符串中的字符。 例如: main ( ) {char *string =“I love China!”; printf(“%s \n”,string); } 其中 char *string =“I love China!”; 等价于下面两行: char *string; string =“I love China!”; 而不等价于 char *string; *string =“I love China!”; 打印时用 printf(“%s\n”,string”); 通过字符数组名或字符指针变量可以输出一个字符串。而对一个数值型数组,是不能企图用数组名输出它的全部元素的。如: int a[ 10]; … printf (“%d\n”,a); 是不行的。 对字符串中字符的存取,可以用下标方法,也可以用指针方法。 【例8.10】将字符串x复制为字符串y。 main ( ) {char x[ ]=“I am a boy.”,y[20]; int i; for(i =0;*(x+i)!=‘\0 ’;i++) *(y+i)=*(x+i); *(y+i)=‘\0’; printf("string a is;%s\n”,x); printf("string b is:”); for(i=0;y[i]!=‘\0’;i++) printf(“%c”,y[i]); printf(“\n”); } 也可以设指针变量,用它的值的改变来指向字符串中的不同的字符。 【例8.11】用指针变量来处理将字符串x复制为字符串y。 main ( ) 第八章指针 {char x[ ]=“I am a boy。”,y[20],*zl,*z2; int i; zl=x;z2=y; for(;*zl!=‘\0’;zl++,z2++) *z2=*zl; *z2=‘\0’; printf("string a is:%s\n”,x); printf(“string b is:”); for(i=0;y[i]!=’\0’;i++) printf(“%c”,y[i]); printf(“\n”); } 二、字符串指针作函数参数 将一个字符串从一个函数传递到另一个函数,可以用地址传递的办法,即用字符数组名作参数或用指向字符串的指针变量作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以得到改变了的字符串。 【例8.12】用函数调用实现字符串的复制。 (一)用字符数组作参数。 void copy_string(char from[ ],char to[ ]) { int i=0; while(from[i]!=‘\0’) { to[i]=from[i];i++;} to[i]=‘\0’; } main ( ) {char x[ ]=“I am a teacher.”; char y[]=“you are a student.”; printf(“string a=%s\n string b=%s\n”,x,y); copy_string(x,y); printf(“\n string x=%s\n string y=%s\n”,x,y); } 在main函数中也可以不定义字符数组,而用字符型指针变量。main函数可改写如下:main ( ) {char *x=“I am a teacher.”; char *y=“you are a student.”; printf(“string x=%s\n string y=%s\n”,x,y); copy_string(x,y); printf(“\n string x=%s\n string y=%s\n”,x,y); } 与上面程序运行结果相同。 (二)形参用字符指针变量。 void copy_strmg(char *from,char *to) { for(;*from!=‘\0’;from++,to++) *to = *from; - 107 - 《C语言程序设计》 - 108 - *to =‘\0’; } main ( ) { char a =“I am a teacher.”; char b =“you are a student.”; printf(“\nstring a = %s\n string b = %s\n”,a,b); copy_string(a,b); printf(“\n string a = %s\n string b=%s\n”,a,b);} (三)对copy_string函数还可作简化。 (1)将copy_string函数改写为 void copy_string(char *from,char *to) { while((*to = *from)!=‘\0’) {to++;from++;} } (2)copy_string函数的函数体还可改为 { while((*to++ = *from++)!=‘\0’); } (3)函数体还可写成 { while(*from!=‘\0’) *to++ = *from++; *to =‘\0’; } 函数体还可简化为 { while(*from) *to++ = *from++; *to =‘\0’; } (四)上面的while语句还可以进一步简化为下面的while语句: while(*to++ = *from++); 它与下面语句等价; while((*to++ = *from++)!=‘\0’); (五)函数体中while语句也可以改用for语句: for(;(* to++ =*from++)!=0;); 或 for(;*to++ = *from++;); (六)也可用指针变量,函数copy_string可写为 void copy_string(char from[ ],char to[ ]) {char *p1,*p2; p1 = from;p2=to; while((*p2++=*pl++)!=‘\0’); 第八章指针 } 归纳起来,作为函数参数,有以下几种情况: 实参形参 数组名数组名 数组名字符指针变量 字符指针变量字符指针变量 字符指针变量数组名 8.4 函数的指针 8.4.1 用函数指针变量调用函数 可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。一个函数在编译时被分配给—个入口地址。这个入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。 【例8.13】求d和6中的大者。先列出按一般方法的程序。 main ( ) {int max(int,int); int x,y,z; scanf(“%d,%d”,&x,&y); z =max(x,y); printf(“x=%d,y=%d,max=%d”,x,y,z); } max(int a,int a) {int c; if(a>b)c = a; else c = b; return(c); } 可以用一个指针变量指向一个函数,通过指针变量来访问它指向的函数。 将main函数改写为: main ( ) {int max(int,int); int(*p1)(); int x,y,z; p1 = max; scanf(“%d,%d”,&x,&y); z=(*p1)(x,y); printf(“x = %d,y = %d,max = %d”,x,y,z); } 在main函数中有一个赋值语句 z=(*p1)(x,y); 它包括函数的调用,和“z=max(x,y);”等价。这就是用指针形式实现函数的调用。以上用 - 109 - 《C语言程序设计》 两种方法实现函数的调用,结果是一样的。 【说明】 1、指向函数的指针变量的一般定义形式为 数据类型(*指针变量名)(); 这里的“数据类型”是指函数返回值的类型。 2、函数的调用可以通过函数名调用,也可以通过函数指针调用(即用指向函数的指针变量调用)。 3、(*p)( )表示定义一个指向函数的指针变量,它不是固定指向哪一个函数的,而只是表示定义了这样一个类型的变量,它是专门用来存放函数的入口地址的。在程序中把哪一个函数的地址赋给它,它就指向哪一个函数。在一个程序中,一个指针变量可以先后指向不同的函数。 4、在给函数指针变量赋值时,只需给出函数名而不必给出参数,如: P1 = max; 5、用函数指针变量调用函数时,只需将(*p1)代替函数名即可(p1为指针变量名),在(*p1)之后的括弧中根据需要写上实参。 6、对指向函数的指针变量,像p1+n、p1++、p1--等运算是无意义的。 8.4.2用指向函数的指针作函数参数 函数指针变量常用的用途之一是把指针作为参数传递到其他函数。指向函数的指针也可以作为参数,以便实现函数地址的传递,也就是将函数名传给形参。 它的原理可以简述如下:有一个函数(假设函数名为sub),它有两个形参(x1和x2),定义x1和x2为指向函数的指针变量。在调用函数sub时,实参用两个函数名f1和f2给形参传递函数地址。这样在函数sub中就可以调用f1和f2函数了。 【例8.14】设一个函数process,在调用它的时候,每次实现不同功能。输入a和b两个数,第一次调用process时找出a和b中大者,第二次找出其中小者,第三次求a与b之和。 main ( ) { int max(int,int); int min(int,int); int add(int,int); int a,b; printf(“enter a and b:”); scanf(“%d,%d”,&a,&b); printf(“max = ”); process(a,b,max); printf(“min = ”); process(a,b,min); printf(“sum = ”); process(a,b,add); } max(int x,int y) { int z; if(x>y)z = x; else z = y; rerurn(z); - 110 - 第八章指针 } min(int x,int y) { int z; if(x else z = y; return(z); } add(int x,int y) {int z; z = x + y; return(z); } process(int x,int y,int(*fun)(int,int)) {int result; result =(*fun)(x,y); printf(“%d\n”,result); } 8.4.3 返回指针值的函数 一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。 这种带回指针值的函数,一般定义形式为: 类型名*函数名(参数表); 例如: int *a(int x,int y); 【例8.15】有若干个学生的成绩(每个学生有4门课程),要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。 main ( ) {float score[ ][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}}; float *search(float(*pointer)[4],int n); float *p; int i,m; printf(“enter the number of student:”); scanf(“%d”,&m); printf(The scores of No.%d are:\n”,m); p = search(score,m); for(i=0;i<4;i++) printf(“%5.2f\t”,*(p+i)); } float *search(float(*pointer)[4],int n) { float *pt; pt = *(pointer+n); return(pt); - 111 - 《C语言程序设计》 - 112 - } 【例8.16】对上例中的学生,找出其中有不及格课程的学生及其学生号。 main() { float score[ ][4] ={{60,70,80,90},{50,89,67,88},{34,78,90,66}};float *search(float(*pointer)[4]); float *p1; int i,j; for(i=0;i<3;i++) {p1 = search(score+i); if(p1 = =*(score+i)) {printf(“No.%d scores;”,i); for(j = 0;j<4;j++) printf(“%5.2f”,*(p1+j)); printf(“\n”);} } } float *search(f1oat(*pomter)[4]) { int i; float *pt1; pt1 = *(pointer+1); for(i=0;i<4;i++) if(*(*pointer+i)<60)pt1 = *pointer; return(pt1); } C++程序设计 第8章指针和引用 在程序运行时变量和函数都存放在内存中,通过变量名来访问数据、通过函数名来调用函数都是直接访问方式。还有另一种间接访问方式就是用指针。指针的本质是内存地址。指针往往用于说明函数的形参,使实参能通过指针传递,以提高函数调用的效率。利用指针能动态地使用内存,以提高内存使用效率。指针也能用来表示数据关联,以构成复杂的数据结构。指针是C程序中最常见的类型。引用是C++扩展的新概念,主要用于函数形参和返回类型。本章将详细介绍指针和引用的概念及应用。 8.1 指针及指针变量 指针(pointer)的本质是内存地址。指针变量就是专门存储地址的一种变量。通过指针变量所存储的地址来访问数据是一种间接寻址方式。由于处理器的机器语言能支持间接寻址,所以使用指针可以达到较高的计算性能。 8.1.1 地址的概念 C++编译器对不同对象或变量按其数据类型分配合适大小的存储空间。例如为char或bool型变量分配1个字节(bytes)的存储空间,short分配2字节,int和float分配4个字节,为double型变量分配8个字节的存储空间。当程序执行时,代码和变量都加载到内存中。计算机内存被分成若干个存储单元,存储单元以字节为单位。每个存储单元都有一个固定的编号,这个编号就是内存地址。尽管一个变量可能占用多个字节空间,但都通过第一个字节的地址来访问。存放某个变量的第一个字节的地址就是该数据的首地址。 指针即内存单元的地址,而数据是内存单元中的内容(或值)。 假设在程序中说明了1个int型的变量a,其值为68。系统为变量a分配4字节的存储空间,设首地址为0X0065FDF4。通过地址0X0065FDF4就能找到变量a在内存中的存储单元,从而对变量a进行访问。0X0065FDF4就是变量a的指针。知道一个变量的地址和变量的类型就能对变量进行访问,就如同知道房间号就能找到房间,从而找到房间里的主人。 指针是一种特殊的数据类型。所有类型的变量,无论是基本类型、用户定义类型、还是这些类型的数组,在一次运行时都有确定的地址,因此它们都有指针。对于32位计算机,地址长度就是32位,因此一个指针需要4个字节,与整型int、浮点型float具有相同大小的长度。一个指针不仅有值,而且还要确定其类型,表示它能指向什么类型的数据,决定了通过它要取用多少字节作为该变量的值。 指针1 选择题 1.如下程序段: int *p ,a=10 ,b=1 ; p=&a ;a=*p+b ; 执行该程序段后,a 的值为。 A. 12 B. 11 C. 10 D. 编译出错 2.若有以下定义和语句: double r=99 ,*p=&r ; *p=r ; 则以下正确的叙述是。 A. 以下两处的*p 含义相同,都说明给指针变量p 赋值 B. 在"double r=99,*p=&r;"中,把r 的地址赋值给了p 所指的存储单元 C. 语句"*p=r;"把变量r 的值赋给指针变量p D. 语句"*p=r;"取变量r 的值放回r 中 3.若有说明int *p,a;则能通过scanf语句正确给a存入数据的程序段是()。 A.p=&a; scanf(“%d”,p); B.scanf(“%d”,a); C.p=&a; scanf(“%d”,*p); D.*p=&a; scanf(“%d”,p); 4.若int x ,*pb;,则正确的赋值表达式是。 A. pb=&x B. pb=x; C. *pb=&x; D. *pb=*x 5.若有说明:int i, j=2,*p=&i;,则能完成i=j 赋值功能的语句是。 A. i=*p; B. *p=*&j; C. i=&j; D. *p=i; 6.若已定义:int a[9] ,*p=a;并在以后的语句中未改变p 的值,不能表示a[1]地 址的表达式是()。 A. p+1 B. a+1 C. a++ D. ++p 7.若有以下说明:int a[10]={1,2,3,4,5,6,7,8,9,10} ,*p=a ; 则数值为6 的表达式是( )。 A. *p+6 B. *(p+6) C. *p+=5 D. p+5 8.以下程序段的运行结果是()。 int a[10]={1,2,3,4,5,6,7,8,9,10}; int *p=&a[3],*q; q=p+2; printf(“%d”,*p+*q); A.16 B.10 C.8 D.6 1文档来源为:从网络收集整理.word 版本可编辑. 第八章 地址和指针 第一节 变量的地址和指针 1、计算机的内存是以字节为单位的一片连续的存储空间,每一个字节都有一个编号,这个编号就成为内存地址。 2、程序中定义了一个变量,c 编译系统就会根据定义中变量的类型,为其分配一定字节数的内存空间: Short int 2 Int float 4 Double 8 char 1 指针 4 图8.1 变量在内存中所占字节的地址示意图 每个变量的地址就是指该变量所占存储单元的第一个字节的地址。 3、直接存取:程序中我们对变量进行存取操作,实际上也就是对某个地址的存储单元进行操作。这种直接按变量的地址存取变量值的方式。 4、在c 语言中,还可以定义一种特殊的变量,这种变量只 a 2001 p 3001 a b x 1012 1013 1015 1016 1201 1202 1203 1204 a 1文档来源为:从网络收集整理.word 版本可编辑. 是用来存放内存地址的。 图8.2 存放地址的指针变量示意图 通过变量p 间接得到变量a 的地址,然后再存取变量a 的值的方式称为“间接存取”方式,通常变量p 指向了变量a ,变量a 是变量p 所指向的对象。 5、用来存放指针地址的变量就称作“指针变量”。 6、“变量p 指向变量a ”的含义就是指针变量p 中存放了变量a 的地址。 7、在某些场合,指针是使运算得以进行的唯一途径。 第二节 指针的定义赋值 一、 指针变量的定义和指针变量的基本类型 1、 定义指针变量的一般形式如下: 类型名 *指针变量名1,*指针变量名2…… 说明:1)类型名是基本类型。 2)*是说明符。(而在调用的时候,代表的是存储单元中的值) 3)指针变量名是用户标识符。 例:1)int *pi,*pj; pi pj 只能存放整型变量的地址 int I,*pi;double *pj; 2)p 为一个指向指针的指针变量 2002 2002 1012 1013 p p 第八章变量的地址和指针 变量:整 int 4B 单精度 float 4B 双精度 double 8B 存储空间:随机存放 short int a,b; float c,d; a 2B b 2B c 4B d 4B 1012 1013 1018 1019 2010 2011 2012 2013 3033 3034 3035 3036 变量的地址:该变量所占存储单元的第一个字节的 地址 &a=1012 p a *定义一个指针变量用于存放地址 4039 1012 地址就是指针 1.指针变量:存放(指向)不同地址的指针 定义:类型说明 *变量名; int *p; (指向一个整型变量) float *f; 2.给指针变量赋值 (1)int a,*p; p=&a; /*将a变量的地址赋给指针p*/ (2)初始化赋值 int a,*p=&a; 3.赋值形式: (1)赋值运算 int a,*p; p=&a; 注意:p=1000; (错误) (2)初始化赋值 int a,*p=&a; (3)把一个指针变量的值送给相同类型的另一个指针变量 int a,*pa,*pb; pa=&a; pb=pa; /*两个指针同时指向a*/ (4)把数组的首地址,送给指针变量 (数组名就表示数组的首地址) int b[5],*p; p=b; 或者 p=&b[0] (也可以指在数组中间 p=&b[2]) (5)把字符串的首地址送给字符型的指针变量 char *p; p="abcdef"; (把字符串的首地址送给p) 4.指针变量的运算 &:取地址符号 *:取内容运算符(把指针所指变量内容取出来) 优先级相同 main() {int a,*p=&a; a=5; printf("%d\n",*p); printf("%d\n",p); printf("%d\n",&p);} 第八章指针 一、选择题 1、设有定义:int n1=0,n2,*p=&n2,*q=&n1;,以下赋值语句中与n2=n1;语句等价的是 A)*p=*q; B)p=q; C)*p=&n1; D)p=*q; 2、若有定义:int x=0, *p=&x;,则语句printf("%d\n",*p);的输出结果是 A)随机值 B)0 C)x的地址 D)p的地址 3、以下定义语句中正确的是 A)char a='A'b='B'; B)float a=b=10.0; C)int a=10,*b=&a; D)float *a,b=&a; 4、有以下程序 main() { int a=7,b=8,*p,*q,*r; p=&a;q=&b; r=p; p=q;q=r; printf("%d,%d,%d,%d\n",*p,*q,a,b); } 程序运行后的输出结果是 A)8,7,8,7 B)7,8,7,8 C)8,7,7,8 D)7,8,8,7 5、设有定义:int a,*pa=&a;以下scanf语句中能正确为变量a读入数据的是 A)scanf("%d",pa) ; B)scanf("%d",a) ; C)scanf("%d",&pa) ; D)scanf("%d",*pa) ; 6、设有定义:int n=0,*p=&n,**q=&p;则以下选项中,正确的赋值语句是 A)p=1; B)*q=2; C)q=p; D)*p=5; 7、有以下程序 void fun(char *a,char *b) { a=b; (*a)++; } main () { char c1='A', c2='a', *p1, *p2; p1=&c1; p2=&c2; fun(p1,p2); printf("%c%c\n",c1,c2); }程序运行后的输出结果是 A)Ab B)aa C)Aa D)Bb 8、有以下程序 #include main() { printf("%d\n", NULL); } 程序运行后的输出结果是 A)0 B)1 C)-1 D)NULL没定义,出错 9、已定义以下函数 fun(int *p) { return *p; } 该函数的返回值是 A)不确定的值 B)形参p中存放的值 第八章地址和指针 8.1变量的地址和指针 在程序中变量实际上代表了内存中的某个存储单元。那么C是怎样存取这些单元的数据内容的呢? 我们知道计算机的内存是以字节为单位的一片连续的存储空间,每个内存单元都有一个唯一的编号,我们将其称为“内存地址”。计算机对数据的存取操作都是依赖于内存地址进行的。因为计算机的内存空间是连续的,所以内存中的地址空间也是连续的,并且用二进制数据来表示,为了方便和直观,我们将用十进制数据进行描述。 若在程序中定义了一个变量,C编译系统就会自动根据变量的类型,为其分配一定字节数量的存储空间。如int型2个字节,float型4个字节,double型8个字节,char型1个字节等。此后,这个变量的内存地址也就唯一的确定了。 一般情况下,我们在程序中只要给出变量名,不需要知道每个变量在内存中的具体地址,变量与地址之间的联系由C编译系统来完成。程序中我们对变量进行存取操作,实际上就是对变量地址的存储单元进行操作。这种直接按照变量地址进行存取的操作方式称为“直接存取”方式。 在C语言中我们还可以定义一种特殊的变量,这种变量只是用于存放内存变量地址的。如:p93 图8.2 这种通过变量p间接得到变量a的地址,然后再存取变量a的值的方式称为“间接存取”的方式。这种用来存放地址的变量称为“指针变 量”或“指针”。 由此我们可以知道,在C语言中,地址是指变量在内存中的存放的位置,即存放该变量的内存单元的名字。而指针是指一个变量,在该变量中存放的是其指向的那个变量在内存存储单元的地址。也就是说,变量的地址就可以理解为指针。 在C语言中,指针被广泛使用,他可以使程序简洁并高效运行,但使用不当就会产生意料不到的严重后果。因此,正确使用和掌握指针是十分必要的。 8.2 指针变量的定义和基本类型 定义指针变量的形式: 类型名*指针变量名1,*指针变量名2,……; 例如: int *pi,*pj; float *i,*j,*k; double *l,*m,*n; char *c,*s; 在每个变量前面的星号*是一个指针说明符,用来说明该变量是指针类型。变量前面的星号*不可以省略。指针变量名前面的类型定义是说明指针指向的数据类型。 另外,我们还可以定义一个指向指针的指针变量。定义形式为: 类型名**指针变量名1,**指针变量名2,……; 内存管理指针的基本概念指针应用实例指针作函数参数第8章指针(1) 复习回顾 上次课的内容: ◆局部变量和全局变量 ◆变量的作用域 ◆变量的生存期 ◆声明与定义 ◆内部函数 ◆外部函数◆你开始习惯写函数了吗? 2012是如何实现的?假定造成世界末日的上帝是一个程序员,作为一名合格的程序员,他绝不应该写出类似于“摧毁地球”这样的程序,而应该写一个“摧毁(行星)”的函数,然后把地球作为参数传进去! C语言新手的晋级之路 第一步:萧规曹随 ◆在这一步要求按照教材或讲义上的程序实例进行原样 输入,运行一下程序看是否正确。 ◆在这一步,掌握C语言编程软件的使用方法(包括新 建、打开、熟练输入、编辑、保存、关闭C程序); 初步记忆新学章节的知识点;养成良好的编程风格( 是讲义提倡的而不是教材上的) ◆难点:小心数字1和字母l,字母o和数字0,中英文标 点符号的区别 C语言新手的晋级之路 第二步:移花接木 ◆在第一步输入的C程序的基础上进行试验性的修改, 运行一下程序看一看结果发生了什么变化,分析结果变化的原因,加深新学知识点的理解。 ◆可与第一步同步进行,“输入”可加深记忆,“修改 ”可加深理解,二者相辅相成,互相促进。 ◆友提,一次进行一处修改即可,免得把自己改晕了。 C语言新手的晋级之路 第三步:无中生有 ◆面对教材的例子题目,不参考教材,自己从头开始编 写程序。看能否写出正确运行的代码。 ◆初学者易犯的错误:scanf格式控制和输入不匹配或把变量 名当地址作参数,使用未定义的变量、漏掉或多写“;”、“{” 与“}”、“(”与“)”不匹配,控制语句(选择、分支、循环)的格式不正确,调用库函数没有包含相应头文件,调用未声明 的函数、调用函数时实参和形参不匹配、数组边界越界等等 ◆要学会看编程工具的错误信息提示:双击错误提示光标可 跳转到发生错误的行,如果该行没有错误就往前查找。错误要一 个一个修改,每改完一次编译一下程序。 指针思考题 一、填空题 【1】下面函数要求用来求出两个整数之和,并通过形参传回两数相加之和值,请填空。 int add( int x, int y, ) { =x+y;} 【2】若有定义: char ch; (1) 使指针p 可以指向变量ch的定义语句是。 (2) 使指针p指向变量ch的赋值语句是。 (3) 通过指针p给变量ch读入字符的scanf函数调用语句是。 (4) 通过指针p 给变量ch赋字符A的语句是。 (5) 通过指针p输出ch中字符的语句是。 (6) 在16位微机上,指针变量p在内存中所占字节数是,变量ch在内在所占字 节数是。 二、选择题 【3】若有说明:int i,j=7,*p=&i;则与i=j;等价的语句是()。 A.i=*p; B.*p=*&j; C.i=&j; D.i=**p; 【4】若有定义:int x,*pb;则正确的赋值表达式是()。 A.pb=&x B.pb=x C. *pb=&x D.*pb=*x 【5】对于类型相同的指针变量,不能进行的运算是()。 A.< B. = C. + D. – 【6】以下程序的输出结果是()。 A.23 B. 24 C. 25 D. 26 Void fun ( int *x) { printf(%d\n”,++*x); } main( ) { int a=25; fun (&a); } 【7】以下程序的输出结果是()。 A.6 B. 7 C. 8 D. 9 main() { int k=2,m=4,n=6; int *pk=&k,*pm=&m,*p; *(p=&n)=*pk*(*pm); printf("%d\n",n); } 【8】以下程序的输出结果是()。 A.100 B. 50 C. 101 D.200 main() { int *v,b; v=&b; b=100; 指针 一、指针概述 1. 地址 C++程序中每一个实体,如变量、数组和函数等,都要在内存中占有一个可标识的存储区域。每一个存储区域由若干个字节组成,在内存中,每一个字节都有一个“地址”,一个存储区的“地址”指的是该存储区中第一个字节的地址。 2.指针 指针就是存储区域的地址。一个地址指向一个程序实体的存储空间。 直接访问:通过变量名或地址访问程序中一个实体的存储空间的方式(其实通过变量名访问也就是通过地址访问)。 间接访问:把一个变量的地址放在另一个变量中。 3.指针变量 专门用来存放地址的变量就叫指针变量,需要专门加以定义。 二、指针的类型与指针的定义 指针也是具有类型的。指针的类型就是它所指向的变量的类型。例如,一个指向int型的指针,一个指向一维数组的指针。 在使用一个指针变量之前,先要用声明语句对其进行定义。例如:int *p; 定义了一个指向整型数据的指针变量p。即p是一个存放整型变量地址的变量。 应当特别注意的是,定义一个指针变量必须用符号“*”,它表示其后的变量为指针变量,指针变量为p,而不是*p。 要想使一个指针变量指向一个变量,必须将变量的地址赋给指针变量。例如:C++程序设计 第八章 指针和引用
第8章指针1练习答案
C语言第八章地址与指针
第八章 二级c语言
计算机二级c语言第八章 指针习题
第八章地址和指针
第8章 善于利用指针(1)
第8章-指针练习题
指针1