秦玉平,冷强奎,李祥纳
(1.渤海大学 数学科学学院,辽宁 锦州121013;2.渤海大学 信息科学与技术学院,辽宁 锦州121013;3.北京国电通网络技术有限公司,北京100761)
内存中每一个字节都有一个标明其位置的地址.C语言编译程序在对源程序编译时,为每个变量分配连续的一定字节数的存储单元.变量所分配的存储单元的第一字节的地址就是该变量的地址[1-2].
编译程序在为变量分配存储单元的时,记录下变量的名称、变量的数据类型和变量的地址.在对变量进行操作时,先通过变量名查取变量的地址,再根据变量的数据类型到相应的内存单元存取数据.由于地址起到寻找操作对象的作用,就像一个指向对象的指针,所以把地址称为“指针”.
程序离硬件越近,其执行效率就越高.C语言中有指针类型,能够直接访问内存,即直接对物理硬件进行操作.因此,合理使用指针能有效提高C语言代码的质量,但使用不当也会导致效率低下.指针是C语言教学的重点,也是学习者学习的难点[3-5].为便于学习者快速掌握C语言指针知识,并熟练使用指针进行C语言程序设计,本文对C语言中指针的定义、指向对象的引用及注意事项进行详细的阐述.
变量的指针是常量,可通过取地址符“&”得到.指针变量定义的一般形式为:
类型标识符*变量名
用指针引用基本型变量的一般形式为:*指针变量名
注意:定义变量时,变量名前加“*”,表示该变量为指针变量;使用指针变量时,指针变量名前加“*”,表示该指针变量所指向的变量.
【例1】用指针引用基本类型变量举例.
例1中,指针变量p指向整型变量a,此时可以用p引用变量a,即*p与a等价.变量a、p的值及它们之间的关系如图1所示.
图1 变量a和p的值及其关系
指针变量的地址是指针变量所指向对象的二级指针(指针常量),可以定义二级指针变量,其一般形式为:
类型标识符 **变量名
【例2】用二级指针引用操作对象举例.
例2中,一级指针变量p指向变量a,二级指针变量pp指向一级指针变量p.此时可用pp引用变量a,即**pp与a等价.变量a、p和pp的值及它们之间的关系如图2所示.
图2 变量a、p和pp的值及其关系
用结构体(共用体)指针变量可以引用其所指向变量的成员,其引用方式为:
(*指针变量名).成员名 或 指针变量名->成员名
【例3】用指针引用结构体变量举例.
例3中,把变量stu的地址(&stu)赋给指针变量p,可用指针变量p引用变量stu的成员,此时,(*p).name与stu.name等价,p->score与stu.score等价.
常用指针作函数的参数[6],此时形参和对应实参指向同一对象,修改形参指向的对象相当于修改实参指向的对象,通过一次函数调用可以得到多个返回值.另外,常用指针对链表操作.
【例4】用指针操作链表举例.
例4中,函数max的功能是计算单链表中数据的最大值,用结构体指针变量p遍历单链表,用结构体指针变量m指向最大值结点,最后返回最大值结点的指针.
注意:由于链表的存储空间不一定连续,因此链表中的指针后移不能用自加运算完成,而是用所指向结点的指针域值更新实现.
C语言中,数组名代表数组的起始地址,是地址常量.数组元素的引用可以用数组名法,也可以用指针变量法.
用数组名引用数组元素的一般形式为:
*(数组名+表达式)
其中,表达式类型任意,一般为算术表达式,其值为数组元素的下标.
【例5】用一维数组名引用数组元素举例.
例5中,*(a+i)表示a[i],在编译时,编译系统自动将下标法转换为数组名法,所以下标法和数组名法的执行效率相同.
指向一维数组元素的指针变量的定义与指向变量的指针变量的定义相同.用指针变量引用数组元素的一般形式为:
*(指针变量+表达式)或指针变量[表达式]
其中,表达式类型任意,一般为算术表达式.若指针变量指向数组的第一个元素,则表达式的值就是要引用的数组元素的下标,否则要引用的数组元素的下标为:指针变量-数组名+表达式.
【例6】用指针变量引用一维数组元素举例.
例6中,通过赋值运算p=a,指针变量p指向数组a[0],*(p+2+2)表示数组元素a[4];通过赋值运算p=&a[2],指针变量p指向数组a[2],p[1]表示数组元素a[3],p[-1]表示数组元素a[1];for语句中用指针变量p遍历一维数组,因为p是变量,所以可以进行自加自减运算.
C语言对二维数组的处理方法是将其分解成多个一维数组.
若有定义:int a[3][4];
则C语言对数组a的处理方法如图3所示.即将每一行看作一个一维数组,数组名为a[i](0≤i≤2),元素依次为a[i][0]、a[i][1]、a[i][2]和a[i][3],把a看作由元素a[0]、a[1]和a[2]组成的一维数组.
图3 二维数组a的处理方法
可以用一维数组名和二维数组名引用二维数组元素,也可用指针变量引用二维数组元素.
用一维数组名引用二维数组元素的一般形式为:
*(一维数组名+表达式)
其中,表达式的类型任意,一般为算术表达式,其值为二维数组元素的列标.
由于二维数组在内存中按行连续存储,所以可以把二维数组a看成是数组名为a[0]的一维数组,二维数组元素a[i][j]可表示为:*(a[0]+i*列数+j).
【例7】用一维数组名引用二维数组元素举例.
例7中,*(a[2]+3)、*(a[1]+7)和*(a[0]+11)都表示数组元素a[2][3],*(a[0]+2*4+1)表示数组元素a[2][1].
用二维数组的数组名引用二维数组元素的一般形式为:
*(*(数组名+表达式1)+表达式2)
其中,表达式1和表达式2的类型任意,一般为算术表达式.表达式1的值是行标,表达式2的值是列标.
【例8】用二维数组名引用二维数组元素举例.
例8中,*(*(a+i)+j)表示数组元素a[i][j].编译时,编译系统自动将二维数组元素的下标表示法转换为数组名表示法.
指向二维数组元素的指针变量的定义与指向变量的指针变量的定义相同.用指向二维数组元素的指针变量引用二维数组元素的一般形式为:
*(指针变量+表达式)
其中,表达式的类型任意,一般为算术表达式.若指针变量指向二维数组的第一个元素,则引用的数组元素的行标为:(表达式)/列数,列标为:(表达式)%列数;否则引用的数组元素的行标为:(指针变量-数组名[0]+表达式)/列数,列标为:(指针变量-数组名[0]+表达式)%列数.
【例9】用指向二维数组元素的指针变量引用数组元素
例9中,通过赋值运算p=a[0],p指向a[0][0],*(p+2+2)表示数组元素a[1][0];通过赋值运算p=a[1],p指向a[1][0],p[1]表示数组元素a[1][1];for语句中用指针变量p遍历数组,依次输出二维数组元素值.
指向一维指数组指针变量定义的一般形式为:
类型标识符(*指针变量名)[常量表达式]
其中,常量表达式的值是二维数组的列数.
用指向一维数组的指针变量引用二维数组元素的一般形式为:
*(*(指针变量+表达式1)+表达式2)
其中,表达式1和表达式2的类型任意,一般为算术表达式.表达式2的值为要引用的二维数组元素列标.若指针变量指向第一行,则表达式1的值为要引用的二维数组元素行标;否则,要引用的二维数组元素行标为:指针变量-二维数组名+表达式1.
【例10】用指向一维数组的指针变量引用二维数组元素.
例10中,p是向一维数组的指针变量,指向二维数组a的第一行,*(*(p+row)+col)表示数组元素a[row][col].用指向一维数组的指针变量引用二维数组元素时,也可用下标法表示,程序中的*(*(p+row)+col)可以写成p[row][col].
C语言中没有字符串变量,可以用字符数组实现字符串(对字符串中字符的引用与前面介绍的数组元素的引用相同),也可以用字符指针变量实现字符串.
【例11】用指针变量实现字符串举例.
例11中,p是字符指针变量,并将字符串常量"china"的首地址赋给它,即p指向字符串的第一个字符“c”.程序的输出结果是从p+2指向字符开始的子串"ina".
函数名代表函数的入口地址,是地址常量.指向函数的指针变量定义的一般形式为:
[返回值类型](*指针变量名)([形参表列])
可以用指向函数的指针变量调用函数,其一般形式为:
(*指针变量名)([实参表列])或 指针变量名([实参表列])
【例12】用指向函数的指针变量调用函数举例.
例12中,指针变量p指向函数max,函数调用(*p)(a,b)等价于max(a,b)).
用户在使用文件时,须将文件调入内存,并通过一个结构体类型的指针与其建立联系,从而对文件进行操作.这种结构体类型的指针是对文件信息的描述,包括文件的描述符、缓冲区的大小、文件的长度、文件中当前处理的数据位置等.文件指针变量定义的一般形式为:
FILE*指针变量;
【例13】用文件指针对文件操作举例.
例13中,文件指针fp1与文件e1.txt建立了联系,文件指针fp2与文件e2.txt建立了联系,程序的功能是将文件e1.txt的内容复制到文件e2.txt中.
注意:文件指针与文件位置指针不同,文件指针用来标识文件,须在程序中定义说明,文件位置指针是指文件打开之后,在文件内部进行移动的指针,用以指示文件内部的当前读写位置,每读写一次,文件位置指针自动向后移动,它是系统设置的,不需要在程序中定义说明.
指针是C语言的精髓,合理利用指针能提高代码的质量,但使用不当会导致效率低下.使用指针引用操作对象时需注意四点:一是区分常量和变量,二是指向对象的数据类型,三是对象引用方式,四是指针变量使用前是否被赋值.变量的指针是常量,常用指针变量引用所指向的变量.数组的指针是常量,常用指向数组元素和数组的指针变量引用数组元素.函数的指针是常量,可用指向函数的指针变量调用函数.stdin、stdout和stderr是文件指针常量,分别指向标准输入、标准输出和标准出错输出文件,对文件进行操作时,需用文件指针变量与文件建立联系.指针变量在使用前一定要赋初值,且类型一定要匹配,不能将基本类型数据(0除外)直接赋给指针变量,也不能将一个指针直接赋给与它类型不同的指针变量.