97、C++ 字符串视图及相关功能详解

C++ 字符串视图及相关功能详解

1. 字符串视图操作

字符串视图( basic_string_view )是 C++ 中用于高效处理字符串的轻量级对象,它提供了一系列实用的操作函数。

1.1 交换操作
constexpr void swap(basic_string_view& s) noexcept;

此函数的作用是交换当前字符串视图对象和参数 s 的值。

1.2 复制操作
constexpr size_type copy(charT* s, size_type n, size_type pos = 0) const;
  • 确定 rlen n size() - pos 中的较小值。
  • pos > size() ,则抛出 out_of_range 异常。
  • 要求 [s, s + rlen) 是一个有效的范围。
  • 其效果等同于 traits::copy(s, data() + pos, rlen)
  • 返回 rlen
  • 复杂度为 $O(rlen)$。
1.3 子字符串操作
constexpr basic_string_view substr(size_type pos = 0, size_type n = npos) const;
  • 确定 rlen n size() - pos 中的较小值。
  • pos > size() ,则抛出 out_of_range 异常。
  • 确定要引用的字符串的有效长度 rlen
  • 返回一个新的字符串视图,其数据从 data() + pos 开始,长度为 rlen
1.4 比较操作
constexpr int compare(basic_string_view str) const noexcept;
  • 确定 rlen 为当前字符串视图和 str 的长度中的较小值。
  • 通过调用 traits::compare(data(), str.data(), rlen) 来比较两个字符串。
  • 复杂度为 $O(rlen)$。
  • 返回值根据比较结果而定,具体如下表所示:
    | 条件 | 返回值 |
    | ---- | ---- |
    | size() < str.size() | < 0 |
    | size() == str.size() | 0 |
    | size() > str.size() | > 0 |

此外,还有多种不同参数形式的 compare 函数,它们的效果通常是通过调用 substr 函数进行转换后再比较。

1.5 前缀和后缀检查
constexpr bool starts_with(basic_string_view x) const noexcept;
constexpr bool starts_with(charT x) const noexcept;
constexpr bool starts_with(const charT* x) const;
constexpr bool ends_with(basic_string_view x) const noexcept;
constexpr bool ends_with(charT x) const noexcept;
constexpr bool ends_with(const charT* x) const;

这些函数用于检查字符串视图是否以指定的字符串、字符或字符指针开头或结尾。

2. 字符串视图搜索功能

字符串视图提供了多种搜索函数,如 find rfind find_first_of 等。

2.1 搜索函数的通用规则
  • 对于形式为 constexpr return-type F (const charT* s, size_type pos) const; 的成员函数,其效果等同于 return F (basic_string_view(s), pos);
  • 对于形式为 constexpr return-type F (const charT* s, size_type pos, size_type n) const; 的成员函数,其效果等同于 return F (basic_string_view(s, n), pos);
  • 对于形式为 constexpr return-type F (charT c, size_type pos) const noexcept; 的成员函数,其效果等同于 return F (basic_string_view(addressof(c), 1), pos);
2.2 具体搜索函数
constexpr size_type find(basic_string_view str, size_type pos = 0) const noexcept;
  • 找到满足以下条件的最低位置 xpos
  • pos <= xpos
  • xpos + str.size() <= size()
  • 对于 str 引用的字符串的所有元素 I ,都有 traits::eq(at(xpos + I), str.at(I))
  • 若能找到这样的 xpos ,则返回该位置;否则返回 npos
constexpr size_type rfind(basic_string_view str, size_type pos = npos) const noexcept;

find 类似,但找到的是满足条件的最高位置。

constexpr size_type find_first_of(basic_string_view str, size_type pos = 0) const noexcept;

找到满足以下条件的最低位置 xpos
- pos <= xpos
- xpos < size()
- 对于 str 引用的字符串的某个元素 I ,有 traits::eq(at(xpos), str.at(I))

constexpr size_type find_last_of(basic_string_view str, size_type pos = npos) const noexcept;

find_first_of 类似,但找到的是满足条件的最高位置。

constexpr size_type find_first_not_of(basic_string_view str, size_type pos = 0) const noexcept;

找到满足以下条件的最低位置 xpos
- pos <= xpos
- xpos < size()
- 对于 str 引用的字符串的所有元素 I ,都有 traits::eq(at(xpos), str.at(I)) 不成立。

constexpr size_type find_last_not_of(basic_string_view str, size_type pos = npos) const noexcept;

find_first_not_of 类似,但找到的是满足条件的最高位置。

3. 非成员比较函数

为了方便对字符串视图进行比较,提供了一系列非成员比较函数。

表达式 等效表达式
t == sv S(t) == sv
sv == t sv == S(t)
t != sv S(t) != sv
sv != t sv != S(t)
t < sv S(t) < sv
sv < t sv < S(t)
t > sv S(t) > sv
sv > t sv > S(t)
t <= sv S(t) <= sv
sv <= t sv <= S(t)
t >= sv S(t) >= sv
sv >= t sv >= S(t)

以下是 operator== 的示例实现:

template<class charT, class traits>
constexpr bool operator==(basic_string_view<charT, traits> lhs,
                          basic_string_view<charT, traits> rhs) noexcept {
    return lhs.compare(rhs) == 0;
}
4. 插入器和提取器
template<class charT, class traits>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>& os, basic_string_view<charT, traits> str);

此函数的作用是将字符串视图输出到输出流中。它的行为类似于格式化输出函数,会根据输出流的宽度和字符串视图的大小进行填充,最后将字符串视图插入到输出流中,并将输出流的宽度设置为 0。

5. 哈希支持

为了支持哈希操作,提供了对不同类型字符串视图的哈希特化:

template<> struct hash<string_view>;
template<> struct hash<u8string_view>;
template<> struct hash<u16string_view>;
template<> struct hash<u32string_view>;
template<> struct hash<wstring_view>;

这些特化是启用的,并且字符串视图对象的哈希值与相应字符串对象的哈希值相等。

6. 字符串视图字面量后缀

为了方便创建字符串视图对象,提供了一系列字面量后缀操作符:

constexpr string_view operator""sv(const char* str, size_t len) noexcept;
constexpr u8string_view operator""sv(const char8_t* str, size_t len) noexcept;
constexpr u16string_view operator""sv(const char16_t* str, size_t len) noexcept;
constexpr u32string_view operator""sv(const char32_t* str, size_t len) noexcept;
constexpr wstring_view operator""sv(const wchar_t* str, size_t len) noexcept;

这些操作符返回相应类型的字符串视图对象。

7. 空终止序列实用工具

C++ 标准库还提供了一系列与空终止序列相关的头文件和函数,这些头文件的内容和意义与 C 标准库的相应头文件基本相同。

7.1 <cctype> 头文件
namespace std {
    int isalnum(int c);
    int isalpha(int c);
    int isblank(int c);
    int iscntrl(int c);
    int isdigit(int c);
    int isgraph(int c);
    int islower(int c);
    int isprint(int c);
    int ispunct(int c);
    int isspace(int c);
    int isupper(int c);
    int isxdigit(int c);
    int tolower(int c);
    int toupper(int c);
}

这些函数用于字符分类和大小写转换。

7.2 <cwctype> 头文件
namespace std {
    using wint_t = see below ;
    using wctrans_t = see below ;
    using wctype_t = see below ;
    int iswalnum(wint_t wc);
    int iswalpha(wint_t wc);
    int iswblank(wint_t wc);
    int iswcntrl(wint_t wc);
    int iswdigit(wint_t wc);
    int iswgraph(wint_t wc);
    int iswlower(wint_t wc);
    int iswprint(wint_t wc);
    int iswpunct(wint_t wc);
    int iswspace(wint_t wc);
    int iswupper(wint_t wc);
    int iswxdigit(wint_t wc);
    int iswctype(wint_t wc, wctype_t desc);
    wctype_t wctype(const char* property);
    wint_t towlower(wint_t wc);
    wint_t towupper(wint_t wc);
    wint_t towctrans(wint_t wc, wctrans_t desc);
    wctrans_t wctrans(const char* property);
}
#define WEOF see below

这些函数用于宽字符的分类和转换。

7.3 <cstring> 头文件
namespace std {
    using size_t = see 16.2.4;
    void* memcpy(void* s1, const void* s2, size_t n);
    void* memmove(void* s1, const void* s2, size_t n);
    char* strcpy(char* s1, const char* s2);
    char* strncpy(char* s1, const char* s2, size_t n);
    char* strcat(char* s1, const char* s2);
    char* strncat(char* s1, const char* s2, size_t n);
    int memcmp(const void* s1, const void* s2, size_t n);
    int strcmp(const char* s1, const char* s2);
    int strcoll(const char* s1, const char* s2);
    int strncmp(const char* s1, const char* s2, size_t n);
    size_t strxfrm(char* s1, const char* s2, size_t n);
    const void* memchr(const void* s, int c, size_t n);
    void* memchr(void* s, int c, size_t n);
    const char* strchr(const char* s, int c);
    char* strchr(char* s, int c);
    size_t strcspn(const char* s1, const char* s2);
    const char* strpbrk(const char* s1, const char* s2);
    char* strpbrk(char* s1, const char* s2);
    const char* strrchr(const char* s, int c);
    char* strrchr(char* s, int c);
    size_t strspn(const char* s1, const char* s2);
    const char* strstr(const char* s1, const char* s2);
    char* strstr(char* s1, const char* s2);
    char* strtok(char* s1, const char* s2);
    void* memset(void* s, int c, size_t n);
    char* strerror(int errnum);
    size_t strlen(const char* s);
}
#define NULL see 16.2.3

这些函数用于字符串的复制、连接、比较、查找等操作。

7.4 <cwchar> 头文件
namespace std {
    using size_t = see 16.2.4;
    using mbstate_t = see below ;
    using wint_t = see below ;
    struct tm;
    int fwprintf(FILE* stream, const wchar_t* format, ...);
    int fwscanf(FILE* stream, const wchar_t* format, ...);
    int swprintf(wchar_t* s, size_t n, const wchar_t* format, ...);
    int swscanf(const wchar_t* s, const wchar_t* format, ...);
    int vfwprintf(FILE* stream, const wchar_t* format, va_list arg);
    int vfwscanf(FILE* stream, const wchar_t* format, va_list arg);
    int vswprintf(wchar_t* s, size_t n, const wchar_t* format, va_list arg);
    int vswscanf(const wchar_t* s, const wchar_t* format, va_list arg);
    int vwprintf(const wchar_t* format, va_list arg);
    int vwscanf(const wchar_t* format, va_list arg);
    int wprintf(const wchar_t* format, ...);
    int wscanf(const wchar_t* format, ...);
    wint_t fgetwc(FILE* stream);
    wchar_t* fgetws(wchar_t* s, int n, FILE* stream);
    wint_t fputwc(wchar_t c, FILE* stream);
    int fputws(const wchar_t* s, FILE* stream);
    int fwide(FILE* stream, int mode);
    wint_t getwc(FILE* stream);
    wint_t getwchar();
    wint_t putwc(wchar_t c, FILE* stream);
    wint_t putwchar(wchar_t c);
    wint_t ungetwc(wint_t c, FILE* stream);
    double wcstod(const wchar_t* nptr, wchar_t** endptr);
    float wcstof(const wchar_t* nptr, wchar_t** endptr);
    long double wcstold(const wchar_t* nptr, wchar_t** endptr);
    long int wcstol(const wchar_t* nptr, wchar_t** endptr, int base);
    long long int wcstoll(const wchar_t* nptr, wchar_t** endptr, int base);
    unsigned long int wcstoul(const wchar_t* nptr, wchar_t** endptr, int base);
    unsigned long long int wcstoull(const wchar_t* nptr, wchar_t** endptr, int base);
    wchar_t* wcscpy(wchar_t* s1, const wchar_t* s2);
    wchar_t* wcsncpy(wchar_t* s1, const wchar_t* s2, size_t n);
    wchar_t* wmemcpy(wchar_t* s1, const wchar_t* s2, size_t n);
    wchar_t* wmemmove(wchar_t* s1, const wchar_t* s2, size_t n);
    wchar_t* wcscat(wchar_t* s1, const wchar_t* s2);
    wchar_t* wcsncat(wchar_t* s1, const wchar_t* s2, size_t n);
    int wcscmp(const wchar_t* s1, const wchar_t* s2);
    int wcscoll(const wchar_t* s1, const wchar_t* s2);
    int wcsncmp(const wchar_t* s1, const wchar_t* s2, size_t n);
    size_t wcsxfrm(wchar_t* s1, const wchar_t* s2, size_t n);
    int wmemcmp(const wchar_t* s1, const wchar_t* s2, size_t n);
    const wchar_t* wcschr(const wchar_t* s, wchar_t c);
    wchar_t* wcschr(wchar_t* s, wchar_t c);
    size_t wcscspn(const wchar_t* s1, const wchar_t* s2);
    const wchar_t* wcspbrk(const wchar_t* s1, const wchar_t* s2);
    wchar_t* wcspbrk(wchar_t* s1, const wchar_t* s2);
    const wchar_t* wcsrchr(const wchar_t* s, wchar_t c);
    wchar_t* wcsrchr(wchar_t* s, wchar_t c);
    size_t wcsspn(const wchar_t* s1, const wchar_t* s2);
    const wchar_t* wcsstr(const wchar_t* s1, const wchar_t* s2);
    wchar_t* wcsstr(wchar_t* s1, const wchar_t* s2);
    wchar_t* wcstok(wchar_t* s1, const wchar_t* s2, wchar_t** ptr);
    const wchar_t* wmemchr(const wchar_t* s, wchar_t c, size_t n);
    wchar_t* wmemchr(wchar_t* s, wchar_t c, size_t n);
    size_t wcslen(const wchar_t* s);
    wchar_t* wmemset(wchar_t* s, wchar_t c, size_t n);
    size_t wcsftime(wchar_t* s, size_t maxsize, const wchar_t* format, const struct tm* timeptr);
    wint_t btowc(int c);
    int wctob(wint_t c);
    int mbsinit(const mbstate_t* ps);
    size_t mbrlen(const char* s, size_t n, mbstate_t* ps);
    size_t mbrtowc(wchar_t* pwc, const char* s, size_t n, mbstate_t* ps);
    size_t wcrtomb(char* s, wchar_t wc, mbstate_t* ps);
    size_t mbsrtowcs(wchar_t* dst, const char** src, size_t len, mbstate_t* ps);
    size_t wcsrtombs(char* dst, const wchar_t** src, size_t len, mbstate_t* ps);
}
#define NULL see 16.2.3
#define WCHAR_MAX see below
#define WCHAR_MIN see below
#define WEOF see below

这些函数用于宽字符的输入输出、字符串操作以及多字节字符与宽字符的转换。

7.5 <cuchar> 头文件
namespace std {
    using mbstate_t = see below ;
    using size_t = see 16.2.4;
    size_t mbrtoc8(char8_t* pc8, const char* s, size_t n, mbstate_t* ps);
    size_t c8rtomb(char* s, char8_t c8, mbstate_t* ps);
    size_t mbrtoc16(char16_t* pc16, const char* s, size_t n, mbstate_t* ps);
    size_t c16rtomb(char* s, char16_t c16, mbstate_t* ps);
    size_t mbrtoc32(char32_t* pc32, const char* s, size_t n, mbstate_t* ps);
    size_t c32rtomb(char* s, char32_t c32, mbstate_t* ps);
}

这些函数用于多字节字符与不同编码字符的转换。

8. 多字节 / 宽字符串和字符转换函数

这些函数主要用于多字节字符和宽字符之间的转换,部分函数在使用时需要注意数据竞争的问题。

int mbsinit(const mbstate_t* ps);
int mblen(const char* s, size_t n);
size_t mbstowcs(wchar_t* pwcs, const char* s, size_t n);
size_t wcstombs(char* s, const wchar_t* pwcs, size_t n);
int mbtowc(wchar_t* pwc, const char* s, size_t n);
int wctomb(char* s, wchar_t wchar);
size_t mbrlen(const char* s, size_t n, mbstate_t* ps);
size_t mbrtowc(wchar_t* pwc, const char* s, size_t n, mbstate_t* ps);
size_t wcrtomb(char* s, wchar_t wc, mbstate_t* ps);
size_t mbsrtowcs(wchar_t* dst, const char** src, size_t len, mbstate_t* ps);
size_t wcsrtombs(char* dst, const wchar_t** src, size_t len, mbstate_t* ps);
size_t mbrtoc8(char8_t* pc8, const char* s, size_t n, mbstate_t* ps);
size_t c8rtomb(char* s, char8_t c8, mbstate_t* ps);

其中, mbrtoc8 函数用于将多字节字符转换为 UTF - 8 编码的字符, c8rtomb 函数用于将 UTF - 8 编码的字符转换为多字节字符。

总结

本文详细介绍了 C++ 中字符串视图的各种操作、搜索功能、非成员比较函数、插入器和提取器、哈希支持以及相关的空终止序列实用工具和多字节 / 宽字符串转换函数。这些功能为 C++ 开发者提供了强大而高效的字符串处理能力,在实际应用中可以根据具体需求选择合适的函数进行使用。同时,在使用一些涉及多线程的函数时,需要注意数据竞争的问题,以确保程序的正确性和稳定性。

C++ 字符串视图及相关功能详解

9. 多字节/宽字符串和字符转换函数详解
9.1 mbrtoc8 函数
size_t mbrtoc8(char8_t* pc8, const char* s, size_t n, mbstate_t* ps);
  • 功能 :该函数用于将多字节字符转换为 UTF - 8 编码的字符。
  • 操作步骤
    1. s nullptr ,则等效于 mbrtoc8(nullptr, "", 1, ps)
    2. 检查从 s 指向的字节开始的最多 n 个字节,以确定完成下一个多字节字符(包括任何移位序列)所需的字节数。
    3. 如果确定下一个多字节字符完整且有效,则确定相应 UTF - 8 代码单元的值。若 pc8 不为 nullptr ,则将第一个(或唯一)这样的代码单元的值存储在 pc8 指向的对象中。后续调用将存储连续的 UTF - 8 代码单元,而无需消耗任何额外的输入,直到所有代码单元都被存储。
    4. 如果对应的 Unicode 字符是 U+0000,则得到的状态是初始转换状态。
  • 返回值
    • 若接下来的 n 个或更少字节完成了对应于 U+0000 Unicode 字符的多字节字符(即存储的值),则返回 0。
    • 若接下来的 n 个或更少字节完成了一个有效的多字节字符(即存储的值),则返回完成该多字节字符的字节数,该值在 1 到 n 之间(包含 1 和 n )。
    • 若上一次调用产生的下一个字符已被存储(本次调用未消耗输入中的任何字节),则返回 (size_t)(-3)
    • 若接下来的 n 个字节对一个不完整(但可能有效)的多字节字符有贡献,且所有 n 个字节都已处理(未存储任何值),则返回 (size_t)(-2)
    • 若发生编码错误,即接下来的 n 个或更少字节对一个完整且有效的多字节字符无贡献(未存储任何值),则将宏 EILSEQ 的值存储在 errno 中,返回 (size_t)(-1) ,并且转换状态未指定。
9.2 c8rtomb 函数
size_t c8rtomb(char* s, char8_t c8, mbstate_t* ps);
  • 功能 :该函数用于将 UTF - 8 编码的字符转换为多字节字符。
  • 操作步骤
    1. s nullptr ,则等效于 c8rtomb(buf, u8'\0', ps) ,其中 buf 是一个内部缓冲区。
    2. c8 完成了一个有效的 UTF - 8 代码单元序列,则确定表示该多字节字符(包括任何移位序列)所需的字节数,并将多字节字符表示存储在以 s 指向的元素为首的数组中。最多存储 MB_CUR_MAX 个字节。
    3. 若多字节字符是一个空字符,则存储一个空字节,并在前面加上恢复初始移位状态所需的任何移位序列;得到的状态是初始转换状态。
  • 返回值 :返回存储在数组对象中的字节数(包括任何移位序列)。若 c8 对对应于有效多字节字符的 char8_t 序列无贡献,则将宏 EILSEQ 的值存储在 errno 中,返回 (size_t)(-1) ,并且转换状态未指定。
  • 注意事项 :对 c8rtomb 函数使用 s nullptr 的调用可能会与其他使用 s nullptr c8rtomb 调用引入数据竞争。
10. 函数调用关系与流程图

为了更清晰地理解这些函数之间的调用关系,下面给出一个简单的 mermaid 流程图:

graph TD;
    A[mbsinit] -->|调用| B[mbrlen];
    A -->|调用| C[mbrtowc];
    A -->|调用| D[wcrtomb];
    A -->|调用| E[mbsrtowcs];
    A -->|调用| F[wcsrtombs];
    C -->|调用| G[mbrtoc8];
    D -->|调用| H[c8rtomb];

这个流程图展示了多字节/宽字符串和字符转换函数之间的一些基本调用关系。例如, mbsinit 函数可能会作为其他函数的前置检查,而 mbrtowc 可能会调用 mbrtoc8 进行多字节到 UTF - 8 字符的转换。

11. 实际应用场景与示例

在实际的 C++ 开发中,这些字符串视图和相关转换函数有很多应用场景。以下是一些示例:

11.1 字符串搜索与比较
#include <iostream>
#include <string_view>

int main() {
    std::string_view str = "Hello, World!";
    std::string_view sub = "World";

    // 查找子字符串
    size_t pos = str.find(sub);
    if (pos != std::string_view::npos) {
        std::cout << "Found 'World' at position: " << pos << std::endl;
    } else {
        std::cout << "Did not find 'World'" << std::endl;
    }

    // 比较字符串
    if (str.compare(0, 5, "Hello") == 0) {
        std::cout << "The first 5 characters are 'Hello'" << std::endl;
    }

    return 0;
}

在这个示例中,我们使用了 std::string_view find compare 函数进行字符串的搜索和比较操作。

11.2 多字节字符转换
#include <iostream>
#include <cuchar>
#include <cwchar>

int main() {
    char8_t utf8_char;
    char multi_byte[] = "你";
    mbstate_t state{};

    // 多字节字符转换为 UTF - 8 字符
    size_t result = mbrtoc8(&utf8_char, multi_byte, sizeof(multi_byte), &state);
    if (result > 0) {
        std::cout << "Converted multi - byte char to UTF - 8 successfully" << std::endl;
    }

    return 0;
}

此示例展示了如何使用 mbrtoc8 函数将多字节字符转换为 UTF - 8 字符。

12. 性能考虑

在使用这些字符串处理函数时,性能是一个重要的考虑因素。例如,字符串视图的搜索函数在最坏情况下的复杂度为 $O(size() * str.size())$,但实现应该尽量优化。在处理大规模字符串时,选择合适的函数可以显著提高性能。

  • 避免不必要的复制 :使用字符串视图可以避免对字符串进行不必要的复制,因为它只是对现有字符串的一个视图。
  • 选择合适的搜索算法 :对于搜索函数,一些实现可能会使用更高效的算法,如 KMP 算法或 Boyer - Moore 算法,以减少搜索时间。
13. 总结与建议

本文全面介绍了 C++ 中字符串视图及相关功能,包括字符串视图的操作、搜索功能、非成员比较函数、插入器和提取器、哈希支持,以及空终止序列实用工具和多字节/宽字符串转换函数。这些功能为 C++ 开发者提供了丰富的字符串处理能力。

在实际应用中,建议开发者:
- 合理使用字符串视图 :在需要处理字符串但不需要复制的场景中,优先使用字符串视图,以提高性能。
- 注意数据竞争 :在使用涉及多线程的函数时,如 c8rtomb 等,要注意数据竞争问题,确保程序的正确性和稳定性。
- 根据需求选择函数 :根据具体的应用场景,选择合适的字符串处理函数,以达到最佳的性能和效果。

通过深入理解和合理运用这些功能,开发者可以更高效地处理 C++ 中的字符串操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值