C语言的结构体到类
Contact me
- Blog -> https://cugtyt.github.io/blog/index
- Email -> cugtyt@qq.com
- GitHub -> Cugtyt@GitHub
由于C++对C语言层面上的兼容性和相似性,可以比较明显的看出来,C++的类与C的结构体是大同小异的,除了C++的struct除了多了些东西,比如可以控制成员的可见范围,成员函数等似乎没太大区别。当然,对指针熟悉一些的话,可以把指针用来替代成员函数。这里我们将用python作为例子说说逻辑上,C的结构体基本可以实现python类的大体功能。
首先,类虽然是面向对象的概念,但是对于C而言,多做些封装即可实现大部分的要求。例如先说继承:
class Animal:
def __init__(self):
pass
class Cat(Animal):
pass
可以很容易想到,结构体当然可以做到:
struct Animal {};
struct Cat {
struct Animal;
};
当然面向对象的语言提供了一些语法糖,可以直接访问父类的字段,而这里C要多加一层来访问。但是一个潜在的好处是,你可以明确的区分你访问的变量是来自父类还是子类,而在python中,你则需要多加确认。多继承的方式也是很容易想到的,python要注意菱形继承的问题,而C虽然没有这个问题,但是代码复用却多了些限制。另一种多继承的方法是用一个列表来储存父类们,当然实现起来多少会有些复杂。同时你可能注意到,在python中,我们可以判断一个类是否属于某个父类,而在C中,你大概只能靠这个类有没有那个对应的结构体了(不嫌麻烦的话可以加一个字段来判断,当然这里可以把父类的结构体用指针来表示,用指针连接来判断,但是可能要注意空指针等情况)。
其次封装,类的一个基本功能就是封装。这里和struct没啥区别,但是注意到,一般像java,python,c++这些天生面向对象的语言一般都可以把函数封装到类里面。C怎么做呢?答案当然是函数指针了。
class Animal:
def func(self):
pass
int dosth(int param){}
struct Animal {
int (*func)(int);
};
struct Animal an;
an.func = &dosth;
虽说有些麻烦(毕竟人家语法糖),但是功能上是一致。这里值得注意的是,像java,c++等语言可以提供变量的访问控制,比如private,public等,这里C就不太容易做到。但是有趣的是,python中并没有这种东西,它的内容全部是公开的,意味着你可以访问所有的内部变量,基本上和C的结构体没啥区别了,使用的时候就是君子协定了。
你也许有时候奇怪为什么python中的方法都要有个self参数,但是又不用传入(当然有类方法、静态方法这种不需要)。这就很像C了,在结构体中,你可以让函数(函数指针)有一个参数为该结构体,这时候就可以之间访问具体结构体里面的变量了(要注意声明和实现的区别)。
最后是多态,大概可以认为是让同一类(签名相同)行为可以根据该类的性质(具体子类)表现出不同结果(具体实现)。比如:
class Animal:
def action(self):
print('animal')
class Cat(Animal):
def action(self):
print('cat')
class Dog(Animal):
def action(self):
print('dog')
这时候Cat和Dog都有相似的外形Animal,但是实际执行action的时候结果却不同。那么C怎么做呢?比较尴尬的是,C没法直接做。虽然我们是直接在结构体中加入父类结构体的,需要在调用的时候手工调用对应的函数。但是依旧没法保证Cat和Dog有相似的外形,即他们无法表现为同一个类。这是面向对象语言低层的语法糖,C无法直接做到。
由以上的分析可以想到,在C中借用一些面向对象的思想实现粗糙的类是可行的,毕竟思想是共通的。这里提一下C++,你可能知道在查看类占用空间的时候,函数并不占用空间,它是编译器来处理的。虚函数是占一个指针大小,注意是所有虚函数。所以可以推断出来,它维护了一个虚函数指针的列表,这个列表中每个元素就是一个函数,有点像我们在继承里面提到的多继承用列表来实现的方式。