如何创建删除 C 字符串中所有选定字符的函数
How to create a function that removes all of a selected character in a C-string?
我想创建一个函数来删除 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*&
类型。 它正在制作一个合同,声明"如果我愿意,我可以修改你的指针",即使它永远不会这样做。
有两种方法可以修复该错误:
可怕的请不要这样做:
// 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!!!
我承认我的函数定义被破坏了:
简单地说,你的函数定义应该采用指针,而不是指针引用:
void removeAll(char* s, const char ch)
这意味着您可以在任何可修改的内存块(包括数组)上调用它。 您可以感到欣慰的是,调用方的指针永远不会被修改。
现在,以下内容将起作用:
// This is now 100% legit! char str[] = "abmas$sachus#settes"; removeAll(str, 'e');
现在我的免费水晶球阅读已经完成,你的问题已经消失了,让我们解决房间里的大象:
您的代码是不必要的低效!
您不需要对字符串进行第一次传递(使用
strlen
)来计算其长度内部循环有效地为您的算法提供了O(N^2)的最坏情况时间复杂度。
修改
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
,复制字符就没有意义了。 因此,如果需要,可以添加该测试。 我会说它可能对性能几乎没有影响,并且由于额外的分支而可能降低性能。
但我会把它作为一个练习。 如果你想梦想真正快速的代码以及它有多疯狂,那么去看看glibc中strlen
源代码。 准备好让你大吃一惊。
您可以通过编写如下函数来使逻辑更简单、更高效:
void removeAll(char * s, const char charToRemove)
{
const char * readPtr = s;
char * writePtr = s;
while (*readPtr) {
if (*readPtr != charToRemove) {
*writePtr++ = *readPtr;
}
readPtr++;
}
*writePtr = ' ';
}
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- 使用.find函数在c++中查找字符和另一个字符之间的大小
- 将字符随机转换为大写的函数
- 固有构造函数的字符和访问级别
- constexpr 函数获取常量字符*
- 将字符缓冲区强制转换为函数指针
- 函数签名与调用的函数不匹配,常量字符[]和字符*之间的区别?
- 将字符串数组传递给接受常量字符**的函数
- 在函数中返回无符号字符数组,但不返回指针
- 如何返回实际值(在我的例子中是无符号字符数组)而不是来自 C++ 函数的指针?
- 在函数 strcpy() 中访问字符数组时出现分段错误
- 如何循环访问 cpp 中的函数返回的字符指针数组
- 自定义 std::fstream,std::filebuf 的溢出和下溢函数未为每个字符调用
- 如何在字符函数中选择某些字符?
- 使用排序函数 c++ 对字符数组进行排序
- 字符串到字符* 函数
- 空间字符函数
- 在字符函数中传递字符值时出现奇怪的错误
- 正在更新字符* 函数C++
- 反转字符函数,但不会输出