前言
今天的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;
}