C程序设计语言5Word文档格式.docx

上传人:b****6 文档编号:21352264 上传时间:2023-01-29 格式:DOCX 页数:52 大小:98.68KB
下载 相关 举报
C程序设计语言5Word文档格式.docx_第1页
第1页 / 共52页
C程序设计语言5Word文档格式.docx_第2页
第2页 / 共52页
C程序设计语言5Word文档格式.docx_第3页
第3页 / 共52页
C程序设计语言5Word文档格式.docx_第4页
第4页 / 共52页
C程序设计语言5Word文档格式.docx_第5页
第5页 / 共52页
点击查看更多>>
下载资源
资源描述

C程序设计语言5Word文档格式.docx

《C程序设计语言5Word文档格式.docx》由会员分享,可在线阅读,更多相关《C程序设计语言5Word文档格式.docx(52页珍藏版)》请在冰豆网上搜索。

C程序设计语言5Word文档格式.docx

Y、=*ip;

*ip=0;

ip=&z【0】;

ip现在指向x+/

y的值现在为l+/

x的值现在为04/

ip现在指向Z[0]4/

变量x、Y与Z的声明方式我们已经在前面的章节中见到过。

我们来看指针ip的声明,如

下所示:

int·

这样声明是为了便于记忆。

该声明语句表明表达式*ip的结果是int类型。

这种声明变量的语

法与声明该变量所在表达式的语法类似。

同样的原因,对函数的声明也可以采用这种方式。

例如,声明

double*dp,atof(char·

);

表明,在表达式中,*dp和atof(S)的值都是double类型,J王atof的参数是一个指向]char

类型的指针。

我们应该注意,指针只能指向某种特定类型的对象,也就是说,每个指针都必须指向某

种特定的数据类型。

(一个例外情况是指向v。

id类型的指针可以存放指向任何类型的指针,

但它不能间接引用其自身。

我们将在5.11节中详细讨论该问题)。

如果指针ip指向整型变量x,那么在x可以出现的任何上下文中都可以使用4ip,因此,

语句

*ip;

*ip+10;

将把*ip的值增iJIl0。

一元运算符木和&

的优先级比算术运算符的优先级高,因此,赋值语句

Y2*ip+1、

将把*ip指向的对象的值取出并加l,然后再将结果赋值给Y,而下列赋值语句:

*ip+=1

回则将ip指向的对象的值加l,它等同于

++·

ip’

(*ip)++

语句的执行结果。

语句(术iD)十+中的圆括号是必益的,否则,该表达式将对ip进行加二运算,

而不是塑ip指向的对象进行地二蓝算,这是因为,类似于术和++这样的一元运算符遵循从右

l

指针与数纽

至左的结合顺序。

最后说明一点,由于指针也是变量,所以在程序中可以直接使用,而不必通过间接引用

的方法使用。

例如,如果iq是另一个指向整型的指针,那么语句

iq=ip

将把ip中的值拷贝到iq中,这样,指针iq也将指向ip指向的对象。

5.2指针与函数参数

由于9蔓盖星丛传值的方煎!

睦数值传递给被调用函数,因此,被调用函数不能直接修改

主调函数中变量的值。

例如,排序函数可能会使用二个名为swap的函数来交换两个次序颠倒

的元素。

但是,如果将swap函数定义为下列形式:

厂———————————————~九

ivoidswap(intX。

intY),/4错误定义的函数+/J

T—————————————√、

inttemp:

temp=X;

X2Y;

Y。

temp;

则下列语句无法达到该目的。

’swap(a,b);

这是因为,由于参数传递采用传值方式,因此上述的swap函数不会影响到调用它的例程中的

参数a和b的值。

该函数这堡交换了a和b的副本的值。

—’0-o-_-●o__l-___00●-0■__-____-_-一

那么,如何实现我们的目标呢?

可以使主调程序将指向所要交换的变量的指针传递给被

调用函数,即:

swapC&a。

&b);

由于一元运算符&

用来取变量的地址,这样&

a就是一个指向变量a的指针。

swap函数的所有

参数都声明为指针,并且通过这些指针来间接访问它们指向的操作数。

眄习

卜、————————————-、

lVoidswap(int*px。

py'

1/+交换+px和4PY+/。

1。

————————————一

$nttemp;

temp。

*px;

●px。

●PY:

*Py。

temp:

我们通过图5—2进行说明。

塑针参数使堡垫塑盥数能够访问和修改主诅函数中对象的值。

我们来看这样一个例子:

函数9etint接受自由格式的输入,并执行转换,将输入的字符流分解成整数,且每次调用得

到一个整数。

9etint需要返回转换后得到的整数,并且,在到达输入结尾时要返回文件结

标记。

这些值必须通过不同的方式返回。

EOF(文件结束标记)可以用任何值表示,当然也

可用一个输入的整数表示。

在主调函数中:

图5—2

可以这样设计该函数:

将标识是否到达文件结尾的状态作为9etint函数的返回值,同时,

使用一个指针参数存储转换后得到的整数并传回给主调函数。

函数scanf的实现就采用了这

种方法,具体细节请参见7.4节。

下面的循环语句调用getint函数给一个整型数组赋值:

intn.array[SIZE]。

getint(int·

).,

forInt0:

n《SIZE&&

getint(&

array[n])1;

EOF;

n++J

每次调用9etint时,输入流中的下一个整数将被赋值给数组元素array【n],同时,n的值

将增加1。

请注意,这里必须将array[n]的地址传递给函数9etint,否则函数getint将

无法把转换得到的整数传回给调用者。

该版本的9etint函数在到达文件结尾时返回EoF,当下一个输入不是数字时返回0,当

陌习输入中包含一个有意义的数字时返回一个正值。

#include<

ctype.h>

intgetch(void);

voidungetch(int):

/+getint函数:

将输入中的下一个整型数赋值给+pn+/

intgetint(int.pn)

{

intc。

sign;

while.I≤》(c—geteh()))/+跳过空白符4/

if(1isdiglt(c)&

&

cl_EOF&_&

cl-’+’&矗eIt’·

’){

ungetch(c);

/+输入不是一个数字+/

return0;

J

指针与教纽

signt(ct一’-’)7—1:

1:

if(c一·

’+’:

ct-’一’)

c。

getch();

for(*pn-0;

isdiglt(c);

ctgetch())

.pn_10·

-pn+(c一’0’);

*pn·

tsign:

if(cI=EOF)

returnC;

}

在getint函数中,幸pn始终作为一个普通的整型变量使用。

其中还使用了9etch和

ungetch两个函数(参见4.3节),借助这两个函数,函数getint必须读人的一个多余字符

就可以重新写回到输入中。

练>

U5-1在上面的例子中,如果符号+或一的后面紧跟的不是数字,getint函数将把符

号视为数字0的有效表达方式。

修改该函数,将这种形式的+或一符号重新写回到输入流中。

练习5-2模仿函数9etint的实现方法,编写一个读取浮点数的函数9etfloat。

getfloat函数的返回值应该是什么类型?

5.3指针与数组

在c语言中,指针和数组之间的关系十分密切,因此,在接下来的部分中,我们将同时讨

论指针与数组。

通过数组下标所能完成的任何操作都可以通过指针来实现。

一般来说,用指

针编写的程序比用数组下标编写的程序执行速度快,但另一方面,用指针实现的程序理解起

来稍微困难一些。

臣口

声明

Int“10】;

定义了一个长度为l0的数组a。

换句话说,它定义了一个由10个对象组成的集合,这l0个对象

存储在相邻的内存区域中,名字分别为a[0]、a[1]、…、a[9](参见图5—3)。

a:

[工]二[工]][[丁卫

^【O】■【1】-【9】

图5—3

a[i]表示该数组的第i个元素。

如果pa的声明为

int*pa;

则说明它是一个指向整型对象的指针,那么,赋值语句

pa-&“0】;

则可以将指针pa指向数组a的第0个元素,也就是说,pa的值为数组元素a[0]的地址(参见

图5—4)。

这样,赋值语句

x_·

pa;

pa:

a【0】

图5—4

将把数组元素a[0]中的内容复制到变量X中。

如果pa指向数组中的某个特定元素,那么,根据指针运算的定义,pa+1将指向下l个

、‘-,,’——’——————————一—'

-。

蓝pa+i将指向pa所指向数组元素之后的第i个霾素,而pa—i将指向pa所指向数组元

素之前的第i个元素。

因此,如果指针pa指向a[0],那么木(pa+1)引用的是数组元素

a[1]的内容,pa+i是数组元素a[i]的地址,术(pad)引用的是数组元素a[i幽容

(参见图5—5)。

图5.5

无论数组a中元素的类型或数组长度是什么,上面的结论都成立。

“指针加l”就意味着,

pa+1指向pa所指向的对象的下一个对象。

相应地,pa+i指向pa所指向的对象之后的第i个

乜纠对象。

c。

下标和指针运算之间具有密切的对应关系。

根据定义,数组类型的变量或表达式的值是

该数组第0个元素的地址。

执行赋值语句

pa薯&

a【0】;

后,pa和a具有相同的值。

因为数组名所代表的就是该数组最开始的一个元素的地址,所以,

赋值语句pa=&

a[0]也可以写成下列形式:

pa=a;

对数组元素a[i]的引用也可以写成半(a+i)这种形式。

对第一次接触这种写法的人来说,

可能会觉得很奇怪。

在计算数组元素a[i]的值时,C语言实际上先将其转换为t(a+i)的形式,

然后再进行求值,因此在程序中这两种形式是等价的。

如果对这两种等价的表示形式分别施

加地址运算符&

,便可以得出这样的结论:

a[i]和a+i的含义也是相同的。

a+i是a之后第i

指针s数组

个元素的地址。

相应地,如果pa是一个指针,那么,在表达式中也可以在它的后面加下标。

3a[i]与t(pa+i)是等价的。

简而言之,一个通过数组和下标实现的表达式可等价地通过指

针和偏移量实现。

但是,我们必须记住,数组名和指针之间有一个不同之处。

指针是一个变量,因此,在C

浯言中,语句pa=a和pa++都是合法的。

但数组名不是变量,因此,类似于a=pa和a++形式

的语句是非法的。

当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。

在被调用函

数中,该参数是一个局部变量,因此,数组名参数必须是一个指针,也就是一个存储地址值

的变量。

我们可以利用该特性编写strlen函数的另一个版本,该函数用于计算一个字符串的

长度。

/4Strlen函数:

返回字符串S的长度+/

intstrlen(char·

s)

intn;

forIn昌0;

·

sI=’\0’;

s++)

n++;

returnn;

因为S是一个指针,所以对其执行自增运算是合法的。

执行S++运算不会影响到Strlen函数

的调用者中的字符串,它仅对该指针在Strlen函数中的私有副本进行自增运算。

因此,、类似

于下面这样的函数调用:

7

strlen(”hell0,world”);

/4字符串常量+/

strlen(array):

/4字符数组array有100个元素+/

strlen(ptr);

/+ptr是一个指向char类型对象的指针+/

部可以正确地执行。

在函数定义中,形式参数

char8【】;

是等价的。

我们通常更习惯于使用后一种形式,因为它比前者更直观地表明了该参数是一个

指针。

如果将数组名传递给函数,函数可以根据情况判定是按照数组处理还是按照指针处理,

随后根据相应的方式操作该参数。

为了直观且恰当地描述函数,在函数中甚至可以同时使用

故组和指针这两种表示方法。

也可以将指向子数组起始位置的指针传递给函数,这样,就将数组的一部分传递给了函

牧。

例如,如果a是一个数组,那么下面两个函数调用

fl&a【2】)

f(a+2)7

都将把起始于a[2]的子数组的地址传递给函数f。

在函数f中,参数的声明形式可以为

f(intaEE【】){…)

f(int*aEr){…,

对于函数f来说,它并不关心所引用的是否只是一个更大数组的部分元素。

如果确信相应的元素存在,也可以通过下标访问数组第一个元素之前的元素。

类似于

P【-1]、P卜2]这样的表达式在语法上都是合法的,它们分别引用位于p[0]之前的两个元素。

当然,引用数组边界之外的对象是非法的。

5.4地址算术运算

如果P是一个指向数组中某个元素的指针,那么P++将对P进行自增运算并指向下一个元

素,而p+=i将对P进行加i的增量运算,使其指向指针P当前所指向的元素之后的第i个元素。

这类运算是指针或地址算术运算中最简单的形式。

C语言中的地址算术运算方法是一致且有规律的,将指针、数组和地址的算术运算集成在

一起是该语言的一大优点。

为了说明这一点,我们来看一个不完善的存储分配程序。

它由两

个函数组成。

第一个函数alloc(n)返回一个指向n个连续字符存储单元的指针,alloc函数

的调用者可利用该指针存储字符序列。

第二个函数afree(P)释放已分配的存储空间,以便

以后重用。

之所以说这两个函数是“不完善的”,是因为对afree函数的调用次序必须与调用

1100lallOC函数的次序相反。

换句话说,alloc与afree以栈的方式(即后进先出的列表)进行

存储空间的管理。

标准库中提供了具有类似功能的函数malloc和free,它们没有上述限制,

我们将在8.7节中说明如何实现这些函数。

最容易的实现方法是让allOC函数对一个大字符数组allocbuf中的空间进行分配。

数组是allOC和afree两个函数私有的数组。

由于函数alloc和afree处理的对象是指针而

不是数组下标,因此,其他函数无需知道该数组的名字,这样,可以在包含alloc和afree

的源文件中将该数组声明为StatiC类型,使得它对外不可见。

实际实现时,该数组甚至可以

没有名字,它可以通过调用malloc函数或向操作系统申请一个指向无名存储块的指针获得。

a110Cbuf中的空间使用状况也是我们需要-『解的信息。

我们使用指针allocp指向

a110Cbuf中的下一个空闲单元。

当调用all0C申请n个字符的空间时,all0C检查

allocbuf数组中有没有足够的剩余空间。

如果有足够的空闲空间,贝r]alloc返回allocp的

当前值(即空闲块的开始位置),然后将allocp力In以使它指向下一个空闲区域。

如果空闲

空间不够,则all0C返回0。

如果P在a110cbuf的边界之内,则afree(P)仅仅只是将

allocp的值设置为P(参见图5—6)。

#defineALLOCSIZEl0000/+可用空间大小4/

staticcharallocbuf[ALLOCSIZE]:

/+alloc使用的存储区+/

staticchar.allocp2a110cbuf;

/+下一个空闲位置4/

char*alloc(intn)/+返回指向n个字符的指针+/

if(allocbuf+ALLOCSIZE—allocp)-2n){/+有足够的空闲空间+/

allocp+窜n:

retur—nallocp—n;

/4分配前的指针p8/

)else/+空闲空间不够+/

'

voidafree(char·

p)/+释放P指向的存储区+/

if(p>

=allocbuf&

p《allocbuf+ALLOCSIZE)

allocp。

p;

调用allOC之前:

allocp:

almbur:

[工二口二[二二二二二二]

_已使用—_+_空闲————一.

调用allOC之后:

all。

cbur:

[[二口二[二]二二二二]

_已使用H_空闲——一.

图5—6

一般情况下,同其他类型的变量一样,指针也可以初始化。

通常,对指针有意义的初始

七值只能是0或者是表示地址的表达式,对后者来说,表达式所代表的地址必须是在此前已定

泛的具有适当类型的数据的地址。

例如,声明

staticchar*allocp2allocbuf;

辱allocp定义为字符类型指针,并将它初始化为allocbuf的起始地址,该起始地址是程序

jl【行时的下一个空闲位置。

上述语句也可以写成下列形式:

’staticchar*allocp2&

allocbuf[0】;

茎是因为该数组名实际上就是数组第0个元素的地址。

下列if测试语句:

if(allocbuf+ALLOCSIZE—allocp>

=n){/+有足够的空闲空间+/

釜查是否有足够的空闲空间以满足n个字符的存储空间请求。

如果空闲空间足够,则分配存储

!

间后allocp的新值至多比allocbuf的尾端地址大l。

如果存储空间的申请可以满足,

lloc将返回一个指向所需大小的字符块首地址的指针(注意函数本身的声明)。

如果申请无

满足,allOC必须返回某种形式的信号以说明没有足够的空闲空间可供分配。

℃语言保证,

永远不是有效的数据地址,因此,返回值0可用来表示发生了异常事件。

在本例中,返回值D

表示没有足够的空闲空间可供分配。

指针与整数之间不能相互转换,但O是惟一的例外:

常量0可以赋值给指针,指针也可以

和常量0进行比较。

程序中经常用符号常量NULL代替常量0,这样便于更清晰地说明常量0是

指针的一个特殊值。

符号常量NuLL定义在标准头文件<

Stddef.h>

中。

我们在后面部分经常

会用NNULL。

类似于.

if(allocbuf+ALLOCSIZE—allocp'

zn){/4有足够的空闲空间+/

以及

if(P>

=a110cbuf&&P《allocbuf+ALLOCSIZE)

的条件测试语句表明指针算术运算有以下几个重要特点。

首先,在某些情况下对指针可以进

行比较运算。

例如,如果指针p和q指向同一个数组的成员,那么它们之间就可以进行类似于

==、!

=、<

、>

=的关系比较运算。

如果P指向的数组元素的位置在q指向的数组元素位置之前,

那么关系表达式

P‘q

匝圈的值为真(true)。

任何指针与0R行相等或不等的比较运算都有意义。

但是,指向不同数组

的元素的指针之间的算术或比较运算没有定义。

(这里有一个特例:

指针的算术运算中可使用

数组最后一个元素的下一个元素的地址。

其次,我们从前面可以看到,指针可以和整数进行相加或相减运算。

例如,结构

P+n

表示指针P当前指向的对象之后第n个对象的地址。

无论指针P指向的对象是何种类型,上述

结论都成立。

在计算p+n时,lq将根据p指向的对象的长度按比例缩放,而b指向的对象的长度

则取决于p的声明。

例如,如果int类型占4个字节的存储空间,那么在int类型的计算中,

对应的n将按4的倍数来计算。

指针的减法运算也是有意义的:

如果P和q指向相同数组中的元素,且P<

q,那么q—p+l

就是位于P和q指向的元素之间的元素的数目。

我们由此可以编写出函数strlen的另一个版

本,如下所示:

/+Strlen函数:

返回字符串S的长度+/t

intstrlen(char·

B)

{

char●p=S:

while(·

pl=’\0’)

p++;

returnP一8:

在上述程序段的声明中,指针P被初始化为指向S,即指向该字符串的第一个字符。

whileN

环语句将依次检查字符串中的每个字符,直到遇到标识字符数组结尾的字符’\0’为止。

由于

P是指向字符的指针,所以每执行一次p++,P就将指向下一个字符的地址,P—S则表示已经

检查过的字符数,即字符串的长度。

(字符串中的字符数有可能超过int类型所能表示的最大

范围。

头文件<

中定义的类型ptrdiff_t足以表示两个指针之间的带符号差值。

但是,我们在这里使用SiZe,t作为函数Strlen的返回值类型,这样可以与标准库中的函数

版本相匹配。

SiZe_t是由运算符Sizeof返回的无符号整型。

指针的算术运算具有一致性:

如果处理的数据类型是比字符型占据更多存储空间的浮点

类型,并且P是一个指向浮点类型的指针,那么在执细++后,P将指向下一个浮点数的地址。

因此,只需要将alloc和afree函数中所有的char类型替换为float类型,就可以得到一个

适用于浮点类型而非字符型的内存分配函数。

所有的指针运算都会自动考虑它所指向的对象

的长度。

有效的指针运算包括相同类型指针之间的赋值运算;

指针同整数之间的加法或减法运

茸;

指向相同数组中元素的两个指针间的减法或比较运算;

将指针赋值为0或指针与0之间的

比较运算。

其他所有形式的指针运算都是非法的,例如两个指针间的加法、乘法、除法、移

直或屏蔽运算;

指针同float或double类型之间的加法运算;

不经强制类型转换而直接

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1