C语言K&R圣经笔记 5.5字符串指针与函数

5.5 字符串指针与函数

字符串常量是一个字符数组,它写成

"I am a string" 

在内部表示中,数组以空字符 '\0' 结尾,这样程序就能找到其结尾。因此内存中的长度就比双引号之间的字符数多一。

可能最常见的字符串常量是作为函数参数来使用的,例如

printf("hello, world\n");

当像这样的字符串常量出现在程序中时,是通过字符指针来访问的;printf 收到一个指向字符数组开头的指针。也就是说,字符串常量是通过指向其首个元素的指针来访问的。

字符串常量不一定是函数参数。如果 pmessage 声明为

char *pmessage;

则语句

pmessage = "now is the time";

将 pmessage 赋值为指向字符数组的指针。这不是字符串拷贝;只涉及到指针。C语言没有提供任何能将字符数组作为一个整体单元来操作的操作符。

下面两个定义有重要的区别

char amessage[] = "now is the time";    /* 数组 */
char *pmessage = "now is the time";     /* 指针 */

amessage 是一个数组,恰好足够大,以包含初始的字符串序列及末尾的 '\0'。数组里面每个字符都可以独立修改,但 amessage 总是指向同一个内存空间。另一方面,pmessage 是一个指针,初始化时指向了一个字符串常量;这个指针后续可以修改以指向其他位置,但如果你想要修改字符内容的话,其结果是未定义的。

C语言K&R圣经笔记 5.5字符串指针与函数_第1张图片

我们将研究从标准库改编而来的两个函数,用以说明指针和数组更多方面的内容。第一个函数是 strcpy(s, t),将字符串 t 拷贝到字符串 s。如果能简单写个 s=t 就好了,可惜那只是指针拷贝,而不是每个字符的拷贝。为了拷贝这些字符,我们需要使用循环。首先是数组版本:

/* 把t拷贝到s,数组下标版本 */
void strcpy(char *s, char *t)
{
    int i;

    i = 0;
    while ((s[i] = t[i]) != '\0')
         i++;
}

 对比下面的指针版本

/* 把t拷贝到s,指针版本1 */
void strcpy(char *s, char *t)
{
    while ((*s = *t) != '\0') {
        s++;
        t++;
    }
}

由于参数是值传递的,strcpy 可以用它喜欢的任何方式来使用参数 t 和 s。在这里,它们都是已经初始化好的指针,每次都分别在各自的数组上前进一个字符,直到 t 结尾的 '\0' 拷贝到 s 后才停止。

实际上,strcpy 不会写成上面那样。有经验的程序员会写成

/* 把t拷贝到s,指针版本2 */
void strcpy(char *s, char *t)
{
    while ((*s++ = *t++) != '\0')
        ;
}

这里把对 s 和 t 的递增移到了循环的检查部分。*t++ 的值是 t 在递增之前指向的字符;后缀的 ++ 要到这个字符被取出来之后才会改变 t。同样的,这个字符也被保存到 s 在递增之前的老位置。这个字符的值还被用来和 '\0' 比较,以控制这个循环。最终的效果就是字符都被从 t 拷贝到 s, 一直到(而且包括)末尾的 '\0'。

最后还能做个简化,因为问题仅仅是表达式是否为零,可知与 '\0' 的比较是多余的。因此这个函数可能写成:

/* 把t拷贝到s,指针版本3 */
void strcpy(char *s, char *t)
{
    while (*s++ = *t++)
        ;
}

尽管第一眼看起来这有点晦涩难懂,但这种写法是相当方便的,而且你应当掌握这种习惯用法(idiom),因为你将会在C程序中频繁遇见它。

标准库()中的 strcpy 函数把目标字符串作为返回值。

我们要审视的第二个例程是 strcmp(s, t),它比较两个字符串 s 和 t,当 s 的字典顺序小于、等于或大于 t 的时候,分别返回负数、0 和 正数。返回值的获取,是将 s 和 t 第一个不相同的字符进行相减得到的。


int strcmp(char *s, char *t)
{
    int i;

    for (i = 0; s[i] == t[i]; i++)
        if (s[i] == '\0')
            return 0;
    return s[i] - t[i];
}

指针版本为

int strcmp(char *s, char *t)
{
    for ( ; *s == *t; s++, t++)
        if (*s == '\0')
            return 0;
    return *s - *t;
}

由于 ++ 和 -- 不是前缀就是后缀操作符,也会有 * 和 ++/-- 的其他组合,尽管出现频率低一些。例如

*--p

在获取 p 指向的字符之前对 p 进行递减。实际上,下面这对语句

*p++ = val;    /* 推val入栈 */
val = *--p;    /* 栈顶出栈赋给val */

是出栈和入栈的标准用法;参见 4.3 节。

标准库头文件 包含了本节提到的两个函数,以及其他多种字符串处理函数的声明。

练习5-3、写出第二章中 strcat 函数的指针版本:strcat(s, t) 将字符串 t 连接到字符串 s 的末尾。

练习5-4、写个函数 strend(s, t),如果字符串 t 出现在字符串 s 的末尾,则返回 1,否则返回 0。 

练习5-5、实现库函数 strncpy,strncat 和 strncmp ,这些函数最多操作它们字符串参数的 n 个字符。例如, strncpy(s, t, n) 最多将 n 个字符从 t 拷贝到 s。函数的完整描述参见附录B。 

练习5-6、前面章节正文和练习中的程序,如果是用数组下标的,将其改用指针来重写。这些程序至少包括 getline(第1、4章),atoi,itoa及其变种(第2、3、4章),reverse(第3章),以及 strindex 和 getop(第4章)

你可能感兴趣的:(C语言,c语言,笔记,开发语言)