第 7章 地址和指针
7.1 地址和指针的概念
7.2 指针变量的定义和指针变量的基类型
7.3 给指针变量赋值
7.4 对指针变量的操作
7.5 指针在函数方面的应用
7.1 地址和指针的概念
7.1.1 地址和指针的概念
1.,地址,的概念
2,存储单元,地址,的表示
3.,指针,的概念
7.1.2 为什么使用指针使用指针,实现了,间接访问,变量,
这只是一个过程,其实质是可以达到方便地引用数组,提高计算机的运行效率,调用函数时可以修改或返回多个参数值,还可以描述更复杂的数据结构,…… 为了加深读者对指针重要性的理解,下面我们举一函数调用的实例 。
例 7.1 欲想调用 fun函数,计算出两个数相加和相减的值,然后在 main函数中显示结果。
程序如下:
fun ( int x,int y )
{ int add=0,sub=0 ;
add=x+y ;
sub=x–y ;
}
main ( )
{ int a,b,add=0,sub=0 ;
scanf ( " %d %d ",&a,&b ) ;
printf ( " a=%d,b=%d \n ",a,b ) ;
fun ( a,b ) ;
printf ( " %d + %d =%d \n ",a,b,add ) ;
printf ( " %d – %d =%d \n ",a,b,sub ) ;
}
假设程序运行时给变量 a输入 8,给变量 b输入 5,程序运行结果:
a=8,b=5
8 + 5=0
8 – 5=0
程序运行结果与我们“想象”的不一致,为什么呢?其原因是 fun 函数中变量
add和 sub与 main函数中变量 add和 sub各自占用自己的存储单元,因此 fun函数中变量
add存放的两个数的求和值无法传回到
main函数的变量 add中,同理,fun函数中变量 sub存放的两个数的相减值也无法传回到 main函数的变量中。下面我们修改一下源程序,借助指针,将 fun函数中运行的结果传回到 main函数中。
7.2 指针变量的定义和指针变量的基类型
7.2.1 指针变量的定义和指针变量的基类型定义指针变量的一般形式如下:
类型名 *指针变量名 1,*指针变量名
2,…… ;
例如,int *pi,*pj ;
以上定义语句中,pi和 pj都是用户标识符,表示具体的变量名,现在每个变量名前加一星号,*”说明符,用来说明所定义的变量是指针变量。
注意:变量名前的星号,*”不可省略,
若省略了星号,*”说明符,就变成了普通整型变量。
int是数据类型名,在这里,说明了 pi
和 pj是两个指向整型( int类型)变量的指针,也就是说变量 pi和 pj中只能存放 int类型变量的地址,这时我们称 int是指针变量
pi和 pj的基类型。又如:
double *pd ;
char *s1,*s2 ;
在这里定义了三个指针变量 pd,s1和
s2,其中指针变量 pd的基类型为 double类型,在指针变量 pd中,只能存放 double类型变量的地址,指针变量 s1和 s2的基类型为 char类型,在指针变量 s1和 s2中只能存放 char类型变量的地址。又如:
int **p ;
以上是定义了一个指向指针的指针变量 p,该指针变量 p只能存放基类型为 int类型的指针变量的地址。又如:
int *pi,**p,k ;
以上语句是在同一语句中,同时定义了指针变量 pi,指向指针的指针变量 p和变量 k,这是允许的。
7.2.2 指针变量的基类型的作用任何一个指针变量都是用于存放它所指向变量的地址,只要能存放地址就可以了,为何还要区别不同的基类型呢?
其原理是:不同的数据类型变量,C
语言系统为它们开辟的存储空间的字节数是不同的,
int类型的数据存储空间是 2个字节,
float类型的数据存储空间是 4个字节,……,
系统表示每一个存储空间的地址时,是取该存储空间的第 1个字节的地址作为该变量存储空间的地址。那么当一个基类型为 int
类型的指针变量 p指向了一个 int类型的变量 a时,是将该变量 a所占的 2个字节的存储空间中的第 1个字节存储空间的“地址”存入指针变量 p中,如图 7-2( b) 所示。
图 7-2 指针和指针所指向的对象所以根据指针变量 p中存放的“地址”,
只能寻找到变量 a第 1个字节的存储空间,
如果只提取变量 a所占存储空间第 1个字节的数据,显而易见不是 int类型变量 a的原值,因为变量 a的原值是通过 2个字节来存储的数据。此时我们可以通过指针变量 p的基类型解决问题,知道了变量 a的第 1个字节的地址,再根据指针变量 p的基类型为
int类型,系统就将从变量 a的第 1个字节所在的地址开始,连续提取 2个字节中的数据,
此时的数据就是 int类型变量 a的原值。
同理,基类型为 float类型的指针变量,根据指针变量中存放 float类型变量的地址值,可以寻找到所需存储空间中的第 1个字节所在位置,然后再根据基类型为 float类型,连续地提取 4个字节中的数据,作为被访问的数据,这才是 float类型变量中存放的真实数据。由此,我们可以看到指针变量在定义时,其基类型是何等的重要。因此,定义什么样的基类型指针变量,该指针变量只能存放什么样类型变量的地址,两者必须一致,
否则就可能出现了张冠李戴的错误现象。
7.3 给指针变量赋值
7.3.1 使指针指向一个对象
1,通过求地址运算符 ( &) 把一个变量的地址赋给指针变量
,&” 是求地址运算符,该运算符为单目运算符,用于求变量的地址,且该变量必须为内存变量 。
例如,int k=1,j =2,*q1,*q2,*p ;
float x=4.5 ;
q1=&k ;
q2=&j ;
以上第三条语句,是把变量 k的地址赋给了指针变量 q1,使指针变量 q1中存放了变量 k的地址,或称指针变量 q1指向了变量
k。 同理,以上第四条语句,是把变量 j的地址赋给了指针变量 q2,使指针变量 q2中存放了变量 j的地址,或称指针变量 q2指向了变量 j。
注意:在使用 & 运算符求变量的地址,
并赋给指针变量时,一定要确保所求地址的变量数据类型与存放该变量地址的指针变量基类型一致。
接上例,如执行 p=&x ; 语句,则是绝对错误的。为什么?请读者想一想!
读者还记得在前面调用 scanf 函数时,
其函数调用格式中,输入数据所对应的各变量名之前都必须加运算符 &,这就是我们所说的求地址运算符。 scanf 函数把从终端读入的数据依次放入这些地址所代表的存储单元中,也就是说 scanf 函数要求输入项是地址值。因此接上例,scanf ( " %d
%d ",&k,&j ) ; 语句和 scanf ( " %d %d
",q1,q2 ) ; 语句是等价的,都是将终端输入的整型数据存入到变量 k和变量 j所在的存储单元中。
2.同类型指针变量之间可以直接赋值可以把指针变量的值赋给指针变量,
但一定要确保这两个指针变量的基类型是相同的。
接上例,p=q1 ;
执行以上语句后,使指针变量 p也存放了变量 k的地址,也就是说指针变量 p和
q1同时指向了变量 k。
7.3.2 给指针变量赋,空,
值为了使指针变量有一确定的数值,除了给指针变量赋一地址值外,当指针变量没有指向的对象时,也可以给指针变量赋
NULL值,此值为空值 。 例如,int *p ;
p=NULL ; 表示指针变量 p的值为空 。
NULL是在 stdio.h头文件中定义的预定义符,因此在使用 NULL时,应该在程序的前面出现预定义命令行,# include "
stdio.h "。 NULL的代码值为 0,所以语句
p=NULL ;等价于,p=0 ;,都是表示指针变量 p是一个空指针,没有指向任何对象。
7.4 对指针变量的操作
7.4.1 通过指针或地址引用一个存储单元当指针变量中存放了一个确切的地址值时,通过指针可以用,间接运算符,( *)
来引用该地址所代表的存储单元 。
1.在赋值号右边由,*”运算符和指针变量组成的表达式,代表指针所指存储单元的内容例如,int *p,i=10,j ;
p=&i ;
j=*p ;
第二条语句是把变量 i的地址赋给了指针变量 p,第三条语句是把指针变量 p所指向的变量 i存储单元的值 10赋给变量 j。
“*”号在这里是一个“间接运算符”,
它为单目运算符,与运算对象自右至左结合,且运算对象必须为一个地址对象。
例如,j=*&i ;
该语句中,&”运算符求出变量 i的地址,,*”运算符取变量 i地址中的值 10赋给变量 j。
例如,int **pp,*p,k=20,j ;
p=&k ;
pp=&p ;
j=**pp ;
以上语句执行后的结果是:把变量 k的地址赋给了指针变量 p,把指针变量 p的地址赋给了指向指针的指针变量 pp,这种关系如图 7-4所示 。
最后一条语句是把变量 k中的值 20赋给了变量 j,
这条语句使用了两个,间接运算符,,**p表示获取指针变量 p中的值 ( 此值为变量 k的地址值 ),
**pp表示通过指针变量 p,再获取变量 k中的值 20。
图 7-4 变量 pp,p和 k之间的关系
2,在赋值号左边由,*” 和指针变量组成的表达式,代表指针所指的存储单元例如,int *p,k=0 ;
p=&k ;
*p=150 ;
以上第三条语句是把整数 150存入变量 k中 。
*p=*p +1 ; 或 *p+=1 ;
以上语句是获取指针变量 p所指向的存储单元 k中的值 150,然后加 1再放入指针变量 p所指向的存储单元 k中,此时变量 k中存放的数值为 151。
例如,int **p,*p,k ;
p=&k ;
pp=&p ;
**pp=200 ;
以上第四条语句是将整数 200存入变量
k中。
例 7.3 指针指向三个变量,通过指针运算选出数值最小的那个数并打印出来 。
main()
{ int a,b,c,min,*pa,*pb,*pc,
*pmin ;
pa=&a ; pb=&b ; pc=&c ;
pmin=&min ;
scanf ( " %d %d %d ",pa,pb,pc ) ;
/* 将键盘输入的三个整数依次放入变量 a,b,c中 */
printf ( " a=%d,b=%d,c=%d \n ",a,b,c ) ;
*pmin= *pa ; /* 假定变量 a中的数值最小,将其放入变量 min */
if (*pmin> *pb ) *pmin= *pb ;
/* 若 b中的数值比 min小,将其放入变量 min */
if (*pmin> *pc ) *pmin= *pc ;
/* 若 c中的数值比 min小,将其放入变量 min */
printf ( " min=%d \n ",min ) ;
/* 通过直接访问方式输出变量 min中的值 */
printf ( " min=%d \n ",*pmin ) ;
/* 通过间接访问方式输出变量 min中的值 */
}
当运行程序时输入 25 12 36↙
程序运行结果:
a=25,b=12,c=36
min=12
min=12
程序运行结果说明,当指针指向变量后,可以通过指针对所指向的存储单元进行数据的存取,直接访问方式和间接访问方式的结果是相同的。
7.4.2 指针 ( 变量 ) 的运算
1.指针的移动
2,指针的比较
7.5 指针在函数方面的应用
7.5.1 在被调用函数中直接改变主调用函数中的参数值在第 6章中,我们曾经介绍过,通过
return语句可以返回函数值,但这种方式只能返回一个数据,若想修改或传回多个数值,可以借用指针 。
7.5.2 使函数返回一个地址一个函数运行结束时,可以返回一个整型值,实型值,字符型值等,同样也可以返回指针型的数据,即返回地址值 。 这种返回指针型数据的函数,定义的形式为:
类型名 *函数名 ( 参数表 )
{ …… }
说明:函数名前的星号,*”,说明了此函数是一个指针型函数,函数的返回值是一个地址值 。
7.1 地址和指针的概念
7.2 指针变量的定义和指针变量的基类型
7.3 给指针变量赋值
7.4 对指针变量的操作
7.5 指针在函数方面的应用
7.1 地址和指针的概念
7.1.1 地址和指针的概念
1.,地址,的概念
2,存储单元,地址,的表示
3.,指针,的概念
7.1.2 为什么使用指针使用指针,实现了,间接访问,变量,
这只是一个过程,其实质是可以达到方便地引用数组,提高计算机的运行效率,调用函数时可以修改或返回多个参数值,还可以描述更复杂的数据结构,…… 为了加深读者对指针重要性的理解,下面我们举一函数调用的实例 。
例 7.1 欲想调用 fun函数,计算出两个数相加和相减的值,然后在 main函数中显示结果。
程序如下:
fun ( int x,int y )
{ int add=0,sub=0 ;
add=x+y ;
sub=x–y ;
}
main ( )
{ int a,b,add=0,sub=0 ;
scanf ( " %d %d ",&a,&b ) ;
printf ( " a=%d,b=%d \n ",a,b ) ;
fun ( a,b ) ;
printf ( " %d + %d =%d \n ",a,b,add ) ;
printf ( " %d – %d =%d \n ",a,b,sub ) ;
}
假设程序运行时给变量 a输入 8,给变量 b输入 5,程序运行结果:
a=8,b=5
8 + 5=0
8 – 5=0
程序运行结果与我们“想象”的不一致,为什么呢?其原因是 fun 函数中变量
add和 sub与 main函数中变量 add和 sub各自占用自己的存储单元,因此 fun函数中变量
add存放的两个数的求和值无法传回到
main函数的变量 add中,同理,fun函数中变量 sub存放的两个数的相减值也无法传回到 main函数的变量中。下面我们修改一下源程序,借助指针,将 fun函数中运行的结果传回到 main函数中。
7.2 指针变量的定义和指针变量的基类型
7.2.1 指针变量的定义和指针变量的基类型定义指针变量的一般形式如下:
类型名 *指针变量名 1,*指针变量名
2,…… ;
例如,int *pi,*pj ;
以上定义语句中,pi和 pj都是用户标识符,表示具体的变量名,现在每个变量名前加一星号,*”说明符,用来说明所定义的变量是指针变量。
注意:变量名前的星号,*”不可省略,
若省略了星号,*”说明符,就变成了普通整型变量。
int是数据类型名,在这里,说明了 pi
和 pj是两个指向整型( int类型)变量的指针,也就是说变量 pi和 pj中只能存放 int类型变量的地址,这时我们称 int是指针变量
pi和 pj的基类型。又如:
double *pd ;
char *s1,*s2 ;
在这里定义了三个指针变量 pd,s1和
s2,其中指针变量 pd的基类型为 double类型,在指针变量 pd中,只能存放 double类型变量的地址,指针变量 s1和 s2的基类型为 char类型,在指针变量 s1和 s2中只能存放 char类型变量的地址。又如:
int **p ;
以上是定义了一个指向指针的指针变量 p,该指针变量 p只能存放基类型为 int类型的指针变量的地址。又如:
int *pi,**p,k ;
以上语句是在同一语句中,同时定义了指针变量 pi,指向指针的指针变量 p和变量 k,这是允许的。
7.2.2 指针变量的基类型的作用任何一个指针变量都是用于存放它所指向变量的地址,只要能存放地址就可以了,为何还要区别不同的基类型呢?
其原理是:不同的数据类型变量,C
语言系统为它们开辟的存储空间的字节数是不同的,
int类型的数据存储空间是 2个字节,
float类型的数据存储空间是 4个字节,……,
系统表示每一个存储空间的地址时,是取该存储空间的第 1个字节的地址作为该变量存储空间的地址。那么当一个基类型为 int
类型的指针变量 p指向了一个 int类型的变量 a时,是将该变量 a所占的 2个字节的存储空间中的第 1个字节存储空间的“地址”存入指针变量 p中,如图 7-2( b) 所示。
图 7-2 指针和指针所指向的对象所以根据指针变量 p中存放的“地址”,
只能寻找到变量 a第 1个字节的存储空间,
如果只提取变量 a所占存储空间第 1个字节的数据,显而易见不是 int类型变量 a的原值,因为变量 a的原值是通过 2个字节来存储的数据。此时我们可以通过指针变量 p的基类型解决问题,知道了变量 a的第 1个字节的地址,再根据指针变量 p的基类型为
int类型,系统就将从变量 a的第 1个字节所在的地址开始,连续提取 2个字节中的数据,
此时的数据就是 int类型变量 a的原值。
同理,基类型为 float类型的指针变量,根据指针变量中存放 float类型变量的地址值,可以寻找到所需存储空间中的第 1个字节所在位置,然后再根据基类型为 float类型,连续地提取 4个字节中的数据,作为被访问的数据,这才是 float类型变量中存放的真实数据。由此,我们可以看到指针变量在定义时,其基类型是何等的重要。因此,定义什么样的基类型指针变量,该指针变量只能存放什么样类型变量的地址,两者必须一致,
否则就可能出现了张冠李戴的错误现象。
7.3 给指针变量赋值
7.3.1 使指针指向一个对象
1,通过求地址运算符 ( &) 把一个变量的地址赋给指针变量
,&” 是求地址运算符,该运算符为单目运算符,用于求变量的地址,且该变量必须为内存变量 。
例如,int k=1,j =2,*q1,*q2,*p ;
float x=4.5 ;
q1=&k ;
q2=&j ;
以上第三条语句,是把变量 k的地址赋给了指针变量 q1,使指针变量 q1中存放了变量 k的地址,或称指针变量 q1指向了变量
k。 同理,以上第四条语句,是把变量 j的地址赋给了指针变量 q2,使指针变量 q2中存放了变量 j的地址,或称指针变量 q2指向了变量 j。
注意:在使用 & 运算符求变量的地址,
并赋给指针变量时,一定要确保所求地址的变量数据类型与存放该变量地址的指针变量基类型一致。
接上例,如执行 p=&x ; 语句,则是绝对错误的。为什么?请读者想一想!
读者还记得在前面调用 scanf 函数时,
其函数调用格式中,输入数据所对应的各变量名之前都必须加运算符 &,这就是我们所说的求地址运算符。 scanf 函数把从终端读入的数据依次放入这些地址所代表的存储单元中,也就是说 scanf 函数要求输入项是地址值。因此接上例,scanf ( " %d
%d ",&k,&j ) ; 语句和 scanf ( " %d %d
",q1,q2 ) ; 语句是等价的,都是将终端输入的整型数据存入到变量 k和变量 j所在的存储单元中。
2.同类型指针变量之间可以直接赋值可以把指针变量的值赋给指针变量,
但一定要确保这两个指针变量的基类型是相同的。
接上例,p=q1 ;
执行以上语句后,使指针变量 p也存放了变量 k的地址,也就是说指针变量 p和
q1同时指向了变量 k。
7.3.2 给指针变量赋,空,
值为了使指针变量有一确定的数值,除了给指针变量赋一地址值外,当指针变量没有指向的对象时,也可以给指针变量赋
NULL值,此值为空值 。 例如,int *p ;
p=NULL ; 表示指针变量 p的值为空 。
NULL是在 stdio.h头文件中定义的预定义符,因此在使用 NULL时,应该在程序的前面出现预定义命令行,# include "
stdio.h "。 NULL的代码值为 0,所以语句
p=NULL ;等价于,p=0 ;,都是表示指针变量 p是一个空指针,没有指向任何对象。
7.4 对指针变量的操作
7.4.1 通过指针或地址引用一个存储单元当指针变量中存放了一个确切的地址值时,通过指针可以用,间接运算符,( *)
来引用该地址所代表的存储单元 。
1.在赋值号右边由,*”运算符和指针变量组成的表达式,代表指针所指存储单元的内容例如,int *p,i=10,j ;
p=&i ;
j=*p ;
第二条语句是把变量 i的地址赋给了指针变量 p,第三条语句是把指针变量 p所指向的变量 i存储单元的值 10赋给变量 j。
“*”号在这里是一个“间接运算符”,
它为单目运算符,与运算对象自右至左结合,且运算对象必须为一个地址对象。
例如,j=*&i ;
该语句中,&”运算符求出变量 i的地址,,*”运算符取变量 i地址中的值 10赋给变量 j。
例如,int **pp,*p,k=20,j ;
p=&k ;
pp=&p ;
j=**pp ;
以上语句执行后的结果是:把变量 k的地址赋给了指针变量 p,把指针变量 p的地址赋给了指向指针的指针变量 pp,这种关系如图 7-4所示 。
最后一条语句是把变量 k中的值 20赋给了变量 j,
这条语句使用了两个,间接运算符,,**p表示获取指针变量 p中的值 ( 此值为变量 k的地址值 ),
**pp表示通过指针变量 p,再获取变量 k中的值 20。
图 7-4 变量 pp,p和 k之间的关系
2,在赋值号左边由,*” 和指针变量组成的表达式,代表指针所指的存储单元例如,int *p,k=0 ;
p=&k ;
*p=150 ;
以上第三条语句是把整数 150存入变量 k中 。
*p=*p +1 ; 或 *p+=1 ;
以上语句是获取指针变量 p所指向的存储单元 k中的值 150,然后加 1再放入指针变量 p所指向的存储单元 k中,此时变量 k中存放的数值为 151。
例如,int **p,*p,k ;
p=&k ;
pp=&p ;
**pp=200 ;
以上第四条语句是将整数 200存入变量
k中。
例 7.3 指针指向三个变量,通过指针运算选出数值最小的那个数并打印出来 。
main()
{ int a,b,c,min,*pa,*pb,*pc,
*pmin ;
pa=&a ; pb=&b ; pc=&c ;
pmin=&min ;
scanf ( " %d %d %d ",pa,pb,pc ) ;
/* 将键盘输入的三个整数依次放入变量 a,b,c中 */
printf ( " a=%d,b=%d,c=%d \n ",a,b,c ) ;
*pmin= *pa ; /* 假定变量 a中的数值最小,将其放入变量 min */
if (*pmin> *pb ) *pmin= *pb ;
/* 若 b中的数值比 min小,将其放入变量 min */
if (*pmin> *pc ) *pmin= *pc ;
/* 若 c中的数值比 min小,将其放入变量 min */
printf ( " min=%d \n ",min ) ;
/* 通过直接访问方式输出变量 min中的值 */
printf ( " min=%d \n ",*pmin ) ;
/* 通过间接访问方式输出变量 min中的值 */
}
当运行程序时输入 25 12 36↙
程序运行结果:
a=25,b=12,c=36
min=12
min=12
程序运行结果说明,当指针指向变量后,可以通过指针对所指向的存储单元进行数据的存取,直接访问方式和间接访问方式的结果是相同的。
7.4.2 指针 ( 变量 ) 的运算
1.指针的移动
2,指针的比较
7.5 指针在函数方面的应用
7.5.1 在被调用函数中直接改变主调用函数中的参数值在第 6章中,我们曾经介绍过,通过
return语句可以返回函数值,但这种方式只能返回一个数据,若想修改或传回多个数值,可以借用指针 。
7.5.2 使函数返回一个地址一个函数运行结束时,可以返回一个整型值,实型值,字符型值等,同样也可以返回指针型的数据,即返回地址值 。 这种返回指针型数据的函数,定义的形式为:
类型名 *函数名 ( 参数表 )
{ …… }
说明:函数名前的星号,*”,说明了此函数是一个指针型函数,函数的返回值是一个地址值 。