| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 4543 人关注过本帖, 6 人收藏
标题:【解剖麻雀】通过一道小型课题解答一些常见问题
只看楼主 加入收藏
yahwei
Rank: 7Rank: 7Rank: 7
来 自:湖~
等 级:黑侠
威 望:3
帖 子:145
专家分:644
注 册:2011-11-10
收藏
得分:15 
好细致,好耐心……

[qq]949654600[/qq]
2014-12-29 20:19
c语言总虐我
Rank: 2
等 级:论坛游民
帖 子:112
专家分:66
注 册:2014-11-22
收藏
得分:15 
我是第一个看到的~~~
Q-Q

连渣都不是——5.8
要拜雍正,专治八阿哥  b u g——6.27
2014-12-30 00:28
TonyDeng
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:贵宾
威 望:304
帖 子:25859
专家分:48889
注 册:2011-6-22
收藏
得分:0 
以下是引用c语言总虐我在2014-12-30 00:28:57的发言:

我是第一个看到的~~~
Q-Q

这是连载。你已经有现成可运行的全部源代码和项目文件,可以运行对照着看,也可以尝试修改测试。有什么问题,可以在这里问。还有你隐藏了的几个需求,也可以发出来思考一下怎么做,看我是不是已经留下了实现的途径。

授人以渔,不授人以鱼。
2014-12-30 16:29
TonyDeng
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:贵宾
威 望:304
帖 子:25859
专家分:48889
注 册:2011-6-22
收藏
得分:0 
现在继续看学生模块是怎么实现的。这个模块,分两个文件,分别是头(.h)和实现部分(.cpp),具体代码如下:

Student.h
程序代码:
#pragma once

// 学生数据结构
struct StudentItem
{
    char   Code[7];         // 编码
    char   Name[21];        // 姓名
    int    Sex;             // 性别
    int    SchoolCode;      // 学院编码
    double Scores[10];      // 课程成绩
};

// 学生数据表结构
struct StudentInfo
{
    size_t Count;
    StudentItem Data[100];
};
extern StudentInfo Students;

// 从磁盘文件载入性别编码方案
bool LoadSex(const char* fileName);

// 从磁盘文件载入学生数据
bool LoadStudents(const char* fileName, StudentInfo* students);

// 列出学生明细清单
void ListStudents(const StudentInfo* students, const char* outputFileName, bool outputAverage);

// 按姓名排序
void StudentsSortByName(StudentInfo* students);

// 按平均成绩排序
void StudentsSortByScore(StudentInfo* students);

 
Student.cpp
程序代码:
#include <Windows.h>
#include <stdio.h>
#include <string.h>
#include "MyTools.h"
#include "School.h"
#include "Student.h"

// 性别数据结构及数据
struct SexItem
{
    int  Code;          // 编码
    char Name[3];       // 名称
} Sex[2];

extern CollegeInfo Colleges;

// 从磁盘文件载入性别编码方案
bool LoadSex(const char* fileName)
{
    FILE* file;

    if (fopen_s(&file, fileName, "rt") != 0)
    {
        HLOCAL message = GetSystemErrorMessageA(GetLastError());
        if (message != NULL)
        {
            printf_s("文件%s无法打开: %s\n", fileName, message);
            LocalFree(message);
        }
        return false;
    }

    for (size_t index = 0; (index < _countof(Sex)) && (fscanf_s(file, "%d %s\n", &(Sex[index].Code), Sex[index].Name, _countof(Sex[index].Name))) == 2; ++index)
    {
        ;
    }

    fclose(file);

    return true;
};

// 从磁盘文件载入学生数据
bool LoadStudents(const char* fileName, StudentInfo* students)
{
    FILE* file;

    if (fopen_s(&file, fileName, "rt") != 0)
    {
        HLOCAL message = GetSystemErrorMessageA(GetLastError());
        if (message != NULL)
        {
            printf_s("文件%s无法打开: %s\n", fileName, message);
            LocalFree(message);
        }
        return false;
    }

    for (size_t index = 0; ; ++index)
    {
        if (fscanf_s(file, "%s", students->Data[index].Code, _countof(students->Data[index].Code)) != 1)
        {
            break;
        }
        if (fscanf_s(file, "%s", students->Data[index].Name, _countof(students->Data[index].Name)) != 1)
        {
            break;
        }
        if (fscanf_s(file, "%d", &(students->Data[index].Sex)) != 1)
        {
            break;
        }
        if (fscanf_s(file, "%d", &(students->Data[index].SchoolCode)) != 1)
        {
            break;
        }
        bool success = true;
        for (size_t subjectIndex = 0; subjectIndex < _countof(students->Data[index].Scores); ++subjectIndex)
        {
            if (fscanf_s(file, "%lf", &(students->Data[index].Scores[subjectIndex])) != 1)
            {
                success = false;
                break;
            }
        }
        if (!success)
        {
            break;
        }
        ++(students->Count);
    }

    fclose(file);

    return true;
}

// 求指定学生的平均成绩
double AverageScore(const StudentItem* student)
{
    double total = 0.0;
    for (size_t index = 0; index < _countof(student->Scores); ++index)
    {
        total += student->Scores[index];
    }

    return total / _countof(student->Scores);
}

// 列出学生明细清单
void ListStudents(const StudentInfo* students, const char* outputFileName, bool outputAverage)
{
    FILE* file = stdout;        // 默认向控制台标准设备输出结果

    if (outputFileName != NULL)
    {
        if (fopen_s(&file, outputFileName, "wt") != 0)
        {
            HLOCAL message = GetSystemErrorMessageA(GetLastError());
            if (message != NULL)
            {
                printf_s("输出文件%s无法建立: %s,改向标准设备输出.\n", outputFileName, message);
                LocalFree(message);
                file = stdout;
            }
        }
    }

    for (size_t index = 0; index < students->Count; ++index)
    {
        fprintf_s(file, "%s, ", students->Data[index].Code);
        fprintf_s(file, "%s, ", students->Data[index].Name);
        fprintf_s(file, "%s, ", Sex[students->Data[index].Sex].Name);
        fprintf_s(file, "%s, ", Colleges.Data[students->Data[index].SchoolCode - 1].Name);     // 注:这里假定学院数据以编码与数组下标挂钩,否则应编写检索函数
        for (size_t subjectIndex = 0; subjectIndex < _countof(students->Data[index].Scores); ++subjectIndex)
        {
            fprintf_s(file, "%.0f ", students->Data[index].Scores[subjectIndex]);
        }
        if (outputAverage)
        {
            fprintf_s(file, "[%6.2f]\n", AverageScore(&(students->Data[index])));
        }
        else
        {
            fputc('\n', file);
        }
    }

    if (file != stdout)
    {
        fclose(file);
    }
}

// 复制一个学生数据
StudentItem CopyStudent(StudentItem* source)
{
    StudentItem target;

    strcpy_s(target.Code, source->Code);
    strcpy_s(target.Name, source->Name);
    target.Sex = source->Sex;
    target.SchoolCode = source->SchoolCode;
    memcpy_s(target.Scores, sizeof(target.Scores), source->Scores, sizeof(source->Scores));

    return target;
}

// 交换两个学生的变量内容
void SwapStudent(StudentItem* s1, StudentItem* s2)
{
    StudentItem temp = CopyStudent(s2);
    *s2 = CopyStudent(s1);
    *s1 = CopyStudent(&temp);
}

// 按姓名排序
void StudentsSortByName(StudentInfo* students)
{
    // 用冒泡法进行排序
    for (size_t i = 0; i < students->Count - 1; ++i)
    {
        for (size_t j = i; j < students->Count; ++j)
        {
            if (strcmp(students->Data[i].Name, students->Data[j].Name) < 0)     // 升序
            {
                SwapStudent(&(students->Data[i]), &(students->Data[j]));
            }
        }
    }
}

// 按平均成绩排序
void StudentsSortByScore(StudentInfo* students)
{
    // 用冒泡法进行排序
    for (size_t i = 0; i < students->Count - 1; ++i)
    {
        for (size_t j = i; j < students->Count; ++j)
        {
            if (AverageScore(&(students->Data[i])) > AverageScore(&(students->Data[j])))    // 降序
            {
                SwapStudent(&(students->Data[i]), &(students->Data[j]));
            }
        }
    }
}




[ 本帖最后由 TonyDeng 于 2014-12-30 17:55 编辑 ]

授人以渔,不授人以鱼。
2014-12-30 17:54
Alar30
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:10
帖 子:988
专家分:1627
注 册:2009-9-8
收藏
得分:0 
真心好
谢谢指教了
2014-12-30 18:16
c语言总虐我
Rank: 2
等 级:论坛游民
帖 子:112
专家分:66
注 册:2014-11-22
收藏
得分:0 
我们刚刚下午跟晚上都上课,继续做剩下的三个问题!然后还要在打开文件前加密码!不过加密码那部分程序她已经给了!然后下星期要答辩!四个问题做成PPT,她抽出一个让我们讲!这道题是四个问题里的一个!我向来运气不好T_T

连渣都不是——5.8
要拜雍正,专治八阿哥  b u g——6.27
2014-12-30 18:18
TonyDeng
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:贵宾
威 望:304
帖 子:25859
专家分:48889
注 册:2011-6-22
收藏
得分:0 
Student的这个程序的核心模块,我慢慢分析。数据结构的问题,前面在School模块中已经讲了,两者是完全一样的结构和用法,不再重复。

现在先讲结构体数组的排序问题,正如我们平常做练习那样,排序首要是数据的复制,普通内置数据类型的复制很简单,用=算符就可以了,但结构体与普通的内置数据类型不同(在C中它是一块复合数据区,在C++中则不单是数据区,还包括函数和方法代码段,实际上的C++的类就是从C的结构体发展出来的,原本命名为“带类的C”,C++的关键词class和struct实际上没有区别,只是在默认的访问可见性上相反而已,两者是同义词),所以对这样的数据/代码区,存在隐藏字段,是不能简单用memcpy()这类函数复制的。在C和C++的编程规范中,以及C标准的解释者,都告诫我们不要试图一揽子复制对象,原因也在这里。对象(结构体就是对象的一种)的复制,必须逐个字段复制,就如字符串的复制必须逐个字符拷贝一样!

所以,看看14楼的代码实现,我专门写一个复制结构体的函数,就是逐个字段复制的,这繁琐动作不能省。之所以把这个功能拆分为函数,除了这个功能要大量使用之外,还因为要给面向对象编程思想作埋伏,这种复制函数,在C++中被写成运算符重载函数,代码就是这样的。在面向过程的编程之中,完全可以按面向对象的思想组织代码,就是这种方式。只要你习惯了这种拆分函数的思维,将来迁移到面向对象的语言时,会觉得顺理成章,否则很难扭转过来,从而抵触面向对象思维,殊不知两者其实是可以不冲突的。


授人以渔,不授人以鱼。
2014-12-30 18:46
TonyDeng
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:贵宾
威 望:304
帖 子:25859
专家分:48889
注 册:2011-6-22
收藏
得分:0 
现在谈一谈代码的可读性和自说明的问题。

留意一下14楼的代码,我专门写了一个交换数据的函数SwapStudent()。这也是我们平时写过的东西了,原理都懂得,也都会写,但我极少看见有人为此提炼为一个函数的,都是内嵌在逻辑代码之中,搅乱阅读者的大脑:本来读一个冒泡排序的算法代码,大脑就是要反映一个数据交换的步骤,这下好,一到这里,就跳出另外一个算法,让你思考一番才看出那几行或十几行是干什么的,就算不干扰、不打断原本的思路,这展开了的代码也把函数体拉长,让人未看先怕。把交换数据这一个简单动作提取出来,起一个自我说明干什么的名字,阅读者就不用再看具体如何交换的,除非他怀疑你的交换代码写错了,才有必要去看,否则你假定他没错就可以了,集中注意力于当前代码段的逻辑。swap就是交换的意思,这就是代码的自说明,不用像很多人那样,到处是密密麻麻的注释。基本的英文还是要懂的。

你看我两个冒泡排序的算法,一眼看下去,都是非常典型和一致的,除了比较行不同,其余完全相同。从这里也可以看到某种抽象的东西,看得出,你就可能会想更进一步,排序函数只写一个行不行?你有这种意识,就明白C++的模板和C#的泛型到底是为何而来;搞不明白,就一辈子重复劳动永远敲大同小异的代码。当我们要修改交换算法的时候,只要改swap()函数的具体实现就可以了,逻辑代码是根本不用碰的,想象一下你把那3行代码嵌入到这两个排序代码中,将来要修改得花多少功夫?更烦的是你未必找得全所有用到这些代码的地方!我这里是一层一层的,除了交换函数,还提取了数据实体复制函数,在交换代码中,也是一眼看出是在从事复制动作。事实上,这两个函数,稍微分析一下就知道,必定是经常用的,所以在我写的时候,根本不用想,首先就写出这些性质的函数,不管当前用不用得着,先写了出来,必定有用得着的时候!

授人以渔,不授人以鱼。
2014-12-30 19:20
c语言总虐我
Rank: 2
等 级:论坛游民
帖 子:112
专家分:66
注 册:2014-11-22
收藏
得分:0 
我还没看完呢......

连渣都不是——5.8
要拜雍正,专治八阿哥  b u g——6.27
2014-12-31 01:19
c语言总虐我
Rank: 2
等 级:论坛游民
帖 子:112
专家分:66
注 册:2014-11-22
收藏
得分:0 
回复 2楼 TonyDeng
读四遍了,真心看不明白这种高大上的词汇。。。

连渣都不是——5.8
要拜雍正,专治八阿哥  b u g——6.27
2015-01-03 04:26
快速回复:【解剖麻雀】通过一道小型课题解答一些常见问题
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.048082 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved