[英]Split a string based on contiguous delimiters
我希望根据特定的字符序列来拆分字符串,但前提是它们必须顺序排列。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int i = 0;
char **split;
char *tmp;
split = malloc(20 * sizeof(char *));
tmp = malloc(20 * 12 * sizeof(char));
for(i=0;i<20;i++)
{
split[i] = &tmp[12*i];
}
char *line;
line = malloc(50 * sizeof(char));
strcpy(line, "Test - Number -> <10.0>");
printf("%s\n", line);
i = 0;
while( (split[i] = strsep(&line, " ->")) != NULL)
{
printf("%s\n", split[i]);
i++;
}
}
这将打印出:
Test
Number
<10.0
但是我只想在->周围拆分,以便可以提供输出:
Test - Number
<10.0>
我认为,使用有序的分隔符序列进行拆分的最好方法是使用strstr
复制strtok_r
行为,如下所示:
#include <stdio.h>
#include <string.h>
char *substrtok_r(char *str, const char *substrdelim, char **saveptr)
{
char *haystack;
if(str)
haystack = str;
else
haystack = *saveptr;
char *found = strstr(haystack, substrdelim);
if(found == NULL)
{
*saveptr = haystack + strlen(haystack);
return *haystack ? haystack : NULL;
}
*found = 0;
*saveptr = found + strlen(substrdelim);
return haystack;
}
int main(void)
{
char line[] = "a -> b -> c -> d; Test - Number -> <10.0> ->No->split->here";
char *input = line;
char *token;
char *save;
while(token = substrtok_r(input, " ->", &save))
{
input = NULL;
printf("token: '%s'\n", token);
}
return 0;
}
行为类似于strtok_r
但仅在找到子字符串时才拆分。 输出为:
$ ./a
token: 'a'
token: ' b'
token: ' c'
token: ' d; Test - Number'
token: ' <10.0>'
token: 'No->split->here'
与strtok
和strtok_r
,它要求源字符串是可修改的,因为它会写入'\\0'
终止字节以创建和返回令牌。
编辑
嗨,您介意解释为什么
'*found = 0'
意味着返回值只是分隔符之间的字符串。 我真的不明白这里发生了什么或为什么它起作用。 谢谢
您首先要了解的是字符串在C中的工作方式。字符串只是一个字节序列(字符),以'\\0'
结尾。 我用括号写了字节和字符,因为C中的字符只是一个1字节的值(在大多数系统上一个字节是8位长),代表字符的整数值是ASSCI代码表中定义的值 ,即7位长值。 从表中可以看到,值97代表字符'a'
,值98代表'b'
,依此类推。
char x = 'a';
和做的一样
char x = 97;
值0是字符串的特殊值,称为NUL
(空字符)或'\\0'
终止字节。 此值用于告诉函数字符串在何处结束。 像strlen
这样的函数可以返回字符串的长度,它通过计算遇到的字节数直到遇到值为0的字节来完成。
这就是为什么字符串使用存储char
数组,因为一个指针数组给予其中的序列的存储器块的开始char
s的存储。
让我们看一下:
char string[] = { 'H', 'e', 'l', 'l', 'o', 0, 48, 49, 50, 0 };
该数组的内存布局为
0 1 2 3 4 5 6 7 8 9
+-----+-----+-----+-----+-----+----+-----+-----+-----+----+
| 'H' | 'e' | 'l' | 'l' | 'o' | \0 | '0' | '1' | '2' | \0 |
+-----+-----+-----+-----+-----+----+-----+-----+-----+----+
或者更精确地使用整数值
0 1 2 3 4 5 6 7 8 9 10
+----+-----+-----+-----+-----+---+----+----+----+---+
| 72 | 101 | 108 | 108 | 111 | 0 | 48 | 49 | 50 | 0 |
+----+-----+-----+-----+-----+---+----+----+----+---+
注意,值0表示'\\0'
,48表示'0'
,49表示'1'
,50表示'2'
。 如果你这样做
printf("%lu\n", strlen(string));
输出将为strlen
将在第5个位置找到值0并停止计数,但是string
存储了两个字符串,因为从第6个位置开始,新的字符序列也以0结尾,因此使其成为第二个字符数组中的有效字符串。 要访问它,您将需要使指针指向第一个0值之后。
printf("1. %s\n", string);
printf("2. %s\n", string + strlen(string) + 1);
输出将是
Hello
012
此属性用于strtok
(及上面的mine)等函数中,可从较大的字符串返回子字符串,而无需创建副本(这将创建一个新数组,动态分配内存,使用strcpy
创建副本) 。
假设您有以下字符串:
char line[] = "This is a sentence;This is another one";
在这里,您只有一个字符串,因为终止字符'\\0'
'e'
在字符串的最后一个'e'
之后。 但是,如果我这样做:
line[18] = 0; // same as line[18] = '\0';
然后我在同一数组中创建了两个字符串:
"This is a sentence\0This is another one"
因为我替换了分号';'
与'\\0'
,从而从位置0到18创建一个新字符串,并从位置19到38创建第二个字符串。
printf("string: %s\n", line);
输出将是
string: This is a sentence
现在让我们来看一下函数本身:
char *substrtok_r(char *str, const char *substrdelim, char **saveptr);
第一个参数是源字符串,第二个参数是定界符字符串,第三个参数是char
doule指针。 您必须将指针传递给char
指针。 这将用于记住该功能应在哪里继续扫描,以后再继续。
这是算法:
if str is not NULL:
start a new scan sequence from str
otherwise
resume scanning from string pointed to by *saveptr
found position of substring_d pointed to by 'substrdelim'
if no such substring_d is found
if the current character of the scanned text is \0
no more substrings to return --> return NULL
otherwise
return the scanned text and set *saveptr to
point to the \0 character of the scanned text,
so that the next iteration ends the scanning
by returning NULL
otherwise (a substring_d was found)
create a new substring_a until the found one
by setting the first character of the found
substring_d to 0.
update *saveptr to the start of the found substring_d
plus it's previous length so that *saveptr
points to the past the delimiter sequence found in substring_d.
return new created substring_a
第一部分很容易理解:
if(str)
haystack = str;
else
haystack = *saveptr;
在这里,如果str
不为NULL
, str
开始一个新的扫描序列。 这就是为什么在main
中将input
指针设置为指向line
保存的字符串的开头的原因。 其他所有迭代都必须使用str == NULL
调用,这就是为什么while
循环中要做的第一件事是将input = NULL;
设置input = NULL;
因此substrtok_r
使用*saveptr
恢复扫描。 这是strtok
的标准行为。
下一步是查找定界子字符串:
char *found = strstr(haystack, substrdelim);
下一部分处理未找到定界子字符串2的情况 :
if(found == NULL)
{
*saveptr = haystack + strlen(haystack);
return *haystack ? haystack : NULL;
}
*saveptr
已更新为指向整个源,因此它指向'\\0'
终止字节。 返回行可以改写为
if(*haystack == '\0')
return NULL
else
return haystack;
这说明如果源已经是一个empy字符串1 ,则返回NULL
。 这意味着找不到更多子字符串,结束调用该函数。 这也是strtok
标准行为。
最后一部分
*found = 0;
*saveptr = found + strlen(substrdelim);
return haystack;
当找到定界子字符串时,is处理。 这里
*found = 0;
基本上在做
found[0] = '\0';
如上所述创建子字符串。 为了清楚起见,在此之前
之前
*found = 0;
*saveptr = found + strlen(substrdelim);
return haystack;
内存看起来像这样:
+-----+-----+-----+-----+-----+-----+
| 'a' | ' ' | '-' | '>' | ' ' | 'b' | ...
+-----+-----+-----+-----+-----+-----+
^ ^
| |
haystack found
*saveptr
后
*found = 0;
*saveptr = found + strlen(substrdelim);
内存看起来像这样:
+-----+------+-----+-----+-----+-----+
| 'a' | '\0' | '-' | '>' | ' ' | 'b' | ...
+-----+------+-----+-----+-----+-----+
^ ^ ^
| | |
haystack found *saveptr
because strlen(substrdelim)
is 3
记住,如果我做printf("%s\\n", haystack);
此时,由于found中的'-'
已设置为0,因此它将打印a
。 *found = 0
像上面说明的那样从一个创建了两个字符串。 strtok
(和我基于strtok
函数)使用相同的技术。 所以当函数执行
return haystack;
token
的第一个字符串将是拆分之前的令牌。 最终substrtok_r
返回NULL
并存在循环,因为在无法创建更多拆分时, substrtok_r
返回NULL
,就像strtok
一样。
脚注
1空字符串是第一个字符已经是'\\0'
终止字节的字符串。
2这是非常重要的部分。 C库中的大多数标准函数(例如strstr
都不会在内存中返回新的字符串,也不会创建副本并返回副本(除非文档中如此说明)。 会返回一个指向原始对象的指针,外加一个偏移量。
成功执行后, strstr
将返回一个指向子字符串开头的指针,该指针将位于源字符串的偏移量处。
const char *txt = "abcdef";
char *p = strstr(txt, "cd");
在这里, strstr
将返回一个指针,该指针指向"abcdef"
中的子字符串"cd"
的开头。 要获取偏移量,请执行p - txt
,该操作返回有多少字节的appart
b = base address where txt is pointing to
b b+1 b+2 b+3 b+4 b+5 b+6
+-----+-----+-----+-----+-----+-----+------+
| 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | '\0' |
+-----+-----+-----+-----+-----+-----+------+
^ ^
| |
txt p
因此txt
指向地址b
, p
指向地址b+2
。 这就是为什么通过执行p-txt
得到偏移量的原因,这将是(b+2) - b => 2
。 因此, p
指向原始地址加上2个字节的偏移量。 由于这种行为, *found = 0;
首先工作。
请注意,执行txt + 2
将返回一个新指针,该指针指向txt
指向的位置加上偏移量2。这称为指针算术。 就像常规算法一样,但是这里的编译器会考虑对象的大小。 char
是定义为大小为1的类型,因此sizeof(char)
返回1。但是,假设您有一个整数数组:
int arr[] = { 7, 2, 1, 5 };
在我的系统上,一个int
大小为4,因此一个int
对象需要4个字节的内存。 该数组在内存中看起来像这样:
b = base address where arr is stored
address base base + 4 base + 8 base + 12
in bytes +-----------+-----------+-----------+-----------+
| 7 | 2 | 1 | 5 |
+-----------+-----------+-----------+-----------+
pointer arr arr + 1 arr + 2 arr + 3
arithmetic
在这里, arr + 1
返回一个指向arr
的存储位置的指针,外加4个字节的偏移量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.