对C语言指针教学问题的探究
2009-11-02赵忠孝杨亚蕾
赵忠孝 杨亚蕾
摘要:本文讨论了C语言中指针的各种应用形式,认真分析了指针在数组、函数和动态内存分配等方面应用的特点和优点,对如何学好和掌握C语言的指针有十分重要的指导意义。
关键词:指针;指针变量;数组;函数;动态内存分配
中图分类号:G642 文献标识码:A
1引言
指针是C语言中一种特殊的数据类型,运用指针编程是C语言最主要的风格之一。利用指针变量可以访问各种类型的数据;能动态地分配内存空间;能很方便地使用数组和字符串;并能像汇编语言一样处理内存地址,编出精练而高效的程序。但是,大部分学生对指针的理解和应用还是感到困惑,特别是什么场合用到什么类型的指针变量,应用指针变量应注意哪些问题等。本文对指针应用中的一些问题进行了梳理,以利于学生掌握指针的应用。
2数组中指针的应用
2.1使用指针引用数组元素的优点
对数组元素既可以用下标a[i]的方式引用,也可以用指针变量*p的方式引用。应该说,下标方式能对数组进行随机访问,指针变量却做不到这一点。但是,引入指针的主要目的是为了提高对数组元素访问的速度。
在C语言中,数组中每一维下标的下界定义为0。对一维数组,设a[i]的存储地址为Loc(a[i]),每个数据元素占d个存储地址,则第i个数据元素的地址为
Loc(a[i])=Loc(a[0])+i*d (1)
对二维数组a[m][n],a[m][n]的存储地址是:
Loc(a[i][j])=Loc(a[0][0]) + ( i*n + j ) * d (2)
实际上,对数组元素的引用,都要先计算数组元素的地址,才能对指定单元进行操作。显然,一维数组中的地址要进行1次乘法和1次加法运算;二维数组中的地址则要进行2次乘法和2次加法运算。如果用指针变量p指向数组,连续对数组元素进行引用,可用p++和p--来移动指针。每次的引用地址只须进行简单的加法运算,引用数组元素的速度比使用数组下标要快得多。表1是在IBM-R40上使用下标和指针两种不同方式对10000个元素进行10000次访问的时间比较。
从测试结果看,随着数组维数的增加,使用指针方式访问数组的速度基本不变,但下标方式的访问速度明显减慢。
2.2动态数组的应用
在C语言中,数组一般都是静态的。数组已经定义,所占用的内存空间就一直被占用,直到该函数退出时为
止。但如果使用指针变量,就可以实现需要时给数组分配内存,不需要时释放内存,可节约大量的内存空间。下面的程序就是使用了指针变量,使用动态数组的一个例子。
typedef struct
{int a[MAXSIZE];
} A;
main()
{int i;
A *p;
p=(A *)malloc(sizeof(A));/*动态分配内存*/
…………
free(p);/*释放内存*/
}
3函数参数中指针的应用
在C语言中,函数的形参是局部变量。实参和形参变量间的传递是值传递,即将实参的值传递给形参变量。形参在函数中如何变化,并不改变实参的值,我们称之为单向传递。这种参数的单向传递减少了函数之间的耦合性,增加了其内聚性,有利于结构化编程。但是,如果调用函数想从被调函数中得到一个以上的返回值,就比较困难。当然,通过全局变量也能实现,但过多的使用全局变量又增加了函数之间的耦合性,不利于结构化编程。如果实参和形参都使用指针变量,就可达到此目的。下面的程序就是使用指针变量实现两个变量值交换的例子。
int swap(int *p1,int *p2)
{int temp;
temp=*p1;
*p1=*p2;
*p2=temp;
}
main()
{
int a,b;
int *pointer_1,*pointer_2;
scanf("%d,%d",&a,&b);
pointer_1=&apointer_2=&b
if(a
printf(" %d,%d ",a,b);
}
需要说明的是:
(1) 实参和形参之间传递的仍然是值,但该值不是变量的值,而是变量的地址,如图1所示。此时,实参和形参都指向了同一变量,对形参变量所指变量值的改变也必然改变实参变量的值,如图2所示。
(2) 但在被调函数中不能改变形参指针变量的指向,否则实参变量和形参变量各指向不同的变量。如:p=p1;p1=p2;p2=p;仅交换了形参变量的指向,实参变量的指向并没有改变,也就是不能企图通过改变形参指针的值而使实参指针的值也发生改变,如图3所示。
4指向指针的指针变量的应用
如果说指针是C语言中比较难掌握的内容,而指针的指针则是更加难学难懂。学生往往不知道指针的指针到底应用在哪些方面,有什么特点和优点。
如果一组变量只能通过指针(地址)来引用,一般来说就需要用指针的指针变量来实现。C语言中没有字符串变量,只能在字符数组中存储字符串。对二维字符串数组的引用大都要用到指向指针的指针变量,在各种教科书都举过这样的例子,也就不再赘述了。现在主要对指针的指针在函数参数中的应用进行探讨。
如果主调函数要得到一个以上普通变量的返回值,函数的形参可以用指针变量来实现。同样,如果要返回一个改变了的指针变量的指向,形参就需要用指向指针的指针变量。比如,在对二叉排序树的操作中,每个结点是一个指针变量,当删除一个节点后,为了使其仍能保持二叉排序树原有的特性,必须对其余结点进行整理。如果删除的是根结点,删除后二叉排序树的根结点就发生了变化,如图4所示。应该将新的根结点(指针变量)返回,此时形参就应该用指向指针的指针变量。
int DeleteNode(NodeType **t,KeyType kx)
{NodeType *p=*t,*q,*s,**f;
int flag=0;
if(SearchElem(*t,&p,&q,kx)); /*查找值为kx的结点,p指向该结点,q指向其父结点*/
{flag=1; /*查找成功,置删除成功标志*/
…… /*确定删除结点为根结点p,调整二叉排序树*/
t= p->lc ; /*p左孩子为新根结点*/
ree(p);
}
return flag;
}
这样,主调函数的实参变量就指向了二叉排序树新的根结点。
5注意两个区别
5.1数组指针变量和指针数组变量
(1) 数组指针变量
int (*p)[4];
方括号“[4]”前面(*p)不是合法的标识符,因此(*p)[4]不是数组。定义中有*p,则p一定是指针变量,后面又有方括号“[4]”,肯定是和数组有关的指针变量。它表示p是一个指针变量,指向包含4个元素的一维数组。