Halo

A magic place for coding

0%

中山大学初级实训

前言

   中山大学软件学院的大一想必都会经历这样一个初级实训。这个实训主要是完成一个基于命令行的 ** 会议管理系统 **,使用的语言是 C++,含有部分 C11 特性。主要考察的是大家 C++ 的编程知识,难度不算很大,但是需要大家很细心。因为每天只有一次测评的机会,因此拿满分还是有点难度的。本人不是大佬,当时实训横跨了三个周末,debug 找错的也很煎熬,但是最后还是做出来的,在这里也是想分享一些经验,希望能帮助到师弟师妹们。想要代码的直接点左上角去 github 看,或者直接点击 这个,但是还是希望大家能够自己完成。

实训介绍

   实训的开发环境是云平台的 Liunx,这要求大家对 Linux 要有一定的熟悉程度。从得分的角度,内容主要有以下几点:

  • 命令行操作(第一周 TA 检查)
  • makefile 编写(第二周 TA 检查)
  • User、Date、Meeting 三个类的实现(第一周)
  • Storage、AgendaService 两个类的实现(第二周)
  • UI 的实现、加分项(第三周)

   接下来我将分点介绍上面这几个内容,务求让大家多拿分。

命令行操作

   实训使用的环境是 Linux 系统,在里面编译、运行、调试 C++
程序需要使用命令行。很多人一直使用的都是在 Windows 环境下的 IDE 如 DevC++,一下子转到 Linux 下使用命令行会觉得很不习惯也很不方便,但当你熟练运用以后会发现,其实命令行也有它的过人之处。
  TA 检查的时候主要检查大家的文件系统操作,如打开文件(夹)、创建文件(夹)、删除文件(夹)、复制文件(夹)、重命名。一般都不会刁难大家,临急抱佛脚也是可以的,但是如果对 Linux 比较熟悉的话,pass 这个检查也是完全没问题的。这里简单举几个例子:

  • 创建文件夹。创建一个名为 test1 的文件夹
    1
    mkdir test1
  • 打开文件夹。进入刚刚创建好的 test1 文件夹
    1
    cd test1
  • 在 test1 文件夹中创建名为 test.cpp 的文件
    1
    touch test.cpp
  • 查看当前目录下所有文件,如果要查看隐藏文件,参数加上 - a
    1
    2
    ls
    ls -a
  • 用 Vim 或 SublimeText 打开 test.cpp 文件
    1
    2
    vim test.cpp
    sub test.cpp
  • 删除 test1.cpp 文件
    1
    rm test1.cpp
  • 删除文件夹 test1
    1
    rm -r test1

makefile 编写

  Makefile 是 C++ 工程项目里面的一个很有用的工具。当我们需要编译一个工程时,往往会涉及许多文件,在命令行中逐个文件和参数输入效率是很低的,如果我们提前编写好 makefile,每次需要编译运行的时候就输入 make 命令,编译器就会根据文件中制定好的顺序和路径逐个编译并链接文件,最后运行程序,这样就大大地提高了我们的开发效率。
  TA 主要检查对 makefile 语法的理解和使用,然后会让你跑一个提前写好的 makefile,难度基本也不大,提前熟悉一下语法就可以了。这里推荐一个 [博客的教程](http://chuzhiyan.com/2017/03/11/% E4% B8%80% E4% B8% AA% E7% AE%80% E5%8D%95% E7%9A%84Makefile% E6%95%99% E7% A8%8B/)。

数据层实现

   第一阶段是实现数据层的内容,包括了 User、Date、Meeting 三个类,主要是一些属性的 get 和 set 方法,这一部分难度是很小的,写代码的时候注意细节,写完后自己测试一下,基本上可以一次通过测评。
   这里有点难度的是 Date 里面对日期先后的大小比较和日期有效性的判断。日期先后的比较很巧妙地依赖于格式化后字符串的比较。有效性的判断第一是格式的有效性,第二是逻辑有效性。

  • 逻辑有效性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
     bool Date::isValid(const Date &t_date)
    {
    if( t_date.m_year < 1000 || t_date.m_year > 9999 )
    {
    return false;
    }
    else
    {
    if( t_date.m_month <= 0 || t_date.m_month > 12 )
    {
    return false;
    }
    else
    {
    if( t_date.m_day <= 0 || t_date.m_day > daysOfMonthy ( t_date.m_year, t_date.m_month ) )
    {
    return false;
    }
    else
    {
    if( t_date.m_hour < 0 || t_date.m_hour > 23 )
    {
    return false;
    }
    else
    {
    if( t_date.m_minute < 0 || t_date.m_minute > 59 )
    {
    return false;
    }
    }
    }
    }
    }
    return true;
    }
  • 格式有效性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    int flag = 0;
    for( int i = 0; i < t_dateString.size (); i++ )
    {
    if( i == 4 || i == 7 || i == 10 || i == 13 )
    {
    continue;
    }
    else
    {
    if( t_dateString [ i ] < '0' || t_dateString [ i ] > '9' )
    {
    flag = 1;
    break;
    }
    }
    }
    if(t_dateString [4] != '-' || t_dateString [7] != '-' || t_dateString [10] !='/' || t_dateString [13] !=':' )
    {
    flag = 1;
    }
    if( t_dateString.size () != 16 )
    {
    flag = 1;
    }
       完成上述两个关键部分,基本上这个一阶段就没什么难点了。

    逻辑层实现

       第二阶段是逻辑层的实现,这里牵涉到了一些数据的逻辑问题,比如说组合多各类的问题等等。同时,这一块的难度也是最大的,因为牵涉到了大量的遍历,因此数组访问越界的问题常常会导致内存泄漏(本人就是被越界问题坑了两个星期),这里需要用到 gbd 调试工具来帮助我们找到内存泄漏的地方。
       这里着重提及一个很容易越界的地方。在遍历数组时,如果对元素有涉及到删除操作,就很容易导致越界。这里以 deleteUser () 为例。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    int Storage::deleteUser(std::function<bool(const User &)> filter)
    {
    int delete_num = 0;
    for( auto it = this->m_userList.begin (); it != this->m_userList.end (); )
    {
    if( filter (*it) )
    {
    it = this->m_userList.erase (it);

    delete_num++;
    }
    else
    {
    it++;
    }
    }
    m_dirty = true;

    return delete_num;
    }
       需要特别注意在调用 erase () 后,迭代器指针 it 实质上已经 ** 指向了下一个元素 **,如果这时候还是直接 it++,就很有可能出现越界,因此需要把以前循环遍历的格式改为以上这种添加了 if 和 else 的方法,来避免越界的产生。

UI 实现与创新加分

   第三阶段的内容是 UI 实现与创新部分。因为许多同学在第二周的时间内是完不成第二阶段的内容,因此第三周的部分实现也要用来填第二周的坑,UI 部分和创新内容的时间会相对较少。
  UI 方面 TA 会提供一个标准的样式,大家只需要对着写就可以了,可以说没什么难度,就是一些格式上的东西需要注意,整体做出来没什么大差错就可以了。创新部分需要自己头脑风暴,比较多同学做的是 UI 的优化(比如删除前添加一个确认的提示)、输入密码加密(命令行界面中显示 *)、密码加密等。基本上 TA 都会给大家满分的,所以只要自己用心做了,基本上都会是满分。

总结

   我这一届的初级实训是在大一下学期中进行的,横跨了三个周末,相比起以往在小学期进行实训,感觉时间更加紧迫,也要同时兼顾其他科目的作业。过程中自己也是处于一个不断摸索的阶段,对于一些 C11 的新特性也要自己学习。
   最困难的部分还是 Storage 的实现,基本上实现了这个类就完成了实训的 85%。这个类是逻辑层的核心部分,连接着 UI 和数据,本人一直在这里卡死,连续四次测评的结果都是 RE (Runtime Error)。最后对每一个遍历都进行了仔细的测试,真的是逐行代码检测,自己也写了上百个测例,终于在最后一次测评的时候得到了满分。
   整个阶段最重要的还是心态,初级实训涉及的都是一些很基础的东西,并没有算法方面的内容,因此大家还是要沉住气,仔细一点,就算是测评不过也要继续找 bug,我相信大家都一定可以在实训中取得很好的成绩的!

Welcome to my other publishing channels