今天的文章我們來看看如何結合面向對象的思想使用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