今天的文章我们来看看如何结合面向对象的思想使用C语言写出结构良好的代码。直接看代码,然后我们来分析一下代码中的含义。首先是头文件user.h:
#ifndef USER_H #define USER_H #define USERNAME_LEN 255 #define PASSWORD_LEN 255 typedef struct { char username[USERNAME_LEN]; char password[PASSWORD_LEN]; } USER, *PUSER; PUSER init_user(char *username, char *password); char *get_username(PUSER puser); void set_username(PUSER puser, char *username); int check_password(PUSER puser, char *check_pwd); void set_password(PUSER puser, char *pwd); void destory_user(PUSER puser); #endif
最上面我们定义了一个结构体代表用户类,但是结构体中的成员变量只能是基本类型,数组或者指针,没有办法定义类方法,我们该怎么办呢?看下面的方法定义,init_user相当于我们的构造函数,传入username和password,init_user返回一个创建好的用户类对象的指针,而destory_user则类似于析构函数。
中间我们还定义了一些属性的getter和setter方法,可以看到,第一个参数是我们要操作的对象,也就是消息的接收者,是一个用户类对象的指针,它起到的作用类似于我们在一些面向对象语言中的this。
接下来具体看一下函数的实现,在user.c中,首先,我们声明了一个静态函数
static int is_user_valid(PUSER puser);
之前说过,静态函数的作用于为文件作用域,也就是说,这个函数的作用域仅限于user.c文件,而我们之前的user.h文件中并没有声明该函数,所以,我们可以理解为,这个函数是一个私有函数,只在我们的用户类内部使用。该函数定义如下:
static int is_user_valid(PUSER puser) { if (NULL == puser) { return 0; } else { return 1; } }
简单起见,我这里只是校验了puser是否为NULL,还可以在这个函数中添加其他用户有效性校验,比如校验该用户是否是我们创建并记录在案的等等。
接下来看看我们的公有函数,也就是在user.h中声明的函数
PUSER init_user(char *username, char *password) { if (NULL == username || NULL == password) { printf("init_user error: username or password is NULL\n"); return NULL; } PUSER puser = (PUSER)malloc(sizeof(USER)); memset(puser, 0, sizeof(USER)); strncpy(puser->username, username, USERNAME_LEN - 1); strncpy(puser->password, password, PASSWORD_LEN - 1); return puser; }
init_user函数创建一个用户对象,开始我们进行了入参校验,然后使用malloc动态分配了空间,之后初始化属性字段。
void destory_user(PUSER puser) { if (!is_user_valid(puser)) { return; } free(puser); puser = NULL; }
destory_user函数销毁对象,首先入参校验,之后free内存空间,将用户指针置为NULL,这是C语言动态内存释放常用的手段。
char *get_username(PUSER puser) { if (!is_user_valid(puser)) { return ""; } return puser->username; } void set_username(PUSER puser, char *username) { if (!is_user_valid(puser) || NULL == username) { return; } memset(puser->username, 0, USERNAME_LEN); strncpy(puser->username, username, USERNAME_LEN - 1); } int check_password(PUSER puser, char *check_pwd) { if (!is_user_valid(puser) || NULL == check_pwd) { return 0; } return 0 == strncmp(puser->password, check_pwd, PASSWORD_LEN); } void set_password(PUSER puser, char *pwd) { if (!is_user_valid(puser) || NULL == pwd) { return; } memset(puser->password, 0, PASSWORD_LEN); strncpy(puser->password, check_pwd, PASSWORD_LEN - 1); }
其他四个函数比较简单,大家自己看一下,注意入参校验及使用字符串安全函数进行操作。最后,是我们的main.c
#include <stdio.h> #include "user.h" int main(void) { PUSER puser = init_user("yjp", "123456"); if (NULL == puser) { printf("init user error!\n"); return 1; } printf("init username: %s\n", get_username(puser)); printf("change username\n"); set_username(puser, "yjp1"); printf("now username: %s\n", get_username(puser)); printf("check password: %d\n", check_password(puser, "123456")); printf("change password\n"); set_password(puser, "654321"); printf("now check password: %d\n", check_password(puser, "123456")); destory_user(puser); return 0; }
执行结果如下:
init username: yjp
change username
now username: yjp1
check password: 1
change password
now check password: 0
从上面的代码不难看出,使用C语言的语言机制可以写出结构很好的代码,清晰简洁,封装也很到位。
接下来再思考一个问题,如果我们想提供一个接口,允许模块的使用者使用自己的密码加密方式该怎么办?如果了解设计模式,能够想到,这里我们使用模板方法模式。下面看看使用C语言如何实现。首先user.h中添以下内容:
typedef char* (*PWD_DEAL_FUNC)(char *pwd); typedef struct { PWD_DEAL_FUNC pwd_deal; } USER_OPERATIONS, *PUSER_OPERATIONS; void user_ops_register(PUSER_OPERATIONS ops);
定义一个函数指针类型,该函数指针指向的函数以一个字符串为参数返回一个字符串,定义一个结构体代表用户操作接口,可以看到,结构体可以将函数指针作为成员变量,只有函数指针的结构体,我们可以将其当做其他面向对象语言中的接口或者抽象类。之后声明了一个注册用户操作的函数。下面看一下user.c中的调整:
static PUSER_OPERATIONS g_user_ops = NULL; void user_ops_register(PUSER_OPERATIONS ops) { if (NULL == ops) { return; } g_user_ops = ops; }
首先定义一个全局的用户操作对象指针,然后实现了注册方法。如果在并发环境下执行,这里应当考虑全局变量的共享问题,这里简化问题,不去过多说明。与密码相关的两个方法作出修改:
int check_password(PUSER puser, char *check_pwd) { if (!is_user_valid(puser) || NULL == check_pwd) { return 0; } char *dealed_pwd = check_pwd; if (NULL != g_user_ops) { dealed_pwd = g_user_ops->pwd_deal(dealed_pwd); if (NULL == dealed_pwd) { return 0; } } printf("check password is %s\n", dealed_pwd); return 0 == strncmp(puser->password, dealed_pwd, PASSWORD_LEN); } void set_password(PUSER puser, char *pwd) { if (!is_user_valid(puser) || NULL == pwd) { return; } char *dealed_pwd = pwd; if (NULL != g_user_ops) { dealed_pwd = g_user_ops->pwd_deal(pwd); if (NULL == dealed_pwd) { return; } } memset(puser->password, 0, PASSWORD_LEN); strncpy(puser->password, dealed_pwd, PASSWORD_LEN - 1); printf("password set to %s\n", puser->password); }
在赋值和检查密码前都对密码使用模板方法,也就是调用我们注册的密码处理操作函数。main.c改动:
#include <string.h> #include <stdio.h> #include "user.h" static char *password_deal(char *pwd) { if (NULL == pwd) { return NULL; } if (0 == strncmp("654321", pwd, PASSWORD_LEN)) { return "123456"; } return pwd; } static USER_OPERATIONS g_user_ops = { .pwd_deal = password_deal }; int main(void) { user_ops_register(&g_user_ops); ...... return 0; }
我们的密码处理就是如果要设置为654321,就将其改变为123456。在main开始,对我们的操作接口进行了注册。上面的结构体初始化使用了字段初始化。执行结果为:
init username: yjp
change username
now username: yjp1
check password is 123456
check password: 1
change password
password set to 123456
check password is 123456
now check password: 1
通过今天的实践可以看到,面向对象编程思想具有普适性,作为编程思想,可以用任何语言加以实现,面向对象语言,只是在语法层面上提供了面向对象编程的支持,方便我们写出面向对象的代码,但是像C这样的编程语言,依然可以把面向对象思想作为我们编写代码的武器,这样写出的C语言代码十分工整,也易于扩展和维护。
对于C语言的学习,就告一段落了,个人认为,学习C语言最好的方式,可以去学习Linux内核代码,去看看庞大的内核代码中C语言使用的亮点,Linux内核代码很好的展现了C语言的博大精深,代码结构也很好,非常值得了解。
代码已上传至github
https://github.com/yjp19871013/c_study