如何使用读写方法以C语言以普通文本格式读取二进制文件?

问题描述

我要从用户获取输入,并将该输入以二进制格式写入test.bin文件中。但事实是我想以普通文本形式读取文件,而不是以存储文件的二进制格式。

我已经写了下面的代码,但是当我阅读它时,它不会以普通文本显示数据。这个你能帮我吗。我是C语言的新手。预先感谢。

public class MyDailogFragment extends DialogFragment {

    DatabaseReference dailog;
    String Id;

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {

        Bundle bundle = getArguments();
        final String name=  bundle.getString("Name","");
        Id = bundle.getString("Id","");

        AlertDialog.Builder alertDialog = new AlertDialog.Builder(getActivity());
        alertDialog.setMessage("Delete "+name+ " ?")
                .setPositiveButton("Delete",new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog,int which) {
                        Toast.makeText(getActivity(),""+Id,Toast.LENGTH_SHORT).show();
                        dailog = FirebaseDatabase.getInstance().getReference("Faculty").child(Id);
                        dailog.addListenerForSingleValueEvent(new ValueEventListener() {
                            @Override
                            public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
                                dataSnapshot.getRef().removeValue();
                                Toast.makeText(getActivity(),name+" Deleted Successfully ",Toast.LENGTH_SHORT).show();
                            }

                            @Override
                            public void onCancelled(@NonNull DatabaseError databaseError) {

                            }
                        });
                    }
                })
                .setNegativeButton("Cancel","nothing",Toast.LENGTH_SHORT).show();
                    }
                });

        return alertDialog.create();
    }
}

这是我从上面的代码中得到的输出Output

解决方法

如何处理?

这个问题有很多。显然,简单的答案只是选择一个位置,您将为添加到您的藏书中的每个新学生分配一个位置,但是我担心答案远远不能为您提供任何帮助。

所以让我们修复问题-从头开始。从代码的角度讲,我将很快从描述的角度出发。

第一个明显的问题是,15字符不足以处理所有合理预期的名称。至少要加倍(但要注意,您的结构将包含静态数组,并且每个结构都需要存储每个数组的完整大小)。合理的开始是,消除 Magic Numbers 和硬编码的文件名可能是:>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define MAXGRP   16             /* if you need a constant,#define one (or more) */
#define MAXNM    32             /* 15 is inadequate for many names */
#define MAXC   1024

typedef struct student {        /* typedef for convenience */
    char name[MAXNM];
    char lastname[MAXNM];
    int course;
    char group[MAXGRP];
} student;

接下来,由于您将为输入的每个学生读取字符串和整数,因此继续编写一个函数来读取和验证字符串和整数输入。您可以提供一个可选提示,以便在每次输入之前输出(或在其输入处提供NULL,并且不输出任何提示)。要读取字符串或整数输入,请使用fgets()读取用户输入的完整行。因此,stdin中保留的内容不会影响后续输入。

使用临时缓冲区(数组)保存用户输入(不要忽略缓冲区大小),然后在复制字符串之前验证字符串是否适合您的结构:

char *getstring (char *str,size_t maxlen,char *prompt)
{
    char buf[MAXC];                         /* buffer to read each user-input */
    size_t len;                             /* length of string read */
    
    for (;;) {                              /* loop continually */
        if (prompt)                         /* display prompt if not NULL */
            fputs (prompt,stdout);
        
        if (!fgets (buf,MAXC,stdin)) {    /* get string,handle manual EOF */
            puts ("(input canceled)");
            return NULL;
        }
        buf[(len = strcspn(buf,"\n"))] = 0;     /* trim \n,save length */
        
        if (len < maxlen)                   /* validate string fits */
            break;
        
        fputs ("error: input length exceeds storage.\n",stderr);
    }
    
    return memcpy (str,buf,len + 1);      /* copy buf to str,return ptr to str */
}

对于整数输入,请执行完全相同的操作,除了这次,尝试使用int转换为sscanf(),并且在匹配失败的情况下,因为使用fgets()进行输入时,在下一次尝试读取之前,无需清除行尾以删除有问题的字符,例如

int getint (int *val,char *prompt)
{
    char buf[MAXC];                         /* buffer to read each user-input */
    
    for (;;) {                              /* loop continually */
        if (prompt)                         /* display prompt if not NULL */
            fputs (prompt,stdout);
    
        if (!fgets (buf,handle manual EOF */
            puts ("(input canceled)");
            return 0;
        }
        
        if (sscanf (buf,"%d",val) == 1)   /* convert to int / validate */
            break;
        
        fputs ("error: invalid integer input.\n",stderr);
    }
    
    return 1;       /* return success */
}

现在,让我们将您的getstudent()函数更改为仅负责填充struct student并稍后将所有内存分配移至addstudent()函数,仅当学生数据成功完成后才分配内存存储在临时结构中。您对getstudent()的读取将使用指向临时结构的指针来填充,并在成功时返回指向该结构的指针,而在失败时返回指向该结构的指针,例如:

NULL

注意:您所有可能成功或失败的功能都应返回可用于验证该功能成功或失败的类型)

通过将所有验证代码卸载到student *getstudent (student *s) { putchar ('\n'); /* provide empty line before each set of input */ if (!getstring (s->name,MAXNM,"Enter name: ")) /* read/validate first */ return NULL; if (!getstring (s->lastname,"Enter lastname: ")) /* read/validate last */ return NULL; if (!getint (&s->course,"Enter course: ")) /* read/validate course */ return NULL; if (!getstring (s->group,MAXGRP,"Enter group: ")) /* read/validate group */ return NULL; return s; /* return pointer to filled struct */ } getstring()函数中,可以使逻辑更加清晰。

现在,getint()函数仅需要调用addstudent(),如果成功,则分配(getstudent())内存并将新学生存储在集合中的该新内存中。通过指向总人数的指针,然后递增该地址处的值,例如

,它还将更新您的藏书中的学生总数。
realloc()

您的showdata函数可以大大简化,以简单地遍历集合中的每个结构,以您选择的格式输出数据,例如:

student *addstudent (student **students,size_t *nstudents)
{
    student s = {.name = ""};       /* declare temporary struct */
    
    if (!getstudent (&s))           /* fill/validate temporary struct */
        return NULL;
    
    /* allocate / validate memory to hold struct */
    void *tmp = realloc (*students,(*nstudents + 1) * sizeof **students);
    if (!tmp) {
        perror ("addstudent-tmp");
        return NULL;
    }
    
    *students = tmp;                /* assign reallocated block to students */
    (*students)[*nstudents] = s;    /* fill new mem with temp struct */
    (*nstudents)++;                 /* increment student count */
    
    return *students;       /* return pointer to students */
}

现在要将学生从文件加载到集合中,您将使用类似于void showdata (student *s,size_t nstudents) { if (!nstudents) { /* if no students,so indicate */ puts ("no student data."); return; } puts ("\nname\tlastname\tcourse\tgroup"); /* output heading */ for (size_t i = 0; i < nstudents; i++) /* loop outputting each student */ printf ("%s\t%-8s\t%d\t%s\n",s[i].name,s[i].lastname,s[i].course,s[i].group); } 的方法,将从文件中读取文件到临时结构,然后仅在成功读取后才从该结构中分配存储空间并将其添加到您的收藏中。


注意,这实际上不是您如何在文件中读写结构数据的方式。为什么?编译器可以在结构的任何成员之间或之后(但不能在第一个成员之前)自由插入填充字节,并且C标准中没有任何内容要求编译器将在何处添加填充。这意味着将一系列构造文件写入二进制文件在编译器之间是完全不可移植的(但出于学习目的,它应在同一台计算机上正常工作-同一编译器至少应在工作上保持一致)在将数据以二进制格式写入之前,将序列化数据,因此您可以以可移植的方式读回相同的数据。


在这一点上,由于填充的潜在差异,您不能将结构数组写入二进制文件,并且不能让另一个编译器读回该文件。

话虽如此,您可以在同一台计算机上使用以下命令将addstudent()的集合读入内存:

struct student

以不可移植的方式将student *loadstudents (int fd,student **students,size_t *nstudents) { for (;;) { /* loop continually reading students from file */ student s; /* temporary struct */ ssize_t nbytes; /* no. of bytes read */ errno = 0; /* reset errno */ nbytes = read (fd,&s,sizeof **students); /* read student into temp struct */ if (nbytes == -1) { /* validate no error */ perror ("loadstudents-read"); return NULL; } if (nbytes != sizeof **students) { /* validate no. of bytes read */ if (nbytes != 0) fputs ("error: less than student no. of bytes read.\n",stderr); break; } /* allocate / validate memory to hold struct */ void *tmp = realloc (*students,(*nstudents + 1) * sizeof **students); if (!tmp) { perror ("loadstudents-tmp"); return NULL; } *students = tmp; /* assign reallocated block to students */ (*students)[*nstudents] = s; /* fill new mem with temp struct */ (*nstudents)++; /* increment student count */ } return *students; /* return pointer to students */ } 的集合以二进制形式写入文件是不重要的。您知道您有多少个学生,而且您知道编译器为每个结构提供的每个结构有多少字节,因此您只需将struct student个字节写入文件,例如

number * sizeof (struct student)

简而言之,这些就是您的功能及其用途。现在,只需编写一个int writestudents (int fd,student *students,size_t nstudents) { ssize_t nbytes = sizeof *students * nstudents; /* no. of bytes to write */ return write (fd,students,nbytes) == nbytes; /* write/validate bytes written */ } 函数即可读取数据文件中已经存在的函数(您将其名称作为main()的自变量,而不必对其进行硬编码-这就是main()是给)。您可以将文件中的任何学生添加到集合中,从而节省了从文件中加载的学生数量,因此您不必担心会再次将其写出,因为您已打开文件int argc,char **argv。 (将其打开O_RDWR,将所有学生读入内存中的集合,关闭文件,然后准备好编写文件,将其打开O_RDONLY,然后将所有学生写出来,会更简单)

从文件中加载所有学生后,您调用O_WRONLY并添加任何您喜欢的其他学生(如果在数据输入提示下生成手册addstudent(),则根本不添加任何学生)

要在末尾写出集合(而不是逐个写出每个新结构),只需写出最后添加的所有新结构,然后关闭文件(检查EOF的返回以捕获写入最后一个结构时不存在任何刷新或流错误),然后释放数据并完成操作。

您可以执行许多种方法,但是一种方法是:

close()

就是这样,您只需将以上所有内容一起复制/粘贴,编译和测试即可。如果您看一下代码,则代码中的大多数附加内容都是 validation (每个用户输入函数和每个分配的返回)。那是强制性的。在代码中为int main (int argc,char **argv) { char *file = NULL,buf[MAXC]; /* pointer to filename & temp buffer (y/n) */ int fd; /* file descriptor */ size_t nstudents = 0,loaded = 0; /* total students,no. loaded from file */ student *students = NULL; /* pointer to students */ mode_t mode = S_IRWXU | S_IRGRP | S_IROTH; /* open mode */ if (argc < 2) { /* validate one argument given for filename */ fprintf (stderr,"error: insufficient arguments provided\n" "usage: %s filename\n",argv[0]); return 1; } file = argv[1]; /* assign filename to pointer */ fd = open (file,O_CREAT | O_APPEND | O_RDWR,mode); /* open file */ if (fd == -1) { perror ("open-file"); return 1; } loadstudents (fd,&students,&loaded); if (errno) return 1; nstudents = loaded; /* assign no. of students loaded (saving loaded) */ if (nstudents) /* if students read from file,output number */ printf ("%zu students read from file: %s\n",nstudents,file); do { /* loop adding students to collection */ if (!addstudent (&students,&nstudents)) break; fputs ("\nenter another student (y/n)? ",stdout); /* prompt to enter more? */ if (!fgets (buf,sizeof buf,stdin)) break; } while (*buf != 'n' && *buf != 'N'); /* while 1st char in buf now 'n/N' */ showdata (students,nstudents); /* output all in students */ if (nstudents - loaded) { /* if new students added since file loaded */ /* write new students added to file */ if (writestudents (fd,students + loaded,nstudents - loaded)) printf ("\n%zu students written to file: %s\n",file); else perror ("writestudents"); } if (close (fd) == -1) /* always validate close after write */ perror ("close-file"); if (nstudents) /* free allocated memory */ free (students); } 键入"a100",看看会发生什么...

您验证每个分配,因为这不是 是否 的问题,而是 何时 分配失败。这就是为什么我最初说过,仅将所有分配移到单个位置可能无法为代码中需要解决的所有问题提供有意义的答案。

使用/输出示例

从没有数据文件开始,让我们添加两个学生并将它们写入您的文件:

course

现在确认已将其写入您的二进制文件:

$ ./bin/student_input dat/student_input.bin

Enter name: John
Enter lastname: Doe
Enter course: 100
Enter group: 1000

enter another student (y/n)? y

Enter name: Mickey
Enter lastname: Mouse
Enter course: 101
Enter group: 1002

enter another student (y/n)? n

name    lastname        course  group
John    Doe             100     1000
Mickey  Mouse           101     1002

2 students written to file: dat/student_input.bin

看起来不错,现在让我们再次运行该程序并添加另一个学生:

$ hexdump -Cv dat/student_input.bin
00000000  4a 6f 68 6e 00 00 00 00  00 00 00 00 00 00 00 00  |John............|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020  44 6f 65 00 00 00 00 00  00 00 00 00 00 00 00 00  |Doe.............|
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040  64 00 00 00 31 30 30 30  00 00 00 00 00 00 00 00  |d...1000........|
00000050  00 00 00 00 4d 69 63 6b  65 79 00 00 00 00 00 00  |....Mickey......|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000070  00 00 00 00 4d 6f 75 73  65 00 00 00 00 00 00 00  |....Mouse.......|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  00 00 00 00 65 00 00 00  31 30 30 32 00 00 00 00  |....e...1002....|
000000a0  00 00 00 00 00 00 00 00                           |........|
000000a8

现在验证您的文件是否包含所有三个文件:

$ ./bin/student_input dat/student_input.bin
2 students read from file: dat/student_input.bin

Enter name: Minnie
Enter lastname: Mouse
Enter course: 101
Enter group: 1004

enter another student (y/n)? n

name    lastname        course  group
John    Doe             100     1000
Mickey  Mouse           101     1002
Minnie  Mouse           101     1004

3 students written to file: dat/student_input.bin

全部在那里。

当然,这样做的结果比原先计划的要长,但实际上没有任何办法可以提供1/2的答案,也不会给您带来与所遇到的一样多的问题(没有一两个问题)。大收获-使用$ hexdump -Cv dat/student_input.bin 00000000 4a 6f 68 6e 00 00 00 00 00 00 00 00 00 00 00 00 |John............| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000020 44 6f 65 00 00 00 00 00 00 00 00 00 00 00 00 00 |Doe.............| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000040 64 00 00 00 31 30 30 30 00 00 00 00 00 00 00 00 |d...1000........| 00000050 00 00 00 00 4d 69 63 6b 65 79 00 00 00 00 00 00 |....Mickey......| 00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000070 00 00 00 00 4d 6f 75 73 65 00 00 00 00 00 00 00 |....Mouse.......| 00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000090 00 00 00 00 65 00 00 00 31 30 30 32 00 00 00 00 |....e...1002....| 000000a0 00 00 00 00 00 00 00 00 4d 69 6e 6e 69 65 00 00 |........Minnie..| 000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000c0 00 00 00 00 00 00 00 00 4d 6f 75 73 65 00 00 00 |........Mouse...| 000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 000000e0 00 00 00 00 00 00 00 00 65 00 00 00 31 30 30 34 |........e...1004| 000000f0 00 00 00 00 00 00 00 00 00 00 00 00 |............| 000000fc (或POSIX fgets())接受所有用户输入,因此getline()中保留的内容不受stdin转换或匹配的限制失败,始终验证,验证,验证,请考虑使用stdio文件流函数而不是低级系统调用(scanf()read等。 ),不要试图将所有数据输入,验证,分配和计数都塞入一个函数中-将其分解为更小,更易于维护的函数-有助于保持逻辑清晰。

仔细研究一下,消化一下,如果还有其他问题,请告诉我。