大型门户网站建设服务,wordpress 置顶排序,自学建立网站,域名备案需要什么资料5.3 指针和数组
在 C 语言中#xff0c;指针和数组有着非常强的关联#xff0c;强到应当把两者同时拿出来讨论。任何可以通过数组下标来做到的操作#xff0c;也都能用指针来做到。而指针的版本通常会更快#xff0c;但至少对初学者来说会更难理解。
如下声明
int a[10]…5.3 指针和数组
在 C 语言中指针和数组有着非常强的关联强到应当把两者同时拿出来讨论。任何可以通过数组下标来做到的操作也都能用指针来做到。而指针的版本通常会更快但至少对初学者来说会更难理解。
如下声明
int a[10];
定义了一个大小为 10 的数组 a即由10个名为 a[0], a[1], ... a[9] 的连续对象所组成的块。 用 a[i] 来表示数组的第 i 个元素。如果 pa 是指向整数的指针其声明为
int *pa;
则赋值语句
pa a[0];
使 pa 指向 a 的第 0 个元素也就是说 pa 包含了 a[0] 的地址。 现在如果再赋值
x *pa;
会将 a[0] 的内容拷贝给 x。
如果 pa 指向数组的某个特定元素则根据定义pa 1 会指向下一个元素pa i 指向 pa 之后的第 i 个元素 pa - i 指向 pa 之前的第 i 个元素。因此如果 pa 指向 a[0]则
*(pa1)
表示 a[1] 的内容而 pa i 表示 a[i] 的地址而 *(pai) 是 a[i] 的内容。 其实不管数组 a 里面的变量是什么类型占据多大空间上述说法都是正确的。“将指针加1” 的含义是 pa 1 指向下一个对象由此扩展到所有指针运算可得 pa i 指向 pa 之后的第 i 个对象。
下标和指针运算之间有非常紧密的关联。根据定义类型为数组的变量或表达式其值为数组第 0 个元素的地址。因此经过如下赋值之后
pa a[0];
pa 和 a 有相同的值。由于数组的名称就是其首个元素位置的同义词赋值 pa a[0] 也能够写成
pa a;
而更令人惊奇至少在首次看到时的事实是a[i] 也能够写成 *(ai)。在计算 a[i] 时C会立即将其转换为 *(ai)两种形式是等价的。将操作符 分别应用到两者就能得到 a[i] 和 ai 也是等价的ai 是 a 之后第 i 个元素的地址。从另一个角度看如果 pa 是指针可以对它加下标来使用pa[i] 等价于 *(pai)。简而言之 数组下标的表达式等价于指针偏移的表达式。
数组名称和指针有一个区别必须牢记。指针是一个变量因此 paa 和 pa 都是合法的。但数组名不是变量像 apa 和 a 这样的结构是非法的。
当数组名称被传递给函数时传递的是数组首元素的位置。在被调函数中该参数是一个局部变量因此数组名参数是一个指针即一个包含地址的变量。我们可以利用这个事实来写另一个版本的 strlen计算字符串的长度 /* strlen: 返回字符串s的长度 */
int strlen(char *s)
{int n;for (n 0; *s ! \0; s)n;return n;
} 由于 s 是一个指针对其递增是完全合法的s 对调用 strlen 的函数里面的字符串不起任何效果它仅仅是对该指针在 strlen 中的私有拷贝进行递增。这意味着如下调用
strlen(hello, world); /* 字符串常量 */
strlen(array); /* char array[100]; */
strlen(ptr); /* char *ptr; */
都是可行的。
作为函数定义中的形参
char s[];
和
char *s;
是等价的我们更偏向后者因为它更显式地表明该参数是指针。当数组名称被传递给函数时函数可以根据自己的意愿来认定它处理的是数组还是指针并进行对应的操作。如果能够让代码看起来更恰当、更清晰甚至可以使用两种表示法。
通过传递指向子数组开头的指针可以将数组的一部分传递给函数。例如如果 a 是数组则
f(a[2])
和
f(a2)
都是把从 a[2] 开头的子数组地址传给函数 f 。在函数 f 中参数声明可以写为
f(int arr[]) { ... }
或是
f(int *arr) { ... }
从 f 的角度而言参数指向的是大数组的一部分还是数组真实的首地址都无关紧要。
如果能保证元素的存在可以将数组向前索引p[-1] 和 p[-2] 等在语法上都是合法的它们都指向 紧挨着 p[0] 的之前的元素。当然引用数组边界之外的元素是非法的。 5.4 地址运算 如果 p 是指向数组中某个元素的指针则 p 将 p 递增以指向下一个元素而 pi 将 p 递增 i 以指向当前元素后的第 i 个元素。这些及其类似的结构是指针或地址运算的最简单形式。
C 语言地址运算的方式是一致而且有规律的对指针数组和地址运算的集成是 C 语言的优势之一。我们写一个简单原始的内存管理器来说明。有两个例程。第一个是 alloc(n)返回一个指针 p 指向 n 个连续字符的位置alloc 的调用者可以用它来保存字符。第二个是 afree(p)释放通过alloc 获取到的内存使这块内存后续能被重用。说它们是“简单原始”的因为必须以 alloc 相反的顺序来调用 afree 。也就是说alloc 和 afree 管理的内存是一个栈或者叫后进先出队列。标准库提供的类似函数叫做 malloc 和 free 没有这个限制在8.7节会展示如何来实现它们。
最简单的实现是让 alloc 交出一个我们称之为 allocbuf 的大字符数组中的一小部分。这个数组是 alloc 和 free 私有的。因为它们使用指针而不是下标来处理其他例程不需要知道数组的名字因此在包含 alloc 和 free 的源文件中该数组可以声明为 static使之对外部不可见。在实际的内存管理器中数组甚至都不需要有名字它可能是通过调用 malloc 或请求操作系统从而获取到的一个指向未命名内存块的指针。
另一个所需的信息是 allocbuf 用了多少。我们使用一个指针 allocp 来指向下一个空闲的元素。当 alloc 被要求 n 个字符时它检查在 allocbuf 中是否存在足够的空间。如果是它返回当前的 allocp 值即空闲块的开头并将其递增 n以指向下一个空闲区域。如果没有足够空间alloc 返回零。afree(p) 仅仅是将 allocp 设为 p如果 p 在 allocbuf 内部的话。 #define ALLOCSIZE 10000 /* 可用空间 */static char allocbuf[ALLOCSIZE]; /* alloc所用空间 */
static char *allocp allocbuf; /* 下一个空闲位置 */char *alloc(int n) /* 返回指向n个字符的指针 */
{if (allocbuf ALLOCSIZE - allocp n) { /* 空间足够 */allocp n;return allocp - n; /* 旧的指针 */} else { /* 空间不足 */return 0;}
}void afree(char *p) /* 释放p指向的空间 */
{if (p allocbuf p allocbuf ALLOCSIZE)allocp p;
} 通常指针可以像其他变量一样初始化不过正常情况下有意义的初始值只有零或者是包含之前定义过且类型匹配的地址的表达式。如下声明
static char *allocp allocbuf;
将 allocp 定义为字符串指针并将其初始化为指向 allocbuf 的开头即程序启动时的下一个空闲位置。这也可以写成
static char *allocp allocbuf[0];
因为数组名称正是其第0个元素的地址。
如下判断
if (allocbuf ALLOCSIZE - allocp n) { /* 足够 */
用来检查是否有足够的空间可满足分配 n 个字符的请求。如果有则 allocp 的新值最多能到达的位置比 allocbuf 的末尾元素还超过一个【注意这个地址已经不属于allocbuf了只可用来比较不能分配】。如果请求能够满足alloc 返回指向一块字符的起始位置的指针注意alloc函数的声明。如果不能alloc 必须能够返回指示空间不足的信号。C 语言保证 0 永远不会是数据的合法地址因此返回值零用来指示不正常的事件此时为空间不足。
指针和整数是不可以相互交换使用的。零是唯一的例外常量零可以被赋给指针且指针可以与常量零比较。通常用符号常量 NULL 作为助记符来代替零以更清晰地表示这是指针的特殊值。NULL 在 stdio.h 中定义。此后我们都将使用 NULL。
如下判断
if (allocbuf ALLOCSIZE - allocp n) { /* 足够 */
以及
if (p allocbuf p allocbuf ALLOCSIZE)
显示了指针运算的一些重要方面。首先指针在某些环境下可以进行比较。如果 p 和 q 都指向同一数组的元素则关系操作符如 ! 等等都能正常使用。例如若要
p q
为真则 p 指向的元素在 q 指向的元素之前。任何指针都能与零进行相等或不等的比较这是有意义的。但如果在不指向相同数组的指针之间进行运算或比较其行为是未定义的。有一个例外数组末尾之后的第一个元素可以用于指针运算
第二我们已经观察到指针和整数可以相加或相减。如下结构
p n
表示 p 当前所指地址之后的第 n 个对象的地址。不管 p 指向何种类型的对象这个说法总是正确的n 会根据 p 指向的对象的大小进行放大对象大小是由 p 的声明所决定的。例如如果 int 占四个字节则 int 会乘以四。【即如果p指向整数则C编译器在计算 p n 时会把n乘以4例如 p12345678 n1则 pn 12345678 1*4】
指针减法也是合法的如果 p 和 q 指向同一数组内的元素且 pq则 q-p1 是 p 和 q 之间的元素个数包含两端。可用利用这个事实再写出另一个版本的 strlen /* strlen: 计算字符串s的长度 */
int strlen(char *s)
{char *p s;while(*p ! \0)p;return p - s;
} 在声明中p 被初始化为 s即字符串的首个字符。在 while 循环中挨个检查每个字符直到发现末尾的 \0。由于 p 指向的是字符p 每次都将 p 移到下一个字符而 p - s 表示移动过的字符数即字符串的长度。字符串中的字符数量可能太大int 保存不下。头文件stddef.h 定义了一个类型 ptrdiff_t它足够大可以用来保存两个指针之间的有符号差值。然而如果我们更仔细的话会使用 size_t 来做 strlen 的返回值以与标准库的版本相匹配。size_t 是 sizeof 操作符返回的有符号整型。
指针运算是一致的如果我们要处理比 char 占内存更多的 float而 p 是指向 float 的指针则 p 会指向下一个 float。这样仅仅需要把 alloc 和 afree 中的所有 char 替换成 float我们就能写出 alloc 的 float 版本 。所有的指针操作都会自动地考虑到所指向对象的大小。
合法的指针操作有将指针赋给相同类型一个指针与一个整数的加减指向相同数组的两个指针的减法或比较以及与零的赋值和比较。其他所有指针运算都是非法的。对两个指针相加是非法的非法的还有相乘或相除移位或者掩码以及将指针与 float 或 double 相加甚至在没有强制类型转换的情况下将一个类型的指针赋给另一个类型的指针。最后一种情况对 void * 是特例它是可以不用强制类型转换。