你混淆了你所看到的和真实发生的事情。这getline
函数不做任何字符替换。 [注1]
您看到替换字符 (U+FFFD) 是因为您的控制台在要求呈现无效的 UTF-8 代码时输出该字符。大多数控制台在 UTF-8 模式下都会这样做;也就是说,当前的语言环境是 UTF-8。
另外,说文件包含“字符Føö»BÃ¥r
“充其量是不精确的。文件并不真正包含字符。它包含可以解释为字符的字节序列 - 例如,通过控制台或其他用户演示软件将它们呈现为字形 - 根据某种编码。不同不同的编码会产生不同的结果;在这种特殊情况下,您有一个由软件使用 Windows-1252 编码(或者大致相当于 ISO 8859-15)创建的文件,并且您使用 UTF-8 在控制台上渲染它。
这意味着 getline 读取的数据包含无效的 UTF-8 序列,但它(可能)不包含替换字符代码。根据您提供的字符串,它包含十六进制字符\xbb
,这是海鸠(»
)在 Windows 代码页 1252 中。
查找读取的字符串中所有无效的 UTF-8 序列getline
(或任何其他读取文件的 C 库函数)需要扫描字符串,但不需要扫描特定的代码序列。相反,您需要一次解码一个 UTF-8 序列,查找无效的序列。这不是一个简单的任务,但是mbtowc函数可以提供帮助(如果您启用了 UTF-8 语言环境)。正如您将在链接的联机帮助页中看到的,mbtowc
返回有效“多字节序列”(UTF-8 语言环境中的 UTF-8)中包含的字节数,或 -1 表示无效或不完整的序列。在扫描中,您应该以有效序列传递字节,或者删除/忽略开始无效序列的单个字节,然后继续扫描直到到达字符串末尾。
下面是一些经过简单测试的示例代码(C 语言):
#include <stdlib.h>
#include <string.h>
/* Removes in place any invalid UTF-8 sequences from at most 'len' characters of the
* string pointed to by 's'. (If a NUL byte is encountered, conversion stops.)
* If the length of the converted string is less than 'len', a NUL byte is
* inserted.
* Returns the length of the possibly modified string (with a maximum of 'len'),
* not including the NUL terminator (if any).
* Requires that a UTF-8 locale be active; since there is no way to test for
* this condition, no attempt is made to do so. If the current locale is not UTF-8,
* behaviour is undefined.
*/
size_t remove_bad_utf8(char* s, size_t len) {
char* in = s;
/* Skip over the initial correct sequence. Avoid relying on mbtowc returning
* zero if n is 0, since Posix is not clear whether mbtowc returns 0 or -1.
*/
int seqlen;
while (len && (seqlen = mbtowc(NULL, in, len)) > 0) { len -= seqlen; in += seqlen; }
char* out = in;
if (len && seqlen < 0) {
++in;
--len;
/* If we find an invalid sequence, we need to start shifting correct sequences. */
for (; len; in += seqlen, len -= seqlen) {
seqlen = mbtowc(NULL, in, len);
if (seqlen > 0) {
/* Shift the valid sequence (if one was found) */
memmove(out, in, seqlen);
out += seqlen;
}
else if (seqlen < 0) seqlen = 1;
else /* (seqlen == 0) */ break;
}
*out++ = 0;
}
return out - s;
}
Notes
- 除了底层 I/O 库可能的行尾转换之外,这将用单个替换 CR-LF
\n
在像 Windows 这样的系统上,两个字符 CR-LF 序列用作行结束指示。