学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。
正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:
?
#include <stdio.h>
?
int main ()
{
?? int? var1;
?? char var2[10];
?
?? printf("var1 变量的地址: %pn",&var1? );
?? printf("var2 变量的地址: %pn",&var2? );
?
?? return 0;
}
|
当上面的代码被编译和执行时,它会产生下列结果:
var1 变量的地址: 0x7fff5cc109d4
var2 变量的地址: 0x7fff5cc109de
|
通过上面的实例,我们了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。
8.1什么是指针?
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。
指针变量声明的一般形式为:type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int??? *ip;??? /* 一个整型的指针 */
double *dp;?? ?/* 一个 double 型的指针 */
float? *fp;??? /* 一个浮点型的指针 */
char?? *ch;???? /* 一个字符型的指针 */
|
所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
8.2指针变量
8.2.1 使用指针变量的例子
#include <stdio.h>
int main()
{ int a=100,b=10;
//定义整型变量a,b,并初始化
int *pointer_1,*pointer_2;
//定义指向整型数据的指针变量pointer_1,pointer_2
pointer_1=&a; //把变量a的地址赋给指针变量pointer_1
?????? pointer_2=&b;?? //把变量b的地址赋给指针变量pointer_2
printf("a=%d,b=%dn",a,b); //输出变量a和b的值
printf("*pointer_1=%d,*pointer_2=%dn",*pointer_1,*pointer_2);
//输出变量a和b的值
return 0;
}
|
运行结果:
? 
上面变量对应的关系,如图:
?

?
注:定义指针变量时,左侧应有类型名,否则就不是定义指针变量。也称为基类型
8.2.2 怎样定义指针变量
定义指针的一般形式为:
类型名 *指针变量名;
如:
int *pointer_1;
注意:左端的int是在定义指针变量时必须指定的"基类型".换个说法就是类型名一定要有,否则是错误的定义。
如:
*pointer_1; //企图定义pointer_1为指针变量。出错
int *pointer_1; //正确,必须指定指针变量的基类型
?
说明:在定义指针变量时要注意一下几点:
(1) 指针变量前面的"*"表示该变量的类型为指针型变量。
(2) 在定义指针变量时必须制定基类型(即类型名)
一个变量的指针的含义包括两个方面,一是以存储单元编号表示的地址(如编号为2000的字节),一是它指向的存储单元的数据类型(如int,char,float等)。
(3)如何表示指针类型
指向整型数据的指针类型表示为"int*",读作"指向int的指针"或简称"int指针"
(4) 指针变量中只能存放地址(地址),不要讲一个整数赋给一个指针变量。如:
*pointer_1 = 100; //pointer_1是指针变量,100是整数,不合法的赋值
8.2.3 怎样引用指针变量
(1) 给指针变量赋值。如:
p = &a; //把a的地址赋给指针变量a
(2) 引用指针变量指向的变量
如果已执行"p = &a;",即指针变量p指向了整型变量a,则
printf("%d",*p);
作用:以整数形式输出指针变量p所指向的变量的值,即变量a的值
注:一定要带'*',否则输出地址
也可以用指针的形式对a重新赋值
*p = 1;
表示将1赋值给p当前p指向的变量,因为p指向变量a,则相当于把1赋值给a,即和"a = 1"等价。
(3) 引用指针变量的值。如:
printf("%o",p);
作用:以八进制数形式输出指针变量p的值,如果p指向了a,就是输出了a的地址,即&a.
printf("%o",&a);?? <==> printf("%o",p); //两者等价
例:
#include <stdio.h>
int main()
{
int *p,a;
a = 100;
p = &a;
printf("%on",p);
printf("%on",&a);
?
}
|
? 
要熟练掌握两个有关的运算符:
(1) &取地址运算符。&a是变量a的地址。
(2) * 指针运算符(或称“间接访问”运算符),*p代表指针变量p指向的对象。
?
#include <stdio.h>
int main()
{ int *p1,*p2,*p,b;???? //p1,p2的类型是int *类型
printf("please enter two integer numbers:");
?????? scanf("%d,%d",&a,&b);?????????????????????? //输入两个整数
p1=&a;?????? //使p1指向变量a
p2=&b;?????? //使p2指向变量b
if(a<b)?????? //如果a<b
{ p=p1;p1=p2;p2=p;}?? //使p1与p2的值互换
printf("a=%d,b);?? //输出a,b
printf("max=%d,min=%dn",*p1,*p2); //输出p1和p2所指向的变量的值
return 0;
}
|
运行结果:
? 
指针在过程中的变化:
? 
?
8.2.4 指针变量作为函数参数
例:对输入的两个整数按大小顺序输出。现用函数处理,而且用指针类型的数据作函数参数。
#include <stdio.h>
void swap(int *p1,int *p2);??? //对swap函数的声明
int main()
{
int a,b;
int *pointer_1,*pointer_2; //定义两个int *型的指针变量
printf("please enter a and b:");
scanf("%d,&b);? //输入两个整数
pointer_1=&a;?? //使pointer_1指向a
?????? pointer_2=&b;???????????????? //使pointer_2指向b
if(a<b) swap(pointer_1,pointer_2); //如果a<b,调用swap函数
printf("max=%d,b);? //输出结果
return 0;
}
?
void swap(int *p1,int *p2)?? //定义swap函数
{ int temp;
temp=*p1;???? //使*p1和*p2互换
*p1=*p2;
*p2=temp;
}
|
运行结果:
? 
指针变量pointer_1 pointer_2的变量过程
? 
?
注:要注意区分以下三种形式,可自己调试,增加理解
void swap(int *p1,int *p2)//定义swap函数
{ int temp;
temp=*p1;//使*p1和*p2互换
*p1=*p2;
*p2=temp;
}
|
void swap(int *p1,int *p2)
{ int *temp;
*temp=*p1;
????? *p1=*p2;
*p2=*temp;
}
|
void swap(int x,int y)
{ int temp;
temp=x;
????? x=y;
y=temp;
}
|
下面有一个问题需要注意,也可以直接跳过,如果想提升可以理解,以理解为主,不需要死记硬背。
指针变量作为函数参数
函数的调用可以(而且只可以)得到一个返回值(即函数值),而使用指针变量作参数,可以得到多个变化了的值。如果不用指针变量是难以做到这一点的。要善于利用指针法。
如果想通过函数调用得到n个要改变的值,可以这样做:
?
1.在主调函数中设n个变量,用n个指针变量指向它们;
?
2.设计一个函数,有n个指针形参。在这个函数中改变这n个形参的值;
?
3.在主调函数中调用这个函数,在调用时将这n个指针变量作实参,将它们的值,也就是相关变量的地址传给该函数的形参;
?
3.在执行该函数的过程中,通过形参指针变量,改变它们所指向的n个变量的值;
?
5.主调函数中就可以使用这些改变了值的变量。
|
例:输入3个整数a,b,c,要求按由大到小的顺序将它们输出。用函数实现。
#include <stdio.h>
int main()
{ void exchange(int *q1,int *q2,int *q3); //函数声明
int a,c,*p3;
printf("please enter three numbers:");
scanf("%d,%d,&b,&c);
p1=&a;p2=&b;p3=&c;
exchange(p1,p2,p3);
printf("The order is:%d,%dn",c);
return 0;
}
?
void exchange(int *q1,int *q3) //将3个变量的值交换的函数
{ void swap(int *pt1,int *pt2);? //函数声明
if(*q1<*q2) swap(q1,q2);? //如果a<b,交换a和b的值
if(*q1<*q3) swap(q1,q3);? //如果a<c,交换a和c的值
if(*q2<*q3) swap(q2,q3);? //如果b<c,交换b和c的值
}
?
void swap(int *pt1,int *pt2)?? //交换2个变量的值的函数
{ int temp;
temp=*pt1;??? //交换*pt1和*pt2变量的值
*pt1=*pt2;
*pt2=temp;
}
|
运行结果:
? 
?
8.3 通过指针引用数组
8.3.1 数组元素的指针
数组元素的指针就是素组元素的地址。
int a[10]={1,3,5,7,9,11,13,15,17,19};? //定义a为包含10个整型数据的数组
int *p;???????? //定义p为指向整型变量的指针变量
p=&a[0];??????? //把a[0]元素的地址赋给指针变量p
|
引用数组元素的方式
(1) 下标法
(2) 指针法
p=&a[0]; //p的值是a[0]的地址
等价于
p=a; //p的值是数组a首元素(即a[0])的地址
注:程序中的数组名不代表整个数组,只代表数组首元素的地址。
以下是指针变量的初始化方式:
(1) 在定义后再单独进行初始化
int *p;
p=&a[0]; //不应写成*p=&a[0];
(2) 在定义时直接进行初始化
int *p=&a[0];
等价于
int *p=a;
以上三种的作用:将a数组首元素(即a[0])的地址赋值给指针变量p(而不是赋给*p)
8.3.2 在引用数组元素时指针的运算
在指针已指向一个数组元素时,可以对指针进行以下运算:
1.加一个整数(用+或+=),如p+1,表示指向同一数组中的下一个元素;
?
2.减一个整数(用-或-=),如p-1,表示指向同一数组中的上一个元素;
?
3.自加运算,如p++,++p;
?
4.自减运算,如p--,--p。
?
两个指针相减,如p1-p2(p1和p2都指向同一数组中的元素时才有意义),结果为两个地址之差除以数组元素的长度。
注:两个地址不能相加,如p1+p2是无实际意义的。
?
说明:
*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。
p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址
[]实际上是变址运算符,即将a[i]按a+i计算地址,然后找出此地址单元中的值。
例:有一个整型数组a,有10个元素,要求输出数组中的全部元素。
//下标法
#include <stdio.h>
int main()
{ int a[10];
int i;
printf("please enter 10 integer numbers:");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(i=0;i<10;i++)
printf("%d ",a[i]);
//数组元素用数组名和下标表示
printf("%n");
return 0;
}
|
//通过数组名计算数组元素地址,找出元素的值
#include <stdio.h>
int main()
{ int a[10];
int i;
printf("please enter 10 integer numbers:");
for(i=0;i<10;i++)
scanf("%d",*(a+i));
//通过数组名和元素序号计算元素地址找到该元素
printf("n");
return 0;
}
|
//用指针变量指向数组元素
#include <stdio.h>
int main()
{ int a[10];
int *p,i;
printf("please enter 10 integer numbers:");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(p=a;p<(a+10);p++)
printf("%d ",*p);
//用指针指向当前的数组元素
printf("n");
return 0;
}
|
说明:
第(1)和第(2)种方法执行效率是相同的。C编译系统是将a[i]转换为*(a+i)处理的,即先计算元素地址。因此用第(1)和第(2)种方法找数组元素费时较多。
?
第(3)种方法比第(1)、第(2)种方法快,用指针变量直接指向元素,不必每次都重新计算地址,像p++这样的自加操作是比较快的。这种有规律地改变地址值(p++)能大大提高执行效率。
|
?
例:通过指针变量输出整型数组a的10个元素。
#include <stdio.h>
int main()
{ int *p,i,a[10];
p=a;??? //p指向a[0]? ①
printf("please enter 10 integer numbers:");
for(i=0;i<10;i++)
scanf("%d",p++); //输入10个整数给a[0]~a[9]
for(i=0;i<10;i++,p++)
printf("%d ",*p); //想输出a[0]~a[9] ②
printf("n");
return 0;
}
|
#include <stdio.h>
int main()
{ int i,a[10],*p=a; //p的初值是a,p指向a[0]
printf("please enter 10 integer numbers:");
for(i=0;i<10;i++)
scanf("%d",p++);
p=a;??? //重新使p指向a[0]
for(i=0;i<10;i++,*p);
printf("n");
return 0;
}
|
运行上面两个代码看看有何区别,造成这种区别的原因是什么?
答:第一种是因为指针已经到了数组的末尾,继续循环则超出了数组的范围,则会直接输出P对应的地址值。如图所示:
? 
?
技巧:
设p开始时指向数组a的首元素(即p=a)
(1)
p++; //使p指向下一元素a[1]
*p; //得到下一个元素a[1]的值
(2)
*p++; /*由于++和*同优先级,结合方向自右而左,因此它等价于*(p++)。先引用p的值,实现*p的运算,然后再使p自增1*/
(3)
*(p++); //先取*p值,然后使p加1
*(++p); //先使p加1,再取*p
(4)
++(*p); /*表示p所指向的元素值加1,如果p=a,则相当于++a[0],若a[0]的值为3,则a[0]的值为4。注意: 是元素a[0]的值加1,而不是指针p的值加1*/
(5)
如果p当前指向a数组中第i个元素a[i],则:
*(p--) //相当于a[i--],先对p进行“*”运算,再使p自减
*(++p) //相当于a[++i],先使p自加,再进行“*”运算
*(--p) //相当于a[--i],先使p自减,再进行“*”运算
8.3.4 用数组名作函数参数
以变量名和数组名作为函数参数的比较
? 
?
C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。
?
注意:实参数组名代表一个固定的地址,或者说是指针常量,但形参数组名并不是一个固定的地址,而是按指针变量处理。
?
例:将数组a中n个整数按相反顺序存放
#include <stdio.h>
int main()
{ void inv(int x[],int n); //inv函数声明
int i,a[10]={3,6,4,2};
printf("The original array:n");
for(i=0;i<10;i++)
printf("%d ",a[i]); //输出未交换时数组各元素的值
printf("n");
inv(a,10);??? //调用inv函数,进行交换
printf("The array has been inverted:n");
for(i=0;i<10;i++)
printf("%d ",a[i]); //输出交换后数组各元素的值
printf("n");
return 0;
}
void inv(int x[],int n)? //形参x是数组名
{ int temp,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; //把x[i]和x[j]交换
}
return;
}
|
#include <stdio.h>
int main()
{ void inv(int *x,int n);
int i,a[i]);
printf("n");
inv(a,10);
printf("The array has been inverted:n");
for(i=0;i<10;i++)
printf("%d ",a[i]);
printf("n");
return 0;
}
?
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;} //*i与*j交换
return;
}
|
以上两种方式所得结果是一致的,只是实现的形式有区别。所得结果都为下图所示:

说明:以下的形式要认识,以下四种都是等价的
? 
?
例:将数组a中n个整数按相反顺序存放,用指针变量作实参。
#include <stdio.h>
int main()
{ void inv(int *x,arr[10],*p=arr;? //指针变量p指向arr[0]
printf("The original array:n");
for(i=0;i<10;i++,p++)
scanf("%d",p);? //输入arr数组的元素
printf("n");
p=arr;??? //指针变量p重新指向arr[0]
inv(p,10);??? //调用inv函数,实参p是指针变量
printf("The array has been inverted:n");
for(p=arr;p<arr+10;p++)
printf("%d ",*p);
printf("n");
return 0;
}
?
void inv(int *x,int n)???????? //定义inv函数,形参x是指针变量
{ int *p,m,*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;
}
|
注:如果用指针变量作实参,必须先使指针变量有确定值,指向一个已定义的对象。
?
例:用指针方法对10个整数按由大到小顺序排序。(选择排序法)
//形参是数组
#include <stdio.h>
int main()
{ void sort(int x[],int n); //sort函数声明
int i,a[10];
p=a;???? //指针变量p指向a[0]
printf("please enter 10 integer numbers:");
for(i=0;i<10;i++)
scanf("%d",p++); //输入10个整数
p=a; //指针变量p重新指向a[0]
sort(p,10);?? //调用sort函数
for(p=a,i=0;i<10;i++)
{ printf("%d ",*p); //输出排序后的10个数组元素
p++;
}
printf("n");
return 0;
}
?
void sort(int x[],int n)//x是形参数组名
{ int i,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;}
}
}
|
//形参是指针
#include <stdio.h>
int main()
{ void sort(int x[],*p); //输出排序后的10个数组元素
p++;
}
printf("n");
return 0;
}
?
void sort(int *x,int n) //形参x是指针变量
{ int i,t;
for(i=0;i<n-1;i++)
{ k=i;
for(j=i+1;j<n;j++)
if(*(x+j)>*(x+k)) k=j; //*(x+j)就是x[j],其他亦然
if(k!=i)
{ t=*(x+i); *(x+i)=*(x+k); *(x+k)=t;}
}
}
|
?
8.3.5 通过指针引用多维数组
注:这章节了解即可,如果想提升自己,不仅需要理解还需要会使用。
理解以下这张图便可了解多维数组:
? 
?
说明:我们在这里说明一下,在计算机底层是不存在二维数组这种存储方式的,都是以一维数组为基础逻辑化出二维数组,而一维数组不仅是逻辑化更是物理上也是可以实现的。总的来说,二维数组是一维数组的逻辑化,本质上二维数组还是一维数组。只是为了方便人们理解,才把二维数组分为行和列的方式显示。
?
如果用一个指针变量pt来指向此一维数组:
int (*pt)[4];
//表示pt指向由4个整型元素组成的一维数组,此时指针变量pt的基类型是由4个整型元素组成的一维数组
?
例:输出二维数组的有关数据(地址和元素的值)。
#include <stdio.h>
int main()
{ int a[3][4]={1,19,21,23};
printf("%d,*a);??? //0行起始地址和0行0列元素地址
printf("%d,a[0],*(a+0));?? //0行0列元素地址
printf("%d,&a[0],&a[0][0]);? //0行起始地址和0行0列元素地址
printf("%d,a[1],a+1);?? //1行0列元素地址和1行起始地址
printf("%d,&a[1][0],*(a+1)+0); //1行0列元素地址
printf("%d,a[2],*(a+2));?? //2行0列元素地址
printf("%d,&a[2],a+2);?? //2行起始地址
printf("%d,a[1][0],*(*(a+1)+0)); //1行0列元素的值
printf("%d,*a[2],*(*(a+2)+0)); //2行0列元素的值
return 0;
}
|
结果如下:
? 
?
说明:要注意看懂以上的格式,区分各种形式的联系和区别。如a和*a在输出上是等价的
?
例:有一个3×4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值。
#include <stdio.h>
int main()
{ int a[3][4]={1,23};
int *p;?????? //p是int *型指针变量
for(p=a[0];p<a[0]+12;p++)?? //使p依次指向下一个元素
{ if((p-a[0])%4==0) printf("n"); //p移动4次后换行
????????????? printf("%4d",*p);?????????????????????????? //输出p指向的元素的值
}
printf("n");
return 0;
}
|
运行结果:

?
例:输出二维数组任一行任一列元素的值。
?
#include <stdio.h>
int main()
{ int a[3][4]={1,23};? //定义二维数组a并初始化
int (*p)[4],j;?? //指针变量p指向包含4个整型元素的一维数组
p=a;???? //p指向二维数组的0行
printf("please enter row and colum:");
scanf("%d,&i,&j); //输入要求输出的元素的行列号
printf("a[%d,%d]=%dn",*(*(p+i)+j));?? //输出a[i][j]的值
return 0;
}
|
?
#include <stdio.h>
int main()
{ int a[4]={1,7};? //定义一维数组a,包含4个元素
int (*p)[4];?? //定义指向包含4个元素的一维数组的指针变量中
p=&a;??? //使p指向一维数组
printf("%dn",(*p)[3]); //输出a[3],输出整数7
return 0;
}
|
?
比较:
① int a[4];(a有4个元素,每个元素为整型)
② int (*p)[4];
第②种形式表示(*p)有4个元素,每个元素为整型。也就是p所指的对象是有4个整型元素的数组,即p是指向一维数组的指针,见图8.24。应该记住,此时p只能指向一个包含4个元素的一维数组,不能指向一维数组中的某一元素。p的值是该一维数组的起始地址。虽然这个地址(指纯地址)与该一维数组首元素的地址相同,但它们的基类型是不同的。
|

?
指向由m个元素组成的一维数组的指针变量
要注意指针变量的类型,从“int (*p)[4];”可以看到,p的类型不是int *型,而是int (*)[4]型,p被定义为指向一维整型数组的指针变量,一维数组有4个元素,因此p的基类型是一维数组,其长度是16字节。“*(p+2)+3”括号中的2是以p的基类型(一维整型数组)的长度为单位的,即p每加1,地址就增加16个字节(4个元素,每个元素4个字节),而“*(p+2)+3”括号外的数字3,不是以p的基类型的长度为单位的。由于经过*(p+2)的运算,得到a[2],即&a[2][0],它已经转化为指向列元素的指针了,因此加3是以元素的长度为单位的,加3就是加(3×4)个字节。虽然p+2和*(p+2)具有相同的值,但由于它们所指向的对象的长度不同,因此(p+2)+3和*(p+2)+3的值就不相同了。
|
一维数组名可以作为函数参数,多维数组名也可作函数参数。
用指针变量作形参,以接受实参数组名传递来的地址。可以有两种方法:
① 用指向变量的指针变量;
② 用指向一维数组的指针变量。
例8:有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩。
#include <stdio.h>
int 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的学生的成绩
return 0;
}
?
void average(float *p,int n)? //定义求平均成绩的函数
{ float *p_end;
float sum=0,aver;
p_end=p+n-1;
//n的值为12时,p_end的值是p+11,指向最后一个元素
for(;p<=p_end;p++)
sum=sum+(*p);
aver=sum/n;
printf("average=%5.2fn",aver);
}
?
void search(float (*p)[4],int n)
//p是指向具有4个元素的一维数组的指针
{ int i;
printf("The score of No.%d are:n",n);
for(i=0;i<4;i++)
????????????? printf("%5.2f ",*(*(p+n)+i));
printf("n");
}
|
运行结果:
? 
?
注:实参与形参如果是指针类型,应当注意它们的基类型必须一致。不应把int *型的指针(即数组元素的地址)传给int (*)[4] 型(指向一维数组)的指针变量,反之亦然。
例:在上例的基础上,查找有一门以上课程不及格的学生,输出他们的全部课程的成绩。
#include <stdio.h>
int main()
{ void search(float (*p)[4],int n); //函数声明
float score[3][4]={{65,57,{58,98}};
//定义二维数组函数score
search(score,3);??? //调用search函数
return 0;
}
?
void search(float (*p)[4],int n)
//形参p是指向包含4个float型元素的一维数组的指针变量
{ int i,flag;
for(j=0;j<n;j++)
{ flag=0;
for(i=0;i<4;i++)
if(*(*(p+j)+i)<60) flag=1;
//*(*(p+j)+i)就是score[j][i]
if(flag==1)
{ printf("No.%d fails,his scores are:n",j+1);
for(i=0;i<4;i++)
printf("%5.1f ",*(*(p+j)+i));
//输出*(*(p+j)+i)就是输出score[j][i]的值
printf("n");
}
}
}
|
运行结果:
? 
?
?
8.4 通过指针引用字符串
8.4.1 字符串的引用方式
(1)用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明“%s”输出该字符串。
(2)用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。
?
例:定义一个字符数组,在其中存放字符串″I love China!″,输出该字符串和第8个字符。
#include <stdio.h>
int main()
{ char string[]="I love China!"; //定义字符数组sting
printf("%sn",string);? //用%s格式声明输出string,可以输出整个字符串
?????? printf("%cn",string[7]);???????? //用%c格式输出一个字符数组元素
return 0;
}
|
运行结果:
? 
?
例:通过字符指针变量输出一个字符串。
#include <stdio.h>
int main()
{ char *string="I love China!"; //定义字符指针变量string并初始化
printf("%sn",string);? //输出字符串
return 0;
}
|
运行结果:
? 

?
在C语言中只有字符变量,没有字符串变量。
char *string="I love China!";
等价于
char *string;? //定义一个char *型变量
string=″I love China!″;
//把字符串第1个元素的地址赋给字符指针变量string
注:string被定义为一个指针变量,基类型为字符型。它只能指向一个字符类型数据,而不能同时指向多个字符数据,更不是把″I love China!″这些字符存放到string中(指针变量只能存放地址),也不是把字符串赋给*string。只是把″I love China!″的第1个字符的地址赋给指针变量string。
?
可以对指针变量进行再赋值,string=″I am a student.″;? //对指针变量string重新赋值
可以通过字符指针变量输出它所指向的字符串,printf(″%sn″,string); //%s可对字符串进行整体的输入输出
?
说明:%s是输出字符串时所用的格式符,在输出项中给出字符指针变量名string,则系统会输出string所指向的字符串第1个字符,然后自动使string加1,使之指向下一个字符,再输出该字符……如此直到遇到字符串结束标志′ ′为止。注意,在内存中,字符串的最后被自动加了一个′ ′。
?
例:将字符串a复制为字符串b,然后输出字符串b。
//用数组的方式输出
#include <stdio.h>
int main()
{ char a[]="I am a student.",b[20]; //定义字符数组
int i;
for(i=0;*(a+i)!=' ';i++)
*(b+i)=*(a+i);? //将a[i]的值赋给b[i]
*(b+i)=' ';??? //在b数组的有效字符之后加' '
printf("string a is:%sn",a);//输出a数组中全部有效字符
printf("string b is:");
for(i=0;b[i]!=' ';i++)
printf("%c",b[i]); //逐个输出b数组中全部有效字符
printf("n");
return 0;
}
|
?
//用指针变量
#include <stdio.h>
int main()
{ char a[]="I am a boy.",b[20],*p2;
p1=a;p2=b;
//p1,p2分别指向a数组和b数组中的第一个元素
for(;*p1!=' ';p1++,p2++)? //p1,p2每次自加1
*p2=*p1;
//将p1所指向的元素的值赋给p2所指向的元素
*p2=' ';?? //在复制完全部有效字符后加' '
printf("string a is:%sn",a); //输出a数组中的字符
printf("string b is:%sn",b); //输出b数组中的字符
return 0;
}
|
?
8.4.2 字符指针作函数参数
例:用函数调用实现字符串的复制
(1) 用字符数组名作为函数参数
#include <stdio.h>
int main()
{ void copy_string(char from[],char to[]);
char a[]="I am a teacher.";
char b[]="You are a student.";
printf("string a=%snstring b=%sn",b);
printf("copy string a to string b:n");
copy_string(a,b);? //用字符数组名作为函数实参
printf("nstring a=%snstring b=%sn",b);
return 0;
}
?
void copy_string(char from[],char to[])??? //形参为字符数组
{ int i=0;
while(from[i]!=' ')
{ to[i]=from[i]; i++;}
to[i]=' ';
}
|
运行结果:
? 
?
(2) 用字符型指针变量作实参
#include <stdio.h>
int main()
{ void copy_string(char from[],char to[]); //函数声明
char a[]="I am a teacher.";? //定义字符数组a并初始化
char b[]="You are a student."; //定义字符数组b并初始化
?????? char *from=a,*to=b; //from指向a数组首元素,to指向b数组首元素
printf("string a=%snstring b=%sn",b);
printf("copy string a to string b:n");
copy_string(from,to); //实参为字符指针变量
printf("nstring a=%snstring b=%sn",b);
return 0;
}
void copy_string(char from[],char to[])?? //形参为字符数组
{ int i=0;
while(from[i]!=' ')
{ to[i]=from[i]; i++;}
to[i]=' ';
}
|
运行结果:
? 
?
说明:指针变量from的值是a数组首元素的地址,指针变量to的值是b数组首元素的地址。它们作为实参,把a数组首元素的地址和b数组首元素的地址传递给形参数组名from和to(它们实质上也是指针变量)。其他与程序(1)相同。
(3) 用字符指针变量作形参和实参
#include <stdio.h>
int main()
{ void copy_string(char *from,char *to);
char *a="I am a teacher.";? //a是char*型指针变量
char b[]="You are a student."; //b是字符数组
char *p=b;? //使指针变量p指向b数组首元素
printf("string a=%snstring b=%sn",b); //输出a串和b串
printf("copy string a to string b:n");
copy_string(a,p); //调用copy_string函数,实参为指针变量
printf("nstring a=%snstring b=%sn",b); //输出改变后的a串和b串
return 0;
}
?
void copy_string(char *from,char *to) //定义函数,形参为字符指针变量
{ for(;*from!=' ';from++,to++)
{ *to=*from;}
*to=' ';
}
|
运行结果:
? 
?
函数void copy_string(char *from,char *to)还有以下等价几种,只需记住一种即可,其它做了解。最好的情况当然是全都会用。
void copy_string(char *from,char *to)
{ for(;(*to++=* from++)!=' ';);
//或for(;*to++=* from++;);
}
|
void copy_string(char *from,char *to)
{ while((*to=*from)!=' ')
//或while(*to=*from)
{ to++; from++;}
}
|
void copy_string(char *from,char *to)
{ while(*from!=' ')
//或while(*from) ,因为' '的ASCII码为0
*to++=*from++;
*to=' ';
}
|
void copy_string(char *from,char *to)
{ while((*to++=*from++)!=' ');
//或while(*to++=*from++)
}
|
void copy_string(char from[],char to[])
{ char *p1,*p2;
p1=from;p2=to;
while((*p2++=*p1++)!=' ');
}
|
?
|
字符指针作为函数参数时,实参与形参的类型有以下几种对应关系:
? 
?
8.4.3 使用字符指针变量和字符数组的比较
? 
?
例:改变指针变量的值。
#include <stdio.h>
int main()
{ char *a="I love China!";
a=a+7;?? //改变指针变量的值,即改变指针变量的指向
printf("%sn",a); //输出从a指向的字符开始的字符串
return 0;
}
|
运行结果:
? 
?
?
#include <stdio.h>
int main()
{ char str[]={"I love China!"};
str=str+7;
printf("%sn",str);
return 0;
}
|
说明:
(1)指针变量的值是可以改变的,而字符数组名代表一个固定的值(数组首元素的地址),不能改变。
(2)指针变量a的值是可以变化的。printf函数输出字符串时,从指针变量a当时所指向的元素开始,逐个输出各个字符,直到遇' '为止。而数组名虽然代表地址,但它是常量,它的值是不能改变的。
8.5 指向函数的指针
8.5.1 什么是函数指针
如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间。这段内存空间有一个起始地址,也称为函数的入口地址。每次调用函数时都从该地址入口开始执行此段函数代码。
函数名就是函数的指针,它代表函数的起始地址。
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。例如: int (*p)(int,int);
定义p是一个指向函数的指针变量,它可以指向函数类型为整型且有两个整型参数的函数。此时,指针变量p的类型用int (*)(int,int)表示。
8.5.2 用函数指针变量调用函数
例:用函数求整数a和b中的大者
//(1)通过函数名调用函数
#include <stdio.h>
int main()
{ int max(int,int); //函数声明
int a,c;
printf("please enter a and b:");
scanf("%d,&b);
c=max(a,b);? //通过函数名调用max函数
printf("a=%dnb=%dnmax=%dn",c);
return 0;
}
?
int max(int x,int y)? //定义max函数
{ int z;
if(x>y) z=x;
else z=y;
return(z);
}
|
?
(2) 通过指针变量调用它所指向的函数
#include <stdio.h>
int main()
{ int max(int,int); //函数声明
int (*p)(int,int); //定义指向函数的指针变量p
int a,c;
p=max;?? //使p指向max函数
printf("please enter a and b:");
scanf("%d,&b);
c=(*p)(a,b);? //通过指针变量调用max函数
printf("a=%dnb=%dnmax=%dn",c);
return 0;
}
int max(int x,int y)? //定义max函数
{ int z;
if(x>y)z=x;
else z=y;
return(z);
}
|
运行结果:
? 
?
8.5.3 怎样定义和使用指向函数的指针变量
定义指向函数的指针变量的一般形式为:
类型名 (*指针变量名)(函数参数表列)
如:
int (*p)(int,int);
说明:
(1) 定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。
(2)? 如果要用指针调用函数,必须先使指针变量指向该函数。
(3) 在给函数指针变量赋值时,只须给出函数名而不必给出参数。
(4) 用函数指针变量调用函数时,只须将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。
(5) 对指向函数的指针变量不能进行算术运算,如p+n,p++,p--等运算是无意义的。
(6) 用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。
例:输入两个整数,然后让用户选择1或2,选1时调用max函数,输出二者中的大数,选2时调用min函数,输出二者中的小数。
#include <stdio.h>
int main()
{ int max(int,int); //函数声明
int min(int x,int y); //函数声明
int (*p)(int,int); //定义指向函数的指针变量
int a,n;
printf("please enter a and b:");
scanf("%d,&b);
printf("please choose 1 or 2:");
scanf("%d",&n); //输入1戓2
if(n==1) p=max; //如输入1,使p指向max函数
else if (n==2) p=min; //如输入2,使p指向min函数
c=(*p)(a,b);? //调用p指向的函数
printf("a=%d,b);
if(n==1) printf("max=%dn",c);
else printf("min=%dn",int y)
{ int z;
if(x>y) z=x;
else z=y;
return(z);
}
?
int min(int x,int y)
{ int z;
if(x<y) z=x;
else z=y;
return(z);
}
|
运行结果:
? 
OR
? 
8.5.4 用指向函数的指针作函数参数
? 
?
例:有两个整数a和b,由用户输入1,2或3。如输入1,程序就给出a和b中的大者,输入2,就给出a和b中的小者,输入3,则求a与b之和。
#include <stdio.h>
int main()
{ int fun(int x,int y,int (*p)(int,int)); //fun函数声明
?????? int max(int,int);??????????????? //max函数声明
int min(int,int);?? //min函数声明
int add(int,int);?? //add函数声明
int a=34,b=-21,n;
printf("please choose 1,2 or 3:");
scanf("%d",&n);?? //输入1,2或3之一
if(n==1) fun(a,max);? //输入1时调用max函数
else if(n==2) fun(a,min); //输入2时调用min函数
else if(n==3) fun(a,add); //输入3时调用add函数
return 0;
}
?
int fun(int x,int)) //定义fun函数
{????? int result;
result=(*p)(x,y);
?????? printf("%dn",result);???????????? //输出结果
}
?
?
int max(int x,int y) //定义max函数
{ int z;
if(x>y) z=x;
else z=y;
printf("max=" );
?????? return(z);??????????? //返回值是两数中的大者
}
?
int min(int x,int y) //定义min函数
{ int z;
if(x<y) z=x;
else z=y;
printf("min=");
return(z);? //返回值是两数中的小者
}
?
int add(int x,int y) //定义add函数
{ int z;
z=x+y;
printf("sum=");
return(z);? //返回值是两数之和
}
|
运行结果:不同选择有不同的答案
? 


?
?
?
?
?
?
?
8.6 返回指针值的函数
定义返回指针值的函数的一般形式为:
类型名 *函数名(参数表列);
解释:
一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型而已。如:
int *a(int x,int y);
a是函数名,调用它以后能得到一个int*型(指向整型数据)的指针,即整型数据的地址。x和y是函数a的形参,为整型。
注:在“*a”两侧没有括号,在a的两侧分别为*运算符和()运算符。而()优先级高于*,因此a先与()结合,显然这是函数形式。这个函数前面有一个*,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针指向整型变量。
例:有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
#include <stdio.h>
int main()
{ float score[][4]={{60,80,90},{56,89,88},{34,78,66}};
//定义数组,存放成绩
float *search(float (*pointer)[4],int n); //函数声明
float *p;
int i,k;
printf("enter the number of student:");
scanf("%d",&k); //输入要找的学生的序号
printf("The scores of No.%d are:n",k);
p=search(score,k); //调用search函数,返回score[k][0]的地址
for(i=0;i<4;i++)
printf("%5.2ft",*(p+i)); //输出score[k][0]~score[k][3]的值
printf("n");
return 0;
}
?
float *search(float (*pointer)[4],int n)
//形参pointer是指向一维数组的指针变量
{ float *pt;
pt=*(pointer+n); //pt的值是&score[k][0]
return(pt);
}
|
运行结果:
? 
?
例:对上例题,找出其中有不及格的课程的学生及其学生号。
#include <stdio.h>
int main()
{ float score[][4]={{60,66}};
//定义数组,存放成绩
float *search(float (*pointer)[4]); //函数声明
float *p;
int i,j;
for(i=0;i<3;i++)??? //循环3次
{ p=search(score+i);
//调用search函数,如有不及格返回score[i][0]的地址,否则返回NULL
if(p==*(score+i))
//如果返回的是score[i][0]的地址,表示p的值不是NULL
{ printf("No.%d score:",i);
for(j=0;j<4;j++)
printf("%5.2f? ",*(p+j));
//输出score[i][0]~score[i][3]的值
printf("n");
????????????? }
}
?????? return 0;
}
?
float *search(float (*pointer)[4])
//定义函数,形参pointer是指向一维数组的指针变量
{ int i=0;
float *pt;
pt=NULL; //先使pt的值为NULL
for(;i<4;i++)
if(*(*pointer+i)<60) pt=*pointer;
????????????? //如果有不及格课程,使pt指向score[i][0]
return(pt);
}
|
运行结果:

?
8.7 指针数组和多重指针
8.7.1 什么是指针数组
定义一维指针数组的一般形式为
类型名 *数组名[数组长度];
如:
int *p[4];
一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。
指针数组比较适合用来指向若干个字符串,使字符串处理更加方便灵活。
?
例:将若干字符串按字母顺序(由小到大)输出。
? 
?
?
#include <stdio.h>
#include <string.h>
int main()
{ void sort(char *name[],int n);? //函数声明
void print(char *name[],int n); //函数声明
char *name[]={"Follow me","BASIC",
?????? "Great Wall","FORTRAN","Computer design"};
//定义指针数组,它的元素分别指向5个字符串
int n=5;
?????? sort(name,n); ??? //调用sort函数,对字符串排序
print(name,n); //调用print函数,输出字符串
return 0;
}
?
void sort(char *name[],int n)?? //定义sort函数
{ char *temp;
int i,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;}
}
}
?
void print(char *name[],int n) //定义print函数
{ int i;
for(i=0;i<n;i++)
printf("%sn",name[i]);
//按指针数组元素的顺序输出它们所指向的字符串
}
|
运行结果:
? 
?
在了解了指针数组的基础上,需要了解指向指针数据的指针变量,简称为指向指针的指针。
? 
?
例:使用指向指针数据的指针变量。
#include <stdio.h>
int main()
{ char *name[]={"Follow me","Great Wall","Computer design"};
char **p;
int i;
for(i=0;i<5;i++)
{ p=name+i;
printf("%sn",*p);
}
return 0;
}
|
运行结果:
? 
?
说明:
(1) p是指向char*型数据的指针变量,即指向指针的指针。在第1次执行for循环体时,赋值语句“p=name+i;”使p指向name数组的0号元素name[0],*p是name[0]的值,即第1个字符串首字符的地址,用printf函数输出第1个字符串(格式符为%s)。执行5次循环体,依次输出5个字符串。
?
(2) 指针数组的元素也可以不指向字符串,而指向整型数据或实型数据等。
?
例:有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整型数组各元素的值。
#include <stdio.h>
int main()
{ int a[5]={1,9};
int *num[5]={&a[0],&a[1],&a[3],&a[4]};
int **p,i;??? //p是指向指针型数据的指针变量
p=num;??? //使p指向num[0]
for(i=0;i<5;i++)
{ printf("%d ",**p);
p++;
}
printf("n");
return 0;
}
|
运行结果:
? 

8.7.3 指针数组作main函数的形参
? 
?
?
?
int main(int argc,char *argv[])
{ while(argc>1)
{ ++argv;
printf("%sn",*argv);
--argc;
}
return 0;
}
|
int main(int argc,char *argv[])
{ while(argc-->1)
printf("%sn",*++argv);
return 0;
}.
|
? 
?
?
?
8.8 动态内存分配与指向它的指针变量
8.8.1 什么是内存的动态分配
? 
?
8.8.2 怎样建立内存的动态分配
1. 用malloc函数开辟动态存储区
函数原型为
void *malloc(unsigned int size);
作用:在内存的动态存储区中分配一个长度为size的连续空间。形参size的类型定为无符号整型(不允许为负数)。此函数的值(即“返回值”)是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的第一个字节。如:
malloc(100);????????????? //开辟100字节的临时分配域,函数值为其第1个字节的地址
指针的基类型为void,即不指向任何类型的数据,只提供一个纯地址。如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL)。
?
2.用calloc函数开辟动态存储区
函数原型为
void *calloc(unsigned n,unsigned size);
作用:在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。
p=calloc(50,4);? //开辟50×4个字节的临时分配域,把首地址赋给指针变量p
用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的第一个字节的指针;如果分配不成功,返回NULL。
?
3.用realloc函数重新分配动态存储区
函数原型为
void *realloc(void *p,unsigned int size);
作用:如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用realloc函数重新分配。
realloc(p,50); //将p所指向的已分配的动态空间改为50字节
?
用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。
?
4.用free函数释放动态存储区
函数原型为
void free(void *p);
作用:释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。
free(p);? //释放指针变量p所指向的已分配的动态空间
free函数无返回值。
?
说明:以上4个函数的声明在stdlib.h头文件中,在用到这些函数时应当用“#include <stdlib.h>”指令把stdlib.h头文件包含到程序文件中。
?
8.8.3 void指针类型
C 99允许使用基类型为void的指针类型。可以定义一个基类型为void的指针变量(即void*型变量),它不指向任何类型的数据。在将它的值赋给另一指针变量时由系统对它进行类型转换,使之适合于被赋值的变量的类型。
int *pt;
pt=(int *)mcaloc(100); //mcaloc(100)是void *型,把它转换为int *型
注:不要把“指向void类型”理解为能指向“任何的类型”的数据,而应理解为“指向空类型”或“不指向确定的类型”的数据。
例:建立动态数组,输入5个学生的成绩,另外用一个函放数检查其中有无低于60分的,输出不合格的成绩。
#include <stdio.h>
#include <stdlib.h>??? //程序中用了malloc函数,应包含stdlib.h
int main()
{ void check(int *);??? //函数声明
int *p1,i;????? //p1是int型指针
p1=(int *)malloc(5*sizeof(int)); //开辟动态内存区,将地址转换成int *型,然后放在p1中
?????? for(i=0;i<5;i++)
????????????? scanf("%d",p1+i);?????????????????? //输入5个学生的成绩
check(p1);???? //调用check函数
return 0;
}
?
void check(int *p)???? //定义check函数,形参是int*指针
{ int i;
printf("They are fail:");
for(i=0;i<5;i++)
????????????? if(p[i]<60) printf("%d ",p[i]); ?????? //输出不合格的成绩
printf("n");
}
|
运行结果:
?
? 
有关指针的小结





?
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|