下标值既不是数组,也不是指针或向量,在C中使用struct获取n个字符串的输入

4si2a6ki  于 2023-05-22  发布在  其他
关注(0)|答案(2)|浏览(184)

这个问题说明我从用户那里获得输入,使用C中的结构输入n个名称。我写了下面的代码:

#include<stdio.h>
typedef struct employee{ 
    int code; 
    char name[]; 
} emp; 

int main () {
    emp em; 
    int n; 
    printf ("Number of entries: "); 
    scanf ("%d", &n); 
    
    int i; 
    for (i=0;i<n;i++){ 
        printf ("Enter name: "); 
        gets(em[i].name); 
    }

    return 0; 
}

我在这段代码中得到一个错误:

T2.c:16:16: error: subscripted value is neither array nor pointer nor vector
         gets(em[i].name);

如何编写正确的代码??
我甚至尝试在使用的两个数组中放置维度,并使用scanf函数。似乎什么都不起作用,我一次又一次地出错,

nr9pn0ug

nr9pn0ug1#

这里有几个问题。首先,定义的结构体的大小是一个int

typedef struct employee{ 
    int code; 
    char name[]; 
} emp;

name参数的大小为0。这种定义在更高级的分配方法中很有用,我猜这有点超出您的技能范围。我们需要提供一个实际的大小:

#define MAX_NAME_LEN 128

typedef struct employee{
    int code;
    char name[MAX_NAME_LEN];
} emp;

代码中的原始错误是由于试图访问定义的结构体,就好像它是一个数组,当它是一个单一的示例:

emp em;

我在评论中建议的方法是使用n

emp em[n];

如果你的编译器支持它,这是可以的,但是,这是一种特殊的数组,称为VLA。如果我们想避免这种情况,我们可以像对name参数一样,提供一个大小:

#define MAX_STUDENTS 32
int main() {
   emp em[MAX_STUDENTS];

就我个人而言,我甚至不知道如何使用scanf,因为它在像这样的家庭作业之外非常无用。我用你使用的scanfgets替换了fgets,它是gets的安全哥哥。Don't use gets。注意atoi是一个简单的字符串到int的转换函数。因为我们刚刚为students数组定义了一个最大大小,所以我们需要强制执行该限制,这样我们就不会在该数组的边界之外读取:

int n = atoi(line);
    if (n <= 0 || n > MAX_STUDENTS) {
        printf("student count %d out of range (0, %d]\n", n, MAX_STUDENTS);
        return 1;
    }

这里是一个最终的解决方案,应该让你开始:

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

#define MAX_NAME_LEN 128
#define MAX_STUDENTS 32

typedef struct employee{
    int code;
    char name[MAX_NAME_LEN];
} emp;

int main () {
    char line[MAX_NAME_LEN];
    emp em[MAX_STUDENTS];
    printf ("Number of entries: ");
    fgets(line, MAX_NAME_LEN, stdin);

    // fgets keeps newline, so remove it.
    line[strcspn(line, "\n")] = '\0';
    int n = atoi(line);
    if (n <= 0 || n > MAX_STUDENTS) {
        printf("student count %d out of range (0, %d]\n", n, MAX_STUDENTS);
        return 1;
    }

    int i;
    for (i=0;i<n;i++){
        printf ("Enter name: ");
        fgets(em[i].name, MAX_NAME_LEN, stdin);
        em[i].name[strcspn(em[i].name, "\n")] = '\0';
    }

    return 0;
}

请注意,这个问题的一个更通用的解决方案(不包括使用这些MAX_*常量)将通过malloccalloc使用动态内存分配。这种解决方案将允许学生的数量和他们名字的长度仅受系统上可用内存量的限制。但是,根据作业来猜测,你还没有谈到那个主题。

li9yvcax

li9yvcax2#

在循环中

for (i=0;i<n;i++){ 
    printf ("Enter name: "); 
    gets(em[i].name); 
}

你似乎在使用em,就好像它是一个n元素的数组。但是,它不是数组。它只是一个单一的元素。
要解决这个问题,您可以将em定义为variable-length array,如下所示:

int main( void )
{
    int n;
    printf("Number of entries: ");
    scanf("%d", &n);
    emp em[n];

    [...]

然而,并非所有编译器都支持可变长度数组,ISO C标准也不要求编译器支持它们。
或者,你可以定义一个固定长度的数组,像这样:

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

#define MAX_ENTRIES 20

[...]

int main( void )
{
    emp em[MAX_ENTRIES];
    int n;
    printf("Number of entries: ");
    scanf("%d", &n);

    //verify that number of entries is not so high that it would
    //overflow the buffer
    if ( n > MAX_ENTRIES )
    {
        printf( "Too many entries!\n" );
        exit( EXIT_FAILURE );
    }

    [...]

此解决方案适用于所有编译器,因为它不要求编译器支持可变长度数组。
另一个问题是,在行

gets(em[i].name);

您有责任确保有足够的内存来存储字符串。在程序中,没有足够的内存来存储任何长度的字符串,因为您甚至没有为字符串的终止空字符提供足够的空间。
在宣言中

typedef struct employee{ 
    int code; 
    char name[]; 
} emp;

成员nameflexible array member。如果你决定使用灵活数组成员,那么你有责任确保在对象之后立即有额外的可用内存来存储灵活数组成员的数据。在你的程序中,你不会这样做。您可能一开始就不想使用灵活的数组成员,因此我不会进一步解释如何使用它。
为了确保name有足够的空间来存储字符串,您可以指定为存储字符串保留的固定字符数,例如:

typedef struct employee {
    int code;
    char name[100];
} emp;

但是,如果用户输入的字符超过100个,那么程序将overflow数组name,这将调用undefined behavior。这意味着您的程序可能会崩溃或以其他方式行为不当。
为了防止这种情况,我建议您使用函数fgets而不是gets。但是请注意,与gets不同,fgets会将换行符写入字符串中,因此您可能希望将其删除。
另一个问题是

scanf ("%d", &n);

将在输入流上留下换行符,这将导致下一次调用gets/fgets读取一个空行,这不是您想要的。因此,您必须丢弃这个换行符(以及该行上的任何其他剩余字符),例如:

int c;

do
{
    c = getchar();

} while ( c != '\n' && c != EOF );

下面是整个程序,它不使用可变长度数组:

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

#define MAX_ENTRIES 20

typedef struct employee
{
    int code;
    char name[100];
} emp;

int main( void )
{
    emp em[MAX_ENTRIES];
    int n;
    int c;

    //read number of entries from user
    printf( "Number of entries: " );
    scanf( "%d", &n );

    //discard remainder of line
    do
    {
        c = getchar();

    } while ( c != '\n' && c != EOF );

    //verify that number of entries is not so high that it would
    //overflow the buffer
    if ( n > MAX_ENTRIES )
    {
        printf( "Too many entries!\n" );
        exit( EXIT_FAILURE );
    }

    //read individual entries from user
    for ( int i=0; i < n; i++ )
    {
        char *p;

        printf( "Enter name: " );
        fgets( em[i].name, sizeof em[i].name, stdin );

        //attempt to find the newline character
        p = strchr( em[i].name, '\n' );
        if ( p == NULL )
        {
            //since we found no newline character, we must
            //assume that the line was too long to fit into
            //the memory buffer
            printf( "Line was too long to fit into the buffer!\n" );
            exit( EXIT_FAILURE );
        }

        //remove the newline character by overwriting it with a
        //null character
        *p = '\0';
    }

    //print back the stored strings
    printf( "\nStored the following strings:\n" );
    for ( int i=0; i < n; i++ )
    {
        printf( "%s\n", em[i].name );
    }

    return EXIT_SUCCESS;
}

此程序具有以下行为:

Number of entries: 5
Enter name: Mike
Enter name: Alice
Enter name: Charlie
Enter name: Bob 
Enter name: Jimmy

Stored the following strings:
Mike
Alice
Charlie
Bob
Jimmy

但是,我通常不建议使用scanf进行用户输入,因为对于基于行的用户输入,它的行为方式不直观。如前所述,它总是在缓冲区中留下换行符,可能还有其他一些字符。每当调用scanf时,总是要删除它们,这是一件痛苦的事情。另外,在使用fgets时,总是要检查换行符以验证是否读入了整行,并删除换行符,这是一件痛苦的事情。
出于这个原因,我建议您编写自己的函数来处理所有这些。在下面的代码中,我将这些函数称为get_int_from_userget_line_from_user。我设计这些函数的方式是,它们还验证输入,如果输入无效,则重新提示用户。
请注意,使用这些函数会使函数main更短更简单:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>

int get_int_from_user( const char prompt[] );
void get_line_from_user( const char prompt[], char buffer[], int buffer_size );

#define MAX_ENTRIES 20

typedef struct employee
{
    int code;
    char name[100];
} emp;

int main( void )
{
    emp em[MAX_ENTRIES];
    int n;

    //read number of entries from user
    n = get_int_from_user( "Number of entries: " );

    //verify that number of entries is not so high that it would
    //overflow the buffer
    if ( n > MAX_ENTRIES )
    {
        printf( "Too many entries!\n" );
        exit( EXIT_FAILURE );
    }

    //read individual entries from user
    for ( int i=0; i < n; i++ )
    {
        get_line_from_user(
            "Enter name: ",
            em[i].name, sizeof em[i].name
        );
    }

    //print back the stored strings
    printf( "\nStored the following strings:\n" );
    for ( int i=0; i < n; i++ )
    {
        printf( "%s\n", em[i].name );
    }

    return EXIT_SUCCESS;
}

//This function will attempt to read one integer from the user. If
//the input is invalid, it will automatically reprompt the user,
//until the input is valid.
int get_int_from_user( const char prompt[] )
{
    //loop forever until user enters a valid number
    for (;;)
    {
        char buffer[1024], *p;
        long l;

        //prompt user for input
        fputs( prompt, stdout );

        //get one line of input from input stream
        if ( fgets( buffer, sizeof buffer, stdin ) == NULL )
        {
            fprintf( stderr, "Unrecoverable input error!\n" );
            exit( EXIT_FAILURE );
        }

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small)
        if ( strchr( buffer, '\n' ) == NULL && !feof( stdin ) )
        {
            int c;

            printf( "Line input was too long!\n" );

            //discard remainder of line
            do
            {
                c = getchar();

                if ( c == EOF )
                {
                    fprintf( stderr, "Unrecoverable error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

            } while ( c != '\n' );

            continue;
        }

        //attempt to convert string to number
        errno = 0;
        l = strtol( buffer, &p, 10 );
        if ( p == buffer )
        {
            printf( "Error converting string to number!\n" );
            continue;
        }

        //make sure that number is representable as an "int"
        if ( errno == ERANGE || l < INT_MIN || l > INT_MAX )
        {
            printf( "Number out of range error!\n" );
            continue;
        }

        //make sure that remainder of line contains only whitespace,
        //so that input such as "6abc" gets rejected
        for ( ; *p != '\0'; p++ )
        {
            if ( !isspace( (unsigned char)*p ) )
            {
                printf( "Unexpected input encountered!\n" );

                //cannot use `continue` here, because that would go to
                //the next iteration of the innermost loop, but we
                //want to go to the next iteration of the outer loop
                goto continue_outer_loop;
            }
        }

        return l;

    continue_outer_loop:
        continue;
    }
}

//This function will read exactly one line of input from the
//user. It will remove the newline character, if it exists. If
//the line is too long to fit in the buffer, then the function
//will automatically reprompt the user for input. On failure,
//the function will never return, but will print an error
//message and call "exit" instead.
void get_line_from_user( const char prompt[], char buffer[], int buffer_size )
{
    for (;;) //infinite loop, equivalent to while(1)
    {
        char *p;

        //prompt user for input
        fputs( prompt, stdout );

        //attempt to read one line of input
        if ( fgets( buffer, buffer_size, stdin ) == NULL )
        {
            printf( "Error reading from input!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to find newline character
        p = strchr( buffer, '\n' );

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small to store the entire line)
        if ( p == NULL )
        {
            int c;

            //a missing newline character is ok if the next
            //character is a newline character or if we have
            //reached end-of-file (for example if the input is
            //being piped from a file or if the user enters
            //end-of-file in the terminal itself)
            if ( (c=getchar()) != '\n' && !feof(stdin) )
            {
                if ( ferror(stdin) )
                {
                    printf( "Error reading from input!\n" );
                    exit( EXIT_FAILURE );
                }

                printf( "Input was too long to fit in buffer!\n" );

                //discard remainder of line
                do
                {
                    c = getchar();

                    if ( ferror(stdin) )
                    {
                        printf( "Error reading from input!\n" );
                        exit( EXIT_FAILURE );
                    }

                } while ( c != '\n' && c != EOF );

                //reprompt user for input by restarting loop
                continue;
            }
        }
        else
        {
            //remove newline character by overwriting it with
            //null character
            *p = '\0';
        }

        //input was ok, so break out of loop
        break;
    }
}

下面是程序行为的一个示例:

Number of entries: abc
Error converting string to number!
Number of entries: 6abc
Unexpected input encountered!
Number of entries: 5
Enter name: Mike
Enter name: Alice
Enter name: This is a very long line that is too long to fit into the buffer. This is a very long line that is too long to fit into the buffer.
Input was too long to fit in buffer!
Enter name: Charlie
Enter name: Bob
Enter name: Jimmy

Stored the following strings:
Mike
Alice
Charlie
Bob
Jimmy

正如您所看到的,当输入无效的输入时,该输入被丢弃,并重新提示用户输入。

相关问题