【问题标题】:getting input of variable length in C在C中获取可变长度的输入
【发布时间】:2021-03-30 19:20:26
【问题描述】:

所以我正在为我的 C 实验室编写一个程序,以获取用户输入的坐标,格式如下:

-1.0 5.0

3.2 4.7

-2.0 3.4

1.0 4.4

等等。

他们指示我们专门使用scanf() 来获取输入。 无论如何,程序将使用这些进行一些计算。但是,如果我用scanf() 读取它们并将它们存储在同一个函数中,我会被困在如何准确地确定我将存储这些坐标的数组的大小上。我只是创建一个大小为 99 的数组吗?如果他们给101分呢?有没有办法先读取所有输入,计算换行符的数量,然后将其设置为我的数组的大小?

这是我的尝试:

void readPointsFindBoundingBox(double *bottomLeftX, double *bottomLeftY, double *widthp, double *heightp)
{
  printf("Enter coordinates: ");
double xvaltemp[25],yvaltemp[25];
int i=0;
while(scanf("%f %f/n", &xvaltemp[i], &yvaltemp[i])!=EOF)
{
    i++;
}
double xval[i],yval[i];
for(int x=0;x<i;x++)
{
    xval[x]=xvaltemp[x];
    yval[x]=yvaltemp[x];
}
for(int y=0;y<i;y++){
printf("%f",xval[y]);
printf("%f",yval[y]);
}

}

【问题讨论】:

  • " 专门使用 scanf() 来获取输入。" --> 太糟糕了,fgets() 会是更好的方法——尤其是对于错误处理。顺便说一句,“他们”是谁?
  • 教授!它是一门初级 C 课程,所以他们只向我们展示了 scanf。我假设这就是原因。
  • scanf 可以在您学习时使用。然后在你开发了一些与scanf 相关的头痛之后,也许你应该考虑使用fgets
  • scanf() 对新的 C 程序员来说充满了陷阱,这真的让人怀疑教授是否真的知道他在教什么——或者他是否只是在使用别人的教学大纲.. . 您可以使用scanf(),但您必须非常熟悉每个转换说明符的空白处理,并且知道如何在匹配失败 之后从stdin 中提取任何违规字符。否则——注意无限循环...
  • 这正是我使用附加的代码 sn-p 所面临的问题。我的 while 循环在 scanf 中没有检测到 EOF 条件!我该如何解决这个问题?

标签: c


【解决方案1】:

你可以使用malloc 然后realloc 来调整你的数组大小。这个简单的示例向您展示了它是如何工作的。请注意,我不知道您的用户应该如何指示他们如何完成输入数据,或者您的数据类型/结构名称被称为什么,因此您必须对其进行修改。

int i = 0;
size_t cb = sizeof(Point);
Point *points = malloc(cb), *tmp = NULL;

while(user_isnt_done_yet)
{
    scanf(" %...", points + i);
    cb += sizeof(Point);
    tmp = realloc(points, cb);
    if(tmp != NULL)
    {
        points = tmp;
    }
    else
    {
        // Out of memory error
    }
    i++;
}

free(points);
points = NULL;

另请注意,对每个条目进行重新分配是低效的。我更愿意从预定义的大小开始,并在必要时通过将其大小乘以 2 来重新分配它。

【讨论】:

  • 请注意,我不知道您的用户应该如何表明他们是如何完成输入数据的,谢谢!我们用来提交作业的门户很奇怪,所以 while(scanf()!=EOF) 可以正常工作,很奇怪。
【解决方案2】:

您有两种方法:

  • 根据需要增加数组大小。这是大多数人所做的,静态大小的数组只不过是等待发生的内存损坏。

  • 读取文件两次,一次是计算项目数,第二次是实际读取适当大小的数组中的数字。这是大多数非 I/O C 风格的 API(DirectX、Vulkan 等)所做的。

【讨论】:

  • 这种情况下的输入来自标准输入,就像在键盘中一样,而不是文件。用户输入坐标,然后按回车键。两个进入表示 EOF。我可以将此输入存储为变量,然后读取两次。但是,我必须将它存储在什么类型的变量中?
  • 还有第三种方法 - 分配比您预期需要更多的空间,并跟踪您使用了多少,如果数据过多,抱怨并停止。
  • @JonathanLeffler 这就是#1 所暗示的,只是您拒绝执行分配给您的工作,而不是内存损坏。只要有 RAM,我个人更喜欢增加数组大小。
【解决方案3】:

如果我理解您在做什么,并且您需要确定左下坐标以及包围用户输入的 x、y 坐标的边界框的宽度和高度,那么您不需要在全部。您需要做的就是跟踪左下角和右下角坐标,并使用几个临时的double 值来捕获用户输入的最大 x、y 坐标,然后可以通过减去来设置宽度和高度最大值的左下角坐标。

注意:您可能需要根据您使用的是普通笛卡尔坐标系(x,y 为中心为 0)还是屏幕坐标系(x,y 为 0)来调整比较在屏幕的左上角)。我们将把它留给你)

除非您检查返回,否则您无法正确使用任何输入功能。 scanf() 根据您在 format-string 中包含的 conversion-specifiers 返回发生的成功转换次数。如果在所有转换发生之前遇到EOF 或者如果发生 matching-failure,则它可能返回小于总数。 (例如,用户输入 "banana" 而不是坐标)。发生匹配失败时,将停止从stdin 提取字符,导致失败的字符留在stdin 中未读。 (只是等着你下次尝试输入时咬你)。

scanf()输入失败 上返回EOF(在第一次成功转换发生之前遇到EOF)。除了 matchinginput 失败之外,您还必须处理用户输入的超出您要求的额外或无关字符(例如,用户输入 "10.2 13.8 and other nonsense"。否则下次输入尝试时将发生匹配失败。

有关每次调用scanf() 时必须检查的条件的更多详细信息,请参阅此问题的答案C For loop skips first iteration and bogus number from loop scanf

实现边界框功能以及所需的验证非常简单。但是,使用scanf() 结束输入有点棘手。为了避免设置一些 MagicNumbers 让用户输入来表示完成,用户应该通过按 Ctrl + d(或 Ctrl + z 在 Windows 上)。然后您可以正常处理EOF,同时仍保留所有可能的输入。 (注意:使用推荐的输入法fgets()/sscanf(),用户只需在空行上按Enter即可表示完成)

考虑到这一点,您可以按如下方式实现您的功能:

#include <float.h>      /* for `DBL_MAX` macro */
...
void readPointsFindBoundingBox (double *bottomLeftX, double *bottomLeftY, 
                                double *widthp, double *heightp)
{
    /* temp values for parameters, initialized to max/min of range */
    double x = DBL_MAX, y = DBL_MAX, x_max = DBL_MIN, y_max = DBL_MIN;
    
    *bottomLeftX = *bottomLeftY = DBL_MAX;  /* initialize parameter values */
    *widthp = *heightp = 0;
    
    puts ("Press Ctrl+D (or Ctrl+z on windows twice) when done\n");
    
    for (;;) {      /* loop continually taking input */
        
        int rtn;    /* variable to save scanf() return */
        
        fputs ("Enter coordinates (\"X Y\"): ", stdout);    /* prompt for coordinates */
        
        rtn = scanf ("%lf %lf", &x, &y);    /* read input, save return */
        
        if (rtn == EOF) {                   /* check if user done */
            puts ("(input done)");
            break;
        }
        /* empty remaining characters from stdin */
        for (int c = getchar(); c != '\n'; c = getchar()) {}
        
        /* validate EVERY user-input. 2 valid conversions required. */
        if (rtn != 2) { /* handle matching-failure */
            fputs ("  error: invalid double values.\n", stderr);
            continue;
        }
        
        if (x < *bottomLeftX)           /* set bottomLeftX/Y as minimum coordinate  */
            *bottomLeftX = x;           /* assumes lower-left is minimum coordinate */
        if (y < *bottomLeftY)           /* adjust '>' if Y increases as you go down */
            *bottomLeftY = y;
        
        if (x > x_max)                  /* track maximum bounding coordinates   */
            x_max = x;                  /* to calculate width, height           */
        if (y > y_max)                  /* adjust y compare if Y increases down */
            y_max = y;
    }
    
    *widthp  = x_max - *bottomLeftX;    /* set width and height based on */
    *heightp = y_max - *bottomLeftY;    /* coordinates input by user    */
}

注意:这假定指针 double *bottomLeftX, double *bottomLeftY, double *widthp, double *heightp 都是简单的 double 值,返回到调用函数中,地址已传递给您的函数)

要测试您的功能,您可以写一个简短的main() 并将#include &lt;stdio.h&gt; 添加到顶部,例如

#include <stdio.h>
/* additional include and your function go here */

int main (void) {
    
    double bottomleft, bottomright, width, height;
    
    readPointsFindBoundingBox (&bottomleft, &bottomright, &width, &height);
    
    printf ("\nBounding Box\n"
            "  Lower Left Coordinates (x, y):  (%.2lf, %.2lf)\n"
            "  Height, Width (height, width):  (%.2lf, %.2lf)\n",
            bottomleft, bottomright, width, height);
}

使用/输出示例

$ ./bin/boundingboxfn
Press Ctrl+D (or Ctrl+z on windows twice) when done

Enter coordinates ("X Y"): 5.2 3
Enter coordinates ("X Y"): 9.1 -1
Enter coordinates ("X Y"): 2.2 3.3
Enter coordinates ("X Y"): .5 -.1
Enter coordinates ("X Y"): -1.4 8.1
Enter coordinates ("X Y"): my dog has fleas
  error: invalid double values.
Enter coordinates ("X Y"): 1.1 1.1
Enter coordinates ("X Y"): 4 -2
Enter coordinates ("X Y"): (input done)

Bounding Box
  Lower Left Coordinates (x, y):  (-1.40, -2.00)
  Height, Width (height, width):  (10.50, 10.10)

如果您选择数字,该函数会提供正确的左下角坐标以及包围所有坐标所需的边界框的宽度和高度。 (回想一下前面关于如果您在屏幕坐标而不是正常的 X、Y 坐标系中工作时需要调整的注释,其中 X 和 Y 分别向右和向上增加)

如果我正确理解了您的问题,请告诉我,如果没有,请发表评论,我可以提供进一步的帮助。如果您对上述操作有任何疑问,请告诉我。

【讨论】:

  • if (rtn == EOF) { 如果rtn == 0rtn == 1 会怎样?
  • @mevets -- 都被if (rtn != 2)覆盖
  • 不用担心 --- 不像我从未跳过一两个比较...:)
【解决方案4】:

"...但是,如果我用 scanf() 并将它们存储在同一个函数中。,...有没有办法 首先读取所有输入,计算换行符的数量,然后设置 那作为我的数组的大小?”

是的,有办法做到这一点。实际上,正如 cmets 和其他答案清楚地表明的那样,有很多方法可以完成这项任务,即使仅限于使用 scanf()。以下一组步骤是一种方法。它们说明了可以容纳任意数量输入的代码,而不知道用户将提前输入多少。用户输入的计数被跟踪并保存在索引变量inx 中,在退出输入循环时,该变量用于创建inxdouble 的数组,其中文本输入被解析为...

步骤:

  • 创建char *tok = NULL;
  • 创建char *endPtr = NULL;
  • 创建索引int inx = 0;
  • 创建double input = 0.0;
  • 创建输入大小变量int size = 0;
  • scanf() 用户输入 size 的值,例如 scanf("%d", &amp;size);
  • 使用size 创建一个可变长度数组(VLA) 例如char buffer[size];
  • 循环scanf("%lf", &amp;input); 语句以将输入收集到double 变量中,例如input
  • 使用sprintf(buffer, "%l0.5f,", input) 将输入连接到临时存储中。
  • 为输入循环的每次迭代增加一次索引 (inx++)
  • 输入标志时退出循环
  • 退出时使用inx 的值创建VLA 例如double array[inx];
  • 使用以下代码通过解析字符串来填充数组:

示例:

int i = 0;
tok = strtok(buffer, ", ");//delimiters are space and comma
while(tok)
{
    array[i++] = strtod(tok, &endPtr);
    //error checking for strtod would be advised here
    tok = strtok(NULL, ", ");
}

请注意,这是一个粗略的算法框架,对错误检查很简单,但适用于在编译时不知道输入数量的简单用户输入任务。

这是上述步骤的完整工作说明:

int main(void)
{
    int size = 0;
    int i = 0;
    int inx = 0;
    char *tok = NULL;
    char *endPtr = NULL;
    double input = 1.0;//to avoid tripping flag value test
    double flag = 9999.0;//enter this value to exit input loop
    
    printf("Enter desired size of input buffer:\n");
    scanf("%d", &size);
    char buffer[size];
    char tempbuf[size];//to prevent collision in sprintf later
    memset(buffer, 0, sizeof buffer);
    memset(tempbuf, 0, sizeof tempbuf);
    printf("enter input value:\n");
    scanf("%lf", &input);
    while(fabs(input - flag) > 0.000)//using flag value as exit criteria
    {
        inx++;
        sprintf(tempbuf, "%s", buffer);
        sprintf(buffer, "%s,%0.5lf", tempbuf, input);//concatenate comma
                                                     //separated inputs string
        printf("enter input value:\n");
        scanf("%lf", &input);
    }
    double array[inx];
    memset(array, 0, sizeof array);
    //parse comma separated inputs string into array of double
    tok = strtok(buffer, ", ");
    while(tok)
    {
        array[i++] = strtod(tok, &endPtr);
        tok = strtok(NULL, ", ");
    }
    return 0;
}
        

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-19
    • 2011-12-02
    • 2018-05-25
    • 1970-01-01
    • 2014-01-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多