Crest简单对象的设计
在我们开始Crest的设计之前,我们先看一段参考代码
using System;
using System.Collections.Generic;
public abstract class Bird{
protected String name;
public abstract String tweet();
public String getName(){return name;}
}
public class Cock : Bird{
public override String tweet(){return "woooooo";}
public virtual String walk(){return "cock walk";}
public void setName(String newName){ name = newName;}
}
public class MainClass
{
public static void Main()
{
Bird bird = new Cock();
Cock cock = new Cock();
System.Console.WriteLine(bird.tweet());
}
}
这是一段纯正的面向对象的代码,我们的话题就会沿着这一段代码展开。首先我们从设计Crest以支持最简单的对象。
简单对象
因为C语言的本身限制,所以我们要模拟一个类的定义只能使用struct。最简单的类当然就是空类了。我们的C代码如下:
struct CEmptyObject
{
}
然后我们要让我们的CEmptyObject类加入成员变量,也很简单:
struct CEmptyObject
{
int salary;
};
再加入一个成员函数吧。我想加入一个getSalary,但是问题来了,怎么加呢?如果这样写:
int getSalary(){return salary;}
似乎是对的,但是C编译器报告salary找不到,如果把这行代码放到struct CEmptyObject中,同样也是编译错误[BCC32 Error] raw.c(143): E2200 Functions may not be part of a struct or union。实际上,大部分OO语言的实现都是类似的,我们也就不卖关子了,照搬如下:
int getSalary(struct CEmptyObject * _this)
{
return _this->salary;
}
所有我们代码中写的 xxx.getSalary() 类似的代码,都实际转化为getSalary(xxx)形式,如果大家熟悉C#的扩展函数,就更明白这一点。
简单对象的使用
设计好了简单对象,我们当然要用一下了,先看代码:
int simpleMain()
{
struct CEmptyObject obj;
printf("%d", getSalary(&obj));
}
问题来了,构造函数呢?没关系,先凑合用一下吧,我们用C风格的初始化方法,代码如下:
int simpleMain()
{
struct CEmptyObject obj = {3000};
printf("%d", getSalary(&obj));
}
运行正常!但是万一我们要用构造函数怎么办?没关系,构造函数其实就是一种特殊的成员函数而已,那就加上吧:
void CEmptyObject(struct CEmptyObject * _this, int salary)
{
_this->salary = salary;
}
int simpleMain()
{
struct CEmptyObject obj;
CEmptyObject(&obj, 3000);
printf("%d", getSalary(&obj));
}
运行还是正常!不过,OO语言里不是经常有new动作吗?我们现在模拟的对象,都实际创建在stack中,如果我们要用new创建到heap中呢?那就来模拟new吧!
struct CEmptyObject * new(size_t size)
{
return (struct CEmptyObject *)malloc(size);
};
int simpleMain()
{
struct CEmptyObject obj, *obj2;
CEmptyObject(&obj, 3000);
obj2 = new(sizeof(struct CEmptyObject));
CEmptyObject(obj2, 4000);
printf("%d", getSalary(&obj));
printf("%d", getSalary(obj2));
}
运行,看起来正常!如果你打开监控,会有报告内存泄露,因为我们的new函数用malloc来分配了内存,而退出的时候我们没有释放它。所以我们需要同时实现一个delete函数,但是先别动手,我们要继续谈一下析构函数的问题。从本质上,析构函数也是一个特殊的成员函数,但是带来的问题是这个析构函数何时调用。对于C#、java等有垃圾收集功能的语言来说,析构函数会在销毁之前调用(深究则不然,后文会谈),C++则会在对象退出作用域之前自动调用,但是这里则要小心了,因为我们用手工调用。
void CEmptyObject(struct CEmptyObject * _this, int salary)
{
_this->salary = salary;
}
void _CEmptyObject(struct CEmptyObject * _this)
{
_this->salary = 0;
}
struct CEmptyObject * new(size_t size)
{
return (struct CEmptyObject *)malloc(size);
};
void delete(struct CEmptyObject *_this)
{
_CEmptyObject(_this);
free(_this);
}
int simpleMain()
{
struct CEmptyObject obj, *obj2;
CEmptyObject(&obj, 3000);
obj2 = new(sizeof(struct CEmptyObject));
CEmptyObject(obj2, 4000);
printf("%d", getSalary(&obj));
printf("%d", getSalary(obj2));
delete(obj2);
_CEmptyObject(&obj);
}
代码运行正常。可是大家是不是觉得这样太繁琐了阿?写这么多杂七杂八的东西,看起来一点都不简洁。而且bigtall一向认为一个优秀的程序员必定是"懒惰"的,他应该对任何形式的"重复工作"都无法容忍。所以我们需要用C语言的"宏定义"大法来改善代码的可读性。
上一篇: Crest-大家都来山寨一个GObject吧 下一篇: Crest的语法---宏的魔术汇演

公众号:老翅寒暑
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了