C语言中的指针教学探索

时间:2022-10-17 05:18:59

C语言中的指针教学探索

摘要:指针在C语言中具有重要的地位,也是初学者最不容易掌握的内容。本文首先介绍了计算机的存储机制,在此基础上介绍了指针的概念,并给出了几个典型的指针应用的例子,说明了指针的方法。

关键词:C语言;指针;教学探索

中图分类号:G642.0 ?摇文献标志码:A 文章编号:1674-9324(2014)06-0233-03

指针在C语言中具有重要的地位,也是C语言教学中学生最难掌握的内容,一个学生是否学会了C语言,从他是否掌握了指针就可以大体上做出评判。本文重点探讨一下C语言在指针教学中的要点和体会。

一、计算机的存贮机制

在教学中我们发现,一些学习过计算机原理、汇编的同学,对指针的概念比较容易理解,掌握的也比较快,即便遇到一些比较复杂的指针关系,也往往能够通过自己的思考,给出正确的解释。究其原因,就是因为这些同学对计算机的存储机制比较了解,可以将指针与存储对应起来。为此,我们先简要介绍一下计算机的存储机制。在计算机中,数据以二进制的形式存储在内存中,内存的基本单位是字节,一个字节由8位二进制组成,一个数可以由一个字节表示,也可以由多个字节组合在一起表示,与具体的数值类型有关。比如,一般来说一个字符用一个字节表示就可以了,而一个整数则一般由多个字节表示,比如2个字节或4个字节等。具体由几个字节表示,与具体的C语言编译器有关。为了叙述方便,在以下的讨论中我们假定字符是一个字节,而整数是两个字节。

内存中每个字节都有一个编号,通常称为该字节的地址,用于指定该字节。就好比我们通常用号码对宾馆的房间进行编号一样。地址的长度也是与编译器有关的,在这里我们假定地址的长度为4个字节。图1给出了一个内存存储的示意图。同样为了叙述方便,无论是地址还是数据我们都使用的是十进制或者直接使用字符,而实际上应该是二进制的。在图1中,左边的表示地址,右边的表示存储的内容。在1000~1001这两个地址中,存储的分别是字符a、b,每个各种一个字节。1002和1004开始的两个字节,存储的分别是123和456两个整数。2010、2014和2018开始的4个字节,分别存储了1003、1000和2010,其含义我们在后面再介绍。在C语言中,一个变量是与一个地址所对应的,比如,如果定义了变量:int n,m;则编译器会自动为n、m分别分配一个地址,变量n、m的值就分别存储在它们所对应的地址中。

二、指针的基本概念

什么是指针?简单地说指针就是其值为地址的变量。一个指针变量的值是某个地址,该变量所指向的内容,则是该地址中所存储的内容。如同整型变量一样,指针变量也是一个变量,只不过整型变量的值是整数,而指针变量的值是一个指针,也就是说是一个地址,通过该地址可以找到指针所指向的内容。下面讨论中,指针变量我们简称为指针。我们先通过一个类比的例子来说明指针的概念。假设某宾馆有101、102……等房间,这些房间中,有些是住人的,比如101房间住的是张三,102房间住的是李四,则可以认为101房间的值是张三,102房间的值是李四。这个宾馆有些奇怪,除了住人的房间外,还有一些房间并不住人,里面只是有一个牌子,上面写着某个房间号。比如说,105这个房间,里面的牌子上写着102。那么105房间就可以认为对应一个指针变量,其值是一个地址102,该指针所指向的值,就是102房间的李四。这里要区分出指针自己的值以及指针所指向的值。在该例子中,105房间(指针)自己的值是102,其指向的值是李四。结合图1的例子,我们假设有以下定义:int *p1;char *p2;假设编译器为p1、p2分别分配的地址是图1中的2010和2014,则p1的值是1002,是一个地址,而p1所指向的内容,即*p1的值为地址1002中所存储的内容,即整数123。同样,p2的值是1000,也是一个地址,p2所指向的内容,即*p2的值为字符a。有了以上指针的基本概念后,再复杂的指针,也可以一步推导出具体的含义。比如如下的指针的指针的定义:int **p;还是假定编译器为p分配的地址是图1中的2018。则p的值是2010,是一个地址。P所指向的内容,也就是*p的值是地址2010中的内容1002,还是一个地址。P所指向的内容的内容,即**p就是存贮在1002中的整数123。C语言中,操作符“&”可以得到一个变量的地址,比如:int n,*p;就可以通过p=&n得到变量n的地址,并赋值给指针p。

三、指针的应用

在C语言中,指针的应用比较多,有些情况是用指针会比较方便,有些情况是必须用指针。下面给出几个有关指针使用的例子。

1.交换两个变量的数据,编写一个函数,实现两个变量数据的交换,初学者很可能给出这样的定义:

void swap(int a,int b)

{

int t=a;

a=b;

b=t;

}

当为了交换两个变量的数值时,则这样调用:

main()

{

int x=10,y=20;

printf(“%d %d/n”,x,y);

swap(x,y);

printf(“%d %d/n”,x,y);

}

但是,当你运行该程序时,却惊奇的发现结果并不正确。这是为什么呢?这与C语言中参数的传递机制有关。在调用swap函数时,x、y的值传递给了参数a、b,在函数内部交换的实际上是a、b的值,而不是x、y的值。那么如何解决这个问题呢?在C语言中就需要用到指针了。正确的swap函数的定义及调用如下:

void swap(int *a,int *b)

{

int t=*a;

*a=*b;

*b=t;

}

main()

{

int x=10,y=20;

printf(“%d %d/n”,x,y);

swap(&x,&y);

printf(“%d %d/n”,x,y);

}

这样为什么就是正确的呢?因为通过取地址操作&分别得到了x、y的地址,在函数调用时,作为参数x、y的地址传递给了a、b,在swap内部,交换的是a、b所指向的内容,而a、b所指向的内容,实际上就是x、y的值,从而达到了交换x、y值的目的。假设x、y的地址分别是1000和1002,而在调用swap时,分配给a、b的地址分别是2000和2004。由于x、y均为整数,这里假定整数占用两个字节;而a、b均为指针,假定占用4个字节,如图2所示。通过取地址操作分别得到x、y的地址1000和1002,并作为参数传递给了a和b,在swap内部交换a、b所指向的内容,实际上交换的就是内存地址1000和1002的内容,也就是x和y的内容。所以,通过指针操作,达到了交换x、y的值的目的。

2.链表。链表的功能与数组有些类似,但是链表的长度不固定,可以根据需要动态的调整,可长可短。而且链表最大的好处是插入、删除比较方便。比如说,用链表记录某一门课的成绩,选修的学生人数少的可能有十几人,多的可能有几百人。如果用数组,则可能需要定义一个最大值,无论学生多少,都需要定义这么大的数组。而如果用链表的话,则可以根据学生的多少,动态的变化。再比如,如果某个学生中途退选了这门课程,需要删除该学生的有关信息,如果是数组的话,特别是当需要删除的学生排在第一位时,则需要在数组中移动所有学生的信息。而链表,则可以比较容易地删除一个学生的信息。插入一个学生信息也是如此。特别当这种插入、删除操作频繁发生时,使用链表的效率就会比较高。所谓的链表,就是一些通过指针链接在一起的数据单元。一个数据单元由两部分组成,一部分为数据域,用于存放与该数据单元有关的数据,一部分为指针域,用于存放链表的下一个单元的地址。链表的示意图如图3所示。

其中Di表示数据,数据不一定是一个,可能是一组相关联的数据。链表最后一个单元的指针是空指针NULL,表示链表结束,这里用“^”表示。在C语言中,链表的单元可以用结构定义。比如,我们要实现一个记录某个课程成绩的链表,一个单元记录一个学生的信息,包括学号、姓名、成绩等。则该单元可以定义如下:

struct NODE

{

?摇int number;

?摇double grade;

?摇struct NODE *next;

};

这里用nunber表示学号,grade表示成绩,next是指向下一个单元的指针。那么如何建立一个链表呢?可以通过每次向一个链表插入一个单元的办法建立一个链表。要插入一个单元,首先要为一个单元分配一个空间,这可以通过C语言的函数malloc来实现,其输入参数是单元的大小,其返回值是指向该单元的指针。比如本例中,以下调用就为一个单元分配了空间,并在p中得到了指向该单元的指针:

strunct NODE *p;p=malloc(sizeof(struct NODE));假设list是指向学生成绩链表的指针,如果向该链表插入一个单元,其实很简单,只要令该单元的next等于list,然后再将list指向该单元就可以了。下面给出向学生成绩链表中插入一个单元的函数,其中输入参数分别是指向成绩链表的指针、学号、姓名和成绩,返回值是指向插入了一个单元后的成绩链表的指针。这里假定开始时,成绩链表为空,即list的值为NULL。

struct NODE *insert(struct NODE *list,int number,double grade)

{

?摇struct NODE *p;

?摇p = malloc(sizeof(struct NODE));

?摇p->number = number;

?摇p->grade = grade;

?摇p->next = list;

?摇return p;

}

比如从键盘输入学生学号和成绩建立成绩链表,当学号为0时表示输入结束,则程序如下:

struct NODE *list = NULL; //初始的成绩链表为一个空表

mail()

{

?摇int number = 1;

?摇double grade;

?摇while (1)

?摇{

?摇?摇printf(“请输入学号、成绩:”);

?摇?摇scanf(“%d %f”, &number, &grade);

?摇?摇if (number == 0) break;

?摇?摇list = insert(list, number, grade);?摇}

}

为了输出成绩单,可以用以下的函数实现,其原理就是先输出成绩链表的第一个同学的成绩,然后再通过指针找到下一个同学,以此类推,直到下一个同学为NULL为止。该函数的输入参数为指向成绩链表的指针。

Printlist(strunct NODE *list)

{

while (list)

?摇{

?摇?摇printf(“学号:%d 成绩:%f\n”,list->number, list->grade);

?摇?摇list = list->next;?摇}

}

3.树结构。树结构简称为树,比如当表示一个家族关系时,就需要用到树结构。树结构也可以用指针实现。比如某家族情况如下:

A为祖父,B是他的妻子,C、D、E是他们的孩子,其中C、D为男孩,E为女孩,F是C的妻子,G、H是C的孩子。如何表示这样的一种结构呢?我们可以设计这样的一种结构:

struct TREE

{

char xingming[10];

strunct TREE *peiou;//指向配偶

strunct TREE *xiongmei;//指向自己的一个兄弟姐妹

strunct TREE *haizi;//指向自己的孩子链表

strunct TREE *fuqin;//指向自己的父亲

}

这里,配偶、父亲都是唯一的,用指针指向他们就可以了。由于一个人的兄弟姐妹人数是不确定的,有的人多有的人少,所以兄弟姐妹之间用上节介绍的链表表示,也就是说,同属于一个父亲的兄弟姐妹之间,形成一个链表。父亲的haizi(孩子)指针,指向该链表的第一个单元,通过该指针,就可以得到一个人的所有的孩子。示意图如下:

而这里的父亲、妻子、孩子等,也都与张三具有同样的结构,比如“妻子”,也有指向她父亲的指针,她的配偶指针则指向张三,而她的孩子指针,也同张三一样,指向孩子链表。有了这样的结构和链接关系,就可以很方便地从家族树中提取出想要的关系。假设p是指向张三这个结构的指针,则有:输出张三的父亲:printf(“%s”,p->fuqin->xingming);输出张三的母亲,由于这里并没有直接的“母亲”信息,可以通过父亲的配偶关系得到:Printf(“%s”,p->fuqin->peiou->xingming;当然,也可以在结构中增加一个muqin指针,直接指向自己的母亲,这完全根据实际需要设计。比如,如果频繁的查找某些人母亲信息,则可以考虑添加一个muqin指针,以提高效率。输出张三的所有孩子:q=p->haizi;

while (q)

{

?摇printf(“%s “,q->xingming);

?摇q=q->xiongmei;}

在C语言中,指针是比较重要的概念,也是学生在学习过程中难于理解且遇到的比较多的问题。通过在C语言教学过程中的实践,整理了一些针对指针教学的问题,通过这些问题的讲解和分析,学生往往会加深对指针的理解,希望对有关C语言的教与学能起到一定的帮助。

参考文献:

[1]薛胜军.计算机组成原理[M].武汉:华中科技大学出版社,2000.

[2]王诚,刘卫东,宋佳兴.计算机组成与设计[M].3版.北京:清华大学出版社,2008.

[3]严蔚敏,吴伟民.数据结构(C语言版)[M].北京:清华大学出版社,2006.

[4]谭浩强.C程序设计M].北京:清华大学出版社,2006.

[5]David J.Kruglinski.Visual.C++技术内幕[M].4版.北京:清华大学出版社,2001.

[6]H.M.Deitel,P.J.Deitel.C程序设计教程[M].北京:机械工业出版社,2001.

[7]黄宇.C语言教学中几个常见的问题[J].计算机教育,2009,(10).

上一篇:社会转型期我国大学生德育改革之我见 下一篇:新加坡南洋理工学院人才培养特色与启示