【问题标题】:Store every string that start and end with a special words into an array in C将每个以特殊单词开头和结尾的字符串存储到 C 中的数组中
【发布时间】:2017-05-25 03:24:38
【问题描述】:

我有一个长字符串,我想将每个以特殊单词开头和结尾的字符串存储到一个数组中,然后删除重复的字符串。在我的长字符串中,没有空格、, 或单词之间的任何其他分隔符,因此我无法使用strtok。开始标记是start,结束标记是end。这是我到目前为止的代码(但它不起作用,因为它使用的是strtok())。

char buf[] = "start-12-3.endstart-12-4.endstart-13-3.endstart-12-4.end";
char *array[5];
char *x;
int i = 0, j = 0;
array[i] = strtok(buf, "start");

while (array[i] != NULL) {
    array[++i] = strtok(NULL, "start");
}
//removeDuplicate(array[i]);
for (i = 0; i < 5; i++)
    for (j = 0; j < 5; j++)
        if (strcmp(array[i], array[j]) == 0)
            x[i++] = array[i];

printf("%s", x[i]);

示例输入:

start-12-3.endstart-12-4.endstart-13-3.endstart-12-4.end

输出等价于:

char *array[]= { "start-12-3.end", "start-12-4.end", "start-13-3.end" };

第二个start-12-4.end 字符串已在输出中消除。

*我也使用过strstr,但有一些问题:

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++;
}
}

要将字符串放入数组并删除重复的字符串,我尝试了以下代码,但它有问题:

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;
char *a[50];
int i=0, j;
char *s;

while ((b_mark = strstr(buffer, b_token)) != 0 && (e_mark =strstr(b_mark, e_token)) != 0)
{
    int length = e_mark + e_len - b_mark;

    s = (char *) malloc(length);

    strncpy(s, b_mark, length);

    a[i]=s;
    i++;
    buffer = e_mark + e_len;
}
for (i=0; i<strlen(s); i++)
       printf ("%s",a[i]);
free(s);
/*  
//remove duplicate string

for (i=0; i<4; i++)
  for (j=0; j<4; j++)
  {

    if (a[i] == NULL || a[j] == NULL || i == j)
         continue;

    if (strcmp (a[i], a[j]) == 0)  {
         free(a[i]);
         a[i] = NULL; 
   }
   printf("%s\n", a[i]);
*/

return 0; 
}

【问题讨论】:

  • 但是您的具体问题是什么?我们不会为您编写所有代码。通常你需要展示你的代码尝试,解释它应该做什么以及它有什么问题或者你需要什么帮助。
  • 如果您展示一个明确的示例输入和所需输出,也会有所帮助。
  • @Weather Vane tnx,已编辑。
  • 使用strstr() 定位开始和结束标记的出现。然后使用memmove()(或memcpy())复制部分字符串。请注意,由于您的开始和结束标记在原始字符串中是相邻的,因此您不能简单地在其中插入额外的字符——这也是您不能使用strtok() 的原因。因此,您必须复制原始字符串。
  • 再次使用strstr(),从开始部分结束的地方开始,找到下一个结束标记。然后,知道整个部分的开头,结尾的开头和结尾的长度,您可以安排将正确数量的字符精确地复制到新字符串中,然后在合适的情况下使用 null 终止,或者以逗号终止.类似于:if ((start = strstr(source, "start")) != 0 &amp;&amp; ((end = strstr(start, "end")) != 0) 那么数据在源字符串中的startend + 2(包括)之间。从'end'结束后的字符开始重复。

标签: c arrays string


【解决方案1】:

使用您提供的示例并在 Valgrind 中测试内存泄漏,但可能需要进一步测试。

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

unsigned tokens_find_amount( char const* const string, char const* const delim )
{
    unsigned counter = 0;
    char const* pos = string;
    while( pos != NULL )
    {
        if( ( pos = strstr( pos, delim ) ) != NULL )
        {
            pos++;
            counter++;
        }
    }

    return counter;
}

void tokens_remove_duplicate( char** const tokens, unsigned tokens_num )
{
    for( unsigned i = 0; i < tokens_num; i++ )
    {
        for( unsigned j = 0; j < tokens_num; j++ )
        {
            if( tokens[i] == NULL || tokens[j] == NULL || i == j )
                continue;

            if( strcmp( tokens[i], tokens[j] ) == 0 )
            {
                free( tokens[i] );
                tokens[i] = NULL;
            }
        }
    }
}

void tokens_split( char const* const string, char const* const delim, char** tokens )
{
    unsigned counter = 0;
    char const* pos, *lastpos;
    lastpos = string;
    pos = string + 1;

    while( pos != NULL )
    {
        if( ( pos = strstr( pos, delim ) ) != NULL )
        {
            *(tokens++) = strndup( lastpos, (unsigned long )( pos - lastpos ));
            lastpos = pos;
            pos++;
            counter++;
            continue;
        }

        *(tokens++) = strdup( lastpos );
    }
}

void tokens_free( char** tokens, unsigned tokens_number )
{
    for( unsigned i = 0; i < tokens_number; ++i )
    {
        free( tokens[ i ] );
    }
}

void tokens_print( char** tokens, unsigned tokens_number )
{
    for( unsigned i = 0; i < tokens_number; ++i )
    {
        if( tokens[i] == NULL )
            continue;
        printf( "%s ", tokens[i] );
    }
}

int main(void)
{
    char const* buf = "start-12-3.endstart-12-4.endstart-13-3.endstart-12-4.end";
    char const* const delim = "start";

    unsigned tokens_number = tokens_find_amount( buf, delim );
    char** tokens = malloc( tokens_number * sizeof( char* ) );
    tokens_split( buf, delim, tokens );

    tokens_remove_duplicate( tokens, tokens_number );
    tokens_print( tokens, tokens_number );

    tokens_free( tokens, tokens_number );
    free( tokens );

    return 0;
}

【讨论】:

  • 干得好。以下是挑剔的:小问题。您不检查内存分配。您的重复消除不会报告唯一值的数量。您可能应该压缩阵列,以便其中没有孔(您的打印例程可以跳过)。您的代码不适用于单独的开始和结束标记;它依赖于它们是连续的,并且开始标记没有出现在开始和结束标记之间的文本中。 (也就是说,您的代码错误地拆分了 "start-startendstart-start2end",因为它只查找 start 而不是 end。)在示例数据上没问题。
  • 嗯:有趣——POSIX 确实指定strnlen()strndup();它们将比我意识到的更广泛可用。我之前没有在手册页中注意到它们。
  • 顺便说一句,你也很好地利用了函数——这很重要。您不希望所有内容都在一个主程序中;它很快就会变得笨拙。
【解决方案2】:

基本拆分——识别字符串

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)

那么数据在源字符串中的startend + 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()?

即使假设您修复了 Thisthis 故障,还有其他问题。

  1. 您打印整个字符串。
  2. 您无需更改搜索的起点。
  3. 您的移动代码再次将 4 添加到 end
  4. 你不要使用start
  5. 代码应从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
$

有多个特征需要观察:

  1. 由于main() 忽略它的参数,我删除了参数(我的默认编译器选项不允许未使用的参数)。
  2. 我已对字符串进行大小写更正。
  3. 我为开始和结束标记设置了常量字符串b_tokene_token。名称是故意对称的。例如,这可以很容易地移植到一个函数中,其中标记是函数的参数。
  4. 同样,我为开始和结束标记的位置创建了 b_marke_mark 变量。
  5. 名称buffer 是指向从何处开始搜索的指针。
  6. 循环使用我在 cmets 中概述的测试,并适应所选名称。
  7. 打印代码确定找到的字符串的长度并仅打印该数据。它打印计数器值。
  8. 重新初始化代码会跳过所有以前打印的材料。

通用的命令行选项

您可以通过接受命令行参数并依次处理每个参数(如果有)来概括代码;当没有提供字符串时,您将使用您提供的字符串作为默认值。下一个级别将允许您指定以下内容:

./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_lenb_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 参数允许它识别它正在打印的数据集。我应该将“免费”循环放入一个函数中。重复消除代码可以(应该)在一个函数中;字符串提取代码可以(应该)在一个函数中——如pikkewynanswer。我扩展了测试数据(字符串连接在这样的上下文中很棒)。

#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

【讨论】:

  • 嗨,我不能根据第一个代码将它们放入数组中,对吗?为此,它需要将输出保存到变量中,然后将其放入数组中?
  • 上面的代码可以让你识别需要放入数组的数据。您知道字符串必须有多大;这就是计算出来的长度。它不包括空字节。您为新字符串分配足够的空间(记住空字节),然后使用函数将数据复制到该空间并添加最重要的空字节。您将字符串添加到指针数组中。您可以决定是否随时对数据进行排序(堆排序可能是合适的),并且在某些时候您也可以处理重复数据。或者你可以存储在数组中并在最后进行排序。
  • 对不起,为新字符串分配足够的空间是什么意思(记住空字节)?如果我是对的,我可以使用 strcpy 函数进行复制。
  • 你需要展示你最好的代码。你知道malloc()free()怎么用吗?如果没有,我们还有一些工作要做——但这就是为什么展示你的代码至关重要;它告诉我们你所知道的。你不能真正使用strcpy();它在一个空字节处停止,但是您需要复制的字符串在您希望复制停止的位置没有空字节。 (您也不应该尝试添加一个;这真的很恶心,并且不适用于字符串文字,但您的代码应该能够做到这一点。)您可以像我一样使用strncpy()memmove()memcpy()一开始就提到了。
  • 字符串是一个以空字节结尾的字符序列。为了能够使用像 strcmp()strcpy() 这样的函数,并使用像 %s 这样简单的 printf() 格式而不需要 %.*s,您必须确保您的字符串以空值结尾。
猜你喜欢
  • 2018-02-18
  • 1970-01-01
  • 1970-01-01
  • 2021-03-23
  • 2023-01-30
  • 2019-03-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多