👥User模块
搭建储存和操作用户信息的底层数据结构
当前进度
.---+- include -+- tools -+- color.h
| | +- hint.h
| | +- info.h
| |
| +- user -+- user.h <---
| |
| +- config.h <---
|
+- src -+- tools -+- color.c
| +- hint.c
| +- info.c
|
+- data -+- id.txt
| +- user.txt <---
|
+- user -+- user.c <---
|
+- main.c设计数据类型
根据前面所说的要求,我么可以在user.h中定义如下的用户类型:
结构体很常用了,不过为了以防万一有用法不清晰的地方,还是附上一个参考教程吧。
参考教程:https://www.runoob.com/w3cnote/c-structures-intro.html
之后的内容中,笔者将默认读者至少了解了结构体的基本用法。至于结构体和联合体的混合嵌套使用、结构体的位域等其他的高阶技巧有兴趣的可以自行了解。
这里,我们引入了一个头文件config.h,这个头文件是储存一些全局设定的,这里我们约定本项目中所有的字符串长度均不超过MAX_LEN,我们将其定义在config.h中。
随着程序的不断丰富,我们可能会需要一些其他的全局设定或者约定,我们都可以放在config.h中。这样一来,如果之后我们要更改这些约定,只需要修改config.h头文件即可。
选择数据结构
我选用了数组结构,因此我们需要约定用户的总数不超过MAX_USER,并且在config.h中定义这个宏。
我们在user.c中定义这个数据结构,同时定义用户的初始总数为0:
这里采用全局数组和全局变量将数据结构储存主存的静态数据区,而不是栈区。因为静态数据区变量的生存期是贯穿程序始终的,并且静态数据的空间会比栈区大。
除此之外,用static关键字限制这两个变量的文件作用域为本文件内,这样就只有user模块本身的函数方法可以访问着两个数据结构了。外部模块只有通过本模块提供的方法或者说接口才能读取或者修改数据内容。
这个过程就是模块的封装和抽象。
设计对外接口
接口设计
根据项目刚开始时的需求汇总,我们对于用户的数据结构提供如下一些接口,声明在user.h头文件中。
接口实现
从外部文件导入信息
我们约定,用户的信息储存在文件src/data/user.txt中。另外,别忘了包含
user.h头文件,包含其相对于include文件夹的相对路径。
这里使用static关键字,将filePath的作用域限制在本模块内部(C语言以.c文件作为模块划分的基本单位),这样在其他模块里面就可以继续使用filePath这个标识符作为文件路径的常量名了。
这种手法在编写界面模块的时候会体现的淋漓尽致,可以期待一下😄。
将用户信息导出到外部文件
为了避免浮点数打印精度不确定导致打印格式不统一的问题,我们约定在本项目中,所有的浮点数保留小数点后1位。哪怕用户输入了更高精度的浮点数,因为打印规格符统一使用
.1f,故而当该输入被打印到标准输出的时候总是1位小数。同时,在经历一次文件读写之后,实际精度也会变成一位小数。
获取用户对象
这里涉及到了C语言指针相关的知识点,后续的内容中也会涉及指针,所以如果还不太熟悉的话可以寻找教程复习一下。
参考教程:https://www.runoob.com/w3cnote/c-pointer-detail.html
后续的内容中笔者将默认读者掌握了指针的含义以及基本运算。
打印详细的用户信息
打印内容需要注意对齐和美观。
添加新用户
因为这里用到了
genID函数来生成新用户的ID,所以需要包含info.h头文件。当然,用到了字符串处理的库函数,自然也需要包含string.h头文件。
这里值得提一下的是,所有的字符串的赋值都不可以直接用等号,而应当使用strcpy函数,因为字符串是复杂数据类型,变量中存的只是首地址,而不是字符串本身。不过结构体是可以直接赋值的。结构体赋值的原则是将整个结构体所指向的内存直接拷贝。
当然,这里可以直接赋值其实取决与我结构体中数组的定义方式。
因为我们需要保证所有的用户用户名不同,所以我们需要写一个searchUserName函数来查找用户名,只有找不到的时候,才可以为这个新的用户生成一个独特的ID并把新用户放到我们的数组中去。
因为这个searchUserName其实只在addUser函数中用到了,相当于添加新用户的时候检查是否用户已存在的一个帮助函数,因此使用static关键字将其作用域限制在本模块内,并且不在头文件中给处该函数的声明。
此处,你需要理解模块抽象、封装的过程和手法,在后面的教程中相似的内容我将不再重复。
删除给定ID的用户
who中存放的是行为主体的ID,只有管理员有权限删除用户(管理员ID为NULL)。
打印所有用户的信息
这里我们将表头header和表格分割线divide抽象出来,作为两个模块局部变量,在Good和Order模块中也有相同名称的局部变量。
这样设计是为了简化代码,提高通用性,因为表头和分割线会被使用很多次(这个模块不明显,因为只有管理员有权限查看所有的用户,不过在Good模块的时候,买家、卖家和管理员查看到的商品列表是不一样的,也就需要很多不同的print函数,所以会重复的要用到表头和分隔符)。
除此之外,我还将打印表头和打印分隔符的操作封装成了两个宏print_header和print_divide,放在info.h头文件中。在所有的数据结构模块中都可以使用这两个宏。
因为header和divide都是static作用域,所以在不同的数据结构模块里面使用这两个宏,所指向的header和divide都是模块内自己定义的那两个,所以这两个宏只需要定义一遍就可以供三个数据结构模块多次使用,大大减少了不必要的重复性的代码。
这个地方最好能够驻足思考一下这种处理方式———利用预处理阶段宏展开的性质精简代码、利用static文件作用域的特性保持宏对于多个模块的实用性与泛化特性。
这个地方只是一个print的优化处理,在写interface模块的时候你会发现这样的处理能够精简特别多不必要的重复代码书写。
所以在继续之前还请稍微理解一下这样的处理方式。
在这个部分还用到了printf函数的各种格式化输出的功能,如果对于模式串含义还不清晰的话可以寻找相关教程复习一下。
参考教程: https://www.runoob.com/cprogramming/c-function-printf.html
之后的内容中笔者会默认各位清楚了printf模式串中各种规定符的含义。
检查用户名和密码是否匹配
这里除了返回密码匹配是否成功之外,还需要返回和哪一个用户匹配成功了。
用户充值
至此,我们完成了用户数据结构模块的设计。提供了一些很方便使用的外部接口,等到我们之后写交互界面的时候就可以很方便的使用这些接口了。再接再厉,我们下面写一下商品模块。
最后更新于
这有帮助吗?