Как заменить часть строки другой подстрокой

Мне нужно, чтобы строка "on" была заменена на "in", функция strstr() возвращает указатель на строку, поэтому я решил, что присвоение нового значения этому указателю будет работать, но это не сработало.

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

int main(void) {
    char *m = "cat on couch";
    *strstr(m, "on") = "in";
    printf("%s\n", m);
}

person jacobay43    schedule 08.05.2019    source источник


Ответы (3)


Заменить подстроку другой легко, если обе подстроки имеют одинаковую длину:

  • найти позицию подстроки с strstr
  • если она присутствует, используйте memcpy, чтобы перезаписать ее новой подстрокой.
  • назначение указателя с *strstr(m, "on") = "in"; неверно и должно генерировать предупреждение компилятора. Вы бы избежали таких ошибок с gcc -Wall -Werror.
  • обратите внимание, однако, что вы не можете изменить строковый литерал, вам нужно определить инициализированный массив char, чтобы вы могли его изменить.

Вот исправленная версия:

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

int main(void) {
    char m[] = "cat on couch";
    char *p = strstr(m, "on");
    if (p != NULL) {
        memcpy(p, "in", 2);
    }
    printf("%s\n", m);
    return 0;
}

Если замена короче, код немного сложнее:

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

int main(void) {
    char m[] = "cat is out roaming";
    char *p = strstr(m, "out");
    if (p != NULL) {
        memcpy(p, "in", 2);
        memmove(p + 2, p + 3, strlen(p + 3) + 1);
    }
    printf("%s\n", m);
    return 0;
}

В общем случае это еще сложнее, и массив должен быть достаточно большим, чтобы учесть разницу в длине:

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

int main(void) {
    char m[30] = "cat is inside the barn";
    char *p = strstr(m, "inside");
    if (p != NULL) {
        memmove(p + 7, p + 6, strlen(p + 6) + 1);
        memcpy(p, "outside", 7);
    }
    printf("%s\n", m);
    return 0;
}

Вот общая функция, которая обрабатывает все случаи:

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

char *strreplace(char *s, const char *s1, const char *s2) {
    char *p = strstr(s, s1);
    if (p != NULL) {
        size_t len1 = strlen(s1);
        size_t len2 = strlen(s2);
        if (len1 != len2)
            memmove(p + len2, p + len1, strlen(p + len1) + 1);
        memcpy(p, s2, len2);
    }
    return s;
}

int main(void) {
    char m[30] = "cat is inside the barn";

    printf("%s\n", m);
    printf("%s\n", strreplace(m, "inside", "in"));
    printf("%s\n", strreplace(m, "in", "on"));
    printf("%s\n", strreplace(m, "on", "outside"));
    return 0;
}
person chqrlie    schedule 08.05.2019

Есть несколько проблем с этим подходом. Во-первых, выключено, m указывает на постоянную память, поэтому попытка перезаписать память в этом месте приводит к неопределенному поведению.

Во-вторых, строка: strstr(m, "on") = "in" не будет изменять указанную строку, а вместо этого переназначит указатель.

Решение:

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

int main(void)
{
    char m[] = "cat on couch";
    memcpy(strstr(m, "on"), "in", 2);
    printf("%s\n", m);
}

Обратите внимание, что если бы вы только что использовали простое strcpy, оно завершилось бы нулем после "cat in", поэтому здесь необходимо memcpy. strncpy также будет работать, но перед используй это.

Также следует знать, что если вы имеете дело со строками, которые не являются жестко запрограммированными константами в вашей программе, вы всегда должны проверять возвращаемое значение strstr, strchr и связанных функций для NULL.

person Govind Parmar    schedule 08.05.2019

Эта функция выполняет замену универсального шаблона для всех экземпляров подстроки строкой замены. Он выделяет буфер правильного размера для результата. Поведение хорошо определено для случая пустой подстроки, соответствующей семантике javascript replace(). Там, где это возможно, вместо strcpy используется memcpy.

/*
 * strsub : substring and replace substring in strings.
 *
 * Function to replace a substring with a replacement string. Returns a
 * buffer of the correct size containing the input string with all instances
 * of the substring replaced by the replacement string.
 *
 * If the substring is empty the replace string is written before each character
 * and at the end of the string.
 *
 * Returns NULL on error after setting the error number.
 *
 */

char * strsub (char *input, char *substring, char *replace)
{
    int     number_of_matches = 0;
    size_t  substring_size = strlen(substring), replace_size = strlen(replace), buffer_size;
    char    *buffer, *bp, *ip;

/*
 * Count the number of non overlapping substring occurences in the input string. This
 * information is used to calculate the correct buffer size.
 */
    if (substring_size)
    {
        ip = strstr(input, substring);
        while (ip != NULL)
        {
            number_of_matches++;
            ip = strstr(ip+substring_size, substring);
        }
    }
    else
        number_of_matches = strlen (input) + 1;

/*
 * Allocate a buffer of the correct size for the output.
 */
    buffer_size = strlen(input) + number_of_matches*(replace_size - substring_size) + 1;

    if ((buffer = ((char *) malloc(buffer_size))) == NULL)
    {
        errno=ENOMEM;
        return NULL;
    }

/*
 * Rescan the string replacing each occurence of a match with the replacement string.
 * Take care to copy buffer content between matches or in the case of an empty find
 * string one character.
 */
    bp = buffer;
    ip = strstr(input, substring);
    while ((ip != NULL) && (*input != '\0'))
    {
        if (ip == input)
        {
            memcpy (bp, replace, replace_size+1);
            bp += replace_size;
            if (substring_size)
                input += substring_size;
            else
                *(bp++) = *(input++);
            ip = strstr(input, substring);
        }
        else 
            while (input != ip)
                *(bp++) = *(input++);

    }

/*
 * Write any remaining suffix to the buffer, or in the case of an empty find string
 * append the replacement pattern.
 */
    if (substring_size)
        strcpy (bp, input);
    else
        memcpy (bp, replace, replace_size+1);

    return buffer;
}

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

    #define BUFSIZE 1024

    char * read_string (const char * prompt)
    {
        char *buf, *bp;

        if ((buf=(char *)malloc(BUFSIZE))==NULL)
        {
            error (0, ENOMEM, "Memory allocation failure in read_string");
            return NULL;
        }
        else
            bp=buf;

        printf ("%s\n> ", prompt);

        while ((*bp=getchar()) != '\n')bp++;
        *bp = '\0';

        return buf;
    }

    int main ()
    {
        char * input_string = read_string ("Please enter the input string");
        char * pattern_string = read_string ("Please enter the test string");
        char * replace_string = read_string ("Please enter the replacement string");

        char * output_string = strsub (input_string, pattern_string, replace_string);

        printf ("Result       :\n> %s\n", output_string);

        free (input_string);
        free (pattern_string);
        free (replace_string);
        free (output_string); 
        exit(0);
    }
person Jon Guiton    schedule 08.05.2019
comment
Все вызовы strcat должны быть вызовами strcpy, а лучше memcpy :) - person chqrlie; 10.05.2019
comment
Улучшено использование memcpy, где это возможно; улучшенные комментарии. - person Jon Guiton; 13.05.2019
comment
Вот идеи для дальнейшего упрощения: for (ip = input; (ip = strstr(ip, substring)) != NULL; ip += substring_size) { number_of_matches++; }, первый memcpy (bp, replace, replace_size+1); не должен копировать нулевой байт. Вероятно, имеет смысл создать отдельный цикл для случая substring_size == 0, что приведет к более простому коду. - person chqrlie; 13.05.2019