【问题标题】:Better way to run C code with multiple prompts and scanf使用多个提示和 scanf 运行 C 代码的更好方法
【发布时间】:2023-04-01 22:48:01
【问题描述】:

我正在使用 C 编写一个简单的程序,我需要计算平均值等。我的第一个功能是提示用户参数(我假设)存储在全局变量中,以便我可以在其他功能,如“计算 MIPS”等。

我的问题是,当我调用该函数来收集用户的输入时,程序在第一次提示后停止。总共有7个提示。有谁知道我做错了什么?为了能够询问我的所有提示并存储值,我可以采取哪些建议?我在考虑可能使用while循环,但我不确定当值都存储在全局变量中时如何终止循环。我确定我把一个简单的问题复杂化了。 ????

下面有我的程序的一个片段:

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

/* declare global var's */
int intr_class = 0;
int freq = 0;
int class1 = 0;
int cpi_1 = 0;
int class2 = 0;
int cpi_2 = 0;
int class3 = 0;
int cpi_3 = 0;

void params(){

    printf("Enter the number of instruction classes: ");
    scanf("%s", intr_class);

    printf("Enter the frequency of the machine (MHz): ");
    scanf("%s", freq);

    printf("Enter CPI of class 1: ");
    scanf("%s", cpi_1);

    printf("Enter instruction count of class 1 (millions): ");
    scanf("%s", class1);

    printf("Enter CPI of class 2: ");
    scanf("%s", cpi_2);

    printf("Enter instruction count of class 2 (millions): ");
    scanf("%s", class2);

    printf("Enter CPI of class 3: ");
    scanf("%s", cpi_3);

    printf("Enter instruction count of class 3 (millions): ");
    scanf("%s", class3);
}

int main() {
    /* declare local var's */
    char menuChoice[0];

    /* until user quits, loop */
    /* print out menu list */
    printf("Menu of Options:\n______________\n"
           "a) Enter Parameters\n"
           "b) Calculate average CPI of a sequence of instructions\n"
           "c) Calculate total execution time of a sequence of instructions\n"
           "d) Calculate MIPS of a sequence of instructions\n"
           "e) Quit\n\n"
           "Enter selection: ");
    scanf("%s", menuChoice);

    /* prompt for selection & choose appropriate procedure using either a case statement of if-else if-else statements or switch scanf for character input */
    while(menuChoice[0] != 'e'){
        switch(menuChoice[0]){
            case 'a':
                params();
                break;
            case 'b':
                //avgCPI();
                break;
            case 'c':
                //calcExTime();
                break;
            case 'd':
                //calcMIPS();
                break;
            case 'e':
                exit(0);
            default:
                printf("Menu of Options:\n______________\na) Enter Parameters\nb) Calculate average CPI of a sequence of instructions\nc) Calculate total execution time of a sequence of instructions\nd) Calculate MIPS of a sequence of instructions\ne) Quit\n\nEnter selection: ");
        }
    }
    return(0);
}

【问题讨论】:

  • 您使用 scanf 错误。 scanf ("%d", &variable_name)
  • 您是否都添加了&amp; 并将%s 更改为%d
  • char menuChoice[0]; 在 C 中也是不允许的。没有零长度数组。你的编译器应该对你发出警告。
  • @DavidC.Rankin:GCC 对 0 长度数组很有用有一些有趣的想法。
  • @jemmamariex3 - 使用结构并在函数的参数中传递指向它的指针

标签: c loops printf scanf


【解决方案1】:

您会遇到 UB,因为这不是使用 scanf 的方式。

scanf ("%d", &amp;variable_name) 用于整数,而这样做的方式是使用错误的格式 ("%s") + 传递变量值 (... variable_name) 而不是变量地址 (&amp;variable_name 注意 &)

【讨论】:

    【解决方案2】:

    虽然您发现您对 格式说明符 的错误使用以及需要指向 scanf 中的变量的指针是您的主要问题,但正确使用 scanf 的另一个方面不是尚未解决。

    您不能简单地写scanf ("%d", &amp;intr_class); 来获取用户输入而不验证返回以确保成功转换为整数。在您确信intr_class 拥有一个有效值之前,您必须这样做。 (在您尝试使用intr_class 之前,如果您有任何此类要求,您还需要确保该值在可接受的范围内)

    这适用于所有用户输入,无论您使用什么功能来获取它,但它尤其适用于scanf。如果用户在响应您的提示时输入了错误的内容怎么办?例如

    Enter the number of instruction classes: rabbits
    

    然后呢?除非您检查返回值,否则您永远不会知道 intr_class 未设置,如果未初始化,任何访问该值的尝试都是未定义行为

    此外,scanf 遇到错误时会停止读取,而在stdin 中留下任何未读取的字符。那么,当您尝试阅读其他内容时会发生什么?让我们考虑一个例子:

    Enter the frequency of the machine (MHz): 800
    

    这个条目是成功还是失败? (答案:失败)为什么? stdin 仍然包含 "rabbits\n800\n"(每次用户按下 Enter 时都会添加 '\n',并且您不会每次输入都失败的唯一原因是 '%d' 格式说明符会占用前导空格),但 "rabbits" 不是空格...)

    因此,您必须加倍小心使用scanf 进行用户输入,不仅要考虑转换是成功还是失败,还要考虑输入缓冲区中剩余的任何字符(如果您的下一种输入类型,则为'\n'不占用前导空格,或转换失败后留在输入缓冲区中的任何字符)。

    管理此问题的一种方法是在每次调用 scanf 后手动删除留在 stdin 中的所有字符。 (注意:应该检查返回不是EOF)。在您调用scanf 并检查返回不是EOF 后,您可以简单地调用一个简单的函数来读取并丢弃所有剩余的字符,例如:

    /* function to empty remaining characters in stdin before next input
     * to help avoid the pitfalls associated with using scanf for user input.
     */
    void emptystdin()
    {
        int c = getchar();
        for (; c != '\n' && c != EOF; c = getchar()) {}
    }
    

    避免使用全局变量以及使用struct 保存所有参数的非常好的建议也应该得到解决。如果您定义了一个结构(称为struct parameters_t),在main() 中创建并声明它的一个实例(例如称为parameters),那么您只需将parameters 的地址传递给您的params() 函数并填写params 中的值。您只需将函数声明更改为包含指向结构类型的指针void params (stuct parameters_t *p),然后在params 函数中提示并填写p-&gt;intr_class 等。

    为了简单起见(并且避免一直为该类型键入struct parameters_t,您可以简单地创建一个typedef(别名),将parameters_t 定义为struct parameters_t 的别名以减少打字。

    您还应该将返回类型从 void 更改为可以有意义地指示您在 params 中输入的成功/失败的内容

    将这些部分放在一个简短的示例中,您可以执行类似于以下的操作:

    #include <stdio.h>
    #include <stdlib.h>
    
    /* declare a struct to associate all the values */
    typedef struct {
        int intr_class,
            freq,
            class1,
            cpi_1,
            class2,
            cpi_2,
            class3,
            cpi_3;
    } parameters_t;
    
    /* function to empty remaining characters in stdin before next input
     * to help avoid the pitfalls associated with using scanf for user input.
     */
    void emptystdin()
    {
        int c = getchar();
        for (; c != '\n' && c != EOF; c = getchar()) {}
    }
    
    /* params takes pointer to (address of) a struct parameters_t and
     * prompts for and fills each value. a tmp struct is used to avoid
     * changing any values in 'p' in case of a partial fill. returns
     * address of p on success, NULL otherwise indicating error.
     */
    parameters_t *params (parameters_t *p){
    
        parameters_t tmp = { .intr_class = 0 };
        printf ("Enter the number of instruction classes: ");
        if (scanf ("%d", &tmp.intr_class) != 1)
            return NULL;
    
        printf ("Enter the frequency of the machine (MHz): ");
        if (scanf ("%d", &tmp.freq) != 1)
            return NULL;
    
        printf ("Enter CPI of class 1: ");
        if (scanf ("%d", &tmp.cpi_1) != 1)
            return NULL;
    
        printf ("Enter instruction count of class 1 (millions): ");
        if (scanf ("%d", &tmp.class1) != 1)
            return NULL;
    
        printf ("Enter CPI of class 2: ");
        if (scanf ("%d", &tmp.cpi_2) != 1)
            return NULL;
    
        printf ("Enter instruction count of class 2 (millions): ");
        if (scanf ("%d", &tmp.class2) != 1)
             return NULL;
    
        printf ("Enter CPI of class 3: ");
        if (scanf ("%d", &tmp.cpi_3) != 1)
            return NULL;
    
        printf ("Enter instruction count of class 3 (millions): ");
        if (scanf ("%d", &tmp.class3) != 1)
            return NULL;
    
        *p = tmp;   /* assign temp values to struct p */
    
        return p;
    }
    
    /* simple function to print values stored in p */
    void prnparams (parameters_t *p)
    {
        if (!p || p->intr_class == 0) {
            fprintf (stderr, "error: parameters empty or NULL\n");
            return;
        }
        printf ("parameters:\n"
                "  instruction classes: %d\n"
                "  frequency (MHz)    : %d\n"
                "  CPI of class 1     : %d\n"
                "  class 1 inst count : %d\n"
                "  CPI of class 2     : %d\n"
                "  class 2 inst count : %d\n"
                "  CPI of class 3     : %d\n"
                "  class 3 inst count : %d\n",
                p->intr_class, p->freq, p->cpi_1, p->class1,
                p->cpi_2, p->class2, p->cpi_3, p->class3);
    }
    
    int main (void) {
    
        char menuchoice;
        /* declare a struct & iniailize all values zero */
        parameters_t parameters = { .intr_class = 0 };  
    
        for (;;) {  /* loop until user quits */
    
            /* print out menu list */
            printf ("\nMenu of Options:\n"
                    "______________\n"
                    "  a) Enter Parameters\n"
                    "  b) Calculate average CPI of a sequence of instructions\n"
                    "  c) Calculate total execution time of a sequence of "
                          "instructions\n"
                    "  d) Calculate MIPS of a sequence of instructions\n"
                    "  p) Print stored values\n"
                    "  e) Quit\n\n"
                    "Enter selection: ");
    
            if (scanf ("%c", &menuchoice) == EOF) { /* check user cancels input */
                putchar ('\n');                     /* tidy up before exit */
                break;
            }
            if (menuchoice != '\n') /* make sure user didn't just hit [Enter] */
                emptystdin();       /* remove all chars from stdin */
    
            switch(menuchoice){
                case 'a':
                    if (!params(&parameters))
                        fprintf (stderr, "error: params() failed.\n");
                    emptystdin();  /* critical here or menuchoice would be '\n' */
                    break;
                case 'b':
                    //avgCPI();
                    break;
                case 'c':
                    //calcExTime();
                    break;
                case 'd':
                    //calcMIPS();
                    break;
                case 'p':
                    prnparams (&parameters);
                    break;
                case 'e':
                    exit(0);
                default:
                    fprintf (stderr, "error: invalid menuchoice.\n");
                    break;
            }
        }
    
        return 0;
    }
    

    使用/输出示例

    $ ./bin/scanfparams
    
    Menu of Options:
    ______________
      a) Enter Parameters
      b) Calculate average CPI of a sequence of instructions
      c) Calculate total execution time of a sequence of instructions
      d) Calculate MIPS of a sequence of instructions
      p) Print stored values
      e) Quit
    
    Enter selection: a
    Enter the number of instruction classes: 10
    Enter the frequency of the machine (MHz): 20
    Enter CPI of class 1: 30
    Enter instruction count of class 1 (millions): 40
    Enter CPI of class 2: 50
    Enter instruction count of class 2 (millions): 60
    Enter CPI of class 3: 70
    Enter instruction count of class 3 (millions): 80
    
    Menu of Options:
    ______________
      a) Enter Parameters
      b) Calculate average CPI of a sequence of instructions
      c) Calculate total execution time of a sequence of instructions
      d) Calculate MIPS of a sequence of instructions
      p) Print stored values
      e) Quit
    
    Enter selection: p
    parameters:
      instruction classes: 10
      frequency (MHz)    : 20
      CPI of class 1     : 30
      class 1 inst count : 40
      CPI of class 2     : 50
      class 2 inst count : 60
      CPI of class 3     : 70
      class 3 inst count : 80
    
    Menu of Options:
    ______________
      a) Enter Parameters
      b) Calculate average CPI of a sequence of instructions
      c) Calculate total execution time of a sequence of instructions
      d) Calculate MIPS of a sequence of instructions
      p) Print stored values
      e) Quit
    
    Enter selection: e
    

    仔细看。刚学的时候有很多东西要消化。因此,如果您有任何问题,请发表评论,我很乐意为您提供进一步的帮助。

    【讨论】:

      【解决方案3】:

      https://www.tutorialspoint.com/c_standard_library/c_function_scanf.htm

      这应该有助于您准确了解 scanf 的工作原理。正如另外两个人所说,您应该使用 %d 而不是 %s 并且您需要在变量之前使用与号。如果你学过指针,你就会知道为什么需要 &

      我给你的那个链接也给了你其他数据类型的 % 命令。大多数数据类型都有不同的字母,例如%s 用于字符串(字符数组),而%lf 用于双精度。

      希望这会有所帮助!

      【讨论】:

      • 请不要将 %s 用于字符串 - 缓冲区溢出 - 限制长度,例如 %49s
      【解决方案4】:

      对整数使用scanf("%d",&amp;VariableName),对字符数组(字符串)使用scanf(" %s",&amp;arrCHar)

      另外,你必须在调用函数之前声明函数原型。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-11-05
        • 1970-01-01
        • 1970-01-01
        • 2013-07-19
        • 1970-01-01
        • 2020-11-27
        • 1970-01-01
        相关资源
        最近更新 更多