老汤回味——C语言与面向对象编程

今天的文章我们来看看如何结合面向对象的思想使用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

猜你喜欢

转载自blog.csdn.net/yjp19871013/article/details/80863795
今日推荐