返回介绍

2. 字符串

发布于 2024-10-12 21:58:09 字数 4582 浏览 0 评论 0 收藏 0

年代久的语言在字符串处理上都有沉重负担,这需要去了解一下 ASCII 和 Unicode 背景。同时,应该抛弃一些老旧的概念(ANSI),统一使用基于 UCS/UTF 的执行和存储方案。

ASCII (美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,它主要用于显示现代英语。 Unicode 是为了解决跨语言、跨平台转换和处理需求,用统一编码方案容纳不同国家地区的文字,以解决传统编码方案不兼容问题,故又称作统一码、万国码。

Unicode 为每个字符分配一个称作 码点(code point) 的整数序号,对应方案叫做通用字符集(Universal Character Set, UCS)。依编码整数长度,分作 UCS-2 和 UCS-4 两种。UCS-4 采用 32 位整数,可容纳更多字符。 UCS 只规定了字符与码点的对应关系,并不涉及存储和显示。 UTF (Unicode Transformation Format)的作用是将码点整数转换为可存储和传输的字节格式,有 UTF-8、UTF-16、UTF-32 等多种方案。

其中 UTF-16、UTF-32 以码点为索引,可简单认为 UCS-2/UTF-16、UCS-4/UTF-32 意思相同。至于 UTF-8,采用变长格式。因与 ASCII 兼容,当下使用最为广泛。它对于英文为主的文本内容,有最高的存储效率。相比之下,等宽的 UTF-16、UTF-32 有更快的处理效率,故常被用作执行编码。

可在头部插入字节序标记(byte order mark,BOM),区分大小端(BE、LE)。故此,又可细分为 UTF-16LE、UTF-32BE 等。

字符

相比单字节 char ,宽字符整数类型 wchar_t 足以容纳 UCS-4 码点。

char c = '我';
         ^~~ warning: overflow in conversion from 'int' to 'char'
             
wchar_t c = L'我';
assert(sizeof(c) == 4);

类型前缀:

  • L : wchar_tint ),Unicode Code Point。
  • u8 : char , UTF-8。
  • u : char16_tunsigned short ), UTF-16,。
  • U : char32_tunsigned int ), UTF-32,。

支持转义,比如 '\n' , '\x6c49' , '\u6c49'

#include <uchar.h>

char32_t c = U'汉';
assert(c == 0x6c49);

将码点转换为不同 UTF 编码。

#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <uchar.h>

int main (void) 
{
    setlocale(LC_ALL, "en_US.UTF-8");

    // WCHAR to UTF-8
    char mb[10] = { 0 };
    int n = wctomb(mb, L'汉');

    assert(n == 3);
    assert(strcmp(mb, "汉") == 0);

    // UTF-8 to UTF-16
    char16_t out;
    mbstate_t state;
    size_t ns = mbrtoc16(&out, mb, n, &state);

    assert(ns == 3);
    assert(out == u'汉');

    return 0;
}

字符串

抛弃掉种种因历史造成的混乱定义不说,可简单归纳为:

  • byte string : 单字节,ASCII 字符串。
  • wide string : 宽字符,Unicode 字符串。
  • multibyte string : 多字节存储,默认 UTF-8 编码。
         执行                       存储                       执行
   +=============+         +==================+         +=============+
   | byte string | <-----> | multibyte string | <-----> | wide string |
   +-------------+         +==================+         +-------------+
   | ASCII       |                  |                   | Unicode     |
   +=============+                  |                   +=============+
                        +=======+========+========+
                        | UTF-8 | UTF-16 | UTF-32 |
                        +=======+========+========+
                        

执行是对文本进行处理。比如 '我' 应该是长度为 1 的字符,而非存储方案里的几个字节。对于字符,可判断它是字母、数字,还是大小写等。因此,执行面向字符(characters),而存储基于字节(bytes)。

所有字符串都是以 NULL 结尾的字符数组。字面量默认 UTF-8 编码,可添加类型前缀标示。

char s[] = "a 汉";

assert(sizeof(s) == 5);   // + NULL
assert(strlen(s) == 4);  

char s2[] = "";

assert(sizeof(s2) == 1);  // + NULL
assert(strlen(s2) == 0);
wchar_t s[] = L"我们 a";

assert(sizeof(s) == 16);  // + NULL    
assert(wcslen(s) == 3);   

可专门定义 UTF-16 和 UTF-32 编码字符串。

平台差异,wchar_t 宽度由编译器决定,故引入 char16_t、char32_t 类型。在 Linux 下,wchar_t 是 32 位整数。

标准库为 wchar_t 提供了和 char 几乎一致的字符和字符串函数。所以,用它处理 Unicode 最为便捷。

虽然 char32_t 和 wchar_t 基本一致,但更适合在指定 UTF 格式间转换,作为存储类型使用。

char32_t s[] = U"我们";
assert(sizeof(s) == 12);  // 包括 NULL

char32_t s[] = U"";
assert(sizeof(s) == 4);   // NULL

操作

以空白符分隔的相邻字面量会自动合并,还可以 \ 换行。

char *s = "hello" "," "world!";  // 习惯写成指针样式。
puts(s);
char *s = "hello, \
           world";              // 前面的空白视为内容组成部分。

puts(s);

用标准库函数,进行字符串编码转换。

int main (void) 
{
    setlocale(LC_ALL, "en_US.UTF-8");

    char *s = "我们";

    // UTF-8 to WCHAR
    wchar_t ws[4] = {0};
    int n = swprintf(ws, 4, L"%s", s);

    assert(n == 2);
    assert(wcscmp(ws, L"我们") == 0);
    wprintf(L"%ls\n", ws);

    // WCHAR to UTF-8
    char ss[10]= {0};
    n = snprintf(ss, 10, "%ls", ws);

    assert(n == 6);
    assert(strcmp(ss, s) == 0);
    
    return 0;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文