随着学习的深入,程序的业务逻辑越来越复杂,代码量越来越多,就需要多人组成团队协同开发,那么就必须把任务拆分成若干个文件。
一般的拆分方案:
main.c 只当作程序的入口,不实现业务逻辑代码。
用于实现程序具体的业务逻辑代码
模块名.h 用说明.c文件中有哪些函数、全局变量,也就是函数声明、全局变量声明。
模块名.c 具体的函数实现,全局变量定义。
项目中常用的、通用的工具,宏函数、函数
tools.h
tools.c
只用于类型设计
type.h 结构体、联合、枚举、宏常量、宏函数
头文件中可以写什么:
由于头文件可能会被若干个.c文件包含,那么每包含一次.c文件中就会有一份头文件的内容,所以头文件中的内容必须可重复,因此我们只适合在头文件中实现以下内容:
1、头文件卫士
2、#include 语句
3、宏常量、宏函数
4、全局变量的声明(变量的声明可以有多份,但定义只能有一份)。
5、函数声明
6、结构、联合、枚举复合的类型设计
7、类型重定义
头文件互相包含、递归包含:
注意:头文件卫士只能解决重复包含的问题,但无法解决相互包含、递归包含的问题。
互相包含:
a.h #include “b.h”
b.h #include “a.h”
递归包含:
a.h #include “b.h”
b.h #include “c.h”
c.h #include “a.h”
解决这种问题的文件,再设计一个.h文件,把他们共用的内存,实现在新的.h文件中,被他们共同包含即可。
多文件编译过程:
1、gcc xxx.h 检查头文件是否有语法错误,如果没有语法错误会生成xxx.h.gch,检查完毕后该文件要立即删除。
2、gcc -c xxx.c 把.c文件编译成二进制目标文件
3、gcc *.o 把所有目标文件合成可执行文件,也可以使用-o 设置可执行文件的名字。
接下来以用户管理系统的项目为例子,把用户管理模块拆分成多文件格式。
一、先创建各个模块的. c文件和 .h文件:main.c (只当作程序的入口,不实现业务逻辑代码)、tools.h( 项目中常用的、通用的工具,宏函数、函数)、tools.c(代码主题)、manager_user.h、manager_user.c。
二、把相应的内容放到合适的文件中:
tools.h:
manager_user.h:
在声明完函数后进行编译:gcc -c tools.h/manager_user,值得注意的是编译完后会生成tools.h.gch,这个文件一定要删除,如果这个文件存在编译器会优先使用这个文件,之后在tools.h修改的内容会无效,因为编译器会继续使tools.h.gch。
tools.c:
#include"tools.h" #include #include#include #include char get_cmd(char start,char end) { assert(start <= end); puts("-------------------"); printf("请输入指令:"); for(;;) { char cmd = getch(); if(start <= cmd && cmd <= end) { printf("%cn",cmd); return cmd; } } } void print_sec(const char* msg,float sec) { printf("33[01;32m %s 33[00mn",msg); usleep(1000000*sec); } void anykey_continue(void) { puts("按任意键继续..."); getch(); } char* get_str(char* str,size_t size) { assert(NULL!=str && size>1); // 计算fgets读取了多少个字符 size_t len = strlen(fgets(str,size,stdin)); // 如果最后一个字符是'n',则把它改为' ' if('n' == str[len-1]) str[len-1] = ' '; else // 如果最后一个字符不是'n',则说明缓冲区中有垃圾数据,则需要清理输入缓冲区 while('n' != getchar()); return str; } char* get_passwd(char* passwd,size_t size) { int i = 0; while(i < size-1) { passwd[i] = getch(); // 读取到退格键 if(127 == passwd[i]) { // 数组中有已输入密码 if(i > 0) { // 删除一位密码 i--; printf("b b"); } continue; } i++; printf("*"); } passwd[size-1] = ' '; printf("n"); return passwd; } bool yes_or_no(void) { printf("是否确认此操作(y/n)?"); for(;;) { char cmd = getch(); if('y' == cmd || 'Y' == cmd || 'n' == cmd || 'N' == cmd) { printf("%cn",cmd); return 'y'==cmd || 'Y'==cmd; } } }
manager_user.c:
#include "manger_user.h" #include#include User user[USER_MAX]; size_t cnt; int login_index = -1; bool logoff_user(void) { if(yes_or_no()) { // 把最后一个用户移动给要删除的用户位置 user[login_index] = user[cnt-1]; // 用户数量减1 cnt--; // 还原用户的登录状态 login_index = -1; print_sec("注销用户成功!",0.75); return true; } else { print_sec("取消注销操作!",0.75); return false; } } void modify_passwd(void) { char passwd[7]; printf("请输入旧密码:"); get_passwd(passwd,sizeof(passwd)); if(strcmp(passwd,user[login_index].passwd)) { print_sec("旧密码错误,无法修改密码!",0.75); return; } printf("请输入新密码:"); get_passwd(passwd,sizeof(passwd)); char repasswd[7]; printf("请确认新密码:"); get_passwd(repasswd,sizeof(repasswd)); if(0 == strcmp(passwd,repasswd)) { strcpy(user[login_index].passwd,passwd); print_sec("修改密码成功!",0.75); } else { print_sec("两次输入的密码不符,修改失败!",0.75); } } void modify_user(void) { printf("请输入用户电话:"); get_str(user[login_index].phone,sizeof(user[login_index].phone)); print_sec("修改用户信息成功!",0.75); } void sub_menu(void) { for(;;) { system("clear"); puts("*****用户信息管理模块*****"); puts("1、注销用户"); puts("2、修改密码"); puts("3、修改信息"); puts("4、退出登录"); switch(get_cmd('1','4')) { case '1': if(logoff_user()) return; else break; case '2': modify_passwd(); break; case '3': modify_user(); break; case '4': login_index = -1; return; } } } // 以下是一级菜单的功能函数 void menu(void) { system("clear"); puts("*****用户管理模块*****"); puts("1、注册用户"); puts("2、用户登录"); puts("3、遍历用户"); puts("4、退出系统"); } int query_user(const char* name) { for(int i=0; i if(0 == strcmp(user[i].name,name)) return i; } return -1; } void register_user(void) { // 判断用户是否已满 if(cnt >= USER_MAX) { print_sec("用户数量已满,无法添加!",0.75); return; } // 输入用户名 printf("请输入用户名:"); get_str(user[cnt].name,sizeof(user[cnt].name)); // 检查用户名是否被占用 if(-1 < query_user(user[cnt].name)) { print_sec("该用户名已被占用,无法注册!",0.75); return; } // 输入密码 printf("请输入密码:"); get_passwd(user[cnt].passwd,sizeof(user[cnt].passwd)); // 再一次输入密码 char repasswd[7]; printf("请输入再一次输入密码:"); get_passwd(repasswd,sizeof(repasswd)); // 确认密码 if(strcmp(user[cnt].passwd,repasswd)) { print_sec("两次输入的密码不符,注册失败!",0.75); return; } // 输入电话号 printf("请输入用于找回密码的手机号:"); get_str(user[cnt].phone,sizeof(user[cnt].phone)); // 初始化锁定标记 user[cnt].lock = 0; // 用户数量+1 //cnt++; FILE* fp=fopen("data.txt","a"); if(NULL==fp) return ; fprintf(fp,"%s %s %s %hhdn",user[cnt].name,user[cnt].passwd,user[cnt].phone,user[cnt].lock); fclose(fp); fp=NULL; cnt++; } void login_user(void) { // 输入用户名 char name[20]; printf("请输入用户名:"); get_str(name,sizeof(name)); // 查询用户是否存在 int index = query_user(name); if(-1 == index) { print_sec("该用户不存在,登录失败!",0.75); return; } // 判断是否被锁 if(user[index].lock >= 3) { print_sec("该用户已经被锁定,请联系大师兄!",0.75); return; } // 输入密码 char passwd[7]; printf("请输入密码:"); get_passwd(passwd,sizeof(passwd)); // 比较密码 if(0 == strcmp(user[index].passwd,passwd)) { // 初始化锁定标志 user[index].lock = 0; // 记录登录成功的用户下标 login_index = index; // 登录成功,进入子菜单 sub_menu(); return; } // 锁定标志+1 switch(++user[index].lock) { case 1: print_sec("密码错误,你还有2次机会!",0.75); break; case 2: print_sec("密码错误,你还有1次机会!",0.75); break; case 3: print_sec("三次密码错误,账号被锁定,请联系大师兄!",0.75); break; } } void show_user(void) { for(int i=0; i printf("%s %s %s %hhdn", user[i].name, user[i].passwd, user[i].phone, user[i].lock); } anykey_continue(); }
最后把入口函数移动过去
main.c:
#include"manger_user.h"
int main(int argc,const char* argv[])
{
for(;;)
{
menu();
switch(get_cmd('1','4'))
{
case '1': register_user(); break;
case '2': login_user(); break;
case '3': show_user(); break;
case '4': return 0;
}
}
}
三、进行整合
先进行编译gcc -c main.c,然后把所以生成的.o文件整合在一起:gcc main.o manager_user.o tools.o;最后一步可以使用脚本Makefile,脚本代码:
CC=gcc
STD=-std=gnu99
FLAG=-Wall -Werror
TARGE=manger
OBJECT=main.o tools.o manger_user.o
$(TARGE):$(OBJECT)
$(CC) $(OBJECT) -o $(TARGE)
main.o:%.o:%.c manger_user.h
$(CC) $(STD) $(FLAG) -c $<
tools.o:%.o:%.c tools.c tools.h
$(CC) $(STD) $(FLAG) -c $<
manager_user.o:%.o:%.c manger_user.c manger_user.h tools.h
$(CC) $(STD) $(FLAG) -c $<
clean:
rm -rf $(OBJECT) $(TARGE)
rm -rf .h.gch
使用这个脚本的可以:
1、节约时间
2、记录文件之间依赖关系
3、自动化执行编译过程



