Функция для поиска и замены конкретного текста? - PullRequest
0 голосов
/ 11 сентября 2018

Есть ли функция, которую я могу использовать, которая позволит мне заменить определенный текст.

Например: 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"

1 Ответ

0 голосов
/ 11 сентября 2018

Прежде чем вы сможете начать замену подстрок в строке, вы должны понять, с чем имеете дело. В вашем примере вы хотите знать, можете ли вы заменить символы в строке, и вы привели в качестве примера:

char *test = "^Hello world^";

После объявления и инициализации, как показано выше test, строковый литерал создается в только для чтения памяти (практически во всех системах) и при любой попытке изменить символы хранится в постоянной памяти, вызывает неопределенное поведение (и, скорее всего, ошибка сегментации)

Как отмечено в комментариях, 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 (резервируя место для символа с нулевым символом ).

Однако, если вы решили переместить код замены в отдельную функцию - у вас возникла проблема, заключающаяся в том, что вы не можете вернуть указатель на локально объявленный массив (поскольку локально объявленный массив объявлен в пространстве стека функций - который уничтожается (освобождается для повторного использования) при возврате функции) Локально объявленный массив имеет продолжительность автоматического хранения . См .: C11 Standard - 6.2.4 Длительность хранения объектов

Чтобы избежать проблемы, связанной с тем, что локально объявленный массив не сохранился после возврата функции, вы можете просто динамически распределить хранилище для вашей новой строки, в результате чего у новой строки будет выделенная продолжительность хранения , что хорошо для жизни программы или до освобождения памяти, вызывая free(). Это позволяет вам объявлять и выделять память для новой строки в функции, заменять подстроки, а затем возвращать указатель на новую строку для использования обратно в вызывающей функции.

С вашей точки зрения, разумным подходом является простое объявление новой строки внутри функции и выделение двойного объема памяти в качестве исходной строки. (вы все равно должны отслеживать количество используемых вами байтов памяти, но тогда у вас есть возможность realloc дополнительной памяти, если вы достигнете своего исходного предела выделения). Этот процесс может продолжаться и приспосабливаться к любому количеству строк и замен, до доступной памяти в вашей системе.

Хотя существует несколько способов приблизиться к подстановкам, просто ищите исходную строку для каждой подстроки, а затем копируйте текст до подстроки в новую строку, а затем копируя подстроку замены, вы можете выполнить "дюйм-червь" msgstr "с начала до конца вашей исходной строки с заменой по ходу дела. Единственная сложная задача - отслеживать количество используемых символов (чтобы вы могли перераспределить их при необходимости) и продвигать позицию чтения в оригинале от начала до конца по мере продвижения.

Ваш пример несколько усложняет процесс, так как вам нужно чередовать одну из двух замещающих строк по мере продвижения вниз по строке. Это можно сделать с помощью простого переключателя. (переменная, которую вы чередуете 0,1,0,1,...), которая затем определит правильную строку замены для использования там, где это необходимо.

Оператор троичный (например, test ? if_true : if_false; может помочь уменьшить количество if (test) { if_true; } else { if_false; } блоков, которые вы набросали в своем коде - это ваше дело. Если формат if (test) {} более читабелен для Вы - используйте это, иначе используйте троичный .

Следующий пример принимает (1) исходную строку, (2) подстроку find, (3) 1-ю подстроку замены и (4) 2-ю подстроку замены в качестве аргументов программы. Он выделяет новую строку в функции 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 -определите вашу строку, но учтите, что любая память, добавленная realloc, не будет обнуляться - если вы вручную не обнуляете ее с помощью memset или т. п. Код выше вручную завершает новую строку после каждой копии, так что вы можно использовать malloc или calloc для выделения)

Пример использования / Вывод

Первый пример:

$ ./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)

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

...