您的位置:首页 > 编程语言 > C语言/C++

c语言实现封装,继承和多态

2013-07-05 22:57 260 查看
封装


c语言使用的struct没有访问控制,任何程序都能访问struct的成员。为了隐藏struct里的成员名称和位置,可以将具体的struct定义放在.c文件中,而在头文件中增加一个指向该结构体的指针。因为无法得知指针类型的具体定义,对这个私有结构体成员的访问只能通过提供的set()和get()函数。

下面是头文件test.h的定义:

#ifndef __TEST_H__
#define __TEST_H__

struct test {
struct _test* t;
};

int test_constructor(struct test*);
int test_get(struct test*);
void test_set(struct test*, int);
void test_destructor(struct test*);

#endif
对外提供的结构体信息只有一个指向struct _test的指针,由于struct _test在.c文件中定义,其它程序无法知道它的具体定义。下面是实现具体功能的test.c:

#include <stdlib.h>
#include "test.h"

struct _test {
int v;
};

int test_constructor(struct test* t)
{
t->t = calloc(1, sizeof(struct _test));
if (!t->t)
return -1;

return 0;
}

int test_get(struct test* t)
{
return t->t->v;
}

void test_set(struct test* t, int v)
{
t->t->v = v;
}

void test_destructor(struct test* t)
{
free(t->t);
}


像c++和java这些提供访问权限控制的语言直接把私有变量的定义暴露在头文件中也没关系,因为其它程序不能直接访问这些私有变量。而在c中就只能通过隐藏具体细节来实现这样的功能了,但是如果知道struct _test的结构定义细节,那么完全可以绕过所提供的set()和get()函数。

继承

c中没有像c++那样的继承功能,如果一个类要继承另一个类,一个办法是将另一个类作为它的一个成员,也就是c++中所说的组合:
struct base1 {
int i1;
};

struct base2 {
double d1, d2;
};

struct derive {
int mine;
struct base1 b1;
struct base2 b2;
};


多态

c中实现多态的功能利用了结构体的内存布局,即结构体内每个域的内存位置是按照定义的顺序从低地址往高地址顺序存放的(可能会有字节对齐的情况,但不影响对每个域的访问)。在linux内核中用到了这个特性,下面两个宏的使用在代码里随处可见:

#define offset_of(type, member) \
((unsigned long)(&(((type*)0)->member)))

#define container_of(ptr, type, member) \
((type*)((unsigned long)(ptr) - offset_of(type, member)))


宏offset_of()的作用是返回某个成员member在结构体type中的偏移量。先看后面括号的部分:((type*)0)->member。这部分的意思是把位置0转换成一个指向type的指针,然后访问成员member的值。如果直接执行这句话肯定会段异常,因为位置0是NULL指针。但是在前面加了取地址符号:&(((type*)0)->member),这样就变成了“取位于地址0的结构体type的成员member的地址”。这里只是取member的地址,而不是访问member的内容,因此并不会触发访问异常。把结构体的起始位置设为0,得到的地址就是member在结构体内的偏移量:
+---------+------------+-----------+
|   ...   |   member   |    ...    |
+---------+------------+-----------+
^         ^
|         |
0         offset_of(type, member)


最后把地址值转换成unsigned long就是member的相对偏移量(c中规定指针的值就是一个unsigned long类型的整数,sizeof(void*) == sizeof(unsigned long))。

这样第二个宏container_of()的作用就好理解了:取得当前指向member的指针ptr,减去member的相对偏移量,得到的就是结构体type的起始位置:
container_of(ptr, type, member)
|
|        ptr
|         |
v         v
+---------+------------+-----------+
|   ...   |   member   |    ...    |
+---------+------------+-----------+
\---v---/
|
offset_of(type, member)


了解了结构体的布局之后来看一个例子:三角形,圆形,正方形等都可以看成是一种形状,它们都有一个名字属性name,还有一个操作draw()。除此之外它们还有各自的特殊属性:三角形有三条边,圆形有半径,正方形有边长。我们先定义一个基类shape和对应的虚函数表shape_operations:

struct shape {
const char* name;
struct shape_operations* sops;
};

struct shape_operations {
void (*draw)(struct shape*);
};
然后派生出一个三角形的类:
struct triangle {
int a, b, c;
struct shape base;
};

static void draw_triangle(const struct shape* s)
{
struct triangle* this = container_of(s, struct triangle, base);
printf("draw a %s: %d\t%d\t%d\n", s->name, this->a, this->b, this->c);
}

static struct shape_operations triangle_operations = {
.draw = draw_triangle,
};

void triangle_constructor(struct triangle* t, int a, int b, int c)
{
t->a = (a > 0) ? a : 1;
t->b = (b > 0) ? b : 1;
t->c = (c > 0) ? c : 1;
t->base.name = "triangle";
t->base.sops = &triangle_operations;
}
其中把派生类实现的draw_triangle()的地址赋值给虚函数表对应的入口->draw(),在draw_triangle()中使用container_of()通过指向基类的指针s获得指向实际的struct
triangle结构体的指针this。类似地还有圆形:

struct circle {
int r;
struct shape base;
};

static void draw_circle(const struct shape* s)
{
struct circle* this = container_of(s, struct circle, base);
printf("draw a %s: r = %d\n", s->name, this->r);
}

static struct shape_operations circle_operations = {
.draw = draw_circle,
};

void circle_constructor(struct circle* c, int r)
{
c->r = (r > 0) ? r : 1;
c->base.name = "circle";
c->base.sops = &circle_operations;
}

和正方形:
[/code]
struct square {
int e;
struct shape base;
};

static void draw_square(const struct shape* s)
{
struct square* this = container_of(s, struct square, base);
printf("draw a %s: e = %d\n", s->name, this->e);
}

static struct shape_operations square_operations = {
.draw = draw_square,
};

void square_constructor(struct square* s, int e)
{
s->e = (e > 0) ? e : 1;
s->base.name = "square";
s->base.sops = &square_operations;
}


最后是测试:

void draw_shape(const struct shape* s)
{
s->sops->draw(s);
}

int main(void)
{
struct triangle t;
struct circle c;
struct square s;

triangle_constructor(&t, 5, 5, 5);
draw_shape(&t.base);
circle_constructor(&c, 5);
draw_shape(&c.base);
square_constructor(&s, 3);
draw_shape(&s.base);

return 0;
}


除了实现单一继承外还可以实现多重继承,基类成员可以放在结构体中的任何位置。还有就是程序中没出现void*,看起来算是比较顺眼。

另外有一本叫《Object-Oriented Programming
With ANSI-C》的小册子,有兴趣的可以看一下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: