c语言中指针链表

2024-06-23

c语言中指针链表(通用7篇)

c语言中指针链表 篇1

如何透彻理解C语言中指针的概念

强大的指针功能是C语言区别于众多高级语言的一个重要特征。C语言指针的功能强大,使用灵活多变,可以有效地表示复杂的数据结构、动态分配内存、高效地使用数组和字符串、使得调用函数时得到多个返回值。而它的应用远不限于此。初学者对于指针的概念总是感到无所适从,有时觉得“自己懂了,为什么编译器就是不懂呢”,常有茫然和无助的感觉。

学好指针的关键在于深入了解内存地址的空间可以理解为一个一维线性空间,内存的编址和寻址方法,以及指针在使用上的一些规定。事实上,指针就是方便我们对内存地址直接进行操作的,是为程序员服务的,我们只要抓住指针想要帮助我们解决什么问题这个核心,就可以轻松地理解它的工作原理。

什么是指针,指针有什么作用

指针就是指向一个特定内存地址的一个变量。简化了的内存空间模型是按照从0到某一个数(比如1048575=1M-1)的一维线性空间,其中的每一个数对应一个存储单元,即1个字节。指针有两个属性:指向性和偏移性。指向性指的是指针一定要有一个确定的指向,偏移性则是体现指针重要应用的方面,即指针可以按程序员的要求向前或向后偏移。

指针的应用往往与数组联系在一起,为了方便说明问题,不妨从数组开始解释指针的偏移。数组就是许多的变量,它的一个重要特征就是在内存空间中连续地存放,而且是按下标顺序存放。比如我们定义一个有100个变量的一维整型数组,它一定从内存的某一个存储单元开始按数组下标顺序存放,连续占用100*4=400字节。当我们定义一个数组时,系统就会自动为它分配一个指针,这个指针指向数组的首地址。(在本文剩余部分的论述中,不加区分地使用“指向数组的首地址”与“指向数组的第一个元素”这两种说法,事实上这两种说法也是一致的。)

为了让系统了解每一次指针偏移的单位,也为了方便程序员进行指针偏移(让程序员记住一个整形变量占用4字节,一个字符型变量占用1字节„„等等是很麻烦的),不用每次去计算要偏移多少个字节,C语言引入了指针的基类型的概念。基类型的作用就是让系统了解某个指针每次偏移的字节数。比如,对于一个字符型指针,它每次偏移(比如ptr=ptr+1)所起到的作用就是让指针偏移1字节;而对于一个整型指针,它每次偏移就应该是4字节。这样操作数组时就带来了方便。比如对于一个指向某个整型数组起始存储单元(称为首地址)的指针ptr,ptr=ptr+1就表示将该指针指向这个数组的下一个元素的存储单元,即向后移动4字节,而不仅仅是移动一个存储单元(即移动1字节)。

&()、*()、和[ ]运算符的意义

在本文中,将&()、*()和[ ]都看成是运算符。这样可以方便理解这三个概念。简单地说,&()将某个标识符(比如变量)转化为其在内存空间中的地址,而*()是产生一个对应于某个地址的标识符,[ ]就更复杂一点,ptr[i]表示将ptr这个指针虚拟地按其基类型进行i个单位的后移,再进行*(ptr)运算。但这是一个虚拟的后移,即ptr[i]并不改变ptr的指向,只是将其后移i个单位并取*()运算的结果算出来了而已。要改变指针的指向,我们只能通过类似于ptr=ptr+i这样的语句来实现。

实际中,我们往往不愿意经常改变指针的指向,因为指针的移动虽然是自由的,但移动后往往会“移不回来”,因为我们可能无法清楚地确定指针的偏移量。后面我们将看到,对于用指针来表示的数组,其元素的引用和赋值是完全可以不用改变指向这个数组的首地址的指针指向的,而一旦要改变这个指针的指向,问题就会变得复杂一些,我们在后面有一个关于程序的命令行参数处理例子专门介绍这个问题。

指针类型和系统自动分配的指针

指针可以指向几乎所有我们感兴趣的程序设计要素:函数、数组、结构体、链表节点等等。其中不同函数间往往并不存在严格的线性关系。链表节点可以根据算法需要在逻辑上(或物理上)不按线性连续存储。但数组、结构体的共同特征就是它们在物理上都是线性连续存储的。只要指针指向了它们的首地址,就可以通过简单的偏移来访问各个它们的元素。指针的偏移性在这两种数据结构中发挥着至关重要的作用。这时,我们再回想基类型的定义目的,就会有更深层次的认识了。对于一个数组或结构体,它的基类型长度应当是其元素的长度(这里的长度即指在内存空间中占用的字节数),而不再限于定义为某种简单数据类型的长度。

在我们定义数组和函数时,系统都会为其自动分配一个指向其首地址的指针。其中,指针在数组中的应用是最频繁的,也是最基础的。对于一个数组,其名称就是一个指针变量,亦即假如我们定义“int a[10];”的同时就定义了“int *a=a;”(这只是为了说明问题,这样的语句显然是不合法的)。

数组应用中典型的二级指针

设定一个指向指针的指针,即设定一个二级指针。一般认为,指针不宜超过二级,否则会大大增加逻辑错误出现的可能性。因此,下面详细解释数组二级指针的实现方法及原理。在此基础上理解指针的其它相关概念是非常简单的。

刚才一直提及指针的基类型,以及对它的正确理解方法。请在阅读下面论述的过程中不断地考虑“我们所提到的每个指针的基类型是什么”这个问题。

首先我们先要对二维数组进行重新定义,即将一个M*N的二维数组定义为有M个元素的一维数组,它的每个元素都是一个具有N个元素的一维数组。这种理解方式对于以前学习过Basic、Pascal等语言的程序员来说比较难以接受,因为它们更容易直观地将其理解为一张二维表。事实上,二维数组在内存中的线性存储是这样实现的:把每一行看作它的一个元素,然后按照一维数组的按下标顺序排列的原则以每一行为单位进行排列。而对于每一行,也还是按照一维数组按下标顺序排列的原则进行排列。也就是说,我们可以按行优先的方式将数组的数字逐个“填入”内存空间。或者也可以说,多维数组在内存中的排列方式是递归定义的。

既然如此,当我们定义 “int a[10][10];”的时候,a是什么样的指针呢?是的,a就是一个二级指针。它的基类型是有10个元素的一维数组,不再是整型变量了。它所指向的是一维数组指针(第一行的数组指针)。当我们执行a=a+1的时候,a将指向二维数组第二行的数组指针,而不是第一行的第二个元素,因为基类型的长度决定了a+1跨越了一整行。

因此,我们要得到数组a的(i,j)位置上的元素的值,应该按照下面的步骤来进行:

1、a+i,这表示将a指针移到第i行的首地址。

2、*(a+i),这表示将第i行的首地址转化为第i行的标识符,前面已经述及,*()运算符的作用就是将地址转化为标识符。但*(a+i)不是第i行的第一个元素而是一个指针,这个指针的基类型已经变成了整型变量,不再是有10个元素的一维数组了。或许你要说,第i行的首地址不就是第i行第一个元素的地址吗?那么*(a+i)不就是第i行第一个元素的值了?首先,我们可以肯定*(a+i)不是第i行第一个元素的值,但第i行的首地址的确就是第i行第一个元素的地址。前面对*()运算符的说明只是一个表面现象,下面的说法可以辅助你理解*()运算符的真正本质:*()将指针还原为其所指,而不是简单地将地址变成这个地址所存储的值。*()将地址变成这个地址所存储的值这样的说法只对一级指针是正确的。对于二级指针,*()只是将二级指针还原为其所指,即还原为一级指针。物理上“第i行的首地址同时就是第i行第一个元素的地址”这一事实,是容易导致混淆的根本原因。但只我们要从逻辑的角度出发,就可以较为轻松地理解这个问题。

3、*(a+i)+j,这表示将一级指针向后偏移j个单位,要注意*(a+i)这个指针已经是一个以整型变量为基类型的指针了。这时*(a+i)+j是一个偏移后的一级指针,它的值是a[i][j]元素的地址,亦即它所指的就是a[i][j]元素。

4、*(*(a+i)+j),将一级指针还原为其所指,即得到了a[i][j]元素的值。

理解了以上的概念,将会对指针有全新的认识,而对于二级以上的指针和其它类型的指针,原理也都是类似的。对指针的更深入理解只有在编程的实践中得到。从算法设计的角度来看,使用指针对数组进行遍历等操作可降低时间复杂度,因为指针按照基类型偏移1个单位的效率很高。

一维指针数组中的二级指针

透彻地理解下面这段程序对于进一步理解指针的原理是很有裨益的。下面是一个将系统分配的指针(即数组名指针)进行偏移的例子:

main(int argc,char *argv[]){ while(argc>1){ ++argv;printf(“%sn”,*argv);--argc;} }

粗略地看,不难发现这个程序的作用就是将其命令行参数(不包括第一个程序路径及文件名参数)逐个输出。但其中却用到了二级指针,究竟是也不是,我们从细节入手分析。

首先,argv是一个指针数组,它的每个元素所指向的是每个命令行参数字符串的首地址。比如,我们的参数是“abc def”,那么argv[1]和argv[2]所指向的就分别是字符串“abc”和“def”的首地址(注意argv[0]指向的是程序路径及文件名字符串的首地址)。

那么,第四行的++argv是什么意思呢?我们知道,一个数组的名称就是一个指针,在没有被改动的情况下,它指向这个数组的首地址。++argv的作用就是将argv这个指针(数组名)按照其基类型宽度向后移动一个单位,如果原来argv所指向的是argv这个数组的首地址,那么执行以后它将指向其第二个元素(即argv[1])。也就是说,这个程序改动了数组名(本身也就是一个指针)的指向,不断将其后移。

理解到这里,你可能已经初步感到问题并不像看上去那么简单了。下面的一句“printf(“%sn”,*argv)”更是有意义了。你会不会觉得奇怪呢?因为printf(“%s”,ptr)或者puts(ptr)所需要的参数都是指针。既然argv已经是指针,又为什么要在前面再加上一个“*”运算符呢?原因如下:argv确实是指针,但它所指的argv这个数组自己的某一个元素(因为我们已经分析过,argv这个指针是从自己的第一个元素argv[0]的地址开始不断地后移的)。这看起来和一个指向字符串的指针char *ptr=”string content”是类似的。但我们在输出ptr指针所指的字符串时是使用printf(“%s”,ptr)而不是printf(“%s”,*ptr)来输出的。那如果我们的这句话是“printf(“%sn”,argv)”会怎样呢?程序运行后得到的是一堆乱码。那这堆乱码是什么呢?这堆乱码实际上是argv这个在不断向后移动的指针的所指,即argv数组的元素的地址(如&argv[1],&argv[2]等),也即指向某个命令行参数字符串的首地址的指针的地址。如果能理解到这一点,就会知道为什么我们说这个短短的程序中用到了二级指针了。既然argv只是argv这个数组的某个元素的地址,那么加上一个“*”运算符对其进行间接访问,即可得到argv数组的元素的值,这个值是一个指针,它指向某个命令行参数字符串的首地址。因此这个语句的意义也就大白于天下了。事实上,这个语句还可以等价地写为“printf(“%sn”,argv[0])”,因为对于一个指针来说,*(ptr)运算与ptr[0]运算是无条件等价的。不要认为这个语句等价为“printf(“%sn”,argv[i])”(i是循环变量),因为argv这个数组名指针本身已经在后移了,不能用i再次进行后移。

虽然理解起来显得复杂,但程序本身却短小精悍,可以作为处理命令行参数的一般方法来使用。这也从一个侧面证明了指针可以大大简化某些程序设计过程。

由此我们可以总结出,所有指针数组中都包含了二级指针。第一次由指针数组名指向其元素,第二次由其元素再次指向其它的程序设计要素(本例中是字符串的首地址)。

使用字符串常量的一个常见错误分析

这个部分留给具有一定编程经验的读者。如果要使用C语言编写复杂应用程序,下面的知识是必须的。

在指针应用中容易导致错误的一种常见行为就是对字符串常量进行更改。程序员在对字符串常量进行引用、修改时,一定要特别注意C语言对于字符串常量的处理方法。否则,容易导致十分隐蔽的错误。这些错误往往集中在熟悉Pascal等编程语言的程序员身上,并在OOP编程中出现。

当程序中包含了几个字符串常量时,这些常量是在程序入口一次性分配内存的,而不是在每次执行某个函数时开辟一块新的内存区域来存放的。下面用一个具体的错误例子来说明这个问题。

这是一个C++ Builder 6下的例子。某个窗体的代码中包含下面两个事件函数:

void __fastcall TfrmMain::btnSearchClick(TObject *Sender){ nmuMain->InputString = edtKeyword->Text;char* query = new char[ nmuMain->Encode.Length()+ 1 ];strcpy(query, nmuMain->Encode.c_str());HTTPGet(strcat(“http://&q=”,query));} void __fastcall TfrmMain::nmhMainSuccess(CmdType Cmd){ frmMain->pnlStatus->Caption = “Successfully retrieved data from Google.”;AddResults();}

在btnSearchClick事件中,请注意strcat(str1,str2)这个函数。它的作用是将str2连接到str1的后面。这将导致str1变长。这会导致两种后果:

第一,当用户再次点击btnSearch时,HTTPGet()函数的参数将变成“http://&q=”和第一次用户提供query,以及第二次用户提供的query。为什么呢?因为字符串常量“http://&q=”变长了,它已经包含了第一次用户提供的query。

第二,第二个事件函数中的“Successfully retrieved data from Google.”将被替换成用户输入的query。这又是为什么呢?前已述及,两个字符串常量是在程序入口一次性分配内存的。它们是在内存中连续存放的。因此第一个字符串常量变长自然就替换了后面字符串常量。如果没有意识到这一点,很可能导致难以预料的严重系统错误。事实上,C语言对字符串的起始和结束的判定严格遵守下面的简单规律:根据字符串名称(是一个指针)指向的地址来确定字符串的起始,根据“”转义符来确定字符串的中止。

为了修正上面的错误,应该避免对字符串常量进行修改。即将第一个事件函数改为:

void __fastcall TfrmMain::btnSearchClick(TObject *Sender){ nmuMain->InputString = edtKeyword->Text;char* query = new char[ nmuMain->Encode.Length()+ 1 ];char searchstr[41];char* original_searchstr = “http://&q=”;strcpy(searchstr, original_searchstr);strcpy(query, nmuMain->Encode.c_str());HTTPGet(strcat(searchstr,query));}

此外,在OOP编程中,应该注意两个有关字符串的问题。第一,如果采用 char* str = new char[ length ];来分配内存空间,虽然可以实现根据带有变量的表达式length“动态”分配内存空间的效果,比如length=i+j-1;但这种分配也是一次性的,即下一次执行该语句时,不会再次分配新的内存区域。第二,C++ Builder从Pascal的VCL中引入了AnsiString类型,这是一种指针类型。应注意绝大部分C++ Builder的函数中如果要求AnsiString作参数(不论是要求地址还是指针),都应提供字符串名称来作参数。典型的例子包括str.pos(str1),ShowMessage(str),以及上面的例子中出现的几种情况。

最后,所有C++ Builder程序员不应忘记VCL是用Pascal语言编写的。遇到一些难以解决或解释的问题时,不妨从Pascal语言的编程思想入手,一般很快就能想到解决办法(绝大部分Object Pascal函数都有对应的C语言版本)。这种方法对于学过Delphi的程序员来说是特别有用的。

以上只是对指针基本概念的简单论述,旨在让初学者能透彻地理解指针的概念。限于作者水平,不妥之处在所难免,请各位读者不吝赐教。只有学习好指针的相关概念,才能真正发挥C语言的强大功能,编写出质量上乘的系统软件。而对于指针的使用技巧,只能在编程过程中日积月累,经历从量变到质变的过程。

c语言中指针链表 篇2

线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。所以,这种数据结构中存放数据元素的空间称为结点至少包括两个域,一个域存放该元素的值,称为数据域另一个域存放此线性表中下一个元素的结点在存储器中的地址,称为指针域或链域。这种链式分配的存储结构称为链表。链表中每个结点可包含若干个数据域和指针域,数据域用DATA表示,指针域用NEXT表示,下图为最简单的一种链表:

这种链表中的数据结构必须利用指针变量才能实现。指针是C语言中的一个重要概念,也是C语言的一个重要特色。下面探讨用指针处理链表的优越性和其进行插入操作的方法。

2 用指针处理的链表结构的优越性

在从优选择数据结构的存储方式时,应考虑这几点问题

应有利于基本运算的实现。因为运算的具体实现以存储结构的确定为前提,存储结构在一定程度上,一定范围内决定了运算的实现是否方便、高效应有利于数据的特性应有利于软件环境。具体而言,即应主要从存储空间、运算时间和程序设计语言三方面考虑。

1)存储空间用指针处理的链表存储结构的存处空间是动态分配,只要内存有空间,就可动态申请内存空间,不会产生溢出。

2)运算时间用指针处理的链表存储结构进行元素的插入或删除,只需修改指针并结合一定的查找。对那些需要经常进行元素的插入或删除的线性表,其存储结构应采用链表存储结构。

3)程序设计语言只要是提供了指针类型的高级语言,用链表进行数据结构的操作都很方便。

3 用指针处理的链表进行播入操作的方法

无论对链表作何处理,首先就要建立链表,为了得到结果,要有链表的输出过程。用语言程序描述时,这些过程都被定义为一个个子函数,因此,在编制主函数时需要调用这些子函数。

用指针处理的链表实现线性表插入的方法链表实现线性表插入的位置可能有以下四种情况:1)原来的链表是空表,则插入结点作为表头结点;2)在链表中第一个结点之前插入,则插入结点作为表头结点;3)在链表中间某个位置插入,则要维护插入位置前后结点的指针域;4)如果链表中根本不存在所指定的结点,则将新结点在链表尾插入。

4 时间复杂性的分析

对于长度为n的线性表,以上算法的运算时间主要花费在查找数据域值为的y结点上。设在线性表各位置上播人的概率相等则查找的平均比较次数为(1+2+3+n)/n=(n+1)/2而每次的比较时间是一个常数,所以此擂入算法的时间复杂性为O(n);

参考文献

[1]谭浩强.C程序设计(二级教程)[M].北京:清华大学出版社,2000:15-18.

[2]唐策善.数据结构一用C语言描述[M].北京:高等教育出版社,1995:22-24.

C语言中指针链表的学习探讨 篇3

关键词:动态;链表

中图分类号:TP311.12

C语言中存储数据的结构用的最普遍的是数组,包括简单类型的数组,指针数据和结构体数组等,但是他们在实际应用中,会因为实现定义过大的数组容量而造成内存的浪费,或者因为保守的预测分配而满足不了实际使用的要求,这时就需要另一种方法来解决这个问题,这就是动态数据结构和动态分配内存技术。链表就是这样一种最简单的动态数据结构。那么如何让学生能够很好的学习并掌握它,本人就近几年的教学过程中经过探讨,采用了图示法进行教学,效果很好。

1 基本概念的理解

1.1 指针的理解

(1)指针与简单变量。通过图1所示理解指针与简单变量的关系,当把变量i的地址存入指针变量p1后,就可以说这个指针指向了该变量。

如需要指向下一个元素,只需要指针往后移动即可!

1.2 结构体的理解

(1)结构体类型。结构体类型是一种专门组织和处理复杂关系的数据结构,是一种自定义类型。同一个数据对象由于应用不同定义的类型也有所不同。比如处理学生的信息,可以有很多种方式:

结构体中的成员名可增,可减,形成新的结构体类型。

(2)结构体变量与数组。以上是结构体类型的定义,变量定义方式有三种,这里不一一举例,可根据自己的个人习惯选择不同的定义方式。比如上面两个类型,分别定义简单变量,可这样定义:

struct student s1,s2; struct stu s3,s4;

如定义数组,结合前面所学数组的知识,可这样定义:

struct student s[10]; struct stu s1[20];

2 指针链表

掌握了前面指针和结构体的知识内容,对于指针链表的理解和掌握是非常重要的。

2.1 指针链表的定义

这里主要讲解单项链表结点的结构体类型的定义,对于初学者掌握了这种定义,对于以后学习更为复杂的链表知识的理解是很有帮助的。

单项链表结点的定义:

struct node

{

int data; //存放的数据,可以定义其他更为复杂的数据结构

struct node *next; //指向struct node类型数据,用此建立链表

};

2.2 指针链表的建立

定义好类型后,再定义变量,并将链表建立起来,形成整数数据按升序建立的数据链,下面通过函数create来实现链表的建立。

struct node *create()

{

struct node *head,*p,*q,num;

head=NULL;

scanf("%d",#);

while(num!=0)

{

p=(struct node *)malloc(sizeof(struct node ));

if(p==NULL)

{

printf("Allocation failure\n");

exit(0);

}

p->data=num;

p->next=NULL;

if(head==NULL)

head=p;

else

q->next=p;

q=p;

}

return head;

}

2.3 指针链表的插入

链表建立完成后,最常用的操作之一就是对链表的数据进行增加,也就是插入操作,具体程序通过insert函数实现。

struct node*insert(struct node*head,sturct node*r,int*x)

{

struct nod *p,*q;

if(head==NULL)

{

head=r;

r->next=NULL;

}

else

{

p=head;

while(*x>p->data&&p-;>next!=NULL)

{

q=p;

p=p->next;

}

if(*xdata)

{

if(p==head)

head=r;

else

q->next=r;

p->next=p;

}

else

if(p==NULL)

{

p->next=r;

r->next=NULL;

}

return head;

}

2.4 指针链表的删除

对于链表中数据的删除也是链表数据中操作的重点,具体实现过程通过函数deletenode实现。

struct node*deletenode(struct node*head,int*x)

{

struct nod*p,*q;

if(head==NULL)

{

printf("This is a empty list.");

return head;

}

p=head;

while(*x!=p->data&&p-;>next!=NULL)

{

q=p;

p=p->next;

}

if(*x==p->data)

{

if(p==head)

head=p->next;

else

q->next=p->next;

free(p);

}

else

printf("NOT FOUND");

return head;

}

3总结

单向结点链表的主要操作就是建立,插入和删除数据,而且是链表当中最简单的一种形式,只有理解和掌握单向结点链表的基本操作,才有可能处理更为复杂的数据对象,在课堂上通过以上三个函数的编写与引导,学生对于链表有了初步的认识,并起到了良好的效果。

参考文献

[1]杜友福.C语言程序设计[M].北京:科学出版社,2012.

[2]龚民,朱秀兰.C语言程序设计教学探讨[J].电脑知识与技术,2009.

作者简介:刘山根(1976.8-),男,籍贯:河南新乡,职务:广东省华侨职业技术学校教务科副科长。

C语言:链表的建立、插入和删除 篇4

难于统一。我们只能够根据可能的最大需求来定义数组,经常会造成一定存储空间的浪费。

我们希望构造动态的数组,随时可以调整数组的大小,以满足不同问题的需要。链表就是我们需要的动态数组。它是在程序的执行过程中根据需要有数据存储就向系统要求申请存储空间,决不构成对存储区的浪费。

链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链表、双向链表,下面将逐一介绍。

7.4.1 单链表

图7 - 3是单链表的结构。

单链表有一个头节点h e a d,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为N U L L。

图7 - 3还给出这样一层含义,链表中的各节点在内存的存储地址不是连续的,其各节点的地址是在需要时向系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可以跳跃式分配地址。

看一下链表节点的数据结构定义:

struct node

{

int num;

struct node *p;

} ;

在链表节点的定义中,除一个整型的成员外,成员p是指向与节点类型完全相同的指针。

在链表节点的数据结构中,非常非凡的一点就是结构体内的指针域的数据类型使用了未定义成功的数据类型。这是在C中唯一规定可以先使用后定义的数据结构。

• 单链表的创建过程有以下几步:

1 ) 定义链表的数据结构。

2 ) 创建一个空表。

3 ) 利用m a l l o c ( )函数向系统申请分配一个节点。

4 ) 将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新

节点接到表尾。

5 ) 判定一下是否有后续节点要接入链表,若有转到3 ),否则结束。

• 单链表的输出过程有以下几步

1) 找到表头。

2) 若是非空表,输出节点的值成员,是空表则退出。

3 ) 跟踪链表的增长,即找到下一个节点的地址。

4) 转到2 )。

[例7-5] 创建一个存放正整数(输入- 9 9 9做结束标志)的单链表,并打印输出。

#include /包*含ma l l o c ( ) 的头文件*/

#include

struct node /*链表节点的结构* /

{

int num;

struct node *next;

} ;

m a i n ( )

{

struct node *creat(); / *函数声明* /

void print();

struct node *head; / * 定义头指针* /

head=NULL;/*建一个空表*/

head=creat(head);/*创建单链表*/

print(head);/*打印单链表*/

}

/******************************************/

struct node*creat(structnode*head)函/数*返回的是与节点相同类型的指针*/

{

struct node*p1,*p2;

p1=p2=(structnode*)malloc(sizeof(structnode));申请/*新节点*/

scanf(“%d”,&p1->num);/*输入节点的值*/

p1->next=NULL;/*将新节点的指针置为空*/

while(p1->num>0)/*输入节点的数值大于0*/

{

if(head==NULL)head=p1;/*空表,接入表头*/

elsep2->next=p1;/*非空表,接到表尾*/

p2=p1;

p1=(structnode*)malloc(sizeof(structnode));申/请*下一个新节点*/

scanf(“%d”,&p1->num);/*输入节点的值*/

}

return head;/*返回链表的头指针*/

}

/*******************************************/

void print(struct node*head)输/*出以head为头的链表各节点的值*/

{

struct node *temp;

temp=head;/*取得链表的头指针*/

while(temp!=NULL)/*只要是非空表*/

{

printf(“%6d”,temp->num);/*输出链表节点的值*/

temp=temp->next;/*跟踪链表增长*/

}

}

在链表的创建过程中,链表的头指针是非常重要的参数,

因为对链表的输出和查找都要从链表的头开始,所以链表创建成功后,要返回一个链表头节点的地址,即头指针。

- 链表的建立、插入和删除

7.4.2 单链表的插入与删除

在链表这种非凡的数据结构中,链表的长短需要根据具体情况来设定,当需要保存数据时向系统申请存储空间,并将数据接入链表中。对链表而言,表中的数据可以依此接到表尾或连结到表头,也可以视情况插入表中;对不再需要的数据,将其从表中删除并释放其所占空间,但不能破坏链表的结构。这就是下面将介绍的链表的插入与删除。

1. 链表的删除

在链表中删除一个节点,用图7 - 4描述如下:

[例7-6] 创建一个学生学号及姓名的单链表,即节点包括学生学号、姓名及指向下一个节点的指针,链表按学生的学号排列。再从键盘输入某一学生姓名,将其从链表中删除。

首先定义链表的结构:

struct

从图7 - 4中看到,从链表中删除一个节点有三种情况,即删除链表头节点、删除链表的中

间节点、删除链表的尾节点。题目给出的是学生姓名,则应在链表中从头到尾依此查找各节

点,并与各节点的学生姓名比较,若相同,则查找成功,否则,找不到节点。由于删除的节

点可能在链表的头,会对链表的头指针造成丢失,所以定义删除节点的函数的返回值定义为

C语言数据结构与指针 篇5

学院:

班级:

学号:

姓名:

实验四

(一)实验名称:C语言数据结构与指针

(二)实验目的:巩固复习前期所学C语言的函数参数传递、指针和结构体等知识点,加强学习数据结构语言基础。

(三)实验内容:

1)学生信息的显示,具体要求如下:

定义一个结构体描述学生信息(学号,姓名,性别,年龄,住址);

设计一个函数,用于显示单个学生信息,函数的参数为前面定义的结构体类型;

设计一个主函数,在主函数中输入学生的信息,并调用前面定义的函数进行显示(学生人数不少于5人)。

2)输入若干个整数作为数组元素值,然后按输入时顺序的就地逆置排序,最后打印出逆置后的元素值。要求用指针和动态内存分配方法实现。例如 输入:10 2 30 4 5,逆置后显示为:5 4 30 2 10。

(四)源代码:

#define MAXSIZE 100

#include #include typedef int ElemType;typedef struct {

ElemType data[MAXSIZE];int length;

} SqList;SqList l;

void InitList(SqList &L)

{

L.length = 0;} void CreatSqlist(SqList &L,int n)

{

printf(“请输入节点”);int i;for(i=0;i

} void Output(SqList &L)

{ int i;for(i=0;i

printf(“n”);} int chazhao(SqList &L,int x){ int i,k;printf(“n请输入你要查找的元素 x=?”);scanf(“%d”,&x);for(i=0;i<=(L.length+1);i++){

if(x==L.data[i])

{printf(“要查找的元素%d位于线性表第%d位上nn”,x,i+1);

k=0;

break;

} } if(k!=0)printf(“所要查找的元素%d不在线性表中”,x);return 0;} int GET(SqList &L,int i){ int m;if((i<0)||(i>L.length)){printf(“所查找范围超出线性表长度”);return 1;} else if((i>=1)&&(i<=L.length)){

m=L.data[i-1];}printf(“%d ”,m);return 0;} int DELETE(SqList &L,int i){ int j;if(i<1||i>L.length){printf(“删除错误”);return 0;} else {

for(j=i;j

L.data[j-1]=L.data[j];

L.length--;

} return 1;} int INSERT(SqList &L,int x,int i){ int j;if(L.length>=MAXSIZE-1){printf(“over flow”);return 1;} else if((i<1)||(i>L.length+1)){printf(“插入错误”);return 1;} else

{for(j=L.length;j>=i-1;j--)L.data[j+1]=L.data[j];L.data[i-1]=x;L.length=L.length+1;} return 0;} int main(){int n,i,k,x;InitList(l);printf(“请输入线性表的长度 ”);scanf(“%d”,&n);CreatSqlist(l,n);Output(l);

printf(“请输入你要查找的数所在的节点位置”);scanf(“%d”,&i);GET(l,i);chazhao(l,x);printf(“请输入你要删除元素的位置=?”);scanf(“%d”,&k);DELETE(l,k);Output(l);printf(“请输入你要插入的数和位置x,i=?”);scanf(“%d,%d”,&x,&i);INSERT(l,x,i);Output(l);return 0;}

(五)代码运行结果:

(六)需求分析

1、输入的形式和输出值的范围:1)输入10个整数。2)输出整个顺序线性表。

2、输出的形式:完成各种功能后的线性表。

3、程序所能达到的功能:1)所存储顺序线性表的显示、元素的查找、删除和插入。

(七)所用到的函数:

void CreatSqlist void Output Int chazhao int GET int INSERT int DELETE

(八)心得体会:

C语言程序设计教案 第十章指针 篇6

课题:

教学目的: 教学重点: 教学难点: 第十章 指针

§1-§2

1、了解指针与地址的概念

2、掌握指针变量的定义、初始化及指针的运算 指针变量的定义、初始化及指针的运算

指针的运算

步骤一 复习引导

指针是C语言的一个重要概念,也是C语言的一个重要特色。正确而灵活地运用它,可以有效地表示复杂的数据结构;能动态分配内存;能方便地使用字符串;有效而方便地使用数组;在调用函数时能得到多于1个的值;能直接处理内存地址等,这对设计系统软件是很必要的。

指针的概念比较复杂,使用也比较灵活,因此初学者时常会出错,务请在学习本章内容时十分小心。

步骤二 讲授新课

§10.1 地址和指针的概念

计算机的主存储器被分成一个个存储单元,为了区分各存储单元,要为每个存储单元编号,这个编号即地址。

例:i =3;

或 scanf(“%d”, &i);

是将3送给 i所在的空间。

例:将3送到变量I_pointer所“指向”的单元(即I所标志的单元)。

所谓“指向”,是通过地址来体现的,I_pointer中的值为2000,它是变量I 的地址,这样就在I_pointer和变量之间建立起一种联系,即通过I_pointer能知道I的地址,从而找到变量I的内存单元。因而在C语言中,将地址形象化地称为“指针”。

意思是通过它能找到以它为地址的内存单元。一个变量的地址称为该变量的“指针”。

内存单元的地址和内存单元的内容是两个不同的概念。

指针:就是地址,即内存单元的编号。

指针变量:用来存放另一变量的地址(即指针)的变量。

如:地址2000是变量 i的指针;i_pointer是指针变量,其值就是指针2000。

§10.2变量的指针和指向变量的指针变量

变量的指针就是变量的地址。

存放变量地址的变量是指针变量,用来指向另一个变量。

*i_pointer 表示 i_pointer 所指向的变量。

一、定义一个指针变量

指针变量的定义包含三个方面的内容:

⑴ 指针类型说明,即定义变量为一个指针变量 ⑵ 指针变量名

⑶ 变量值(指针)所指向的变量的数据类型。格式:

存储类型

基类型

*指针变量名;例:int *pointer_1, *pointer_2;

float *pointer_3;

char *pointer_4;

二、指针的引用

指针变量有两个运算符: & :取地址运算符

功能:取变量地址;单目,右结合。

* :取内容运算符(“间接访问”运算符)

功能:只能跟地址,取变量所指向单元的内容;单目,右结合。

例:&a为变量a的地址,*p 为指针变量p所指向的存储单元。

例:int a=5, *p=&a;

printf(“%d”, *p);main(){ int a,b;

int *pointer_1,*pointer_2;

a=100;b=10;

pointer_1=&a;

/*把变量a的地址赋给pointer_1*/

pointer_2=&b;

/*把变量b的地址赋给pointer_2*/

printf(“%d,%dn”,a,b);

printf(“%d,%dn”,*pointer_1, *pointer_2);} 输出结果:100, 10

100, 10

评注:

1、在第3行虽然定义了两个指针变量,只是提供了两个指针变量,但并未指向任何一个整型变量。称为指针“悬空”。

2、最后一行的*pointer_1和pointer_2就是变量a和b。

3、程序中两处出现*pointer_1等,含义不同。程序第3行中的*pointer_1表示定义指针变量pointer_1。它前面的*只是表示该变量是指针变量。程序最后一行中的*pointer_1则代表变量,即pointer_1所指向的变量。

4、第5行中的pointer_1=&a 是将a的地址赋给指针变量pointer_1,而不是*pointer_1。

注意:不应写成:*pointer_1=&a;

5、从上例中可看出,*pointer_1等价于a,*pointer_2等价于b,故凡在程序中出现a的地方均可用 *pointer_1 代替。所以,若有: int x, y, *px=&x;则下面的运算均是正确的: y=*px+1;

/*把 x 的内容加1 送变量y*/ printf(“%dn”, px);

/*打印当前x 的内容*/ d=sqrt((double)px);

/*把x的平方根送变量d*/ px=0;

/*把 x 置为0*/ *px+=1;

/*把 x 的值加 1*/(*px)++;

/*使 x 的值加 1*/ y=(*px)++;

/*即y=x, x++*/

6、假设px和py都被定义为指向int 对象的指针,而且px当前已指向了int 型变量x,则执行:

py=px;

/*把指针px的内容拷贝到指针py中去*/ 即

py和px 这两个不同的指针指向了同一对象x

7、指针不是整数,不能将它与整数混为一谈。例:

# include main(){ int x=100,*p;p=x;printf(“%d”, p);}

例如:

# include main(){ int a, b, *d=&a;

b = d;

printf(“%d n”, b);

……

}——编译不出错,但得不到希望的值

关于&和*运算符的说明:

假设已执行

pointer_1=&a;

1、&*pointer_1含义是什么?

&*pointer_1与&a相同,即变量a的地址。

2、*&a的含义是什么?

先进行&a运算,得a的地址,再进行*运算。

*&a、*pointer_1及变量a等价。

3、(*pointer_1)+ + 相当于a + +。

它与*pointer_1 + + 不同。

4、*pointer_1 + + 等价于*(pointer_1 + +),即先进行*运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。

例10.2:输入a和b两个整数,按先大后小的顺序输出a和b。

main(){ int *p1,*p2,*p, a, b;

scanf(“%d,%d”,&a,&b);

p1=&a;p2=&b;

if(a

{p=p1;p1=p2;p2=p;}

printf(“n a=%d,b=%dn”,a,b);

printf(“max=%d,min=%dn”,*p1,*p2);

} 运行情况: 5,9 a=5,b=9 max=9,min=5

三、指针变量作为函数参数

例10.3对输入的两个整数按大小顺序输出。先考察如下程序,看是否能得到预期的结果

swap(int p1, int p2){ int temp;temp = p1;p1 = p2;p2 =temp;} main(){ int a, b;

scanf(“%d, %d”, &a, &b);

if(a

printf(“n%d,%dn”,a,b);} 不能得到预期的结果。改为:

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=&a;

pointer_2=&b;

if(a

swap(pointer_1,pointer_2);

printf(“n%d,%dn”,a,b);}

注:如果想通过函数调用得到n个改变的值,可以:

1、在主调函数中设n 个变量,用n个指针变量指向它们;

2、然后将指针变量作实参,将这n 个变量的地址传给所调用的函数的形参;

3、通过形参指针变量,改变该n个变量的值;

4、主调函数中就可以使用这些改变了值的变量。

四、指针(变量)的初始化 指针置初始化值的格式:

存储类型

基类型

*指针名=初始化值; 如:main()

{ static int a;

int *p=&a, *p1=p;

…… } 再如: int *p = 0;

int *p = NULL;

五、指针的运算

1、指针的算术运算

指针仅能进行加、减算术运算

如:p+n , p-n , p++ , p--, ++p ,--p

p-= n , p+= n , p1-p2 等 其中n是整数,p、p1、p2均为指针;

施行加法运算时,指针向地址增大的方向移动; 施行减法运算时,指针向地址减小的方向移动; 移动长度取决于指针的基类型,由计算机决定; 如有:int a,b,c, *pt =&a;

pt++ 则指针向后移动两个字节; 再如:main()

{ int *p1,a=8,b=3;

p1=&a;

printf(“%d,%dn”,(*p1)++, *p1++);

printf(“%d,%dn”,a, *p1);

} 运行结果:3,8,4 注:p1+k = p1+k*sizeof(p1的基类型);

p1-k = p1-k*sizeof(p1的基类型);如:

strlen(char *s)

{ char *p=s;

while(*p!=„‟)p++;

return(p-s);

}

2、指针的关系运算

设指针p1,p2指向同一数组中的元素,则

p1

p1

p1= =p2:

表示p1、p2指向数组中的同一元素;

同理可推出>、>=、<=、!=比较的意义; 不可将指针与其他类型的对象作比较;

若两指针指向不同数组中的元素,也不可比较;

允许将指针与NULL或数值0进行= =或!=的比较,以便判定一个指针是否为空指针。

步骤三 课堂小结

本课介绍了指针与地址的概念,指针变量的定义、初始化及指针的运算。

指针:就是地址,即内存单元的编号。

指针变量:用来存放另一变量的地址(即指针)的变量。

例如:int a=5, *p=&a;

printf(“%d”, *p);

注意:运算符*和&的用法,指针变量的自加自减运算。步骤四 布置作业

课后作业:第十章课后练习10.1 10.2 课题:

教学目的: 教学重点: 教学难点: 第十章 指针

§3 掌握指针与数组的知识 指向数组的指针变量

指向二维数组的指针

步骤一 复习引导

上节课介绍了指针变量的定义及其赋值。一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组和数组元素(把数组起始地址或某一元素的地址放到一个指针变量中)。

步骤二 讲授新课

所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。

引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。

一、指向一维数组的指针

定义形式:

int a[10];

int *p;

p=&a[0];

p=a;

含义:把数组的首地址赋给指针变量p。

也即: int *p=&a[0];

int *p=a;

二、通过指针引用数组元素

按C的规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一个数组中的下一个元素(而不是简单地加1)。

如果p的初值为&a[0],则:

p+i  a+i  &a[i],即指向a数组的第i个元素。

*(p+i) *(a+i)

a[i]。

指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价 引用数组元素时,可以用:

1、下标法,如:a[i]

2、指针法,如:*(a+i)

*(p+i)

其中,a是数组名,p是指向数组的指针

例10.5:输出数组中的全部元素

假设有一个a数组,整型,有10个元素。用三种方法输出各元素的值:

1、下标法: main(){ int a[10], i;

for(i=0;i<10;i++)

scanf(“%d”,&a[i]);

printf(“n”);

for(i=0;i<10;i++)

printf(“%d”,a[i]);}

2、利用数组名计算数组元素地址,找出元素的值。main(){ int a[10], i;

for(i=0;i<10;i++)

scanf(“%d”,&a[i]);

printf(“n”);

for(i=0;i<10;i++)

printf(“%d”,*(a+i));}

3、用指针变量指向数组元素。main(){ int a[10], *p, i;

for(i=0;i<10;i++)

scanf(“%d”,&a[i]);

printf(“n”);

for(p=a;p<(a+10);p++)

printf(“%d”,*p);} 评注:

1、第1和2种方法执行效率是相同的。

2、第3种方法效率高。

3、用下标法比较直观。

在使用指针变量时,有几个问题要注意:

1、指针变量可以实现使本身的值改变。如:for(p=a;p<(a+10);p++)

2、要注意指针变量的当前值。

如:要通过指针变量输出a数组的10个元素。main(){ int a[10], i , *p=a;

for(i=0;i<10;i++)

scanf(“%d”, p++);

printf(“n”);

for(i=0;i<10;i++,p++)

printf(“%d”, *p);} 这个程序输出的并不是a数组中各元素的值。因为第一个 for 循环结束时,p已经指向数组的末尾。再继续下去,p指向的是a数组下面的10个元素,是不可预料的。可加一句为:p=a;

3、用指针变量p指向数组元素,可以指到数组以后的内存单元。C编译程序不作下标越界检查。

4、注意指针变量的运算。如果p指向数组a,⑴ p++(或 p+=1),使p指向下一元素a[1]。⑵ *p++

等价 *(p++)。作用是先得到p指向的变量的值(即*p),然后再使p+1→p。⑶ *(p++)与*(++p)不同。前者为a[0],后者为a[1] ⑷(*p)++表示p指向的元素值加1,即(a[0])++ ⑸ 如果p当前指向a数组中第i个元素,则:

*(p--)相当于a[i--],先对p进行*运算,再使p自减; *(+ + p)相当于a[+ +i],先使p自加,再作*运算。*(-){ temp=*i;*i = *j;*j =temp;} return;} main(){ int i, a[10],*p=a;printf(“The original array: n ”);for(i=0;i<10;i++,p++)scanf(“%d”, p);printf(“n”);p=a;inv(p,10);printf(“The array is : n”);for(p=a;p

例10.10:用选择法对10个整数排序。main(){int *p, i, a[10];p=a;for(i=0;i<10;i++)scanf(“%d”,p++);p=a;sort(p,10);for(p=a, i=0;i<10;i++){ printf(“%d”,*p);p++;} } sort(int x[],int n){ int i, j, k, t;for(i=0;ix[k])k=j;if(k!=i){ t = x[i];x[i] = x[k];x[k] = t;} }

四、指向二维数组的指针和指针变量

1、二维数组的地址如:

int a[3][4] A

a[0]

a[0] [0] a[0] [1] a[0] [2] a[0] [3]

A+1

a[1]

a[1] [0] a[1] [1] a[1] [2] a[1] [3]

A+2

a[2]

a[2] [0] a[2] [1] a[2] [2] a[2] [3] 例:int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

设首地址为2000。

 a[k]+p(0<=k< i , 0<=p

 a+k与&a[k][0]等价,而a[0]+k与&a[0][k]等价;  *(a+i)可理解为a[i],故有如下等价关系:

a+0

等价于 a[0]、a[0]+0、&a[0][0]、*(a+0)

a+1

等价于 a[1]、a[1]+0、&a[1][0]、*(a+1)

a+(i-1)等价于 a[i-1]、a[i-1]+0、&a[i-1][0]、*(a+i-1)

 *(a+k)与*a[k]是不同的,前者相当于a[k],是一个地址值;后者相当于*(a[k]+0)或*&a[k][0],是数组元素a[k][0]中存储的值。 数组元素a[k][p]就是*(a[k]+p),即*(*(a+k)+p)

步骤三 课堂小结

本课介绍了指向数组的指针,主要是指向一维数组的指针。用指针变量p指向数组a,指针变量p可以++、--,表示指向数组的上一元素或下一元素。但C编译程序不作下标越界检查。使用指针既方便有灵活,但初学者容易搞错。

步骤四 布置作业

课后作业:第十章课后练习10.31 10.5 课题:

教学目的: 教学重点: 教学难点: 第十章 指针

§3-§4 在掌握指针与数组的基础上,掌握字符串的指针与指向字符串的指针变量 指向字符串的指针变量

用指针处理字符串

步骤一 复习引导

上节课介绍了指向一维数组的指针及二维数组的地址,指向一维数组的指针也即指向元素的指针,那么指向二维数组的指针是怎样的呢?它有两种类型。

步骤二 讲授新课

2、指向二维数组的指针变量

——(1)指向数组元素的指针变量

设指针变量p=a[0]

(&a[0][0]、a、a+0、*(a+0))计算a[i][j]在n*m数组中的位置:

例:……

for(i=0;i<3;i++)

{for(j=0;j<4;j++)printf(“%4d”, *(p+i*m+j));

printf(“n”);}

……

例:用指针变量输出数组元素的值。main(){ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

int *p;

for(p=a[0];p

{if((p-a[0])%4 == 0)printf(“n”);

printf(“%4d”, *p);

} } 计算a[i][j]在n*m数组中的位置:a[0]+i*m+j

——(2)行指针(指向由m个元素组成的一维数组的指针变量)

定义:指针基类型

(*指针名)[m]

其中,m表示二维数组每行有m列;

如: int(*p)[3];

于是,p

指向第0行元素的起始存储地址;

p+1

指向第1行元素的起始存储地址;……

p+I

指向第i 行元素的起始存储地址;

而每一行的元素的表示形式为:

第0行元素的表示为(*p)[j];

第1行元素的表示为(*(p+1))[j];……

第i 行元素的表示为(*(p+ I))[j];

例 :

#include main(){ int(*p)[4], j;

int a[4][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};for(p=a;p

for(j=0;j<4;j++)

if(j==3)printf(“%4dn”,(*p)[j]);

else printf(“%4d” ,(*p)[j]);

} 改写为:

#include main(){ int(*p)[4], i, j;

int a[4][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};

p=a;

for(i=0;i<4;i++)

for(j=0;j<4;j++)

printf(j==3?“%4dn”: “%4d” , *(*(p+i)+j));

}

3、多维数组的指针作函数参数

一维数组的地址可以作为函数参数传递,多维数组的地址也可作函数参数传递。在用指针变量作形参以接受实参数组名传递来的地址时,有两种方法:

1、用指向变量的指针变量;

2、用指向一维数组的指针变量。

例;有一个班,3个学生,各学4门课,计算总平均分数,以及第n个学生的成绩。main(){ void average(float *p ,int n);

void search(float(*p)[4], int n);

float score[3][4]={{65,67,70,60},{80,87,90,81}, {90,99,100,98}};

average(*score, 12);

/*求12个分数的平均分*/

search(score , 2);

/*求第2个学生成绩*/ } void average(float *p , int n){float *p_end;

float sum= 0,aver;

p_end = p+n-1;

for(;p<=p_end;p++)sum =sum+(*p);aver = sum/n;printf(“average=%5.2fn”,aver);} void search(float(*p)[4], int n){ int i;printf(“the score of No %d are:n”,n);for(i=0;i<4;i++)printf(“%5.2f”, *(*(p+n)+i));}

例10.15 在上题基础上,查找一门以上课程不及格的学生,打印出他们的全部课程的成绩。main(){ void search(float(*p)[4], int n);float score[3][4]={{65,57,70,60},{58,87,90,81),{90,99,100,98}};search(score, 3);} void search(float(*p)[4], int n){int i, j, flag;for(j=0;j

§10.4字符串的指针和指向字符串的指针变量

一、字符串的表示形式

1、用字符数组存放一个字符串。如:main()

{ char string[ ]=“I love China!”;

printf(“%sn”, string);}

2、用字符指针指向一个字符串。

如:main()

{ char *string=“I love China!”;

printf(“%sn”, string);} 例10.18 将字符串a复制为字符串b。main(){ char a[]=“I am a boy.” , b[20];

int i;

for(i=0;*(a+i)!=„‟;i++)*(b+i)= *(a+i);

*(b+i)= „‟;

printf(“string a is : %sn”, a);

printf(“string b is :”);

for(i=0;b[i]!=„‟;i++)

printf(“%c”, b[i]);

printf(“n”);}

例10.19 用指针变量来处理上例。main(){ char a[ ]=“I am a boy.” , b[20], *p1, *p2;

int i;

p1= a;p2= b;

for(;*p1!=„‟;p1++,p2++)*p2 = *p1;

*p2 = „‟;

printf(“string a is : %sn”, a);

printf(“string b is :”);

for(i=0;b[i]!=„‟;i++)

printf(“%c”, b[i]);

printf(“n”);}

二、字符串指针作函数参数

例10.20:用函数调用实现字符串的复制。——(1)用字符数组作参数。

void copy_string(char from[],char to[]){ int i=0;

while(from[i]!=„‟)

{ to[i] = from[i];i++;}

to[i]=„‟;

} main(){ char a[]=“I am a teach.”;

char b[]=“you are a student.”;

printf(“string a= %snstring b=%sn”,a,b);

copy_string(a,b);

printf(“nstring a=%snstring b=%sn”,a,b);} ——(2)形参用字符指针变量 void copy_string(char *from,char *to){ for(;* from!=„‟;from++, to++)

*to = *from;

*to=„‟;

} ——(3)对copy_string函数还可以简化。① void copy_string(char *from, char *to)

{ while((*to =* from)!=„‟)

{ to ++;from++;}

} ② void copy_string(char *from, char *to)

{ while((*to ++ =* from++)!=„‟);}

③void copy_string(char *from, char *to)

{ while(* from!=„‟)

*to++ = *from ++;

*to = „‟;

} ④void copy_string(char *from, char *to)

{ while(*to++ = *from ++);}

⑤void copy_string(char *from, char *to)

{ for(;*to++ = *from ++;);}

⑥void copy_string(char from[], char to[])

{ char *p1,*p2;

p1=from;p2=to;

while((*p2++ = *p1++)!=„‟);

}

三、字符指针变量和字符数组的讨论

1、字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址,决不是将字符串放到字符指针变量中。

2、赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值;

char str[14];

str=“I love China.”;对字符指针变量,可以采用下面方法赋值:

char *a;

a= “I love China.”;

/*赋给a的是串的首地址*/

3、对字符指针变量赋初值:

char *a=“I love China.”;

等价于

char *a;

a=“I love China.”;

而对数组的初始化:

char str[14]={“I love China.”};不等价于

char str[14];

str[]=“I love China.”;

即数组可以在变量定义时整体赋初值,但不能在赋值语句中整体赋值。

4、如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个地址值,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋一个地址值,则它并未具体指向一个确定的字符数据。这很危险。

如: char str[10];

scanf(“%s”,str);是可以的 char *a;

scanf(“%s”,a);

能运行,但危险,不提倡,在a单元中是一个不可预料的值。

应当 char *a,str[10];a=str;scanf(“%s”,a);

5、指针变量的值是可以改变的,数组名虽然代表地址,但它的值是不能改变的。可以下标形式引用所指的字符串中的字符。

如:main()

{char *a=“I love China.”;

a=a+7;

printf(“%s”,a);}

又如:char str[]={“I love China.”};str=str+7;printf(“%s”,str);是错的

6、用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。也可以用字符数组实现,但由于不能采用赋值语句对数组整体赋值。

如:char *format;format=“a=%d,b=%fn”;printf(format,a,b);

等价于:printf(“a=%d,b=%fn”,a,b);

也可以:char format[ ]=“a=%d,b=%fn”;printf(format,a,b);

步骤三 课堂小结

本课介绍了指针与二维数组、指针与字符串,指向二维数组的指针有指向元素的指针和行指针,使用时应注意它们的区别。我们既要掌握用数组处理字符串,也要掌握用指针变量处理字符串。要区分这两种方法的不同之处。

步骤四 布置作业

课后作业:第十章课后练习10.61 10.7 课题: 第十章 指针

§5-§7 教学目的: 了解指针与函数的概念 教学重点:

教学难点: 指针数组,二级指针 掌握指针数组,二级指针等知识 掌握指针数组,二级指针等知识

步骤一 复习引导

前面介绍了指针与维数组、指针与字符串,我们可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。

步骤二 讲授新课

§10.5函数的指针和指向函数的指针变量

函数的地址:函数存储区域的首地址就是该函数的入口点,其函数名表示了入口地址。

一、函数指针变量的定义:

存储类型

数据类型

(*函数指针名)();

例:static int(*p)();例10.23 求a和b中的大者。

main()

{ int max(int, int);

int a,b,c;

scanf(“%d,%d”,&a,&b);

c=max(a,b);

printf(“a=%d,b=%d,max=%d”,a,b,c);

} max(int x, int y){ int z;

if(x>y)z=x;

else z=y;

return(z);} 法2:main()

{

int max(int, int);

int(*p)();

int a,b,c;

p=max;

/*将地址送入p */

scanf(“%d,%d”,&a,&b);

c=(*p)(a,b);

/*与max(a,b)等价*/

printf(“a=%d,b=%d,max=%d”,a,b,c);

} 注:int(*p)()定义p是一个指向函数的指针变量,此函数带回整型的返回值。

说明:

1、函数的调用可以通过函数名调用,也可以通过函数指针调用。

2、(*p)()表示定义一个指向函数的指针变量,它不是固定指向哪一个函数的,而只是表示定义了这样一个类型的变量,它是专门用来存放函数的入口地址的。

3、在给函数指针变量赋值时,只需给出函数名而不必给出参数,如:p=max。

4、用函数指针变量调用函数时,只需将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。如:c=(*p)(a,b);

5、对指向函数的指针变量,像p+n、p++、p--等运算是无意义的。

二、用指向函数的指针作函数参数

函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等。现介绍指向函数的指针也可以作为参数,以便实现函数地址的传递,也就是将函数名传给形参。

它的原理可以简述如下:有一个函数(假设函数为sub),它有两个形参(x1和x2),定义x1和x2为指向函数的指针变量。在调用函数sub时,实参用两个函数名f1和f2给形参传递函数地址。这样在函数sub中就可以调用f1和f2函数了。

有人会问,既然在sub函数中要调用f1和f2函数,为什么不直接调用f1和f2而用函数指针变量呢?的确,如果只是用到f1和f2,完全可以直接在sub函数中直接f1和f2,而不必设指针变量x1、x2。但是,如果在每次调用sub函数时,要调用的函数是不固定的,这次调用f1和f2,而下次要调用f3和f4,第三次要调用的名作为实参即可,sub函数不必作任何修改。这种方法是符合结构化程序设计方法原则的,是程序设计中常使用的。

例10.24 设一个函数process,在调用它的时候,每次实现不同的功能。

main(){ int max(int ,int);

int min(int , int);

int add(int , int);

int a, b;

printf(“enter a and b:”);

scanf(“%d,%d”, &a, &b);

printf(“max=”);process(a,b,max);

printf(“min=”);process(a,b, min);

printf(“sum=”);process(a,b, add);}

max(int x, int y)

{ int z;

if(x>y)z = x;else z = y;

return(z);

}

min(int x, int y)

{ int z;

if(x

else z = y;

return(z);} add(int x, int y){ int z;

z = x+y;

return(z);}

process(int x, int y, int(*fun)(int ,int)){int result;

result =(*fun)(x,y);

printf(“%dn” , result);}

在函数process定义中,int(*fun)(int,int)表示fun是指向函数的指针,该函数是一个整型函数,有两个整型形参。

在三次调用中,分别将函数名max、min、add作为实参将其入口地址送给process函数中的形参fun(fun是指向函数的指针变量)。例:process函数中的(*fun)(x,y)相当于max(x,y)。

注:在使用时,应将函数声明,这样编译系统将它按函数名处理(把函数入口地址作实参值),而不是作为变量名,不致出错。

Process函数无固定功能。如果max、min、add换成其它函数,此process函数不需改变,只要修改每函数的函数体。

§10.6 返回指针值的函数

一个函数可以带回一个整型值、字符值、实型值等,也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。格式: 类型名

*函数名(参数表);例: int

*a(int x, int y);

a是函数名,调用它以后能得到一个指向整型数据的指针(地址)。

例10.25 有若干个学生的成绩(每个学生有4门课程),要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。float *search(float(*pointer)[4], int n)

{float *pt;

pt = *(pointer +n);

return(pt);

} main(){ float score[][4]={{60,70,80,90},{56,89,67,88),{34,78,90,66}};

float *p;

int i, m;

printf(“enter the number of student:”);

scanf(“%d”,&m);

printf(“The score of No.%d are:n”, m);

p=search(score, m);

for(i=0;i<4;i++)

printf(“%5.2t”, *(p + i));

}

例10.26 对上例中的学生,找出其中有不及格课程的学生及其学生号。float *search(float(*pointer)[4]){ int i;

float *pt;

pt = *(pointer+1);

for(i=0;i<4;i++)

if(*(*pointer+i)<60)pt = *pointer;

return(pt);} main(){ score[][4]={{60,70,80,90},{56,89,67,88),{34,78,90,66}};

float *search(float(*pointer)[4], int n);

float *p;

int i, m;

for(i=0;i<3;i++){ p=search(score+i);

if(p==*(score + i))

{ printf(“ No.%d score:”, i);

for(j=0;j<4;j++)

printf(“%5.2t”, *(p + i));

printf(“n”);}

} }

关于函数的返回值是指针的情况,程序设计时应注意:

1、因数组名是地址常量,用于接受这种返值的对象不能是数组名,这与把数组名作为实在参数传递给形式参数的情况不同(作为形式参数的数组名总被视为指针)。

2、不应将局部于被调用函数的指针作为返值返回给调用者,理由是局部于被调用函数的数据对象执行返回语句离开被调用函数后,原来分配的被调用函数的所有局部对象的存储空间立即被收回(释放),虽然调用者已经获得了正确的地址值,但此时它指向的存储区域的内容可能已经发生了变化,或许已经分配给其他函数了。如果调用函数中仍然使用这个指针去存取那个区域中的数据,得到的可能并非原先的数据。对于这种情况的正确做法是应该把所处理的对象定义成全局对象或static型对象。

§10.7 指针数组和指向指针的指针

一、指针数组的概念

一个数组中的元素均为指针类型,称为指针数组。

形式:

存储类型

类型名

*数组名[数组长度]

例如:

static

int

*p[4]

定义指针数组时也可以进行初始化,如:

static char ch[][20]={“Beijing”,“Nanjing”,“Shanghai”,“Guangzhou”};

char *p[ ]={ch[0],ch[1],ch[2],ch[3]};该例也可以等价定义为:

char *p[ ]={“Beijing”,“Nanjing”,“Shanghai”,“Guangzhou”};

例如: main(){ int i, min, j;

char *temp, *p[ ]={“Beging”,“Nanjing”,“Shanghai”, “Guangzhou”};

for(i=0;i<3;i++)

{ min=i;

for(j=i+1;j<4;j++)

if(strcmp(p[j], p[min])<0)min=j;

temp=p[i];p[i]=p[min];p[min]=temp;

}

for(i=0;i<4;i++)

printf(“%sn”, p[i]);} 注意:不能把一个二维数组与一个指针数组混淆;

如:int a[10][10];与

int *b[10];的异同点  访问形式相同;如a[5][5],b[5][5];  占用的存储空间数不同;

 每一个b[i]必须置初值者能使用;  使用b优越

不需进行复杂的下标计算;

b[i]指向的数组并非一定要10个元素,但a中,每一行上的元素个数必须相同;因指针指向的是一个地址,故对b而言,各个b[i]指向的存储区域之间不必连续;而对a而言,必须存储100个连续的存储int型数据对象的区域。

例10.27 将若干字符串按字母顺序(由小到大)输出。main(){ void sort(char *name[], int n);

void print(char *name[], int n);

char *name[]={“Follow me”, “Basic”, “Great Wall”, “Fortran”, “Computer”};

int n=5;

sort(name, n);

print(name, n);

} void sort(char *name[], int n){ char *temp;

int i, j, k;

for(i=0;i

{ k=i;

for(j=i+1;j

if(strcmp(name[k], name[j])>0)k=j;if(k!=i)

{temp=name[i];

name[i]=name[k];

name[k]=temp;}

} } void print(char *name[], int n);{ int i;

for(i=0;i

}

二、指向指针的指针

在本章开头已提到“间接访问”变量的方式。利用指针变量访问另一个变量就是“间接访问”。

如果在一个指针变量中存放一个目标变量的地址,这就是“单级间址”。指向指针的指针用的是“二级间址”方法。从理论上讲,间址方法可以延伸到更多的级。但实际上在程序中很少有超过二级间址的。级数愈多,愈难理解,容易产生混乱,出错机会也多。

二级指针的定义:

char **p;

含义:表示指针变量p是指向一个字符指针变量(即指向字符型数据的指针变量)的。main(){ int i, min, j;

char *temp, *p[ ]={“Beging”,“Nanjing”,“Shanghai”, “Guangzhou”};

char **pp;

pp=p;

for(i=0;i<3;i++)

{ min=i;

for(j=i+1;j<4;j++)

if(strcmp(*(pp+j),*(pp+min))<0)min=j;

temp=*(pp+i);

*(pp+i)=*(pp+min);

*(pp+min)=temp;

}

for(i=0;i<4;i++)printf(“%sn”,*pp++);} 例10.28 使用指向指针的指针。main(){ char *name[]={“Follow me”, “Basic”, “Great Wall”, “Fortran”, “Computer”};

char **p;

int i;

for(i=0;i<5;i++)

{ p=name+i;

printf(“%sn”, *p);

}

} 例 main(){ static int a[5]={1,3,5,7,9};

int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};

int **p, i;

p= num;

for(i=0;i<5;i++)

{ printf(“%dt”, **p);

p++;

}

} 运行结果: 步骤三 课堂小结

本课介绍了指针数组、二级指针、指针与函数。要搞清它们的定义及应用; 注意区分:

char a[5];

char(*a)[5];

int

*p(int x);

int(*p)();

步骤四 布置作业

《C语言习题集》同步练习课题: 第十章 指针

§7-§8 教学目的: 了解指针数组作main函数的形参 教学重点:

教学难点: 指针的应用 掌握指针的应用 掌握指针的应用

步骤一 复习引导

上节课介绍了二级指针、指针数组,而指针数组的一个重要应用是作为main函数的形参。main()函数是我们C语言程序必不可少的,以往使用时main()是不带参数的。实际上是可带参数的,如:main(argc, argv)。

步骤二 讲授新课

三、指针数组作main函数的形参

带参数的main原型:

main(int argc, char *argv[ ])

{ ……

}

说明:

第1个参数是指命令行中参数的个数,含文件名本身。

第2个参数是一个指向字符串的指针数组。

main函数是由系统调用的。当处于操作命令状态下,输入main所在的文件名(经过编译、连接后得到的可执行文件名),系统就调用main函数。参数应和命令一起给出。命令形式:

命令名

参数1

参数2

……参数n 例如:有一个目标文件名file1,今想将两个字符串“China”, “Beijing”作为传送给main函数的参数。可写成:

file1 China Beijing

例:编写一程序echo.c,实现将命令行上除程序名之外的所有给出的其他参数都回显到显示器上。

main(int argc, int *argv[ ]){ while(argc>1){ ++argv;

printf(“%s”, *argv);

--argc;} }

若将该程序编译、连接、装配成echo.exe,则在命令行上输入:

echo hello, world! 则通过虚实结合后得:argc=3,argv[0]指向echo,argv[1]指向hello,argv[2]指向world!结果为:hello, world!

§10.8有关指针的数据类型和指针运算的小结

一、有关指针的数据类型的小结

见书中的表

二、指针运算小结

1、指针变量加(减)一个整数 例:p++、p--、p+i、p-=I等

2、指针变量赋值

将一个变量地址赋给一个指针变量。p=&a;p1=p2;

3、指针变量可以有空值,即该指针变量不指向任何变量。如 :

p=NULL;

4、两个指向同一数组元素的指针变量可以相减

5、两个指向同一数组的指针变量可作关系运算

习题举例:

习题10.4

有n个整数,使其前面各数顺序向右移m个位置,最后m个数变成最前面m个数。写一函数实现以上功能,在主函数中输入n个整数,并输出调整后的n个数。

main(){ int number[20],n,m,I;

printf(“How many number?”);

scanf(“%d”,&n);

printf(“Input %d number:n”,n);

for(i=0;i

scanf(“%d”,&number[i]);

printf(“How many place you want to move?”);

scanf(“%d”,&m);

move(number, n, m);

printf(“Now,they are:n”);

for(i=0;i

printf(“%d”, number[i]);} move(int array[20], int n, int m){ int *p,array_end;

array_end = *(array+n-1);

for(p= array+n-1;p>array;p--)

*p= *(p-1);

*array=array_end;

m--;

if(m>0)move(array, n, m);}

习题10.5 有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。程序: main(){ int i, k, m, n, num[50], *p;

printf(“Input number of person:n=”);

scanf(“%d”,&n);

p=num;

for(i=0;i

*(p+i)= i+1;

i=0;k=0;m=0;

while(m

{ if(*(p+i)!=0)k++;

if(k==3)

{ *(p+i)=0;k=0;m++;}

i++;

if(i= =n)i=0;

}

while(*p==0)p++;

printf(“The last one is NO.%dn”,*p);

}

习题10.6

写出一个函数,求一个字符串的长度。在main函数中输入字符串,并输出其长度。main(){ int len;

char *str[20];

printf(“Input string:”);

scanf(“%s”, str);

len=length(str);

printf(“The length of string is %d.”,len);}

length(char *p){ int n;

n=0;

while(*p!=„‟)

{ n++;

p++;

}

return(n);} 习题10.7

有一字符串,包含n个字符。写一个函数,将此字符串中从第m个字符开始的全部字符复制成为另一个字符串。main(){ int m;

char *str1[20],*str2[20];

printf(“Input string:”);

gets(str1);

printf(“Which character that begin to copy?”);

scanf(“%d”,&m);

if(strlen(str1)

printf(“Input error!”);

else { copystr(str1,str2,m);

printf(“result:%s”,str2);

} }

copystr(char *p1, char *p2, int m){ int n;

n=0;

while(n

{n++;

p1++;

}

while(*p1!=„‟)

{ *p2 = *p1;

p1++;p2++;

}

*p2=„‟;}

习题10.9 写一个函数,将3×3的矩阵转置。main(){ int a[3][3],*p, i;

printf(“Input matrix:n”);

for(i=0;i<3;i++)scanf(“%d %d %d”, &a[i][0], &a[i][1] , &a[i][2]);

p=&a[0][0];

move(p);

printf(“Now,matrix:n”);

for(i=0;i<3;i++)

printf(“%d %d %d”,&a[i][0], &a[i][1], &a[i][2]);

} move(int *pointer){ int i, j, t;

for(i=0;i<3;i++)

for(j=i;j<3;j++)

{ t= *(pointer+ 3*i + j);

*(pointer+ 3*i +j)= *(pointer+ 3*j + i);

*(pointer+3*j + i)=t;

}

}习题10.16

输入一个字符串,内有数字和非数字字符,如:

a123x456 17960?302tab5876 将其中连续的数字作为一个整数,依次存放到一数组a中。例如123放在a[0]中,456放在a[1]中……统计共有多少个整数,并输出这些数。#include main(){ char str[50], *pstr;

int i, j, k, m, e10, digit, ndigit, a[10], *pa;

printf(“Input a string:n”);

gets(str);

printf(“n”);

pstr=&str[0];

pa=&a[0];

ndigit = 0;i = 0;j=0;

while(*(pstr+i)!=„‟)

{ if((*(pstr+i)>=„0‟)&&(*(pstr+i)<=„9‟))j++;

else { if(j>0)

{ digit = *(pstr+i-1)-48;

k=1;

while(k

{ e10=1;

for(m=1;m<=k;m++)e10 =e10*10;

digit =digit+(*(pstr+i-k)-48)*e10;

k++;

}

*pa=digit;ndigit++;

pa++;

j=0;

}

}

i++;

}

printf(“There are %d numbers in this line.They are:n”,ndigit);

j=0;

pa=&a[0];

for(j=0;j

printf(“%d”, *(pa+j));

printf(“n”);} 步骤三 课堂小结

本课主要讲了一些习题,希望通过这些例子加深对指针的了解,看到指针在程序设计中的重要应用。同学们课后需要多看、多想、多练,逐步去体会指针这一重要的概念。步骤四 布置作业

C语言中的函数与指针 篇7

随着计算机技术的飞速发展及应用领域的扩大, 熟练掌握一门语言已变的尤为关键。C语言这门课程在计算机的基础教学中一直占有比较重要的地位, 然而要想突破C语言的学习, 对函数和指针的掌握是非常重要的, 本文将具体针对函数和指针的关系做详尽的介绍。

一、函数的有关概念

为了使程序的编写更加清晰、直观且易于修改, C语言中引用了函数。所谓函数, 就是一个程序模块, 该模块用来完成一个特定的程序功能。引用一个函数时, 需要包括对函数的定义、声明, 继而调用。在掌握函数相关概念的同时, 有以下几点需要注意:

(1) 调用函数和被调用函数

由上例可以看出, 函数A在执行的过程中包括了对函数B的调用, 则函数A称为调用函数 (调用函数B) , 而函数B被函数A调用, 称为被调用函数。

(2) 实参和形参

调用函数中定义的变量是实参, 被调用函数中定义的变量是形参。如上例, 函数A中的变量a是实参, 函数B中的变量b是形参。

(3) 实参变量和形参变量之间的独立性

实参变量和形参变量之间只存在值的传递过程, 实参变量的存储空间在调用函数中分配, 而形参变量的存储空间在被调用函数中分配, 被调用函数执行完毕后, 其所分配的存储空间被释放, 即形参变量的存储空间被释放, 它不会返回值给实参变量, 也不会参与调用函数的继续执行。例如 (实现两个数的交换) :

显然, 函数main是调用函数 (调用函数swap) , 函数swap是被调用函数。main函数中的a, b由main函数分配存储空间, 而swap函数中的a, b由swap函数分配存储空间。main函数执行到swap函数时, 调用swap函数, swap函数为其变量分配存储空间, 然后实现了swap函数中变量a, b的值交换, 执行完毕后即释放其分配变量的存储空间。继而, main函数继续执行, 但其变量a, b没有做任何改变, 即main函数不能实现a, b的交换。由上例可以看出, 若单纯的使用变量, 则被调用函数无法改变调用函数中的变量值, 即swap函数无法实现main函数中变量a, b的交换。

二、指针的有关概念

指针是C语言中功能最强大, 使用最广泛的一种数据类型, 主要用于描述存储单元的地址。通过使用指针, 可以在函数中进行传址调用。

(1) 指针变量的定义

定义指针变量的一般形式:类型标识符*变量名;其中, 变量名前的符号“*”表示将要定义的变量, 类型说明符表示该指针变量所指向数据的类型。例如:int*p1;char*p2;float*p3;

(2) 指针变量的引用

&为取地址运算符, 其一般形式为:&变量名, 例如:

int x=3, *p;p=&x;指针变量p指向变量x。

在使用x的值时, 可以直接使用x, 也可以用*p来代替使用x。此外, 指针变量一定是和它所对应的变量相互引用, 即指针变量在使用时一定要有明确的指向, 必须赋予具体的值, 否则将可能导致错误。

三、指针与函数的关系

在函数的编写过程中, 若单纯的只用变量参数, 则无法实现被调用函数改变调用函数中变量值的目的。而为了实现这一目的, 就需要函数和指针之间的结合使用。

(1) 引用指针, 可以实现调用函数和被调用函数中的指针变量共同指向调用函数中的存储单元, 从而实现被调用函数改变调用函数中变量值的目的。例如:

由上例可以看出, 在调用函数 (main函数) 中定义了变量a和指针变量p1, 被调用函数 (change函数) 中定义了指针变量p2。程序首先从main函数开始执行, 分配变量a和指针变量p1的存储单元, 此时指针变量p1指向变量a (p1=&a) 。当程序执行到change函数时, 程序跳转到change函数执行其函数体, change函数为其指针变量p2分配存储单元, 同时p2也得到了p1传过来的值 (变量a的地址) , 此时p2也指向了调用函数中的变量a, 即实现了p1和p2共同指向了调用函数中的存储单元 (变量a的存储单元) , change函数执行其函数体 (*p2=3) , 即使a的值变为3。change函数执行完毕后, 释放其变量的存储空间, 转而继续执行main函数, 此时a的值已经发生改变 (由2变为3) , 实现了被调用函数改变调用函数中变量值的目的。

(2) 调用函数和被调用函数中实参和形参之间的关系图

被调用函数执行完毕后, 释放它所分配的存储单元, 而调用函数分配的存储单元仍继续使用。此外, 只有当调用函数中传指针值 (即实参是指针值) , 而被调用函数中引用变量 (即形参收到指针值后, 在函数体内引用变量值) , 才能达到改变的目的。

例如, 实现变量a和b的交换, 程序如下:

例1虽然传的是指针值, 但在函数体的执行过程中引用的仍然是指针值 (引用x和y) , 所以不能实现a和b的交换;例2程序传指针值后, 引用变量 (*x和*y, 即a和b) , 所以能实现a和b的交换。

四、结束语

在以后的编程过程中, 若遇到想通过被调用函数改变调用函数中变量值的目的, 则可以把该变量的地址值传给被调用函数, 从而达到改变的目的。鉴于文章篇幅及个人能力有限, 本文肯定还存在许多不足之处, 仅供大家学习和参考。

参考文献

[1]谭浩强.C程序设计[M].二版.清华大学出版社, 2004.

[2]杜友福.C语言程序设计[M].二版.科学出版社, 2007.

上一篇:积极发展旅游新业态下一篇:党委副书记民主生活会发言汇总