C语言内中文I/O及处理方法

C语言内处理不同的字符编码一直是个重要问题,正好今天遇到了需要处理读取/输出中文的问题,把研究结果在这里记录一下。

wchar_t 类型

说明

定义于头文件 wchar.h 内,存储宽字符类型;wchar_t 类型的宽字符串可与使用 char 类型的多字节字符串互相转换。

宽字符串字面量在字面量前加 L 标记。e.g. L"s-char-sequence"

相关类型

wint_t 类型是可保有任何合法宽字符,并至少多出一个值的整数类型 。

I/O 函数

本节参考自中文 cppreference 的文件输入/输出一节。

无格式 I/O

函数名函数原型说明
fgetwc/getwcwint_t fgetwc( FILE *stream );从文件流获取一个宽字符
fgetwswchar_t *fgetws( wchar_t *str, int count, FILE *stream );从文件流获取一个宽字符串
fputwc/putwcwint_t fputwc( wchar_t ch, FILE *stream );将一个宽字符写入文件流
fputwsint fputws( const wchar_t *str, FILE *stream );将一个宽字符串写入文件流
getwcharwint_t getwchar(void);stdin 读取一个宽字符
putwcharwint_t putwchar( wchar_t ch );将一个宽字符写入stdout
ungetwcwint_t ungetwc( wint_t ch, FILE *stream );将一个宽字符送回文件流

有格式 I/O

wscanf 系列
函数名函数原型说明
wscanfint wscanf( const wchar_t *format, ... );stdin 读取格式化宽字符输入
fwscanfint fwscanf( FILE *stream, const wchar_t *format, ... );从文件流读取格式化宽字符输入
swscanfint swscanf( const wchar_t *buffer, const wchar_t *format, ... );从缓冲区读取格式化宽字符输入
wprintf 系列
函数名函数原型说明
wprintfint wprintf( const wchar_t *format, ... );打印格式化输出到 stdout
fwprintfint fwprintf( FILE *stream, const wchar_t* format, ... );打印格式化输出到文件流
swprintfint swprintf( wchar_t *buffer, size_t bufsz, const wchar_t* format, ... );打印格式化输出到缓冲区
格式化说明符与使用方式
格式化说明符说明
%lc对应 wint_t 类型
%ls对应 wchar_t * 类型

wscanf/wprintf 系列中的 format 参数若是宽字符串字面量,亦须确保加上了对应的 L 标记。

swprintf 中的 bufsz 参数保证最多会写入 bufsz - 1 个宽字符,再加空终止符。

注意:若 wscanf 系列函数在赋值首个接收参数前出现读取失败,返回值仍为 EOF 而非 wchar.h 中定义的宏 WEOF

示例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <locale.h>
#include <wchar.h>
 
int main(void)
{
    char narrow_str[] = "z\u00df\u6c34\U0001f34c";
                    // 或 "zß水🍌"
                    // 或 "\x7a\xc3\x9f\xe6\xb0\xb4\xf0\x9f\x8d\x8c";
    wchar_t warr[29]; // 期待的字符串为 28 字节加 1 个空终止符
    setlocale(LC_ALL, "en_US.utf8");
    swprintf(warr, sizeof warr/sizeof *warr,
              L"Converted from UTF-8: '%s'", narrow_str);
    wprintf(L"%ls\n", warr);
}

窄字符 scanf/printf 系列函数对宽字符(串)的处理

本节内容参考自浅谈C中的wprintf和宽字符显示

窄字符之 scanf/printf 系列函数亦可使用 %ls/%lc 格式说明符来处理 wchar_t */wchar_t 类型。但由于 scanf/printf 系列函数用于 byte stream,其输入/输出流中的每个字符占 1 bytewscanf/wprintf 系列函数用于 wide stream,其输入/输出流中的每个字符多于 1 byte,故其有区别如下:

组合说明
scanf/printf + %sscanf/printf将将指针对应缓冲区中的内容视作普通字符串,之后逐个字节输出
scanf/printf + %lsscanf/printf 将指针对应缓冲区中的内容视作宽字符串,按照 locale 的设定,将其中的每个字符隐式调用 wcrtomb() 函数将其转换成多字节字符串,之后逐个字节输出
wscanf/wprintf + %swscanf/wprintf 将指针对应缓冲区中的内容视作普通字符串,按照 locale 的设定,将其中的每个字符隐式调用 mbrtowc() 函数将其转换成宽字符串,之后逐个宽字符输出
wscanf/wprintf + %lswscanf/wprintf 将指针对应缓冲区中的内容视作宽字符串,之后逐个宽字符输出

文件读写

本节内容参考自 Linux Manual PageVisual Studio 2019 文档

C语言没有处理宽字符串或其他编码的特殊文件I/O函数,若需指定采用特殊编码读写文件,需采用一个gcc/msvc扩展,在读写模式字符串 flag 末尾加入额外的 ,ccs=encoding 字段来指示读取文件所用编码。

encoding 为 UTF-8 时,应当使用 ,ccs=utf-8

示例
1
2
3
4
FILE *fp = fopen("test.in", "r, ccs=utf-8");
fgetws(s, 2047, fp);
wprintf(L"%ls\n", s);
fclose(fp);

其他相关函数

本节内容参考自中文 cppreference 的 空终止宽字符串

限于篇幅,以下只列出常用函数。

字符串操作

函数名函数原型说明
wcscpywchar_t *wcscpy( wchar_t *dest, const wchar_t *src );将一个宽字符串复制给另一个
wcsncpywchar_t* wcsncpy( wchar_t* dest, const wchar_t* src, size_t count );将一定量的宽字符从一个字符串复制到另一个
wcscatwchar_t *wcscat( wchar_t *dest, const wchar_t *src );将一个宽字符串的副本后附于另一个
wcsncatwchar_t *wcsncat( wchar_t *dest, const wchar_t *src, size_t count );将一定量宽字符串从一个宽字符串后附到另一个

字符串检验

函数名函数原型说明
wcslensize_t wcslen( const wchar_t *str );返回宽字符串的长度
wcscmpint wcscmp( const wchar_t *lhs, const wchar_t *rhs );比较两个宽字符串
wcsncmpint wcsncmp( const wchar_t* lhs, const wchar_t* rhs, size_t count );比较来自两个宽字符串的一定量字符
wcsstrwchar_t* wcsstr( const wchar_t* dest, const wchar_t* src );dest 所指的空终止宽字符串中,寻找 src 所指的空终止宽字符串的首次出现
wcstokwchar_t* wcstok( wchar_t* str, const wchar_t* delim, wchar_t **ptr );寻找 str 所指向的空终止宽字符串中的下个记号。以 delim 所指向的空终止宽字符串鉴别分隔符

宽字符数组操作

函数名函数原型说明
wmemcpywchar_t* wmemcpy( wchar_t* dest, const wchar_t* src, size_t count );在两个不重叠的数组间复制一定数量的宽字符
wmemsetwchar_t *wmemset( wchar_t *dest, wchar_t ch, size_t count );将给定的宽字符复制到宽字符数组的所有位置

locale 设置

采用 setlocale(LC_ALL, "chs"); 将当前 locale 设置为中文环境即可。

updatedupdated2022-05-212022-05-21