文号
t73925
阅读
7749
回复
1
收藏
1
C语言中字符串的原地插入、替换、删除
2015/10/09
IP:四川
为了节省内存空间,避免复制来复制去,我们一般喜欢在原地对字符串进行操作。其中最常见的无非是插入、替换、删除。这三个操作有个特点,都可能使字符串长度发生变化。为了适应这种变化,我们在进行这些操作之前,要调整字符串长度。原地操作字符串,最需要注意的一点,是缓冲区长度一定要足够,比如:
// 这样分配的字符串可以增长到199个字符
char str1[200] = "I am a teacher.";
                         
// 这样分配的字符串无法增长,只能缩短
char str1[] = "I am a teacher.";

在C语言中调整字符串长度,一般用memmove移动内存。它和memcpy不同点在于它允许内存区域重叠。调整字符串长度时要同时移动尾部null字符。调整完字符后,一般需要将要替换为的字符串复制到目标位置,此时也可以使用memmove(当然memcpy也可以),但是并不复制尾部null字符。如果是宽字符的话,wmemmove比较方便,它以宽字符个数而不是字节为单位。

为方便起见,定义strmove和wcsmove函数,其中最后一个参数tz=1则复制尾部null字符,tz=0则不复制尾部null字符:
char *strmove(char *ptr2, const char *ptr1, int tz)
{
    return (char*)memmove(ptr2, ptr1, strlen(ptr1) + tz);
}
                        
wchar_t *wcsmove(wchar_t *ptr2, const wchar_t *ptr1, int tz)
{
    return wmemmove(ptr2, ptr1, wcslen(ptr1) + tz);
}

接着我们可以写出strrepl和wcsrepl函数,它可以将某个子串替换为另一个子串:
char *strrepl(char *repl_pos, rsize_t repl_sz, const char *src)
{
    if (repl_sz > strlen(repl_pos)) repl_sz = strlen(repl_pos);
    strmove(repl_pos + strlen(src), repl_pos + repl_sz, 1);
    strmove(repl_pos, src, 0);
    return repl_pos + strlen(src);
}
                        
wchar_t *wcsrepl(wchar_t *repl_pos, rsize_t repl_sz, const wchar_t *src)
{
    if (repl_sz > wcslen(repl_pos)) repl_sz = wcslen(repl_pos);
    wcsmove(repl_pos + wcslen(src), repl_pos + repl_sz, 1);
    wcsmove(repl_pos, src, 0);
    return repl_pos + wcslen(src);
}

这两个函数的使用方法相同。第一个参数repl_pos是被替换的地址,第二个参数repl_sz是被替换的长度,第三个参数src则是要替换为的字符串。

首先我们先定义两个字符串,内容是『I am a teacher.』,不同的是一个使用char[],一个使用wchar_t[]:
// str1是char[]字符串
char str1[200] = "I am a teacher.";
                        
// str2是wchar_t[]字符串
wchar_t str1[200] = L"I am a teacher.";

插入子串的话很简单,repl_pos是要插入的位置,repl_sz=0,src=要插入的字符串:
// str1是char[]字符串
strrepl(str1 + 7, 0, "good ");
                        
// str2是wchar_t[]字符串
wcsrepl(str2 + 7, 0, L"good ");
插入子串后的字符串『I am a good teacher.』。

替换子串的话,先用strstr和wcsstr找到要替换的地址,再用strlen和wcslen计算替换前长度,最后调用strrepl和wcsrepl:
// str1是char[]字符串
char *repl_pos1 = strstr(str1, "teacher");
if (repl_pos1 != NULL)
{
    strrepl(repl_pos1, strlen("teacher"), "professor");
}
                        
// str2是wchar_t[]字符串
wchar_t *repl_pos2 = wcsstr(str2, L"teacher");
if (repl_pos2 != NULL)
{
    wcsrepl(repl_pos2, wcslen(L"teacher"), L"professor");
}
替换子串后的字符串『I am a good professor.』。

删除子串和替换子串类似,把src改成空字符串就可以了,然而更高效的方法是直接使用strmove或wcsmove:
// str1是char[]字符串
char *del_pos1 = strstr(str1, "I am ");
if (del_pos1 != NULL)
{
    //strrepl(del_pos, strlen("I am "), "");
    strmove(del_pos1, del_pos1 + strlen("I am "), 1);
}
                        
// str2是wchar_t[]字符串
wchar_t *del_pos2 = wcsstr(str2, L"I am ");
if (del_pos2 != NULL)
{
    //wcsrepl(del_pos2, wcslen(L"I am "), L"");
    wcsmove(del_pos2, del_pos2 + wcslen(L"I am "), 1);
}
删除子串后的字符串『a good professor.』。

-----------------------------------------------------------------------------

这里用到了一个小技巧:Visual C++ 2005以上版本中,C运行库支持Unicode模式的控制台。使用_setmode将stdin、stdout、stderr的模式设为_O_U16TEXT可以将控制台设为Unicode模式。在Unicode模式时,只有wprintf、wscanf、fwputs、fwgets等宽字符I/O函数起作用。将它们的模式设为_O_TEXT可以切换回普通模式。

完整程序:
#include <fcntl.h> // _O_U16TEXT (unicode console)
#include <io.h> // _setmode (unicode console)
   
#include <stdio.h> // wprintf
#include <string.h> // memmove, strlen, strstr
#include <wchar.h> // wmemmove, wcslen, wcsstr
   
char *strmove(char *ptr2, const char *ptr1, int tz)
{
    return (char*)memmove(ptr2, ptr1, strlen(ptr1) + tz);
}
   
char *strrepl(char *repl_pos, rsize_t repl_sz, const char *src)
{
    if (repl_sz > strlen(repl_pos)) repl_sz = strlen(repl_pos);
    strmove(repl_pos + strlen(src), repl_pos + repl_sz, 1);
    strmove(repl_pos, src, 0);
    return repl_pos + strlen(src);
}
   
wchar_t *wcsmove(wchar_t *ptr2, const wchar_t *ptr1, int tz)
{
    return wmemmove(ptr2, ptr1, wcslen(ptr1) + tz);
}
   
wchar_t *wcsrepl(wchar_t *repl_pos, rsize_t repl_sz, const wchar_t *src)
{
    if (repl_sz > wcslen(repl_pos)) repl_sz = wcslen(repl_pos);
    wcsmove(repl_pos + wcslen(src), repl_pos + repl_sz, 1);
    wcsmove(repl_pos, src, 0);
    return repl_pos + wcslen(src);
}
   
void narrow_replace(void)
{
    char str1[200] = "I am a teacher.";
   
    printf("%s\n", str1);
   
    strrepl(str1 + 7, 0, "good ");
   
    printf("%s\n", str1);
   
    char *repl_pos1 = strstr(str1, "teacher");
    if (repl_pos1 != NULL)
    {
        strrepl(repl_pos1, strlen("teacher"), "professor");
    }
   
    printf("%s\n", str1);
   
    char *del_pos1 = strstr(str1, "I am ");
    if (del_pos1 != NULL)
    {
        //strrepl(del_pos, strlen("I am "), "");
        strmove(del_pos1, del_pos1 + strlen("I am "), 1);
    }
   
    printf("%s\n", str1);
}
   
void wide_replace(void)
{
    wchar_t str2[200] = L"I am a teacher.";
   
    wprintf(L"%s\n", str2);
   
    wcsrepl(str2 + 7, 0, L"good ");
   
    wprintf(L"%s\n", str2);
   
    wchar_t *repl_pos2 = wcsstr(str2, L"teacher");
    if (repl_pos2 != NULL)
    {
        wcsrepl(repl_pos2, wcslen(L"teacher"), L"professor");
    }
   
    wprintf(L"%s\n", str2);
   
    wchar_t *del_pos2 = wcsstr(str2, L"I am ");
    if (del_pos2 != NULL)
    {
        //wcsrepl(del_pos2, wcslen(L"I am "), L"");
        wcsmove(del_pos2, del_pos2 + wcslen(L"I am "), 1);
    }
   
    wprintf(L"%s\n", str2);
}
                        
int wmain(int argc, wchar_t* argv[])
{
    _setmode(_fileno(stdin), _O_U16TEXT); // unicode console
    _setmode(_fileno(stdout), _O_U16TEXT);
    _setmode(_fileno(stderr), _O_U16TEXT);
   
    wprintf(L"wchar_t[]:\n");
    wide_replace();
 
    _setmode(_fileno(stdin), _O_TEXT); // non-unicode console
    _setmode(_fileno(stdout), _O_TEXT);
    _setmode(_fileno(stderr), _O_TEXT);
 
    printf("char[]:\n");
    narrow_replace();
   
    return 0;
}</wchar.h></string.h></stdio.h></io.h></fcntl.h>
[ 修改于11年2个月前 - 2015/10/10 12:44:23 ]
笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的