回 帖 发 新 帖 刷新版面

主题:求助:读文件错误!

不好意思,我又来求助了!还是同一个程序。
   读文件从查询余额的函数起开始出现错误!请教下,读文件发生错误的原因。
   还有就是我单独存放了用户个数在文件accountnumber中,但是重新打开时,已开户的信息无法正常验证啊。
    谢谢您看完本帖!

回复列表 (共2个回复)

沙发

第191行: fread(&curaccount,sizeof(ACCOUNT),1,fp);
这里恐怕有问题。这里的fread直接影响到第200行,post=ftell(fp);,这个post的值。
如果post的值不正确,查询余额时,第274行fseek(fp,post,0);,就会转到一个错误的文件位置,从而导致读取失败。
程序重新运行时,如果不注册新账户的话,就不会调用New函数,于是就不会读取accountnumber.txt这个文件了,accountnum的值始终为零。这样一来,登陆时第186行:for(i=0;i<accountnum;i++),这个循环实际上什么也不会做,于是始终会验证失败。



其实这些都是表面原因。楼主如果觉得自己已经陷入困境,不妨先停下来,把下面这两样学一学。
1. 如何调试程序。
2. 如何有效的组织程序代码。
学会这两点之后,相信以后遇到类似问题,不必发帖也可以自己轻松解决了。



1. 调试程序。
这里主要指用一些辅助工具来进行调试。例如Visual C++,就自带了调试功能。使用Visual C++,代码编译成功后,按F5键(具体按哪个键是可以自己设置的,这里说的是默认的键,下同),就可以以调试方式运行。把编辑光标移动到代码中的任何位置,按F9键,可以设置“断点”,当程序在调试方式运行的时候,遇到断点会自动暂停下来。
程序暂停后,可以按Alt+3,弹出Watch窗口,这个窗口可以查看各个变量的值。如果此时按F5,则程序继续运行,直到结束或者遇到下一个断点。
程序暂停时,如果按F10,就表示执行一行代码,在下一行代码处再次暂停。如果按F11,也表示执行一行代码,但如果这一行代码是调用一个函数,则会进入到函数内部,在函数内部的第一行代码处再次暂停。

说得比较复杂,但其实还是很简单的,用楼主的程序来做例子吧:
首先我们知道,查询余额的时候会遇到文件读取失败。也就是说程序会执行到第276行:printf("对不起,读文件错误!\n");
光标移动到第276行,按F9设置断点。然后按F5,以调试方式运行程序。程序运行后,按照正常的方式操作(创建帐户、登录、查询),然后程序会暂停下来(执行到第276行,遇到我们所设置的断点了)。
这时我们把鼠标指向各个变量,可以看到它们在程序暂停之时的值(也可以按Alt+3打开Watch窗口,同时查看多个变量的值)。其中post这个值为84,这与我们所希望的不一样。(因为只有一个账户,所以应该从第0个字节开始读取,也就是说post的值应该是0才对)。所以,post这个变量的值有错误。
搜索发现,post这个变量只在一个地方进行了赋值。这就是第200行,post=ftell(fp);。因为我们知道post的值错了,也就是说ftell返回的值并非我们所想。即是说文件读写指针操作不正确。为此,只要检查前面所有的fseek和fread就好。如此一来,很容易就发现第191行,多了一个fread。
基本上,顺藤摸瓜,慢慢地就找到问题的所在了。有了调试工具,就会比纯手工的方便不少。其实调试工具也有很多更加复杂的功能,楼主可以自己摸索。

2. 组织您的代码。
调试工具再怎么厉害,那也是治标不治本。让自己的代码更有组织,设计更合理一些,这样才不容易出错。即使真的出现错误,查找和修改也容易一些。
以您的程序来说,几乎每个函数都在操作文件。从甲、乙两个函数保存进去,又从丙、丁函数读取出来,其中又随意的使用着全局变量,这样的代码结构是很糟糕的,非常容易出现错误。但如果修改一下,变成这个样子,可能就好得多了。

int load_account(ACCOUNT* acc, int max_count) {
  // 从文件读取(最多读取max_account个),返回成功读取的个数。失败返回-1
}

void save_account(ACCOUNT* acc, int count) {
  // 保存到文件。总共count个ACCOUNT
}

int main() {
    int count;
    ACCOUNT acc[MAX_ACCOUNT];
    int i; // 菜单选择
    int n = -1; // 当前登录的用户是第几个用户

    count = load_account(acc, MAX_ACCOUNT);
    if (count < 0) {
        // 读取失败
        return -1;
    }

    do {
        i = menu();
        switch (i) {
        case 1: // 新建。如果新建成功,保存
           if (new(acc, count)) {
               ++count;
               n = count - 1; // 新建的账户默认为登陆账户
               save_account(acc, count);
           }
           break;
        case 2: // 登陆。返回登陆的是第几个账户。如果登陆失败,返回-1。登陆不需要保存
           n = login(acc, count);
           break;
        case 3: // 改密码
           if (n < 0 || n >= count) {
              // 没有登录
              break;
           }
           if (change_password(acc, count, n)) { // 如果修改成功,保存
               save_account(acc, count);
           }
           break;
        // 以下省略。查询余额是不需要保存的,而存款、取款则需要保存。
        }
    } while (i != 7);

    // 程序退出的时候记得保存
    save_account(acc, count);
    return 0;
}

所有的保存都使用同一个函数。所有的读取都使用同一个函数(其实只有程序开始时才需要读取)。这样就不会到处都是fseek, fread, fwrite了。代码看起来会干净很多。因为程序启动就立即读取,所以也不会像楼主的代码那样,在查询余额时accountnumber文件还没有被读取的情况。
不使用全局变量,好处是容易明确责任——如果全局变量的值错了,很难知道到底是哪个函数把它改错的,但局部变量就没有这个问题。

板凳


    首先感谢您在白忙中给予的帮助与教导!
    的确,我的程序中出现了很多次文件调用与几个全局变量,把整个程序变得冗长而不便于查改。调试工具我在用,但是遇到文件就不知道查看什么了,陷入僵局。因为急于写代码,所以思考得不够多,整个代码的组织也存在很严重的问题,混乱不够清晰。终于理解前辈们所说的构建程序结构框架的时间比编码调试的时间花得要多。
    我知道在调试和组织上还有很长的路要走吧。把这么水的帖子发上来,我也很惭愧!总之,真诚的感谢楼上的帮助!
    谢谢![em2]

我来回复

您尚未登录,请登录后再回复。点此登录或注册