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

本文讨论了使用咖啡因和Guava缓存在Java中实施多层缓存以提高应用程序性能。它涵盖设置,集成和绩效优势,以及配置和驱逐政策管理最佳PRA

Java的类上载涉及使用带有引导,扩展程序和应用程序类负载器的分层系统加载,链接和初始化类。父代授权模型确保首先加载核心类别,从而影响自定义类LOA

本文使用lambda表达式,流API,方法参考和可选探索将功能编程集成到Java中。 它突出显示了通过简洁性和不变性改善代码可读性和可维护性等好处

本文讨论了使用JPA进行对象相关映射,并具有高级功能,例如缓存和懒惰加载。它涵盖了设置,实体映射和优化性能的最佳实践,同时突出潜在的陷阱。[159个字符]

本文讨论了使用Maven和Gradle进行Java项目管理,构建自动化和依赖性解决方案,以比较其方法和优化策略。

本文使用选择器和频道使用单个线程有效地处理多个连接的Java的NIO API,用于非阻滞I/O。 它详细介绍了过程,好处(可伸缩性,性能)和潜在的陷阱(复杂性,

本文使用Maven和Gradle之类的工具讨论了具有适当的版本控制和依赖关系管理的自定义Java库(JAR文件)的创建和使用。

本文详细介绍了用于网络通信的Java的套接字API,涵盖了客户服务器设置,数据处理和关键考虑因素,例如资源管理,错误处理和安全性。 它还探索了性能优化技术,我


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

PhpStorm Mac 版本
最新(2018.2.1 )专业的PHP集成开发工具

Dreamweaver CS6
视觉化网页开发工具

禅工作室 13.0.1
功能强大的PHP集成开发环境