在 C 语言面向对象编程(一)里说到继承,这里再详细说一下。java
C++ 中的继承,从派生类与基类的关系来看(出于对比 C 与 C++,只说公有继承):编程
派生类内部能够直接使用基类的 public 、protected 成员(包括变量和函数)框架
使用派生类的对象,能够像访问派生类本身的成员同样访问基类的成员函数
对于被派生类覆盖的基类的非虚函数,在派生类中能够经过基类名和域做用符(::)来访问this
当使用基类指针调用虚函数时,会调用指针指向的实际对象实现的函数,若是该对象未重载该虚函数,则沿继承层次,逐级回溯,直到找到一个实现spa
上面的几个特色,咱们在 C 语言中可否所有实现呢?我以为能够实现相似的特性,但在使用方法上会有些区别。后面咱们一个一个来讲,在此以前呢,先说继承的基本实现。.net
先看 C 语言中经过“包含”模拟实现继承的简单代码框架:指针
[cpp] view plain copyorm
struct base{ 对象
int a;
};
struct derived{
struct base parent;
int b;
};
struct derived_2{
struct derived parent;
int b;
};
上面的示例只有数据成员,函数成员实际上是个指针,能够看做数据成员。 C 中的 struct 没有访问控制,默认都是公有访问(与 java 不一样)。
下面是带成员函数的结构体:
[cpp] view plain copy
struct base {
int a;
void (*func1)(struct base *_this);
};
struct derived {
struct base parent;
int b;
void (*func2)(struct derived* _this;
};
为了像 C++ 中同样经过类实例来访问成员函数,必须将结构体内的函数指针的第一个参数定义为自身的指针,在调用时传入函数指针所属的结构体实例。这是由于 C 语言中不存在像 C++ 中那样的 this 指针,若是咱们不显式地经过参数提供,那么在函数内部就没法访问结构体实例的其它成员。
下面是在 c 文件中实现的函数:
[cpp] view plain copy
static void base_func1(struct base *_this)
{
printf("this is base::func1\n");
}
static void derived_func2(struct derived *_this)
{
printf("this is derived::func2\n");
}
C++ 的 new 操做符会调用构造函数,对类实例进行初始化。 C 语言中只有 malloc 函数族来分配内存块,咱们没有机会来自动初始化结构体的成员,只能本身增长一个函数。以下面这样(略去头文件中的声明语句):
[cpp] view plain copy
struct base * new_base()
{
struct base * b = malloc(sizeof(struct base));
b->a = 0;
b->func1 = base_func1;
return b;
}
好的,构造函数有了。经过 new_base() 调用返回的结构体指针,已经能够像类实例同样使用了:
[cpp] view plain copy
struct base * b1 = new_base();
b1->func1(b1);
到这里咱们已经知道如何在 C 语言中实现一个基本的“类”了。接下来一一来看前面提到的几点。
第一点,派生类内部能够直接使用基类的 public 、protected 成员(包括变量和函数)。具体到上面的例子,咱们能够在 derived_func2 中访问基类 base 的成员 a 和 func1 ,没有任何问题,只不过是显式经过 derived 的第一个成员 parent 来访问:
[cpp] view plain copy
static void derived_func2(struct derived *_this)
{
printf("this is derived::func2, base::a = %d\n", _this->parent.a);
_this->parent.func1(&_this->parent);
}
第二点,使用派生类的对象,能够像访问派生类本身的成员同样访问基类的成员。这个有点变化,仍是只能经过派生类实例的第一个成员 parent 来访问基类的成员(经过指针强制转换的话能够直接访问)。代码以下:
[cpp] view plain copy
struct derived d;
printf("base::a = %d\n",d.parent.a);
struct derived *p = new_derived();
((struct base *)p)->func1(p);
第三点,对于被派生类覆盖的基类的非虚函数,在派生类中能够经过基类名和域做用符(::)来访问。其实经过前两点,咱们已经熟悉了在 C 中访问“基类”成员的方法,老是要经过“派生类”包含的放在结构体第一个位置的基类类型的成员变量来访问。因此在 C 中,严格来说,实际上不存在覆盖这种状况。即使定义了彻底同样的函数指针,也没有关系,由于“包含”这种方式,已经从根本上分割了“基类”和“派生类”的成员,它们不在一个街区,不会冲突。
下面是一个所谓覆盖的例子:
[cpp] view plain copy
struct base{
int a;
int (*func)(struct base * b);
};
struct derived {
struct base b;
int (*func)(struct derived *d);
};
/* usage */
struct derived * d = new_derived();
d->func(d);
d->b.func((struct base*)d);
如上面的代码所示,不存在名字覆盖问题。
第四点,虚函数。虚函数是 C++ 里面最有意义的一个特性,是多态的基础,要想讲明白比较困难,咱们接下来专门写一篇文章讲述如何在 C 中实现相似虚函数的效果,实现多态。
网友评论:
new_derived();的实现没给出来啊,是否是这样啊?
[cpp] view plain copy
struct derived * new_derived()
{
struct derived * d = malloc(sizeof(struct derived));
d->b = 0;
d->func2 = derived_func2;
d->parent = new_base();
return d;
}