老湯回味——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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章