【问题标题】:A function to find and substitute specific text?查找和替换特定文本的功能?
【发布时间】:2019-02-15 10:33:37
【问题描述】:

是否有我可以使用的功能来替换特定文本。

例如: char *test = "^Hello world^"; 将替换为 char *test = "<s>Hello world</s>";

另一个例子:char *test2 = "This is ~my house~ bud" 将被替换为 char *test2 = "This is <b>my house</b> bud"

【问题讨论】:

  • C 中字符串的问题是内存管理不是自动的。因此,用<s> 替换^ 需要分配更大的缓冲区,并将新字符串写入缓冲区。也就是说你要找的函数是你需要写的函数。 mallocstrcpystrcatmemcpysprintfsnprintf 等标准函数可能很有用,但只能帮助您编写函数。
  • 另一个问题是像char *test = "^Hello world^";这样的字符串不应该被修改。任何这样做的尝试都会导致未定义的行为,并且通常会导致崩溃。这与char test[] = "^Hello world^"; 不同,其中字符串的值存储在数组中并且数组是可修改的。但是,要扩展字符串,您将不得不沉迷于内存管理;对于较大的字符串,您需要更多空间,因此您必须以某种方式分配该空间。而且,由于涉及内存管理,因此没有标准的 C 库函数来处理替换部分。
  • 没有库函数可以为您做到这一点,但您只需几行代码即可完成。 C 提供了可以在计算机上完成任何事情的工具——由您将各个部分拼凑在一起以完成拼图。在替换的情况下,最简单的情况是知道 final 字符串需要多长时间(+1 表示 nul-terminating 字符)。这样,您可以简单地分配 final length + 1 个字节(使用malloc),然后用重组后的文本填充新分配的缓冲区。这个网站上有很多这样的例子。让我们知道。

标签: c string char substitution


【解决方案1】:

在开始替换字符串中的子字符串之前,您必须了解您正在处理的内容。在您的示例中,您想知道是否可以替换字符串中的字符,并举个例子:

char *test = "^Hello world^";

通过如上所示test 声明和初始化,是在只读 内存中创建的字符串文字(几乎在所有系统上)以及任何尝试修改存储在只读内存中的字符会调用 未定义行为(很可能是分段错误)

如 cmets 中所述,test 可以声明并初始化为 字符数组,例如char test[] = "^Hello world^"; 并确保 test 是可修改的,但这并不能解决替换字符串比被替换子字符串长的问题。

要处理额外的字符,您有两个选择 (1) 您可以声明 test[] 足够大以容纳替换,或者 (2) 您可以为替换字符串动态分配存储空间,以及 realloc 额外如果您达到原始分配限制,则内存。

例如,如果您将与 test 关联的代码限制为单个函数,则可以使用足够数量的字符来声明 test 以处理替换,例如

#define MAXC 1024  /* define a constant for the maximum number of characters */
...
    test[MAXC] = "^Hello world^";

然后您只需要跟踪原始字符串长度加上每次替换添加的字符数,并确保总数永远不会超过 MAXC-1(为 nul-terminating 保留空间特点)。

但是,如果您决定将替换代码移动到单独的函数中——您现在会遇到无法返回指向本地声明数组的指针的问题(因为本地声明的数组是在函数堆栈空间中声明的——当函数返回时被销毁(释放以供重用))本地声明的数组具有自动存储持续时间。见:C11 Standard - 6.2.4 Storage durations of objects

为避免本地声明的数组无法在函数返回中幸存的问题,您可以简单地为新字符串动态分配存储空间,这会导致新字符串具有分配的存储持续时间,这对程序的生命周期,或者直到调用 free() 释放内存。这允许您在函数中为新字符串声明和分配存储空间,进行子字符串替换,然后返回指向新字符串的指针以供调用函数使用。

对于您的情况,在函数中简单声明一个新字符串并分配两倍于原始字符串的存储量是一种合理的方法。 (您仍然必须跟踪您使用的内存字节数,但是如果您应该达到原始分配限制,您可以realloc 额外内存)此过程可以继续并容纳任意数量的字符串和替换,不超过系统上的可用内存。

虽然有很多方法可以处理替换,但只需搜索每个子字符串的原始字符串,然后将文本复制到子字符串到新字符串,然后复制替换子字符串可以让您“英寸蠕虫" 从原始字符串的开头到结尾进行替换替换。您面临的唯一挑战是跟踪使用的字符数(因此您可以在必要时重新分配)并在您进行时从头到尾推进您在原件中的阅读位置。

您的示例在某种程度上使过程复杂化,因为您需要在两个替换字符串之一之间进行交替,因为您沿着字符串向下工作。这可以通过一个简单的切换标志来处理。 (您替换0,1,0,1,... 的变量),然后它将确定在需要时使用的正确替换字符串。

三元运算符(例如test ? if_true : if_false; 可以帮助减少您在代码中散布的if (test) { if_true; } else { if_false; } 块的数量——这取决于您。如果if (test) {} 格式更具可读性给你——使用它,否则,使用 三元

以下示例将 (1) 原始字符串、(2) 查找子字符串、(3) 第一个替换子字符串和 (4) 第二个替换子字符串作为程序的参数。它在strreplace() 函数中分配新字符串,请求替换并将指向新字符串的指针返回给调用函数。代码被大量注释以帮助您跟进,例如

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* replace all instances of 'find' in 's' with 'r1' and `r2`, alternating.
 * allocate memory, as required, to hold string with replacements,
 * returns allocated string with replacements on success, NULL otherwise.
 */
char *strreplace (const char *s, const char *find, 
                    const char *r1, const char *r2)
{
    const char *p = s,              /* pointer to s */
        *sp = s;                    /* 2nd substring pointer */ 
    char *newstr = NULL,            /* newsting pointer to allocate/return */
        *np = newstr;               /* pointer to newstring to fill */
    size_t newlen = 0,              /* length for newstr */
        used = 0,                   /* amount of allocated space used */
        slen = strlen (s),          /* length of s */
        findlen = strlen (find),    /* length of find string */
        r1len = strlen (r1),        /* length of replace string 1 */
        r2len = strlen (r2);        /* length of replace string 2 */
    int toggle = 0;                 /* simple 0/1 toggle flag for r1/r2 */

    if (s == NULL || *s == 0) { /* validate s not NULL or empty */
        fputs ("strreplace() error: input NULL or empty\n", stderr);
        return NULL;
    }

    newlen = slen * 2;              /* double length of s for newstr */
    newstr = calloc (1, newlen);    /* allocate twice length of s */

    if (newstr == NULL) {           /* validate ALL memory allocations */
        perror ("calloc-newstr");
        return NULL;
    }
    np = newstr;                    /* initialize newpointer to newstr */

    /* locate each substring using strstr */
    while ((sp = strstr (p, find))) {   /* find beginning of each substring */
        size_t len = sp - p;            /* length to substring */

        /* check if realloc needed? */
        if (used + len + (toggle ? r2len : r1len) + 1 > newlen) {
            void *tmp = realloc (newstr, newlen * 2);   /* realloc to temp */
            if (!tmp) {                     /* validate realloc succeeded */
                perror ("realloc-newstr");
                return NULL;
            }
            newstr = tmp;       /* assign realloc'ed block to newstr */
            newlen *= 2;        /* update newlen */
        }
        strncpy (np, p, len);   /* copy from pointer to substring */
        np += len;              /* advance newstr pointer by len */
        *np = 0;                /* nul-terminate (already done by calloc) */
        strcpy (np, toggle ? r2 : r1);  /* copy r2/r1 string to end */
        np += toggle ? r2len : r1len;   /* advance newstr pointer by r12len */
        *np = 0;                /* <ditto> */
        p += len + findlen;     /* advance p by len + findlen */
        used += len + (toggle ? r2len : r1len); /* update used characters */
        toggle = toggle ? 0 : 1;    /* toggle 0,1,0,1,... */
    }

    /* handle segment of s after last find substring */
    slen = strlen (p);          /* get remaining length */
    if (slen) {                 /* if not at end */
        if (used + slen + 1 > newlen) { /* check if realloc needed? */
            void *tmp = realloc (newstr, used + slen + 1);  /* realloc */
            if (!tmp) {         /* validate */
                perror ("realloc-newstr");
                return NULL;
            }
            newstr = tmp;       /* assign */
            newlen += slen + 1; /* update (not required here, know why? */
        }
        strcpy (np, p);         /* add final segment to string */
        *(np + slen) = 0;       /* nul-terminate */
    }

    return newstr;  /* return newstr */
}

int main (int argc, char **argv) {

    const char  *s = NULL,
                *find = NULL,
                *r1 = NULL,
                *r2 = NULL;
    char *newstr = NULL;

    if (argc < 5) { /* validate required no. or arguments given */
        fprintf (stderr, "error: insufficient arguments,\n"
                        "usage: %s <find> <rep1> <rep2>\n", argv[0]);
        return 1;
    }
    s = argv[1];        /* assign arguments to poitners */
    find = argv[2];
    r1 = argv[3];
    r2 = argv[4];

    newstr = strreplace (s, find, r1, r2);  /* replace substrings in s */

    if (newstr) {   /* validate return */
        printf ("oldstr: %s\nnewstr: %s\n", s, newstr);
        free (newstr);  /* don't forget to free what you allocate */
    }
    else {  /* handle error */
        fputs ("strreplace() returned NULL\n", stderr);
        return 1;
    }

    return 0;
}

(上面,strreplace 函数使用指针遍历(“inch-worm”)原始字符串进行替换,但如果这对您更有意义,您可以使用字符串索引和索引变量)

(另请注意使用calloc 进行原始分配。calloc 分配新内存并将其设置为全零,这有助于确保您不会忘记 nul-terminate 您的字符串,但请注意,realloc 添加的任何内存都不会被清零 - 除非您使用 memset 等手动将其清零。上面的代码在每次复制后手动终止新字符串,因此您可以使用malloccalloc 进行分配)

使用/输出示例

第一个例子:

$ ./bin/str_substr_replace2 "^Hello world^" "^" "<s>" "</s>"
oldstr: ^Hello world^
newstr: <s>Hello world</s>

第二个例子:

$ ./bin/str_substr_replace2 "This is ~my house~ bud" "~" "<b>" "</b>"
oldstr: This is ~my house~ bud
newstr: This is <b>my house</b> bud

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此 (2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/str_substr_replace2 "This is ~my house~ bud" "~" "<b>" "</b>"
==8962== Memcheck, a memory error detector
==8962== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==8962== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==8962== Command: ./bin/str_substr_replace2 This\ is\ ~my\ house~\ bud ~ \<b\> \</b\>
==8962==
oldstr: This is ~my house~ bud
newstr: This is <b>my house</b> bud
==8962==
==8962== HEAP SUMMARY:
==8962==     in use at exit: 0 bytes in 0 blocks
==8962==   total heap usage: 1 allocs, 1 frees, 44 bytes allocated
==8962==
==8962== All heap blocks were freed -- no leaks are possible
==8962==
==8962== For counts of detected and suppressed errors, rerun with: -v
==8962== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

查看一下,如果您有任何其他问题,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-06-15
    • 1970-01-01
    • 2018-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-01
    相关资源
    最近更新 更多