我正在尝试使用C实现queue
结构。我的实现非常简单;队列只能容纳int
s而不能保留其他内容。我想知道我是否可以在C++
中模拟C
模板(可能使用预处理器#define
),以便我的queue
可以保存任何数据类型。
注意:我不想使用void*
。我认为它有点风险,很容易造成奇怪的运行时错误。
答案 0 :(得分:44)
您可以使用微妙和丑陋的技巧来创建这种模板。这就是我要做的事情:
我首先要创建一个宏 - 让我们称它为define_list(type)
- 这将为给定类型的列表创建所有函数。然后,我将创建一个包含指向所有列表函数的函数指针的全局结构,然后在列表的每个实例中都有一个指向该全局结构的指针(注意它与virtual method table的相似程度)。这种事情:
#define define_list(type) \
\
struct _list_##type; \
\
typedef struct \
{ \
int (*is_empty)(const struct _list_##type*); \
size_t (*size)(const struct _list_##type*); \
const type (*front)(const struct _list_##type*); \
void (*push_front)(struct _list_##type*, type); \
} _list_functions_##type; \
\
typedef struct _list_elem_##type \
{ \
type _data; \
struct _list_elem_##type* _next; \
} list_elem_##type; \
\
typedef struct _list_##type \
{ \
size_t _size; \
list_elem_##type* _first; \
list_elem_##type* _last; \
_list_functions_##type* _functions; \
} List_##type; \
\
List_##type* new_list_##type(); \
bool list_is_empty_##type(const List_##type* list); \
size_t list_size_##type(const List_##type* list); \
const type list_front_##type(const List_##type* list); \
void list_push_front_##type(List_##type* list, type elem); \
\
bool list_is_empty_##type(const List_##type* list) \
{ \
return list->_size == 0; \
} \
\
size_t list_size_##type(const List_##type* list) \
{ \
return list->_size; \
} \
\
const type list_front_##type(const List_##type* list) \
{ \
return list->_first->_data; \
} \
\
void list_push_front_##type(List_##type* list, type elem) \
{ \
... \
} \
\
_list_functions_##type _list_funcs_##type = { \
&list_is_empty_##type, \
&list_size_##type, \
&list_front_##type, \
&list_push_front_##type, \
}; \
\
List_##type* new_list_##type() \
{ \
List_##type* res = (List_##type*) malloc(sizeof(List_##type)); \
res->_size = 0; \
res->_first = NULL; \
res->_functions = &_list_funcs_##type; \
return res; \
}
#define List(type) \
List_##type
#define new_list(type) \
new_list_##type()
以下是一些宏,只需通过存储的函数指针调用列表的函数:
#define is_empty(collection) \
collection->_functions->is_empty(collection)
#define size(collection) \
collection->_functions->size(collection)
#define front(collection) \
collection->_functions->front(collection)
#define push_front(collection, elem) \
collection->_functions->push_front(collection, elem)
请注意,如果您使用相同的结构来设计除列表之外的其他集合,您将能够将最后的函数用于存储指针的任何集合。
最后,举例说明如何使用我们的新列表模板:
/* Define the data structures you need */
define_list(int)
define_list(float)
int main()
{
List(int)* a = new_list(int);
List(float)* b = new_list(float);
push_front(a, 5);
push_front(b, 5.2);
}
如果你真的想在C中使用某种模板,你可以使用那些技巧,但这相当丑陋(只使用C ++,它会更简单)。唯一的开销是每个数据结构实例多一个指针,因此每当你调用一个函数时就会有一个间接(没有强制转换,你不必存储void*
指针,是啊\ o /)。希望你永远不会使用它:p
由于我们仅仅使用文本替换宏而不是真正的模板,因此当然存在一些限制。
每个编译单元只能定义一次每个类型,否则程序将无法编译。这可能是一个主要缺点,例如,如果您编写库并且某些标头包含一些define_
指令。
如果您要创建List
,其模板类型由多个字组成(signed char
,unsigned long
,const bar
,struct foo
...)或者其模板类型是指针(char*
,void*
...),您必须首先键入typedef
。
define_list(int) /* OK */
define_list(char*) /* Error: pointer */
define_list(unsigned long) /* Error: several words */
typedef char* char_ptr;
typedef unsigned long ulong;
define_list(char_ptr) /* OK */
define_list(ulong) /* OK */
如果要创建嵌套列表,则必须采用相同的技巧。
答案 1 :(得分:23)
嗯,我脑海中唯一可能的是宏(#define
s)。也许是这样的:
queue.h:
#define TYPE int
#define TYPED_NAME(x) int_##x
#include "queue_impl.h"
#undef TYPE
#undef TYPED_NAME
#define TYPE float
#define TYPED_NAME(x) float_##x
#include "queue_impl.h"
#undef TYPE
#undef TYPED_NAME
...
queue_impl.h:
//no include guard, of course
typedef struct
{
TYPE *data;
...
} TYPED_NAME(queue);
void TYPED_NAME(queue_insert) (TYPED_NAME(queue) *queue, TYPE data)
{
...
}
如果它有效(我不是100%确定,不是这样的预处理专家),它应该给你结构int_queue
和float_queue
,以及函数
void int_queue_insert(int_queue *queue, int data);
void float_queue_insert(float_queue *queue, float data);
当然,您必须自己为所需的所有类型实例化“模板”,但这相当于重复queue.h
中的5行块。实际实现只需编写一次。当然,你可以进一步完善它,但基本的想法应该是清楚的。
这至少会为您提供完全类型安全的队列模板,但缺乏完全匹配接口的便利性(函数必须携带类型名称,因为C不支持重载函数)。
答案 2 :(得分:6)
实现一个包含void * data的队列,并将此void *解释为指向任何类型的指针,甚至是像int这样的基本类型。
使用#define是可能的,但如果出现问题,请考虑调试......
答案 3 :(得分:3)
这是一个版本,可以让你实例化(通过预处理器)并在同一个C文件中使用多个类型(小心,它使用token concatenation) :
#include <stdio.h>
#define DEFINE_LL_NODE(CONCRETE_TYPE) \
struct node_of_ ## CONCRETE_TYPE \
{ \
CONCRETE_TYPE data; \
struct node_of_ ## CONCRETE_TYPE *next; \
};
#define DECLARE_LL_NODE(CONCRETE_TYPE,VARIABLE_NAME) \
struct node_of_ ## CONCRETE_TYPE VARIABLE_NAME;
/* Declarations for each type. */
DEFINE_LL_NODE(int)
DEFINE_LL_NODE(char)
int main (void)
{
/* Declaration of instances of each type. */
DECLARE_LL_NODE (int, foo)
DECLARE_LL_NODE (char, bar)
/* And you can then use these instances. */
foo.data = 1;
foo.next = NULL;
bar.data = 'c';
bar.next = NULL;
}
如果我使用cpp
对其进行预处理,我会得到:
struct node_of_int { int data; struct node_of_int *next; };
struct node_of_char { char data; struct node_of_char *next; };
int main (void)
{
struct node_of_int foo;
struct node_of_char bar;
foo.data = 1;
foo.next = ((void *)0);
bar.data = 'c';
bar.next = ((void *)0);
}
答案 4 :(得分:1)
如果真的想要这样做,可以通过一个简单的typedef
来解决:
typedef int data_t;
struct queue
{
data_t* data;
}
现在,您可以在所有地方使用data_t
,而不是普通的int
。但请注意,您将无法一次使用多个类型 (至少,我没有看到如何在纯C中模拟C ++模板的这种特定行为)。< / p>
答案 5 :(得分:1)
我很想知道这件事,但我现在有一个明确的答案,任何人都可以理解;所以看哪!
当我参加数据结构课程时,我不得不阅读Standish的关于数据结构的书,C中的算法;这很痛苦;它没有任何泛型,它充满了可怜的记号和一大堆全球状态变异,它没有任何权证存在;我知道采用他的代码风格意味着搞砸了我未来的所有项目,但我知道有更好的方法,所以,更好的方式:
这是我触摸它之前的样子(事实上,无论如何我触摸它以使其以人类可以阅读的方式格式化,你受到欢迎);它在很多层面都是非常丑陋和错误的,但我会列出它作为参考:
#include <stdio.h>
#define MaxIndex 100
int Find(int A[])
{
int j;
for (j = 0; j < MaxIndex; ++j) {
if (A[j] < 0) {
return j;
}
}
return -1;
}
int main(void)
{
// reminder: MaxIndex is 100.
int A[MaxIndex];
/**
* anonymous scope #1
* initialize our array to [0..99],
* then set 18th element to its negative value(-18)
* to make the search more interesting.
*/
{
// loop index, nothing interesting here.
int i;
// initialize our array to [0..99].
for (i = 0; i < MaxIndex; ++i) {
A[i] = i * i;
}
A[17]= -A[17];
}
/**
* anonymous scope #2
* find the index of the smallest number and print it.
*/
{
int result = Find(A);
printf(
"First negative integer in A found at index = %d.\n",
result
);
}
// wait for user input before closing.
getchar();
return 0;
}
这个程序以一种可怕的坏风格做了很多事情;特别是,它设置了一个仅在单个范围内使用的全局宏,但随后会持续污染任何代码;非常糟糕,并导致Windows API规模全球范围大规模污染。
此外,该程序将参数作为数组传递而没有包含它的结构;换句话说,一旦到达函数Find,数组就会在到达时死亡;我们不再知道数组的大小,所以我们现在有main和Find依赖于一个全局宏,非常糟糕。
有两种蛮力方法可以解决这个问题,但仍然保持代码简单;第一种方法是创建一个全局结构,将数组定义为100个整数的数组;这种传递结构的方式将保留数组的长度。第二种方法是将数组的长度作为find的参数传递,并且在创建数组之前仅使用#define行,然后在之后使用#undef,因为作用域仍将通过sizeof知道数组的大小(A)/ sizeof(A [0])具有0运行时开销,编译器将推导出100并将其粘贴。
为了以第三种方式解决这个问题,我制作了一个标题,可以很好地创建通用数组;它是一种抽象数据类型,但我想将其称为自动数据结构。
<强> SimpleArray.h 强>
/**
* Make sure that all the options needed are given in order to create our array.
*/
#ifdef OPTION_UNINSTALL
#undef OPTION_ARRAY_TYPE
#undef OPTION_ARRAY_LENGTH
#undef OPTION_ARRAY_NAME
#else
#if (!defined OPTION_ARRAY_TYPE) || !defined OPTION_ARRAY_LENGTH || (!defined OPTION_ARRAY_NAME)
#error "type, length, and name must be known to create an Array."
#endif
/**
* Use the options to create a structure preserving structure for our array.
* that is, in contrast to pointers, raw arrays.
*/
struct {
OPTION_ARRAY_TYPE data[OPTION_ARRAY_LENGTH];
} OPTION_ARRAY_NAME;
/**
* if we are asked to also zero out the memory, we do it.
* if we are not granted access to string.h, brute force it.
*/
#ifdef OPTION_ZERO_MEMORY
#ifdef OPTION_GRANT_STRING
memset(&OPTION_ARRAY_NAME, 0, OPTION_ARRAY_LENGTH * sizeof(OPTION_ARRAY_TYPE));
#else
/* anonymous scope */
{
int i;
for (i = 0; i < OPTION_ARRAY_LENGTH; ++i) {
OPTION_ARRAY_NAME.data[i] = 0;
}
}
#endif
#undef OPTION_ZERO_MEMORY
#endif
#endif
如果你被迫使用C预处理器(与PHP / Templating工具包/ ASP /你自己的嵌入式脚本语言相比,这个标题),每个C数据结构头应该是这样的。 >
让我们一起旋转:
#include <stdio.h>
int Find(int A[], int A_length)
{
int j;
for (j = 0; j < A_length; ++j) {
if (A[j] < 0) {
return j;
}
}
return -1;
}
int main(void)
{
// std::array<int, 100> A;
#define OPTION_ARRAY_TYPE int
#define OPTION_ARRAY_LENGTH 100
#define OPTION_ARRAY_NAME A
#include "SimpleArray.h"
/**
* anonymous scope #1
* initialize our array to [0..99],
* then set 18th element to its negative value(-18)
* to make the search more interesting.
*/
{
// loop index, nothing interesting here.
int i;
// initialize our array to [0..99].
for (i = 0; i < (sizeof(A.data) / sizeof(A.data[0])); ++i) {
A.data[i] = i * i;
}
A.data[17]= -A.data[17];
}
/**
* anonymous scope #2
* find the index of the smallest number and print it.
*/
{
int result = Find(A.data, (sizeof(A.data) / sizeof(A.data[0])));
printf(
"First negative integer in A found at index = %d.\n",
result
);
}
// wait for user input before closing.
getchar();
// making sure all macros of SimpleArray do not affect any code
// after this function; macros are file-wide, so we want to be
// respectful to our other functions.
#define OPTION_UNINSTALL
#include "SimpleArray.h"
return 0;
}
BEHOLD,我们在纯C和C预处理器中发明了一个天真的std :: array! 我们使用宏,但我们不是邪恶的,因为我们自己清理!在我们的范围结束时,我们所有的宏都是未定义的。
有问题;除非我们(sizeof(A.data) / sizeof(A.data[0]))
,否则我们不再知道数组的大小。这对编译器没有任何开销,但它不适合儿童使用;也不是宏,但我们在这里的盒子里工作;我们以后可以使用像PHP这样更友好的预处理器来使它对孩子友好。
要解决这个问题,我们可以创建一个实用程序库,作为我们的免费&#34;数组数据结构。
<强> SimpleArrayUtils.h 强>
/**
* this is a smart collection that is created using options and is
* removed from scope when included with uninstall option.
*
* there are no guards because this header is meant to be strategically
* installed and uninstalled, rather than kept at all times.
*/
#ifdef OPTION_UNINSTALL
/* clean up */
#undef ARRAY_FOREACH_BEGIN
#undef ARRAY_FOREACH_END
#undef ARRAY_LENGTH
#else
/**
* array elements vary in number of bytes, encapsulate common use case
*/
#define ARRAY_LENGTH(A) \
((sizeof A.data) / (sizeof A.data[0]))
/**
* first half of a foreach loop, create an anonymous scope,
* declare an iterator, and start accessing the items.
*/
#if defined OPTION_ARRAY_TYPE
#define ARRAY_FOREACH_BEGIN(name, iter, arr)\
{\
unsigned int iter;\
for (iter = 0; iter < ARRAY_LENGTH(arr); ++iter) {\
OPTION_ARRAY_TYPE name = arr.data[iter];
#endif
/**
* second half of a foreach loop, close the loop and the anonymous scope
*/
#define ARRAY_FOREACH_END \
}\
}
#endif
这是一个功能相当丰富的库,基本上是导出
ARRAY_LENGTH ::任何包含数据字段的内容 - &gt; INT
如果我们仍然定义了OPTION_ARRAY_SIZE,或者重新定义了它,那么标题还定义了如何进行foreach循环;这很可爱。
现在让我们发疯:
<强> SimpleArray.h 强>
/**
* Make sure that all the options needed are given in order to create our array.
*/
#ifdef OPTION_UNINSTALL
#ifndef OPTION_ARRAY_TYPE
#undef OPTION_ARRAY_TYPE
#endif
#ifndef OPTION_ARRAY_TYPE
#undef OPTION_ARRAY_LENGTH
#endif
#ifndef OPTION_ARRAY_NAME
#undef OPTION_ARRAY_NAME
#endif
#ifndef OPTION_UNINSTALL
#undef OPTION_UNINSTALL
#endif
#else
#if (!defined OPTION_ARRAY_TYPE) || !defined OPTION_ARRAY_LENGTH || (!defined OPTION_ARRAY_NAME)
#error "type, length, and name must be known to create an Array."
#endif
/**
* Use the options to create a structure preserving structure for our array.
* that is, in contrast to pointers, raw arrays.
*/
struct {
OPTION_ARRAY_TYPE data[OPTION_ARRAY_LENGTH];
} OPTION_ARRAY_NAME;
/**
* if we are asked to also zero out the memory, we do it.
* if we are not granted access to string.h, brute force it.
*/
#ifdef OPTION_ZERO_MEMORY
#ifdef OPTION_GRANT_STRING
memset(&OPTION_ARRAY_NAME, 0, OPTION_ARRAY_LENGTH * sizeof(OPTION_ARRAY_TYPE));
#else
/* anonymous scope */
{
int i;
for (i = 0; i < OPTION_ARRAY_LENGTH; ++i) {
OPTION_ARRAY_NAME.data[i] = 0;
}
}
#endif
#undef OPTION_ZERO_MEMORY
#endif
#endif
<强> SimpleArrayUtils.h 强>
/**
* this is a smart collection that is created using options and is
* removed from scope when included with uninstall option.
*
* there are no guards because this header is meant to be strategically
* installed and uninstalled, rather than kept at all times.
*/
#ifdef OPTION_UNINSTALL
/* clean up, be mindful of undef warnings if the macro is not defined. */
#ifdef ARRAY_FOREACH_BEGIN
#undef ARRAY_FOREACH_BEGIN
#endif
#ifdef ARRAY_FOREACH_END
#undef ARRAY_FOREACH_END
#endif
#ifdef ARRAY_LENGTH
#undef ARRAY_LENGTH
#endif
#else
/**
* array elements vary in number of bytes, encapsulate common use case
*/
#define ARRAY_LENGTH(A) \
((sizeof A.data) / (sizeof A.data[0]))
/**
* first half of a foreach loop, create an anonymous scope,
* declare an iterator, and start accessing the items.
*/
#if defined OPTION_ARRAY_TYPE
#define ARRAY_FOREACH_BEGIN(name, iter, arr)\
{\
unsigned int iter;\
for (iter = 0; iter < ARRAY_LENGTH(arr); ++iter) {\
OPTION_ARRAY_TYPE name = arr.data[iter];
#endif
/**
* second half of a foreach loop, close the loop and the anonymous scope
*/
#define ARRAY_FOREACH_END \
}\
}
#endif
<强>的main.c 强>
#include <stdio.h>
// std::array<int, 100> A;
#define OPTION_ARRAY_TYPE int
#define OPTION_ARRAY_LENGTH 100
#define OPTION_ARRAY_NAME A
#include "SimpleArray.h"
#define OPTION_UNINSTALL
#include "SimpleArray.h"
int Find(int A[], int A_length)
{
int j;
for (j = 0; j < A_length; ++j) {
if (A[j] < 0) {
return j;
}
}
return -1;
}
int main(void)
{
#define OPTION_ARRAY_NAME A
#define OPTION_ARRAY_LENGTH (sizeof(A.data) / sizeof(A.data[0]))
#define OPTION_ARRAY_TYPE int
#include "SimpleArray.h"
/**
* anonymous scope #1
* initialize our array to [0..99],
* then set 18th element to its negative value(-18)
* to make the search more interesting.
*/
{
#include "SimpleArrayUtils.h"
printf("size: %d.\n", ARRAY_LENGTH(A));
ARRAY_FOREACH_BEGIN(item, i, A)
A.data[i] = i * i;
ARRAY_FOREACH_END
A.data[17] = -A.data[17];
// uninstall all macros.
#define OPTION_UNINSTALL
#include "SimpleArrayUtils.h"
}
/**
* anonymous scope #2
* find the index of the smallest number and print it.
*/
{
#include "SimpleArrayUtils.h"
int result = Find(A.data, (sizeof(A.data) / sizeof(A.data[0])));
printf(
"First negative integer in A found at index = %d.\n",
result
);
// uninstall all macros.
#define OPTION_UNINSTALL
#include "SimpleArrayUtils.h"
}
// wait for user input before closing.
getchar();
// making sure all macros of SimpleArray do not affect any code
// after this function; macros are file-wide, so we want to be
// respectful to our other functions.
#define OPTION_UNINSTALL
#include "SimpleArray.h"
return 0;
}
正如你所看到的;我们现在有能力表达自由抽象(编译器替代它们为我们),我们只支付我们需要的东西(结构),其余的被抛出,并且不会污染全局范围。
我强调PHP的强大功能,因为很少有人在HTML文档的上下文之外看到它;但您可以在C文档或任何其他文本文件中使用它。您可以使用Templating Toolkit将您喜欢的任何脚本语言放在宏中;而这些语言将比C预处理器好得多,因为它们具有命名空间,变量和实际功能;这使得它们更容易调试,因为您正在调试生成代码的实际脚本;不是C预处理器,这是调试的地狱,主要是由于熟悉(谁在正确的头脑中花了几个小时玩和熟悉C预处理器?很少)。
以下是使用PHP执行此操作的示例:
<强> SimpleArray.php 强>
<?php
class SimpleArray {
public $length;
public $name;
public $type;
function __construct($options) {
$this->length = $options['length'];
$this->name = $options['name'];
$this->type = $options['type'];
}
function getArray() {
echo ($this->name . '.data');
}
function __toString() {
return sprintf (
"struct {\n" .
" %s data[%d];\n" .
"} %s;\n"
,
$this->type,
$this->length,
$this->name
);
}
};
?>
<强> main.php 强>
#include <stdio.h>
<?php include('SimpleArray.php'); ?>
int Find(int *A, int A_length)
{
int i;
for (i = 0; i < A_length; ++i)
{
if (A[i] < 0) {
return i;
}
}
return -1;
}
int main(int argc, char **argv)
{
<?php
$arr = new SimpleArray(array(
'name' => 'A',
'length' => 100,
'type' => 'int'
));
echo $arr;
?>
printf("size of A: %d.\n", <?php echo($arr->length); ?>);
/* anonymous scope */
{
int i;
for (i = 0; i < <?php echo($arr->length)?>; ++i) {
<?php $arr->getArray(); ?>[i] = i * i;
}
<?php $arr->getArray(); ?>[17] = -<?php $arr->getArray()?>[17];
}
int result = Find(<?php $arr->getArray();?>, <?php echo $arr->length; ?>);
printf(
"First negative integer in A found at index = %d.\n",
result
);
getchar();
return 0;
}
运行php main.php > main.c
然后
gcc main.c -o main
./main
这看起来很像Objective C,因为这基本上是客观C所做的,除了它倾向于链接&#34;宏&#34;编译时间到实际的运行时(就好像在运行期间PHP在运行时可用,反过来你的C可以与php通话,php可以与C通信,除了php是带有许多方括号的小型语言) 。主要区别在于,目标C并不是我所知道的有一种方法可以使静态&#34;像我们在这里所做的那样构建;它的对象实际上是运行时,因此访问起来要贵得多,但更灵活,并保留结构,而一旦标题离开作用域,C结构就会折叠成字节(而对象可以反映回原来的状态)国家使用内部标记的联盟)...
答案 6 :(得分:0)
使用预处理器宏,您无法在C语言中获得高质量的模板。因为,这些宏只展开一次,所以最多可以获得一个可以重新输入的数据结构,但一旦处理完就是整个程序的类型。
这意味着您需要考虑void *
类型解决方案,这会削弱C的类型检查。要尝试修复弱化类型检查,请考虑在结构中嵌入“type”字段,该字段是“在构造时分配一次”字符串,表示非void *类型。然后,您可以改进与维护结构相关的函数中缺少类型检查。也就是说,如果这件事对你来说甚至很重要。
答案 7 :(得分:0)
在另一个答案中使用其中一个代码生成宏,然后使用一些C11过载宏完成它,这样您就不必使用过多的类型信息来丢弃您的呼叫站点。
答案 8 :(得分:0)
基于我在回复中看到的评论。
实际上,在C中解决此问题的方法是在#define宏和函数指针周围进行操作。关键是,C ++模板在其第一个版本中几乎就是这样-仅仅是复制一些代码并生成一些符号的一种方式,通过文本对参数进行“参数化”。在这里,Sky是限制您发挥想象力的方法,请小心,因为在许多方面,类型检查等方面的工作都是您自己决定的,不要期望编译器有太多帮助。
不时说“为什么不只使用C ++”在这里适得其反。问题特别是在没有功能时如何模拟功能。我不得不说,根据我的经验,即使在C ++中,我也曾经模拟过模板。敢猜测为什么?因为它始于1990年,所以有了C ++和C ++中的模板概念,但是几乎没有这样的实现。这就是为什么。好吧,在那之前我也是用C语言完成的。 C ++使它更容易些,只是因为至少您不再需要使用函数指针来模拟类方法,因为,您已经获得了本机语言的支持。否则,就像C语言一样,#define是您进行la-la参数编程的唯一朋友。
答案 9 :(得分:-1)
#define q(t) \
typedef struct _q_##t {t v; struct q_##t *next} q_##t;
q(char);
q(int);
int main(void)
{
q_char qc;
q_int qi;
qc.v = 'c';
qc.next = (void *) 0;
qi.v = 42;
qi.next = (void *) 0;
return 0;
}
但我不确定那是你要找的......