C++高效实现去除字符串头尾指定字符
在C++开发中,去除字符串首尾指定字符是常见需求。相比依赖标准库,手动实现可更深入理解内存与指针操作。本文对比了基于循环移位与使用`memmove`优化的两种TrimHead/TrimTail方案,并通过基准测试揭示实际运行时的性能表现。
一、从头部开始去除指定字符
要实现去除头部字符的功能,核心逻辑是先定位第一个非目标字符的位置,再将后续内容移动至起始处。
第1步:基础循环移位实现
目的:通过遍历找到断点,并将剩余字符逐一拷贝至原位。
入口:源字符串指针 pszSrc 和目标字符 chTrim。
动作:
1. 检查指针有效性,若为空则直接返回。
2. 遍历字符串,跳过所有等于 chTrim 的字符。
3. 将剩下的字符逐个向前覆盖,最后补上字符串结束符 '\0'。
// 实现方式一
void TrimHead(char* pszSrc, char chTrim)
{
if(NULL == pszSrc)
return;
// 从头部开始跳过chTrim指定的字符
int i = 0;
char* psz = pszSrc;
while (*psz && *psz == chTrim) {
i++;
psz++;
}
// 从psz开始将后面字符逐一拷贝到前面
i = 0;
while(*psz)
{
*(pszSrc+i) = *psz;
i++;
psz++;
}
*(pszSrc+i) = 0;
}
完成状态:字符串头部的目标字符被移除,内容左移。
第2步:使用 memmove 优化
目的:减少多次内存写入操作,提升大数据量下的执行效率。
入口:同上。
动作:
1. 记录字符串总长度 iStrLen。
2. 同样先定位第一个非目标字符指针 psz。
3. 若位置发生变化,计算新长度,利用 memmove 一次性将有效部分及末尾空字符整体前移。
// 实现方式二
void TrimHeadEx(char* pszSrc, char chTrim)
{
if(NULL == pszSrc)
return;
// 从头部开始跳过chTrim指定的字符
int iStrLen = strlen(pszSrc);
char* psz = pszSrc;
while (*psz && *psz == chTrim)
psz++;
// psz指向第一个非指定字符的位置
if(psz != pszSrc)
{
// 计算新字符串长度
iStrLen = iStrLen - (psz - pszSrc);
memmove(pszSrc, psz, (iStrLen+1)); // +1表示将末尾的0也一并拷贝
}
}
完成状态:达到相同效果,但涉及更少的指令周期。
二、去除尾部指定的字符
去除尾部字符通常有两种思路:正向遍历记忆最后一个非目标位,或逆向遍历寻找起始截断点。
第1步:正向遍历法
目的:从头扫到尾,标记最后出现的目标字符位置。
动作:
1. 维护一个 pszLast 指针,初始为NULL。
2. 遍历时,若遇到目标字符且未记录过,则标记;若遇到非目标字符,则重置标记。
3. 遍历结束后,若找到了目标字符,将其替换为 '\0'。
// 实现方式一
void TrimTail(char* pszSrc, char chTrim)
{
if(NULL == pszSrc)
return;
char* psz = pszSrc;
char* pszLast = NULL;
// 从头开始遍历直到整个字符串结束
while(*psz)
{
// 遇到指定字符,则用pszLast记住该位置
if(*psz == chTrim)
{
if(NULL == pszLast)
pszLast = psz;
}
else
pszLast = NULL;
psz++;
}
// 如果找到末尾的第一个指定字符,则作为字符串的结尾
if(pszLast != NULL)
*pszLast = 0;
}
缺点:无论尾部是否有目标字符,都必须完整扫描整个字符串。
第2步:逆向遍历法
目的:从尾部倒着查找,快速定位并截断。
入口:同上。
动作:
1. 获取字符串长度 iStrLen,索引从 iStrLen - 1 开始。
2. 只要当前字符是指定字符,索引递减。
3. 停止后将新的位置加1作为新长度,并在该处填入 '\0'。
// 实现方式二
void TrimTailEx(char* pszSrc, char chTrim)
{
if(NULL == pszSrc)
return;
// 从尾部开始跳过chTrim指定字符
int iStrLen = strlen(pszSrc);
char* pszStr = pszSrc;
int iLastIdx = iStrLen - 1;
while(iLastIdx >= 0 && *(pszStr+iLastIdx) == chTrim)
iLastIdx--;
// 计算新字符串长度并在结尾赋值为0
iStrLen = iLastIdx+1;
*(pszSrc+iStrLen) = 0;
}
优势:若尾部没有目标字符,几乎不产生多余开销;若有大量目标字符,能迅速缩小范围。
三、性能测试与分析
为了客观评估各版本的速度差异,可以通过固定次数的循环调用来测量耗时。以下是包含上述函数的完整测试框架:
第1步:构建基准测试环境
目的:模拟高频调用场景,暴露微小的性能差距。
参数:循环次数设为 10000000,确保时钟精度带来的误差最小化。
动作:
1. 准备两组相同的缓冲区 szTrim1 和 szTrim2,初始化带有首尾空格的测试串。
2. 分别调用普通版和优化版的 TrimHead/Tail 函数。
3. 使用 clock() 记录时间差并打印结果。
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 此处省略 TrimHead, TrimHeadEx, TrimTail, TrimTailEx 的定义
void TestSpeedTrim(bool bTrimHead)
{
char szTrim1[256] = {0};
char szTrim2[256] = {0};
char* pszOrigin = " This is a trim test head/tail ";
strcpy(szTrim1, pszOrigin);
strcpy(szTrim2, pszOrigin);
int i = 0;
int iCount = 10000000;
clock_t cStart = 0;
// 第一种Trim方法
cStart = clock();
for(i = 0; i < iCount; i++)
{
bTrimHead ? TrimHead(szTrim1, ' ') : TrimTail(szTrim1, ' ');
}
clock_t cSpan1 = clock() - cStart;
// 第二种Trim方法
cStart = clock();
for(i = 0; i < iCount; i++)
{
bTrimHead ? TrimHeadEx(szTrim2, ' ') : TrimTailEx(szTrim2, ' ');
}
clock_t cSpan2 = clock() - cStart;
printf("cSpan1 = %d, cSpan2 = %d\r\n", cSpan1, cSpan2);
printf("szTrim1=[%s]\r\n", szTrim1);
printf("szTrim2=[%s]\r\n", szTrim2);
}
int main(int argc, char* argv[])
{
// 测试头
printf("删除头部的空字符:\r\n");
TestSpeedTrim(true);
// 测试尾
printf("\r\n删除尾部的空字符:\r\n");
TestSpeedTrim(false);
getchar();
return 0;
}
总结
在实际C/C++底层开发中,虽然 `std::string` 已经高度封装,但在处理 char* 数组时,合理使用 memmove 和逆向索引往往能获得更好的控制力与潜在性能增益。以上关于C++实现去除字符串头尾指定字符功能的详细内容,更多关于C++字符串处理技巧的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









