C语言教材第8章指针.docx
《C语言教材第8章指针.docx》由会员分享,可在线阅读,更多相关《C语言教材第8章指针.docx(92页珍藏版)》请在冰点文库上搜索。
C语言教材第8章指针
第8章指针
教学目标
●理解指针与指针运算符
●能够使用指针通过引用向函数传递参数
●理解指针的算术运算
●理解指针、数组和字符串之间的密切关系
●了解如何使用函数指针
●理解动态内存分配
8.1简介
在本章中,我们将讨论C语言最强大的功能之一:
指针。
指针是C语言中最难以掌握的功能。
指针使得程序可以模拟引用调用以及创建和控制动态数据结构。
掌握指针的应用,可以使程序简洁、高效。
每一个学习和使用C语言的人,都应当深入的学习和掌握指针。
8.2什么是指针?
8.2.1变量、变量的值与变量的地址
认识我们所使用的计算机
无论在外观上存在多大的差异,实际上,每台计算机都可以划分为六个逻辑单元:
(1)输入设备,例如键盘。
(2)输出设备,例如显示器。
(3)内存单元
(4)运算器(ALU)
(5)控制器(CU)
(6)辅助存储单元,例如硬盘。
在微机中,运算器和控制器实际上是一块芯片,称为CPU(中央处理单元)。
它们的关系如下图:
所有的程序必须调入内存才能执行。
也就是说,我们所写的代码在程序运行前会被调入内存,我们所定义的变量都存储在内存中。
那么,内存是如何管理的呢?
内存以字节为基本单位,给每个字节一个编号。
编号从0开始,0,1,2,3……直至每个字节都有编号。
在32位微机中,内存编号是32位的2进制数,其值从00000000000000000000000000000000至111111*********11111111111111111。
因此32位微机最多能访问的内存数是232个字节,即4G字节。
其模型如下图所示:
什么是变量?
变量本质上是一个或者几个内存单元。
例如,我们在程序中写
charx,y;
longa;
doubleb;
实际上是在内存中申请存放这5个变量的空间。
假设本程序从10000号内存单元开始存储这些变量,则程序执行时,内存分配情况如下:
由上图可以看出,变量x占用10013号内存单元,变量y占用10012号内存单元,变量a占用10008、10009、10010和10011号共4个内存单元,变量b则占用10000~10007共8个内存单元。
一个变量实际占用的内存单元的个数由变量的类型决定。
什么是变量的值?
变量的值是变量所占用的内存单元中存放的数值。
例如上例中,若执行语句x=‘a’;y=‘c’;a=39;b=2.718,则内存的情形如下:
什么是变量的地址?
变量的地址是这个变量所占用的第一个内存单元的编号。
例如在上例中,变量x的地址是10013,变量y的地址是10012,变量a的地址是10008,变量b的地址是10000。
8.2.2指针
指针(pointer)是包含另一个变量内存地址的变量。
有时候,我们也称指针为指针变量。
在上一节我们讲到,变量的值是该变量所占用的内存单元中保存的数值。
整形变量中保存的是整数的值,浮点型变量中保存的是浮点数的值,字符型变量中保存的是字符的ASCII码值。
指针的特殊之处在于,在指针变量的内存单元中,保存的是另一个变量的地址。
例如下面三行代码:
int*p;
intx;
p=&x;
第一行代码中的*不是乘号,而是指针申明符,表示紧跟该符号的变量是一个指针变量。
int*联合在一起,表示变量p是一个指向整形的指针(Pointertoint)。
我们通常简称为:
p是一个整形指针。
在scanf函数中,我们已经知道&这个符号是取地址符。
因此,第三行&x的作用是取变量x的地址。
p=&x就是在p的内存单元中存放x这个变量的地址。
可以图示如下:
p被赋值之前p被赋值为x的地址
p=&x的实质是将p赋值为x的地址。
但程序的每次执行,变量x在内存中的位置通常都不同,我们并不关心x的地址值实际上是多少。
因此,我们通常将指针p=&x表述为“让指针p指向变量x”。
图示如下:
8.2.3定义指针变量
与所有其他变量一样,在使用指针变量之前必须先对它们进行定义。
定义指针变量的语法是:
基本类型*指针变量名
指针中包含的是其它变量的地址,编译器必须知道如何解释地址里的数据,因此就要求显式说明指针所指向的数据的类型。
指针指向的数据的类型称为指针的基本类型(basetype)。
例如:
int*p1;/*定义一个整形指针,用来保存整形变量的地址*/
char*p2;/*定义一个字符型指针,用来保存字符型变量的地址*/
float*p3;/*定义一个单精度型指针,用来保存单精度型变量的地址*/
double*p4;/*定义一个双精度型指针,用来保存双精度型变量的地址*/
必须注意,如果使用同一个声明语句来声明多个指针的话,必须给每个变量都加上*。
例如:
int*p1,*p2;
而声明
int*p1,p3
则表示声明p1为指向整形的指针,而p2是整形变量。
8.2.4间接运算符
*运算符通常称为间接运算符或者解参考运算符(dereferencingoperator),它返回其操作数(也就是指针)所指向的对象。
间接运算符是一元运算符,其操作数必须是一个指针值。
当间接引用(dereference)一个指针时,实际上是去引用该指针所指向的变量。
【案例8.1】间接运算符的使用
◆程序实现
#include
intmain()
{
intx=3;
int*p;
p=&x;
printf("x=%d\t",x);
printf("*p=%d\n",*p);
*p=7;
printf("x=%d\t",x);
printf("*p=%d\n",*p);
return0;
}
该程序运行结果是
当程序执行p=&x之后,指针p指向变量x,如下图所示
在语句printf("*p=%d\n",*p)中,*p即对指针p做解参考运算,得到它所指向的变量x。
因此此语句就是输出x的值3。
同理,语句*p=7实际上是将指针p所指向的变量x赋值为7,此操作可图示如下:
所以后面的语句输出x的值和输出*p的值均是7。
指针可以被初始化。
例如以下语句是合法的:
intx;
int*p=&x;
这个语句片段使指针p指向变量x。
需要注意的是,int*p=&x中的*不是间接运算符,而是指针申明符。
该语句是对p赋值而不是对p所指向的变量赋值。
因此下列语句是合法的:
intx=3;
int*p;
p=&x;
而下列语句是错误的:
intx=3;
int*p;
*p=x;
在以上程序片段的第三行,试图将p所指向的变量赋值为x。
而此时指针p还没有被指向任何变量,如果执行该语句,会出现以下错误:
【案例8.2】间接运算符*与取地址运算符&互为逆运算
◆程序实现
#include
intmain()
{
inta;//a是整数
int*aPtr;//*aPtr是指向整数的指针
a=7;
aPtr=&a;//让aPtr指向a
printf("a的地址是%p\n",&a);//输出变量a的地址
printf("aPtr的值是%p\n",aPtr);//输出aPtr的值
printf("\n\na的值是%d\n",a);//输出a的值
printf("*aPtr的值是%d\n",*aPtr);//输出aPtr所指向的变量的值
printf("\n\n*和&互为逆运算\n");
printf("&*aPtr=%p\n",&*aPtr);
printf("*&aPtr=%p\n",*&aPtr);
return0;
}
该程序运行结果如下:
本程序中的格式控制符%p将内存位置作为十六进制整数输出。
注意,a的地址和aPtr的值在输出中是一样的,因此证明了a的地址实际上被赋值给指针变量aPtr。
运算符&和*是互为逆运算的,当它们连续作用于aPtr时,无论顺序如何,输出结果都是相同的。
8.2.5指针赋值
指针赋值就是改变指针的指向。
【案例8.3】指针赋值示例
◆程序实现
#include
intmain()
{
doublea=3.5,b=7.4;
double*pA=&a;
double*pB=&b;
printf("*pA=%.2lf\n",*pA);
printf("*pB=%.2lf\n",*pB);
pA=pB;//使pA指向pB所指向的变量
printf("\n\n*pA=%.2lf\n",*pA);
printf("*pB=%.2lf\n",*pB);
return0;
}
在本程序中,当程序运行double*pA=&a;
double*pB=&b;
后,指针pA指向a,指针pB指向b。
如下图所示:
因此第一次输出*pA=3.50
*pB=7.40
当程序运行pA=pB后,指针pA指向pB所指向的变量,如下图所示:
因此第二次输出*pA=7.40
*pB=7.40
8.2.6指针作为函数的参数
在C语言中,函数的参数传递都是值传递,即形参是实参的一个副本,因此形参的改变不会影响实参。
值传递减低了函数之间的耦合性,有助于实现较好的程序架构。
但在某些情况下,值传递不能满足程序员的要求。
例如下面示例中的myswap函数。
【案例8.4】值传递示例
◆程序实现
#include
voidmyswap(intx,inty);
intmain(void)
{
intx=3,y=7;
printf("x=%d\ty=%d\n",x,y);
myswap(x,y);
printf("x=%d\ty=%d\n",x,y);
return0;
}
voidmyswap(intx,inty)
{
intz;
z=x;
x=y;
y=z;
}
程序运行结果:
本程序试图调用myswap函数交换主函数中变量x与y的值,但是没有成功。
这是因为主函数中的变量x、y与myswap函数中的参数x、y是不同的变量。
如下图所示:
myswap函数中的x、y的值由主函数调用myswap函数时传递而来。
myswap函数执行时,修改了myswap函数的x、y的值,但是并没有改变main函数中x和y的值。
如下图所示:
因此程序最后在main函数中输出x与y的值时,main函数中的x与y并没有被交换。
如上例所示,参数值传递时,被调函数不能改变主调函数的局部变量。
为了使被调函数具有改变主调函数的局部变量的功能,只能以指针作为参数,模拟引用调用。
【案例8.5】指针作为函数参数,模拟引用调用
◆程序实现
1include
2
3voidmyswap(int*xPtr,int*yPtr);
4
5intmain(void)
6{
7intx=3,y=7;
8printf("x=%d\ty=%d\n",x,y);
9myswap(&x,&y);
10printf("x=%d\ty=%d\n",x,y);
11return0;
12}
13
14voidmyswap(int*xPtr,int*yPtr)
15{
16intz;
17z=*xPtr;
18*xPtr=*yPtr;
19*yPtr=z;
20}
本程序运行结果:
可以看到,这里的myswap函数修改了主函数中的变量x和y。
◆问题思考:
myswap函数如何实现对主函数中局部变量的修改?
1、当主函数开始运行,主函数内有局部变量x和y。
如下图所示
因此程序第8行输出x与y的值时,输出结果为x=3y=7。
2、第9行调用myswap函数,通过参数传递,将x的地址和y的地址传递给myswap函数的参数xPtr和yPtr。
参数传递本质上是用实参对形参作初始化。
因此本次参数传递相当于语句int*xPtr=&x;
int*yPtr=&y;
从而使myswap函数中的指针xPtr指向main函数中的x,myswap函数中的指针yPtr指向main函数中的y。
如下图所示:
3、参数传递完成后,myswap函数开始运行。
4、第17行,z=*xPtr;将xPtr所指向的变量赋值给z。
也就是将main函数中的x赋值给z。
如下图所示:
5、第18行,*xPtr=*yPtr;将yPtr所指向的变量赋值给xPtr所指向的变量。
也就是将main函数中的y赋值给main函数中的x。
如下图所示:
6、第19行,*yPtr=z;将z赋值给yPtr所指向的变量。
也就是将myswap函数中的z赋值给main函数中的y。
从而在事实上交换了main函数中变量x和y的值。
如下图所示:
7、myswap函数结束,myswap函数中的局部变量xPtr、yPtr和z生存期结束。
程序返回main函数第10行,内存中只剩main函数的变量x和y。
如下图所示:
8、执行第10行,输出main函数中x与y的值,输出结果为x=7y=3。
9、主函数结束,整个程序运行结束。
【案例8.6】以指针作为函数参数返回多个结果
◆知识聚焦:
指针作为函数参数最常见的情况是当一个函数需要向调用程序返回多个值时。
单个结果可以很简单的作为函数的返回值返回。
如果需要从一个函数返回多个结果时,用返回值就不再合适了。
解决此问题的方法是通过指针作为参数来返回结果。
◆问题描述:
写一个函数,把用分钟表示的时间转换成合适的以小时和分钟表示的时间。
例如,235分钟等于3小时55分钟。
◆思路分析:
该函数需要接收一个表示分钟数的整数,需要返回两个整数:
表示小时的数字和剩余的表示分钟的数字。
因此必须使用指针作为输出参数。
◆程序实现:
#include
voidgetHM(inttime,int*pHoure,int*pMinutes);
intmain()
{
inttime,hours,minutes;
printf("请输入分钟数:
\t");
scanf("%d",&time);
getHM(time,&hours,&minutes);
printf("小时:
分钟\t%d:
%d\n",hours,minutes);
return0;
}
voidgetHM(inttime,int*pHoure,int*pMinutes)
{
*pHoure=time/60;
*pMinutes=time%60;
}
程序运行结果为:
在本程序中,函数getHM有三个参数。
第一个参数time给函数提供输入数据,后两个参数pHours和pMinutes使函数能把结果传递回主调函数。
在调用该函数时,后两个参数必须制定为地址,数据可以存入这些地址,所以调用为getHM(time,&hours,&minutes);函数调用之后,变量之间的关系如下图:
因此getHM函数中对*pHours和*pMinutes的赋值实际上是对main函数中变量hours和minutes的赋值。
8.2.7返回指针的函数
函数的返回值可以是指针。
例如,编写一个简单的程序,来判定两个数字中的较小者。
此时,需要一个指针来指向两个变量a和b中的较小者。
由于为了找到这个指针,需要传递给函数两个指针,并通过一个条件表达式来确定哪个值更小。
一旦知道了较小值,就以指针的形式返回它的位置。
这个返回值随后被放入主调函数的指针p中,使得在调用后,p指向a或b,这取决于他们各自的值。
程序代码如下:
#include
double*smaller(double*p1,double*p2);
intmain(void)
{
doublea,b;
double*p;
printf("请输入两个双精度数\n");
scanf("%lf%lf",&a,&b);
p=smaller(&a,&b);
printf("%lf和%lf中较小数是:
\t%lf\n",a,b,*p);
return0;
}
double*smaller(double*p1,double*p2)
{
return(*p1<*p2?
p1:
p2);
}
若函数的返回值是指针,特别需要注意的是:
不能返回指向局部变量的指针。
因为被调函数结束后会释放它所对应的内存,若有指针指向某个已经被释放的内存空间,会导致程序运行出错。
8.3指针与一维数组
8.3.1使指针指向数组
当声明一个数组时,编译器在相邻的内存空间分配足够的存储空间,以容纳数组的所有元素,并记录下该空间的首地址和空间的大小。
该首地址就是数组第一个元素(索引0)的存储位置。
数组名就是指向这个数组第一个元素的指针常量。
假设声明如下的a数组:
inta[5]={10,20,30,40,50};
假定a数组的首地址为1000,并假定每个整数需要4个字节的存储空间,于是该数组的5个元素如下存储:
数组名a就是指向第一个元素a[0]的指针常量。
如果把p声明为一个整形指针,那么通过赋值语句p=a;就可以使指针p指向a数组的第一个元素a[0]。
也就是说,语句p=a等价于p=&a[0]。
8.3.2指针算术运算
指针可以执行的算术运算有:
1、指针自增
2、指针自减
3、指针加上一个整形常量或整形变量n
4、指针减去一个整形常量或整形变量n
5、两个指针相减
注意,只有当指针指向某个数组元素时,指针的算术运算才有意义。
在本节中,我们分别来讨论指针的这5种运算。
指针自增
指针自增就是指向本数组的下一个元素。
【案例8.7】指针自增
◆程序实现
1#include
2
3intmain(void)
4{
5doublea[5]={3.2,4.75,7.2,9,1.7};
6double*p=a;
7inti;
8
9for(i=0;i<=4;i++)
10{
11printf("a[%d]=%lf\n",i,*p);
12p++;
13}
14
15return0;
16}
本程序运行结果如下:
◆程序分析:
1、程序第5行,数组a被初始化之后,内存图示如下:
2、第6行,double*p=a;之后,p指向数组第一个元素a[0]。
内存图示如下:
3、第一次for循环(此时i的值为0),执行第11行printf(“%lf\n”,*p)时输出指针p指向的变量a[0],因此输出3.200000。
4、执行第12行p++,指针p指向数组的下一个元素,图示如下:
5、第二次for循环(此时i的值为1),执行第11行printf(“%lf\n”,*p)时输出指针p指向的变量a[1],因此输出4.750000。
6、执行第12行p++,指针p指向数组的下一个元素,图示如下:
7、继续执行循环。
每循环一次p的指向后移一个元素。
因此循环5次后输出数组所有元素。
指针自减
指针自减就是指向本数组的上一个元素。
【案例8.8】指针自减
◆程序实现
1#include
2
3intmain(void)
4{
5doublea[5]={3.2,4.75,7.2,9,1.7};
6double*p=&a[4];
7inti;
8
9for(i=0;i<=4;i++)
10{
11printf("%lf\n",*p);
12p--;
13}
14
15return0;
16}
本程序的运行结果如下:
◆程序分析
1、程序第5行,数组a被初始化之后,内存图示如下:
2、第6行,double*p=&a[4];之后,p指向数组第5个元素a[4]。
内存图示如下:
3、第一次for循环(此时i的值为0),执行第11行printf(“%lf\n”,*p)时输出指针p指向的变量a[4],因此输出1.70000。
4、执行第12行p--,指针p指向数组的上一个元素,图示如下:
5、第二次for循环(此时i的值为1),执行第11行printf(“%lf\n”,*p)时输出指针p指向的变量a[3],因此输出9.000000。
6、执行第12行p--,指针p指向数组的上一个元素,图示如下:
7、继续执行循环。
每循环一次p的指向前移一个元素。
因此循环5次后输出数组所有元素。
如果指针间接运算符和指针自增在同一表达式中出现,情况稍微有点复杂。
请看下面这个程序:
1#include
2
3intmain(void)
4{
5doublea[5]={3.2,4.75,7.2,9,1.7};
6double*p=a;
7inti;
8
9for(i=0;i<=4;i++)
10printf("%lf\n",*p++);
11
12return0;
13}
第10行*p++的理解依赖于运算符的优先级和结合性。
查运算符优先级表可知自增运算符与指针间接运算符优先级相同,结合性为从右向左。
因此*p++应理解为*(p++)。
即表示以下含义:
间接引用指针p,使其当前指向的对象作为左值返回。
然后使指针指向数组中的下一个元素。
指针加上一个整形常量或整形变量n
指针加上一个整形常量或整形变量n的结果是得到一个指向本指针所指向的数组元素之后第n个元素的指针。
【案例8.9】指针加上一个整形常量或整形变量n
◆程序实现:
1#include
2
3intmain(void)
4{
5doublea[5]={3.2,4.75,7.2,9,1.7};
6double*p=a;
7
8p=p+3;
9printf("%lf\n",*p);
10
11return0;
12}
本程序的运行结果为:
◆程序分