前言
今天的Windows應用開發課上,教授讓我們用純C語言實現面向對象的開發和封裝。如果用C++那實現面向對象是輕而易舉,但是用純C的話,emmm想想就頭大,光一個字符串的操作就搞了半天,和C++比起來,C語言真的太“基礎”了,幾乎所有的方法都要自己寫。
這個代碼我硬鋼了兩天,一共寫了500行左右,收穫還是蠻大的,算是比較完善的將方法封裝了起來。
代碼要求
- 用純C實現面向對象編程
- 包含基類和一層子類,子類及其繼承的方法由用戶自己設定,但要有接口
- 所有的方法都要進行封裝,不能顯示出來參數類型
代碼實現結構
首先,爲了實現封裝而且不顯示參數類型,將所有的參數都設置爲int類型(包括結構體),通過int轉換爲各種指針類型,再由指針析取爲實際類型進行參數的操作。
所有的方法封裝到WinProc
接口中,通過其中的msg
參數辨別進行哪一個方法的調用。另外,爲了實現基類和子類接口的統一,另外加了一個CallProc
接口,實現分配基類和子類的操作函數。
//封裝的窗口操作函數
int WinProc(int win, int msg, int param1, int param2) {
switch (msg){
case MOVE: move(win, param1, param2); break;
case SET_POSITION: SetPosition(win, param1, param2); break;
case GET_POSITION: {Pos p = GetPosition(win); return (int)&p; }break;
case SET_SIZE: SetSize(win, param1, param2); break;
case GET_SIZE: {WinSize p = GetSize(win); return (int)&p; }break;
case SET_TITLE: SetTitle(win, (char*)param1); break;
case GET_TITLE: return (int)GetTitle(win); break;
case PRINTWINDOW: PrintWindow(win); break;
case CONSTRUCT: return ConstructBase(win); break;
case DESTRACT: DestroyWindow(win); break;
case MEM_GET: return getMem(win, param1); break;
case MEM_SET: setMem(win, param1, param2); break;
case STATIC_GET: return getStatic(win, param1, param2); break;
case STATIC_SET: setStatic(win, param1, param2); break;
case COPY: return CopyWindow(win); break;
default: break;
}
}
//分配基類和子類操作函數的調用接口
int CallProc(int win, int msg, int param1, int param2) {
for (int i = 0; i < objectIndex; ++i) { //遍歷所有實例
if (objects[i].object == win) { //找到這個實例
if (objects[i].index == -1) //如果是基類,則返回基類對應的操作函數
return WinProc(win, msg, param1, param2);
return subclasses[objects[i].index].pFn(win, msg, param1, param2); //如果是子類,則返回子類對應的操作函數
}
}
return 0;
}
該項目建立了一個基類爲window
,其中包括一組窗口的位置信息Pos
包含x
和y
,和窗口的大小信息WinSize
包含width
和height
,還有窗口的名字title
,是一個 char[200]
數組(懷念C++的string啊~~~~)
//位置結構體,包含(x,y)
typedef struct Pos {
int x, y;
};
//窗口大小結構體,包含寬、高
typedef struct WinSize {
int width, height;
};
//基類
struct window {
Pos pos;
WinSize size;
char title[200];
};
在這個基類的基礎上,用戶可以通過RegisterClass()
接口創建子類,子類可以包含附加變量和static變量,在創建時要說明變量類型(即變量所佔用字節數)用於後期分配內存。
//創建一個新的子類
void RegisterClass(char* name, int data, int staticdata, int (*fn)(int, int, int, int)) {
strcpy(subclasses[subClassIndex].Name, name);
subclasses[subClassIndex].Bytes = data;
subclasses[subClassIndex].StaticBytes = staticdata;
subclasses[subClassIndex].pStatic = (char*)malloc(staticdata); //開闢子類的static內存空間
subclasses[subClassIndex].pFn = fn;
//初始化靜態變量
int type;
switch (staticdata) {
case 4: *(int*)subclasses[subClassIndex].pStatic = 0; break; //int初始化爲0
case 1: *(char*)subclasses[subClassIndex].pStatic = 'a'; break; //char初始化爲‘a’
default:strcpy(subclasses[subClassIndex].pStatic, ""); break; //string初始化爲空串
}
subClassIndex++; //子類的個數增加1
}
爲了儲存子類和實例,分別又建立了兩個結構體SubClass
和Object
,並分別開闢了兩個數組來儲存子類和實例。SubClass
結構體中包含了子類的名字、子類附加變量增加字節數、子類static變量增加字節數、指向static變量的指針和一個函數指針(這裏類似C++中函數的重載)。Object
結構體中儲存了類的實例,其中包含了指向類的實例的指針和一個表示子類類型的指針。
//子類
struct SubClass {
char Name[200]; //子類名字
int Bytes; //子類附加變量增加字節數
int StaticBytes; //子類static變量增加字節數
char* pStatic; //子類static的指針
int (*pFn)(int win, int msg, int param1, int param2); //子類的方法函數
}subclasses[50];
int subClassIndex = 0;
//獲取當前創建的子類總數
int getSubClassNum() { return subClassIndex; }
//所有類的實例
struct Object {
int object; //類的實例
int index; //若爲基類的實例則是-1,若爲子類的實例則爲其在subclasses裏面對應子類的下標
}objects[100];
int objectIndex = 0; //實例的個數
//獲取當前創建的實例總數
int getObjNum() { return objectIndex; }
關鍵細節
void* p = malloc(sizeof(window) + subclasses[objects[i].index].Bytes);
開闢子類實例的內存空間。子類實例不僅要開闢基類變量所包含的內存空間(實現繼承),還要有自己的內存空間。【**注意:**子類的實例不包含子類變量的static靜態變量的內存空間。因爲靜態變量是屬於子類的,在定義子類的時候已經開闢,不屬於子類的實例。】window* p = (window*)win;
因爲在項目中所有的參數傳遞都是用指針以int類型進行傳遞,這個語句可以將int(實際上是指針)轉換爲window類型。其他類型的轉換同理。char* p = (char*)win + sizeof(window);
這一步操作本質上只是一個指針的跨越,但是它實現了對子類附加變量的指針定位。
不足之處
代碼中有一個暫未解決的bug,本來說是想實現構造函數和析構函數,但在 delete 清除實例內存的時候出現了一點問題,總是報內存泄漏的錯誤,希望路過的大佬可以幫忙完善一下,不盡感激。
//刪除窗口(暫未通過調試)
void DestroyWindow(int win) {
if (!win) {
delete (window*)win;
}
}
完整代碼
完整的window.cpp如下:
//Author:SongXingJian
#include "stdio.h"
#include "string.h"
#include "malloc.h"
#include "window.h"
//位置結構體,包含(x,y)
typedef struct Pos {
int x, y;
};
//窗口大小結構體,包含寬、高
typedef struct WinSize {
int width, height;
};
//基類
struct window {
Pos pos;
WinSize size;
char title[200];
};
//子類
struct SubClass {
char Name[200]; //子類名字
int Bytes; //子類附加變量增加字節數
int StaticBytes; //子類static變量增加字節數
char* pStatic; //子類static的指針
int (*pFn)(int win, int msg, int param1, int param2); //子類的方法函數
}subclasses[50];
int subClassIndex = 0;
//獲取當前創建的子類總數
int getSubClassNum() { return subClassIndex; }
//所有類的實例
struct Object {
int object; //類的實例
int index; //若爲基類的實例則是-1,若爲子類的實例則爲其在subclasses裏面對應子類的下標
}objects[100];
int objectIndex = 0; //實例的個數
//獲取當前創建的實例總數
int getObjNum() { return objectIndex; }
//創建一個新的子類
void RegisterClass(char* name, int data, int staticdata, int (*fn)(int, int, int, int)) {
strcpy(subclasses[subClassIndex].Name, name);
subclasses[subClassIndex].Bytes = data;
subclasses[subClassIndex].StaticBytes = staticdata;
subclasses[subClassIndex].pStatic = (char*)malloc(staticdata); //開闢子類的static內存空間
subclasses[subClassIndex].pFn = fn;
//初始化靜態變量
int type;
switch (staticdata) {
case 4: *(int*)subclasses[subClassIndex].pStatic = 0; break; //int初始化爲0
case 1: *(char*)subclasses[subClassIndex].pStatic = 'a'; break; //char初始化爲‘a’
default:strcpy(subclasses[subClassIndex].pStatic, ""); break; //string初始化爲空串
}
subClassIndex++; //子類的個數增加1
}
//移動窗口
void move(int win, int deltaX, int deltaY) {
window* p = (window*)win; //將任意封裝類型強制轉化爲所需要的內容
p->pos.x += deltaX;
p->pos.y += deltaY;
}
//設置窗口位置
void SetPosition(int win, int x, int y) {
window* p = (window*)win;
p->pos.x = x;
p->pos.y = y;
}
//返回窗口位置
Pos GetPosition(int win) {
window* p = (window*)win;
Pos win_pos;
win_pos.x = p->pos.x;
win_pos.y = p->pos.y;
return win_pos;
}
//設置窗口寬和高
void SetSize(int win, int width, int height) {
window* p = (window*)win;
p->size.width = width;
p->size.height = height;
}
//獲取窗口寬和高
WinSize GetSize(int win) {
window* p = (window*)win;
WinSize win_size;
win_size.width = p->size.width;
win_size.height = p->size.height;
return win_size;
}
//設置窗口title
void SetTitle(int win, char* newTitle) {
window* p = (window*)win;
//p->title = newTitle;
strcpy(p->title, newTitle);
}
//返回窗口title
char* GetTitle(int win) {
window* p = (window*)win;
return p->title;
}
//創建Window,參數是子類名字,創建基類則傳入0
int CreateWindow(char* name) {
int new_win = 0;
if (!name) { //創建基類實例
window* p = (window*)malloc(sizeof(window));
WinProc((int)p, CONSTRUCT, 0, 0); //初始化基類變量
new_win = (int)p;
objects[objectIndex].object = new_win;
objects[objectIndex].index = -1; //基類的下標設置爲-1
objectIndex++; //實例數+1
}
else { //創建子類實例
for (int j = 0; j < subClassIndex; ++j) {
if (!strcmp(name, subclasses[j].Name)) { //如果name相等,即找到了subclasses的位置
void* p = malloc(sizeof(window) + subclasses[j].Bytes); //開闢該子類實例所需空間
WinProc((int)p, CONSTRUCT, 0, 0); //初始化基類變量
new_win = (int)p;
objects[objectIndex].object = new_win;
objects[objectIndex].index = j; //下標設置爲在subclasses裏面相同子類的下標
objectIndex++; //實例數+1
break;
}
}
}
return new_win;
}
//基類的構造函數,初始化基類變量
int ConstructBase(int win) {
window* p = (window*)win;
strcpy(p->title, "");
p->pos.x = 0;
p->pos.y = 0;
p->size.width = 0;
p->size.height = 0;
return (int)p;
}
//拷貝並創建實例
int CopyWindow(int win) {
int new_win;
for (int i = 0; i < objectIndex; ++i) { //遍歷所有實例
if (objects[i].object == win) { //找到這個實例
if (objects[i].index != -1) { //如果是子類
void* p = malloc(sizeof(window) + subclasses[objects[i].index].Bytes); //開闢該子類實例所需空間
new_win = (int)p;
WinProc(new_win, CONSTRUCT, 0, 0);
//確定子類附加變量的參數類型
int type_mem;
if (subclasses[objects[i].index].Bytes == 4) type_mem = SUB_INT;
else if (subclasses[objects[i].index].Bytes == 1) type_mem = SUB_CHAR;
else type_mem = SUB_STRING;
//複製子類附加變量
WinProc(new_win, MEM_SET, type_mem, WinProc(objects[i].object, MEM_GET, type_mem, 0));
objects[objectIndex].object = new_win;
objects[objectIndex].index = objects[i].index; //下標設置爲在subclasses裏面相同子類的下標
objectIndex++; //實例數+1
}
else { //如果是基類
window* p = (window*)malloc(sizeof(window));
new_win = (int)p;
WinProc(new_win, CONSTRUCT, 0, 0);
objects[objectIndex].object = new_win;
objects[objectIndex].index = -1; //基類的下標設置爲-1
objectIndex++; //實例數+1
}
//複製基類變量
WinProc(new_win, SET_TITLE, WinProc(objects[i].object, GET_TITLE, 0, 0), 0); //賦值新實例的title
Pos w_pos = *(Pos*)WinProc(objects[i].object, GET_POSITION, 0, 0); //獲取被複制實例的pos
WinProc(new_win, SET_POSITION, w_pos.x, w_pos.y); //賦值新實例的pos
WinSize w_size = *(WinSize*)WinProc(objects[i].object, GET_SIZE, 0, 0); //獲取被複制實例的size
WinProc(new_win, SET_SIZE, w_size.width, w_size.height); //賦值新實例的size
return new_win;
}
}
}
//刪除窗口(暫未通過調試)
void DestroyWindow(int win) {
if (!win) {
delete (window*)win;
}
}
//打印窗口的所有屬性
void PrintWindow(int win) {
window* p = (window*)win;
printf_s("\n================ %s ================\nPosition: (%d, %d)\nWidth: %d\tHeight: %d\n", p->title, p->pos.x, p->pos.y, p->size.width, p->size.height);
}
//獲得子類中的變量
int getMem(int win, int sub_type) {
char* p = (char*)win + sizeof(window); //指針p跨越基類內存,指向子類附加內存
if (sub_type == SUB_INT) //子類附加的數據是int
return *(int*)p;
else if (sub_type == SUB_CHAR) //子類附加的數據是char
return *p;
else if (sub_type == SUB_STRING) //子類附加的數據是字符串
return (int)p;
}
//設置子類中的變量
void setMem(int win, int sub_type, int param2) {
char* p = (char*)win + sizeof(window); //指針p跨越基類內存,指向子類附加內存
if (sub_type == SUB_INT) //子類附加的數據是int
*(int*)p = param2;
else if (sub_type == SUB_CHAR) //子類附加的數據是char
*p = param2;
else if (sub_type == SUB_STRING) //子類附加的數據是字符串
strcpy(p, (char*)param2);
}
//獲得子類中的static變量
int getStatic(int win, int sub_type, int param2) {
char* p = 0;
for (int i = 0; i < objectIndex; ++i) { //遍歷所有實例
if (objects[i].object == win) { //找到這個實例
if (objects[i].index != -1) //如果不是基類
p = subclasses[objects[i].index].pStatic;
break;
}
}
if (sub_type == SUB_INT) //子類static變量是int
return *(int*)p;
else if (sub_type == SUB_CHAR) //子類static變量是char
return *p;
else if (sub_type == SUB_STRING) //子類static變量是字符串
return (int)p;
}
//設置子類的static變量
void setStatic(int win, int sub_type, int param2) {
char* p = 0;
for (int i = 0; i < objectIndex; ++i) { //遍歷所有實例
if (objects[i].object == win) { //找到這個實例對應的子類
if (objects[i].index != -1) {
p = subclasses[objects[i].index].pStatic;
if (sub_type == SUB_INT)
*(int*)p = param2;
else if (sub_type == SUB_CHAR)
*p = param2;
else if (sub_type == SUB_STRING)
strcpy(p, (char*)param2);
break;
}
}
}
}
//封裝的窗口操作函數
int WinProc(int win, int msg, int param1, int param2) {
switch (msg){
case MOVE: move(win, param1, param2); break;
case SET_POSITION: SetPosition(win, param1, param2); break;
case GET_POSITION: {Pos p = GetPosition(win); return (int)&p; }break;
case SET_SIZE: SetSize(win, param1, param2); break;
case GET_SIZE: {WinSize p = GetSize(win); return (int)&p; }break;
case SET_TITLE: SetTitle(win, (char*)param1); break;
case GET_TITLE: return (int)GetTitle(win); break;
case PRINTWINDOW: PrintWindow(win); break;
case CONSTRUCT: return ConstructBase(win); break;
case DESTRACT: DestroyWindow(win); break;
case MEM_GET: return getMem(win, param1); break;
case MEM_SET: setMem(win, param1, param2); break;
case STATIC_GET: return getStatic(win, param1, param2); break;
case STATIC_SET: setStatic(win, param1, param2); break;
case COPY: return CopyWindow(win); break;
default: break;
}
}
//分配基類和子類操作函數的調用接口
int CallProc(int win, int msg, int param1, int param2) {
for (int i = 0; i < objectIndex; ++i) { //遍歷所有實例
if (objects[i].object == win) { //找到這個實例
if (objects[i].index == -1) //如果是基類,則返回基類對應的操作函數
return WinProc(win, msg, param1, param2);
return subclasses[objects[i].index].pFn(win, msg, param1, param2); //如果是子類,則返回子類對應的操作函數
}
}
return 0;
}
完整的window.h如下:
//Author:SongXingJian
#define MOVE 1
#define SET_POSITION 2
#define GET_POSITION 3
#define SET_SIZE 4
#define GET_SIZE 5
#define SET_TITLE 6
#define GET_TITLE 7
#define PRINTWINDOW 8
#define CONSTRUCT 9
#define DESTRACT 10 //暫未通過調試
#define MEM_GET 11
#define MEM_SET 12
#define STATIC_GET 13
#define STATIC_SET 14
#define SUB_INT 15
#define SUB_CHAR 16
#define SUB_STRING 17
#define COPY 18
int getSubClassNum(); //獲取當前創建的子類總數
int getObjNum(); //獲取當前創建的實例總數
int CreateWindow(char* name); //創建實例,參數是子類名字,基類則傳入0
int WinProc(int win, int msg, int param1, int param2); //SDK整體封裝
int CallProc(int win, int msg, int param1, int param2); //多態
void RegisterClass(char* name, int data, int staticdata, int (*fn)(int, int, int, int)); //定義一個新的子類
/* 封裝過程
//void move(window* win, int deltaX, int deltaY);
//void move(void* win, int deltaX, int deltaY);
void move(int win, int deltaX, int deltaY); //對外提供接口
struct Pos GetPosition(int win);
void Size(int win, int newX, int newY);
void SetWindowText(int win, char* newTitle);
char* GetWindowText(int win);
*/
完整的main_test.cpp(主函數)如下:
//Author:SongXingJian
#include "window.h"
#include "stdio.h"
//win_num子類的函數
int sub_window_num(int win, int msg, int param1, int param2) {
switch (msg){
//子類構造函數,創建一個子類
case CONSTRUCT: {
int w = CreateWindow((char*)win);
WinProc(w, SET_TITLE, param1, 0); //初始化title
WinProc(w, MEM_SET, SUB_INT, 0); //初始化子類實例附加變量爲0
return w;
}break;
//設置子類實例附加變量
case MEM_SET: {
printf("Set the num: %d ---> %d\n", WinProc(win, MEM_GET, SUB_INT, 0), param2); //展示修改前--->修改後
WinProc(win, msg, SUB_INT, param2);
}break;
//獲取子類實例附加變量
case MEM_GET: {
int p = WinProc(win, msg, SUB_INT, 0);
printf("Num: %d\n", p);
}break;
//設置子類static變量
case STATIC_SET: {
printf("Set the static: %d ---> %d\n", WinProc(win, STATIC_GET, SUB_INT, 0), param2); //展示修改前--->修改後
WinProc(win, msg, SUB_INT, param2);
}break;
//獲取子類static變量
case STATIC_GET: {
int p = WinProc(win, msg, SUB_INT, 0);
printf("Static: %d\n", p);
}break;
//打印子類實例的所有變量
case PRINTWINDOW: {
WinProc(win, msg, param1, param2);
printf("Num: %d\t\tStatic: %d\n", WinProc(win, MEM_GET, SUB_INT, 0), WinProc(win, STATIC_GET, SUB_INT, 0));
}break;
default:return WinProc(win, msg, param1, param2); break;
}
}
//win_c子類的函數
int sub_window_c(int win, int msg, int param1, int param2) {
switch (msg) {
//子類構造函數,創建一個子類
case CONSTRUCT: {
int w = CreateWindow((char*)win);
WinProc(w, SET_TITLE, param1, 0); //初始化title
WinProc(w, MEM_SET, SUB_CHAR, (int)'A'); //初始化子類實例附加變量爲'A'
return w;
}break;
//設置子類實例附加變量
case MEM_SET: {
printf("Set the Char: %c ---> %c\n", WinProc(win, MEM_GET, SUB_CHAR, 0), (char)param2); //展示修改前--->修改後
WinProc(win, msg, SUB_CHAR, param2);
}break;
//獲取子類實例附加變量
case MEM_GET: {
int p = WinProc(win, msg, SUB_CHAR, 0);
printf("Char: %c\n", (char)p);
}break;
//設置子類static變量
case STATIC_SET: {
printf("Set the static: %c ---> %c\n", (char)WinProc(win, STATIC_GET, SUB_CHAR, 0), (char)param2); //展示修改前--->修改後
WinProc(win, msg, SUB_CHAR, param2);
}break;
//獲取子類static變量
case STATIC_GET: {
int p = WinProc(win, msg, SUB_CHAR, 0);
printf("Static: %c\n", (char)p);
}break;
//打印子類實例的所有變量
case PRINTWINDOW: {
WinProc(win, msg, param1, param2);
printf("Char: %c\t\tStatic: %c\n", WinProc(win, MEM_GET, SUB_CHAR, 0), WinProc(win, STATIC_GET, SUB_CHAR, 0));
}break;
default:return WinProc(win, msg, param1, param2); break;
}
}
int main() {
char win_num[] = "sub_win_num";
char win_c[] = "sub_win_c";
char win_num_obj[] = "sub_win_num_obj";
char win_c_obj[] = "sub_win_c_obj";
char win_base_obj[] = "base_win_obj";
/****************** 創建win_num子類 ******************/
RegisterClass(win_num, sizeof(int), sizeof(int), sub_window_num);
//int sub_w = CreateWindow(win_num);
int sub_w = sub_window_num((int)win_num, CONSTRUCT, (int)win_num_obj, 0); // 構造函數,可以創建實例,並初始化title、基類變量和實例附加變量
CallProc(sub_w, MEM_SET, SUB_INT, 5); //設置實例的子類附加變量
CallProc(sub_w, MEM_GET, SUB_INT, 0);
CallProc(sub_w, STATIC_SET, SUB_INT, 3); //設置實例所屬子類static變量
CallProc(sub_w, STATIC_GET, SUB_INT, 0);
CallProc(sub_w, PRINTWINDOW, 0, 0); //打印窗口屬性
//複製這個實例
int sub_w_copy = CallProc(sub_w, COPY, 0, 0);
CallProc(sub_w_copy, PRINTWINDOW, 0, 0); //打印窗口屬性
//驗證子類static變量的性質
printf("\n\n****** 驗證子類static變量的性質 ******\n");
CallProc(sub_w_copy, STATIC_SET, SUB_INT, 100); //通過複製的實例修改靜態變量
CallProc(sub_w, STATIC_GET, SUB_INT, 0); //通過另一個實例調用
printf("\n****** The total number of Objects: %d ******\n\n", getObjNum());
/****************** 創建win_c子類 ******************/
RegisterClass(win_c, sizeof(char), sizeof(char), sub_window_c);
int sub_w_c = sub_window_c((int)win_c, CONSTRUCT, (int)win_c_obj, 0); // 構造函數,可以創建實例,並初始化title、基類變量和實例附加變量
CallProc(sub_w_c, MEM_SET, SUB_CHAR, (int)'B'); //設置實例的子類附加變量
CallProc(sub_w_c, MEM_GET, SUB_CHAR, 0);
CallProc(sub_w_c, STATIC_SET, SUB_CHAR, (int)'S'); //設置實例所屬子類static變量
CallProc(sub_w_c, STATIC_GET, SUB_CHAR, 0);
CallProc(sub_w_c, PRINTWINDOW, 0, 0); //打印窗口屬性
//複製這個實例
int sub_w_c_copy = CallProc(sub_w_c, COPY, 0, 0);
CallProc(sub_w_c_copy, PRINTWINDOW, 0, 0);
printf("\n\n****** The total number of Objects: %d ******\n\n", getObjNum());
printf("\n****** The total number of SubClasses: %d ******\n\n", getSubClassNum());
/****************** 創建基類 ******************/
int base_w = CreateWindow(0);
CallProc(base_w, PRINTWINDOW, 0, 0); //打印初始化的基類
CallProc(base_w, SET_TITLE, (int)win_base_obj, 0); //窗口重命名
CallProc(base_w, SET_POSITION, 3, 4); //設置窗口位置
CallProc(base_w, MOVE, 2, 2); //移動窗口
CallProc(base_w, SET_SIZE, 5, 5); //設置窗口大小
CallProc(base_w, PRINTWINDOW, 0, 0); //打印窗口屬性
printf("\n****** The total number of Objects: %d ******\n\n", getObjNum());
return 0;
}