确保字符串仅包含字母数字字符的更好方法?

Posted

技术标签:

【中文标题】确保字符串仅包含字母数字字符的更好方法?【英文标题】:better way to ensure that a string contains only alphanumeric characters? 【发布时间】:2016-05-02 18:01:41 【问题描述】:

我正在寻找一种简单的方法来检查类型 std::string 是否仅包含字母数字字符。我的代码如下所示:

   std::string sStr("This is a test");
   for (std::string::const_iterator s = sStr.begin(); s != sStr.end(); ++s)
              if (! isalnum(*s)) return false;

这是正确的做法吗?有没有更有效的方法来处理这个问题?

【问题讨论】:

从根本上说,必须检查字符串中的每个字符是否在字母数字字符范围内。您可以通过在<algorithm> 中使用一些循环结构来提高效率,但我认为它们不会很重要。 您忘了说明是要使用不同的语言环境还是纯 ASCII? 【参考方案1】:

我会使用std::find_if:

std::string sStr("This is a test");

auto it = std::find_if(std::begin(sStr), std::end(sStr), [](unsigned char c) 
    return !std::isalnum(c);
);

return it == std::end(sStr);

std::find_if_not 可能更有意义:

auto it = std::find_if_not(std::begin(sStr), std::end(sStr), [](unsigned char c) 
    return std::isalnum(c);
);

【讨论】:

也许更优雅,但我怀疑它是否更有效率。 只是好奇,[](char c) 在这里的最后一个参数是什么,不完全理解语法。不过,感谢您发布更好的解决方案。 @randombits 那是一个 lambda,它就像一个函数,但是匿名的,你不必单独声明它。你可以在这里阅读更多:***.com/questions/7627098/… std::any_ofstd::all_of 会更有意义。 @Rakete1111 不用担心,还使用您的代码为不知道如何使用的人制作了 2 个示例:ideone.com/WiERqn【参考方案2】:

是的,实际上*。 <algorithm> 中的循环结构似乎比优化下的原始循环执行好,至少在 gcc 中是这样。

乍一看,使用<algorithm> 和 lambda 是一种很好的方法:

bool onlyAlnum(const std::string& str)
    return std::all_of(
        str.begin(), 
        str.end(), 
        [loc = std::locale](char c)return std::isalnum(c, loc););

但是这有它的缺点。

locale:当我去测试它时,isalnumlocale 版本似乎比函数的<cctype> 化身慢了很多。 cctype 版本不关注语言环境,但测试单个char 是否它们是字母数字无论如何都适用于一小部分 UTF-8 字符:UTF-8 是可变宽度编码,并且测试多字符字符的一部分将导致字母数字测试的假阴性。

上面的 lambda 是一个 c++14 的 lambda,它初始化了一个变量 loc 以在首次创建时保存语言环境。这允许函数依赖于当前的语言环境工作,同时也避免了每次评估谓词时构建一个新对象来表示语言环境的成本,就像 lambda 一样:

[](char c)return std::isalnum(c, std::locale);

但是,相比之下,它仍然是一个非常缓慢的测试。如果我们不需要std::isalnum<locale> 化身的有限优势,我们可以使用(更快)更快的<cctype> 版本:

[](char c)return std::isalnum(c);

所以我们采用了第二种方法,它使用 cctype 版本。测试表明这要快得多,与您提供的原始循环相当:

bool onlyAlnumAllOf(const std::string& str)
    return std::all_of(
        str.begin(), 
        str.end(), 
        [](char c)return std::isalnum(c););

all_of 测试条件是否对输入迭代器范围内的每个条目都有效。范围由前两个参数提供,这里是str.begin()str.end(),它们自然地定义了字符串的开始和结束。

而demo on coliru 表明onlyAlNum 将为任何仅包含字母或数字字符的字符串返回true,但不包含空格。

最后,您可以测试差异。通过粗略的测试评估“oyn3478qo47nqooina7o8oao7nroOL”1000000 次,结果如下:

我机器上 gcc 5.2.0 的 MinGW-64 端口

g++ main.cpp -Wall -Wextra -Wpedantic --std=c++14 -o3

all_of (with locale information): 652ms for 1000000 iterations
all_of: 63ms for 1000000 iterations
find_if: 63ms for 1000000 iterations
loop: 70ms for 1000000 iterations
range-loop: 69ms for 1000000 iterations

和coliru 使用 gcc 6.1.0:

g++ main.cpp -Wall -Wextra -Wpedantic --std=c++14 -o3

all_of (with locale information): 1404ms for 1000000 iterations
all_of: 101ms for 1000000 iterations
find_if: 110ms for 1000000 iterations
loop: 108ms for 1000000 iterations
range-loop: 119ms for 1000000 iterations

在 coliru 上使用 clang 3.8.0:

clang++ -std=c++14 -O3 -Wall -Wextra -Wpedantic main.cpp

all_of (with locale information): 1127ms for 1000000 iterations
all_of: 85ms for 1000000 iterations
find_if: 72ms for 1000000 iterations
loop: 128ms for 1000000 iterations
range-loop: 88ms for 1000000 iterations

如您所见,它因编译器和版本而异,哪个函数最快。优化不好玩吗?

这是我用来测试每​​个方法的函数:

using StrEvaluator = bool (*)(const std::string&);
using Milliseconds = std::chrono::milliseconds;

void testStrEvaluator(StrEvaluator eval, std::string str)
    auto begin = std::chrono::steady_clock::now();
    bool result = true;
    for(unsigned int i = 0; i < 1000000; ++i)
        str.resize(str.size());
        result &= eval(str);
    
    auto end = std::chrono::steady_clock::now();
    std::cout
        << std::chrono::duration_cast<Milliseconds>(end - begin).count()
        << "ms for 1000000 iterations\n";

测试存在缺陷:coliru 不保证执行期间资源的一致性,而且我没有关闭计算机上的其他程序,因此变化可能是侥幸。但是,它们似乎足够一致,可以从中得出一些结论:算法和原始循环的循环结构都可以很好地执行,并且根据速度在它们之间进行选择(除非您发现循环是瓶颈)更加微观-优化比什么都重要。

【讨论】:

为什么这样会更有效率?据我所知,它是同一件事,只是用更多的功能术语来表达。 @alcedine 哦,亲爱的,我把“高效”误读为“优雅”。你说的很对,这可能是相同的速度,如果不是稍微慢一点的话。 @alcedine 为多个实现和编译器添加了时序数据 哇,你做到了!我真的希望优化器能够达到相同的输出,但显然不是。【参考方案3】:

您可以通过几种表面上不同的方式来实现这一点,但有效的解决方案基本上仍然是您已经编写的。任何解决方案都必须逐个检查字符,直到发现所有字符都是字母数字或非字母数字。

实际上,这不是相当正确的。如果您的字符串很长,您可能会受益于并行性。但我怀疑情况并非如此。

就风格而言,我建议使用 range for (如果你有 C++11);否则,我会写你有什么。

【讨论】:

以上是关于确保字符串仅包含字母数字字符的更好方法?的主要内容,如果未能解决你的问题,请参考以下文章

ctype_alpha() 是检查字符串是不是仅包含字母的更好方法吗

如何验证字符串是不是仅包含字母、数字、下划线和破折号?

检查字符串是不是包含字母数字

检查字符串是不是仅包含数字

使用 gsub 在字符串中仅保留字母数字字符和空格

2021-09-16:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。按键2对应:‘