结构和指针卡住 [英] Struct and Pointers Stuck

查看:107
本文介绍了结构和指针卡住的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的C分配程序遇到一些问题:

I am facing some problems in my C assignment program:

  1. 在选项#4中,成绩仅按降序排列,而在选项#5中,成绩不变,仅交换学生的姓名和分数.

  1. At option #4, the grade only sorted to descending, while at #5, the grade won't change, only the name of the students and their scores are swapped.

在选项#8中,不会显示从文件输入的字符串和浮点,并且我希望选项8灵活(当通过选项#7输入文件时从文件显示或仅显示输入)从#1菜单选项开始).这是文件的示例:

At option #8, the string and float that inputted from file won't show up and I want the option 8 to be flexible (show from file when file was inputted via option #7 or only show the input from #1 menu option). Here is the example of the file:

80.64 John
90.40 Jane
78.00 Jake

代码:

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

struct Studata{
    float min, max;
    int many;
    char *max1, *min1, gx, gn;
}studata;

struct Student{
    char name[100], grade;
    float score[100];
};

float average(struct Student student[100]){
    float sum;
    for(int i=0; i<student.many; i++){
        sum += studata[i].score;
    }
    return sum/(float)student.many;
}

void MM(struct Student student[100]){
    int i;
    studata.min = 0;
    studata.max = 100;
    for (i=0; i<studata.many; i++){
        if(*student[i].score > studata.min){
            studata.min = student[i].score;
            studata.min1 = student[i].name;
            studata.gn = student[i].grade;
        }
    }
    for (i=0; i<studata.many; i++){
        if(student[i].score < studata.min){
            studata.max = student[i].score;
            studata.max1 = student[i].name;
            studata.gx = student[i].grade;
        }
    }

}

void swapname(char *a, char *b){
    char z[100];
    strcpy(z, a);
    strcpy(a, b);
    strcpy(b, z);
}

void swapscore(float a, float b){
    float temporary = a;
    a = b;
    b = temporary;
}

void swapgrade(char A1, char B1) {
    char C1 = A1;
    A1 = B1;
    B1 = C1;
}

void Bubblesort(int mode, struct Student student[100]) {
    int i, j;
    if(mode == 1) {
        for (i=0; i<studata.many; i++) {
            for (j=i+1; j<studata.many; j++) {
                if(student[j].score > student[i].score) {
                    swapname(student[i].name, student[j].name);
                    swapscore(student[i].score, student[j].score);
                    swapgrade(student[i].grade, student[j].grade);
                }
            }
        }
    }
    else if(mode == 0) {
        for(i=0; i<studata.many; i++) {
            for(j=i+1; j<studata.many; j++) {
                if(student[j].score < student[i].score) {
                    swapname(student[i].name, student[j].name);
                    swapscore(student[i].score, student[j].score);
                    swapgrade(student[i].grade, student[j].grade);
                }
            }
        }
    }
}

int main(){
    struct Student student[100];
    int selection=1;
    FILE *file;
    
    while (selection <= 8 && selection >= 1) {
        printf("\n\n\t-------MENU-------\n\n");
        printf("0. Enter Data of Students\n");
        printf("1. Calculate the Average\n");
        printf("2. Show Maximum and Minimum\n");
        printf("3. Sort Score Ascending\n");
        printf("4. Sort Score Descending\n");
        printf("5. Save Scores\n");
        printf("6. Load Scores from File\n");
        printf("7. Load All Data\n");
        printf("Choice (Other than 1-8 to Exit): ");
        scanf("%d", &selection);
        
        if(selection == 1) {
            printf("=============================\n");
            printf("\nHow many students would you like to input: ");
            scanf(" %d", &studata.many);
             for (int i=0; i<studata.many; i++) {
                printf("\nStudent-%d Name\t: ", i+1);
                scanf(" %[^\n]s", student[i].name);
             
                printf("Student-%d Score\t: ", i+1);
                scanf(" %f", &student[i].score);
                while(student[i].score > 100 || student[i].score < 0) {
                     printf("Hey, wrong input, please input correctly, okay?");
                     printf("\nStudent-%d Score\t: ", i+1);
                     scanf(" %f",&student[i].score);
                }

                if (student[i].score <= 100 && student[i].score >= 90 ) {
                    student[i].grade= 'A';
                }
                else if (student[i].score < 90 && student[i].score >= 80) {
                    student[i].grade= 'B';
                }
                else if (student[i].score < 80 && student[i].score >=70) {
                    student[i].grade= 'C';
                }
                else if (student[i].score < 70 && student[i].score >=60) {
                    student[i].grade= 'D';
                }
                else if (student[i].score < 60 && student[i].score >=50) {
                    student[i].grade= 'E';
                }
                else {
                    student[i].grade = 'F';
                }
            }
        }

        else if(selection == 2) {
            printf("=============================\n");
            printf("Average of Score is %.2f", average(student));
        }
        else if(selection == 3) {
            MM(student);
            printf("=============================\n");
            printf("Minimum\t: %s || %4.2f || %c\n", studata.max1, studata.max, studata.gx);
            printf("Maximum\t: %s || %4.2f || %c\n", studata.min1, studata.min, studata.gn);
        }
        else if(selection == 4) {
            printf("=============================\n");
            Bubblesort(0,student);
            for(int i=0; i<studata.many; i++) {
                printf("   %s : %5.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
            }
        }
        else if(selection == 5) {
            printf("=============================\n");
            Bubblesort(1,student);
            for(int i=0; i<studata.many; i++) {
                printf("   %s : %5.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
            }
        }
        else if(selection == 6) {
            char filename[100];
            printf("=============================\n");
            printf("Name of the file (with ext.): ");
            scanf(" %[^\n]s", filename);
            file = fopen(filename, "w");
            for(int i=0; i<studata.many; i++) {
                fprintf(file,"%.2f %s\n", student[i].score, student[i].name);
            }
            fclose(file);
        }
        else if(selection == 7) {
            char filename[100];
            char sub_ch;
            int i;

            printf("Enter name of file you want to open (with extension): ");
            scanf(" %[^\n]s", filename);
            file = fopen(filename, "r");
            while (file == NULL) {
                printf("I'm Error! Reinput? (Y/n): ");
                scanf("%c", &sub_ch);
                if(sub_ch == 'Y') {
                    printf("Enter name of file you want to open (with extension): ");
                    scanf(" %[^\n]s", filename);
                }
                file = fopen(filename, "r");
                if(sub_ch == 'n') {
                    exit(1);
                }
            }
            
            printf("=============================\n");
            fscanf(file, "%f %s", &student[i].score, student[i].name);
            while (!feof(file)) {
                if (student[i].score <= 100 && student[i].score >= 90 ) {
                    student[i].grade= 'A';
                }
                else if (student[i].score < 90 && student[i].score >= 80) {
                    student[i].grade= 'B';
                }
                else if (student[i].score < 80 && student[i].score >=70) {
                    student[i].grade= 'C';
                }
                else if (student[i].score < 70 && student[i].score >=60) {
                    student[i].grade= 'D';
                }
                else if (student[i].score < 60 && student[i].score >=50) {
                    student[i].grade= 'E';
                }
                else {
                    student[i].grade= 'F';
                }
                printf("%s %8.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
                fscanf(file, "%f %s", &student[i].score, student[i].name);
            }
            fclose(file);
        }
        else if(selection == 8) {
            printf("=============================\n");
            for (int i=0; i<studata.many; i++) {
                printf("Name || Score || Grade\t: %s || %3.2f || %c\n", student[i].name, student[i].score, student[i].grade);
            }
        }
    }
    return 0;
}

在尝试为每个可能的变量提供指针之后,我不知道该怎么办.

I don't know what to do again after I tried to give pointer on every possible variable.

推荐答案

在上面的提示中展开说明,使您的代码难以阅读(和维护)的原因是您忽略了上面的注释中的(2).您的实现(应用于数据的逻辑)与界面(与用户进行交互的方式)并不分离.取而代之的是,将您的代码全部杂乱地放在一个菜单中,该菜单跨越了几行屏幕.

Expanding on the tips above, the thing that makes your code so difficult to read (and maintain) is you ignore (2) from the comment above. Your implementation (the logic applied to your data) is not separate from your interface (how you interact with the user). Instead you have your code all jumbled together in a menu that spans screenfuls of lines.

将您的实现与接口分开可以保持每个逻辑的独立性和可读性.这里还有一些其他方面,您可以在其中改进接口代码和实现:

Keeping your implementation separate from interface keeps the logic for each separate and readable. Here are some additional areas where you can improve both the interface code and the implementation:

不要弄乱不必要的重复输出函数调用的实现

在编译期间,编译将所有仅由空格分隔的相邻字符串文字串联在一起.这意味着您不需要10次单独调用 printf()即可输出菜单.实际上,由于显示的菜单中没有必要进行任何转换,因此根本不需要调用 variadic printf()函数,更不用说调用10时代.由于您需要行尾控制,因此只需一次调用 fputs(),例如

During compilation, the compile with concatenate all adjacent string-literals that a separate only by whitespace. That means you do not need 10 separate calls to printf() to output your menu. In fact, since there are no conversions necessary in the menu you display, you don't need to call the variadic printf() function at all, much less call it 10 times. Since you want end-of-line control, a single call to fputs() is all you need, e.g.

    fputs ("\n\n\t-----------MENU-----------\n\n"
                 " 0. Enter Data of Students\n"
                 " 1. Calculate the Average\n"
                 " 2. Show Maximum and Minimum\n"
                 " 3. Sort Score Ascending\n"
                 " 4. Sort Score Descending\n"
                 " 5. Save Scores\n"
                 " 6. Load Scores from File\n"
                 " 7. Load All Data\n\n"
                 "Choice (Other than 1-8 to Exit): ", stdout);

如果您想在末尾输出'\ n',则只需使用 puts()即可.一次调用即可使您的菜单可读.

If you wanted a '\n' output at the end, then simply using puts() would do. A single call makes your menu readable.

创建逻辑功能以实现您的界面

您无需在代码正文的 while 循环中包含10行菜单.如果您创建一个简短的功能来显示菜单,则它使内容更易于阅读和维护,例如:

You don't need to include the 10 lines of menu within the while loop in the body of your code. It makes things much more readable and maintainable if you create a short function to display the menu such as:

void show_menu (void)
{
    fputs ("\n\n\t-----------MENU-----------\n\n"
                 " 0. Enter Data of Students\n"
                 " 1. Calculate the Average\n"
                 " 2. Show Maximum and Minimum\n"
                 " 3. Sort Score Ascending\n"
                 " 4. Sort Score Descending\n"
                 " 5. Save Scores\n"
                 " 6. Load Scores from File\n"
                 " 7. Load All Data\n\n"
                 "Choice (Other than 1-8 to Exit): ", stdout);
}

然后您的主循环就可以保持可读性,例如

Then your main loop can be kept readable, e.g.

while (selection <= 8 && selection >= 1) {
    show_menu();

与主循环开始时的10个 printf()函数相比,它更具可读性且易于维护.

That is far more readable and easier to maintain than 10 printf() functions at the beginning of your main loop.

验证程序的每个输入

您必须验证对程序的每次输入以及每次转换.如果用户滑到'4'并按'e'作为菜单选项,会发生什么情况?试试吧.

You must validate ever input to your program and every conversion. What happens if the user slips reaching for '4' and instead presses 'e' for the menu choice? Try it.

    scanf("%d", &selection);

未经验证,当在 if(selection == 1)中访问 selection (现在具有不确定的值)时,调用 Undefined Behavior .代码>.至少您必须捕获错误并退出,例如

With no validation, you invoke Undefined Behavior when selection (which now holds an indeterminate value) is access in if(selection == 1). At minimum you must catch the error and exit, e.g.

    if (scanf("%d", &selection) != 1) {
        fputs ("error: invalid integer input.\n", stderr);
        exit (EXIT_FAILURE);
    }

要正常处理该错误,您需要知道,使用 scanf(),当发生 matching-failure 时,从输入流中停止字符提取指向在输入流中留下导致失败 未读 的失败字符.您必须在下次尝试输入之前清除有问题的字符,否则,由于相同的原因,输入将再次失败,从而在许多情况下会导致无限循环.这个站点上有数百个答案,显示了如何正确使用 scanf().

To gracefully handle the error, you would need to know that with scanf(), when a matching-failure occurs, character extraction from the input stream ceases at that point leaving the offending characters causing the failure unread in the input stream. You must clear the offending characters before the next attempted input or the input will fail again for the same reason resulting in an infinite loop in many cases. There are hundreds of answers on this site showing how to correctly use scanf().

更好的解释是,您不使用 scanf()接受用户输入,而是将 fgets()与适当大小的缓冲区(字符数组)一起使用,然后使用 sscanf()从所需的缓冲区中检索所有值-这样就消除了输入流中未读字符的任何可能性.

The better ones explain that you do not take user input with scanf(), but instead use fgets() with an adequately sized buffer (character array) and then use sscanf() to retrieve any values from the buffer you need -- which eliminates any possibility of characters being left unread in your input stream.

不要在实现过程中弄乱界面

没有理由将用于确定字母等级的 if .. else if .. else if ... 逻辑放在程序循环的中间.就像上面的 show_menu()一样,该实现应该是一个短函数,您可以从主循环中调用它,例如

There is no reason the if .. else if .. else if ... logic for determining the letter grade should be in the middle of your program loop. Just as with show_menu() above, that implementation should be in a short function you can call from your main loop, e.g.

char get_ltrgrade (float f)             /* return letter grade given float score */
{
    if      (f >= 90) return 'A';
    else if (f >= 80) return 'B';
    else if (f >= 70) return 'C';
    else if (f >= 60) return 'D';
    else              return 'F';
}

然后,您只需对 get_ltrgrade();

将数据结构与您拥有的数据匹配

您使用的 struct Student struct Studata 不适合您显示的输入数据.您显示的输入数据对每个学生有一个分数.为了匹配您的数据,单独使用 struct Student 即可.现在,如果您有很多班的学生,那么 struct Studata 就会变得更加有意义. float评分[100]; 将允许每个学生多个成绩,但同样与您显示的数据不匹配,并且在 struct Student 中没有计数器可以跟踪该学生的有效成绩数.从您显示的内容来看,浮动分数:更有意义.

Your use of struct Student and struct Studata does not fit the input data you show. The input data you show has one score per student. To match your data, struct Student alone would do. Now if you have many classes of students, then struct Studata starts to make more sense. float score[100]; would allow multiple grades per-student, but again that doesn't match the data you show, and you have no counter within struct Student to track the number of valid grades for that student. From what you show, float score: makes more sense.

使用 qsort()

Sort Arrays With qsort()

C提供了 qsort()来处理任何类型的排序数组. qsort()函数的效率要比您编写的任何排序函数高几个数量级,并且已经过数十年的测试.新的C程序员通常避免使用 qsort(),因为它需要编写一个简短的 compare()函数,该函数通常少于5-10行告诉 qsort()的代码.如何比较数组的元素.其实很容易.

C provides qsort() to handle sorting arrays of any type. The qsort() function will be orders of magnitude more efficient than any sort you write and it has been tested for decades. New C programmers usually avoid qsort() because it requires writing a short compare() function that is generally less than 5-10 lines of code telling qsort() how to compare elements of the array. It is actually quite easy.

有关 qsort 使用的 compare 函数的解释以及如何使用它,请参见如何停止从用户那里获取输入的更多详细信息当我按下"q"键时?.

For explantion of the compare function used by qsort and how to use it, see How to sort an array of a structure using qsort? and further detail in How can I stop taking inputs from user when I press the key 'q'?.

以下内容汇总了从文件中读取学生数据,为成绩分配字母等级并将所有数据存储在struct数组中的实现的各个部分.然后,程序调用 qsort()对结构数组进行排序并输出数据.由于您包括了 char grade; (下面的 char ltrgrade; ),因此,当从文件中读取数据时便会指定等级,从而消除了计算范围内所有等级的需要您实施的中间.

The following puts together the pieces of your implementation that reads the student data from the file, assigning a letter grade to the score and storing all data in an array of struct. The program then calls qsort() to sort the array of struct and outputs the data. Since you include char grade; (char ltrgrade; below), the grade is assigned when the data is read from the file, eliminating the all need to compute the grade within the middle of your implementation.

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

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define MAXN  128
#define MAXS  256

typedef struct {        /* typedef allows use of student_t as type */
    char name[MAXN],
        ltrgrade;
    float score;
} student_t;

char get_ltrgrade (float f)             /* return letter grade given float score */
{
    if      (f >= 90) return 'A';
    else if (f >= 80) return 'B';
    else if (f >= 70) return 'C';
    else if (f >= 60) return 'D';
    else              return 'F';
}

/* qsort compare by student_t->scoore (descending),
 * change to >, < for ascending sort.
 */
int compare_score (const void *a, const void *b)
{
    student_t *sa = (student_t*)a,
              *sb = (student_t*)b;
    
    return (sa->score < sb->score) - (sa->score > sb->score);
}

int main (int argc, char **argv) {
    
    char buf[MAXC];                     /* buffer to hold each line read from file */
    student_t student[MAXS];            /* array of student_t (MAXS of them) */
    size_t n = 0;                       /* array index (counter) */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }
    
    /* while array not full, read each line into buf, VALIDATE read */
    while (n < MAXS && fgets (buf, MAXC, fp)) {
        /* split line into score and name, VALIDATE conversion */
        if (sscanf (buf, "%f %127[^\n]", &student[n].score, student[n].name) == 2) {
            /* get letter grade based on score */
            student[n].ltrgrade = get_ltrgrade (student[n].score);
            n += 1;     /* increment index only on valid converison */
        }
        else            /* handle error */
            fprintf (stderr, "error: invalid line format, student[%zu].\n", n);
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    qsort (student, n, sizeof *student, compare_score);     /* sort by score */
    
    for (size_t i = 0; i < n; i++)                          /* output result */
        printf ("%-12s  %6.2f  %c\n", 
                student[i].name, student[i].score, student[i].ltrgrade);
    
    return 0;
}

注意:在这种情况下, compare()函数采用整个2行代码(为了便于阅读,分成3行).可以使用 b-a 代替条件(a< b)-(a> b)降序排列,但是当 b 是一个较大的正值,而 a 是一个较大的负值(反之亦然).条件的差用于返回 -1、0、1 ,用于( -1 - a b ),( 0 - a b 相等)和( 1 - b排在 a 之前),以消除潜在的下溢/上溢.

Note: in this case the compare() function takes a whole 2-lines of code (split into 3 for readability). Instead of the conditional (a < b) - (a > b) for sort descending, you could have used b - a, but that is prone to overflow when b is a large positive value and a a large negative value (or vice-versa). The difference of the conditional are used to return -1, 0, 1 for (-1 - a sorts before b), (0 - a and b are equal), and (1 - b sorts before a) which eliminates potential under/overflow.

还请注意,对任何数组进行 qsort 处理,无论多么复杂,都只需要一行代码:

Also note, qsorting any array, not matter how complicated, takes only 1-line of code:

    qsort (student, n, sizeof *student, compare_score);     /* sort by score */

使用/输出示例

鉴于您的示例数据在文件 dat/studata.txt 中,程序的操作和输出为:

Given your sample data in the file dat/studata.txt, the program operation and output is:

$ ./bin/studata dat/studata.txt
Jane           90.40  A
John           80.64  B
Jake           78.00  C

每当您编写包含用户界面的代码时,都应努力使界面和实现分开(最大可能).从理论上讲,您的接口应该是不依赖任何给定实现的完整独立程序.您应该将要操作和运行菜单系统,而不必担心任何其他数据(在需要时使用伪数据).这不仅使您的代码可读性可维护,还使您可以在不依赖任何给定接口的小型可测试单元中创建实现逻辑.这使您的实现简短,易读且可测试.总是有小的重叠区域,但是您的目标是最大程度地减少和消除所有不必要的东西.

Whenever you go to write code that includes a user-interface, work to keep the interface and implementation separate (to the greatest extent possible). In theory, your interface should be a complete stand-alone program that doesn't depend on any given implementation. You should be about to operate and run your menu-system without having to worry about any other data (using dummy data where needed). Not only does this keep your code readable an maintainable, it also allows you to create your implementation logic in small testable units that do not depend on any given interface. This keeps your implementation short, readable and testable. There are always small areas of overlap, but your goal is to minimize and eliminate all but what is absolutely necessary.

这里有很多东西,请花点时间进行研究,如果您还有其他问题,请告诉我.

There is a lot here, take your time going through it and if you have further questions, just let me know.

这篇关于结构和指针卡住的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆