第7章 地址和指针 7.1 地址和指针的概念 7.2 指针变量的定义和指针变量的基类型 7.3 给指针变量赋值 7.4 对指针变量的操作 第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,程序运行结果: 8 + 5=0 8 – 5=0
程序运行结果与我们“想象”的不一致,为什么呢?其原因是fun 函数中变量add和sub与main函数中变量add和sub各自占用自己的存储单元,因此fun函数中变量add存放的两个数的求和值无法传回到main函数的变量add中,同理,fun函数中变量sub存放的两个数的相减值也无法传回到main函数的变量中。下面我们修改一下源程序,借助指针,将fun函数中运行的结果传回到main函数中。
类型名 *指针变量名1,*指针变量名2,……; 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 指针变量的基类型的作用 任何一个指针变量都是用于存放它所指向变量的地址,只要能存放地址就可以了,为何还要区别不同的基类型呢? 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.通过求地址运算符(&)把 一个变量的地址赋给指针变量 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 ; 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 通过指针或地址引用一 个存储单元 7.4 对指针变量的操作 7.4.1 通过指针或地址引用一 个存储单元 当指针变量中存放了一个确切的地址值时,通过指针可以用“间接运算符”(*)来引用该地址所代表的存储单元。
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.在赋值号左边由“*”和指针 变量组成的表达式,代表指针所指 的存储单元 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 程序运行结果说明,当指针指向变量后,可以通过指针对所指向的存储单元进行数据的存取,直接访问方式和间接访问方式的结果是相同的。
7.4.2 指针(变量)的运算 1.指针的移动 2.指针的比较
7.5 指针在函数方面的应用 7.5.1 在被调用函数中直接改 变主调用函数中的参数值 7.5 指针在函数方面的应用 7.5.1 在被调用函数中直接改 变主调用函数中的参数值 在第6章中,我们曾经介绍过,通过return语句可以返回函数值,但这种方式只能返回一个数据,若想修改或传回多个数值,可以借用指针。
7.5.2 使函数返回一个地址 一个函数运行结束时,可以返回一个整型值、实型值、字符型值等,同样也可以返回指针型的数据,即返回地址值。这种返回指针型数据的函数,定义的形式为: 类型名 *函数名(参数表) { …… } 说明:函数名前的星号“*”,说明了此函数是一个指针型函数,函数的返回值是一个地址值。