上一页 下一页

C 编程基础

字符串

十万个为什么.com

C 语言中的字符串在很大程度上与指针交织在一起。您必须熟悉前面文章中介绍的指针概念,才能有效地使用 C 字符串。然而,一旦您习惯了它们,您通常可以非常高效地执行字符串操作。

C 语言中的字符串简单来说就是字符数组。以下行声明了一个数组,该数组最多可以容纳 99 个字符的字符串。

广告

char str[100];

它按预期保存字符:str[0] 是字符串的第一个字符,str[1] 是第二个字符,依此类推。但是,为什么一个 100 元素的数组不能容纳 100 个字符呢?因为 C 使用空终止字符串,这意味着任何字符串的末尾都用 ASCII 值 0(空字符)标记,在 C 语言中也表示为 '\0'

空终止符与许多其他语言处理字符串的方式大不相同。例如,在 Pascal 中,每个字符串都由一个字符数组组成,其中有一个长度字节,用于记录数组中存储的字符数量。这种结构使得 Pascal 在您查询字符串长度时具有明显的优势。Pascal 可以直接返回长度字节,而 C 必须计算字符直到找到 '\0'。这个事实使得 C 在某些情况下比 Pascal 慢得多,但在其他情况下则更快,我们将在下面的示例中看到。

由于 C 语言本身不提供对字符串的显式支持,所有字符串处理函数都在库中实现。字符串 I/O 操作(gets、puts 等)在 <stdio.h> 中实现,而一组相当简单的字符串操作函数在 <string.h> 中实现(在某些系统上是 <strings.h>)。

字符串不是 C 语言原生类型的事实迫使您创建一些相当迂回的代码。例如,假设您想将一个字符串赋值给另一个字符串;也就是说,您想将一个字符串的内容复制到另一个字符串。在 C 语言中,正如我们在上一篇文章中看到的,您不能简单地将一个数组赋值给另一个数组。您必须逐个元素地复制它。字符串库(<string.h> 或 <strings.h>)包含一个名为 strcpy 的函数来完成此任务。以下是在普通 C 程序中非常常见的一段代码

char s[100];
strcpy(s, "hello");

在这两行执行后,下图显示了 s 的内容

顶部图显示了带有字符的数组。底部图显示了字符的等效 ASCII 码值,这就是 C 语言实际如何看待字符串的方式(作为包含整数值的字节数组)。有关 ASCII 码的讨论,请参阅位和字节的工作原理

以下代码演示了如何在 C 语言中使用 strcpy

#include <string.h>
int main()
{
    char s1[100],s2[100];
    strcpy(s1,"hello"); /* copy "hello" into s1 */
    strcpy(s2,s1);      /* copy s1 into s2 */
    return 0;
}

在 C 语言中初始化字符串时,都会使用 strcpy。您可以使用字符串库中的 strcmp 函数来比较两个字符串。它返回一个整数,表示比较结果。零表示两个字符串相等,负值表示 s1 小于 s2,正值表示 s1 大于 s2

#include <stdio.h>
#include <string.h>
int main()
{
    char s1[100],s2[100];
     gets(s1);
    gets(s2);
    if (strcmp(s1,s2)==0)
        printf("equal\n");
    else if (strcmp(s1,s2)<0)
        printf("s1 less than s2\n");
    else
        printf("s1 greater than s2\n");
    return 0;
}

字符串库中其他常用函数包括 strlen(返回字符串的长度)和 strcat(连接两个字符串)。字符串库还包含许多其他函数,您可以通过阅读手册页来仔细研究。

为了帮助您开始构建字符串函数,并帮助您理解其他程序员的代码(每个人似乎都有自己的一套用于程序中特殊目的的字符串函数),我们将看两个例子:strlenstrcpy。下面是一个严格的 Pascal 风格的 strlen 版本

int strlen(char s[])
{
    int x;
    x=0;
    while (s[x] != '\0')
         x=x+1;
    return(x);
}

大多数 C 程序员都回避这种方法,因为它似乎效率低下。相反,他们通常使用基于指针的方法。

int strlen(char *s)
{
    int x=0;
    while (*s != '\0')
    {
        x++;
        s++;
    }
    return(x);
}

您可以将此代码缩写为以下形式。

int strlen(char *s)
{
    int x=0;
    while (*s++)
        x++;
    return(x);
}

我想真正的 C 专家可以使这段代码更短。

当我在 MicroVAX 上使用 gcc 编译这三段代码,不进行优化,并在一个 120 字符的字符串上分别运行 20,000 次时,第一段代码耗时 12.3 秒,第二段 12.3 秒,第三段 12.9 秒。这意味着什么?对我来说,这意味着您应该以最容易理解的方式编写代码。指针通常会产生更快的代码,但上面的 strlen 代码表明并非总是如此。

我们可以对 strcpy 进行相同的演变。

strcpy(char s1[],char s2[])
{
    int x;
    for (x=0; x<=strlen(s2); x++)
        s1[x]=s2[x];
}

请注意,在这里 <=for 循环中很重要,因为代码随后会复制 '\0'。务必复制 '\0'。如果省略它,稍后会发生严重错误,因为字符串没有结束符,因此长度未知。另请注意,此代码效率非常低,因为 strlen 在每次 for 循环中都会被调用。为了解决这个问题,您可以使用以下代码。

strcpy(char s1[],char s2[])
{
    int x,len;
     len=strlen(s2);
    for (x=0; x<=len; x++)
        s1[x]=s2[x];
}

指针版本类似。

strcpy(char *s1,char *s2)
{
    while (*s2 != '\0')
    {
        *s1 = *s2;
        s1++;
        s2++;
    }
}

您可以进一步压缩此代码。

strcpy(char *s1,char *s2)
{
    while (*s2)
        *s1++ = *s2++;
}

如果您愿意,甚至可以说 while (*s1++ = *s2++);strcpy 的第一个版本复制一个 120 字符的字符串 10,000 次需要 415 秒,第二个版本需要 14.5 秒,第三个版本 9.8 秒,第四个版本 10.3 秒。如您所见,指针在此处提供了显著的性能提升。

字符串库中 strcpy 函数的原型表明它旨在返回一个指向字符串的指针。

char *strcpy(char *s1,char *s2)

大多数字符串函数都返回一个字符串指针作为结果,strcpy 返回 s1 的值作为其结果。

将指针与字符串结合使用有时可以显著提高速度,如果您稍加思考,就可以利用这些优势。例如,假设您想删除字符串中的前导空格。您可能倾向于将字符移到空格上以将其删除。在 C 语言中,您可以完全避免这种移动。

#include <stdio.h>
#include <string.h>

int main()
{
    char s[100],*p;
     gets(s);
    p=s;
    while (*p==' ')
        p++;
    printf("%s\n",p);
    return 0;
}

这比移动技术快得多,特别是对于长字符串。

随着您的深入和阅读其他代码,您将掌握许多其他字符串技巧。实践是关键。