非常建议先看0x1

说起来我才发现,因为我超喜欢用波浪线,一大堆东西被替换成了删除线QAQ

前言

我们先来解决上一节中遗留下来的几个问题吧

UNUSED

由于在上一节中,我们强行规定了函数该长什么样。但是,有些时候我们可能并用不到某些参数,这个时候编译器就会报warning或error(取决你有没有开Wall、Werror、Wextra)。

为了保证编译器既不会报错,又能符合固定的函数原型,在此我们需要定义UNUSED宏。

1
2
3
4
5
6
7
8
#ifdef UNUSED
#elif defined(__GNUC__) || defined(MSVC)
#define UNUSED(x) UNUSED_##x __attribute__((unused))
#elif defined(__LCLINT__)
#define UNUSED(x) (void *)(x)
#else
#deifne UNUSED(x)(void *)(x)
#endif

该宏的作用是:若编译器有__attribute__((unused))选项时,则给该变量添加上unused属性,并重命名为UNUSED_var
其示例用法如下:

1
2
3
4
void foo(void *UNUSED(var1),int var2)   //var1可以不被使用,var2则必须被使用
{
int UNUSED(var3); //var3可以不被使用
}

简单的获取指针指向的数据

由于为了泛型,void *满天飞导致获取数据时需要大量的强制类型转换,对此我们可以加上个方便的宏:ptr2data

1
2
3
#ifndef ptr2data
#define ptr2data(type, x) (*((type *)x))
#endif

遗漏的operator

在上一期的operator里并没有自增和自减(主要因为作为参考的python里没有QAQ)

对此,我们在class定义里补上这几个函数

1
2
void (*operator_inc)(void *_this);
void (*operator_dec)(void *_this);

并在class.h里补上这两个函数声明

1
2
void inc(void *_this);
void dec(void *_this);

关于为什么不直接将inc(_this)链接到iadd(_this,1):说不定有人想重载呢对吧?

当然这里可以做一个fallback:若未定义operator_inc,就自动退化到iadd 1

用ioperator替换operator

ioperator:对自身进行操作
operator:复制一个新的对象并进行操作

我们发现,要调用operator,可以直接由操作符本身来复制对象,并对新对象来使用ioperator,这样我们实现类的时候又可以少实现一些了诶~!

但是我们这里还是保留最原始的用法吧~ 说不定会有人想重载呢?w~

目标

为了实现一部分的模板多态性,同时也是为了来基本的尝试一下使用,我们需要来实现一个iterator。

一个好的iterator必须具有以下的功能:

  • 能自增一、自减一
  • 能获取对应数据的地址
  • 能比较是否相等(关于比较大小(先后):前面的蛆以后再来探索吧?x)

于是,我们可以列出如下的一个todo-list:

  • constructor
  • deep_copy
  • destructor
  • operator_add
  • operator_iadd
  • operator_min
  • operator_imin
  • operator_eq
  • increase_iterator
  • decrease_iterator
  • iterator_data

实现class iterator

PS:在vs code上,不知道为什么直接将文件命名为iterator.h会导致无法解析(和C++的冲突了……?).对此,名称改为my_iterator.h

~辣鸡vscode~

让我们开始吧~!

iterator原型

首先,它需要继承class_base

其次,我们要提供一个将iterator自增、自减的模板函数,供子类来重载

最后,我们需要提供一个获取iterator指向的数据地址的模板函数,供子类来重载。

于是,iterator原型长这样:

1
2
3
4
5
6
typedef struct ITERATOR_BASE_T
{
class_base obj_base;
void *(*data)(void *this);

} iterator_base;

暴露的接口函数

我们需要:获得iterator指向指针的函数void *it_data(void *_this);

我们还需要暴露一些写好的iterator类方法,供定义iterator的子类时使用,也就是

1
2
3
4
5
6
void iterator_constructor(void *UNUSED(_this), va_list *UNUSED(ap_p));
void iterator_destructor(void *UNUSED(_this));
void iterator_operator_iadd(void *_this, va_list *ap_p);
void *iterator_operator_add(void *_this, va_list *ap_p);
void iterator_operator_imin(void *_this, va_list *ap_p);
void *iterator_operator_min(void *_this, va_list *ap_p);

这里可能会有些疑惑:为什么我们需要这么暴露呢?

举个栗子:我们现在定义了一个vector_iterator,显然,这个vector_iterator的operator_add/iadd/min/imin是应该直接使用它的父类iterator的,也就是:

但是,直接使用.operator_add=iterator->operator_add是不可行的,因为iterator并不是一个常量(哪怕你加满了const)

C语言并不将const视为常量!

所以,你必须暴露这些方法,以便让子类能很好的继承

这就是用C语言来写类的代价啊——

实现原型中的各个函数

data函数由子类实现

constructor和destructor

我们发现:其实new一个iterator对象并不需要构造函数(delete相似),因为一个iterator的模板里就没啥要初始化的

虽然我们可以留空(会自动赋值成null,因为函数原型在全局变量里),但最好我们还是实现一下,也就是用到UNUSED

1
2
3
4
5
6
void iterator_constructor(void *UNUSED(_this), va_list *UNUSED(ap_p))
{
}
void iterator_destructor(void *UNUSED(_this))
{
}

add和min

接下来的add就是直接重复调用inc就好(inc会被子类实现w),加上的数字可以从ap_p中获取;而iadd就是先copy一份自己,再调用add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void iterator_operator_iadd(void *_this, va_list *ap_p)
{
iterator_base *this = _this;
int inc_num = va_arg(*ap_p, int);
for (int i = 0; i < inc_num; i++)
{
this->obj_base.self_class->operator_inc(this);
}
}

void *iterator_operator_add(void *_this, va_list *ap_p)
{
iterator_base *this = _this;

iterator_base *res = copy(this);
res->obj_base.self_class->operator_iadd(res, ap_p);
return res;
}

min的实现类似

实现获取data指针的接口

1
2
3
4
5
void *it_data(void *_this)
{
iterator_base *this = _this;
return this->data(this);
}

“注册”iterator类

我们首先需要一个已经赋值好的iterator原型

1
2
3
4
5
6
7
8
class iterator_c = {
.type_size = sizeof(iterator_base),
.constructor = iterator_constructor,
.destructor = iterator_destructor,
.operator_add = iterator_operator_add,
.operator_iadd = iterator_operator_iadd,
.operator_min = iterator_operator_min,
.operator_imin = iterator_operator_imin};

接下来在.c文件中注册它

1
class *iterator = &iterator_c;

同时,我们需要在.h文件中用extern声明

1
extern class * iterator;

为什么这里一定要加extern?

C语言在include头文件时相当于直接复制。如果你不加extern的话,若最终代码中有多个代码都include了该头文件,就相当于该变量在多个文件中都被定义了,就会出现重定义的锅

而用了extern,就相当于告诉该变量是在别处定义。此时再在该.c文件中定义,就总体只会定义一次ouo

一件非常重要的事情!

你们或许会注意到:在operator(i.e. 非i的operator)内,都会将当前的拷贝一份,并返回新的

而拷贝的时候是会new一个新的对象的

同时原来的对象并不会被自动析构!

(与C++直接定义是不同的,毕竟C++不用new的话,每个对象都只是局部变量)

(而这里是每个都是new)

也就是说:a=add(a,b)的话,是会漏内存的!(原来的a并没有被析构

其实这也是C++中使用new会造成的事,只是一般很少在C++里的这种地方用new,就不会太注意罢了?

一个iterator的子类

由于这里的iterator类更像一个抽象类,为了方便理解,我这里给出vector的iterator实现,对照之下应该会更有益于理解~

vector_iterator原型(.h中)

1
2
3
4
5
6
7
8
9
extern class *vector_iterator;

typedef struct VECTOR_ITERATOR_T
{
iterator_base inhert; //继承

void *m_data; //指向的data位置
size_t data_size; //data的大小(自增时要用)
} vector_iterator_base;

vector_iterator内部函数实现:(.c中)

构造、析构函数

1
2
3
4
5
6
7
8
9
10
11
12
void vector_iterator_constructor(void *_this, va_list *ap_p)
{
vector_iterator_base *this = _this;

constructor(iterator, this, ap_p); //调用父类的构造函数(标准操作)
this->inhert.data = vector_iterator_data; //实现父类中的data函数(也就是获取data地址的函数)
}

void vector_iterator_destructor(void *_this)
{
destructor(iterator, _this); //调用父类的析构函数(标准操作)
}

这里提一句:一般而言,

构造函数:先调用父类的构造函数,再构造自己新增的部分

析构函数:先析构自己的部分,再调用父类的析构函数

operator实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void vector_iterator_inc(void *_this)   //自增
{
vector_iterator_base *this = _this;
this->m_data += this->data_size; //指向下一个元素的地址
}

void vector_iterator_dec(void *_this) //自减
{
vector_iterator_base *this = _this;
this->m_data -= this->data_size; //指向上一个元素的地址
}

bool vector_iterator_eq(void *_this, va_list *ap_p) //是否相等
{
vector_iterator_base *this = _this;

vector_iterator_base *that = va_arg(*ap_p, vector_iterator_base *); //用va_list巧妙传入了另一个的_this(为什么用va_list见0x1)

return (this->m_data == that->m_data) && (this->data_size == that->data_size); //当仅当指向地址与数据类型大小相同是,认为相等
}

data函数实现

1
2
3
4
5
void *vector_iterator_data(void *_this)
{
vector_iterator_base *this = _this;
return this->m_data;
}

注册vector_iterator

1
2
3
4
5
6
7
8
9
10
11
12
13
class vector_iterator_c = {
.type_size = sizeof(vector_iterator_base),
.constructor = vector_iterator_constructor,
.destructor = vector_iterator_destructor,
.operator_eq = vector_iterator_eq,
.operator_inc = vector_iterator_inc,
.operator_dec = vector_iterator_dec,
.operator_add = iterator_operator_add,
.operator_iadd = iterator_operator_iadd,
.operator_min = iterator_operator_min,
.operator_imin = iterator_operator_imin}; //注意到这里直接复用了父类的函数

class *vector_iterator = &vector_iterator_c; //注册vector_iterator类

尾声

有问题的话留言、发邮件、发推都是可以的~ 联系方式见主页哦

希望以上的东西能给你带来启发,要是能学到新的东西就再好不过了~

下一章,我们来尝试用一个很基础的容器练练手:vector