Я хочу разбить строку по алфавитным символам. Я начал с strtok

char str[] = "A89 99B0 C11D98 9";
char delim[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

char *ptr = strtok(str, delim);

while(ptr != NULL)
{
    printf("%s\n", ptr);
    ptr = strtok(NULL, delim);
}

Но мне нужно сохранить разделитель. Что-то вроде

A89 99
B0 
C11
D98 9

Я сомневаюсь, что это выполнимо с помощью strtok, и мне следует поискать другой подход (например, регулярное выражение).

Одно из возможных решений, которое я придумал, - это вставить не буквенно-цифровой разделитель перед каждым буквенным символом в строке, а затем использовать strtok для этого разделителя.

c
0
Googlebot 8 Июл 2021 в 04:16

4 ответа

Лучший ответ

Это зависит от того, что вы делаете с данными, поскольку вполне возможно, что вам не нужно создавать фактические строки. Если вы можете избежать копирования данных, чтобы включить нулевые терминаторы, это может быть полезно. например:

#include <stdio.h>
#include <string.h>
char str[] = "A89 99B0 C11D98 9";
char delim[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

int
main(void)
{
        char *ptr = str;
        while( *ptr ){
                size_t next = strcspn(ptr + 1, delim) + 1;
                fwrite(ptr, 1, next, stdout);
                putc('\n', stdout);
                ptr += next;
        }
}
3
William Pursell 8 Июл 2021 в 01:42

Если все, что вы хотите сделать, это вывести символы на экран, вы можете просто перебрать строку и распечатать биты, но если вам нужно фактически токенизировать строку, чтобы вы могли использовать ее позже, у вас есть два варианта (о которых я упоминал в комментарии):

  1. Если вы хотите выполнить токенизацию типа strtok, где в итоге вы получите строку с завершающим NUL, содержащую терминаторы NUL между отдельными частями, вы можете либо сделать это на месте, либо создать новую строку. Чтобы принять решение, вам необходимо знать размер новой строки, которая будет создана. Затем, если буфер исходной строки достаточно велик, вы можете сделать это на месте, а если это не так, вызывающий может выделить что-то достаточно большое и передать это вместо этого. Чтобы измерить размер новой строки, вы можете использовать тот же трюк, что и sprintf, и если выходная строка является указателем NULL, вы можете вернуть размер, а если это не так, вы можете просто заполнить его.

  2. Вы можете пропустить шаг измерения, и функция сама решит, есть ли во входной строке пробел или нет. Вы бы передали NULL-указатель в качестве выходной строки, и функция сделала бы это на месте, если бы могла, и выходной указатель остался бы NULL, или он выделил бы необходимое пространство, и выходной указатель будет указывать на новую строку. Функция может возвращать выходную строку в любом случае, что может быть более полезным, чем возвращение размера в качестве возвращаемого значения, и вызывающий будет знать, нужно ли ему освобождать пространство на основе значения выходного указателя.

  3. Если вам нужен токенизированный вывод как фактический массив строк, вы всегда можете передать указатель NULL в качестве выходного буфера и заставить функцию распределять все части внутри. Вызывающий всегда должен освобождать части, потому что нет возможности сделать это на месте.

В этом примере показано, как я бы поступил №1:

// if output is not NULL this will take an input and a delimiter
// and construct a NUL terminated set of NUL terminated strings into output
// and return the size of the whole thing
//
// if output is not NULL this will only calculate how much space would be used
// and then return the size of the whole thing
long tokenise(char *output, char *input, char *delimiters)
{
    long length, size = 0;
    char *next, *current = input, *destination = output;
    
    while(next = strpbrk(current+1,delimiters))
    {
        length = next-current;
        if(destination) // if we aren't just measuring
        {
            if(output == input)
                strcpy(destination+length+1,next++); // if we are doing it in-place
            else
                strncpy(destination,current,length); // if we are making a new string
            destination[length] = '\0';
            destination += length+1;
        }
        size += length+1; // +1 = single NUL
        current = next;
    }
    
    length = strlen(current);
    if(destination) // if we aren't just measuring
    {
        if(output != input)
            strncpy(destination,current,length); // if we are making a new string
        destination[length] = '\0';
        destination[length+1] = '\0';
    }
    return size+length+2; // +2 = double NUL
}

И для перебора, распечатав строки:

char *pointer = output;
while(*pointer)
{
    puts(pointer);
    pointer += strlen(pointer)+1;
}

Попробуйте это на https://www.onlinegdb.com/4upvpivar

И повторно используя ту же функцию токенизации, описанную выше, вы можете сделать # 2 следующим образом:

char* onesteptokenise(char **output, char *input, long input_max, char *delimiters)
{
    long size = tokenise(NULL,input,delimiters); // measure the size
    if (size<=input_max)
    {
        // do it in place
        *output = NULL;
        tokenise(input,input,delimiters);
        return input;
    }
    else
    {
        // make a new string
        *output = malloc(size);
        if (*output) tokenise(*output,input,delimiters);
        return *output;
    }
}

Попробуйте это на https://onlinegdb.com/IW_h9hiNV

1
Jerry Jeremiah 8 Июл 2021 в 23:33

Может быть, струнный траверс, попробуйте

// Pseudocode
for (int i = 0; i < strlen(str); i++) {
    if (isalpha(str[i])) printf("\n");
    printf("%c", str[i]);
}
1
Cloud Blue 8 Июл 2021 в 06:26

Как насчет использования результатов из strtok в качестве маски поверх полной копии строки?

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

int main()
{   
    char str[] = "A89 99B0 C11D98 9";
    char delim[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    char *str_dup = strdup(str);

    if (str_dup != NULL)
    {
        char *ptr = strtok(str, delim);

        while(ptr != NULL)
        {
            int field_len = strlen(ptr) + 1;
            char *field_start = str_dup + (ptr - str) - 1;

            printf("%.*s\n", field_len, field_start);

            ptr = strtok(NULL, delim);
        }

        free (str_dup);
    }

    return 0;
}   
2
Jardel Lucca 8 Июл 2021 в 03:09