今天的文章我们来看看如何结合面向对象的思想使用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
![](https://i-blog.csdnimg.cn/blog_migrate/cf11c43456de066e4b5cd013565ccd72.png)