1、第第9章章 算法和数据结构基础算法和数据结构基础呼风唤雨的指针呼风唤雨的指针哈尔滨工业大学哈尔滨工业大学9.1指针类型指针类型C语言世界中的如意金箍棒语言世界中的如意金箍棒n指针是指针是C语言中的语言中的如意金箍棒如意金箍棒“稀饭(稀饭(C Fans)”最挚爱的武器最挚爱的武器n强转与指针,并称强转与指针,并称C C语言的两大神器语言的两大神器某存储区域1000ContentsContentsContents00b03700直接到变量名标识的存储单元中直接到变量名标识的存储单元中读取变量的值读取变量的值直接寻址直接寻址通过其他变量间接找到变量的通过其他变量间接找到变量的地址读取变量的值地址读取
2、变量的值间接间接寻址寻址9.1.1变量的寻址方式变量的寻址方式printf(&a=%pn,&a);变量变量首字节首字节的地址的地址&a9.1.2 指针变量的定义、初始化及其解引用指针变量的定义、初始化及其解引用n指针(指针(Pointer)类型的变量类型的变量指针变量指针变量 pa;/pa可以指向一个可以指向一个int型数据,但指向哪里不确定型数据,但指向哪里不确定指针变量指向的数据类型指针变量指向的数据类型,称为指针的称为指针的基类型基类型指针变量只能指向指针变量只能指向同一基类型同一基类型的变量的变量某存储区域1000ContentsContentsContents?00b037009.1
3、.2 指针变量的定义、初始化及其解引用指针变量的定义、初始化及其解引用int*pa;n指针变量使用之前指针变量使用之前必须初始化必须初始化n若你不知把它指向哪里,那就指向若你不知把它指向哪里,那就指向NULLint*pa=NULL;warning:pa is used uninitialized空指针空指针无效指无效指针针(在(在stdio.h中定义为中定义为0)9.1.2 指针变量的定义、初始化及其解引用指针变量的定义、初始化及其解引用n如何如何访问访问指针变量指向的数据?指针变量指向的数据?间接寻址运算符间接寻址运算符访问(引用)指针变量指向的变量的值访问(引用)指针变量指向的变量的值指针
4、的解引用指针的解引用(Pointer Dereference)#include int main(void)int a=0;int *pa=&a;*pa=7;printf(a=%dn,a);printf(*p=%dn,*pa);return 0;n*pa是是a的的别名别名(alias)n解引用空指针解引用空指针程序崩溃程序崩溃*pa returns a(because pa points to a)9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参n如何在被调函数中改变实参的值?如何在被调函数中改变实参的值?按地址调用(按地址调用(Call by Reference)n为什么用指针变量作
5、函数参数能改变主调函数中的变量的值为什么用指针变量作函数参数能改变主调函数中的变量的值?9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参n【例【例9.1】下面两个程序分别演示利用普通变量和指针变量作函数参数。】下面两个程序分别演示利用普通变量和指针变量作函数参数。/利用普通变量作函数参数利用普通变量作函数参数#include int CubeByValue(int n);int main(void)int number=5;number=CubeByValue(number);printf(%dn,number);return 0;int CubeByValue(int n)retur
6、n n*n*n;/利用指针变量作函数参数利用指针变量作函数参数#include void CubeByReference(int*nPtr);int main(void)int number=5;CubeByReference(&number);printf(%dn,number);return 0;void CubeByReference(int*nPtr)*nPtr=*nPtr*nPtr*nPtr;9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参/利用普通变量作函数参数利用普通变量作函数参数#include int CubeByValue(int n);int main(void)
7、int number=5;number=CubeByValue(number);printf(%dn,number);return 0;int CubeByValue(int n)return n*n*n;9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参/利用普通变量作函数参数利用普通变量作函数参数#include int CubeByValue(int n);int main(void)int number=5;number=CubeByValue(number);printf(%dn,number);return 0;int CubeByValue(int n)return n*n
8、*n;9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参/利用指针变量作函数参数利用指针变量作函数参数#include void CubeByReference(int*nPtr);int main(void)int number=5;CubeByReference(&number);printf(%dn,number);return 0;void CubeByReference(int*nPtr)*nPtr=*nPtr*nPtr*nPtr;9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参n【例【例9.2】从键盘任意输入两个整数,编程实现将其交换后再重新输出。】从键盘任意输入两个
9、整数,编程实现将其交换后再重新输出。请通过单步执行的方式运行下面程序,并分析哪个两数交换函数能够真请通过单步执行的方式运行下面程序,并分析哪个两数交换函数能够真正实现两数的交换。正实现两数的交换。#include void Swap1(int x,int y);void Swap2(int*x,int*y);int main(void)int a=15,b=8;printf(Before swap1:a=%d,b=%dn,a,b);Swap1(a,b);printf(After swap1:a=%d,b=%dn,a,b);a=15;b=8;printf(Before swap2:a=%d,b=
10、%dn,a,b);Swap2(&a,&b);printf(After swap2:a=%d,b=%dn,a,b);return 0;void Swap1(int x,int y)int temp;temp=x;x=y;y=temp;void Swap2(int*x,int*y)int temp;temp=*x;*x=*y;*y=temp;9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参n【例【例9.2】从键盘任意输入两个整数,编程实现将其交换后再重新输出。】从键盘任意输入两个整数,编程实现将其交换后再重新输出。请通过单步执行的方式运行下面程序,并分析哪个两数交换函数能够真请通过单步执行
11、的方式运行下面程序,并分析哪个两数交换函数能够真正实现两数的交换。正实现两数的交换。void Swap1(int x,int y)int temp;temp=x;x=y;y=temp;9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参n【例【例9.2】从键盘任意输入两个整数,编程实现将其交换后再重新输出。】从键盘任意输入两个整数,编程实现将其交换后再重新输出。请通过单步执行的方式运行下面程序,并分析哪个两数交换函数能够真请通过单步执行的方式运行下面程序,并分析哪个两数交换函数能够真正实现两数的交换。正实现两数的交换。void Swap2(int*x,int*y)int temp;temp
12、=*x;*x=*y;*y=temp;9.2 指针变量与模拟按引用传参指针变量与模拟按引用传参n思考:思考:下面下面两两个个Swap()函数能否实现两数互换呢?函数能否实现两数互换呢?void Swap(int*x,int*y)int*pTemp;pTemp=x;x=y;y=pTemp;void Swap(int*x,int*y)int*pTemp;*pTemp=*x;*x=*y;*y=*pTemp;n例如,若有函数原型为:例如,若有函数原型为:int Fun(int a,int b);n则可定义函数指针则可定义函数指针 int(*f)(int,int);n令令f=Fun;就是让就是让f指向函数
13、指向函数Fun()编译器将编译器将不带不带()()的函数名的函数名解释为该解释为该函数的入口地址函数的入口地址函数指针变量存储的是函数在内存中的入口地址函数指针变量存储的是函数在内存中的入口地址n函数指针函数指针(Function Pointer)就是指向函数的指针变量就是指向函数的指针变量数据类型数据类型(*指针变量名指针变量名)(形参列表形参列表);9.3.1 函数指针的概念函数指针的概念函数指针的定义函数指针的定义n而若有函数原型为:而若有函数原型为:float Fun(float a,float b);n则需定义函数指针则需定义函数指针 float(*f)(float,float);n
14、令令f=Fun;n定义时的参数类型与指向的函数参数类型不匹配定义时的参数类型与指向的函数参数类型不匹配 float(*f)(int,int);/错误错误float(*f)();/不建议不建议9.3.1 函数指针的概念函数指针的概念定义函数指针时的常见错误定义函数指针时的常见错误 int(*f)(int,int);n忘了写前一个忘了写前一个()()int*f(int,int);声明了一个函数名为声明了一个函数名为f、返回值是整型指针类型的函数返回值是整型指针类型的函数n忘了写后一个忘了写后一个()()int(*f);定义了一个定义了一个整型指针变量整型指针变量9.3.1 函数指针的概念函数指针的
15、概念9.3.1 函数指针的概念函数指针的概念n【例【例9.3】下面程序仅用于演示函数指针的应用】下面程序仅用于演示函数指针的应用#include void Fun(int x,int y,int(*f)(int,int);int Max(int x,int y);int Min(int x,int y);int main(void)int a,b;scanf(%d,%d,&a,&b);Fun(a,b,Max);Fun(a,b,Min);return 0;void Fun(int x,int y,int(*f)(int,int)int result=(*f)(x,y);printf(%dn,re
16、sult);int Max(int x,int y)printf(max=);return xy?x:y;int Min(int x,int y)printf(min=);return xy?x:y;9.3.1 函数指针的概念函数指针的概念n【例【例9.3】下面程序仅用于演示函数指针的应用】下面程序仅用于演示函数指针的应用9.3.1 函数指针的概念函数指针的概念n【例【例9.3】下面程序仅用于演示函数指针的应用】下面程序仅用于演示函数指针的应用n【例例9.49.4】计算函数的定积分。采用如图计算函数的定积分。采用如图9-229-22所示的梯形法编程计算所示的梯形法编程计算如下两个函数在积分区间
17、如下两个函数在积分区间a,ba,b内的定积分内的定积分9.3.2 函数指针的应用函数指针的应用1021d)1(xxy3022d1xxxyn为区间等分数nabhfloat IntegralF1(float a,float b)float s,h;int n=100,i;s=(F1(a)+F1(b)/2;h=(b-a)/n;for(i=1;in;i+)s=s+F1(a+i*h);return s*h;float F1(float x)return 1+x*x;211)(xxfy1=IntegralF1(0.0,1.0);float IntegralF2(float a,float b)float
18、s,h;int n=100,i;s=(F2(a)+F2(b)/2;h=(b-a)/n;for(i=1;in;i+)s=s+F2(a+i*h);return s*h;float F2(float x)return x/(1+x*x);y1=IntegralF2(0.0,3.0);221)(xxxf9.3.2 函数指针的应用函数指针的应用y1=Integral(F1,0.0,1.0);y2=Integral(F2,0.0,3.0);float Integral(float(*f)(float),float a,float b)float s,h;int n=100,i;s=(*f)(a)+(*f)
19、(b)/2;h=(b-a)/n;for(i=1;in;i+)s=s+(*f)(a+i*h);return s*h;float F1(float x)return 1+x*x;211)(xxffloat F2(float x)return x/(1+x*x);221)(xxxffloat IntegralF1(float a,float b)float s,h;int n=100,i;s=(F1(a)+F1(b)/2;h=(b-a)/n;for(i=1;in;i+)s=s+F1(a+i*h);return s*h;y1=IntegralF1(0.0,1.0);9.3.2 函数指针的应用函数指针的
20、应用9.4.1指针上的游走指针上的游走int*p=a;int a10;a0a1 a2 a3 a4apaint*p=&a0;a5a6 a7 a8 a9n当指针变量指向数组元素时当指针变量指向数组元素时可以使用指针代替数组下标进行操作可以使用指针代替数组下标进行操作n对指针变量执行算术运算对指针变量执行算术运算访问数组的其他元素访问数组的其他元素n对指针可以进行哪些算术运算?对指针可以进行哪些算术运算?加上整数,减去整数,两个指针相减加上整数,减去整数,两个指针相减*p=5;59.4.1指针上的游走指针上的游走int*p,*q;int a10;a0a1 a2 a3 a4aa5a6 a7 a8 a9
21、n如果如果p指向指向ai则则p+j指向指向ai+j(前提是(前提是ai+j必须存在)必须存在)p+j不是加不是加j个字节,而是取决于个字节,而是取决于p的基类型的基类型p=&ai;q=p+j;pq9.4.1指针上的游走指针上的游走int*p;int a10;a0a1 a2 a3 a4aa5a6 a7 a8 a9n指针的算术运算允许通过对指针变量指针的算术运算允许通过对指针变量重复自增重复自增来访问数组的元素来访问数组的元素for(p=a;p=q 为真(为真(q=p 为假)为假)p=&ai;q=&aj;pq9.4.1指针上的游走指针上的游走n指针变量不同于其他类型变量的特殊性主要体现在如下几点:
22、指针变量不同于其他类型变量的特殊性主要体现在如下几点:n1)1)指针变量中保存的内容是地址值(例如变量的地址或函数的地址);指针变量中保存的内容是地址值(例如变量的地址或函数的地址);n2)2)指针变量必须初始化后才能使用,否则指向不确定的存储单元,这个不指针变量必须初始化后才能使用,否则指向不确定的存储单元,这个不确定的存储单元有可能是只读的,对其进行写操作就会出现非法内存访问确定的存储单元有可能是只读的,对其进行写操作就会出现非法内存访问的错误;的错误;n3)3)指针变量只能指向同一基类型的变量或数组。指针变量只能指向同一基类型的变量或数组。n4)4)指针变量可参与的运算是有限的,仅包括:
23、加、减整数,自增、自减、指针变量可参与的运算是有限的,仅包括:加、减整数,自增、自减、关系运算和赋值运算。关系运算和赋值运算。n5)5)只有作用于数组时,指针算术运算才是有意义的。只有作用于数组时,指针算术运算才是有意义的。n6)6)指针算术运算的结果依赖于指针所指向对象的字节长度,并且依赖于具指针算术运算的结果依赖于指针所指向对象的字节长度,并且依赖于具体机器和编译器。体机器和编译器。9.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘n数组名代表数组的首地址数组名代表数组的首地址&a0n&ai (a+i)na+1不是加上不是加上1个个字节,取决于字节,取决于a的基类型的基类型na+1
24、 a+sizeof(基类型基类型)na+i a+i*sizeof(基类型基类型)n一维数组元素的等价引用形式一维数组元素的等价引用形式 ai *(a+i)n用下标形式访问数组元素的本质用下标形式访问数组元素的本质n计算该元素在内存中的地址计算该元素在内存中的地址int a5;9.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘n为什么一个为什么一个int型指针能指向一个型指针能指向一个int型的一维数组呢?型的一维数组呢?int*p=a;int*p=&a0;&a0是是int型元素的地址型元素的地址p是是int型指针,基类型是型指针,基类型是intp的基类型的基类型与它指向的元素类型相同与
25、它指向的元素类型相同ai *(a+i)&ai (a+i)int a5;9.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘ai *(a+i)&ai (a+i)#include int main()int a5,i;for(i=0;i5;i+)scanf(%d,&ai);for(i=0;i5;i+)printf(%4d,ai);printf(n);return 0;#include int main()int a5,i;for(i=0;i5;i+)scanf(%d,a+i);for(i=0;i5;i+)printf(%4d,*(a+i);printf(n);return 0;9.4.2指针
26、和一维数组的前世之缘指针和一维数组的前世之缘ai *(a+i)int*p=a;int a5;pi *(p+i)#include int main()int a5,i,*p=NULL;p=a;for(i=0;i5;i+)scanf(%d,&pi);p=a;for(i=0;i5;i+)printf(%4d,pi);printf(n);return 0;9.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘p+不是增加不是增加1字节,取决于字节,取决于p的基类型的基类型int*p=a;int a5;#include int main()int a5,*p=NULL;for(p=a;pa+5;p+
27、)scanf(%d,p);for(p=a;pa+5;p+)printf(%4d,*p);printf(n);return 0;9.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘p+不是增加不是增加1字节,取决于字节,取决于p的基类型的基类型int*p=a;int a5;*p+与与(*p)+有何区别?有何区别?printf(%dn,+(*p);printf(%dn,(*p)+);(*p)+,对对*p加加1 1,不改变,不改变p的指向的指向printf(%dn,*p+);printf(%dn,*(p+);printf(%dn,*p);p+;当指针指向数组时,当指针指向数组时,p+才有意义才
28、有意义556569.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘被调函数的形参声明为被调函数的形参声明为数组类型数组类型,用用下标法下标法访问数组元素访问数组元素void InputArray(int a,int n)int i;for(i=0;in;i+)scanf(%d,&ai);void OutputArray(int a,int n)int i;for(i=0;in;i+)printf(%4d,ai);printf(n);n【例例9.59.5】编程分别用数组和指针变量作函数参数,先输入编程分别用数组和指针变量作函数参数,先输入1010个个整型数,然后输出这整型数,然后输出这1
29、010个整型数。个整型数。#include void InputArray(int a,int n);void OutputArray(int a,int n);int main(void)int a10;printf(Input ten numbers:);InputArray(a,10);OutputArray(a,10);return 0;9.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘被调函数的形参声明为被调函数的形参声明为数组类型数组类型,用用指针指针算术运算算术运算访问数组元素访问数组元素void InputArray(int a,int n)int i;for(i=0;
30、in;i+)scanf(%d,a+i);void OutputArray(int a,int n)int i;for(i=0;in;i+)printf(%4d,*(a+i);printf(n);n【例例9.59.5】编程分别用数组和指针变量作函数参数,先输入编程分别用数组和指针变量作函数参数,先输入1010个个整型数,然后输出这整型数,然后输出这1010个整型数。个整型数。#include void InputArray(int a,int n);void OutputArray(int a,int n);int main(void)int a10;printf(Input ten numbe
31、rs:);InputArray(a,10);OutputArray(a,10);return 0;9.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘n【例例9.59.5】编程分别用数组和指针变量作函数参数,先输入编程分别用数组和指针变量作函数参数,先输入1010个个整型数,然后输出这整型数,然后输出这1010个整型数。个整型数。#include void InputArray(int a,int n);void OutputArray(int a,int n);int main(void)int a10;printf(Input ten numbers:);InputArray(a,1
32、0);OutputArray(a,10);return 0;void InputArray(int*p,int n)int i;for(i=0;in;i+)scanf(%d,p+);void OutputArray(int*p,int n)int i;for(i=0;in;i+)printf(%4d,*p+);printf(n);被调函数的形参声明为被调函数的形参声明为指针指针类型类型,用用指针指针算术运算算术运算访问数组元素访问数组元素9.4.2指针和一维数组的前世之缘指针和一维数组的前世之缘n【例例9.59.5】编程分别用数组和指针变量作函数参数,先输入编程分别用数组和指针变量作函数参数,
33、先输入1010个个整型数,然后输出这整型数,然后输出这1010个整型数。个整型数。#include void InputArray(int a,int n);void OutputArray(int a,int n);int main(void)int a10;printf(Input ten numbers:);InputArray(a,10);OutputArray(a,10);return 0;void InputArray(int*p,int n)int i;for(i=0;in;i+)scanf(%d,&pi);void OutputArray(int*p,int n)int i;f
34、or(i=0;idemo demo outfile.txt从终端(显示器)输出数据改成向文件中写数据从终端(显示器)输出数据改成向文件中写数据【例9.7】下面程序用于演示函数freopen()的使用,把标准输出流stdout重定向到文件test.txt,即将程序中原有的向屏幕输出改为向文件test.txt输出。9.5 文件指针和数据的格式化文件读写文件指针和数据的格式化文件读写#include#include int main(void)if(freopen(test.txt,w,stdout)=NULL)printf(Failure to open test.txt!n);exit(0);f
35、or(int i=0;i10;i+)printf(%dt,i+1);fclose(stdout);return 0;函数freopen()通过实现标准I/O重定向功能来访问文件,而fopen函数则通过文件I/O来访问文件【例9.8】下面程序演示如何利用函数freopen()将把标准输入流stdin重定向到文件input.txt,把标准输出流stdout重定向到文件output.txt。9.5 文件指针和数据的格式化文件读写文件指针和数据的格式化文件读写#include#include int main(void)int a10;if(freopen(input.txt,r,stdin)=NUL
36、L)printf(Failure to open input.txt!n);exit(0);if(freopen(output.txt,w,stdout)=NULL)printf(Failure to open output.txt!n);exit(0);for(int i=0;i10;i+)scanf(%d,&ai);printf(%dt,ai);fclose(stdin);fclose(stdout);return 0;n格式化读写格式化读写int fscanf(FILE*fp,const char*format,.);fscanf(fp,%d,%6.2f,&i,&t);参数参数1:文件指
37、针:文件指针参数参数2:格式控制参数:格式控制参数参数参数3:地址参数表列:地址参数表列int fprintf(FILE*fp,const char*format,.);fprintf(fp,%d,%6.2f,i,t);参数参数1:文件指针:文件指针参数参数2:格式控制参数:格式控制参数参数参数3:输出参数表列:输出参数表列9.5 文件指针和数据的格式化文件读写文件指针和数据的格式化文件读写9.5 文件指针和数据的格式化文件读写文件指针和数据的格式化文件读写n在使用指针时,需要注意以下安全编码规范:在使用指针时,需要注意以下安全编码规范:(1)使用指针前一定要对指针进行初始化,让指针指向确定的
38、存储单元,)使用指针前一定要对指针进行初始化,让指针指向确定的存储单元,不要使用未初始化的指针不要使用未初始化的指针(2)在使用指针前检查指针是否为空指针)在使用指针前检查指针是否为空指针NULL(3)不要对指向非数组对象的指针加上或减去整数)不要对指向非数组对象的指针加上或减去整数(4)不要对不引用同一数组的两个指针执行减法或比较运算)不要对不引用同一数组的两个指针执行减法或比较运算(5)避免整型数据与指针类型数据之间的互相转化)避免整型数据与指针类型数据之间的互相转化9.6 安全编码规范安全编码规范n在使用文件时,需要注意以下安全编码规范:在使用文件时,需要注意以下安全编码规范:(1)使用系统)使用系统I/O函数创建文件时必须显式指定合适的文件访问权限函数创建文件时必须显式指定合适的文件访问权限(2)使用文件路径前必须进行规范化并校验)使用文件路径前必须进行规范化并校验(3)在库函数调用后,要检查其是否返回无效指针)在库函数调用后,要检查其是否返回无效指针(4)对于不再使用的文件,立即关闭它们)对于不再使用的文件,立即关闭它们(5)使用安全的函数调用)使用安全的函数调用(6)关注文件的可移植性)关注文件的可移植性(7)使用)使用C11提供的互斥写模式提供的互斥写模式9.6 安全编码规范安全编码规范本章知识树本章知识树Q&A