【问题标题】:Error in self written C function to split strings自写 C 函数以拆分字符串时出错
【发布时间】:2019-11-25 19:00:13
【问题描述】:

几周前我刚刚了解了字符串,我对制作自己的函数很感兴趣。一开始我尝试了一个短句,它成功了,但是当我尝试长句时遇到了问题......

问题可能出在“getSplitText”函数中,我该如何解决这个错误?

在这种情况下使用 for 循环不好吗?

之前

之后

这是我的代码

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

char *getSplitText(char *var_text, char split, int index);
int getLengthSplitText(char *var_text, char split);
int getLengthString(char *var_text);

int main(){
    char var_text[100];
    fgets(var_text,100,stdin);
    int i;
    printf("Total Characters: %d\n",getLengthString(var_text));
    printf("Total Words: %d\n",getLengthSplitText(var_text,' '));
    printf("Split Result:\n");
    for(i=1;i<=getLengthSplitText(var_text,' ');i++){
        printf("#%d-%s\n",i,getSplitText(var_text,' ',i));
    }

   return 0;
}

int getLengthString(char *var_text){
    int len = 0, i = 0;
    while (var_text[i] != '\0'){
        len++;
        i++;
    }
    return len;
}

int getLengthSplitText(char *var_text, char split){

    int textlen = getLengthString(var_text);
    int lenKata=0;
    char *resultKata = malloc(sizeof(char) * textlen);
    resultKata[0]='\0';
    int i,j=0;
    for(i=0;i<textlen;i++){

        if(var_text[i]!=split){

            if(textlen-1==i){
                resultKata[j] = var_text[i];
                resultKata[j + 1] = '\0';
                lenKata ++;
            }else {
                resultKata[j] = var_text[i];
                j++;
                resultKata[j + 1] = '\0';
            }
            //printf("#1 %s\n",resultKata);

        }else if(var_text[i]==split){

            if(resultKata[0]!='\0'){
                lenKata ++;
                }else{
                    j = 0;
                    resultKata[0] = '\0';
                }
            }
            //printf("#2 %s\n",resultKata);
        }
    return lenKata;
    }

char *getSplitText(char *var_text,char split, int index){

    int textlen = getLengthString(var_text);
    char *resultKata = malloc(sizeof(char) * textlen);
    resultKata[0]='\0';

    if(index<=getLengthSplitText(var_text,split)) {
        int i,j=0;
        int lenKata=0;

        for (i = 0; i < textlen; i++) {

            if (var_text[i] != split) {

                if (textlen - 1 == i) {
                    resultKata[j] = var_text[i];
                    resultKata[j + 1] = '\0';
                    lenKata++;
                    break;
                } else {
                    resultKata[j] = var_text[i];
                    j++;
                    resultKata[j + 1] = '\0';
                }
                //printf("#1 %s\n",resultKata);

            } else if (var_text[i] == split) {

                if (resultKata[0] != '\0') {
                    lenKata++;
                    if (lenKata == index) {
                        resultKata[j] = '\0';
                        break;
                    } else {
                        j = 0;
                        resultKata[0] = '\0';
                    }
                }
                //printf("#2 %s\n",resultKata);
            }
        }
    }
    return resultKata;
}

【问题讨论】:

  • 作为纯粹的文体问题,您应该使用break; 而不是goto END_LOOP;
  • 你也不应该使用gets。它的手册页明确说明了这一点。这可能是您的问题的原因(或者至少,允许它未被检测到)。您的字符串缓冲区包含 20 个char 值,这为您提供了 19 个可用的字符串值加上一个终止空字符。如果您输入任何大于 19 个字符的字符串,您将写到数组的末尾,并受到未定义行为的影响。
  • 函数:gets() 已经贬值多年,在最后两个 C 版本中完全删除。建议阅读 fgets() 的 MAN 页面并使用该函数
  • 关于:char *resultKata = malloc(sizeof(char) * textlen); 1) 表达式:sizeof(char) 在 C 标准中定义为 1。将任何内容乘以 1 没有任何效果,只会使代码混乱。建议删除该表达式。 2) 函数:malloc() 需要一个类型为:size_t 的参数,但 textlen 被声明为 int 3) 始终检查 (!=NULL) 返回值以确保操作成功。如果不成功,调用perror( "malloc failed" );将你的错误信息和系统认为错误发生的文本原因输出到stderr

标签: c string loops


【解决方案1】:

OP的代码中出现了很多malloc()s。

关于已经给出的 cmets:

  1. 将任何内容乘以 1 没有任何效果,只会使代码混乱。建议删除该表达式。

恕我直言,这是风格问题。如果它提高或削弱代码的可读性是个人喜好的问题。我假设编译器足够聪明,可以忽略乘以 1,我也会忽略。

  1. 函数:malloc() 需要一个类型为:size_t 的参数,但 textlen 被声明为 int

虽然我同意,但只要提供的 int 值为正值,我不认为这是一个关键点。

  1. 始终检查 (!=NULL) 返回值以确保操作成功。

这也是我推荐的。

令我个人烦恼的是代码中有一些malloc()s,但没有一个free()。使用malloc() 分配内存意味着,一些堆内存在内部堆内存管理器中被标记为“正在使用”。如果不是free()d,它将被标记为“已使用”,直到过程寿命结束。丢失(例如覆盖)指向已分配内存的指针会使其丢失。 (不可能再次解决它。)这称为memory leak

当然,这在公开的示例代码中不是一个关键问题,但它可能会在具有更多堆内存消耗的大型应用程序中成为一个问题。因此,我觉得值得一提。

整个示例似乎类似于使用标准库函数可以完成的操作

(不是你误会我的意思。我认为类似于标准函数用于学习目的没有什么不好的。)

getLengthString() 其实没什么问题。但是,它管理两个计数变量ilen。 OP 可能错过了在每个迭代步骤之后它们始终具有相同的值。因此,其中之一是多余的,可以消除:

int getLengthString(char *var_text)
{
  int len = 0;
  while (var_text[len]) ++len;
  return len;
}

关于getLengthSplitText()(负责计算字数),我不明白为什么需要任何malloc()。因此,我编写了一个新函数getNumTokens(),它计算由某个拆分字符分隔的非空单词(我称它们为“令牌”):

int getNumTokens(char *var_text, char split)
{
  int n = 0;
  for (int inToken = 0; *var_text; ++var_text) {
    if (!inToken && *var_text != split) ++n; // count if new token starts
    inToken = *var_text != split; // update flag
  }
  return n;
}

它有点短,不需要任何malloc()

请注意变量inToken,它实际上用作(布尔)标志。它负责记住在迭代文本时是否已经计算了令牌。通过分配当前字符 (*var_text) 和分隔符 (split) 的比较结果,在每个迭代步骤中对其进行更新。

提供的var_text 直接用于访问和进度——不使用额外的索引。由于只需要对文本进行一次迭代,因此更改指针并没有什么坏处。它是一个本地副本(按值传递),在函数内部具有有限的生命周期。

关于strtok(),我曾经写过一个变体strtoke()(只是为了好玩)作为SO: Split string into Tokens in C, when there are 2 delimiters in a row的答案。

有两个事实需要注意:

  1. strtok() 通过将出现的分隔符替换为 \0 字节(以分隔找到的标记)来修改输入字符串。

  2. strtok() 管理一个内部全局状态,使其不可重入。

OP 的解决方案似乎没有任何使用malloc() 购买的限制(考虑到上述所有问题)。

所以,我稍微改变了假设(实际上在 OP 问题中没有提到任何限制)。我认为修改输入 (1.) 是可以接受的,并编写了一个新函数 getNextToken():

char* getNextToken(char *var_text, char split)
{
  // skip space
  for (; *var_text && *var_text == split; ++var_text);
  // remember start of token
  char *token = var_text;
  // skip token
  for (; *var_text && *var_text != split; ++var_text);
  // remark end of token
  *var_text = '\0'; // doesn't hurt if there is already a '\0'
  // done
  return token;
}

该函数将返回var_text 中第一个标记的开始。如果它是一个空字符串(返回指针的内容是\0),则找不到令牌。 因此,找到的标记的结尾会用\0-Byte(在给定的输入字符串中)进行注释,这可能是之前的拆分字符。

由于我不想要全局内部状态,我必须在 getNextToken() 之外从一个令牌移动到另一个令牌。我发现这是可以接受的,因为它可以再次使用函数getLengthString() 来完成。有了这个,指针可以从标记的开头移动到结尾(\0-Byte 已被写入的位置)。加 1,达到下一个令牌的可能开始。当然,当到达文本结尾时,这可能会中断(终止符 \0 之后的地址可能超出范围)。幸运的是,代币的数量是已知的。

完整示例:

#include <stdio.h>

int getLengthString(char *var_text);
int getNumTokens(char *var_text, char split);
char* getNextToken(char *var_text, char split);

int main()
{
  char var_text[100];
  if (!fgets(var_text, 100, stdin)) {
    fprintf(stderr, "Input failed!\n");
    return -1;
  }
  printf("Total Characters: %d\n", (int)getLengthString(var_text));
  const char split = ' ';
  const int nTokens = getNumTokens(var_text, split);
  printf("Total Words: %d\n", nTokens);
  printf("Split Result:\n");
  char *token = var_text;
  for (int i = 1; i <= nTokens; ++i) {
    token = getNextToken(token, split);
    printf("#%2d-%s\n", i, token);
    token += getLengthString(token) + 1;
  }
  return 0;
}

int getLengthString(char *var_text)
{
  int len = 0;
  while (var_text[len]) ++len;
  return len;
}

int getNumTokens(char *var_text, char split)
{
  int n = 0;
  for (int inToken = 0; *var_text; ++var_text) {
    if (!inToken && *var_text != split) ++n; // count if new token starts
    inToken = *var_text != split; // update flag
  }
  return n;
}

char* getNextToken(char *var_text, char split)
{
  // skip space
  for (; *var_text && *var_text == split; ++var_text);
  // remember start of token
  char *token = var_text;
  // skip token
  for (; *var_text && *var_text != split; ++var_text);
  // remark end of token
  *var_text = '\0'; // doesn't hurt if there is already a '\0'
  // done
  return token;
}

I like C programming language but I have problem about this code. 的输出:

Total Characters: 66
Total Words: 12
Split Result:
# 1-I
# 2-like
# 3-C
# 4-programming
# 5-language
# 6-but
# 7-I
# 8-have
# 9-problem
#10-about
#11-this
#12-code.

Thank you for the help. Now, the code can work. I like programming. 的输出:

Total Characters: 68
Total Words: 13
Split Result:
# 1-Thank
# 2-you
# 3-for
# 4-the
# 5-help.
# 6-Now,
# 7-the
# 8-code
# 9-can
#10-work.
#11-I
#12-like
#13-programming.

Live Demo on coliru

【讨论】:

  • @auprojectsall 不要太担心。猜猜所有专业人士是如何开始的...... ;-)
猜你喜欢
  • 1970-01-01
  • 2017-11-05
  • 1970-01-01
  • 2021-07-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多