如何创建删除 C 字符串中所有选定字符的函数

How to create a function that removes all of a selected character in a C-string?

本文关键字:字符 函数 字符串 何创建 创建 删除      更新时间:2023-10-16

我想创建一个函数来删除 c 字符串中 ch 的所有字符。 但是我不断收到访问冲突错误。

Unhandled exception at 0x000f17ba in testassignments.exe: 0xC0000005: Access violation writing location 0x000f787e.
void removeAll(char* &s, const char ch)
{
int len=strlen(s);
int i,j;
for(i = 0; i < len; i++)
{
if(s[i] == ch)
{
for(j = i; j < len; j++)
{
s[j] = s[j + 1];
}
len--;
i--;    
} 
}
return;
}

我希望 c 字符串不包含字符"ch",但相反,我收到访问冲突错误。 在调试中,我得到了以下错误:

s[j] = s[j + 1];

我试图修改函数,但不断收到此错误。

编辑-- 示例输入:

s="abmas$sachus#settes";
ch='e' Output->abmas$sachus#settes, becomes abmas$sachus#stts
ch='t' Output-> abmas$sachus#stts, becomes abmas$sachus#ss.

我没有生成这些输出,而是收到访问冲突错误。 编辑 2: 如果有任何帮助,我正在使用Microsoft Visual C++ 2010 Express。

除了每当遇到要删除的单个字符时,函数移动字符串的整个其余部分的效率低下之外,它实际上没有太大问题。

在评论中,人们假设您正在用s[j+1]读取字符串的末尾,但这是不正确的。 他们忘记了s[len]是完全有效的,因为这是字符串的 null 终止符。

所以我现在正在使用我的水晶球,我相信错误是因为您实际上是在字符串文字上运行它。

// This is NOT okay!
char* str = "abmas$sachus#settes";
removeAll(str, 'e');

上面的代码(有点)不合法。 字符串文本"abmas$sachus#settes"不应存储为非常量char*。 但是为了向后兼容 C(前提是您不尝试修改字符串),这通常作为编译器警告而不是错误发出。

但是,您确实不允许修改字符串。 而且您的程序在您尝试的那一刻就崩溃了。

如果要对char数组(可以修改)使用正确的方法,那么您会遇到不同的问题:

// This will result in a compiler error
char str[] = "abmas$sachus#settes";
removeAll(str, 'e');

结果在

错误:从类型为"char*"的右值初始化类型为"char*&"的非常量引用无效

这是为什么呢? 好吧,您的函数采用强制调用方使用指针的char*&类型。 它正在制作一个合同,声明"如果我愿意,我可以修改你的指针",即使它永远不会这样做。

有两种方法可以修复该错误:

  1. 可怕的请不要这样做

    // This compiles and works but it's not cool!
    char str[] = "abmas$sachus#settes";
    char *pstr = str;
    removeAll(pstr, 'e');
    

    我之所以说这很糟糕,是因为它开创了一个危险的先例。 如果该函数确实在将来的"优化"中修改指针,那么您可能会在不知不觉中破坏一些代码。

    假设您想稍后输出删除字符的字符串,但删除了第一个字符,并且您的函数决定修改指针以从第二个字符开始。 现在,如果您输出str,您将得到与使用pstr不同的结果。

    此示例仅假设您将字符串存储在数组中。 想象一下,如果你真的分配了一个这样的指针:

    char *str = new char[strlen("abmas$sachus#settes") + 1];
    strcpy(str, "abmas$sachus#settes");
    removeAll(str, 'e');
    

    然后,如果removeAll更改指针,则稍后使用以下命令清理此内存时,您将遇到不好的时间:

    delete[] str;  //<-- BOOM!!!
    
  2. 我承认我的函数定义被破坏了:

    简单地说,你的函数定义应该采用指针,而不是指针引用:

    void removeAll(char* s, const char ch)
    

    这意味着您可以在任何可修改的内存块(包括数组)上调用它。 您可以感到欣慰的是,调用方的指针永远不会被修改。

    现在,以下内容将起作用:

    // This is now 100% legit!
    char str[] = "abmas$sachus#settes";
    removeAll(str, 'e');
    

现在我的免费水晶球阅读已经完成,你的问题已经消失了,让我们解决房间里的大象:

您的代码是不必要的低效!

  1. 您不需要对字符串进行第一次传递(使用strlen)来计算其长度

  2. 内部循环有效地为您的算法提供了O(N^2)的最坏情况时间复杂度。

  3. 修改len的小技巧,以及更糟糕的是,循环变量i使您的代码更难阅读。

如果你能避免所有这些不受欢迎的事情呢!? 嗯,你可以!

想想你在删除字符时在做什么。 从本质上讲,当你删除了一个字符的那一刻,你需要开始将未来的字符向左洗牌。 但是您不需要一次洗牌一个。 如果在更多字符之后,您遇到要删除的第二个字符,那么您只需将未来的字符进一步分流到左侧即可。

我想说的是,每个角色最多只需要移动一次

已经有一个使用指针的答案来证明这一点,但它没有解释,你也是一个初学者,所以让我们使用索引,因为你理解这些。

首先要做的是摆脱strlen. 请记住,您的字符串以 null 结尾。strlen所做的只是搜索字符,直到找到空字节(也称为0'')...

[请注意,strlen的实际实现非常智能(比一次搜索单个字符更有效)......但是,当然,没有呼叫strlen更快]

您所需要的只是您的循环来查找 NULL 终止符,如下所示:

for(i = 0; s[i] != ''; i++)

好的,现在要放弃内部循环,你只需要知道每个新角色的粘贴位置。 如何只保留一个变量new_size,您将在其中计算最终字符串的长度。

void removeAll(char* s, char ch)
{
int new_size = 0;
for(int i = 0; s[i] != ''; i++)
{
if(s[i] != ch)
{
s[new_size] = s[i];
new_size++;
} 
}
// You must also null-terminate the string
s[new_size] = '';
}

如果你看了一会儿,你可能会注意到它可能做毫无意义的"复制"。 也就是说,如果i == new_size,复制字符就没有意义了。 因此,如果需要,可以添加该测试。 我会说它可能对性能几乎没有影响,并且由于额外的分支而可能降低性能。

但我会把它作为一个练习。 如果你想梦想真正快速的代码以及它有多疯狂,那么去看看glibcstrlen源代码。 准备好让你大吃一惊。

您可以通过编写如下函数来使逻辑更简单、更高效:

void removeAll(char * s, const char charToRemove)
{
const char * readPtr = s;
char * writePtr = s;
while (*readPtr) {
if (*readPtr != charToRemove) {
*writePtr++ = *readPtr;
}
readPtr++;
}
*writePtr = '';
}