在C ++中删除多余的空格

时间:2016-02-09 20:21:42

标签: c++ string algorithm

我尝试编写一个删除多余空格的脚本,但我没有设法完成它。

基本上我想将abc sssd g g sdg gg gf转换为abc sssd g g sdg gg gf

在PHP或C#等语言中,它很容易,但在C ++中却没有,我知道。这是我的代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <unistd.h>
#include <string.h>

char* trim3(char* s) {
    int l = strlen(s);

    while(isspace(s[l - 1])) --l;
    while(* s && isspace(* s)) ++s, --l;

    return strndup(s, l);
}

char *str_replace(char * t1, char * t2, char * t6)
{
    char*t4;
    char*t5=(char *)malloc(10);
    memset(t5, 0, 10);
    while(strstr(t6,t1))
    {
        t4=strstr(t6,t1);
        strncpy(t5+strlen(t5),t6,t4-t6);
        strcat(t5,t2);
        t4+=strlen(t1);
        t6=t4;
    }

    return strcat(t5,t4);
}

void remove_extra_whitespaces(char* input,char* output)
{
    char* inputPtr = input; // init inputPtr always at the last moment.
    int spacecount = 0;
    while(*inputPtr != '\0')
    {
        char* substr;
        strncpy(substr, inputPtr+0, 1);

        if(substr == " ")
        {
            spacecount++;
        }
        else
        {
            spacecount = 0;
        }

        printf("[%p] -> %d\n",*substr,spacecount);

        // Assume the string last with \0
        // some code
        inputPtr++; // After "some code" (instead of what you wrote).
    }   
}

int main(int argc, char **argv)
{
    printf("testing 2 ..\n");

    char input[0x255] = "asfa sas    f f dgdgd  dg   ggg";
    char output[0x255] = "NO_OUTPUT_YET";
    remove_extra_whitespaces(input,output);

    return 1;
}

它不起作用。我尝试了几种方法。我想要做的是逐个字符地迭代字符串并将其转储到另一个字符串中,只要一行中只有一个空格;如果有两个空格,请不要将第二个字符写入新字符串。

我该如何解决这个问题?

12 个答案:

答案 0 :(得分:24)

已经有很多很好的解决方案。我建议您选择基于专用<algorithm>的替代方案,以避免连续重复: unique_copy()

void remove_extra_whitespaces(const string &input, string &output)
{
    output.clear();  // unless you want to add at the end of existing sring...
    unique_copy (input.begin(), input.end(), back_insert_iterator<string>(output),
                                     [](char a,char b){ return isspace(a) && isspace(b);});  
    cout << output<<endl; 
}

这是 live demo 请注意,我从c样式字符串更改为更安全,更强大的C ++字符串。

编辑: 如果代码中需要保留c风格的字符串,则可以使用几乎相同的代码但使用指针代替迭代器。这就是C ++的神奇之处。这是another live demo

答案 1 :(得分:8)

这是一个简单的非C ++ 11解决方案,使用与问题相同的remove_extra_whitespace()签名:

#include <cstdio>

void remove_extra_whitespaces(char* input, char* output)
{
    int inputIndex = 0;
    int outputIndex = 0;
    while(input[inputIndex] != '\0')
    {
        output[outputIndex] = input[inputIndex];

        if(input[inputIndex] == ' ')
        {
            while(input[inputIndex + 1] == ' ')
            {
                // skip over any extra spaces
                inputIndex++;
            }
        }

        outputIndex++;
        inputIndex++;
    }

    // null-terminate output
    output[outputIndex] = '\0';
}

int main(int argc, char **argv)
{
    char input[0x255] = "asfa sas    f f dgdgd  dg   ggg";
    char output[0x255] = "NO_OUTPUT_YET";
    remove_extra_whitespaces(input,output);

    printf("input: %s\noutput: %s\n", input, output);

    return 1;
}

输出:

input: asfa sas    f f dgdgd  dg   ggg
output: asfa sas f f dgdgd dg ggg

答案 2 :(得分:6)

由于您使用C ++,因此您可以利用为此类工作设计的标准库功能。您可以使用std::string(而不是char[0x255])和std::istringstream来代替大部分指针算法。

首先,创建一个字符串流:

std::istringstream stream(input);

然后,从中读取字符串。它将自动删除空白分隔符:

std::string word;
while (stream >> word)
{
    ...
}

在循环内部,构建输出字符串:

    if (!output.empty()) // special case: no space before first word
        output += ' ';
    output += word;

这种方法的一个缺点是它动态分配内存(包括几个重新分配,在输出字符串增长时执行)。

答案 3 :(得分:3)

有很多方法可以做到这一点(例如,使用正则表达式),但是你可以这样做的一种方法是使用std::copy_if和状态函子来记住最后一个字符是否是空格:

#include <algorithm>
#include <string>
#include <iostream>

struct if_not_prev_space
{
    // Is last encountered character space.
    bool m_is = false;

    bool operator()(const char c)
    {                                      
        // Copy if last was not space, or current is not space.                                                                                                                                                              
        const bool ret = !m_is || c != ' ';
        m_is = c == ' ';
        return ret;
    }
};


int main()
{
    const std::string s("abc  sssd g g sdg    gg  gf into abc sssd g g sdg gg gf");
    std::string o;
    std::copy_if(std::begin(s), std::end(s), std::back_inserter(o), if_not_prev_space());
    std::cout << o << std::endl;
}

答案 4 :(得分:2)

对于就地修改,您可以应用erase-remove技术:

#include <string>
#include <iostream>
#include <algorithm>
#include <cctype>

int main()
{
    std::string input {"asfa sas    f f dgdgd  dg   ggg"};
    bool prev_is_space = true;
    input.erase(std::remove_if(input.begin(), input.end(), [&prev_is_space](char curr) {
        bool r = std::isspace(curr) && prev_is_space;
        prev_is_space = std::isspace(curr);
        return r;

    }), input.end());

    std::cout << input << "\n";
}

因此,您首先将所有额外空格移动到字符串的末尾,然后截断它。

C ++的巨大优势在于它足够通用,可以将代码移植到只有少量修改的普通c静态字符串:

void erase(char * p) {
    // note that this ony works good when initial array is allocated in the static array
    // so we do not need to rearrange memory
    *p = 0; 
}

int main()
{
    char input [] {"asfa sas    f f dgdgd  dg   ggg"};
    bool prev_is_space = true;
    erase(std::remove_if(std::begin(input), std::end(input), [&prev_is_space](char curr) {
        bool r = std::isspace(curr) && prev_is_space;
        prev_is_space = std::isspace(curr);
        return r;

    }));

    std::cout << input << "\n";
}

这里有趣的remove步骤是字符串表示独立的。它可以与std::string一起使用而不需要修改。

答案 5 :(得分:1)

由于你正在编写c风格,这是一种做你想做的事情的方法。 请注意,您可以删除换行符'\r''\n'(当然,如果您考虑这些空格,那么这取决于您)。

这个函数应该比任何其他替代方法更快或更快,即使用std :: strings调用它也没有内存分配(我已经超载了它)。

char temp[] = " alsdasdl   gasdasd  ee";
remove_whitesaces(temp);
printf("%s\n", temp);

int remove_whitesaces(char *p)
{
    int len = strlen(p);
    int new_len = 0;
    bool space = false;

    for (int i = 0; i < len; i++)
    {
        switch (p[i])
        {
        case ' ': space = true;  break;
        case '\t': space = true;  break;
        case '\n': break; // you could set space true for \r and \n
        case '\r': break; // if you consider them spaces, I just ignore them.
        default:
            if (space && new_len > 0)
                p[new_len++] = ' ';
            p[new_len++] = p[i];
            space = false;
        }
    }

    p[new_len] = '\0';

    return new_len;
}

// and you can use it with strings too,

inline int remove_whitesaces(std::string &str)
{
    int len = remove_whitesaces(&str[0]);
    str.resize(len);
    return len; // returning len for consistency with the primary function
                // but u can return std::string instead.
}

// again no memory allocation is gonna take place,
// since resize does not not free memory because the length is either equal or lower

如果你简单地看一下C ++标准库,你会注意到很多返回std :: string的C ++函数,或者其他std :: objects基本上都是一个写得很好的extern的包装器&#34; C& #34;功能。所以不要害怕在C ++应用程序中使用C函数,如果编写得好,你可以重载它们以支持std :: strings等。

例如,在Visual Studio 2015中,std::to_string的编写完全如下:

inline string to_string(int _Val)
    {   // convert int to string
    return (_Integral_to_string("%d", _Val));
    }

inline string to_string(unsigned int _Val)
    {   // convert unsigned int to string
    return (_Integral_to_string("%u", _Val));
    }

和_Integral_to_string是C函数sprintf_s的包装器

template<class _Ty> inline
    string _Integral_to_string(const char *_Fmt, _Ty _Val)
    {   // convert _Ty to string
    static_assert(is_integral<_Ty>::value,
        "_Ty must be integral");
    char _Buf[_TO_STRING_BUF_SIZE];
    int _Len = _CSTD sprintf_s(_Buf, _TO_STRING_BUF_SIZE, _Fmt, _Val);
    return (string(_Buf, _Len));
    }

答案 6 :(得分:1)

我有一种沉闷的感觉,那就是'scanf'会做得很好(事实上,这是C学校相当于Anatoly的C ++解决方案):

void remove_extra_whitespaces(char* input, char* output)
{
    int srcOffs = 0, destOffs = 0, numRead = 0;

    while(sscanf(input + srcOffs, "%s%n", output + destOffs, &numRead) > 0)
    {
        srcOffs += numRead;
        destOffs += strlen(output + destOffs);
        output[destOffs++] = ' '; // overwrite 0, advance past that
    }
    output[destOffs > 0 ? destOffs-1 : 0] = '\0';
}

我们利用scanf具有神奇的内置空间跳过功能的事实。然后,我们使用可能不太知名的%n“转换”规范,该规范为我们提供了scanf消耗的字符数量。这个功能在读取字符串时经常会派上用场,就像这里一样。使这个解决方案不完美的苦涩是对输出的strlen调用(不幸的是,没有“我实际上只有多少字节”转换说明符)。

最近使用scanf很简单,因为output保证存在足够的内存;如果不是这种情况,由于缓冲和溢出处理,代码会变得更加复杂。

答案 7 :(得分:0)

这里有一个很长(但很简单)的解决方案,它不使用指针。 它可以进一步优化,但它可以工作。

#include <iostream>
#include <string>
using namespace std;
void removeExtraSpace(string str);
int main(){
    string s;
    cout << "Enter a string with extra spaces: ";
    getline(cin, s);
    removeExtraSpace(s);
    return 0;
}
void removeExtraSpace(string str){
    int len = str.size();
    if(len==0){
        cout << "Simplified String: " << endl;
        cout << "I would appreciate it if you could enter more than 0 characters. " << endl;
        return;
    }
    char ch1[len];
    char ch2[len];
    //Placing characters of str in ch1[]
    for(int i=0; i<len; i++){
        ch1[i]=str[i];
    }
    //Computing index of 1st non-space character
    int pos=0;
    for(int i=0; i<len; i++){
        if(ch1[i] != ' '){
            pos = i;
            break;
        }
    }
    int cons_arr = 1;
    ch2[0] = ch1[pos];
    for(int i=(pos+1); i<len; i++){
        char x = ch1[i];
        if(x==char(32)){
            //Checking whether character at ch2[i]==' '
            if(ch2[cons_arr-1] == ' '){
                continue;
            }
            else{
                ch2[cons_arr] = ' ';
                cons_arr++;
                continue;
            }
        }
        ch2[cons_arr] = x;
        cons_arr++;
    }
    //Printing the char array
    cout << "Simplified string: " << endl;
    for(int i=0; i<cons_arr; i++){
        cout << ch2[i];
    }
    cout << endl;
}

答案 8 :(得分:0)

我最终在这里遇到了一个非常不同的问题。由于我不知道还有什么地方可以把它放在哪里,而且我发现了什么是错的,我在这里分享。请不要和我交叉。 我有一些字符串可以在它们的末端打印额外的空格,同时在调试中显示没有空格。在窗口中形成的字符串调用类似于VerQueryValue(),除了其他东西之外还输出字符串长度,例如,以下行中的iProductNameLen将结果转换为名为strProductName的字符串:

    strProductName = string((LPCSTR)pvProductName, iProductNameLen)
然后

生成了一个末尾带有\ 0字节的字符串,它在调试器中不易显示,但在屏幕上显示为空格。我将这个解决方案作为一个练习,因为一旦你意识到这一点,它就一点也不难。

答案 9 :(得分:0)

您可以使用std::unique根据定义如何使两个元素相等的方式将相邻重复项简化为单个实例。

如果两个元素都是空白字符,则在此处将它们定义为相等:

inline std::string& remove_extra_ws_mute(std::string& s)
{
    s.erase(std::unique(std::begin(s), std::end(s), [](char a, char b){
        return std::isspace(a) && std::isspace(b);
    }), std::end(s));

    return s;
}

inline std::string remove_extra_ws_copy(std::string s)
{
    return remove_extra_ws_mute(s);
}

std::unique将重复项移到字符串的末尾,并将迭代器返回到它们的开头,以便将其删除。

此外,如果您必须使用低级字符串,则仍可以在指针上使用std::unique

char* remove_extra_ws(char const* s)
{
    std::size_t len = std::strlen(s);

    char* buf = new char[len + 1];
    std::strcpy(buf, s);

    // Note that std::unique will also retain the null terminator
    // in its correct position at the end of the valid portion
    // of the string    
    std::unique(buf, buf + len + 1, [](char a, char b){
        return (a && std::isspace(a)) && (b && std::isspace(b));
    });

    return buf;
}

答案 10 :(得分:0)

我不知道这是否有帮助,但这是我在家庭作业中所做的事情。唯一可能中断的情况是在字符串EX“ wor ds”的开头有空格时,在这种情况下,它将更改为“ wor ds”

void ShortenSpace(string &usrStr){
   char cha1;
   char cha2;
   for (int i = 0; i < usrStr.size() - 1; ++i) {
      cha1 = usrStr.at(i);
      cha2 = usrStr.at(i + 1);
      
      if ((cha1 == ' ') && (cha2 == ' ')) {
         usrStr.erase(usrStr.begin() + 1 + i);
         --i;//edit: was ++i instead of --i, made code not work properly
      }
   }
}

答案 11 :(得分:-1)

在不使用任何内置函数的情况下删除多余空格的简单程序。

switch_to.frame()