地址算术运算
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
地址算术运算
如果p 是一个指向数组中某个元素的指针,那么p++将对p 进行自增运算并指向下一个
元素,而p+=i 将对p 进行加i 的增量运算,使其指向指针p 当前所指向的元素之后的第i 个元素。
这类运算是指针或地址算术运算中最简单的形式。
C语言中的地址算术运算方法是一致且有规律的,将指针、数组和地址的算术运算集成在
一起是该语言的一大优点。
为了说明这一点,我们来看一个不完善的存储分配程序。
它由两个函数组成。
第一个函数alloc(n)返回一个指向n 个连续字符存储单元的指针,alloc 函数的调用者可利用该指针存储字符序列。
第二个函数afree(p)释放已分配的存储空间,以便以后重用。
之所以说这两个函数是“不完善的”,是因为对afree 函数的调用次序必须与
调用alloc函数的次序相反。
换句话说,alloc与afree以栈的方式(即后进先出的列表)进行存储空间的管理。
标准库中提供了具有类似功能的函数malloc和free,它们没有上述限制,我们将在8.7节中说明如何实现这些函数。
最容易的实现方法是让alloc函数对一个大字符数组allocbuf中的空间进行分配。
该
数组是alloc 和afree 两个函数私有的数组。
由于函数alloc 和afree 处理的对象是指针而不是数组下标,因此,其它函数无需知道该数组的名字,这样,可以在包含alloc和afree
的源文件中将该数组声明为static 类型,使得它对外不可见。
实际实现时,该数组甚至可
以没有名字,它可以通过调用malloc 函数或向操作系统申请一个指向无名存储块的指针获
得。
allocbuf 中的空间使用状况也是我们需要了解的信息。
我们使用指针allocp 指向allocbuf 中的下一个空闲单元。
当调用alloc 申请n 个字符的空间时,alloc 检查allocbuf 数组中有没有足够的剩余空间。
如果有足够的空闲空间,则alloc 返回allocp 的当前值(即空闲块的开始位置),然后将allocp加n以使它指向下一个空闲区域。
如果空
闲空间不够,则alloc返回0。
如果p在allocbuf的边界之内,则afree(p)仅仅只是将allocp的值设置为p(参见图5-6)。
#define ALLOCSIZE 10000 /* size of available space */
static char allocbuf[ALLOCSIZE]; /* storage for alloc */
static char *allocp = allocbuf; /* next free position */
char *alloc(int n) /* return pointer to n characters */
{
if (allocbuf + ALLOCSIZE - allocp >= n) { /* it fits */
allocp += n;
return allocp - n; /* old p */
} else /* not enough room */
return 0;
}
void afree(char *p) /* free storage pointed to by p */
{
if (p >= allocbuf && p < allocbuf + ALLOCSIZE)
allocp = p;
}
图5-6
一般情况下,同其它类型的变量一样,指针也可以初始化。
通常,对指针有意义的初始
化值只能是0 或者是表示地址的表达式,对后者来说,表达式所代表的地址必须是在此前已定义的具有适当类型的数据的地址。
例如,声明
static char* allocp = allocbuf;
将allocp 定义为字符类型指针,并将它初始化为allocbuf 的起始地址,该起始地址是程
序执行时的下一个空闲位置。
上述语句也可以写成下列形式:
static char* allocp = &allocbuf[0];
这是因为该数组名实际上就是数组第0个元素的地址。
下列if测试语句:
if (allocbuf + ALLOCSIZE - allocp >= n) { /* it fits */
检查是否有足够的空闲空间以满足n 个字符的存储空间请求。
如果空闲空间足够,则分配存
储空间后allocp的新值至多比allocbuf的尾端地址大1。
如果存储空间的申请可以满足,alloc将返回一个指向所需大小的字符块首地址的指针(注意函数本身的声明)。
如果申请无
法满足,alloc必须返回某种形式的信号以说明没有足够的空闲空间可供分配。
C语言保证,0 永远不是有效的数据地址,因此,返回值0 可用来表示发生了异常事件。
在本例中,返回值
0 表示没有足够的空闲空间可供分配。
指针与整数之间不能相互转换,但0 是惟一的例外:常量0 可以赋值给指针,指针也可
以和常量0进行比较。
程序中经常用符号常量NULL代替常量0,这样便于更清晰地说明常量0 是指针的一个特殊值。
符号常量NULL定义在标准头文件<stddef.h>中。
我们在后面部分
经常会用到NULL。
类似于
if (allocbuf + ALLOCSIZE - allocp >= n) { /* it fits */
以及
if (p >= allocbuf && p < allocbuf + ALLOCSIZE)
的条件测试语句表明指针算术运算有以下几个重要特点。
首先,在某些情况下对指针可以进行比较运算。
例如,如果指针p 和q 指向同一个数组的成员,那么它们之间就可以进行类似
于==、!=、<、>=的关系比较运算。
如果p 指向的数组元素的位置在q 指向的数组元素位置
之前,那么关系表达式
p < q
的值为真。
任何指针与0 进行相等或不等的比较运算都有意义。
但是,指向不同数组的元素的指针之间的算术或比较运算没有定义。
(这里有一个特例:指针的算术运算中可使用数组最
后一个元素的下一个元素的地址。
)
其次。
我们从前面可以看到,指针可以和整数进行相加或相减运算。
例如,结构
p + n
表示指针p 当前指向的对象之后第n 个对象的地址。
无论指针p 指向的对象是何种类型,上
述结论都成立。
在计算p+n 时,n 将根据p 指向的对象的长度按比例缩放,而p 指向的对象
的长度则取决于p的声明。
例如,如果int类型占4个字节的存储空间,那么在int类型的
计算中,对应的n将按4的倍数来计算。
指针的减法运算也是有意义的:如果p和q指向相同数组中的元索,且p<q,那么q-p+1
就是位于p 和q 指向的元索之间的元素的数目。
我们由此可以编写出函数strlen 的另一个
版本,如下所示:
/* strlen: return length of string s */
int strlen(char *s)
{
char *p = s;
while (*p != '\0')
p++;
return p - s;
}
在上述程序段的声明中,指针p 被初始化为指向s,即指向该字符串的第一个字符。
whi1e 循环语句将依次检查字符串中的每个字符,直到遇到标识字符数组结尾的字符'\0' 为止。
由
于p 是指向字符的指针,所以每执行一次p++,p 就将指向下一个字符的地址,p-s 则表示
已经检查过的字符数,即字符串的长度。
(字符串中的字符数有可能超过int类型所能表示的
最大范围。
头文件<stddef.h>中定义的类型ptrdiff_t足以表示两个指针之间的带符号差
值。
但是,我们在这里使用size_t 作为函数strlen 的返回值类型,这样可以与标准库中
的函数版本相匹配。
Size_t是由运算符sizeof返回的无符号整型。
)
指针的算术运算具有一致性:如果处理的数据类型是比字符型占据更多存储空间的浮点
类型,并且p 是一个指向浮点类型的指针,那么在执行p++后,p 将指向下一个浮点数的地址。
因此,只需要将alloc和afree函数中所有的char类型替换为float类型,就可以
得到一个适用于浮点类型而非字符型的内存分配函数。
所有的指针运算都会自动考虑它所指向的对象的长度。
有效的指针运算包括相同类型指针之间的赋值运算;指针同整数之间的加法或减法运算;
指向相同数组中元素的两个指针间的减法或比较运算;将指针赋值为0 或指针与0 之间的比较运算。
其它所有形式的指针运算都是非法的,例如两个指针间的加法、乘法、除法、移位或屏蔽运算;指针同float或double类型之间的加法运算;不经强制类型转换而直接将指向一种类型对象的指针赋值给指向另一种类型对象的指针的运算(两个指针之一是void * 类型的情况除外)。