一、前言
本文介绍一个编程语言中非常通用的概念,也就是结构体、类、接口这三个东西。
虽然各种编程语言中对它们的命名不一定一致,但其本质其实都是如此。
二、结构体
结构体,在C/C++语言中经常看到,其它语言中也常有,比如rust
、go
,而且基本都是用的关键字struct
来命名。
它的基本作用就是将一些相关的属性联合到一起,比如一个经典的例子就是学生。
一个学生是可以有很多属性的,比如姓名、性别、学号、成绩等等等等。
如果不将这些相关的属性进行联合,就会导致使用起来非常混乱,以C/C++代码为例:
string name1="小明";//学生1
int age1=10;
string name2="小红"; //学生2
int age2=20;
如果编程语言本身不提供将属性联合起来的功能(比如汇编语言似乎就没有这种功能),那么你就不得不这样分开写每个学生的信息,非常的麻烦,并且不直观。
而结构体这个功能就允许我们任意组合属于自己的类型:
struct Stu{
string name;
int age;
}
//使用
Stu s1; //学生1
s1.name="小明";
s1.age=10;
此时s1
的类型为我们自定义的Stu
类型,其中就组合了一个学生的基本信息,这样在使用起来就会非常的方便,符合我们一贯的直觉操作。
三、类
结构体虽然好,但也有不足,因为现实世界中,一个对象不仅仅有属性,也会有行为。
还是以上面的那个学生为例,如果我想要打印每个学生自己的姓名、年龄,一般就会像下面这样做:
void print(Stu s){
printf("%s,%d\n",s.name.data(),s.age);
}
Stu s1; //学生1
s1.name="小明";
s1.age=10;
print(s1);
即:定义一个函数,专门来打印这个结构体,需要的时候调用就行了。
但此时你会发现,此时这个print
函数参数被固定为了Stu
类型,这意味着,这个函数只能被用于这个结构体。
既然如此,那为什么不让结构体把这个函数也组合进去呢?
这就可以被称为类了,一般都是以关键字class
来定义的:
class Stu{
public:
string name;
int age;
void print(){
printf("%s,%d\n",name.data(),s.age);
}
}
一般高级语言都会有类的概念,比如C++、python、Java等等,其中Java更是其中的极端代表,以万物皆对象为基本理念,即使是最基本的数据类型,比如int
类型的数字,在Java中都是一个类。
类的基本理念其实就是将属性、行为封装到了一起,这符合人一贯的现实世界直觉:
Stu s1; //学生1
s1.name="小明";
s1.age=10;
s1.print(); //调用这个对象上的行为、也就是方法、函数,
当然,这仅是类的基本内容,更多的还有继承、权限、多态等概念。
但事实上,有了上面的基本知识,你就已经完成能够开始正常写代码了。
而这些继承、权限、多态等概念,其本质上都只是在进一步简化代码、提升代码安全性而已,即使不用,你也一样可以写代码。
但这涉及到了各种语言的细节,这里就不再过多赘述了。
四、接口
相比于结构体、类这种各种语言都很统一的概念,接口就很有意思了, 因为它在各种语言中的概念都不是很统一,但其本质上又都代表了一种东西:方法的集合。
比如Java中专门有一个声明接口的关键字:Interface
。
go中的关键字与之相同:interface
。
rust语言有个类似的东西为Trait
,即特性、特征的意思,声明关键字为:trait
。
C++中有个概念叫做纯虚类,使用方式就是一个类中的所有方法都是虚函数:virtual
。
可以看到,在现代语言中,基本都会有接口这种概念,但并不一定会有类的概念。
比如go、rust,这两个本身是没有类的概念的,但可以通过其它方式来实现类的功能。
它的主要作用在于抽象一个方法集合,便于程序员使用与实现。
举个例子,车会跑、动物会跑,这两个虽然一看就不是一类的,但都可以跑。
再进一步抽象一下,跑这个行为本身,其实就是物体有速度,那么此刻你就发现,几乎任何东西都可以有这个行为了。
但此时采取以往类的方式,就会出现一个问题,比如我们想要定义一个跑的函数,那么车、动物你可能使用的函数名是:run
,而飞机你使用的函数名大概率就是flight
了,又或者海生动物那就是swimming
。
这缺乏通用性,如果作为用户想要使用某个类,就必须研究一下这个类有哪些特性才行,但类是多变的,就会导致这个过程是耗时的。
而接口就不一样了,上面的一切行为我都可以定义为一个move
的函数名,都是在移动嘛!
然后我可以将这个接口命名为MoveAct
,即移动行为的意思。
而这些类、结构体,就不用单独实现自己的run
、flight
、swimming
等方法了,而是可以直接来实现这个MoveAct
接口,进而也就需要来实现其中这个move
函数的内容。
此时用户一看你类实现了这个MoveAct
接口,那还有必要看你类是怎么实现的吗?完全没必要嘛!
我们只需要看一下这个MoveAct
接口里面有哪些方法,只要你的类、结构体使用了这个接口,那就必然有这个方法,此时我就完全不用管你这个类是怎么实现的了!
因为无论是什么类,最后都是通过move
这个函数来完成移动这个行为的!
在rust中将其定义为Trait
,即特性,这也是一个很符合感觉的概念,因为事实上确实如此,这些行为本身就可以看作一个有一个特性,即使是不同类,也是可以有同一种特性的!
五、简单总结
本文并没有具体讨论各种语言本身实现结构体、类、接口这三者的技术细节以及使用方式,而是更加侧重于从总体上感受、理解这三者的缘由与关系。
简单来说就是:
- 结构体:属性的集合
- 接口:方法的集合
- 类:属性与方法共同的集合
结构体不用说,这是必然需要的,因为我们有集合各种属性的需求。
而类这个概念,如今其实已经在慢慢势颓了,如今很火的go
、rust
等新兴语言,已经不再原生支持类了。
大家不约而同的都在向接口这个概念开始转变,甚至Java这个原本以类为基石的语言,如今都已经开始被戏称为面向接口编程的语言了。