如何在 c++ 中从字符串中删除所有非字母数字字符

How to strip all non alphanumeric characters from a string in c++?

本文关键字:数字字符 字符串 c++ 删除      更新时间:2023-10-16

我正在编写一个软件,它要求我使用 libcurl 处理从网页获得的数据。当我获得数据时,由于某种原因,它有额外的换行符。我需要找到一种方法只允许字母、数字和空格。并删除其他所有内容,包括换行符。有什么简单的方法可以做到这一点吗?谢谢。

编写一个函数,该函数采用char并返回true,如果要删除该字符,则返回false,如果要保留该字符:

bool my_predicate(char c);

然后使用 std::remove_if 算法从字符串中删除不需要的字符:

std::string s = "my data";
s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end());

根据您的要求,您可以使用标准库谓词之一,例如 std::isalnum ,而不是编写自己的谓词(您说需要匹配字母数字字符和空格,所以这可能并不完全符合您的需求(。

如果要使用标准库std::isalnum函数,则需要一个强制转换来消除 C 标准库标头<cctype>中的std::isalnum函数(这是要使用的函数(与 C++ 标准库标头<locale>中的std::isalnum(这不是要使用的函数, 除非您要执行特定于区域设置的字符串处理(:

s.erase(std::remove_if(s.begin(), s.end(), (int(*)(int))std::isalnum), s.end());

这同样适用于任何序列容器(包括std::stringstd::vectorstd::deque(。 这个成语通常被称为"擦除/删除"成语。 std::remove_if算法也适用于普通数组。 std::remove_if只对序列进行一次传递,因此它具有线性时间复杂度。

以前对 std::isalnum 的用法不会在不传递一元参数的情况下用 std::ptr_fun 进行编译,因此这个带有 lambda 函数的解决方案应该封装正确答案:

s.erase(std::remove_if(s.begin(), s.end(), 
[]( auto const& c ) -> bool { return !std::isalnum(c); } ), s.end());
如果您

使用 string,您可以随时循环并只erase所有非字母数字字符。

#include <cctype>
size_t i = 0;
size_t len = str.length();
while(i < len){
    if (!isalnum(str[i]) || str[i] == ' '){
        str.erase(i,1);
        len--;
    }else
        i++;
}

使用标准库更好的人可能可以在没有循环的情况下做到这一点。

如果仅使用char缓冲区,则可以循环访问,如果字符不是字母数字,请将所有字符向后移动(以覆盖有问题的字符(:

#include <cctype>
size_t buflen = something;
for (size_t i = 0; i < buflen; ++i)
    if (!isalnum(buf[i]) || buf[i] != ' ')
        memcpy(buf[i], buf[i + 1], --buflen - i);

只是将James McNellis的代码扩展了一点。他的功能是删除 alnum 字符而不是非 alnum 字符。

从字符串中删除非 alnum 字符。(alnum = 字母或数字(

  • 声明一个函数(如果传递的字符不是 alnum,则 isalnum 返回 0(

    bool isNotAlnum(char c) {
        return isalnum(c) == 0;
    }
    
  • 然后写这个

    s.erase(remove_if(s.begin(), s.end(), isNotAlnum), s.end());
    

那么你的字符串只有 alnum 字符。

对不同的方法进行基准测试。

如果你正在寻找一个基准,我做了一个。

(115830 cycles) 115.8ms -> using stringstream
( 40434 cycles)  40.4ms -> s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return !isalnum(c); }), s.end());
( 40389 cycles)  40.4ms -> s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return ispunct(c); }), s.end());
( 42386 cycles)  42.4ms -> s.erase(remove_if(s.begin(), s.end(), not1(ptr_fun( (int(*)(int))isalnum ))), s.end());
( 42969 cycles)  43.0ms -> s.erase(remove_if(s.begin(), s.end(), []( auto const& c ) -> bool { return !isalnum(c); } ), s.end());
( 44829 cycles)  44.8ms -> alnum_from_libc(s) see below
( 24505 cycles)  24.5ms -> Puzzled? My method, see below
(  9717 cycles)   9.7ms -> using mask and bitwise operators
Original length: 8286208, current len with alnum only: 5822471
<小时 />
  • Stringstream给出了可怕的结果(但我们都知道(
  • 已经给出的不同答案给出了大约相同的运行时
  • 以 C 方式始终如一地提供更好的运行时(几乎快两倍!(,这绝对值得考虑,最重要的是它与 C 语言兼容。
  • 我的按位方法(也兼容 C(快了 400% 以上。

注意,必须修改所选答案,因为它只保留特殊字符

注意2:测试文件是一个(几乎(8192 kb的文本文件,大约有62个alnum和12个特殊字符,随机均匀地写入。

<小时 />

基准测试源代码

#include <ctime>
#include <iostream>
#include <sstream>
#include <string>
#include <algorithm>
#include <locale> // ispunct
#include <cctype>
#include <fstream> // read file
#include <streambuf>
#include <sys/stat.h> // check if file exist
#include <cstring>
using namespace std;
bool exist(const char *name)
{
  struct stat   buffer;
  return !stat(name, &buffer);
}
constexpr int SIZE = 8092 * 1024;
void keep_alnum(string &s) {
    stringstream ss;
    int i = 0;
    for (i = 0; i < SIZE; i++)
        if (isalnum(s[i]))
            ss << s[i];
    s = ss.str();
}
/* my method, best runtime */
void old_school(char *s) {
    int n = 0;
    for (int i = 0; i < SIZE; i++) {
        unsigned char c = s[i] - 0x30; // '0'
        if (c < 10 || (c -= 0x11) < 26 || (c -= 0x20) < 26) // 0x30 + 0x11 = 'A' + 0x20 = 'a'
            s[n++] = s[i];
    }
    s[n] = '';
}
void alnum_from_libc(char *s) {
    int n = 0;
    for (int i = 0; i < SIZE; i++) {
        if (isalnum(s[i]))
            s[n++] = s[i];
    }
    s[n] = '';
}
#define benchmark(x) printf("33[30m(%6.0lf cycles) 33[32m%5.1lfmsn33[0m", x, x / (CLOCKS_PER_SEC / 1000))
int main(int ac, char **av) {
    if (ac < 2) {
        cout << "usage: ./a.out "{your string} or ./a.out FILE "{your file}n";
        return 1;
    }
    string s;
    s.reserve(SIZE+1);
    string s1;
    s1.reserve(SIZE+1);
    char s4[SIZE + 1], s5[SIZE + 1];
    if (ac == 3) { 
        if (!exist(av[2])) {
            for (size_t i = 0; i < SIZE; i++)
              s4[i] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnoporstuvwxyz!@#$%^&*()__+:"<>?,./'"[rand() % 74];
          s4[SIZE] = '';
          ofstream ofs(av[2]);
          if (ofs)
            ofs << s4;
        }
        ifstream ifs(av[2]);
        if (ifs) {
          ifs.rdbuf()->pubsetbuf(s4, SIZE);
          copy(istreambuf_iterator<char>(ifs), {}, s.begin());
        }
        else
          cout << "errorn";
        ifs.seekg(0, ios::beg);
        s.assign((istreambuf_iterator<char>(ifs)), istreambuf_iterator<char>());
    }
    else
        s = av[1];
    double elapsedTime;
    clock_t start;
    bool is_different = false;
    s1 = s;
    start = clock();
    keep_alnum(s1);
    elapsedTime = (clock() - start);
    benchmark(elapsedTime);
    string tmp = s1;
    s1 = s;
    start = clock();
    s1.erase(std::remove_if(s1.begin(), s1.end(), [](char c) { return !isalnum(c); }), s1.end());
    elapsedTime = (clock() - start);
    benchmark(elapsedTime);
    is_different |= !!strcmp(tmp.c_str(), s1.c_str());
    s1 = s;
    start = clock();
    s1.erase(std::remove_if(s1.begin(), s1.end(), [](char c) { return ispunct(c); }), s1.end());
    elapsedTime = (clock() - start);
    benchmark(elapsedTime);
    is_different |= !!strcmp(tmp.c_str(), s1.c_str());
    s1 = s;
    start = clock();
    s1.erase(remove_if(s1.begin(), s1.end(), not1(ptr_fun( (int(*)(int))isalnum ))), s1.end());
    elapsedTime = (clock() - start);
    benchmark(elapsedTime);
    is_different |= !!strcmp(tmp.c_str(), s1.c_str());
    s1 = s;
    start = clock();
    s1.erase(remove_if(s1.begin(), s1.end(), []( auto const& c ) -> bool { return !isalnum(c); } ), s1.end());
    elapsedTime = (clock() - start);
    benchmark(elapsedTime);
    is_different |= !!strcmp(tmp.c_str(), s1.c_str());
    memcpy(s4, s.c_str(), SIZE);
    start = clock();
    alnum_from_libc(s4);
    elapsedTime = (clock() - start);
    benchmark(elapsedTime);
    is_different |= !!strcmp(tmp.c_str(), s4);
    memcpy(s4, s.c_str(), SIZE);
    start = clock();
    old_school(s4);
    elapsedTime = (clock() - start);
    benchmark(elapsedTime);
    is_different |= !!strcmp(tmp.c_str(), s4);
    cout << "Original length: " << s.size() << ", current len with alnum only: " << strlen(s4) << endl;
    // make sure that strings are equivalent
    printf("33[3%cm%sn", ('3' + !is_different), !is_different ? "OK" : "KO");
    return 0;
}
<小时 />

我的解决方案

对于按位方法,您可以直接在我的 github 上检查它,基本上由于掩码,我避免了分支指令(if(。我避免发布带有C++标签的按位操作,我对此感到非常讨厌。

对于 C 样式的字符串,我迭代字符串并有两个索引:n用于我们保留的字符,i遍历字符串,我们一个接一个地测试它是数字、大写还是小写。

添加此函数:

void strip_special_chars(char *s) {
    int n = 0;
    for (int i = 0; i < SIZE; i++) {
        unsigned char c = s[i] - 0x30;
        if (c < 10 || (c -= 0x11) < 26 || (c -= 0x20) < 26) // 0x30 + 0x11 = 'A' + 0x20 = 'a'
            s[n++] = s[i];
    }
    s[n] = '';
}

并用作:

char s1[s.size() + 1]
memcpy(s1, s.c_str(), s.size());
strip_special_chars(s1);

remove_copy_if标准算法将非常适合您的情况。

#include <cctype>
#include <string>
#include <functional>
std::string s = "Hello World!";
s.erase(std::remove_if(s.begin(), s.end(),
    std::not1(std::ptr_fun(std::isalnum)), s.end()), s.end());
std::cout << s << std::endl;

结果:

"HelloWorld"

您可以使用isalnum来确定每个字符是否为字母数字,然后使用 ptr_fun 将函数传递给返回值的 NOT not1,只留下您想要的字母数字内容。

您可以通过这种方式使用删除-擦除算法 -

// Removes all punctuation       
s.erase( std::remove_if(s.begin(), s.end(), &ispunct), s.end());

下面的代码应该适用于给定的字符串s。它利用<algorithm><locale>库。

std::string s("He!!llo  Wo,@rld! 12 453");
s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return !std::isalnum(c); }), s.end());

提到的解决方案

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

非常好,但不幸的是,在Visual Studio(调试模式(中不适用于"Ñ"等字符,因为以下行:

_ASSERTE((unsigned)(c + 1) <= 256)

在 isctype.c 中

所以,我会推荐这样的东西:

inline int my_ispunct( int ch )
{
    return std::ispunct(unsigned char(ch));
}
...
s.erase( std::remove_if(s.begin(), s.end(), &my_ispunct), s.end());

以下内容对我有用。

str.erase(std::remove_if(str.begin(), str.end(), &ispunct), str.end());
str.erase(std::remove_if(str.begin(), str.end(), &isspace), str.end());
void remove_spaces(string data)
{ int i=0,j=0;
    while(i<data.length())
    {
        if (isalpha(data[i]))
        {
        data[i]=data[i];
        i++;
        }
        else
            {
            data.erase(i,1);}
    }
    cout<<data;
}