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

ran*_*its 3 c++

我正在寻找一种简单的方法来检查类型是否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;
Run Code Online (Sandbox Code Playgroud)

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

Rak*_*111 5

我会用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);
Run Code Online (Sandbox Code Playgroud)

std::find_if_not 也许会更有意义:

auto it = std::find_if_not(std::begin(sStr), std::end(sStr), [](unsigned char c) {
    return std::isalnum(c);
});
Run Code Online (Sandbox Code Playgroud)

  • `std :: any_of`或`std :: all_of`会更有意义. (3认同)

jag*_*ire 5

是的,实际上是*.在优化中,找到的循环结构<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);});
}
Run Code Online (Sandbox Code Playgroud)

然而,这有其缺点.

locale:当我去测试时,它的locale版本isalnum似乎比<cctype>函数的版本慢得多.cctype版本没有注意语言环境,但测试单个char是否是字母数字是否适用于UTF-8字符的微小子集:UTF-8是一种可变宽度编码,并测试一个多字符字符的一部分将导致对字母数字测试的错误否定.

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

[](char c){return std::isalnum(c, std::locale{});}
Run Code Online (Sandbox Code Playgroud)

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

[](char c){return std::isalnum(c);}
Run Code Online (Sandbox Code Playgroud)

因此,我们采用第二种方式,使用cctype版本.测试显示这个速度要快得多,与您给出的原始循环相同:

bool onlyAlnumAllOf(const std::string& str){
    return std::all_of(
        str.begin(), 
        str.end(), 
        [](char c){return std::isalnum(c);});
}
Run Code Online (Sandbox Code Playgroud)

all_of测试条件是否对一系列输入迭代器中的每个条目有效.的范围由第一两个参数,这里提供的str.begin()str.end(),这自然定义串的开始和结束.

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

如您所见,它因编译器和版本而异,功能最快.优化不是很有趣吗?

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

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";
}
Run Code Online (Sandbox Code Playgroud)

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