C++ 从字符串中去除非ASCII字符

9

在开始之前,是的,我知道这是一个重复的问题,是的,我已经查看了发布的解决方案。我的问题是我无法让它们起作用。

bool invalidChar (char c)
{ 
    return !isprint((unsigned)c); 
}
void stripUnicode(string & str)
{
    str.erase(remove_if(str.begin(),str.end(), invalidChar), str.end()); 
}

我在“Prusæus, Ægyptians”上尝试了这种方法,但没有任何作用。我还尝试用isprint替换isalnum
当我将字符串转换为宽字符串再转换回字符串时,在程序的另一个部分出现了真正的问题。如果在字符串->宽字符串转换中有unicode字符,则会出现转换错误。
参考: 如何从字符串中删除非ASCII字符?(使用C#) 如何从字符串中删除所有非字母数字字符?(使用C++) 编辑: 我仍然想要删除所有的非ASCII字符,但如果有帮助的话,这是我的程序崩溃的地方:
// Convert to wstring
wchar_t* UnicodeTextBuffer = new wchar_t[ANSIWord.length()+1];
wmemset(UnicodeTextBuffer, 0, ANSIWord.length()+1);
mbstowcs(UnicodeTextBuffer, ANSIWord.c_str(), ANSIWord.length());
wWord = UnicodeTextBuffer; //CRASH

错误对话框

MSVC++ 调试库

调试断言失败!

程序://myproject

文件:f:\dd\vctools\crt_bld\self_x86\crt\src\isctype.c

行://上面

表达式:(unsigned)(c+1)<=256

编辑:

更加复杂的是,我正在读取的 .txt 文件是 ANSI 编码。在 内的所有内容应该都是有效的。

解决方案:

bool invalidChar (char c) 
{  
    return !(c>=0 && c <128);   
} 
void stripUnicode(string & str) 
{ 
    str.erase(remove_if(str.begin(),str.end(), invalidChar), str.end());  
}

如果有其他人想要复制/粘贴这个内容,我可以勾选此问题。

编辑:

供将来参考:尝试使用__isascii, iswascii命令。


1
在进行转换之前,请确保调用 setlocale("");。如果无法处理非 ASCII 字符,那么进行转换就没有意义了! - Kerrek SB
是的,它必须是你程序中的第一件事情! - Kerrek SB
你的环境语言环境设置为有用的值了吗?尝试一些流行的选项(ISO-8859-15UTF-8)。 - Kerrek SB
@KerrekSB 我可能做错了,但是上面的代码和 setlocale(LC_ALL, "UTF-8"); 都没有任何效果。 - AnthonyW
如果您保留 "",则可以在 shell 中设置区域设置:LC_ALL=en_GB.utf8 ./myprog - Kerrek SB
显示剩余5条评论
4个回答

13

解决方案:

bool invalidChar (char c) 
{  
    return !(c>=0 && c <128);   
} 
void stripUnicode(string & str) 
{ 
    str.erase(remove_if(str.begin(),str.end(), invalidChar), str.end());  
}

编辑:

供以后参考:尝试使用__isascii、iswascii命令。


1
这里应该使用 unsigned char 而不是 char,因为普通的 char 始终小于 128... #include int main(){ unsigned char c = 129; std::cout << (c<128) << "\n"; return 0; }``` - CIsForCookies

2

你的invalidChar函数至少存在一个问题。它应该是这样的:

return !isprint( static_cast<unsigned char>( c ) );

将一个 char 转换为 unsigned 如果 char 是负数,可能会得到一些非常大的值(UNIT_MAX+1 + c)。将这样的值传递给 isprint 是未定义行为。

按照规定切换方法可以修复“Prusæus”,但无法修复“Ægyptians”,仍会导致崩溃。 - AnthonyW
将switch语句改为return !(c>=0 && c <128); <-- 这样就可以移除它了。显然,Æ是扩展ASCII字符146,并且落在系统对于<256的检查范围内。然而,即使如此,这也无法解释上面的错误对话框声称Æ超出范围的情况。 - AnthonyW
必须有两个版本的该字符,因为即使检查了 <256,它也会被删除。 - AnthonyW
对于95个可打印ASCII字符和33个控制字符,编码方式几乎是通用的(除了大型机上的EBCDIC);所有常见的编码方式都使用相同的代码,在0...127范围内。对于其他任何字符(因此包括æÆ),实际值将取决于编码方式;例如,在Latin 1中,其值与UTF-8中不同(在UTF-8中,它们将使用多字节编码)。isprint如何处理它们将取决于语言环境。 - James Kanze
@AnthonyW 关于可能的错误:如果char是有符号的,那么它不能包含146;如果你将其转换为int,结果将是-110。调用isprint时传入一个负数(除了EOF,可能是-1)是未定义行为。将其强制转换为unsigned char会导致-110被转换为146,并且随后的转换为int应该保留这个值。当传入146时,isprint返回的结果取决于区域设置,但不应该导致崩溃。 - James Kanze
显示剩余2条评论

1

isprint 取决于区域设置,因此所涉及的字符必须在当前区域设置中可打印。

如果您想要严格的 ASCII,请检查 [0..127] 范围。如果您想要可打印的 ASCII,请检查范围和 isprint


1

另一种解决方案不需要定义两个函数,而是使用C++17及以上版本中可用的匿名函数:

void stripUnicode(string & str) 
{ 
    str.erase(remove_if(str.begin(),str.end(), [](char c){return !(c>=0 && c <128);}), str.end());  
}

我认为这样更加清晰易懂


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接