C로 구분하는 문자열 분할
C 프로그래밍 언어로 딜리미터가 있는 문자열에 대해 배열을 분할하고 반환하는 함수를 작성하려면 어떻게 해야 합니까?
char* str = "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC";
str_split(str,',');
함수를 사용하여 문자열을 분할할 수 있습니다(및 사용할 딜리미터를 지정합니다).주의:strtok()
는 전달된 문자열을 변경합니다.한 경우 하여 복사한 후 을 전달하십시오.strtok()
.
편집:
예(예를 들어 연속되는 딜리미터(JAN, …FEB, MAR)는 취급하지 않습니다).
#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;
}
출력:
$ ./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]
생각에는strsep
여전히 최적의 도구입니다.
while ((token = strsep(&str, ","))) my_fn(token);
그것은 말 그대로 줄을 나누는 한 줄이다.
것을 연산자가 아닙니다.==
.
하려면 , 「 」token
★★★★★★★★★★★★★★★★★」str
다 이 있다char *
. 리터럴로 문자열 리터럴로 시작하는 경우 먼저 복사본을 만듭니다.
// 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);
되어 있는 경우str
, 를 얻을 수 있습니다.token
value입니다.「」의 값str
는 검출된 각 딜리미터가0 바이트로 덮어쓰기 되어 있기 때문에 수정되어 있습니다.이것은, 최초로 해석되는 문자열을 카피하는 적절한 이유이기도 합니다.
어떤 로 '아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 네.strtok
보다 strsep
strtok
이치노 및 는 Ubuntu mac Mac OS X ub를 탑재하고 있습니다.strsep
; 다른 Unixy 시스템도 마찬가지라고 추측해도 무방합니다.에 Windows가 없다strsep
단, ,가 있습니다.strbrk
'스위트'를 하게 합니다.strsep
★★★★
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;
}
여기 좋은 설명이 있습니다.strsep
»strtok
할 수 아, 아, 아, 아, 아, '우리에게 좋은 '라고 생각합니다.strsep
는 을 대체하기 위해 고안되었습니다.strtok
.
문자열 토큰라이저 이 코드는 올바른 방향으로 이동합니다.
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;
}
제 의견은 이렇습니다.
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;
}
사용방법:
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);
다음과 같은 솔루션이 이상적이라고 생각합니다.
- 소스 문자열을 파괴하지 않습니다.
- 재진입 - 1개 이상의 스레드 어디에서나 안전하게 호출할 수 있습니다.
- 휴대용
- 여러 개의 구분자를 올바르게 처리
- 빠르고 효율적인
코드 설명:
- 를
token
- 즉, 이 경우에 충분한 를 할당할 수 있습니다.
str
되어 있기 에, 「세퍼레이터」가 있습니다.strlen(str) + 1
빈 , "tokens, empty strings" - ★★
str
, 모든 및 " " " "의 - 하여 올바른합니다.도 사용할 수 .여유 공간도 포함됩니다.
NULL
값 - 및 할당,및 : " " " " " " " " " " " 를 사용합니다.
memcpy
빠르기strcpy
를 알 수 있습니다. - 토큰 주소 및 길이 배열 해제
- 토큰 배열 반환
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;
}
★★★ malloc
이치노
일반적으로, 저는 일련의 제품을 반환하지 않을 것입니다.char *
이러한 분할 기능으로부터의 포인터는, 발신자에게 올바르게 해방시키는 큰 책임을 지게 되기 때문에, 다음과 같이 기능합니다.여기서 설명한 바와 같이 발신자가 콜백함수를 전달하고 모든 토큰에 대해 콜백함수를 호출할 수 있도록 하는 것이 바람직합니다.
다.strtok()
★★★★★★★★★★★★★★★★★」strsep()
입력 문자열을 변경합니다.strspn() 및 strpbrk()를 사용하여 딜리미터를 기준으로 문자열을 분할하는 함수를 작성할 수 있습니다.
알고리즘:
- 있지 않으면 2로 돌아갑니다.그렇지 않으면 반환됩니다.
null
. - 구분자가 워드의 를 사용).
strspn()
경우)라고.start
. - 이전 위치 더 끝)를.
strpbrk()
경우)라고.end
. - 및 을 " " " 에서 할당합니다.
start
로로 합니다.end
아, 아, 아, 아, 아, 아, 아, 네. - 리턴 토큰
장점:
- 스레드 세이프
- 복수의 딜리미터를 처리합니다.
- 포터블.
- 를 들어 입력 문자열은 수정하지 않습니다).
strtok()
★★★★★★★★★★★★★★★★★」strsep()
구현:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
* alloc_str function allocates memory and copy substring
* to allocated memory.
*/
static char * alloc_str (const char * start, const char * end) {
if (!start || !end || (start >= end)) {
return NULL;
}
char * tmp = malloc (end - start + 1);
if (tmp) {
memcpy (tmp, start, end - start);
tmp[end - start] = '\0';
} else {
fprintf (stderr, "Failed to allocate memory\n");
exit (EXIT_FAILURE);
}
return tmp;
}
/*
* str_split function returns the next token which is sequences of contiguous
* characters separated by any of the characters that are part of delimiters.
*
* Parameters:
* p_str : Address of pointer to the string that you want to split.
* sep : A set of characters that delimit the pieces in the string.
*
* Behaviour is undefined if sep is not a pointer to a null-terminated string.
*
* Return :
* Returns the pointer to dynamically allocated memory where the token is copied.
* If p_str is NULL or empty string, NULL is returned.
*/
char * str_split (char ** p_str, const char * sep) {
char * token = NULL;
if (*p_str && **p_str) {
char * p_end;
// skip separator
*p_str += strspn(*p_str, sep);
p_end = *p_str;
// find separator
p_end = strpbrk (p_end, sep);
// strpbrk() returns null pointer if no such character
// exists in the input string which is part of sep argument.
if (!p_end) {
p_end = *p_str + strlen (*p_str);
}
token = alloc_str (*p_str, p_end);
*p_str = p_end;
}
return token;
}
/*==================================================*/
/*==================================================*/
/*
* Just a helper function
*/
void token_helper (char * in_str, const char * delim) {
printf ("\nInput string : ");
if (in_str) printf ("\"%s\"\n", in_str);
else printf ("NULL\n");
if (delim) printf ("Delimiter : \"%s\"\n", delim);
char * ptr = in_str;
char * token = NULL;
printf ("Tokens:\n");
while ((token = str_split(&ptr, delim)) != NULL) {
printf ("-> %s\n", token);
/* You can assign this token to a pointer of an array of pointers
* and return that array of pointers from this function.
* Since, this is for demonstration purpose, I am
* freeing the allocated memory now.
*/
free (token);
}
}
/*
* Driver function
*/
int main (void) {
/* test cases */
char string[100] = "hello world!";
const char * delim = " ";
token_helper (string, delim);
strcpy (string, " hello world,friend of mine!");
delim = " ,";
token_helper (string, delim);
strcpy (string, "Another string");
delim = "-!";
token_helper (string, delim);
strcpy (string, " one more -- string !");
delim = "- !";
token_helper (string, delim);
strcpy (string, "");
delim = " ";
token_helper (string, delim);
token_helper (NULL, "");
strcpy (string, "hi");
delim = " -$";
token_helper (string, delim);
strcpy (string, "Give papa a cup of proper coffee in a copper coffee cup.");
delim = "cp";
token_helper (string, delim);
strcpy (string, "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC");
delim = ",";
token_helper (string, delim);
return 0;
}
출력:
# ./a.out
Input string : "hello world!"
Delimiter : " "
Tokens:
-> hello
-> world!
Input string : " hello world,friend of mine!"
Delimiter : " ,"
Tokens:
-> hello
-> world
-> friend
-> of
-> mine!
Input string : "Another string"
Delimiter : "-!"
Tokens:
-> Another string
Input string : " one more -- string !"
Delimiter : "- !"
Tokens:
-> one
-> more
-> string
Input string : ""
Delimiter : " "
Tokens:
Input string : NULL
Delimiter : ""
Tokens:
Input string : "hi"
Delimiter : " -$"
Tokens:
-> hi
Input string : "Give papa a cup of proper coffee in a copper coffee cup."
Delimiter : "cp"
Tokens:
-> Give
-> a
-> a a
-> u
-> of
-> ro
-> er
-> offee in a
-> o
-> er
-> offee
-> u
-> .
Input string : "JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
Delimiter : ","
Tokens:
-> JAN
-> FEB
-> MAR
-> APR
-> MAY
-> JUN
-> JUL
-> AUG
-> SEP
-> OCT
-> NOV
-> DEC
파티에는 늦었지만, 여기 2가지 기능이 더 있습니다.또, 고객의 요구에 맞추어 조정할 수도 있습니다(투고 하단의 소스 코드).
고객의 요구에 적합한 기능을 판별하려면 아래 구현 노트를 참조하십시오.
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h> // C99
// tokenize destructively
char **str_toksarray_alloc(
char **strp, /* InOut: pointer to the source non-constant c-string */
const char *delim, /* c-string containing the delimiting chars */
size_t *ntoks, /* InOut: # of tokens to parse/parsed (NULL or *ntoks==0 for all tokens) */
bool keepnulls /* false ignores empty tokens, true includes them */
);
// tokenize non-destructively
char **str_toksarray_alloc2(
const char *str, /* the source c-string */
const char *delim,
size_t *ntoks,
bool keepnulls
);
사용상의 주의
source-string)을 거의 합니다.strp
★★★★★★★★★★★★★★★★★」str
(신호)
strp
(스트링에 할당됨)은 이미 할당되어 있는 토큰화되지 않은c 스트링의 주소입니다. str
는 변경되지 않은c 스트링입니다(문자열일 수도 있습니다).'c-string'은nul
- of - "terminated buffer of chars." (문자버퍼).나머지 인수는 두 함수에 대해 동일합니다.
사용 가능한 모든 토큰을 해석하려면 음소거 ntoks
입니다).NULL
않은 는 최대 해석됩니다.*ntoks
또는 토큰이 없어질 때까지(최초 토큰이 우선입니다).경우든, 「」가 되었을 때ntoks
non-NULL
정상적으로 해석된 토큰의 수에 따라 갱신됩니다.
또한 음소거되지 않은 것은 ntoks
이치노 10개의 토큰이 되어 있는 ntoks
1000까지 990개의 포인터가 불필요하게 할당됩니다.되어 있는데, 한 경우, source-string 1000은 10개입니다.ntoks
10월 10일
두 함수는 모두 char-pointer 배열을 할당하고 반환합니다만,str_toksarray_alloc()
"source-string"은 "source-string"으로 지정합니다.str_toksarray_alloc2()
는 동적으로 할당된 토큰의 복사본을 가리키도록 합니다(이름 끝에 있는2는 할당의 2를 나타냅니다).
에는 ""가 됩니다.NULL
sentinel의 (sentinel 포인터).ntoks
않은 는)non-NULL
,ntoks
첫 번째 레벨 사이즈가 아닌 반환된 어레이의 길이를 발신자에게 전달합니다).
keepnulls
로 설정되어 있다.true
결과 토큰은 strsep() 함수에서 기대할 수 있는 것과 비슷합니다.대부분 source-string 내의 연속된 딜리미터가 빈 토큰(늘)을 생성하는 것을 의미합니다.delim
는 빈 c-string 이거나 소스 문자열에 포함된 딜리미터-chars 가 발견되지 않았습니다.그 결과, 토큰은 1개뿐입니다.송신원 문자열입니다.strsep()와 달리 빈 토큰은 다음 설정으로 무시할 수 있습니다.keepnulls
로로 합니다.false
.
함수의 실패한 콜은 반환값을 체크함으로써 식별할 수 있습니다.NULL
""의 값을 ntoks
제공된0 의 경우)ntoks
non-NULL
어레이에 에 항상 할 것을 크래시를 할 수 가 포함되어 있기 " a an " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "기능에는 즉시 크래시를 연기할 수 있는 온전성 체크가 포함되어 있기 때문입니다(예를 들어 패스 등).NULL
의
정상적으로 종료하면, 발신자는 어레이를 해방할 수 있습니다.위해서str_toksarray_alloc()
, 단순한 free()로 충분합니다.위해서str_toksarray_alloc2()
두 번째 수준의 할당으로 인해 루프가 발생합니다.NULL
sentinel의 )non-NULL
ntoks
사소한 것으로 만, 저는 을 하고 toksarray_free2()
에 대해과 같이 :)
두 기능을 모두 사용한 간단한 예를 다음에 나타냅니다.
준비:
const char *src = ";b,test,Tèst,;;cd;ελληνικά,nørmälize,;string to";
const char *delim = ";,";
bool keepnulls = true;
size_t ntoks = 0;
str_toksarray_param():
// destructive (use copy of src)
char *scopy = strdup( src );
if (!scopy) { ... }; // handle strdup failure
printf( "%s\n", src );
char **arrtoks = str_toksarray_alloc( &scopy, delim, &ntoks, keepnulls );
printf( "%lu tokens read\n", ntoks );
if ( arrtoks ) {
for (int i=0; arrtoks[i]; i++) {
printf( "%d: %s\n", i, arrtoks[i] );
}
}
free( scopy );
free( arrtoks );
/* OUTPUT
;b,test,Tèst,;;cd;ελληνικά,nørmälize,;string to
11 tokens read
0:
1: b
2: test
3: Tèst
4:
5:
6: cd
7: ελληνικά
8: nørmälize
9:
10: string to
*/
str_toksarray_param2():
// non-destructive
keepnulls = false; // reject empty tokens
printf( "%s\n", src );
arrtoks = str_toksarray_alloc2( src, delim, &ntoks, keepnulls );
printf( "%lu tokens read\n", ntoks );
if ( arrtoks ) {
for (int i=0; arrtoks[i]; i++) {
printf( "%d: %s\n", i, arrtoks[i] );
}
}
toksarray_free2( arrtoks ); // dangling arrtoks
// or: arrtoks = toksarray_free2( arrtoks ); // non-dangling artoks
/* OUTPUT
;b,test,Tèst,;;cd;ελληνικά,nørmälize,;string to
7 tokens read
0: b
1: test
2: Tèst
3: cd
4: ελληνικά
5: nørmälize
6: string to
*/
구현에 관한 주의사항
두 함수 모두 토큰화에 strsep()를 사용하여 스레드 세이프를 실현하지만 표준 함수는 아닙니다.제공하지 않으면 언제든지 오픈 소스 구현을 사용할 수 있습니다(예를 들어 GNU나 Apple 등).에서 사용되는 함수 strdup()도 마찬가지입니다.str_toksarray_alloc2()
(그 실장은 사소한 것이지만, 예를 들면 GNU나 Apple의 실장이 있습니다).
에서 strsep()를 사용하는 경우의 부작용str_toksarray_alloc()
source-string의 시작 포인터가 해석 루프의 모든 단계에서 다음 토큰으로 계속 이동하는 것입니다.즉, 발신자가 개시 주소를 추가 포인터에 보존하지 않는 한, 해석된 문자열을 해방할 수 없습니다.이 기능을 로컬로 수행함으로써 고객의 번거로움을 덜어줍니다.strpSaved
포인터 str_toksarray_alloc2()
소스 문자열에 닿지 않기 때문에 이 영향을 받지 않습니다.
두 기능의 주요 차이점은 다음과 같습니다.str_toksarray_alloc()
는 검출된 토큰에 메모리를 할당하지 않습니다.오히려 어레이 포인터에만 공간을 할당하고 소스 문자열을 직접 가리키도록 설정합니다.이것은 strsep() 때문에 동작합니다. nul
- 을 in-place로 in-place 토큰을 in-place로 종료합니다.이러한 의존성으로 인해 지원 코드가 복잡해질 수 있지만 문자열이 크면 성능도 크게 달라질 수 있습니다.source-string을 유지하는 것이 중요하지 않은 경우 메모리 설치 공간에도 큰 차이가 있을 수 있습니다.
반,는str_toksarray_alloc2()
는 토큰의 동적으로 할당된 복사본을 더 이상 의존하지 않고 자체 유지 배열을 할당하여 반환합니다.첫 번째로 source-string의 로컬 복제에서 배열을 만들고 두 번째로 실제 토큰 내용을 배열에 복제하여 이를 수행합니다.는 이이 this this this and this this this this 、 이 this 。str_toksarray_alloc()
특별한 되어 있지 이것에 의해, 보다 간단하게 서포트 코드를 쓸 수 있게 됩니다(따라서 유지보수가 용이합니다).
두의 또 다른 첫 포인터입니다.ntoks
음소거되어 있습니다.둘 다 사용 가능한 모든 토큰을 해석하지만 접근 방식은 상당히 다릅니다. str_toksarray_alloc()
는 초기 사이즈가 16(char-ahead)인 alloc-ahead를 사용하여 해석 루프에서 필요에 따라 2배 증가시킵니다. str_toksarray_alloc2()
는 사용 가능한 모든 토큰을 카운트하여 첫 번째 패스를 하고 그 많은 문자를 한 번만 할당합니다. 첫 .str_toksfound()
표준 함수 strpbrk() 및 strchr을 사용합니다.그 기능의 소스코드도 아래에 기재하고 있습니다.
프로젝트의 요구에 따라 어떤 접근방식이 더 나은지 결정하는 것은 전적으로 고객님의 몫입니다.각 함수의 코드를 자유롭게 조정하여 접근 및 취득할 수 있습니다.
특히 초기 크기와 성장률이 케이스별로 미세 조정된 경우(예를 들어 기능 파라미터로 설정)에는 평균적으로 매우 큰 문자열의 경우 할당 속도가 훨씬 빠릅니다. 를 그 것들과 하다.strchr()
»strpbrk()
는 거기서 차이를 만들 수 있습니다.하지만, 비교적 작은 문자열이 일반적이기 때문에, 한 무더기의 차자를 미리 할당하는 것은 단지 과잉 살상일 뿐입니다.아프지 않지만 이 경우 정당한 이유 없이 코드가 엉망이 됩니다.어쨌든, 당신에게 가장 적합한 것을 자유롭게 고르세요.
2번입니다.의 라고 할 수 str_toksarray_alloc2()
메모리와 퍼포먼스가 작은 문자열에서 중간 문자열로 문제가 되는 경우는 거의 없기 때문에 대처가 훨씬 간단합니다.큰 에 대해서 다루어야 할 쓰도록 .str_toksarray_alloc()
(단, 이 경우 프로젝트의 요구나 입력 사양에 가까운 특수한 문자열 해석 기능을 롤링해야 합니다).
이런, 그건 2센트보다 조금 더 많은 것 같아요(웃음).
어쨌든, 2개의 기능과 헬퍼의 코드에 대해 설명합니다(이미 거의 모든 것을 커버했기 때문에, 설명 코멘트의 대부분을 삭제했습니다).
소스 코드
str_toksarray_param():
// ----------------------------------------
// Tokenize destructively a nul-terminated source-string.
// Return a dynamically allocated, NULL terminated array of char-pointers
// each pointing to each token found in the source-string, or NULL on error.
//
char **str_toksarray_alloc(char **strp, const char *delim, size_t *ntoks, bool keepnulls)
{
// sanity checks
if ( !strp || !*strp || !**strp || !delim ) {
goto failed;
}
char *strpSaved = *strp; // save initial *strp pointer
bool ntoksOk = (ntoks && *ntoks); // false when ntoks is muted
size_t _ntoks = (ntoksOk ? *ntoks : 16); // # of tokens to alloc-ahead
// alloc array of char-pointers (+1 for NULL sentinel)
char **toksarr = malloc( (_ntoks+1) * sizeof(*toksarr) );
if ( !toksarr ) {
goto failed;
}
// Parse *strp tokens into the array
size_t i = 0; // # of actually parsed tokens
char *tok;
while ( (tok = strsep(strp, delim)) ) {
// if requested, ignore empty tokens
if ( *tok == '\0' && !keepnulls ) {
continue;
}
// non-muted ntoks reached? we are done
if ( ntoksOk && i == _ntoks ) {
*ntoks = i;
break;
}
// muted ntoks & ran out of space? double toksarr and keep parsing
if ( !ntoksOk && i == _ntoks ) {
_ntoks *= 2;
char **tmparr = realloc( toksarr, (_ntoks+1) * sizeof(*tmparr) );
if ( !tmparr ) {
*strp = strpSaved;
free( toksarr );
goto failed;
}
toksarr = tmparr;
}
toksarr[i++] = tok; // get token address
}
toksarr[i] = NULL; // NULL sentinel
*strp = strpSaved; // restore initial *strp pointer
if (ntoks) *ntoks = i; // pass to caller # of parsed tokens
return toksarr;
failed:
if (ntoks) *ntoks = 0;
return NULL;
}
str_toksarray_param2():
// ----------------------------------------
// Tokenize non-destructively a nul-terminated source-string.
// Return a dynamically allocated, NULL terminated array of dynamically
// allocated and nul-terminated string copies of each token found in the
// source-string. Return NULL on error.
// The 2 at the end of the name means 2-levels of allocation.
//
char **str_toksarray_alloc2( const char *str, const char *delim, size_t *ntoks, bool keepnulls )
{
// sanity checks
if ( !str || !*str || !delim ) {
if (ntoks) *ntoks = 0;
return NULL;
}
// make a copy of str to work with
char *_str = strdup( str );
if ( !_str ) {
if (ntoks) *ntoks = 0;
return NULL;
}
// if ntoks is muted we'll allocate str_tokscount() tokens, else *ntoks
size_t _ntoks = (ntoks && *ntoks) ? *ntoks : str_tokscount(_str, delim, keepnulls);
if ( _ntoks == 0 ) { // str_tokscount() failed
goto fail_free_str;
}
// alloc the array of strings (+1 for an extra NULL sentinel)
char **toksarr = malloc( (_ntoks+1) * sizeof(*toksarr) );
if ( !toksarr ) {
goto fail_free_str;
}
// Parse str tokens and duplicate them into the array
size_t i = 0; // # of actually parsed tokens
char *tok;
while ( i < _ntoks && (tok = strsep(&_str, delim)) ) {
// if requested, skip empty tokens
if ( *tok == '\0' && !keepnulls ) {
continue;
}
// duplicate current token into the array
char *tmptok = strdup( tok );
if ( !tmptok ) {
goto fail_free_arr;
}
toksarr[i++] = tmptok;
}
toksarr[i] = NULL; // NULL sentinel
free( _str ); // release the local copy of the source-string
if (ntoks) *ntoks = i; // pass to caller the # of parsed tokens
return toksarr;
// cleanup before failing
fail_free_arr:
for (size_t idx=0; idx < i; idx++) {
free( toksarr[idx] );
}
free( toksarr );
fail_free_str:
free( _str );
if (ntoks) *ntoks = 0;
return NULL;
}
str_tokscount() - 도우미 함수. str_toksar_slots2()에서 사용됩니다.
// ----------------------------------------
// Return the count of tokens present in a nul-terminated source-string (str),
// based on the delimiting chars contained in a 2nd nul-terminated string (delim).
// If the boolean argument is false, empty tokens are excluded.
//
// To stay consistent with the behavior of strsep(), the function returns 1 if
// delim is an empty string or none of its delimiters is found in str (in those
// cases the source-string is considered a single token).
// 0 is returned when str or delim are passed as NULL pointers, or when str is
// passed as an empty string.
//
size_t str_tokscount( const char *str, const char *delim, bool keepnulls )
{
// sanity checks
if ( !str || !*str || !delim ) {
return 0;
}
const char *tok = str;
size_t nnulls = strchr(delim, *str) ? 1 : 0;
size_t ntoks = 1; // even when no delims in str, str counts as 1 token
for (; (str = strpbrk(tok, delim)); ntoks++ ) {
tok = ++str;
if ( strchr(delim, *str) ) {
nnulls++;
}
}
return keepnulls ? ntoks : (ntoks - nnulls);
}
toksarray_free2() - str_toksar_free2()에 의해 반환된 배열에서 사용합니다.
// ----------------------------------------
// Free a dynamically allocated, NULL terminated, array of char-pointers
// with each such pointer pointing to its own dynamically allocated data.
// Return NULL, so the caller has the choice of assigning it back to the
// dangling pointer. The 2 at the end of the name means 2-levels of deallocation.
//
// NULL terminated array means ending with a NULL sentinel.
// e.g.: toksarr[0] = tok1, ..., toksarr[len] = NULL
//
char **toksarray_free2( char **toksarr )
{
if ( toksarr ) {
char **toks = toksarr;
while ( *toks ) { // walk until NULL sentinel
free( *toks++ );
}
free( toksarr );
}
return NULL;
}
다음 방법은 모든 작업(메모리 할당, 길이 카운트)을 수행합니다.자세한 내용과 설명은 여기를 참조하십시오.C 문자열을 분할하기 위한 Java String.split() 메서드의 구현
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;
}
사용방법:
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;
}
외부 도서관을 이용하실 의향이 있으시다면 아무리 추천드려도 부족할 것 같습니다.약간의 추가 설정이 필요하지만 장기적으로 사용하기 쉽습니다.
를 들어, 아래 '아까', '아까', '아까', '아까'가 만들어집니다.bstring
bfromcstr()
)bstring
차(버)다음 쉼표로 를 '따로 나누다'로합니다.struct bstrList
" " " 가 qty
및 andentry
의 배열입니다.bstring
s.
bstrlib
에는 그 할 수 있는 이 많이 .bstring
s
식은 죽 먹기야...
#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], '_'));
}
}
#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;
}
위의 예에서는 (필요한 대로) 문자열 내에 있는 늘 종단 문자열 배열을 반환하는 방법이 있습니다.단, 리터럴 문자열은 함수에 의해 수정되어야 하므로 전달되지 않습니다.
#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;
}
더 깔끔한 방법이 있을 수 있지만, 이해하실 수 있을 겁니다.
내 버전:
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;
}
이 최적화된 메서드는 *결과에 포인터의 배열을 만들고(또는 기존 포인터를 업데이트하고) *카운트 내의 요소 수를 반환합니다.
예상되는 최대 문자열 수를 나타내려면 "max"를 사용하고(기존 어레이 또는 기타 모든 reaseon을 지정하는 경우), 그렇지 않으면 0으로 설정합니다.
딜리미터 목록과 비교하려면 딜리미터를 문자*로 정의하고 행을 바꿉니다.
if (str[i]==delim) {
다음 두 줄과 함께 합니다.
char *c=delim; while(*c && *c!=str[i]) c++;
if (*c) {
즐거운 시간 되세요.
#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;
}
사용 예:
#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;
}
분해 및 삽입 - 초기 문자열은 그대로 유지되며 동적 메모리 할당
#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;
}
사용방법:
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;
}
나는 아주 간단한 것을 만들려고 했다.메인()에도 예를 제시하겠습니다.
#include <stdio.h>
#include <string.h>
void split(char* inputArr, char** outputArr, char* delim) {
char *temp;
temp = strtok(inputArr, delim);
for(int i = 0; temp != NULL; i++) {
outputArr[i] = temp;
temp = strtok(NULL, " ");
}
}
int main(int argc, char **argv){
/* check for proper arguments */
if(argc != 2){
printf("One Argument Expected\n");
} else {
printf("\n");
/*---------main code starts here----------*/
FILE * myScriptFile;
myScriptFile = fopen(argv[1], "r");
/* read txt file and split into array like java split() */
int bufferLen = 100;
char buffer[bufferLen];
char *splitArr[100];
while(fgets(buffer, bufferLen, myScriptFile) != NULL){
split(buffer, splitArr, " ");
printf("Index 0 String: %s\n", splitArr[0]);
printf("Index 1 String: %s\n", splitArr[1]);
printf("Index 2 String: %s\n", splitArr[2]);
printf("Index 3 String: %s\n", splitArr[3]);
}
fclose(myScriptFile);
}
printf("\nProgram-Script Ended\n");
return 0;
}
.txt 파일에는
Hello this is test
Hello2 this is test2
파라미터로서 .txt 파일을 사용하여 실행하면
Index 0 String: Hello
Index 1 String: this
Index 2 String: is
Index 3 String: test
Index 0 String: Hello2
Index 1 String: this
Index 2 String: is
Index 3 String: test2
이는 다중 문자 구분 기호를 처리할 수 있는 문자열 분할 함수입니다. " " " " " " " " 입니다.buffer
★★★★★★★★★★★★★★★★★」stringLengths
로 설정됩니다.(void *) 0
, , , , 입니다.numStrings
로 설정됩니다.0
.
이 알고리즘은 이미 테스트되어 기능하고 있습니다.(이해:ASCII 이외의 문자열에 대해서는 테스트되지 않았으며, 발신자가 유효한 파라미터를 제공했다고 가정합니다).
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;
}
샘플 코드:
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]);
}
}
라이브러리:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <cstring>
#include <cstdio>
int main()
{
char buf[] = "This is Luke Skywalker here!";
for( char* tok = strtok( buf, " ");
tok != nullptr;
tok = strtok( nullptr, " ")) {
puts( tok);
}
}
출력
This
is
Luke
Skywalker
here!
이 함수는 char* 문자열을 디미네이터로 분할합니다.연속적으로 여러 개의 디리미네이터가 존재할 수 있습니다.이 함수는 원래 문자열을 변경합니다.원본 문자열의 복사본을 먼저 만들어야 원본 문자열이 변경되지 않습니다.이 함수는 cstring 함수 호출을 사용하지 않기 때문에 다른 함수에 비해 조금 빠를 수 있습니다.메모리 할당에 관심이 없는 경우, sub_strings를 size strlen(src_str)/2로 함수의 맨 위에 할당할 수 있습니다.또한 (c++"버전"과 같이) 함수의 맨 아래 절반을 건너뜁니다.이렇게 하면 기능이 O(N)로 감소하지만, 아래와 같은 메모리 최적화 방법은 O(2N)입니다.
기능:
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);
}
사용방법:
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);
내 접근법은 문자열을 스캔하여 포인터가 삭제자(및 첫 번째 문자) 뒤의 모든 문자를 가리키도록 하고 동시에 문자열 내의 삭제자의 모양을 '\0'에 할당하는 것입니다.
먼저 원본 문자열의 복사본을 만든 다음(일정한 문자열이므로), 스캔하여 분할 수를 포인터 매개 변수 len으로 전달합니다.그 후 첫 번째 결과 포인터를 복사 문자열 포인터로 포인트하고 복사 문자열을 스캔합니다.삭제자를 만나면 이전 결과 문자열이 종료되도록 "\0"에 할당하고 다음 결과 문자열 포인터를 다음 문자 포인터로 포인트합니다.
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;
}
는 저의 ★★★★★★★★★★★★★★★★★★.strtok()
zString 라이브러리에서 구현합니다. zstring_strtok()
도서관과 strtok()
연속된 구분 기호를 처리하는 방식입니다.
아래 코드를 보시면 동작에 대해 알 수 있을 것입니다(가능한 한 많은 코멘트를 사용하려고 했습니다).
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;
}
다음으로 사용 예를 나타냅니다.
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)
라이브러리는 Github https://github.com/fnoyanisi/zString 에서 다운로드할 수 있습니다.
이거 써봐.
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;
}
내 코드(테스트 완료):
#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);
}
결과:
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
이 질문에 관한 두 가지 문제는 메모리 관리와 스레드 안전성입니다.많은 투고에서 알 수 있듯이, 이것은 C에서 매끄럽게 이루어지기 쉬운 작업이 아닙니다.다음과 같은 솔루션을 원했습니다.
- 스레드 세이프(스트록은 스레드 세이프가 아닙니다)
- malloc 또는 그 파생 모델을 채용하지 않음(메모리 관리 문제 회피)
- 개별 필드의 어레이 경계를 확인합니다(알 수 없는 데이터의 세그먼트 장애를 방지하기 위해).
- 멀티바이트 필드 구분 기호(utf-8)와 함께 작동
- 입력의 추가 필드를 무시합니다.
- 유효하지 않은 필드 길이에 대한 소프트 오류 루틴을 제공합니다.
제가 생각해낸 솔루션은 이 모든 기준을 충족합니다.여기에 게재되어 있는 다른 솔루션보다 셋업 작업이 조금 더 많을 수 있지만, 실제로는 다른 솔루션의 일반적인 함정을 피하기 위해 추가 작업을 할 가치가 있다고 생각합니다.
#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);
}
다음으로 컴파일 및 출력 예를 나타냅니다.이 예에서는 소프트 에러가 어떻게 동작하는지를 확인할 수 있도록, 「APRIL」이라고 일부러 표기하고 있습니다.
$ 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
맛있게 드세요!
여기에 할당된 포인터를 char로 반환하는 질문에서 요청된 프로토타입과 일치하는 문자열 리터럴을 토큰화하기 위해 안전하게 작동하는 또 다른 구현이 있습니다(예: char).char **
딜리미터 문자열에는 여러 문자를 포함할 수 있으며 입력 문자열에는 토큰 수를 지정할 수 있습니다. 및 all음 、 음음 、 음음 、 。malloc
★★★★★★★★★★★★★★★★★」realloc
POSIX »strdup
.
는, 「포인터」에됩니다.NPTRS
0으로 하다char **
반환되는 것은 초병 포함 NULL
한 경우*argv[]
있습니다.execv
,execvp
★★★★★★★★★★★★★★★★★」execve
.
★★★★★★★와 같이strtok()
딜리미터로서 에, 「」는 「1개의 딜리미터로서 취급됩니다."JAN,FEB,MAR,APR,MAY,,,JUN,JUL,AUG,SEP,OCT,NOV,DEC"
으로 해석됩니다.','
"MAY,JUN"
.
으로 코멘트를 코멘트를 합니다.main()
이치노는 할된음음음음음음음음음음음음음음음음음음음 the the the the the the로 되었습니다.2
입력 스트링 토큰화 중에 강제로3개의 재할당을 실시하려면 , 다음의 순서를 실행합니다.
#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 */
}
}
사용/출력 예시
$ ./bin/splitinput
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
DEC
더 궁금하신 점이 있으시면 알려주세요.
간단한 해결책을 찾다가 이걸 발견했어요.모든 옵션에 매료되어 있습니다만, 자신의 사용 예나 취향에 만족하지 않습니다(매우 나쁠지도 모릅니다).
메모리를 재할당하지 않고, 사람이 읽을 수 있는 + 코멘트를 사용하는 것을 목표로 하는, 다소 독특한 솔루션을 개발했습니다.
다음 URL에서 gist.github에 업로드되었습니다.
예:
#include "./strutils.c"
struct str_split_info info;
info.source = " SPLIT ME hello SPLIT ME world SPLIT ME whats SPLIT ME going SPLIT ME on SPLIT ME today";
info.delimiter = " SPLIT ME ";
str_split_begin(&info);
char * substr;
for (int i=0; i<info.splitStringsCount; i++) {
substr = info.splitStrings[i];
printf("substring: '%s'\n", substr);
}
str_split_end(&info);
출력:
$ ./test
substring: ''
substring: 'hello'
substring: 'world'
substring: 'whats'
substring: 'going'
substring: 'on'
substring: 'today'
스트럿의 모든 소스.c
#ifndef STRUTILS_C
#define STRUTILS_C 1
#ifndef str
#define str char *
#endif
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
struct str_split_info {
/* The string to be split
* Provided by caller of str_split_begin function
*/
str source;
/* The string that cuts the source string, all occurances of
* this string will be removed from the source string
* Provided by caller of str_split_begin function
*/
str delimiter;
/* Array of strings split by delimiter
* Provided and allocated by str_split_begin function
* Must be garbage collected by str_split_end function
*/
str * splitStrings;
/* Array of string lengths split by delimiter
* Provided and allocated by str_split_begin function
* Must be garbage collected by str_split_end function
*/
int * splitStringsLengths;
/* Number of strings split by delimiter contained in splitStrings
* Provided by str_split_begin function
*/
int splitStringsCount;
};
#define str_split_infop struct str_split_info *
/* Split a string by a delimiting string
*
* The caller is responsible only for calling str_split_end
* when finished with the results in 'info'
*/
void str_split_begin (str_split_infop info) {
info->splitStringsCount = 0;
int sourceLength = strlen(info->source);
int sourceOffset = 0;
char sourceChar;
int delimiterLength = strlen(info->delimiter);
int delimiterOffset = 0;
char delimiterChar;
//first pass, simply count occurances so we can allocate only once
for (sourceOffset = 0; sourceOffset<sourceLength; sourceOffset++) {
sourceChar = info->source[sourceOffset];
delimiterChar = info->delimiter[delimiterOffset];
if (sourceChar == delimiterChar) {
delimiterOffset++;
if (delimiterOffset >= delimiterLength) {
delimiterOffset = 0;
//increment count
info->splitStringsCount ++;
}
} else {
delimiterOffset = 0;
}
}
info->splitStringsCount++;
//allocate arrays since we know the count
//this one is an array of strings, which are each char arrays
info->splitStrings = (str *) malloc(sizeof (str *) * info->splitStringsCount);
//this one is an array of ints
info->splitStringsLengths = (int*) malloc(sizeof(int) *info->splitStringsCount);
int stringBegin = 0;
int stringEnd = 0;
int splitIndex = 0;
int splitLength = 0;
//second pass, fill the arrays
for (sourceOffset = 0; sourceOffset<sourceLength; sourceOffset++) {
sourceChar = info->source[sourceOffset];
delimiterChar = info->delimiter[delimiterOffset];
if (sourceChar == delimiterChar) {
delimiterOffset++;
//if we've reached the end of the delimiter
if (delimiterOffset >= delimiterLength) {
//don't worry about delimiter trailing null, strlen doesn't count those
stringEnd = sourceOffset - delimiterLength;
//char count of substring we want to split
splitLength = stringEnd - stringBegin + 1;
//allocate for our substring split
info->splitStrings[splitIndex] = (str) malloc(
//+1 for trailing null for c-string
sizeof(char) * splitLength + 1
);
//copy substring from source into splitStrings array
memcpy(
info->splitStrings[splitIndex],
info->source + stringBegin,
splitLength
);
//explicitly set the last char of this split to a NULL just for fun
info->splitStrings[splitIndex][splitLength] = 0x00;
//conveniently put the substring split size for the
//user of str_split_begin :)
info->splitStringsLengths[splitIndex] = splitLength;
//move to next split index
splitIndex ++;
//reset delimiter offset so we look for new occurances of it
delimiterOffset = 0;
//next substring split should occur after the current delimiter
stringBegin = sourceOffset+1;
}
} else {
//reset delimiter offset so we look for new occurances of it
delimiterOffset = 0;
}
}
//handle edge case of last substring after last delimiter
if (stringEnd != stringBegin) {
stringEnd = sourceLength-1;
splitLength = stringEnd - stringBegin + 1;
//allocate for our substring split
info->splitStrings[splitIndex] = (str) malloc(
//+1 for trailing null for c-string
sizeof(char) * splitLength + 1
);
//copy substring from source into splitStrings array
memcpy(
info->splitStrings[splitIndex],
info->source + stringBegin,
splitLength
);
}
}
int str_split_count (str_split_infop info) {
return info->splitStringsCount;
}
void str_split_get (str_split_infop info, str * out) {
for (int i=0; i < info->splitStringsCount; i++) {
strcpy(out[i], info->splitStrings[i]);
}
}
void str_split_end (str_split_infop info) {
if (info->splitStringsCount > 0 && info->splitStrings != NULL) {
//free each string allocated
for (int i=0; i < info->splitStringsCount; i++) {
free(info->splitStrings[i]);
}
//free string array pointer
free (info->splitStrings);
//free string lengths array pointer
free(info->splitStringsLengths);
info->splitStringsCount = 0;
}
}
void str_split_test () {
char * source = "hello world this is a test";
str delimiter = " ";
struct str_split_info info;
info.source = source;
info.delimiter = delimiter;
str_split_begin (&info);
//iterate thru split substrings
//NOTE: removed/memory cleanup after str_split_end
for (int i=0; i<info.splitStringsCount; i++) {
// info.splitStrings[i];
}
str_split_end(&info);
}
#endif
언급URL : https://stackoverflow.com/questions/9210528/split-string-with-delimiters-in-c
'programing' 카테고리의 다른 글
구성 요소에서 Vuex 모듈 작업에 액세스하는 방법 (0) | 2022.07.11 |
---|---|
Vue.js 앱은 http://localhost:8080에서 실행되며, 백엔드 API에 악시를 사용하여 GET 요청을 할 때마다 로컬 호스트가 URL 앞에 추가됩니다. (0) | 2022.07.11 |
vuex에 중요한 정보 저장 (0) | 2022.07.11 |
fork() 브랜치가 예상보다 많습니까? (0) | 2022.07.11 |
부팅 시 지정된 브라우저 URL로 리디렉션 (0) | 2022.07.11 |