如何在C++中有效地检查字符串是否包含特殊字符?

14

我试图寻找更好的方法来检查字符串中是否有特殊字符。在我的情况下,除了字母数字和下划线之外的任何东西都被视为特殊字符。目前,我有一个包含特殊字符的字符串,例如std :: string ="! @ # $%^&". 然后我使用 std::find_first_of() 算法来检查字符串中是否存在任何特殊字符。

我想知道如何基于白名单列表进行操作。我想在一个字符串中指定小写/大写字母、数字和下划线(我不想逐个列出它们)。有没有办法可以指定一些ASCII范围,比如[a-zA-Z0-9_]。我该如何实现这一点?然后我打算使用 std::find_first_not_of()。通过这种方式,我可以说明我真正想要的内容,并检查相反的内容。


https://dev59.com/73NA5IYBdhLWcg3wBpDs - Sai Ganesh
@Sai Ganesh:那是不同的编程语言(C#)。 - MSalters
C++ 不假定 ASCII。它甚至与 EBCDIC 兼容,其中 A-Z 不是连续的。 - MSalters
9个回答

19

尝试:

std::string  x(/*Load*/);
if (x.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_") != std::string::npos)
{
    std::cerr << "Error\n";
}

或者尝试使用 boost 正则表达式:

// Note: \w matches any word character `alphanumeric plus "_"`
boost::regex test("\w+", re,boost::regex::perl);
if (!boost::regex_match(x.begin(), x.end(), test)
{
    std::cerr << "Error\n";
}

// The equivalent to \w should be:
boost::regex test("[A-Za-z0-9_]+", re,boost::regex::perl);   

2
我知道我可以这样做,但是想知道是否可以提到范围,比如[a-z A-Z 0-9 _]或者ASCII值的范围之类的。 - Praveen
@Praveen:已添加 Boost 版本。 - Martin York
自从发布后,正则表达式在 Go 中变得更加简单:#include <regex>; /*....*/ if(!std::regex_match(str_val,std::regex("[A-Za-z0-9\-_]+")) throw; - OverInflatedWalrus

4

使用标准的 C 或 C++ 无法使用字符范围来完成此操作,您必须列出所有字符。对于 C 字符串,您可以使用 strspn(3)strcspn(3) 来查找字符串中第一个属于给定字符集或不属于给定字符集的字符。例如:

// Test if the given string has anything not in A-Za-z0-9_
bool HasSpecialCharacters(const char *str)
{
    return str[strspn(str, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_")] != 0;
}

对于C++字符串,您可以等效地使用find_first_offind_first_not_of成员函数。
另一个选择是使用<ctype.h>中的isalnum(3)和相关函数来测试给定字符是否为字母数字;请注意,这些函数是区域设置相关的,因此它们的行为在其他区域设置中可能会发生变化。如果您不希望出现这种情况,则不要使用它们。如果您选择使用它们,则还必须单独测试下划线,因为没有测试“字母数字或下划线”的功能,并且您还必须编写自己的循环来搜索字符串(或使用具有适当函数对象的std::find)。

4

我认为我会稍微不同地处理这个任务,将std::string看作一个集合,并使用算法。使用C++0x lambda表达式,它的代码大致如下:

bool has_special_char(std::string const &str) {
    return std::find_if(str.begin(), str.end(),
        [](unsigned char ch) { return !(isalnum(ch) || ch == '_'); }) != str.end();
}

至少在处理char(而不是wchar_t)时,isalnum通常会使用表查找,因此通常比基于find_first_of的任何内容要快得多(后者通常会使用线性搜索)。 换句话说,这是O(N)(N = str.size()),而基于find_first_of的内容将是O(N * M)(N = str.size(),M = pattern.size())。

如果您想使用纯C完成作业,则可以使用scanf和扫描集转换,理论上不可移植,但由几乎所有最新/流行的编译器支持:

char junk;
if (sscanf(str, "%*[A-Za-z0-9_]%c", &junk))
    /* it has at least one "special" character
else
    /* no special characters */

这里的基本思路非常简单:scanset跳过所有连续的非特殊字符(但由于 *,不会将结果分配给任何内容),然后我们尝试读取一个更多的字符。如果成功,则意味着至少有一个字符是未被跳过的,因此我们必须至少有一个特殊字符。如果失败,则表示scanset转换匹配整个字符串,因此所有字符都是“非特殊”的。
正式地说,C标准规定,尝试在类似这样的scanset转换中放置范围是不可移植的(除了在scanset的开头或结尾处放置“-”以外的任何地方都会产生实现定义的行为)。甚至还有一些编译器(来自Borland)会因此而失败 - 它们将把A-Z视为仅匹配三个可能的字符,即“A”,“-”和“Z”。大多数当前的编译器(或更准确地说,标准库实现)采用了这种做法: "A-Z" 匹配任何大写字母。

isalnum被优化了,它不会进行表查找,而是检查字符是否在一个范围内:代码的工作方式类似于('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')其中c是字符。它利用了ASCII码中字符(如大写或小写字母或数字)在彼此之后是线性的这一事实。这比需要解析器或解释器的正则表达式或find_first_not_of更有效率。 - cmdLP
@cmdLP: 当然,如何实现取决于代码的编写者。但是,例如在 Linux 的 libstdc++ 中,实现方式通常为使用表格 _M_table 并返回 __c 的无符号字符的值和 __m 作按位与运算的结果,代码如下:return _M_table[static_cast<unsigned char>(__c)] & __m;(来自:gcc/libstdc++-v3/config/os/gnu-linux/ctype_inline.h)。而在 libcxx 中,则实现方式为:return isascii(c) ? (ctype<char>::classic_table()[c] & m) != 0 : false;。(libcxx/src/locale.cpp)。因此,虽然可能会有例外情况,但通常都是基于表格实现的。 - Jerry Coffin
@cmdLP:如果您知道一种基于范围比较的方法,我很想知道它是什么——我相信目前这种方法比查表更有效率,但我不知道是否有任何实现。 - Jerry Coffin
“如果参数的值既不能表示为无符号字符,也不等于EOF,则std::isalnum的行为是未定义的。” 与cctype中的所有函数一样,它们的参数应首先转换为unsigned char - 303

4
你需要考虑的第一件事是“这是否仅为ASCII字符集”?如果是,我建议您认真考虑是否应该仅允许ASCII。我目前在一家公司工作,我们由于没有从一开始就考虑支持Unicode而在进入外国市场方面遇到了一些麻烦。
话虽如此,ASCII使检查非字母数字字符变得非常容易。请查看ASCII表。 http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
  • 迭代每个字符
  • 检查字符是否为十进制值48-57、65-90、97-122或95(下划线)

2

我会在这里使用内置的C函数。遍历字符串中的每个字符,并检查它是否为_isalpha(ch)为真。如果是,则它是有效的,否则它是一个特殊字符。


1
你可以使用类似这样的代码:

#include <ctype>

for(int i=0;i<s.length();i++){
    if( !std::isalpha(s[i]) && !std::isdigit(s[i]) && s[i]!='_')
          return false
}

isalpha()函数检查一个字符是否为字母数字,而isdigit()则检查它是否为数字。


这个可以正常工作,而不需要导入 ctype - Zubair Idris Aweda

1

使用

    s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end());

    bool my_predicate(char c)
    {
     return !(isalpha(c) || c=='_');
    }

将会得到一个干净的字符串s

Erase函数将剥离所有特殊字符,并且可以通过my_predicate函数进行高度自定义。


1

函数(宏)受地区设置的影响,但您应该研究isalnum()及来自<ctype.h><cctype>的相关内容。


0
如果你想要这个,但不想使用正则表达式,而且考虑到你的测试是针对ASCII字符的 - 只需创建一个函数来生成字符串以供find_first_not_of使用...
#include <iostream>
#include <string>

std::string expand(const char* p)
{
    std::string result;
    while (*p)
        if (p[1] == '-' && p[2])
        {
            for (int c = p[0]; c <= p[2]; ++c)
                result += (char)c;
            p += 3;
        }
        else
            result += *p++;
    return result;
}

int main()
{
    std::cout << expand("A-Za-z0-9_") << '\n';
}

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