基本拆分——识别字符串
在comment 中,我建议:
使用strstr() 定位开始和结束标记的出现。然后使用memmove()(或memcpy())复制部分字符串。请注意,由于您的开始和结束标记在原始字符串中是相邻的,因此您不能简单地在其中插入额外的字符——这也是您不能使用strtok() 的原因。因此,您必须复制原始字符串。
strtok() 的另一个问题是它会查找任何一个分隔符——它不会按顺序查找字符。但是strtok() 修改了它的输入字符串,改变了它找到的分隔符,这显然不是你需要的。通常,IMO,strtok() 只是令人头疼的根源,很少能解决问题。如果您必须使用 strtok() 之类的东西,请使用 POSIX strtok_r() 或 Microsoft 的 strtok_s()。微软的功能与strtok_r()基本相同,只是函数名的拼写不同。 (strtok_s() 的标准 C 附件 K 版本与 POSIX 和 Microsoft 不同——请参阅Do you use the TR 24731 'safe' functions?)
在另一个comment,我注意到:
再次使用strstr(),从开始部分的结束位置开始,查找下一个结束标记。然后,知道整个部分的开头,结尾的开头和结尾的长度,您可以安排将正确数量的字符精确地复制到新字符串中,然后在合适的情况下使用 null 终止,或者以逗号终止.比如:
if ((start = strstr(source, "start")) != 0 && ((end = strstr(start, "end")) != 0)
那么数据在源字符串中的start 和end + 2(含)之间。从'end'结束后的字符开始重复。
然后你said:
我尝试了以下代码,但它不能正常工作;你能告诉我它有什么问题吗?
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char string[] = "This-one.testthis-two.testthis-three.testthis-two.test";
int counter = 0;
while (counter < 4)
{
char *result1 = strstr(string, "This");
int start = result1 - string;
char *result = strstr(string, "test");
int end = result - string;
end += 4;
printf("\n%s\n", result);
memmove(result, result1, end += 4);
counter++;
}
}
我observed:
主要问题似乎是用大写 T 搜索 This,但字符串只包含一个大写 T。您还应该查看 Is there a way to specify how many characters of a string to print out using printf()?
即使假设您修复了 This 与 this 故障,还有其他问题。
- 您打印整个字符串。
- 您无需更改搜索的起点。
- 您的移动代码再次将 4 添加到
end。
- 你不要使用
start。
- 代码应从
result1 打印,而不是result。
这些修复后,代码运行但产生:
testthis-two.testthis-three.testthis-two.test
testtestthis-three.testthis-two.test
testtthis-two.test
test?
和核心转储(分段错误)。
识别字符串的代码
这是我根据您的代码和我的评论创建的:
#include <stdio.h>
#include <string.h>
int main(void)
{
char string[] = "this-one.testthis-two.testthis-three.testthis-two.test";
int counter = 0;
const char *b_token = "this";
const char *e_token = "test";
int e_len = strlen(e_token);
char *buffer = string;
char *b_mark;
char *e_mark;
while ((b_mark = strstr(buffer, b_token)) != 0 &&
(e_mark = strstr(b_mark, e_token)) != 0)
{
int length = e_mark + e_len - b_mark;
printf("%d: %.*s\n", ++counter, length, b_mark);
buffer = e_mark + e_len;
}
return 0;
}
显然,此代码不会移动数据,但能够隔离要移动的数据是完成该部分练习的关键第一步。扩展它以复制字符串以便可以比较它们是相当容易的。如果您可以使用,strndup() 函数将很有用:
char *strndup(const char *s1, size_t n);
strndup() 函数最多从字符串 s1 复制 n 字符,始终以 NUL 终止复制的字符串。
如果你没有它,它的实现非常简单,但如果你有 strnlen() 可用,它就更简单了:
size_t strnlen(const char *s, size_t maxlen);
strnlen() 函数尝试计算
s 的长度,但从不扫描超过 s 的第一个 maxlen 字节。
这些都不是标准 C 库函数,但它们被定义为 POSIX 的一部分 (strnlen()
和strndup()) 并且在 BSD 和 Mac OS X 上可用; Linux hasthem,可能还有其他版本的 Unix。显示的规范是从 Mac OS X 手册页中引用的。
示例输出:
我调用了程序stst(用于启动-停止)。
$ ./stst
1: this-one.test
2: this-two.test
3: this-three.test
4: this-two.test
$
有多个特征需要观察:
- 由于
main() 忽略它的参数,我删除了参数(我的默认编译器选项不允许未使用的参数)。
- 我已对字符串进行大小写更正。
- 我为开始和结束标记设置了常量字符串
b_token 和e_token。名称是故意对称的。例如,这可以很容易地移植到一个函数中,其中标记是函数的参数。
- 同样,我为开始和结束标记的位置创建了
b_mark 和 e_mark 变量。
- 名称
buffer 是指向从何处开始搜索的指针。
- 循环使用我在 cmets 中概述的测试,并适应所选名称。
- 打印代码确定找到的字符串的长度并仅打印该数据。它打印计数器值。
- 重新初始化代码会跳过所有以前打印的材料。
通用的命令行选项
您可以通过接受命令行参数并依次处理每个参数(如果有)来概括代码;当没有提供字符串时,您将使用您提供的字符串作为默认值。下一个级别将允许您指定以下内容:
./stst -b beg -e end 'kalamazoo-beg-waffles-end-tripe-beg-for-mercy-end-of-the-road'
你会得到如下输出:
1: beg-waffles-end
2: beg-for-mercy-end
这是使用 POSIX getopt() 实现的代码。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv)
{
char string[] = "this-one.testthis-two.testthis-three.testthis-two.test";
const char *b_token = "this";
const char *e_token = "test";
int opt;
int b_len;
int e_len;
while ((opt = getopt(argc, argv, "b:e:")) != -1)
{
switch (opt)
{
case 'b':
b_token = optarg;
break;
case 'e':
e_token = optarg;
break;
default:
fprintf(stderr, "Usage: %s [-b begin][-e end] ['beginning-to-end...' ...]\n", argv[0]);
return 1;
}
}
/* Use string if no argument supplied */
if (optind == argc)
{
argv[argc-1] = string;
optind = argc - 1;
}
b_len = strlen(b_token);
e_len = strlen(e_token);
printf("Begin: (%d) [%s]\n", b_len, b_token);
printf("End: (%d) [%s]\n", e_len, e_token);
for (int i = optind; i < argc; i++)
{
char *buffer = argv[i];
int counter = 0;
char *b_mark;
char *e_mark;
printf("Analyzing: [%s]\n", buffer);
while ((b_mark = strstr(buffer, b_token)) != 0 &&
(e_mark = strstr(b_mark + b_len, e_token)) != 0)
{
int length = e_mark + e_len - b_mark;
printf("%d: %.*s\n", ++counter, length, b_mark);
buffer = e_mark + e_len;
}
}
return 0;
}
注意这个程序如何记录它正在做什么,打印出控制信息。这在调试过程中可能非常重要——它有助于确保程序正在处理您期望它正在处理的数据。搜索也更好;它可以正确使用与开始和结束标记相同的字符串(或者结束标记是开始标记的一部分),而以前的版本没有(因为这个版本使用b_len,b_token 的长度,在第二个strstr() 电话中)。两个版本都对相邻的结束标记和开始标记非常满意,但它们同样乐于在结束标记和下一个开始标记之间跳过材料。
示例运行:
$ ./stst -b beg -e end 'kalamazoo-beg-waffles-end-tripe-beg-for-mercy-end-of-the-road'
Begin: (3) [beg]
End: (3) [end]
Analyzing: [kalamazoo-beg-waffles-end-tripe-beg-for-mercy-end-of-the-road]
1: beg-waffles-end
2: beg-for-mercy-end
$ ./stst -b th -e th
Begin: (2) [th]
End: (2) [th]
Analyzing: [this-one.testthis-two.testthis-three.testthis-two.test]
1: this-one.testth
2: this-th
$ ./stst -b th -e te
Begin: (2) [th]
End: (2) [te]
Analyzing: [this-one.testthis-two.testthis-three.testthis-two.test]
1: this-one.te
2: this-two.te
3: this-three.te
4: this-two.te
$
更新问题后
您必须通过为length + 1 字节分配足够的空间来解决尾随空字节的问题。使用strncpy() 很好,但在这种情况下可以保证字符串不是以空结尾的;你必须 null 终止它。
您的重复消除代码(被注释掉)并不是特别好——太多的空检查,而没有必要。我创建了一个打印功能; tag 参数允许它识别它正在打印的数据集。我应该将“免费”循环放入一个函数中。重复消除代码可以(应该)在一个函数中;字符串提取代码可以(应该)在一个函数中——如pikkewyn的answer。我扩展了测试数据(字符串连接在这样的上下文中很棒)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void dump_strings(const char *tag, char **strings, int num_str)
{
printf("%s (%d):\n", tag, num_str);
for (int i = 0; i < num_str; i++)
printf("%d: %s\n", i, strings[i]);
putchar('\n');
}
int main(void)
{
char string[] =
"this-one.testthis-two.testthis-three.testthis-two.testthis-one.test"
"this-1-testthis-1-testthis-2-testthis-1-test"
"this-1-testthis-1-testthis-1-testthis-1-test"
;
const char *b_token = "this";
const char *e_token = "test";
int b_len = strlen(b_token);
int e_len = strlen(e_token);
char *buffer = string;
char *b_mark;
char *e_mark;
char *a[50];
int num_str = 0;
while ((b_mark = strstr(buffer, b_token)) != 0 && (e_mark = strstr(b_mark + b_len, e_token)) != 0)
{
int length = e_mark + e_len - b_mark;
char *s = (char *) malloc(length + 1); // Allow for null
strncpy(s, b_mark, length);
s[length] = '\0'; // Null terminate the string
a[num_str++] = s;
buffer = e_mark + e_len;
}
dump_strings("After splitting", a, num_str);
//remove duplicate strings
for (int i = 0; i < num_str; i++)
{
for (int j = i + 1; j < num_str; j++)
{
if (strcmp(a[i], a[j]) == 0)
{
free(a[j]); // Free the higher-indexed duplicate
a[j] = a[--num_str]; // Move the last element here
j--; // Examine the new string next time
}
}
}
dump_strings("After duplicate elimination", a, num_str);
for (int i = 0; i < num_str; i++)
free(a[i]);
return 0;
}
使用valgrind 进行测试给出了一个干净的健康证明:没有内存故障,没有泄漏数据。
样本输出:
After splitting (13):
0: this-one.test
1: this-two.test
2: this-three.test
3: this-two.test
4: this-one.test
5: this-1-test
6: this-1-test
7: this-2-test
8: this-1-test
9: this-1-test
10: this-1-test
11: this-1-test
12: this-1-test
After duplicate elimination (5):
0: this-one.test
1: this-two.test
2: this-three.test
3: this-1-test
4: this-2-test