C'de ayırıcılarla bölünmüş dize


155

C programlama dilinde sınırlayıcıları olan bir dizenin dizisini ayırmak ve döndürmek için nasıl işlev yazabilirim?

char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
str_split(str,',');

25
strtokAynı şeyi elde etmek için standart kitaplıktaki işlevi kullanabilirsiniz .
Daniel Kamil Kozar


Bir yorum ... bir strtok()aile işlevinin kilit noktası static variablesC.'de anlamaktır , yani kullanıldıkları ardışık işlev çağrısı arasında nasıl davranırlar. Aşağıdaki
koduma

Yanıtlar:


165

strtok()Bir dizeyi bölmek (ve kullanılacak ayırıcıyı belirtmek) için işlevi kullanabilirsiniz. strtok()İçine geçirilen dizeyi değiştirecek unutmayın . Orijinal dize başka bir yerde gerekiyorsa, bir kopyasını alın ve kopyayı adresine iletin strtok().

DÜZENLE:

Örnek (örneğin, "JAN ,,, FEB, MAR" gibi ardışık sınırlayıcıları işlemediğini unutmayın):

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

char** str_split(char* a_str, const char a_delim)
{
    char** result    = 0;
    size_t count     = 0;
    char* tmp        = a_str;
    char* last_comma = 0;
    char delim[2];
    delim[0] = a_delim;
    delim[1] = 0;

    /* Count how many elements will be extracted. */
    while (*tmp)
    {
        if (a_delim == *tmp)
        {
            count++;
            last_comma = tmp;
        }
        tmp++;
    }

    /* Add space for trailing token. */
    count += last_comma < (a_str + strlen(a_str) - 1);

    /* Add space for terminating null string so caller
       knows where the list of returned strings ends. */
    count++;

    result = malloc(sizeof(char*) * count);

    if (result)
    {
        size_t idx  = 0;
        char* token = strtok(a_str, delim);

        while (token)
        {
            assert(idx < count);
            *(result + idx++) = strdup(token);
            token = strtok(0, delim);
        }
        assert(idx == count - 1);
        *(result + idx) = 0;
    }

    return result;
}

int main()
{
    char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    char** tokens;

    printf("months=[%s]\n\n", months);

    tokens = str_split(months, ',');

    if (tokens)
    {
        int i;
        for (i = 0; *(tokens + i); i++)
        {
            printf("month=[%s]\n", *(tokens + i));
            free(*(tokens + i));
        }
        printf("\n");
        free(tokens);
    }

    return 0;
}

Çıktı:

$ ./main.exe
months=[JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC]

month=[JAN]
month=[FEB]
month=[MAR]
month=[APR]
month=[MAY]
month=[JUN]
month=[JUL]
month=[AUG]
month=[SEP]
month=[OCT]
month=[NOV]
month=[DEC]

60
Selam! strtoktarafından obsoleted olarak işaretlenmiş strsep(3)kılavuz sayfasında.
osgx

4
Bunun için Stack Overflow ile ilgili kanonik soru / cevap olabileceğinden, strtok kullanarak çoklu iş parçacığına ilişkin bazı uyarılar yok mu?
Peter Mortensen

3
@osgx Bu sayfaya göre, taşınabilirliğin strsepyerine geçiyor strtok, ancak strtoktercih ediliyor . Bu nedenle, boş alanlar için desteğe veya aynı anda birden fazla dizeyi bölmeye ihtiyacınız yoksa, strtokdaha iyi bir seçimdir.

4
@Dojo: Hatırlıyor; sorunlu olmasının nedenlerinden biri de budur. Kullanımda daha iyi olurdu strtok_s()(Microsoft, C11 Ek K, isteğe bağlı) ya da strtok_r()düz daha (POSIX) strtok(). Düz strtok()bir kütüphane fonksiyonunda kötüdür. Kitaplık işlevini çağıran hiçbir işlev strtok()o anda kullanılamaz ve kütüphane işlevi tarafından çağrılan hiçbir işlev çağrılamaz strtok().
Jonathan Leffler

3
Sadece bir strtok()iş parçacığı güvenli olmayan bir not (@JonathanLeffler belirtilen nedenlerle) ve bu nedenle bu işlev tamamen iş parçacığı güvenli değildir. Bunu işlenmiş bir ortamda kullanmaya çalışırsanız, düzensiz ve öngörülemeyen sonuçlar alırsınız. Değiştirme strtok()için strtok_r()bu konu düzeltmeleri.
Sean W

70

Bunun strsepiçin hala en iyi araç olduğunu düşünüyorum :

while ((token = strsep(&str, ","))) my_fn(token);

Kelimenin tam anlamıyla bir dize ayıran bir satır.

Ek parantezler, bir eşitlik operatörü değil, bir görevin sonucunu kasten test ettiğimizi gösteren stilistik bir unsurdur ==.

Bu modelin çalışması tokenve strher ikisinin de türü vardır char *. Bir dizgi değişmeziyle başladıysanız, önce bunun bir kopyasını oluşturmak istersiniz:

// More general pattern:
const char *my_str_literal = "JAN,FEB,MAR";
char *token, *str, *tofree;

tofree = str = strdup(my_str_literal);  // We own str's memory now.
while ((token = strsep(&str, ","))) my_fn(token);
free(tofree);

İki sınırlayıcı birlikte görünürse , boş dize olan strbir tokendeğer alırsınız . Değeristr karşılaşılan her sınırlayıcının üzerine sıfır bayt yazılması ile değiştirilir - ilk olarak ayrıştırılan dizeyi kopyalamanın başka bir nedeni.

Bir yorumda, birisi bunun daha taşınabilir olduğundan strtokdaha iyi olduğunu önerdi . Ubuntu ve Mac OS X ; diğer unixy sistemlerinin de yaptığını tahmin etmek güvenlidir. Windows yok , ancak bu kısa ve tatlı değiştirmeyi sağlayan:strsepstrtokstrsepstrsepstrbrkstrsep

char *strsep(char **stringp, const char *delim) {
  if (*stringp == NULL) { return NULL; }
  char *token_start = *stringp;
  *stringp = strpbrk(token_start, delim);
  if (*stringp) {
    **stringp = '\0';
    (*stringp)++;
  }
  return token_start;
}

İştestrsep vs'nin iyi bir açıklaması strtok. Artıları ve eksileri öznel olarak değerlendirilebilir; ancak, bunun strsepyerine geçecek şekilde tasarlanmış bir işaret olduğunu düşünüyorum strtok.


3
Daha doğrusu taşınabilirlik konusunda: POSIX 7 değil , BSD türetilmiş ve glibc'de uygulanmıştır .
Ciro Santilli 法轮功 7 病 六四 事件 法轮功

Hemen sormak üzereydim ... Pelle C'nin strdup'u var (), ama strsep () yok.
rdtsc

1
neden tofreeözgür ve değil str?
Sdlion

1
Serbest bırakamazsınız strçünkü değeri çağrılarla değiştirilebilir strsep(). tofreeTutarlı olarak değeri, boşaltmak istediğiniz belleğin başlangıcına işaret eder.
Tyler

26

String tokenizer bu kod sizi doğru yönde koymalıdır.

int main(void) {
  char st[] ="Where there is will, there is a way.";
  char *ch;
  ch = strtok(st, " ");
  while (ch != NULL) {
  printf("%s\n", ch);
  ch = strtok(NULL, " ,");
  }
  getch();
  return 0;
}

13

Aşağıdaki yöntem sizin için tüm işi (bellek ayırma, uzunluk sayma) yapacak. Daha fazla bilgi ve açıklama burada bulunabilir - C dizesini bölmek için Java String.split () yönteminin uygulanması

int split (const char *str, char c, char ***arr)
{
    int count = 1;
    int token_len = 1;
    int i = 0;
    char *p;
    char *t;

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
            count++;
        p++;
    }

    *arr = (char**) malloc(sizeof(char*) * count);
    if (*arr == NULL)
        exit(1);

    p = str;
    while (*p != '\0')
    {
        if (*p == c)
        {
            (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
            if ((*arr)[i] == NULL)
                exit(1);

            token_len = 0;
            i++;
        }
        p++;
        token_len++;
    }
    (*arr)[i] = (char*) malloc( sizeof(char) * token_len );
    if ((*arr)[i] == NULL)
        exit(1);

    i = 0;
    p = str;
    t = ((*arr)[i]);
    while (*p != '\0')
    {
        if (*p != c && *p != '\0')
        {
            *t = *p;
            t++;
        }
        else
        {
            *t = '\0';
            i++;
            t = ((*arr)[i]);
        }
        p++;
    }

    return count;
}

Bu nasıl kullanılır:

int main (int argc, char ** argv)
{
    int i;
    char *s = "Hello, this is a test module for the string splitting.";
    int c = 0;
    char **arr = NULL;

    c = split(s, ' ', &arr);

    printf("found %d tokens.\n", c);

    for (i = 0; i < c; i++)
        printf("string #%d: %s\n", i, arr[i]);

    return 0;
}

4
Huh Üç yıldızlı Programcı :)) Bu kulağa ilginç geliyor.
Michi

Bunu yaptığımda, ya son belirtecine çok fazla şey ekler ya da çok fazla bellek ayırır. Bu çıktı: found 10 tokens. string #0: Hello, string #1: this string #2: is string #3: a string #4: test string #5: module string #6: for string #7: the string #8: string string #9: splitting.¢
KeizerHarm

2
Bu örnekte birden fazla bellek sızıntısı var. Bunu okuyan herkes için bu yaklaşımı kullanmayın. Bunun yerine strtok veya strsep tokenizasyonu yaklaşımlarını tercih edin.
Jorma Rebane

7

İşte iki sentim:

int split (const char *txt, char delim, char ***tokens)
{
    int *tklen, *t, count = 1;
    char **arr, *p = (char *) txt;

    while (*p != '\0') if (*p++ == delim) count += 1;
    t = tklen = calloc (count, sizeof (int));
    for (p = (char *) txt; *p != '\0'; p++) *p == delim ? *t++ : (*t)++;
    *tokens = arr = malloc (count * sizeof (char *));
    t = tklen;
    p = *arr++ = calloc (*(t++) + 1, sizeof (char *));
    while (*txt != '\0')
    {
        if (*txt == delim)
        {
            p = *arr++ = calloc (*(t++) + 1, sizeof (char *));
            txt++;
        }
        else *p++ = *txt++;
    }
    free (tklen);
    return count;
}

Kullanımı:

char **tokens;
int count, i;
const char *str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";

count = split (str, ',', &tokens);
for (i = 0; i < count; i++) printf ("%s\n", tokens[i]);

/* freeing tokens */
for (i = 0; i < count; i++) free (tokens[i]);
free (tokens);

3
oh boi, üç işaretçi! Zaten lol sadece ben kullanarak korkuyorum, ben c işaretçiler ile çok iyi değilim.
Hafiz Temuri

Teşekkürler adam, yukarıdaki tüm strtok cevapları çabaları bir sürü sonra bile benim durumumda işe yaramadı ve kod bir cazibe gibi çalışır!
hmmftg

4

Yukarıdaki örnekte, dizede boş sonlandırılmış dizeler dizisini (istediğiniz gibi) yerine döndürmenin bir yolu olurdu. Bununla birlikte, işlev tarafından değiştirilmesi gerektiği için bir değişmez dizeyi geçirmeyi mümkün kılmaz:

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

char** str_split( char* str, char delim, int* numSplits )
{
    char** ret;
    int retLen;
    char* c;

    if ( ( str == NULL ) ||
        ( delim == '\0' ) )
    {
        /* Either of those will cause problems */
        ret = NULL;
        retLen = -1;
    }
    else
    {
        retLen = 0;
        c = str;

        /* Pre-calculate number of elements */
        do
        {
            if ( *c == delim )
            {
                retLen++;
            }

            c++;
        } while ( *c != '\0' );

        ret = malloc( ( retLen + 1 ) * sizeof( *ret ) );
        ret[retLen] = NULL;

        c = str;
        retLen = 1;
        ret[0] = str;

        do
        {
            if ( *c == delim )
            {
                ret[retLen++] = &c[1];
                *c = '\0';
            }

            c++;
        } while ( *c != '\0' );
    }

    if ( numSplits != NULL )
    {
        *numSplits = retLen;
    }

    return ret;
}

int main( int argc, char* argv[] )
{
    const char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";

    char* strCpy;
    char** split;
    int num;
    int i;

    strCpy = malloc( strlen( str ) * sizeof( *strCpy ) );
    strcpy( strCpy, str );

    split = str_split( strCpy, ',', &num );

    if ( split == NULL )
    {
        puts( "str_split returned NULL" );
    }
    else
    {
        printf( "%i Results: \n", num );

        for ( i = 0; i < num; i++ )
        {
            puts( split[i] );
        }
    }

    free( split );
    free( strCpy );

    return 0;
}

Muhtemelen bunu yapmanın daha temiz bir yolu var, ama fikri anladınız.


3

Bu işlev bir char * dizesi alır ve ayırıcı tarafından böler. Arka arkaya birden fazla sınırlayıcı olabilir. İşlevin orignal dizgiyi değiştirdiğini unutmayın. Orijinalin değiştirilmemesi için öncelikle orijinal dizenin bir kopyasını oluşturmanız gerekir. Bu işlev herhangi bir cstring işlev çağrısı kullanmaz, bu nedenle diğerlerinden biraz daha hızlı olabilir. Bellek ayırmayı önemsemiyorsanız, alt strst'leri işlevin üst kısmına size strlen (src_str) / 2 ile ayırabilirsiniz ve (belirtilen c ++ "sürümü" gibi) işlevin alt yarısını atlayabilirsiniz. Bunu yaparsanız, işlev O (N) değerine düşürülür, ancak aşağıda gösterilen bellek için optimize edilmiş yol O (2N) olur.

İşlev:

char** str_split(char *src_str, const char deliminator, size_t &num_sub_str){
  //replace deliminator's with zeros and count how many
  //sub strings with length >= 1 exist
  num_sub_str = 0;
  char *src_str_tmp = src_str;
  bool found_delim = true;
  while(*src_str_tmp){
    if(*src_str_tmp == deliminator){
      *src_str_tmp = 0;
      found_delim = true;
    }
    else if(found_delim){ //found first character of a new string
      num_sub_str++;
      found_delim = false;
      //sub_str_vec.push_back(src_str_tmp); //for c++
    }
    src_str_tmp++;
  }
  printf("Start - found %d sub strings\n", num_sub_str);
  if(num_sub_str <= 0){
    printf("str_split() - no substrings were found\n");
    return(0);
  }

  //if you want to use a c++ vector and push onto it, the rest of this function
  //can be omitted (obviously modifying input parameters to take a vector, etc)

  char **sub_strings = (char **)malloc( (sizeof(char*) * num_sub_str) + 1);
  const char *src_str_terminator = src_str_tmp;
  src_str_tmp = src_str;
  bool found_null = true;
  size_t idx = 0;
  while(src_str_tmp < src_str_terminator){
    if(!*src_str_tmp) //found a NULL
      found_null = true;
    else if(found_null){
      sub_strings[idx++] = src_str_tmp;
      //printf("sub_string_%d: [%s]\n", idx-1, sub_strings[idx-1]);
      found_null = false;
    }
    src_str_tmp++;
  }
  sub_strings[num_sub_str] = NULL;

  return(sub_strings);
}

Bu nasıl kullanılır:

  char months[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
  char *str = strdup(months);
  size_t num_sub_str;
  char **sub_strings = str_split(str, ',', num_sub_str);
  char *endptr;
  if(sub_strings){
    for(int i = 0; sub_strings[i]; i++)
      printf("[%s]\n", sub_strings[i]);
  }
  free(sub_strings);
  free(str);

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

/**
 *  splits str on delim and dynamically allocates an array of pointers.
 *
 *  On error -1 is returned, check errno
 *  On success size of array is returned, which may be 0 on an empty string
 *  or 1 if no delim was found.  
 *
 *  You could rewrite this to return the char ** array instead and upon NULL
 *  know it's an allocation problem but I did the triple array here.  Note that
 *  upon the hitting two delim's in a row "foo,,bar" the array would be:
 *  { "foo", NULL, "bar" } 
 * 
 *  You need to define the semantics of a trailing delim Like "foo," is that a
 *  2 count array or an array of one?  I choose the two count with the second entry
 *  set to NULL since it's valueless.
 *  Modifies str so make a copy if this is a problem
 */
int split( char * str, char delim, char ***array, int *length ) {
  char *p;
  char **res;
  int count=0;
  int k=0;

  p = str;
  // Count occurance of delim in string
  while( (p=strchr(p,delim)) != NULL ) {
    *p = 0; // Null terminate the deliminator.
    p++; // Skip past our new null
    count++;
  }

  // allocate dynamic array
  res = calloc( 1, count * sizeof(char *));
  if( !res ) return -1;

  p = str;
  for( k=0; k<count; k++ ){
    if( *p ) res[k] = p;  // Copy start of string
    p = strchr(p, 0 );    // Look for next null
    p++; // Start of next string
  }

  *array = res;
  *length = count;

  return 0;
}

char str[] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,";

int main() {
  char **res;
  int k=0;
  int count =0;
  int rc;

  rc = split( str, ',', &res, &count );
  if( rc ) {
    printf("Error: %s errno: %d \n", strerror(errno), errno);
  }

  printf("count: %d\n", count );
  for( k=0; k<count; k++ ) {
    printf("str: %s\n", res[k]);
  }

  free(res );
  return 0;
}

3

Aşağıda benim strtok()uygulama zString kütüphanesinden . zstring_strtok()standart kütüphanelerden farklıdırstrtok()ardışık sınırlayıcıları işleme biçiminde .

Sadece aşağıdaki koda bir göz atın, nasıl çalıştığı hakkında bir fikir edineceğinizden emin olun (olabildiğince çok yorum kullanmaya çalıştım)

char *zstring_strtok(char *str, const char *delim) {
    static char *static_str=0;      /* var to store last address */
    int index=0, strlength=0;       /* integers for indexes */
    int found = 0;                  /* check if delim is found */

    /* delimiter cannot be NULL
    * if no more char left, return NULL as well
    */
    if (delim==0 || (str == 0 && static_str == 0))
        return 0;

    if (str == 0)
        str = static_str;

    /* get length of string */
    while(str[strlength])
        strlength++;

    /* find the first occurance of delim */
    for (index=0;index<strlength;index++)
        if (str[index]==delim[0]) {
            found=1;
            break;
        }

    /* if delim is not contained in str, return str */
    if (!found) {
        static_str = 0;
        return str;
    }

    /* check for consecutive delimiters
    *if first char is delim, return delim
    */
    if (str[0]==delim[0]) {
        static_str = (str + 1);
        return (char *)delim;
    }

    /* terminate the string
    * this assignmetn requires char[], so str has to
    * be char[] rather than *char
    */
    str[index] = '\0';

    /* save the rest of the string */
    if ((str + index + 1)!=0)
        static_str = (str + index + 1);
    else
        static_str = 0;

        return str;
}

Aşağıda örnek bir kullanım verilmiştir ...

  Example Usage
      char str[] = "A,B,,,C";
      printf("1 %s\n",zstring_strtok(s,","));
      printf("2 %s\n",zstring_strtok(NULL,","));
      printf("3 %s\n",zstring_strtok(NULL,","));
      printf("4 %s\n",zstring_strtok(NULL,","));
      printf("5 %s\n",zstring_strtok(NULL,","));
      printf("6 %s\n",zstring_strtok(NULL,","));

  Example Output
      1 A
      2 B
      3 ,
      4 ,
      5 C
      6 (null)

Kütüphane Github'dan indirilebilir https://github.com/fnoyanisi/zString


güzel bir! Aradığım şey buydu.
Kostia Kim

3

Aşağıdaki çözümün ideal olduğunu düşünüyorum:

  • Kaynak dizgiyi yok etmez
  • Tekrar giriş - yani, bir veya daha fazla dizideki herhangi bir yerden güvenle arayabilirsiniz.
  • Taşınabilir
  • Birden çok ayırıcıyı doğru şekilde işler
  • Hızlı ve verimli

Kodun açıklaması:

  1. Bir yapı tanımlama tokenJetonların adreslerini ve uzunluklarını saklamak için
  2. En kötü durumda bunlar için yeterli bellek ayırın, yani strtamamen ayırıcılardan oluşur, böylecestrlen(str) + 1 jetonlar vardır, hepsi boş dizeler
  3. taramak strHer jetonun adresini ve uzunluğunu kaydederek
  4. Bunu, bir boyut için ek alan da dahil olmak üzere, doğru boyuttaki çıktı dizisini ayırmak için kullanın. NULLSentinel değeri
  5. , Tahsis kopyalama ve başlangıç ve uzunluk bilgilerini kullanarak belirteçleri eklemek - kullanımını memcpydaha hızlı o en olarakstrcpy ve uzunlukları biliyoruz
  6. Jeton adresini ve uzunluk dizisini serbest bırakın
  7. Jeton dizisini döndürme
typedef struct {
    const char *start;
    size_t len;
} token;

char **split(const char *str, char sep)
{
    char **array;
    unsigned int start = 0, stop, toks = 0, t;
    token *tokens = malloc((strlen(str) + 1) * sizeof(token));
    for (stop = 0; str[stop]; stop++) {
        if (str[stop] == sep) {
            tokens[toks].start = str + start;
            tokens[toks].len = stop - start;
            toks++;
            start = stop + 1;
        }
    }
    /* Mop up the last token */
    tokens[toks].start = str + start;
    tokens[toks].len = stop - start;
    toks++;
    array = malloc((toks + 1) * sizeof(char*));
    for (t = 0; t < toks; t++) {
        /* Calloc makes it nul-terminated */
        char *token = calloc(tokens[t].len + 1, 1);
        memcpy(token, tokens[t].start, tokens[t].len);
        array[t] = token;
    }
    /* Add a sentinel */
    array[t] = NULL; 
    free(tokens);
    return array;
}

Not malloc denetleme kısaca geçilmiştir.

Genel olarak, char *doğru bir şekilde serbest bırakmak için arayan üzerinde çok sorumluluk yerleştirir gibi böyle bir bölünmüş işlev işaretçiler bir dizi döndürmez . Tercih ettiğim bir arayüz, burada açıkladığım gibi, çağıranın bir geri çağırma işlevini geçmesine ve her jeton için çağırmasına izin vermektir: C'de bir String bölün .


Ayırıcıları iki kez taramak, büyük olasılıkla büyük bir dizi ayırmaktan daha fazla tavsiye edilir token.
chqrlie

2

Bunu kullanmayı deneyin.

char** strsplit(char* str, const char* delim){
    char** res = NULL;
    char*  part;
    int i = 0;

    char* aux = strdup(str);

    part = strdup(strtok(aux, delim));

    while(part){
        res = (char**)realloc(res, (i + 1) * sizeof(char*));
        *(res + i) = strdup(part);

        part = strdup(strtok(NULL, delim));
        i++;
    }

    res = (char**)realloc(res, i * sizeof(char*));
    *(res + i) = NULL;

    return res;
}

2

Bu optimize edilmiş yöntem * sonucundaki işaretçiler dizisini oluşturur (veya günceller) ve * sayımındaki öğe sayısını döndürür.

Beklediğiniz maksimum dize sayısını (mevcut bir diziyi veya başka bir yeniden ifadeyi belirttiğinizde) belirtmek için "max" kullanın, aksi takdirde 0 olarak ayarlayın

Sınırlayıcılar listesiyle karşılaştırmak için sınırlamayı karakter * olarak tanımlayın ve satırı değiştirin:

if (str[i]==delim) {

aşağıdaki iki satırla:

 char *c=delim; while(*c && *c!=str[i]) c++;
 if (*c) {

Zevk almak

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

char **split(char *str, size_t len, char delim, char ***result, unsigned long *count, unsigned long max) {
  size_t i;
  char **_result;

  // there is at least one string returned
  *count=1;

  _result= *result;

  // when the result array is specified, fill it during the first pass
  if (_result) {
    _result[0]=str;
  }

  // scan the string for delimiter, up to specified length
  for (i=0; i<len; ++i) {

    // to compare against a list of delimiters,
    // define delim as a string and replace 
    // the next line:
    //     if (str[i]==delim) {
    //
    // with the two following lines:
    //     char *c=delim; while(*c && *c!=str[i]) c++;
    //     if (*c) {
    //       
    if (str[i]==delim) {

      // replace delimiter with zero
      str[i]=0;

      // when result array is specified, fill it during the first pass
      if (_result) {
        _result[*count]=str+i+1;
      }

      // increment count for each separator found
      ++(*count);

      // if max is specified, dont go further
      if (max && *count==max)  {
        break;
      }

    }
  }

  // when result array is specified, we are done here
  if (_result) {
    return _result;
  }

  // else allocate memory for result
  // and fill the result array                                                                                    

  *result=malloc((*count)*sizeof(char*));
  if (!*result) {
    return NULL;
  }
  _result=*result;

  // add first string to result
  _result[0]=str;

  // if theres more strings
  for (i=1; i<*count; ++i) {

    // find next string
    while(*str) ++str;
    ++str;

    // add next string to result
    _result[i]=str;

  }

  return _result;
}  

Kullanım örneği:

#include <stdio.h>

int main(int argc, char **argv) {
  char *str="JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
  char **result=malloc(6*sizeof(char*));
  char **result2=0;
  unsigned long count;
  unsigned long count2;
  unsigned long i;

  split(strdup(str),strlen(str),',',&result,&count,6);
  split(strdup(str),strlen(str),',',&result2,&count2,0);

  if (result)
  for (i=0; i<count; ++i) {
    printf("%s\n",result[i]);
  }

  printf("\n");

  if (result2)
  for (i=0; i<count2; ++i) {
    printf("%s\n", result2[i]);
  }

  return 0;

}

2

Benim versiyonum:

int split(char* str, const char delimeter, char*** args) {
    int cnt = 1;
    char* t = str;

    while (*t == delimeter) t++;

    char* t2 = t;
    while (*(t2++))
        if (*t2 == delimeter && *(t2 + 1) != delimeter && *(t2 + 1) != 0) cnt++;

    (*args) = malloc(sizeof(char*) * cnt);

    for(int i = 0; i < cnt; i++) {
        char* ts = t;
        while (*t != delimeter && *t != 0) t++;

        int len = (t - ts + 1);
        (*args)[i] = malloc(sizeof(char) * len);
        memcpy((*args)[i], ts, sizeof(char) * (len - 1));
        (*args)[i][len - 1] = 0;

        while (*t == delimeter) t++;
    }

    return cnt;
}

2

Bu, çok karakterli sınırlayıcıları işleyebilen bir dize bölme işlevidir. Sınırlayıcının bölünmekte olan dizeden daha uzun olması durumunda bufferve stringLengthsolarak ayarlanacağını (void *) 0ve numStringsolarak ayarlanacağını unutmayın 0.

Bu algoritma test edilmiştir ve çalışır. (Feragatname: ASCII olmayan dizeler için test edilmemiştir ve arayanın geçerli parametreler verdiğini varsayar)

void splitString(const char *original, const char *delimiter, char ** * buffer, int * numStrings, int * * stringLengths){
    const int lo = strlen(original);
    const int ld = strlen(delimiter);
    if(ld > lo){
        *buffer = (void *)0;
        *numStrings = 0;
        *stringLengths = (void *)0;
        return;
    }

    *numStrings = 1;

    for(int i = 0;i < (lo - ld);i++){
        if(strncmp(&original[i], delimiter, ld) == 0) {
            i += (ld - 1);
            (*numStrings)++;
        }
    }

    *stringLengths = (int *) malloc(sizeof(int) * *numStrings);

    int currentStringLength = 0;
    int currentStringNumber = 0;
    int delimiterTokenDecrementCounter = 0;
    for(int i = 0;i < lo;i++){
        if(delimiterTokenDecrementCounter > 0){
            delimiterTokenDecrementCounter--;
        } else if(i < (lo - ld)){
            if(strncmp(&original[i], delimiter, ld) == 0){
                (*stringLengths)[currentStringNumber] = currentStringLength;
                currentStringNumber++;
                currentStringLength = 0;
                delimiterTokenDecrementCounter = ld - 1;
            } else {
                currentStringLength++;
            }
        } else {
            currentStringLength++;
        }

        if(i == (lo - 1)){
            (*stringLengths)[currentStringNumber] = currentStringLength;
        }
    }

    *buffer = (char **) malloc(sizeof(char *) * (*numStrings));
    for(int i = 0;i < *numStrings;i++){
        (*buffer)[i] = (char *) malloc(sizeof(char) * ((*stringLengths)[i] + 1));
    }

    currentStringNumber = 0;
    currentStringLength = 0;
    delimiterTokenDecrementCounter = 0;
    for(int i = 0;i < lo;i++){
        if(delimiterTokenDecrementCounter > 0){
            delimiterTokenDecrementCounter--;
        } else if(currentStringLength >= (*stringLengths)[currentStringNumber]){
            (*buffer)[currentStringNumber][currentStringLength] = 0;
            delimiterTokenDecrementCounter = ld - 1;
            currentStringLength = 0;
            currentStringNumber++;
        } else {
            (*buffer)[currentStringNumber][currentStringLength] = (char)original[i];
            currentStringLength++;
        }
    }
    buffer[currentStringNumber][currentStringLength] = 0;
}

Basit kod:

int main(){
    const char *string = "STRING-1 DELIM string-2 DELIM sTrInG-3";
    char **buffer;
    int numStrings;
    int * stringLengths;

    splitString(string, " DELIM ", &buffer, &numStrings, &stringLengths);

    for(int i = 0;i < numStrings;i++){
        printf("String: %s\n", buffer[i]);
    }
}

Kütüphaneler:

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

Bunu ana hattan nasıl adlandırırım? Arabelleğe ne geçeceğimi bilmiyorum.
Aymon Fournier

Ayırma mantığı yanlış. realloc () yeni işaretçiyi döndürür ve döndürülen değeri atarsınız. Yeni bellek işaretçisini döndürmek için uygun bir yol yoktur - işlev prototipi, ayrılan boyutu kabul etmek bufferve arayana ayırmayı bırakmak, maksimum boyut öğelerini işlemek için değiştirilmelidir .
Alex

@Alex Sabit, tamamen yeniden yazılmış ve test edilmiştir. Not: Bunun ASCII olmayanlar için çalışıp çalışmayacağından emin değilim.
Élektra

Yeni başlayanlar için bu C kodu değildir. Ve neden C ++ gerçek referans ile işaretçiler geçmek?
Kamiccolo

@Kamiccolo Üzgünüm, bu tam olarak nasıl C kodu değil? Ayrıca, burada işaretçilerden geçerek neden bir sorun var?
Élektra

1

Benim yaklaşımım dizeyi taramak ve sınırlayıcılardan (ve ilk karakterden) sonra her karakterin işaret etmesine izin vermek, aynı zamanda dizgide ayırıcı görünümünü '\ 0' olarak atamaktır.
Öncelikle orijinal dizenin bir kopyasını oluşturun (sabit olduğundan), ardından böler sayısını işaretçi parametresi len'e ileterek tarayın . Bundan sonra, ilk sonuç işaretçisini kopya dizesi işaretçisine doğrultun, sonra kopya dizesini tarayın: bir sınırlayıcıyla karşılaştığınızda, '\ 0' değerine atayın, böylece önceki sonuç dizesi sonlandırılır ve sonraki sonuç dizesi işaretçisini bir sonrakine yönlendirir karakter işaretçisi.

char** split(char* a_str, const char a_delim, int* len){
    char* s = (char*)malloc(sizeof(char) * strlen(a_str));
    strcpy(s, a_str);
    char* tmp = a_str;
    int count = 0;
    while (*tmp != '\0'){
        if (*tmp == a_delim) count += 1;
        tmp += 1;
    }
    *len = count;
    char** results = (char**)malloc(count * sizeof(char*));
    results[0] = s;
    int i = 1;
    while (*s!='\0'){
        if (*s == a_delim){
            *s = '\0';
            s += 1;
            results[i++] = s;
        }
        else s += 1;
    }
    return results;
}

Bu yöntem yanlış. Bu yazıyı yeni sildim, ama sonra bazılarınız için belki de ilginç olduğunu fark ettim.
metalcrash

1

Kodum (test edildi):

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

int dtmsplit(char *str, const char *delim, char ***array, int *length ) {
  int i=0;
  char *token;
  char **res = (char **) malloc(0 * sizeof(char *));

  /* get the first token */
   token = strtok(str, delim);
   while( token != NULL ) 
   {
        res = (char **) realloc(res, (i + 1) * sizeof(char *));
        res[i] = token;
        i++;
      token = strtok(NULL, delim);
   }
   *array = res;
   *length = i;
  return 1;
}

int main()
{
    int i;
    int c = 0;
    char **arr = NULL;

    int count =0;

    char str[80] = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    c = dtmsplit(str, ",", &arr, &count);
    printf("Found %d tokens.\n", count);

    for (i = 0; i < count; i++)
        printf("string #%d: %s\n", i, arr[i]);

   return(0);
}

Sonuç:

Found 12 tokens.
string #0: JAN
string #1: FEB
string #2: MAR
string #3: APR
string #4: MAY
string #5: JUN
string #6: JUL
string #7: AUG
string #8: SEP
string #9: OCT
string #10: NOV
string #11: DEC

1
Strtok işlevinin 'str' dizesine uygulandığını değiştirdiğini unutmayın!
SchLx

1

Patlat ve implode - ilk dize değişmeden kalır, dinamik bellek ayırma

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

typedef struct
{
    uintptr_t   ptr;
    int         size;
} token_t;

int explode(char *str, int slen, const char *delimiter, token_t **tokens)
{
    int i = 0, c1 = 0, c2 = 0;

    for(i = 0; i <= slen; i++)
    {
            if(str[i] == *delimiter)
            {
                c1++;
            }
    }

    if(c1 == 0)
    {
            return -1;
    }

    *tokens = (token_t*)calloc((c1 + 1), sizeof(token_t));
    ((*tokens)[c2]).ptr = (uintptr_t)str;

    i = 0; 
    while(i <= slen)
    {
        if((str[i] == *delimiter) || (i == slen))
        {
                ((*tokens)[c2]).size = (int)((uintptr_t)&(str[i]) - (uintptr_t)(((*tokens)[c2]).ptr));
                if(i < slen)
                {
                    c2++;
                    ((*tokens)[c2]).ptr = (uintptr_t)&(str[i + 1]);
                }
        }
        i++;
    }
    return (c1 + 1);
}

char* implode(token_t *tokens, int size, const char *delimiter)
{
    int     i, len = 0;
    char    *str;

    for(i = 0; i < len; i++)
    {
        len += tokens[i].size + 1;
    }

    str = (char*)calloc(len, sizeof(char));

    len = 0;
    for(i = 0; i < size; i++)
    {
        memcpy((void*)&str[len], (void*)tokens[i].ptr, tokens[i].size);
        len += tokens[i].size;
        str[(len++)] = *delimiter;
    }

    str[len - 1] = '\0';

    return str;
}

Kullanımı:

int main(int argc, char **argv)
{
    int         i, c;
    char        *exp = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
    token_t     *tokens;
    char        *imp;

    printf("%s\n", exp);

    if((c = explode(exp, strlen(exp), ",", &tokens)) > 0)
    {
        imp = implode(tokens, c, ",");
        printf("%s\n", imp);

        for(i = 0; i < c; i++)
        {
            printf("%.*s, %d\n", tokens[i].size, (char*)tokens[i].ptr, tokens[i].size);
        }
    }

    free((void*)tokens);
    free((void*)imp);
    return 0;
}

0

Harici bir kütüphane kullanmak istiyorsanız, tavsiye edemem bstrlib yeterince . Biraz ekstra kurulum gerektirir, ancak uzun vadede kullanımı daha kolaydır.

Örneğin, aşağıdaki dizeyi böldüğünüzde, önce çağrı bstringile bir a oluşturulur bfromcstr(). (A bstring, bir char tamponu etrafındaki bir sargıdır). Ardından, dizeyi virgüllere bölün ve sonucu struct bstrListalanlara qtyve s entrydizisine sahip bir diziye bstringkaydedin.

bstrlib üzerinde çalışacak birçok işlevi var bstrings

Pasta kadar kolay ...

#include "bstrlib.h"
#include <stdio.h>
int main() {
  int i;
  char *tmp = "Hello,World,sak";
  bstring bstr = bfromcstr(tmp);
  struct bstrList *blist = bsplit(bstr, ',');
  printf("num %d\n", blist->qty);
  for(i=0;i<blist->qty;i++) {
    printf("%d: %s\n", i, bstr2cstr(blist->entry[i], '_'));
  }

}

0

Yine başka bir cevap (bu, buradan buraya taşındı) ):

Strtok işlevini kullanmaya çalışın:

bu konuyla ilgili ayrıntıları burada görebilirsiniz veya burada görün

Buradaki sorun, wordsderhal işlemeniz gerektiğidir . Eğer onu bir dizide saklamak istiyorsanız bunun correct sizeiçin cadı ayırmak zorunda değilsiniz .

Yani mesela:

char **Split(char *in_text, char *in_sep)
{
    char **ret = NULL;
    int count = 0;
    char *tmp = strdup(in_text);
    char *pos = tmp;

    // This is the pass ONE: we count 
    while ((pos = strtok(pos, in_sep)) != NULL)
    {
        count++;
        pos = NULL;
    }

    // NOTE: the function strtok changes the content of the string! So we free and duplicate it again! 
    free(tmp);
    pos = tmp = strdup(in_text);

    // We create a NULL terminated array hence the +1
    ret = calloc(count+1, sizeof(char*));
    // TODO: You have to test the `ret` for NULL here

    // This is the pass TWO: we store
    count = 0;
    while ((pos = strtok(pos, in_sep)) != NULL)
    {
        ret[count] = strdup(pos);
        count++;
        pos = NULL;
    }
    free(tmp);

    return count;
}

// Use this to free
void Free_Array(char** in_array)
{
    char *pos = in_array;

    while (pos[0] != NULL)
    {
        free(pos[0]);
        pos++;

    }

    free(in_array);

}

Not : Ayırma problemlerinden kaçınmak için sayıları hesaplamak (bir geçiş) ve kopya yapmak (iki geçiş) için aynı döngü ve işlevi kullanırız.

Not 2 : Ayrı yazılarda belirtilen nedenleri kullanarak strtok'un başka bir uygulamasını kullanabilirsiniz.

Bunu aşağıdaki gibi kullanabilirsiniz:

int main(void)
{
  char **array = Split("Hello World!", " ");
  // Now you have the array
  // ...

  // Then free the memory
  Free_Array(array);
  array = NULL;
  return 0;
}

(Test etmedim, bu yüzden lütfen çalışmazsa bana bildirin!)


0

Bu soruyu çevreleyen iki konu bellek yönetimi ve iş parçacığı güvenliği. Çok sayıda yazıdan görebileceğiniz gibi, bu C'de sorunsuz bir şekilde başarmak için kolay bir görev değil: Aşağıdaki gibi bir çözüm istedim:

  • İplik güvenli. (strtok iplik güvenli değildir)
  • Malloc veya türevlerinden herhangi birini kullanmaz (bellek yönetimi sorunlarını önlemek için)
  • Tek tek alanlardaki dizi sınırlarını kontrol eder (bilinmeyen verilerde segment hatalarını önlemek için)
  • Çok baytlı alan ayırıcılarıyla çalışır (utf-8)
  • girişteki fazla alanları yok sayar
  • geçersiz alan uzunlukları için yumuşak hata rutini sağlar

Ortaya koyduğum çözüm tüm bu kriterleri karşılıyor. Muhtemelen burada yayınlanan diğer çözümlerden biraz daha fazla iş, ancak pratikte, diğer çözümlerin ortak tuzaklarından kaçınmak için ekstra işin buna değer olduğunu düşünüyorum.

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

struct splitFieldType {
    char *field;
    int   maxLength;
};

typedef struct splitFieldType splitField;

int strsplit(splitField *fields, int expected, const char *input, const char *fieldSeparator, void (*softError)(int fieldNumber,int expected,int actual))  {
    int i;
    int fieldSeparatorLen=strlen(fieldSeparator);
    const char *tNext, *tLast=input;

    for (i=0; i<expected && (tNext=strstr(tLast, fieldSeparator))!=NULL; ++i) {
        int len=tNext-tLast;
        if (len>=fields[i].maxLength) {
            softError(i,fields[i].maxLength-1,len);
            len=fields[i].maxLength-1;
        }
        fields[i].field[len]=0;
        strncpy(fields[i].field,tLast,len);
        tLast=tNext+fieldSeparatorLen;
    }
    if (i<expected) {
        if (strlen(tLast)>fields[i].maxLength) {
            softError(i,fields[i].maxLength,strlen(tLast));
        } else {
            strcpy(fields[i].field,tLast);
        }
        return i+1;
    } else {
        return i;
    }
}


void monthSplitSoftError(int fieldNumber, int expected, int actual) {
    fprintf(stderr,"monthSplit: input field #%d is %d bytes, expected %d bytes\n",fieldNumber+1,actual,expected);
}


int main() {
  const char *fieldSeparator=",";
  const char *input="JAN,FEB,MAR,APRI,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR";

  struct monthFieldsType {
    char field1[4];
    char field2[4];
    char field3[4];
    char field4[4];
    char field5[4];
    char field6[4];
    char field7[4];
    char field8[4];
    char field9[4];
    char field10[4];
    char field11[4];
    char field12[4];
  } monthFields;

  splitField inputFields[12] = {
    {monthFields.field1,  sizeof(monthFields.field1)},
    {monthFields.field2,  sizeof(monthFields.field2)},
    {monthFields.field3,  sizeof(monthFields.field3)},
    {monthFields.field4,  sizeof(monthFields.field4)},
    {monthFields.field5,  sizeof(monthFields.field5)},
    {monthFields.field6,  sizeof(monthFields.field6)},
    {monthFields.field7,  sizeof(monthFields.field7)},
    {monthFields.field8,  sizeof(monthFields.field8)},
    {monthFields.field9,  sizeof(monthFields.field9)},
    {monthFields.field10, sizeof(monthFields.field10)},
    {monthFields.field11, sizeof(monthFields.field11)},
    {monthFields.field12, sizeof(monthFields.field12)}
  };

  int expected=sizeof(inputFields)/sizeof(splitField);

  printf("input data: %s\n", input);
  printf("expecting %d fields\n",expected);

  int ct=strsplit(inputFields, expected, input, fieldSeparator, monthSplitSoftError);

  if (ct!=expected) {
    printf("string split %d fields, expected %d\n", ct,expected);
  }

  for (int i=0;i<expected;++i) {
    printf("field %d: %s\n",i+1,inputFields[i].field);
  }

  printf("\n");
  printf("Direct structure access, field 10: %s", monthFields.field10);
}

Aşağıda bir derleme ve çıktı örneği verilmiştir. Örneğimde, yumuşak hatanın nasıl çalıştığını görebilmeniz için "NİSAN" ı kasten dile getirdiğimi unutmayın.

$ gcc strsplitExample.c && ./a.out
input data: JAN,FEB,MAR,APRIL,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR
expecting 12 fields
monthSplit: input field #4 is 5 bytes, expected 3 bytes
field 1: JAN
field 2: FEB
field 3: MAR
field 4: APR
field 5: MAY
field 6: JUN
field 7: JUL
field 8: AUG
field 9: SEP
field 10: OCT
field 11: NOV
field 12: DEC

Direct structure access, field 10: OCT

Zevk almak!


0

İşte tokenize için güvenle çalışacak başka uygulamasıdır dize değişmezi bir tahsis işaretçi-to-pointer char (örneğin dönen prototip söz konusu talep eşleştirme char **). Sınırlayıcı dize birden çok karakter içerebilir ve giriş dizesi herhangi bir sayıda belirteç içerebilir. Tüm tahsisler ve yeniden tahsisler POSIX ile mallocveya reallocPOSIX olmadan gerçekleştirilir strdup.

Tahsis edilen ilk işaretçi sayısı NPTRSsabit tarafından kontrol edilir ve tek sınırlama sıfırdan büyük olmasıdır. char **Döndürülen bir içeren sentinel NULL sonra geçen benzer belirteci *argv[]ve form kullanılabilir içinde execv, execvpveexecve .

Olduğu gibi strtok()çoklu sıralı sınırlayıcı böylece, tek bir ayırıcı olarak kabul edilir "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"sadece tek sanki ayrıştırılır ','ayrıldığı "MAY,JUN".

Aşağıdaki işlev satır içinde yorumlanır main()ve aylar bölünerek kısa bir süre eklenir. Tahsis edilen ilk işaretçi sayısı 2, giriş dizesini tokenleştirirken üç yeniden tahsisi zorlayacak şekilde ayarlanmıştır :

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

#define NPTRS 2     /* initial number of pointers to allocate (must be > 0) */

/* split src into tokens with sentinel NULL after last token.
 * return allocated pointer-to-pointer with sentinel NULL on success,
 * or NULL on failure to allocate initial block of pointers. The number
 * of allocated pointers are doubled each time reallocation required.
 */
char **strsplit (const char *src, const char *delim)
{
    int i = 0, in = 0, nptrs = NPTRS;       /* index, in/out flag, ptr count */
    char **dest = NULL;                     /* ptr-to-ptr to allocate/fill */
    const char *p = src, *ep = p;           /* pointer and end-pointer */

    /* allocate/validate nptrs pointers for dest */
    if (!(dest = malloc (nptrs * sizeof *dest))) {
        perror ("malloc-dest");
        return NULL;
    }
    *dest = NULL;   /* set first pointer as sentinel NULL */

    for (;;) {  /* loop continually until end of src reached */
        if (!*ep || strchr (delim, *ep)) {  /* if at nul-char or delimiter char */
            size_t len = ep - p;            /* get length of token */
            if (in && len) {                /* in-word and chars in token */
                if (i == nptrs - 1) {       /* used pointer == allocated - 1? */
                    /* realloc dest to temporary pointer/validate */
                    void *tmp = realloc (dest, 2 * nptrs * sizeof *dest);
                    if (!tmp) {
                        perror ("realloc-dest");
                        break;  /* don't exit, original dest still valid */
                    }
                    dest = tmp;             /* assign reallocated block to dest */
                    nptrs *= 2;             /* increment allocated pointer count */
                }
                /* allocate/validate storage for token */
                if (!(dest[i] = malloc (len + 1))) {
                    perror ("malloc-dest[i]");
                    break;
                }
                memcpy (dest[i], p, len);   /* copy len chars to storage */
                dest[i++][len] = 0;         /* nul-terminate, advance index */
                dest[i] = NULL;             /* set next pointer NULL */
            }
            if (!*ep)                       /* if at end, break */
                break;
            in = 0;                         /* set in-word flag 0 (false) */
        }
        else {  /* normal word char */
            if (!in)                        /* if not in-word */
                p = ep;                     /* update start to end-pointer */
            in = 1;                         /* set in-word flag 1 (true) */
        }
        ep++;   /* advance to next character */
    }

    return dest;
}

int main (void) {

    char *str = "JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC",
        **tokens;                           /* pointer to pointer to char */

    if ((tokens = strsplit (str, ","))) {   /* split string into tokens */
        for (char **p = tokens; *p; p++) {  /* loop over filled pointers */
            puts (*p);
            free (*p);      /* don't forget to free allocated strings */
        }
        free (tokens);      /* and pointers */
    }
}

Örnek Kullanım / Çıktı

$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC

Başka sorunuz varsa bize bildirin.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.