zoukankan      html  css  js  c++  java
  • C语言实现一个泛型的vector

    问题描述:

    使用纯$C$语言实现一个泛型的$vector$,支持拷贝构造和移动构造。

    设计方案:

    $vector$是动态的数组,因此我们保存$vector$申请的内存块的指针,此外我们需要两个$size$_$t$类型的数保存当前开辟的空间和当前已经存有的元素个数。故需要一个我们定义以下的$vector$结构体:

    struct vector
    {
    	T* buf;
    	size_t size, capacity;
    };
    

     由于我们设计的是泛型$vector$,$T$的类型不定,所以一个元素占用的内存不定,所以这个内存块的大小就不能,考虑两种方案:

    方案一、

    在$vector$中加入一个$u$_$size$表示元素的大小。

    那我们就可以设计成这样子:

    struct vector
    {
    	T* buf;
    	size_t size, capacity;
    	size_t u_size;
    };
    

     这样子$push$_$back$或者是复制$vector$的时候就可以计算出$malloc$的空间是$n*u$_$size$。

    但是有两个问题:

    问题一、

    考虑这个结构体:

    struct T
    {
    	void* buf;
    	size_t val;
    };
    

     在$push_back$的时候,就会有以下问题:

    复制前:

    复制后:

    问题二:

    如果我们需要复制这个$vector$,也会有类似的问题:

    复制前:

    复制后:

    显然这个$buf$存在危险,它会被几个指针访问,可能会造成数据的错误。(C里面没有$unique$_$ptr<T>$和$shared$_$ptr<T>$)。

    同时,析构的时候,$buf$这一块内存极易引发内存泄漏或者是重复释放。

    那我们如何改进呢:

    改进方案一:

    如果在$push$_$back$的时候传入构造函数的函数指针,如:

    $size$_$t$ $push$_$back(vector*$ _$vec,$ $const$ $T* $ _$dat,$ $T*(*copy$_$assign)(const$ $T*$ _$src))$。然后在$push_back$函数中调用$copy$_$assign$,这样子就可以成功解决上面的问题一。析构的时候也是一样的道理,传入析构函数的函数指针即可。在复制和销毁$vector$的时候,我们也是同样的传入这些函数指针。就解决了问题二。

    这个方案已经够好了,但是,它太麻烦了,每次都要传函数指针。

    改进方案二:

    我们为什么不把它保存下来呢,然后$vector$不就可以自动调用了嘛?

    有一说一,确实。

    这样子我们设计一下这个结构体:

    struct vector
    {
    	T* buf;
    	size_t size, capacity;
    	size_t u_size;
    	void* (*assign)(const void* _src);
    	void* (*destroy)(void* _dat);
    };
    

     这样子就可以了,实际上是空间换时间的策略。

    但是如果函数指针多起来了,这个$vector$结构体在初始化这些函数指针的时候,就会非常麻烦,比如说你要加上移动构造函数,或者其他函数的时候。

    所以我们就把它封装一下好了。

    struct data_arg
    {
    	size_t u_size;
    	void* (*assign)(const void* _src);
    	void* (*destroy)(void* _dat);
    };
    struct vector
    {
    	T* buf;
    	size_t size, capacity;
    	data_arg dat_arg;
    };
    

     这样子需要修改函数指针的时候直接修改$dat$_$arg$就可以了。

    方案二:

    我们使用一个$void*$去指向这些元素,这样子无论元素是什么是什么类型的我们都可以指向它。

    我们设计以下的结构体:

    struct vector
    {
    	void** buf;
    	size_t size, capacity;
    };
    

     显然,方案一上的两个问题,方案二依然存在。而且无论如何,复制的时候一样需要知道元素的大小。

    所以我们就集思广益,把方案一的操作搬下来。

    struct vector
    {
    	void** buf;
    	size_t size, capacity;
    	data_arg dat_arg;
    };
    

     这样子我们也解决这两个问题。

    ???

    $Q$:这两个方案既然都行,那我们随便选一个是不是就可以了。

    $A$:大多数情况是这样的。但是我们可以注意到:方案一少了一层指针寻址,效率会更高,但是方案一进行移动构造的时候,它实际上相当于是$memcpy$。而方案二虽然需要进行多一层寻址,但是移动的时候可以直接把指向这个元素的指针赋给$vector$。但是其他情况仍需重写函数。所以根据应用场景灵活选择方案即可。

    接下来的举例实现使用第二种方案。

    !!!

    那我们开始实现吧:

    结构体定义,不说了。

    struct data_arg
    {
    	size_t u_size;
    	void* (*assign)(const void* _src);
    	void* (*destroy)(void* _dat);
    };
    struct vector
    {
    	void** buf;
    	size_t size, capacity;
    	data_arg dat_arg;
    };
    

     然后我们开始设计函数:

    (注:$catch$_$exec$是我的项目的一个异常处理的库,看官自行忽略就好)

    首先是创建:

    vector* vec_init(data_arg _dat_arg)
    {
    	vector* ptr = (vector*)malloc(sizeof(vector));
    	*ptr = { (void**)malloc(sizeof(void*) * vec_init_size),
    		0,vec_init_size,_dat_arg 
    	};
    	return ptr;
    }
    

    比较简单,不说了。

    然后实现调用数据的拷贝构造函数和析构函数的函数:

    void* vec_new_data(vector* _vec, const void* _dat)
    {
    	if (!_vec->dat_arg.assign)
    	{
    		void* dst = malloc(_vec->dat_arg.u_size);
    		memcpy(dst, _dat, _vec->dat_arg.u_size);
    		return dst;
    	}
    	return _vec->dat_arg.assign(_dat);
    }
    void* vec_delete_data(vector* _vec, void* _dat)
    {
    	if (!_vec->dat_arg.destroy)
    		free(_dat);
    	else
    		_vec->dat_arg.destroy(_dat);
    	return NULL;
    }
    

     注:我自己的设计是如果是不含指针的结构体,就直接复制内存,这样子拷贝构造函数和析构函数指针都是$NULL$,效率高一点。

    然后是拷贝构造函数:

    void* vec_assign(const void* _vec)
    {
    	if (!_vec)
    		catch_exce(6);
    	vector* vec = (vector*)_vec;
    	vector* newvec = vec_init(vec->dat_arg);
    	vec_resize(newvec, vec->capacity);
    	newvec->size = vec->size;
    	for (int i = 0; i < newvec->size; ++i)
    		newvec->buf[i] = vec->dat_arg.assign(vec->buf[i]);
    	return newvec;
    }
    

     然后是清空和析构函数二人组:

    void vec_clear(vector* _vec)
    {
    	if (!_vec)
    		catch_exce(6);
    	for (int i = 0; i < _vec->size; ++i)
    		_vec->buf[i] = vec_delete_data(_vec, _vec->buf[i]);
    }
    void* vec_destroy(void* _vec)
    {
    	if (!_vec)
    		catch_exce(6);
    	vector* vec = (vector*)_vec;
    	vec_clear(vec);
    	free(vec->buf);
    	free(vec);
    	return NULL;
    }
    

     然后剩下的功能自己实现一下就完事了。

    检查容量:

    void vec_check_capacity(vector* _vec)
    {
    	if (!_vec)
    		catch_exce(6);
    	if (_vec->size == _vec->capacity)
    		vec_resize(_vec, _vec->capacity << 1);
    }
    

    重设容量:

    size_t vec_resize(vector* _vec, size_t _size)
    {
    	if (!_vec)
    		catch_exce(6);
    	void** tmp = (void**)realloc(_vec->buf, sizeof(void*) * _size);
    	if (!tmp)
    		catch_exce(6);
    	_vec->buf = tmp, _vec->capacity = _size;
    	return _vec->capacity;
    }
    

     $push$_$back$(拷贝),$push$_$back$_$no$_$copy$(移动)

    size_t vec_push_back(vector* _vec, void* _dat)
    {
    	if (!_vec)
    		catch_exce(6);
    	if (!_dat)
    		catch_exce(7);
    	vec_check_capacity(_vec);
    	_vec->buf[_vec->size++] = vec_new_data(_vec, _dat);
    	return _vec->size;
    }
    size_t vec_push_back_no_copy(vector* _vec, void* _dat)
    {
    	if (!_vec)
    		catch_exce(6);
    	if (!_dat)
    		catch_exce(7);
    	vec_check_capacity(_vec);
    	_vec->buf[_vec->size++] = _dat;
    	return _vec->size;
    }
    

     $pop$_$back$(析构),$pop$_$back$_$no$_$delete$(不析构)

    size_t vec_pop_back(vector* _vec)
    {
    	if (!_vec)
    		catch_exce(6);
    	_vec->buf[--_vec->size] = vec_delete_data(_vec, _vec->buf[_vec->size]);
    	return _vec->size;
    }
    size_t vec_pop_back_no_delete(vector* _vec)
    {
    	if (!_vec)
    		catch_exce(6);
    	_vec->buf[--_vec->size] = NULL;
    	return _vec->size;
    }
    

     注:没有检查边界,看官自己加上。

    感谢大家!

    全部源代码看这里。这是我自己用纯$C$语言实现的一个$C$语言子集的词法和语法分析器项目,里面需要使用泛型$vector$,故设计。

  • 相关阅读:
    2020软件工程第三次作业
    2020软件工程作业02
    2020软件工程作业01
    2020软件工程个人作业06——软件工程实践总结作业
    2020软件工程作业05
    2020软件工程作业00——问题清单
    2020软件工程作业04
    2020软件工程作业03
    2020软件工程作业02
    2020软件工程作业01
  • 原文地址:https://www.cnblogs.com/Aya-Uchida/p/12556365.html
Copyright © 2011-2022 走看看