第一次从无到有的构建了第一个多模块化复杂系统,根据教程完成了界面—系统—文件的三层信息交互,也算是由模仿走向创造的第一步

程序架构

整体的程序大致分为三个主要部分:

  • 用户、商品、订单的数据结构部分,包含对数据结构元素的修改、输出,以及与文件的信息交互

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
#pragma once
#include <string>

class User {
public:
std::string user_ID;
std::string user_name;
std::string user_passwd;
std::string contact;
std::string address;
double balance;
};
// 用户与文件操作
void pull_users();

void push_users();
// 用户获取,添加,与删除
User* get_user(int index);

bool add_user(User* u);

bool delete_user(std::string& id, std::string authority);

// 用户信息输出
void user_info(int i);

void print_all_users();

// 用户信息验证及修改
bool check_pass(std::string name, std::string passwd, int& idx);

bool user_topup(std::string id, double m);
  • 前端界面的设计,规定功能数量,限制用户操作,提供与用户的交互界面

  • 交互端口的编写,负责界面间的跳转和实现用户操作对应的程序功能执行行为

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
37
38
39
40
41
42
#include <iostream>
#include <string>

#include "interface/interface.h"

static void check_all_goods() { print_all_goods(); }

static void search_goods() {
std::string name;
std::cout << "Input the name of goods : ";
std::cin >> name;
search_goods_for_admin(name);
}

static void check_all_orders() { print_all_orders(); }

static void check_all_users() { print_all_users(); }

static void ban_user() {
std::string id;
std::cout << "Input the user id : ";
std::cin >> id;
if (delete_user(id, {}))
successMessage();
else
failureMessage();
}

static void ban_goods() {
std::string id;
std::cout << "Input the goods id : ";
std::cin >> id;
if (delete_goods(id, {}))
successMessage();
else
failureMessage();
}

static HANDLER handler[] = {check_all_goods, search_goods, check_all_orders,
check_all_users, ban_user, ban_goods};

make_interface(AD, MIN);

教程的编写顺序是从底层向上层完善,是以数据结构—>界面—>交互端口的顺序编写程序来进行开发

然而在软件的实际开发过程中,首先应该编写完成的是交互端口,根据交互时需要的功能来完善数据结构中的函数

并且执行开发的代码实现前一定要有一个总体和规划,想到哪做哪很有可能渐渐地被陷入代码中

开发中用到的奇技淫巧

原教程是以c语言的形式实现,没有面向对象的编程模式和STL库中的海量轮子,唯一的优势在于printf简洁的格式化输出,并且以makefile的形式进行包管理,我对这种工具流的编程经验较浅,为了享受到随时随地的便捷代码提示,我选择xmake+clangd+vsc的c++工具流,增加使用经验的同时使得编程体验十分愉快

在教程中,最为亮眼的技巧莫过于宏的使用和函数指针的应用,通过同类型函数的部分相同命名实现代码泛化,将代码的复用率提高了很多个层次

宏定义:

1
2
3
4
5
6
7
8
9
10
11
12
#define make_interface(TY, PE)                 \
void TY##PE##_interface() { \
successMessage(); \
int op = menu_terminal(TY##PE); \
while (op != menu_operation[TY##PE]) { \
loadingMessage(); \
handler[op - 1](); \
op = menu_terminal(TY##PE); \
} \
loadingMessage(); \
successMessage(); \
}

函数指针:

1
2
3
4
5
//interface.h
typedef void (*HANDLER)(void);

//main_interface.cpp
static HANDLER handler[] = {USER_login, USER_register, ADMIN_login};

尤其是函数指针的使用,使得一个函数可以作为一个参数进入另一个函数中,可以简便地根据不同条件进入不同的函数,相比于ifelse结构,极大优化了代码空间

然而,优化后结构的可读性会有一定程度上的下降,这点可以在符号定义和注释中尽可能改善,同时代码经验的不断提高也有助于更加流畅地使用宏以及函数指针来完成重复度较高的编码操作。

除此之外,static变量及函数的使用实现了编译单元内部的封装,并防止不同编译单元中同名变量、函数重复定义的问题(但为了易于理解不会定义那么多同名元素);enum枚举这一基础数据结构的使用有效限制了状态、字符串界面的种类,并且在后续的变量操作上可读性大大增加

1
typedef enum { SELLING, SOLD, BANNED } State;

用户输入判断:

1
2
3
4
5
6
7
8
9
10
11
12
double num;
std::cout << "Input the top up amout : ";
std::string buffer;
std::cin >> buffer;
num = atof(buffer.c_str());
while (true) {
if (num > 0) break;
invalidMessage();
std::cout << "please try again: ";
std::cin >> buffer;
num = atof(buffer.c_str());
}

如果硬性需要用户输入一个数字,那么最好的方法不是标明这里要用数字,而是将输入的数据存进一个字符串类型的buffer中再进行判断,这种处理方式可以应对用户任何输入形式

碎碎念

由于代码能力的极大缺陷,我的心态经历了多次波折,由最初的不愿动手,害怕自己完成不了,到之后想要自己实现系统功能时的心如乱麻,问题不断,麻烦重重,到最后认清自己代码能力的不足,再降一个档次,只是跟着教程由自己用c++重构一遍整个管理系统。这其中从不敢做到真正开始实践,又从实践中遭受打击,以一种更实际的方式来学习。既是一个体验实践重要性的过程,也是一个认清自己能力的过程。

编程的学习任重而道远,重要的不在于能否完全按照理想的要求来学习实践,而是根据自己的实际能力选择如何学习。不好高骛远,脚踏实地才是学习中最有用的方法论