C语言中的字符串
C语言中的字符串仅仅是字符数组。字符串的长度由终止空字符('\0')确定。所以,例如,一个包含内容“abc”的字符串有四个字符:'a'、'b'、'c'和终止空字符('\0')。

终止空字符的值为零。

语法
在C语言中,字符串常量(字面量)由双引号(")包围,例如 "Hello world!",并编译为指定的char值数组,附加一个值为零的终止空字符代码,用以标记字符串的结尾。字符串常量的类型是char[]

反斜杠转义
字符串字面量在源代码中不能直接包含嵌入的换行符或其他控制字符,或某些在字符串中有特殊意义的字符。

为了在字符串中包含这些字符,可以使用反斜杠转义,如下所示:

转义字符 含义
\ 字面意义的反斜杠
" 双引号
' 单引号
\n 换行符(换行)
\r 回车符
\b 退格符
\t 水平制表符
\f 换页符
\a 警告(铃声)
\v 垂直制表符
? 问号(用于转义三字节符号)
\nnn 八进制值为nnn的字符
\xhh 十六进制值为hh的字符

宽字符字符串
C语言支持宽字符字符串,定义为wchar_t类型的数组,wchar_t是至少16位的值。它们的表示方法是,在字符串前加一个L,如下所示:

wchar_t *p = L"Hello world!";

这一特性允许使用超过256个不同字符的字符串(尽管也可以使用可变长度的char字符串)。这些字符串以零值的wchar_t结尾。宽字符字符串不被 <string.h> 函数支持,而是有自己的一套函数,声明在 <wchar.h> 中。

字符编码
C语言标准没有指定char和wchar_t所表示的字符编码,除了值0x00和0x0000表示字符串的结尾,而不是字符之外。字符编码直接影响输入和输出代码,其他代码应不受太大影响。编辑器也应该能够处理编码,以便在源代码中编写字符串。

字符编码有三种主要类型:

  1. 每字符一个字节:通常基于ASCII。最多支持255个不同的字符和零终止字符。
  2. 可变长度char字符串:允许支持超过255个不同字符。这类字符串作为普通的char数组书写,通常基于ASCII编码,常见的编码有UTF-8或Shift JIS。
  3. 宽字符字符串:它们是wchar_t值的数组。UTF-16是最常见的编码,它也是可变长度的,意味着一个字符可能占用两个wchar_t。

<string.h> 标准头文件
由于程序员发现处理原始字符串不太方便,因此编写了<string.h>库。它并不是一个精心设计的方案,而是不同作者多年积累的贡献。

首先,字符串库中有三类函数:

  • mem函数:操作任意字符序列,不考虑空字符;
  • str函数:操作以空字符终止的字符序列;
  • strn函数:操作非空字符的序列。

常用的字符串函数
在字符串库中,最常用的九个函数是:

  1. strcat - 连接两个字符串
  2. strchr - 字符串扫描操作
  3. strcmp - 比较两个字符串
  4. strcpy - 复制一个字符串
  5. strlen - 获取字符串的长度
  6. strncat - 将一个字符串与另一个字符串的部分连接
  7. strncmp - 比较两个字符串的部分
  8. strncpy - 复制字符串的一部分
  9. strrchr - 字符串扫描操作

其他函数,如strlwr(转换为小写)、strrev(返回字符串的反转)、strupr(转换为大写)等,可能很流行;然而,这些函数既没有被C标准规定,也没有被单一Unix标准(Single Unix Specification)规定。并且这些函数是否返回原始字符串的副本,或者原地转换字符串,也是未指定的。

strcat 函数

char *strcat(char * restrict s1, const char * restrict s2);

有些人建议使用 strncat()strlcat() 来代替 strcat,以避免缓冲区溢出问题。

strcat() 函数将指向 s2 的字符串(包括终止空字节)追加到指向 s1 的字符串的末尾。s2 的首字节会覆盖 s1 末尾的空字节。如果复制发生在重叠的对象之间,行为是未定义的。该函数返回 s1

此函数用于将一个字符串附加到另一个字符串的末尾。必须确保第一个字符串(s1)有足够的空间来存储两个字符串。

注意
C99 标准增加了对参数的 restrict 要求。

示例:

#include <stdio.h>
#include <string.h>
...
static const char *colors[] = {"Red", "Orange", "Yellow", "Green", "Blue", "Purple"};
static const char *widths[] = {"Thin", "Medium", "Thick", "Bold"};
...
char penText[20];
...
int penColor = 3, penThickness = 2;
strcpy(penText, colors[penColor]);
strcat(penText, widths[penThickness]);
printf("My pen is %s\n", penText); /* 打印 'My pen is GreenThick' */

在调用 strcat() 之前,目标字符串必须当前包含一个以空字符结束的字符串,或者第一个字符必须已经初始化为空字符(例如 penText[0] = '\0';)。

以下是 strcat 的一个公共领域实现:

#include <string.h>
/* strcat */
char *strcat(char *restrict s1, const char *restrict s2) {
    char *s = s1;
    /* 将 s 移动到 s1 的末尾。 */
    while (*s != '\0')
        s++;
    /* 将 s2 的内容复制到 s1 的末尾空间。 */
    strcpy(s, s2);
    return s1;
}

strchr 函数

char *strchr(const char *s, int c);

strchr() 函数用于定位 s 指向的字符串中首次出现的字符 c(转换为字符)。终止空字节被视为字符串的一部分。该函数返回找到的字符的位置,或者如果没有找到字符,则返回空指针。

此函数用于在字符串中查找特定字符。

曾经在历史上,该函数被命名为 index。然而,strchr 这个名称虽然略显晦涩,但符合一般命名模式。

以下是 strchr 函数的公共领域实现:

#include <string.h>
/* strchr */
char *strchr(const char *s, int c)
{
    char ch = c;
    /* 扫描 s 查找字符。完成此循环后,
       s 将指向字符串的结尾,或者指向我们要查找的字符。 */
    while (*s != '\0' && *s != ch)
        s++;
    return (*s == ch) ? (char *)s : NULL;
}

strcmp 函数

int strcmp(const char *s1, const char *s2);

strcmp() 函数进行基本的字符串比较。它接受两个字符串作为参数,如果第一个字符串按字典顺序小于第二个字符串,则返回小于零的值;如果第一个字符串按字典顺序大于第二个字符串,则返回大于零的值;如果两个字符串相等,则返回零。比较是通过逐字符地比较字符的编码(ASCII 值)来进行的。

这种简单的字符串比较方式现在通常被认为在排序字符串列表时不可接受。存在更先进的算法,它们能够按字典顺序对字符串进行排序,并能解决一些问题,比如 strcmp() 会认为 "Alpha2" 大于 "Alpha12"(在这个例子中,"Alpha2" 比 "Alpha12" 大,因为字符集中的 '2' 排在 '1' 后面)。我们想要表达的是,不要单独使用 strcmp() 来进行通用的字符串排序,尤其是在商业或专业代码中。

strcmp() 函数比较指向 s1 的字符串和指向 s2 的字符串。非零返回值的符号由比较的字符串中第一对不同字节(都解释为 unsigned char 类型)之间的差异决定。比较完成后,strcmp() 返回一个大于、等于或小于零的整数,分别表示指向 s1 的字符串大于、等于或小于指向 s2 的字符串。

由于单纯比较指针本身并没有实际用途,除非是在同一数组内比较指针,strcmp() 函数执行的是对两个指针所指向字符串的字典顺序比较。

该函数在比较中非常有用,例如:

if (strcmp(s, "whatever") == 0) /* 做某事 */
    ;

strcmp() 使用的字符排序序列等价于计算机本机的字符集。关于排序的唯一保证是数字字符 '0' 到 '9' 会按照顺序排列。

以下是 strcmp 的公共领域实现:

#include <string.h>
/* strcmp */
int strcmp(const char *s1, const char *s2)
{
    unsigned char uc1, uc2;
    /* 将 s1 和 s2 移动到各自字符串中的第一个不同字符处,
       如果字符串相同,则移到字符串的结尾。 */
    while (*s1 != '\0' && *s1 == *s2) {
        s1++;
        s2++;
    }
    /* 将字符作为 unsigned char 比较,并返回差值。 */
    uc1 = (*(unsigned char *)s1);
    uc2 = (*(unsigned char *)s2);
    return ((uc1 < uc2) ? -1 : (uc1 > uc2));
}

strcpy 函数

char *strcpy(char *restrict s1, const char *restrict s2);

一些人建议总是使用 strncpy() 而不是 strcpy,以避免缓冲区溢出问题。

strcpy() 函数将指向 s2 的 C 字符串(包括终止空字节)复制到指向 s1 的数组中。如果复制发生在重叠的对象之间,行为是未定义的。该函数返回 s1。没有用于指示错误的返回值:如果 strcpy() 的参数正确,并且目标缓冲区足够大,该函数永远不会失败。

示例:

#include <stdio.h>
#include <string.h>
/* ... */
static const char *penType = "round";
/* ... */
char penText[20];
/* ... */
strcpy(penText, penType);

重要事项
你必须确保目标缓冲区(s1)能够容纳源数组中的所有字符,包括终止空字节。否则,strcpy() 将会覆盖缓冲区末尾以外的内存,导致缓冲区溢出,这可能会导致程序崩溃,或者被黑客利用来危害计算机的安全。

以下是 strcpy 函数的公共领域实现:

#include <string.h>
/* strcpy */
char *strcpy(char *restrict s1, const char *restrict s2)
{
    char *dst = s1;
    const char *src = s2;
    /* 使用循环进行复制 */
    while ((*dst++ = *src++) != '\0')
        ; /* 循环体为空 */
    /* 返回目标字符串 */
    return s1;
}

strlen 函数

size_t strlen(const char *s);

strlen() 函数计算字符串中字符的数量,不包括终止空字节。它返回字符串的字节数。没有返回值用于表示错误。

以下是 strlen 的公共领域实现:

#include <string.h>
/* strlen */
size_t strlen(const char *s)
{
    const char *p = s; /* 字符常量指针 */
    /* 遍历 s 中的数据 */
    while (*p != '\0')
        p++;
    return (size_t)(p - s);
}

注意:

const char *p = s;

这行代码声明并初始化了指针 p,指向字符常量,即 p 不能改变它所指向的值。


strncat 函数

char *strncat(char *restrict s1, const char *restrict s2, size_t n);

strncat() 函数将最多 n 个字节(不包括终止空字节和随后的字节)从 s2 指向的数组追加到 s1 指向的字符串末尾。s2 的初始字节覆盖 s1 末尾的空字节。始终会在结果末尾追加一个终止空字节。如果复制发生在重叠的对象之间,行为是未定义的。该函数返回 s1

以下是 strncat 的公共领域实现:

#include <string.h>
/* strncat */
char *strncat(char *restrict s1, const char *restrict s2, size_t n)
{
    char *s = s1;
    /* 遍历 s1 中的数据 */
    while (*s != '\0')
        s++;
    /* s 现在指向 s1 的终止空字符,现在从 s2 中复制最多 n 个字节
       到 s 中,遇到空字符时停止。
       在这里不安全使用 strncpy,因为它会精确地复制 n 个字符,
       并在必要时填充 NULL。 */
    while (n != 0 && (*s = *s2++) != '\0') {
        n--;
        s++;
    }
    if (*s != '\0')
        *s = '\0';
    return s1;
}

strncmp 函数

int strncmp(const char *s1, const char *s2, size_t n);

strncmp() 函数比较从 s1 指向的数组和 s2 指向的数组中的最多 n 个字节(遇到空字节后面的字节不进行比较)。非零返回值的符号由比较的字符串中第一对不同字节(都解释为 unsigned char 类型)之间的差异决定。关于返回值的解释,请参见 strcmp

该函数与 strcmp 函数类似,常用于比较。

以下是 strncmp 的公共领域实现:

#include <string.h>
/* strncmp */
int strncmp(const char *s1, const char *s2, size_t n)
{
    unsigned char uc1, uc2;
    /* 如果没有可比较的字节,返回零 */
    if (n == 0)
        return 0;
    /* 循环比较字节 */
    while (n-- > 0 && *s1 == *s2) {
        /* 如果已用完字节或遇到空字符,返回零
           因为我们已经知道 *s1 == *s2 */
        if (n == 0 || *s1 == '\0')
            return 0;
        s1++;
        s2++;
    }
    uc1 = (*(unsigned char *)s1);
    uc2 = (*(unsigned char *)s2);
    return ((uc1 < uc2) ? -1 : (uc1 > uc2));
}

strncpy 函数

char *strncpy(char *restrict s1, const char *restrict s2, size_t n);

strncpy() 函数将最多 n 个字节(包括终止空字节后的字节不被复制)从 s2 指向的数组复制到 s1 指向的数组。如果复制发生在重叠的对象之间,行为是未定义的。如果 s2 指向的数组字符串长度小于 n 字节,则会向 s1 中追加空字节,直到写入 n 字节。该函数返回 s1;没有返回值用于指示错误。

如果 s2 字符串的长度超过 n 字节,则可能导致函数返回一个没有终止空字节的字符串。

以下是 strncpy 的公共领域实现:

#include <string.h>
/* strncpy */
char *strncpy(char *restrict s1, const char *restrict s2, size_t n)
{
    char *dst = s1;
    const char *src = s2;
    /* 一次复制一个字节 */
    while (n > 0) {
        n--;
        if ((*dst++ = *src++) == '\0') {
            /* 如果在这里,我们找到了 s2 的终止空字符,
               使用 memset 在 s1 的末尾填充空字节 */
            memset(dst, '\0', n);
            break;
        }
    }
    return s1;
}

strrchr 函数

char *strrchr(const char *s, int c);

strrchr 函数类似于 strchr 函数,区别在于 strrchr 返回的是 cs 中最后一次出现的位置,而不是第一次。

strrchr() 函数将在字符串 s 中定位 c(转换为字符类型)的最后一次出现。终止空字节被视为字符串的一部分。它的返回值类似于 strchr 的返回值。

历史上,这个函数曾被命名为 rindex。尽管 strrchr 名称看起来有些难以理解,但它符合一般的命名规则。

以下是 strrchr 的公共领域实现:

#include <string.h>
/* strrchr */
char *strrchr(const char *s, int c)
{
    const char *last = NULL;
    /* 如果我们要查找的字符是终止空字符,
       那么只需要查找该字符,因为字符串中只有一个空字符 */
    if (c == '\0')
        return strchr(s, c);
    /* 循环查找,找到最后一次匹配字符后停止 */
    while ((s = strchr(s, c)) != NULL) {
        last = s;
        s++;
    }
    return (char *) last;
}

不太常用的字符串函数

不太常用的字符串函数包括:

  • memchr - 查找内存中的字节
  • memcmp - 比较内存中的字节
  • memcpy - 复制内存中的字节
  • memmove - 在内存中复制字节,允许重叠区域
  • memset - 设置内存中的字节
  • strcoll - 根据特定区域的排序序列比较字节
  • strcspn - 获取互补子字符串的长度
  • strerror - 获取错误信息
  • strpbrk - 查找字符串中的字节
  • strspn - 获取子字符串的长度
  • strstr - 查找子字符串
  • strtok - 将字符串分割为多个标记
  • strxfrm - 转换字符串

复制函数

memcpy 函数

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);

memcpy() 函数将从 s2 指向的对象复制 n 个字节到 s1 指向的对象。如果复制发生在重叠的对象之间,行为是未定义的。该函数返回 s1

由于该函数不需要处理重叠情况,因此它可以进行最简单的复制操作。

以下是 memcpy 的公共领域实现:

#include <string.h>
/* memcpy */
void *memcpy(void *restrict s1, const void *restrict s2, size_t n)
{
    char *dst = s1;
    const char *src = s2;
    /* 循环并复制 */
    while (n-- != 0)
        *dst++ = *src++;
    return s1;
}

memmove 函数

void *memmove(void *s1, const void *s2, size_t n);

memmove() 函数将从 s2 指向的对象复制 n 个字节到 s1 指向的对象。复制的过程就像先将 s2 指向的对象中的 n 个字节复制到一个不与 s1s2 指向的对象重叠的临时数组中,然后再将这 n 个字节从临时数组复制到 s1 指向的对象中。该函数返回 s1 的值。

实现时,为了避免直接的升序复制发生重叠,可以通过检查条件,如果条件满足则进行降序复制。

以下是一个公共领域的 memmove 实现,尽管它不是完全便携的:

#include <string.h>
/* memmove */
void *memmove(void *s1, const void *s2, size_t n) 
{
    /* 注意:这些指针不需要指向无符号字符 */
    char *p1 = s1;
    const char *p2 = s2;
    /* 检测是否存在阻止升序复制的重叠 */
    if (p2 < p1 && p1 < p2 + n) {
        /* 进行降序复制 */
        p2 += n;
        p1 += n;
        while (n-- != 0) 
            *--p1 = *--p2;
    } else 
        while (n-- != 0) 
            *p1++ = *p2++;
    return s1; 
}

memcmp 函数

int memcmp(const void *s1, const void *s2, size_t n);

memcmp() 函数将比较 s1s2 所指向的对象中的前 n 个字节(每个字节被解释为无符号字符)。非零返回值的符号由在被比较的对象中第一个不同字节的值差决定(两个字节均解释为无符号字符)。

以下是 memcmp 的公共领域实现:

#include <string.h>
/* memcmp */
int memcmp(const void *s1, const void *s2, size_t n)
{
    const unsigned char *us1 = (const unsigned char *) s1;
    const unsigned char *us2 = (const unsigned char *) s2;
    while (n-- != 0) {
        if (*us1 != *us2)
            return (*us1 < *us2) ? -1 : +1;
        us1++;
        us2++;
    }
    return 0;
}

strcollstrxfrm 函数

int strcoll(const char *s1, const char *s2);
size_t strxfrm(char *s1, const char *s2, size_t n);

ANSI C 标准规定了两个特定于区域设置的比较函数。

  • strcoll 函数将比较 s1 指向的字符串与 s2 指向的字符串,两者都被解释为符合当前区域设置中 LC_COLLATE 类别的规则。返回值类似于 strcmp
  • strxfrm 函数将 s2 指向的字符串转换,并将结果存放在 s1 指向的数组中。转换后的字符串与 strcmp 函数应用于这两个转换后的字符串时,返回的值应与 strcoll 函数对原始字符串的结果一致。转换后的字符串最多包含 n 个字符(包括终止空字符)。如果 n 为零,s1 可以是空指针。如果发生重叠复制,行为是未定义的。函数返回转换后字符串的长度。

这些函数使用较少,编写起来不简单,因此在此部分没有提供代码。


memchr 函数

void *memchr(const void *s, int c, size_t n);

memchr() 函数将查找 s 指向的对象中前 n 个字节(每个字节被解释为无符号字符)中首次出现 c(转换为无符号字符)的字节。如果没有找到 c,则返回空指针。

以下是 memchr 的公共领域实现:

#include <string.h>
/* memchr */
void *memchr(const void *s, int c, size_t n)
{
    const unsigned char *src = s;
    unsigned char uc = c;
    while (n-- != 0) {
        if (*src == uc)
            return (void *) src;
        src++;
    }
    return NULL;
}

strcspnstrpbrkstrspn 函数

size_t strcspn(const char *s1, const char *s2);
char *strpbrk(const char *s1, const char *s2);
size_t strspn(const char *s1, const char *s2);
  • strcspn 函数计算字符串 s1 中最大初始片段的长度,该片段完全由不属于 s2 中的字符组成。
  • strpbrk 函数在字符串 s1 中查找第一个出现在 s2 中的字符,并返回指向该字符的指针,如果没有找到则返回空指针。
  • strspn 函数计算字符串 s1 中最大初始片段的长度,该片段完全由属于 s2 中的字符组成。

这三个函数除了测试条件和返回值不同外,其余部分非常相似。

以下是 strcspnstrpbrkstrspn 的公共领域实现:

#include <string.h>
/* strcspn */
size_t strcspn(const char *s1, const char *s2)
{
    const char *sc1;
    for (sc1 = s1; *sc1 != '\0'; sc1++)
        if (strchr(s2, *sc1) != NULL)
            return (sc1 - s1);
    return sc1 - s1;            /* 终止的空字符匹配 */
}
#include <string.h>
/* strpbrk */
char *strpbrk(const char *s1, const char *s2)
{
    const char *sc1;
    for (sc1 = s1; *sc1 != '\0'; sc1++)
        if (strchr(s2, *sc1) != NULL)
            return (char *)sc1;
    return NULL;                /* 终止的空字符匹配 */
}
#include <string.h>
/* strspn */
size_t strspn(const char *s1, const char *s2)
{
    const char *sc1;
    for (sc1 = s1; *sc1 != '\0'; sc1++)
        if (strchr(s2, *sc1) == NULL)
            return (sc1 - s1);
    return sc1 - s1;            /* 终止的空字符不匹配 */
}

strstr 函数

char *strstr(const char *haystack, const char *needle);

strstr() 函数用于在 haystack 指向的字符串中查找 needle 指向的字节序列(不包括终止空字符)第一次出现的位置。该函数返回 haystack 中匹配字符串的指针,若没有找到则返回空指针。如果 needle 是一个空字符串,函数将返回 haystack

以下是 strstr 的公共领域实现:

#include <string.h>
/* strstr */
char *strstr(const char *haystack, const char *needle)
{
    size_t needlelen;
    /* 检查空 needle 的情况 */
    if (*needle == '\0')
        return (char *) haystack;
    needlelen = strlen(needle);
    for (; (haystack = strchr(haystack, *needle)) != NULL; haystack++)
        if (memcmp(haystack, needle, needlelen) == 0)
            return (char *) haystack;
    return NULL;
}

strtok 函数

char *strtok(char *restrict s1, const char *restrict delimiters);

strtok() 函数通过多次调用将 s1 指向的字符串分解为一系列标记,每个标记由 delimiters 指向的字符串中的一个字节分隔。序列中的第一次调用以 s1 作为第一个参数,后续的调用以空指针作为第一个参数。每次调用时,delimiters 可以不同。

第一次调用会在 s1 指向的字符串中查找第一个不属于当前分隔符字符串 delimiters 中的字节。如果没有找到这样的字节,则 s1 中没有标记,strtok() 返回空指针。如果找到了该字节,则它是第一个标记的起始位置。

接下来,strtok() 会继续从当前字节开始查找包含在 delimiters 中的字节。如果没有找到这样的字节,当前标记将扩展到 s1 字符串的结尾,随后的标记查找将返回空指针。如果找到了这样的字节,它将被覆盖为空字节,以终止当前标记。strtok() 函数会保存下一个字节的指针,从该字节开始进行下一个标记的查找。

每次后续调用时,使用空指针作为第一个参数,开始从保存的指针位置继续查找标记,行为与上面描述的一致。

strtok() 函数不需要是重入的。一个不需要重入的函数不要求是线程安全的。

由于 strtok() 函数需要在调用之间保存状态,且不能同时有两个标记器工作,单一的 Unix 标准定义了类似的函数 strtok_r(),该函数不需要保存状态。其原型如下:

char *strtok_r(char *s, const char *delimiters, char **lasts);

strtok_r() 函数将 s 视为一个由零个或多个文本标记组成的字符串,标记之间由一个或多个来自 delimiters 分隔符字符串的字符隔开。lasts 参数指向一个用户提供的指针,该指针保存 strtok_r() 在扫描同一字符串时所需的状态信息。

第一次调用时,s 指向一个空终止的字符串,delimiters 指向一个空终止的分隔符字符字符串,lasts 的值被忽略。strtok_r() 函数将返回第一个标记的第一个字符的指针,并将返回的标记后立即用空字符终止,并更新 lasts 指向的指针。

后续的调用时,s 为空指针,lasts 保持不变,使得后续调用从 s 字符串中继续返回标记,直到没有标记剩下。分隔符字符串 delimiters 每次可以不同。当 s 中没有标记时,返回空指针。

以下是 strtokstrtok_r 的公共领域代码,其中将前者作为后者的特例来实现:

#include <string.h>
/* strtok_r */
char *strtok_r(char *s, const char *delimiters, char **lasts)
{
    char *sbegin, *send;
    sbegin = s ? s : *lasts;
    sbegin += strspn(sbegin, delimiters); // 跳过分隔符
    if (*sbegin == '\0') {
        *lasts = "";
        return NULL;
    }
    send = sbegin + strcspn(sbegin, delimiters); // 查找下一个分隔符
    if (*send != '\0')
        *send++ = '\0'; // 结束当前标记
    *lasts = send;
    return sbegin;
}
/* strtok */
char *strtok(char *restrict s1, const char *restrict delimiters)
{
    static char *ssave = "";
    return strtok_r(s1, delimiters, &ssave);
}

杂项函数

这些函数不属于上述任何一种类别。


memset 函数

void *memset(void *s, int c, size_t n);

memset() 函数将 c 转换为无符号字符,然后将该字符存储到 s 指向的内存的前 n 个字节中。

以下是 memset 的公共领域实现:

#include <string.h>
/* memset */
void *memset(void *s, int c, size_t n)
{
    unsigned char *us = s;
    unsigned char uc = c;
    while (n-- != 0)
        *us++ = uc;
    return s;
}

strerror 函数

char *strerror(int errorcode);

该函数返回与 errorcode 参数相对应的本地化错误信息。根据不同的情况,这个函数可能非常简单,但因为实现可能会有所不同,本作者不再提供实现。

单一 Unix 系统版本 3 中有一个变体 strerror_r,其原型为:

int strerror_r(int errcode, char *buf, size_t buflen);

该函数将错误信息存储在 buf 中,buf 的大小为 buflen


示例

要确定字符串的字符数,可以使用 strlen() 函数:

#include <stdio.h>
#include <string.h>
...
int length, length2;
char *turkey;
static char *flower = "begonia";
static char *gemstone = "ruby ";

length = strlen(flower);
printf("Length = %d\n", length); // 打印 'Length = 7'
length2 = strlen(gemstone);

turkey = malloc(length + length2 + 1);
if (turkey) {
    strcpy(turkey, gemstone);
    strcat(turkey, flower);
    printf("%s\n", turkey); // 打印 'ruby begonia'
    free(turkey);
}

请注意,为 turkey 分配的内存量是字符串长度之和加一。这个额外的空间是为终止空字符所预留的,空字符在字符串的长度计算中是不包含的。


练习

  • 字符串函数使用了大量的循环结构。有无可能便携地展开这些循环?
  • 目前的库中是否缺少某些函数?

参考文献

  • A Little C Primer/C String Function Library
  • C++ 编程/代码/输入输出/流/string
  • 由于标准 string.h 库中的许多函数易受到缓冲区溢出错误的影响,一些人建议避免使用 string.h 库和 "C 风格字符串",而是使用动态字符串 API,如字符串库比较中列出的那些。
  • 还有一个非常简单的公共领域 concat() 函数,可以安全地连接任意数量的字符串,并且可以在便携的 C/C++ 代码中使用。
最后修改: 2025年01月12日 星期日 13:29