我一直对计算机充满好奇,总是会想:“好吧,我知道怎么用,但它到底是怎么工作的?”在这个过程中,我经常会做个思想实验:如果让我从零开始实现它,我会怎么做?在本文中,我们将探讨接口在面向对象编程中的工作原理(使用Java),然后在C语言中实现一个简陋的接口版本。
我们的例子很简单:计算车辆的价格。如果是汽车,价格将根据其最高速度计算;如果是摩托车,价格将根据其排量计算。我们首先用接口定义车辆的行为:
<code class="language-java">public class Main { public interface Vehicle { Integer price(); } }</code>
这里没什么特别的,只是一个返回整数的方法。现在让我们实现汽车类:
<code class="language-java">public class Main { // ... public static class Car implements Vehicle { private final Integer speed; public Car(Integer speed) { this.speed = speed; } @Override public Integer price() { return speed * 60; } } }</code>
很经典:一个构造函数和price方法的实现,将速度乘以60。现在,让我们实现摩托车类:
<code class="language-java">public class Main { // ... public static class Motorcycle implements Vehicle { private final Integer cc; public Motorcycle(Integer cc) { this.cc = cc; } @Override public Integer price() { return cc * 10; } } }</code>
几乎一样,唯一的区别是现在我们将排量乘以10。然后,我们实现一个打印车辆价格的方法:
<code class="language-java">public class Main { // ... public static void printVehiclePrice(Vehicle vehicle) { System.out.println("$" + vehicle.price() + ".00"); } }</code>
没什么秘密。最后,我们的main方法:
<code class="language-java">public class Main { // ... public static void main(String[] args) { Car car = new Car(120); Motorcycle motorcycle = new Motorcycle(1000); printVehiclePrice(car); printVehiclePrice(motorcycle); } }</code>
<code>$ java Main.java 00.00 000.00</code>
这就是我们想要达到的模型,但是现在要在C语言中从零开始实现。
当我想到对象时,首先想到的是一组表示状态的数据和操作和管理该状态的方法。在C语言中表示数据集合最直接的方法是结构体。对于方法,最接近的方法是接收状态作为参数的函数。此状态将对应于类中的this,例如:
<code class="language-c">typedef struct { int height_in_cm; int weight_in_kg; } Person; float person_bmi(Person *person) { float height_in_meters = (float)person->height_in_cm / 100; float bmi = (float)person->weight_in_kg / (height_in_meters * height_in_meters); return bmi; }</code>
在这里,我们在Person结构体中定义了一个人的数据,并使用这些数据进行简单的计算。这是我们在C语言中可以拥有的最接近类的结构。也许在结构体中使用函数指针也是一个好主意?好吧,这留到下一篇文章再讨论。
好的,我们有了一种类似类的结构。现在,我们如何在C语言中定义接口?如果仔细想想,编译器/解释器不会做魔法来猜测哪些类实现了接口。它可以在编译时确定这一点,并将我们使用接口的所有部分替换为具体的类型。在编译后的程序中,接口甚至不存在。
由于C语言编译器没有提供这种可能性,我们必须自己实现这个方案。我们需要知道所有实现我们接口的类型,并想办法使用这些实现的函数。
首先,让我们定义我们简陋接口的框架。我们将创建一个枚举,其中包含不同的实现和我们函数的签名。
<code class="language-java">public class Main { public interface Vehicle { Integer price(); } }</code>
在这里,我们定义了我们的枚举,其中包含我们稍后将要实现的实现。这看起来可能不像,但这部分非常重要。接下来,我们声明了vehicle_free函数(稍后将解释)和vehicle_price函数,我们希望在我们的“类”中实现这些函数。现在让我们来看汽车的实现:
<code class="language-java">public class Main { // ... public static class Car implements Vehicle { private final Integer speed; public Car(Integer speed) { this.speed = speed; } @Override public Integer price() { return speed * 60; } } }</code>
car_init函数在内存中初始化一个新的“对象”Car。在Java中,这将通过new自动完成。在这里,我们需要手动完成。vehicle_free函数将用于释放之前初始化的任何“对象”分配的内存,使用car_free等实现。摩托车的实现非常相似:
<code class="language-java">public class Main { // ... public static class Motorcycle implements Vehicle { private final Integer cc; public Motorcycle(Integer cc) { this.cc = cc; } @Override public Integer price() { return cc * 10; } } }</code>
几乎一样,只是现在我们用VEHICLE_MOTORCYCLE初始化,并乘以10。现在让我们来看打印车辆价格的函数:
<code class="language-java">public class Main { // ... public static void printVehiclePrice(Vehicle vehicle) { System.out.println("$" + vehicle.price() + ".00"); } }</code>
如此简单……这样看来,我们似乎没有做太多工作。现在,最后也是最重要的一点,我们必须实现我们在上面接口定义中声明的函数,还记得吗?幸运的是,我们甚至不需要考虑这个实现。我们总会有一个简单的穷举switch/case,仅此而已。
<code class="language-java">public class Main { // ... public static void main(String[] args) { Car car = new Car(120); Motorcycle motorcycle = new Motorcycle(1000); printVehiclePrice(car); printVehiclePrice(motorcycle); } }</code>
现在我们可以使用我们所做的一切:
<code>$ java Main.java 00.00 000.00</code>
<code class="language-c">typedef struct { int height_in_cm; int weight_in_kg; } Person; float person_bmi(Person *person) { float height_in_meters = (float)person->height_in_cm / 100; float bmi = (float)person->weight_in_kg / (height_in_meters * height_in_meters); return bmi; }</code>
成功了!但是你可能会想:“好吧,这有什么用?”
我最喜欢的项目类型之一是解析器,从解释器到简单的数学表达式解析器。通常,当您实现这些解析器时,会遇到称为AST(抽象语法树)的东西。顾名思义,它是一棵树,它将表示您正在处理的语法,例如,变量声明int foo = 10; 是AST的一个节点,它包含另外三个节点,一个类型节点,用于int,一个标识符节点,用于foo,以及一个表达式节点,用于10,该节点包含另一个值为10的整数节点。看到它有多复杂了吗?
当我们在C语言中这样做时,我们必须在包含多个字段的巨大结构体之间进行选择,以表示任何可能的AST节点,或者使用多个小型结构体实现抽象定义,每个结构体表示不同的节点,就像我们在这里用我们的“接口”所做的那样。如果您想查看一个简单的示例,在这个数学表达式解析器中,我实现了第二种方法。
编译器或解释器所做的任何事情都不是魔法。尝试自己实现一些东西总是一个有趣的练习。希望这是一篇有益的阅读。谢谢!
以上是C语言中的面向对象?从头开始实现接口。的详细内容。更多信息请关注PHP中文网其他相关文章!