- 1. 第十章 指针 指针是C语言中的一个概念,正确而灵活地运用指针,可以有效地表示复杂的数据结构、难动态分配内存、方便使用字符串和数组、能使函数返回一个以上的结果、能直接使用内存地址等。
- 2. 本章内容包括:
10.1 地址和指针的概念
10.2 变量的指针和指向变量的指针变量
10.3 数组与指针10.4 字符串与指针
10.5 指向函数的指针
10.6 返回指针值的函数10.7 指针数组与指向指针的指针
- 3. 10.1地址和指针的概念一、指针概述:
1、地址的概念与取地址运算:
内存以字节编码,每个编码都是一个地址。我们原先学过的变量、数组、函数等都放在内存中,在程序中,我们是通过变量名等使用变量、给变量赋值等,但实际运行时,系统使用的是内存地址,而不是变量名。我们怎样知道机器将某种数据放在内存的什么地方呢?可用求地址运算符&
如:int a = 3 ; &a 就是变量在内存中的地址。
可以用printf(“%x \n” , &a); 看出其地址。注意,这个地址并不是始终不变的,这是由机器和操作系统来安排的,我们无法预先知道。
- 4. 在数组中,数组名代表数组的首地址
故a表示的地址和&a [0]的地址相同。 但&不能施加在常数、常量或表达式上,也不能施加在寄存器变量上(因为寄存器变量在cpu中,不在内存中)。
二、指针变量: 既然存储在内存中的各种变量都有一个地址,我们能否这样设想:定义某种变量,让这个变量的值等于某个变量的地址,如同某个房间号、门牌号一样?回答是肯定的。我们把这种存放某种变量地址的变量称为指针变量。 1035….ab20102012p2010q2012因此,在C语言中,将地址形象化地称为指针
- 5. 说明(系统对变量的访问形式分为两种)
一个变量的访问(访问是指取出其值或向它赋值)方式有两种:
(1)直接访问,通过变量名访问,如通过变量名i直接访问。
(2)间接访问,通过该变量的指针来访问,如通过指针p访问变量i。
- 6. 1.内存地址──内存中存储单元的编号
(1)计算机硬件系统的内存储器中,拥有大量的存储单元(容量为1字节)。
为了方便管理,必须为每一个存储单元编号,这个编号就是存储单元的"地址"。每个存储单元都有一个惟一的地址。
(2)在地址所标识的存储单元中存放数据。
注意:内存单元的地址与内存单元中的数据是两个完全不同的概念。
2.指针──即地址
一个变量的地址称为该变量的指针。通过变量的指针能够找到该变量
2.变量地址──系统分配给变量的内存单元的起始地址。
假设有这样一个程序:
- 7. main()
{ int i;
scanf("%d",&i);
printf("i=%d\n", i);
}
C编译程序编译到该变量定义语句时,将变量i 登录到"符号表"中。符号表的关键属性有两个:一是"标识符名(id)" ,二是该标识符在内存空间中的"地址(addr)" 。
为描述方便,假设系统分配给变量i的2字节存储单元为 2000 和2001,则起始地址2000就是变量i在内存中的地址。
3.变量值的存取──通过变量在内存中的地址进行
系统执行"scanf("%d",&i);"和"printf("i=%d\n", i);"时,存取变量i值的方式可以有两种:
- 8. (1)直接访问──直接利用变量的地址进行存取
1)上例中scanf("%d",&i)的执行过程是这样的:
用变量名i作为索引值,检索符号表,找到变量i的起始地址2000;然后将键盘输入的值(假设为3)送到内存单元2000和2001中。此时,变量i在内存中的地址和值,如图10-1所示。
2)printf("i=%d\n",i)的执行过程,与scanf()很相似:
首先找到变量i的起始地址2000,然后从2000和2001中取出其值,最后将它输出。
(2)间接访问──通过另一变量访问该变量的值
C语言规定:在程序中可以定义一种特殊的变量(称为指针变量),用来存放其它变量的地址。
- 9. 例如,假设定义了这样一个指针变量i_pointer,它被分配到3010、3011单元,其值可通过赋值语句"i_pointer=&i;"得到。此时,指针变量i_pointer的值就是变量i在内存中的起始地址2000,如图10-1所示。
通过指针变量i_pointer存取变量i值的过程如下:
首先找到指针变量i_pointer的地址(3010),取出其值2000(正好是变量i 的起始地址); 然后从2000、2001中取出变量i的值(3)。
(3)两种访问方式的比较
两种访问方式之间的关系,可以用某人甲(系统)要找某人乙(变量)来类比。
一种情况是,甲知道乙在何处,直接去找就是(即直接访问)。
另一种情况是,甲不知道乙在哪,但丙(指针变量)知道,此时甲可以这么做:先找丙,从丙处获得乙的去向,然后再找乙(即间接访问)。
- 10. 10.2 变量的指针和指向变量的指针变量(1)指针──即地址
一个变量的地址称为该变量的指针。通过变量的指针能够找到该变量。
(2)指针变量──专门用于存储其它变量地址的变量
指针变量i_pointer的值就是变量i的地址。指针与指针变量的区别,就是变量值与变量的区别。
(3)为表示指针变量和它指向的变量之间的关系,用指针运算符"*"表示。
例如,指针变量i_pointer与它所指向的变量i的关系,表示为:
*i_pointer,即*i_pointer等价于变量i。
因此,下面两个语句的作用相同:
i=3; /*将3直接赋给变量i*/
i_pointer=&i; /*使i_pointer指向i */
*i_pointer=3; /*将3赋给指针变量i_pointer所指向的变量*/
- 11. 10.2.1定义一个指针变量 指针变量的定义一般形式为:
基类型 *指针变量名;例如:
int i, j, *pi, *pj;
float x, y, *p1, *p2;
指针变量的赋值:使得指针变量指向变量
指针变量名=&变量名;
如: pi=&i; pj=&j; p1=&y; p2=&x;
注意:指针变量只能存放指针(地址),且只能是相同类型变量的地址。
例如,指针变量pi、pj,只能接收int型、p1, p2只能接收float型的地址,否则出错。
- 12. 10.2.2 指针变量的引用 在程序中,可以用:*指针变量名 代替其所指变量。如若
int i, *p;
p=&i;
则 i=5; 与*p=5; 的作用相同,即可用*p代替i,这里*号称为指针运算符(或称为间接访问运算符)例10.1 通过指针变量访问整型变量
main()
int a, b, point_1, point_2;
a=100; b=10;
point_1=&a; point_2=&b;
printf("\n%d,%d\n",a,b);
printf("%d,%d",*point_1,*point_2);}
- 13. 例10.2 输入a和b两个整数,按先大后小的顺序输出a和b的值
main()
{int a, b, *p, *p1, *p2;
p1=&a;p2*=&b;
scanf("%d%d",p1,p2);
if(a
- 14. 10.2.3 指针变量作为函数参数实参:变量地址或已赋值的指针变量,形参:指针变量
功能:地址传送方式,会将改变后的值带回。例10.3 通过函数调用实现例10.2的功能。
swap(int *p1, int *p2)
{int temp;
temp=*p1; *p1=*p2; *p2=temp;
}
main()
{int a, b, *pointer1,*pointer2;
scanf("%d%d",&a,&b);
pointer1=&a; pointer2=&b;
if(a
- 15. 例10.4 输入3个整数,按降序(从大到小的顺序)输出。要求使用变量的指针作函数调用的实参来实现。
void exchange(int *pointer1, int *pointer2)
{ int temp;
temp=*pointer1, *pointer1=*pointer2, *pointer2=temp;
}
main()
{ int a,b,c;
printf("Input the first number: "); scanf("%d", &a);
printf("Input the second number: "); scanf("%d", &b);
printf("Input the third number: "); scanf("%d", &c);
printf("a=%d, b=%d, c=%d\n", a, b, c);
/*排序*/
if( a < b ) /*a
- 16. 10.3 数组与指针10.3.1指向数组元素的指针
1.概念
数组的指针──数组在内存中的起始地址,用数组名表示
2.指向数组的指针变量---赋于数组名的指针变量
例如,int a [10], *p=a (或*p=&a[0]);或者: int a [10], *p;
p=a;
- 17. 10.3.2 通过指针引用数组元素
如果有“int a [10],*p=a;” ,则:
(1)p+i==a+i==&a[i]。
(2)*(p+i)==*(a+i)==a[i]。
(3)p[i]==*(p+i)==a[i]。
注意:p+1指向数组的下一个元素,而不是简单地使指针变量p的值+1。其实际变化为p+1*size(size为一个元素占用的字节数)。
例如,假设指针变量p的当前值为3000,则p+1为3000+1*2=3002,而不是3001
- 18. 例10.5 输出数组的全部元素#include
void main()
{int a[10],i,*p;
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(i=0;i<10;i++) printf("%5d",a[i]);//下标法
printf("\n")
for(i=0;i<10;i++) printf("%5d",*(a+i));
//通过数组名计算数组元素地址,找出元素的值
printf("\n");
for(p=a;p
- 19. 说明:
(1)指针变量的值是可以改变的,所以必须注意其当前值,否则容易出错。
(2)指向数组的指针变量,可以指向数组以后的内存单元,虽然没有实际意义。
(3)对指向数组的指针变量(px和py)进行算术运算和关系运算的含义如下:
1)可以进行的算术运算,只有以下几种:
px±n:将指针从当前位置向前(+n)或回退(-n)n个数据单位,而不是n个字节。
px-py:两指针之间的数据个数,而不是指针的地址之差。
- 20. 2)关系运算
表示两个指针所指地址之间、位置的前后关系:前者为小,后者为大。
例如,如果指针px所指地址在指针py所指地址之前,则px
- 21. 例:通过指针变量输出数组的10个元素
#include
void main(){
int *p,i,a[10];
p=a;
for(i=0;i<10;i++)
scanf("%d",p++);
printf("\n");
for(i=0;i<10;i++)
printf("%5d",a[i]);
printf("\n");
for(i=0;i<10;i++,p++)
printf("\t%d",*p);
printf("\n");
}
- 22. 10.3.3 用数组名作为函数参数
形参:数组或指针变量
实参:数组名或指向数组的指针变量
传递方式:地址传送方式
作用:若函数中对数组作了修改,则调用函数中的数组也会作同样的修改
- 23. 例10.7 将数组a中的n个整数按相反次序存放#include
void main()
{int i, a[10]={0,1,2,3,4,5,6,7,8,9},*p;
void inv(int x[],int n);
for(i=0;i<10;i++) printf("%5d",a[i]);
printf("\n");p=a;
inv(a,10);inv(p,10);
for(i=0;i<10;i++) printf("%5d",a[i]);
}void inv(int *x, int n)
{int temp,*i,*j,*p,m=(n-1)/2;
i=x;j=x+n-1;p=x+m;
for(;i<=p;i++,j--)
{temp=*i; *i=*j; *j=temp;}
}
void inv(int x[], int n)
{int temp,i,j,m=(n-1)/2;
for(i=0;i<=m;i++)
{j=n-i-1;
temp=x[i]; x[i]=x[j]; x[j]=temp;
}}
- 24. 10.3.4 多维数组与指针
1. 多维数组元素的地址 假设有如下数组定义语句: int a [3][4];
(1)从2维数组角度看,数组名a代表数组的起始地址, 是一个以行为单位进行控制的行指针: a+i:行指针值,指向2维数组的第i行。 *(a+i):(列)指针值(&a[i][0]),指向第i行第0列(控制由行转为列,但仍为指针)。 *(*(a+i)):数组元素a [i][0]的值。 用a作指针访问数组元素a [i][j]的格式:
*(*(a+i)+j)
(2)从1维数组角度看,数组名a和第1维下标的每一个值, 共同构成一组新的1维数组名a [0]、a [1]、a [2],它们均由4个元素组成。
- 25. C语言规定:数组名代表数组的地址,所以a [i]是第i行1维数组的地址, 它指向该行的第0列元素,是一个以数组元素为单位进行控制的列指针:
a[i]+j:(列)指针值,指向数组元素a [i][j]。
*(a[i]+j):数组元素a [i][j]的值。
如果有“int a[3][4],*p=a [0];”,则p+1指向下一个元素,
用p作指针访问数组元素a [i][j]的格式:
*(p+(i*每行列数+j) )a+i 即 a[i]a[i]+j 即 &a[i][j]*(a+i)+j即 &a[i][0]+j即&a[i][j]*(*(a+i)+j)即a[i][j]
- 26. 2,指向多维数组元素的指针变量(1)指向数组元素的指针变量
例10.11 用指针变量输出数组元素的值
#include
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
- 27. 例10.12输出二维数组的任一行任一列的值
#include
void main()
{int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
int (*p)[4],i,j;//定义一个指向含有4个元素的整型数组
p=a;
scanf("i=%d,j=%d",&i,&j);
printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));
}
- 28. 3.指向数组的指针作为函数参数例10.13 有一个班,3个学生,各学4门课程,计算总1平均分数及输出第n个学生的成绩。
算法设计:
主函数:1,函数声明
2,初始化成绩
3,调用求平均成绩的函数
4,调用函数输出第i个学生的成绩
求平均值函数:
1,求总成绩
2,返回平均成绩
输出函数
使用for循环,直接输出成绩
- 29. #include
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);
search(score,2);
}
void average(float *p,int n){
float *p_end;
float sum=0,aver;
p_end=p+n-1;
- 30. for(;p<=p_end;p++)
sum=sum+(*p);
aver=sum/n;
printf("average=%5.2f",aver);
}
void search(float (*p)[4], int n){
int i;
printf("The score of NO:%d\n",n);
for(i=0;i<4;i++)
printf("%5.2f",*(*(p+n)+i));
}
- 31. 10.4 字符串与指针10.4.1 字符串的表示形式
1,用数组存放一个字符串
例10.15 定义一个字符数组,对它进行初始化,然后输出该字符串。
#include
void main()
{char string[]="I love China");
printf("%s",string);
}
- 32. 2, 用字符指针指向一个字符串。
例10.16
#include
void main()
{char *string="I love China";
printf("%s",string);
}
- 33. 10.4.2 字符串指针作为函数参数
例10.20 用函数调用实现字符串的复制
#include
void copy_string (char *from, char *to)
{for(; (*to=*from)!='\0'; from++, to++) ; }
void main()
{void copy_string (char *from, char *to);
char array_str1[20]="I am a teacher.";
char array_str2[20];
copy_string(array_str1, array_str2); /*数组名作实参*/
printf("array_str2=%s\n", array_str2);
}
- 34. 程序说明: for(; (*to=*from)!='\0'; from++, to++) ;
语句的执行过程为:首先将源串中的当前字符,复制到
目标串中;然后判断该字符(即赋值表达式的值)是否是结
束标志。如果不是,则相对位置变量i的值增1,以便复制下
一个字符;如果是结束标志,则结束循环。其特点是:先复
制、后判断,循环结束前,结束标志已经复制。
在C语言中,用赋值运算符、而不是赋值语句来实现赋
值操作,能给某些处理带来很大的灵活性,该语句(实现字
符串的复制)的用法就是最好的例证。
- 35. 10.5 指向函数的指针1.函数指针的概念 一个函数在编译时,被分配了一个入口地址,这个地址就称为该函数的指针。
可以用一个指针变量指向一个函数,然后通过该指针变量调用此函数。
- 36. 2.指向函数的指针变量 (1)定义格式
函数类型 (*指针变量)( ); 注意:“*指针变量”外的括号不能缺,否则成了返回指针值的函数。 例如,int (*fp)(); /* fp为指向int函数的指针变量*/
- 37. (2)赋值 函数名代表该函数的入口地址。因此,可用函数名给指向函数的指针变量赋值。 指向函数的指针变量=[&]函数名; 注意:函数名后不能带括号和参数;函数名前的“&”符号是可选的。
- 38. (3)调用格式
(*函数指针变量)([实参表])
3.指向函数的指针变量作函数参数
指向函数的指针变量的常用用途之一,就是将函数指针作参数,
传递到其它函数。
注意:对指向函数的指针变量,诸如p+i、p++/p--等运算是没有意义的。
例10.24 设有一个函数process,在调用它时,每次实现不同的
功能。输入a和b两个数,第一次调用时找出a与b中的大者,第
二次找出小者,第三次求和。
- 39. #include
void main()
{int max(int,int);
int min(int,int);
int add(int,int);
int process(int, int, int (*fun)());
int a,b;
printf("enter a and b:");
scanf("%d%d",&a,&b);
printf("\nmax=");
process(a,b,max);
printf("\nmin=");
process(a,b,min);
printf("\nsum=");
process(a,b,add);
}
- 40. max(int x, int y)
{return x>y?x:y;}
min(int x, int y)
{return x>y?y:x;}
add(int x, int y)
{return x+y;}
process(int x, int y, int (*fun)(int,int)) /*fun是一个指向函数的指针,该函数是一个有两个整型参数的返回整型值的函数*/
{int result;
result=(*fun)(x,y);
printf("%d\n",result);
}
- 41. 作业10.4,10.5
要求相应的写出算法和程序
- 42. 10.6 返回指针值的函数 一个函数可以返回一个int型、float型、char型的数据,也可以返回一个指针类型的数据。 返回指针值的函数(简称指针函数)的定义格式如下:
函数类型 *函数名(形参表列)
例如:
int *a(int x,int y);
该函数的函数名为a,返回的是一个整型的指针。
- 43. 例10.24 有若干个学生的成绩(每个学生有4门课程)要求在输入学生序号后,能输出该学生的全部成绩
#include
void main(){
float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
float *search(float (*point)[4],int n);
float *p;
int i,m;
printf("请输入学生的序号:");
scanf("%d",&m);
printf("序号为%d的学生的成绩:\n",m);
p=search(score,m);
for(i=0;i<4;i++)
printf("%5.2f\t",*(p+i));
printf("\n");
}
- 44.
float *search(float (*pointer)[4],int n)
{
float *pt;
pt=*(pointer+n);
return(pt);
}
- 45. 例10.25 对上例中的学生,找出其中不及格课程的学生及其学生号.
#include
void main(){
float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
float *search(float (*point)[4]);
float *p;
int i,j;
for(i=0;i<3;i++){
p=search(score+i);
if(p==*(score+i)){
printf("NO.%d score:",i);
for(j=0;j<4;j++)
printf("%5.2f\t",*(p+j));
printf("\n");}
}
}
- 46.
float *search(float (*pointer)[4]){
int i;
float *pt;
pt=*(pointer+1);
for(i=0;i<4;i++)
if(*(*pointer+i)<60) pt=*pointer;
return(pt);
}
- 47. 10.7 指针数组和指向指针的指针10.7.1 指针数组的概念 数组的每个元素都是一个指针数据。指针数组比较适合用于指向多个字符串,使字符串处理更加方便、灵活。
定义格式
数据类型 *数组名[元素个数]
例如: int *p[4]; char *string[10];
例10.26 将若干字符串按字母的顺序(由小到大)输出
主函数中进行初始化工作,然后调用排序函数和输出函数进行排序和输出。
- 48. #include
#include
void main()
{void sort(char *name[ ],int n);
void print(char *name[],int n);
int n=5;
char *name[]={"Follow me","BASIC","Great Wall",
"FORTRAN", "Computer design"};
sort(name,5);
print(name,5);
}
- 49. void sort(char *name[],int n)
{char *temp;
int i,j,k;
for(i=0;i0) k=j;
if(k!=i)
{temp=name[i];name[i]=name[k];name[k]=temp;}
}
}
void print(char *name[],int n)
{int i=0;
for(i=0;i
- 50. 10.7.2 指向指针的指针
定义格式:类型名 **数组名;
功能:定义二级指针。
例:int **p;
例10.27 使用指向指针的指针
#include
void main(){
char *name[]={"Follow me","Greate Wall",FORTRAN",
"Computer design"};
char **p;
int i;
for(i=0;i<5;i++){
p=name+i;
printf("%s"\n",*p);}
}
- 51. 10.7.3 指针数组作main函数的形参 在以往的程序中,主函数main()都使用其无参形式。实际上,主函数main()也是可以指定形参的。其格式为:
void main(int argc,char *argv[])
形参说明
(1)形参argc是命令行中参数的个数(可执行文件名本身也
算一个)。
在本例中,形参argc的值为3(lock、+|-、文件名)。
(2)形参argv是一个字符指针数组,即形参argv首先是一个数
组(元素个数为形参argc的值),其元素值都是指向实参字符
串的指针,即用于保存命令行中的字符串。
- 52. 例:一个主函数包含有参数的文件
void main(int argc,char *argv[]){
while(argc>1)
{++argv;
printf("%s\n",*argv);
--argv;
}
}
- 53. 空指针在指针的定义中,可以将指针的类型定义为void,此时该指针的类型在赋值时确定。如:
void *p1,*p2;
int i;
char s[20];
p1=&i; //p1为整型
p2=s; //p2为字符型。
- 54. 作业 10.15,10.21要求自然语言写出算法