C语言大学实用教程 第7章 指针 西南财经大学经济信息工程学院 刘家芬 jfliu@swufe.edu.cn
数据在内存中的存储 计算机的内存区由连续的内存单元构成,每个内存单元的大小是一个字节,每一个字节有一个编号,也就是常说的"地址"。 每一个变量在内存中都占据一定内存空间,空间的大小由变量的类型确定。整型变量在内存中占用2个字节,float型变量在内存中占用4个字节。 在程序中一般是通过变量名来对内存单元进行存取操作的。而实际上,编译过程中所有的变量名都会被转换为变量的地址,计算机对变量的值的存取,都是通过地址来进行的。
假设程序中定义了2个整型变量i和j,编译器在编译的时候,为变量i分配地址编号为2000和2001的两个字节;而为变量j分配2002、2003的两个字节。 如果有语句 j=i+j,计算机在执行过程中将首先从地址2000、2001中取出其中的值(变量i的值),然后从地址2002、2003中再取出其中的值(变量j的值),然后将这两个值进行相加,将相加的结果保存在内存地址2002、2003中。 这种通过变量地址直接存取变量值的方式,称为"直接访问"方式。
除了"直接访问"方式外,还可以采用"间接访问"方式,来对一个变量的值进行存取: 将变量i的地址2000,存放在另一个变量i_pointer当中,访问的时候,通过取出i_pointer的值,找到变量i的地址2000,然后再从2000、2001中,取出变量i的值。 i_pointer i 这种间接访问,从变量i_pointer开始,最终也能访问到变量i的值。 2000 2000 由于通过地址能找到所需的变量,因此可以说地址指向该变量单元。在C语言中,将地址形象化地称作“指针”。
指针和地址的概念 变量的值保存在内存单元中,该内存单元可以通过地址来标识。 变量的地址即存放数据的内存地址,就是该变量的指针。 指针可以保存在变量中,这种专门用来保存指针的变量,称为指针变量。 指针变量i_pointer中保存了变量i的地址2000。2000指向变量i,我们也可以说指针变量i_pointer指向变量i。
变量的指针和指向变量的指针变量 变量的指针就是变量的地址,而指针变量就是指存放地址型数据的变量。 为了表示指针变量和所指向的变量之间的联系,C语言中用*表示“指向”关系。 右图中(*i_pointer)就是指针变 量i_pointer所指向的变量,即i。 指针变量的定义 格式:基类型 * 指针变量名; 基类型是该指针变量指向的变量的类型。例如: float *p; (p是指向浮点型变量的指针变量) int *q; (q是指向整型变量的指针变量) i_pointer *i_pointer 3 i 2000 2000
注意: 指针变量名前面的*表明该变量为指针变量,指针变量的名字是p、q,而不是*p、*q。 在定义指针变量时必须指定基类型。基类型规定了指针类型变量所指向的变量的类型,一个指针变量只能指向同一类型的变量。 例如: float *p; 这里定义的指针类型变量p只能指向float型的变量,也就是说,它只能保存一个float型变量的地址,而不能保存其他类型变量(比如int型的变量)的地址。以下是错误的: int i; float *p=&i;
定义了指针变量后,如何使其指向某个变量? 可以通过赋值语句使指针变量得到另一个变量的地址,从而使它指向另一个变量: int i=6; int *p; p=&i; (这个赋值语句将整型变量i的地址赋给了指针变量p,从而使p指向了变量i) p i &i 6
指针变量的引用 指针变量只能存放地址,不能将一个整数(或其他非地址类型的数据)赋给一个指针变量。例如下面是错误的: int *p=100; 两个有关的运算符: (1) & : 取地址运算符 (2) * : 指针运算符,取其指向的变量的内容 例:int a,*p; p=&a; *p=5; 使p所指向的那个变量的值为5,等价于a=5;
例7.1 #include <stdio.h> void main() { int a,b; int *p1,*p2; p1=&a;p2=&b; printf("%d,%d\n",a,b); printf("%d,%d\n",*p1,*p2); }
说明: 在开始定义了两个指针变量p1和p2,但是定义的时候,并没有给它们赋初值,这个时候,p1和p2并未指向任何变量。 "p1=&a;p2=&b;"这两个语句分别使用了取地址运算符,使p1指向了变量a,p2指向了变量b。 程序最后的*p1和*p2,其实就是变量a和b。所以最后两个printf函数的作用是相同的。 程序中两次出现了*p1和*p2,它们代表不同的意义。 第一次出现是: int *p1,*p2; 用于声明p1和p2是两个指针变量。 第二次出现是: printf("%d,%d\n",*p1,*p2); 这里*p1和*p2分别代表p1和p2所指向的变量的内容。
有如下程序段: int a,*p; p=&a; 问:(1) &*p的含义是什么? 分析:&和*这两个运算符的优先级相同,按照右结合,先进行*p的运算,它就是变量a,然后再执行&运算,即&a运算,得到变量a的地址。 (2) *&a的含义是什么? 分析:和上面类似,&a运算是取变量a的地址,然后执行*运算,即&a所指向的变量,即是变量a。
例7.2 输入a和b两个整数,按照先大后小的顺序输出a和b。 #include <stdio.h> void 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("max=%d,min=%d\n",*p1,*p2); }
这个程序的算法思想是:使用p1和p2分别指向a和b。当a小于b的时候,交换p1和p2的内容,使得p1指向b,p2指向a。输出时,先输出p1指向的那个变量,后输出p2指向的那个变量。 5 5 p2 p2 b b &b &a 10 10 (2)输出时 (1)开始时
指针变量作为函数参数 指针变量也可以作为函数参数来传递信息 例7.3 输入两个整数,按照先大后小的顺序输出。
#include <stdio.h> void main() { void swap(int *p1,int *p2); int a,b, *point_1,*point_2; scanf("%d,%d",&a,&b); point_1=&a; point_2=&b; if(a<b) swap(point_1,point_2); printf("max=%d,min=%d\n",a,b); } void swap(int *p1,int *p2) int temp; temp = *p1; *p1 = *p2; *p2 = temp;
分析程序: 1.在main函数中,swap的实参是point_1,point_2。这两个指针变量分别保存了a和b的地址。 2.在swap被调用时,形参p1和p2分别被赋予实参point_1和point_2的值,因此,p1指向变量a,p2指向变量b 3.swap函数的执行,使p1指向的变量(a)的内容和p2指向的变量(b)的内容进行了交换。 4.在main中按先a后b的顺序打印结果。这个时候,a中保存的值,大于b中保存的值的。
p1 p1 &a a &a a point_1 5 point_1 9 &a &a p2 p2 &b b &b b point_2 9 point_2 5 &b &b swap结束时 swap开始时
思考 能否使用普通变量作函数参数,即 void swap(int x,int y) { int temp; temp=x; x=y; y=temp; } 并在main函数中调用swap: swap(a,b);
答案:不行。因为a和b的值在传递给swap的形参x和y后,不会改变(我们称为值传递): 若要使被调函数对某个变量的值的改变,在主调函数中可见,那么可以使用指针变量作函数参数。调用时,传递变量的地址给被调函数。 a b a b 5 9 5 9 5 9 9 5 x y x y swap开始时 swap即将结束时
另一个解决方案,是否可行? #include <stdio.h> void main() { void swap(int *p1,int *p2); int a,b, *point_1,*point_2; scanf("%d,%d",&a,&b); point_1=&a; point_2=&b; if(a<b) swap(point_1,point_2); printf("max=%d,min=%d\n",*point_1,*point_2); } void swap(int *p1,int *p2) { int *p; p=p1; p1=p2; p2=p; 编程者意图通过swap,交换main函数中的point_1和point_2的内容。但是这是不能实现的,因为swap只能交换形参p1和p2的内容,而不能把这种影响反映到main中的point_1和point_2中去。
*p是指针变量p所指向的变量,而p指向哪里? 如果对*p赋值,可能造成系统崩溃! 再一个解决方案,是否可行? #include <stdio.h> void main() { void swap(int *p1,int *p2); int a,b, *point_1,*point_2; scanf("%d,%d",&a,&b); point_1=&a; point_2=&b; if(a<b) swap(point_1,point_2); printf("max=%d,min=%d\n",*point_1,*point_2); } void swap(int *p1,int *p2) { int *p; *p=*p1; *p1=*p2; *p2=*p; *p是指针变量p所指向的变量,而p指向哪里? 如果对*p赋值,可能造成系统崩溃!
数组和指针 数组也要占用内存。数组占用一个连续的内存区域,数组中元素在内存中的存储位置是相邻的。例如:int a[6]; 2000 2002 2004 2006 2008 2010 a a[0] a[1] a[2] a[3] a[4] a[5]
指向数组元素的指针 数组元素相当于变量,所以指向数组元素的指针,和指向变量的指针相同: int a[10]; int *p; p= &a[0]; (p指向数组a中的第一个元素a[0]) 数组名代表数组的首地址,而数组的首地址和数组中第一个元素的地址是相同的,所以下面两条语句等价: p= &a[0]; p=a;
通过指针引用数组元素 程序段:int a[10]; int *p=&a[9]; *p=100; 的作用是? 将数组a中最后一个元素a[9]赋值为100
C语言规定:如果指针变量p已经指向数组中的一个元素,则p+1指向同一个数组中的下一个元素。例如: int a[10]; int *p=&a[0]; *(p+1)=100;(等价于a[1]=100;) 这里p+1不是简单的对地址数值进行加1。假设数组a的首地址为2000,p初始值为2000,p+1的值为2002,因为int型的变量在内存中占两个字节,p+1为2002,指向元素a[1] 若p指向数组中第一个元素a[0],p+i和a+i就是a[i]的地址,指向数组元素a[i];*(p+i)和*(a+i)就代表数组元素a[i]。
注意:指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价 数组名也是一个指针变量,它指向数组的首地址。因此: int a[10]; *(a+5)=100;等价于a[5]=100;
例7.4 输出数组中全部元素 (1)使用下标法访问 #include <stdio.h> void main() { int a[10]; int i; for(i=0;i<10;i++) scanf("%d",&a[i]); printf("%d ", a[i]); }
(2) 使用数组名计算数组元素地址,找出元素的值 #include <stdio.h> void main() { int a[10]; int i; for(i=0;i<10;i++) scanf("%d",&a[i]); printf("%d ", *(a+i)); }
#include <stdio.h> void main() { int a[10]; int i,*p; (3) 使用指针变量指向数组元素 #include <stdio.h> void main() { int a[10]; int i,*p; for(i=0;i<10;i++) scanf("%d",&a[i]); for(p=a;p<(a+10);p++) printf("%d ", *p); } 注意:p作为指向数组元素的指针变量,可以通过改变指针变量的值指向不同的元素。可以进行p++操作,即p的值可以更改;数组名a虽然也作为指针,但是a的值不能修改,因此不能进行a++操作。
注意: 1. int a[10]; int *p=a; 则p+10指向的内存地址,已经超出了数组a的范围。如果程序中出现 *(p+10)=100; 已经超出了数组a的边界。因为不确定(p+10)指向的内存区域用于何种目的,所以这样赋值可能会使系统崩溃。 2.比较*(p++) *(++p) ++(*p) *p,然后p指向数组下一元素 p++使p指向数组下一元素,读取当前*p 使p指向的内存单元中的值加1
指向同一数组的指针变量之间的减法运算 指针变量1-指针变量2 结果为:两个指针指向的数组元素间的距离 例:指向同一数组的指针变量相减 float x[10],*p1=x+2,*p2=&x[8]; p1-p2 的值是-6 p2-p1 的值是6 说明两个元素之间的距离是6
用数组名作函数参数 第六章中已经讲过:用数组名作函数参数时,实际上传递的只是实参数组的首地址;在内存中只分配了一个数组空间,而不是两个。被调函数中对形参数组的修改,在主调函数中可见。 在掌握了指针的概念之后,我们再来深入分析一下用数组名作函数参数。
void f(int arr[ ],int n) { …… } 实参数组名array代表该数组的首地址,形参用来接收从实参传递过来的首地址,因此形参应该是一个指针变量,因为只有指针变量能存放地址。 使用数组名作函数参数时,形参int arr[ ] 表示arr是数组名,C编译器会将形参数组名作为指针变量来处理。因此void f(int arr[ ],int n) 和void f(int *arr,int n) 是等价的,编译器会建立一个指针变量arr,用来存放从主调函数中传过来的实参数组的首地址。 void main( ) { …… int array[10]; f(array,10); }
实参数组名为array ,当调函数f时,形参arr接受array数组的首地址,因此arr也指向array数组。 根据前面讲的指向数组元素的指针所具有的性质,我们可以使用几种等价的方式来访问数组array中的元素。例如array[1]还可以用以下两种方式来表示:arr[1]、*(arr+1)。 arr array arr+1
数组名作函数参数时,C编译器将形参数组名作为指针变量来处理。传递给这个形参的值是实参数组的首地址。 注意: 1. 因为形参数组名被编译成指针变量,所以可以改变它的值。例如 "arr++;" 是允许的。 2. 而实参数组名array虽然也指向数组中第一个元素,但是array的值不能被修改,它是一个指针常量。例如 "array++;" 是错误的。
例7.5 将数组a中的n个整数按相反顺序存放 #include <stdio.h> void main() { void inv(int x[ ],int n); int i, a[10]={3,7,9,1,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 been inverted:\n"); }
3 7 9 1 6 5 4 2 2 4 5 7 6 1 9 3 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; } 3 7 9 1 6 5 4 2 j i m 2 4 5 7 6 1 9 3
算法:将a[0]与a[n-1]互换,再将a[1]与a[n-2]互换…直到中间元素。用for循环处理,i和j分别指示需要对换的两个元素位置,初值为0和n-1,然后i++并且j--,直到i=(n-1)/2。 第一个形参x实际上是一个指针变量,x指向数组a的首元素。通过x[i]指向的实际上就是a[i]。 第二个形参n用来接收需要进行处理的元素的个数。可以通过inv(a,5)表示对数组前5个元素进行颠倒。 注意:算法并不是唯一的!
对程序进行修改,将inv函数中的参数x改成指针: #include <stdio.h> void main() { void inv(int *x,int n); int i, a[10]={1,3,5,7,9,11,13,15,17,19}; printf("The original array:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); inv(a,10); printf("The array has been inverted:\n"); }
3 7 9 1 这个函数里采用了指向整型变量 的指针变量i和j,分别指向将要 6 交换的两个数组元素。 5 4 2 a数组 i,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; } 这个函数里采用了指向整型变量 的指针变量i和j,分别指向将要 交换的两个数组元素。 3 7 9 1 6 5 4 2 a[0] a[1] a[2] p=x+m a[3] a[4] a[5] a[6] a[7] a[8] j a[9]
归纳起来,如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参和形参可分别为: 形参和实参都用数组名 实参用数组名,形参用指针变量 实参形参都用指针变量 实参用指针变量,形参用数组名 void main( ) { int a[10]; int *p=a; … f(p,10); } void f(int x[ ],int n) void main( ) { int a[10]; int *p=a; … f(p,10); } void f(int *x,int n) void main( ) { int a[10]; … f(a,10); } void f(int *x,int n) void main( ) { int a[10]; … f(a,10); } void f(int x[],int n)
这个main()有问题吗? #include <stdio.h> void main() { void inv(int *x,int n); int i, *a; printf("The original array:\n"); for(i=0;i<10;i++) scanf("%d",a+i); printf("\n"); inv(a,10); printf("The array has been inverted:\n"); printf("%d",*(a+i)); } 这个main()有问题吗?
总结:在指针变量的值未被初始化时,没有指向某个变量,不要使用该指针变量! #include <stdio.h> void main() { int a=5,*p1=&a,*p2; *p2=*p1; printf("%d,%d\n",*p1,*p2); } warning:p2变量没有初始化。 p2指向一个随机的内存空间,程序通过*p2=*p1;这条语句,对这个未知内存空间的值进行改变,这是很危险的。如果这个地址存放的是系统关键程序内容,可能会使系统崩溃。 总结:在指针变量的值未被初始化时,没有指向某个变量,不要使用该指针变量!
例7.6选择法排序 void 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[i]) k=j; if(k!=i) {t=k[i];x[i]=x[k];x[k]=t;} }
多维数组与指针 回顾"一维数组与指针"小节所讲的内容: 1.数组名是个指针常量,它指向数组中的首元素。 2.若数组名为a,a+i指向数组中第i+1个元素a[i];对数组中第i个元素的访问,可以使用a[i],也可以使用*(a+i)。
多维数组元素的地址 C语言把二维数组,作为一维数组来处理;这个一维数组的每个元素,又是一个一维数组。 例如int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}} 数组a是一个二维数组。C语言把a被看作一个一维数组,它包含3个元素a[0],a[1],a[2]。 而每一个元素,又是一个一维数组。例如a[0]是一维数组名,a[0]这个一维数组包括了a[0][0], a[0][1], a[0][2],a[0][3]这4个元素。
a(2000) a[0] 1 3 5 7 = a[1] = 9 11 13 15 a[2] = 17 19 21 23 按照一维数组的知识: 数组名是个指针常量,它指向数组中的第一个元素。可以得出:二维数组名a是个指针常量,它指向数组中第一个元素a[0] (a[0]同时也是一个一维数组{1, 3, 5, 7})。 同时,我们也可以得出:a=2000的话,a+1=2008,因为a+1指向a数组中第二个元素a[1](每一行是一个一维数组,包含4个整型数,占用8字节) 因为a+1指向a数组中的第二个元素a[1],因此可以用*(a+1)来表示第二个元素a[1](a[1]是一维数组{9,11,13,15})
a(2000) a[0] 1 3 5 7 = a[1] = 9 11 13 15 a[2] = 17 19 21 23 a[0],a[1],a[2]是3个一维数组。因此,a[0]作为一维数组的名字,代表数组{1,3,5,7}的首地址,即&a[0][0]。同样,a[1]中的值是&a[1][0],a[2]中的值是&a[2][0]。 考虑0行1列的元素地址如何表示? 答:a[0]+1。也可以用&a[0][1]来表示。a[0]的值为2000,a[0]+1的值为2002。 a[0]可以用*(a+0)来表示 因此0行1列的元素的地址a[0]+1也可以用*(a+0)+1来表示 i行j列的元素的地址为a[i]+j或*(a+i)+j 那么i行j列的元素的值,可以用*(*(a+i)+j)来表示。
在分析二维数组的时候,首先把二维数组看成一个一维数组,然后按照一维数组的方法来处理。 二维数组作为一维数组处理,其中的每一个元素,又是一个一维数组。对这些一维数组,也使用一维数组的方法来处理。
指向多维数组元素的指针变量 (1)指向数组元素的指针变量 例7.7 用指针变量输出二维数组元素的值 include <stdio.h> void main() { int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int *p; for(p=a[0];p<a[0]+12;p++) { if((p-a[0])%4==0) printf("\n"); printf("%4d",*p); } printf("\n");
因为p是指向int型变量的指针变量,每次p++操作,p指向下一个数组元素。 分析: 因为p是指向int型变量的指针变量,每次p++操作,p指向下一个数组元素。 二维数组元素按行依次存放,先存第一行,然后存第二行,再存第三行...... 1 3 5 7 9 11 13 15 17 19 21 23 p p+1
注意:a[i][j]的地址是&a[0][0]+i*m+j。其中m是二维数组的第二维的大小 a[1][2]是a[0][0]后面第1*4+2个元素
指向一维数组的指针变量 分析下面几个定义的含义: int *p; (定义一个指针变量p,p指向一个整型变量) int a[10];(定义一个长度为10的整型数组a。a是数组名,因此可以看作指针常量,a指向数组的首元素) int (*q)[10]; (定义一个指针变量q,它指向一个长度为10的整型数组) 以上定义的p和a,它们指向的内容是一个整型元素,占2个字节。但指针q,它指向的是一个数组,占2*10个字节。
例7.8 输出二维数组中任意一个元素 include <stdio.h> void main() { int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; int (*p)[4],i,j; /*定义p为指针变量,指向包含4个元素的整型数组*/ p=a; for(i=0;i<3;i++) for(j=0;j<4;j++) printf("%2d ",*(*(p+i)+j)); printf("\n"); }
说明: “int (*p)[4];” 表示p是一个指针变量,它指向包含4个整型元素的一维数组。这里如果不加括号int *p[4],表示定义一个指针数组(后面会讲到),因此括号不能省略。 a是二维数组名,因此a指向数组中的第一个元素a[0]。a[0]是一个包含4个整型元素的一维数组。因此,p=a这个赋值是合理的。
3. p+i是二维数组a的i行的起始地址,即元素a[i]的地址。 而*(p+i)表示的是二维数组的第i个元素a[i],它即是包含4个整型元素的普通一维数组名。因此对应的第j个元素的地址可以用*(p+i)+j来表示,这个地址中存放的值就用*(*(p+i)+j)表示。 p a[0] 1 3 5 7 a[1] 9 11 13 15 p+2 17 19 21 23 a[2]
用指向数组的指针作函数参数 一维数组名可以作函数参数,二维数组名也可以作函数参数。二维数组名,就是一个指向一维数组的指针。 例7.9 有一个班,3个学生,学习4门课程,计算总的平均分,以及第n个学生的成绩。
#include <stdio.h> void main() { void average(float *p, int n); void search(float (*p)[4], int n); float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}}; average(*score,12);/*12个成绩的平均值*/ search(score,2);/*求序号为2的学生成绩*/ } void average(float *p,int n) /*指向数组元素的指针p*/ { float *p_end; float sum=0,aver; p_end=p+n-1; for(;p<=p_end;p++) sum=sum+(*p); aver=sum/n; printf("average=%5.2f",aver);
void search(float (*p)[4], int n) /*指向长度为4的一维数组的指针p*/ { int i; printf("the score of No.%d are:\n",n); for(i=0;i<4;i++) printf("%5.2f",*(*(p+n)+i)); }
字符串与指针 在C语言中,可以用两种方法访问一个字符串: 1. 用字符数组存放一个字符串,然后输出该字符串。例如: void main() { char string[]="I love china!"; printf("%s\n",string); }
2. 用字符指针指向一个字符串。例如: void main() { char *string="I love China!"; printf("%s\n",string); } C编译器首先在内存中分配一个字符数组空间来保存字符串常量“I love China!”,然后,把这个字符串的第一个字符的地址,赋给指针变量string;不要误认为是把整个字符串"I love China!"赋给了变量string。 对一个字符串,可以通过首地址,使用%s来输出。
程序中,a和b都是数组名,因此*(a+i)表示的就是a[i],*(b+i)表示的是b[i]。 void main() { char a[]="I am a boy.", b[20]; int i; for(i=0;*(a+i)!='\0';i++) *(b+i)=*(a+i); *(b+i)='\0'; printf("string a is:%s\n",a); printf("string b is:"); for(i=0;b[i]!='\0';i++) printf("%c",b[i]); printf("\n"); } 程序中,a和b都是数组名,因此*(a+i)表示的就是a[i],*(b+i)表示的是b[i]。 输出一个字符串,可以使用%s输出,还可以通过%c依次输出每个字符。
字符指针作函数参数 例7.11用函数调用实现字符串的复制 (1)用字符数组作函数参数 void main() { void copy(char from[],char to[]); char a[]="I am a teacher"; char b[]="You are a student"; copy(a,b); printf("string b=%s\n", b); } void copy(char from[],char to[]) { int i=0; while(from[i]!='\0') to[i]=from[i]; i++; } to[i]='\0';
(2)使用字符指针变量作形参 void main() { void copy(char *from,char *to); char a[]="I am a teacher"; char b[]="You are a student"; char *x=a,*y=b; copy(x,y); /*使用copy(a,b);也可以*/ printf("string b=%s\n",b); } void copy(char *from,char *to) { for(;*from!='\0';form++,to++) *to=*from; *to='\0';
对字符指针变量和字符数组的说明 1.字符数组由若干个元素组成,每个元素存放一个字符。而字符指针变量用来存放一个地址。 2.字符指针变量可以通过赋值进行字符串的复制,但字符数组不行,只能通过字符串拷贝函数来复制。 char *p; char str[14], a[]="I am a boy"; p=a;(正确) str=a; (错误)
3. 对字符指针变量赋初值: char *p="I am a boy"; 是将字符串“I am a boy”第一个元素的地址赋给p。 4. 指针变量的值是可以改变的。例 void main() { char *a="I love China"; a=a+7; printf("%s\n",a); } 输出结果:China
指向函数的指针 函数也要占用内存。一个函数在编译之后,会生成二进制代码,并且编译器会为这个函数分配一个地址空间,这个地址空间的起始地址(入口地址),就称为函数的指针。 可以用一个指针变量指向函数,然后通过这个指针变量来调用此函数。
回顾第一章的例子: #include <stdio.h> void main( ) { int max(int x, int y); int a,b,c; scanf ("%d, %d" ,&a ,&b); c= max (a, b); printf ("max= %d" , c) } int max( int x , int y) { int z; if (x>y) z=x; else z=y; return (z); }
改写这个例子中的主函数: #include <stdio.h> void main( ) { int max(int x, int y); int (*p)(int, int); /*定义指向函数的指针变量p*/ p=max;/*max是函数名,指向函数入口地址*/ int a,b,c; scanf ("%d, %d" ,&a ,&b); c= (*p)(a, b); /*通过指针p来调用max*/ printf ("max= %d" , c) }
分析: 1. int (*p)(int, int); 这条语句是定义一个指向函数的指针变量p,该函数具有两个整型参数,并且函数返回值为整型。 定义中(*p)的括号不能省略,如果省去括号 int *p(int, int); 代表声明一个函数p,它的返回值是一个指向整型变量的指针。
3. 要调用max函数,可以直接使用函数名: 2. 与数组名指向数组首元素类似,函数名max也是一个地址常量,它保存该函数的起始地址。 p=max;这条赋值语句 使p指向函数max。 3. 要调用max函数,可以直接使用函数名: max(a,b); 也可以通过指针p (*p)(a,b); 因此,程序里有语句: c=(*p)(a,b); max p 指令1 指令2
指向函数的指针变量的定义的一般形式为: 数据类型 (*指针变量名)(函数参数表列) 例如: int (*p)(int, int); 指向函数的指针p,不能使用p++使p指向下一条指令。p+n或者p-n是无意义的。
指向函数的指针变量作函数参数 例7.12 设一个函数process,在调用它的时候,每次实现不同的功能。输入a和b两个数,第一次调用process时找出a和b中大的那个数,第二次找出其中小者,第三次求a与b之和。
#include <stdio.h> void main() { int max(int,int); int min(int,int); int add(int,int); void process(int,int,int(*fun)(int,int)); int a,b; scanf("%d,%d",&a,&b); printf("max="); process(a,b,max); printf("min="); process(a,b,min); printf("sum="); process(a,b,add); } int max(int x,int y) {return(x>y?x:y);} int min(int x,int y) {return(x>y?y:x);} int add(int x,int y) {return(x+y);} void process(int x,int y,int (*fun)(int,int)) { int result; result=(*fun)(x,y); printf("%d\n",result); }
说明: 在这个程序中,max,min,add分别是三个函数名,分别指向三个函数的起始位置。 main函数中3次调用了process,每一次的执行都不相同。因为process使用了指向函数的指针作参数,3次执行,分别调用了max,min和add函数。 这种程序设计方法增加了设计的灵活性。在UNIX操作系统中,内核和驱动程序接口部分的代码,就大量使用了指向函数的指针变量作函数参数。
返回指针值的函数 一个函数可以返回整型值,字符型值,浮点型值,还可以返回指针型值。 返回指针值的函数的一般形式: 类型名 * 函数名(参数表列); 例如int *a(int x,int y); a是函数名,调用它之后,能得到一个指向整型数据的指针。x和y是函数a的形参。 注意:*a的两侧没有加括号,如果加了括号 int (*a)(int x,int y); 就是定义了一个指向函数的指针变量a。
例7.13 有若干名学生的成绩(每个学生4门课程),要求在用户输入学生序号之后,输出该学生的全部成绩。 #include <stdio.h> void 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); p=search(score,m); for(i=0;i<4;i++) printf("%5.2f",*(p+i)); printf("\n"); } score是一个二维数组,score指向数组的第一行 a. 形参pointer是一个指向一维浮点数组的指针 b. 函数的返回值,是一个指向float型变量的指针。 实参score和形参pointer都是指向float型一维数组的指针,score传值给pointer。
float *search(float(*pointer)[4],int n) { float *pt; pt=*(pointer+n); return(pt); } 调用search函数返回的指针,即指向第n的学生的第一门成绩(这是第n行一维数组的第一个元素)。因此,可以使用循环语句打印*(p+i),即为各门成绩 pt为指向浮点型变量的指针,pt=*(pointer+n); pointer接受来自实参score的值,指向二维数组的第0行,因此pointer+n指向二维数组的第n行。 *(pointer+n)即为第n行构成的一维数组的首地址。将*(pointer+n)赋值给pt,使得pt指向这个一维数组的首元素。然后由return语句返回指针pt的值。
指针数组和指向指针的指针 指针数组 本质是一个数组,其中每个元素均为指针类型,称为指针数组。 指针数组的一般定义形式: 类型名 *数组名[数组长度]; 例如: int *p[4]; 定义了一个一维数组p,该数组长度为4,每个数组元素都为指向整型变量的指针。
指针数组的用途 指针数组适合用来指向若干个字符串,使字符串处理更加灵活。 考虑:图书馆有若干本书,书名为字符串,因此考虑用字符数组来存放书名。因为每个书名都是一个字符串,那么很多本书,就需要二维数组来存放。 从图中可以看出来,二维数组存放,比较浪费内存空间,且需要一个很大的连续内存空间才能存放。 F o l l o w m e \0 B A S I C \0 G r e a t W a l l \0 F O R T R A N \0 C o m p u t e r d e s i g n \0
如果使用指针数组,各元素分别指向各书名构成的字符串。这些字符串可单独存放,比用二维数组存放更节省空间。如果想对字符串排序也不用改变字符串的位置,直接对指针数组中的元素值进行修改,指向不同的字符串即可。 指针数组name 字符串 name[0] Follow me name[1] BASIC name[2] Great Wall name[3] FORTRAN name[4] Computer design
例7.14 将若干字符串按字母顺序输出 #include <stdio.h> #include <string.h> 例7.14 将若干字符串按字母顺序输出 #include <stdio.h> #include <string.h> void main() { void sort(char *name[],int n); void print(char *name[],int n); char *name[]={"Follow me","BASIC“, "Great Wall", "FORTRAN", "Computer design"}; int n=5; sort(name,n); print(name,n); } 定义了一个指针数组name,它有5个元素,初值分别是"Follow me", "BASIC“,"Great Wall", "FORTRAN", "Computer design"这5个字符串的起始地址。
void sort(char *name[],int n) { char *temp; 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) { temp=name[i]; name[i]=name[k]; name[k]=temp; } sort函数中交换的是name数组中的元素,其实就是使name数组中的指针元素,按从小到大的次序依次指向5个字符串。5个字符串在内存中始终未改变其存储地址。 sort函数使用strcmp对字符串进行比较。strcmp的两个实参name[k]和name[j]分别指向第k个和第j个字符串。strcmp的返回值表明了两个字符串的大小。 sort函数用于对字符串进行排序。sort函数的形参与实参名字相同name,是指针数组名。数组名作为函数参数时,形参name数组和实参name数组指向同一个数组。 void print(char *name[],int n) { int i; for(i=0;i<n;i++) printf("%s\n",name[i]); }
指向指针的指针 回顾指针数组,指针数组名name,是个指针常量,它指向数组中第一个元素name[0]。而元素name[0] 又是一个指针,指向字符串“Follow me"。因此,name就是指向指针的指针。 可以定义一个指针变量p,使它指向指针数组的元素,那么p就是指向指针的指针变量。 Follow me BASIC Great Wall FORTRAN Computer design name name[0] name[1] name[2] name[3] name[4] p
如何定义指向指针的指针变量? int **p; 相当于int *(*p); 先把(*p)看成一个整体,因此可以知道(*p)是一个指向整型量的指针变量。再对(*p)进行分解,p是指向指针变量的指针变量。 如果有: p=name+2; printf("%o",*p); printf("%s",*p); 第一个打印语句输出name[2]的值,即字符串“Great Wall”的地址; 第二个打印语句输出"Great Wall"。
例7.15 使用指向指针的指针 #include <stdio.h> void 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); } 定义了一个指针数组name并进行初始化,使数组中每一个元素都指向一个字符串。name作为数组名,指向首元素name[0]。 定义了一个指向指针的指针变量p,p和name都是指向指针的指针,处于等同的地位。用name对p赋值:p=name+i; 这个赋值语句使p指向数组元素name[i]。引用*p等同于引用name[i]。 printf语句通过%s输出name[i]
指针相关的数据类型总结 定义 含 义 int i; 定义整型变量i int *p p为指向整型量的指针变量 int a[n]; 含 义 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是一个指针变量,它指向一个指向整型数据的指针变量
一维数组与指针 int a[10]; int *p; p= &a[0]; p为指向数组元素的指针 p= &a[0]等价于p=a 通过a[i]或者*(p+i)来访问数组元素
二维数组与指针 int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a是一个二维数组,包含3个元素a[0],a[1],a[2]。 a作为数组名,指向其第一个元素a[0],a=&a[0] 而a[0]又是一个一维数组,所以数组名a[0]=&a[0][0] int *p=a[0]; p是指向数组元素的指针,赋值语句为p=a[0]; int (*p)[4]; p是指向一维数组的指针,赋值语句为p=a; int *p[4]; p是一个一维数组的名字,该数组的元素为指向整型量的指针
指针与函数 int (*p)(int, int); 指针p为指向函数的指针,该函数具有两个整型参数,并且返回值为整型。赋值语句为p=max; 函数调用语句max(a,b);相当于(*p)(a,b); int *p(int, int); 定义一个函数p,该函数的返回值为指向整型量的指针 。
指针相关的运算 指针变量加(减)一个整数: 例如:p++、p--、p+i、p-i、p+=i、p-=i 指针变量加1(减)是将该指针变量的值(为地址)和它指向的变量所占用的内存单元字节数进行加(减)。 两个指针变量可以相减:如果两个指针变量指向同一个数组的元素,则两个指针变量值之差是两个指针之间的元素个数。
指针相关的运算 指针变量赋值:将一个变量的地址赋给一个指针变量。 p=&a; (将变量a的地址赋给p) 不能执行p=1000; p=array; (将数组array的首地址赋给p) p=&array[i]; (将数组array第i个元素的地址赋给p) p=max; (max为已定义的函数,将max的入口地址赋给p) p1=p2; (p1和p2都是指针变量,将p2的值赋给p1) 不能执行p=1000; 指针变量可以有空值,即该指针变量不指向任何变量:p=NULL;
作业 P308 页练习题7.11 有n个人围坐一圈,顺序排号(n<500)。从第一个人开始1-3重复报数。凡报到3的退出圈子,请问最后剩下的是原来第几号的那位?