我做了比“普通的旧C”编程更多的C ++编程。在普通C编程时,我非常想念的一件事是类型安全的通用数据结构,它是通过模板在C ++中提供的。
为了具体起见,请考虑一般的单链表。在C ++中,定义自己的模板类,然后根据所需的类型实例化它是一件简单的事情。
在C中,我可以想到一些实现通用单链表的方法:
我不喜欢选项1,因为它颠覆了类型系统,并且可能比特定类型的特定实现具有更差的性能。对于所有类型使用数据结构的统一表示,并向/从void指针进行转换,据我所知,需要一个专门针对元素类型的实现可以避免的间接。
选项2不需要任何额外的工具,但感觉有些笨拙,并且在使用不当时可能会导致错误的编译错误。
选项3可以提供比选项2更好的编译器错误消息,因为专用数据结构代码将以扩展形式存在,可以在编辑器中打开并由程序员检查(而不是由预处理器宏生成的代码)。但是,这个选项是最重量级的,一种“穷人的模板”。我之前使用过这种方法,使用一个简单的sed脚本来专门化一些C代码的“模板化”版本。
我想用C而不是C ++编写我未来的“低级”项目,但是为每种特定类型重写通用数据结构的想法让我感到害怕。
人们对此问题有什么经验? C中是否存在通用数据结构和算法的良好库,它们不与选项1一起使用(即向无效指针进行转换,这会牺牲类型安全性并增加间接级别)?
答案 0 :(得分:22)
选项1是我看到的大多数通用容器的C实现所采用的方法。 Windows驱动程序工具包和Linux内核使用宏来允许容器的链接嵌入到结构中的任何位置,宏用于从指向链接字段的指针获取结构指针:
选项2是BSD的tree.h和queue.h容器实现所采用的方法:
我认为我不认为这些方法中的任何一种都是安全的。有用,但不是类型安全。
答案 1 :(得分:3)
选项1,使用void *
或基于某些union
的变体是大多数C程序使用的,并且它可以提供比C ++ / macro样式更好的性能,具有针对不同类型的多个实现,因为它具有更少的代码重复,因此更少的icache压力和更少的icache未命中。
答案 2 :(得分:2)
GLib中有一堆通用数据结构,http://www.gtk.org/
CCAN有很多有用的摘录,例如http://ccan.ozlabs.org/
答案 3 :(得分:1)
您的选项1是大多数旧时间程序员可以使用的选项,可能只需要少量2来减少重复键入,并且可能使用一些函数指针来调整风格多态性。
答案 4 :(得分:1)
选项1有一个常见的变体,它更有效,因为它使用联合将值存储在列表节点中,即没有额外的间接。这有一个缺点,即列表只接受某些类型的值,如果类型的大小不同,可能会浪费一些内存。
但是,如果你愿意打破严格的别名,可以通过使用灵活的数组成员来摆脱union
。 C99示例代码:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct ll_node
{
struct ll_node *next;
long long data[]; // use `long long` for alignment
};
extern struct ll_node *ll_unshift(
struct ll_node *head, size_t size, void *value);
extern void *ll_get(struct ll_node *head, size_t index);
#define ll_unshift_value(LIST, TYPE, ...) \
ll_unshift((LIST), sizeof (TYPE), &(TYPE){ __VA_ARGS__ })
#define ll_get_value(LIST, INDEX, TYPE) \
(*(TYPE *)ll_get((LIST), (INDEX)))
struct ll_node *ll_unshift(struct ll_node *head, size_t size, void *value)
{
struct ll_node *node = malloc(sizeof *node + size);
if(!node) assert(!"PANIC");
memcpy(node->data, value, size);
node->next = head;
return node;
}
void *ll_get(struct ll_node *head, size_t index)
{
struct ll_node *current = head;
while(current && index--)
current = current->next;
return current ? current->data : NULL;
}
int main(void)
{
struct ll_node *head = NULL;
head = ll_unshift_value(head, int, 1);
head = ll_unshift_value(head, int, 2);
head = ll_unshift_value(head, int, 3);
printf("%i\n", ll_get_value(head, 0, int));
printf("%i\n", ll_get_value(head, 1, int));
printf("%i\n", ll_get_value(head, 2, int));
return 0;
}
答案 5 :(得分:1)
一个老问题,我知道,但是如果仍有兴趣:我今天正在尝试选项2)(预处理器宏),并提出了我将在下面粘贴的示例。确实有点笨重,但并不可怕。代码不是完全类型安全的,但包含健全性检查以提供合理的安全级别。在编写时编译错误消息与我在C ++模板发挥作用时所看到的相比是温和的。您可能最好开始阅读&#34; main&#34;中的示例使用代码。功能
#include <stdio.h>
#define LIST_ELEMENT(type) \
struct \
{ \
void *pvNext; \
type value; \
}
#define ASSERT_POINTER_TO_LIST_ELEMENT(type, pElement) \
do { \
(void)(&(pElement)->value == (type *)&(pElement)->value); \
(void)(sizeof(*(pElement)) == sizeof(LIST_ELEMENT(type))); \
} while(0)
#define SET_POINTER_TO_LIST_ELEMENT(type, pDest, pSource) \
do { \
ASSERT_POINTER_TO_LIST_ELEMENT(type, pSource); \
ASSERT_POINTER_TO_LIST_ELEMENT(type, pDest); \
void **pvDest = (void **)&(pDest); \
*pvDest = ((void *)(pSource)); \
} while(0)
#define LINK_LIST_ELEMENT(type, pDest, pSource) \
do { \
ASSERT_POINTER_TO_LIST_ELEMENT(type, pSource); \
ASSERT_POINTER_TO_LIST_ELEMENT(type, pDest); \
(pDest)->pvNext = ((void *)(pSource)); \
} while(0)
#define TERMINATE_LIST_AT_ELEMENT(type, pDest) \
do { \
ASSERT_POINTER_TO_LIST_ELEMENT(type, pDest); \
(pDest)->pvNext = NULL; \
} while(0)
#define ADVANCE_POINTER_TO_LIST_ELEMENT(type, pElement) \
do { \
ASSERT_POINTER_TO_LIST_ELEMENT(type, pElement); \
void **pvElement = (void **)&(pElement); \
*pvElement = (pElement)->pvNext; \
} while(0)
typedef struct { int a; int b; } mytype;
int main(int argc, char **argv)
{
LIST_ELEMENT(mytype) el1;
LIST_ELEMENT(mytype) el2;
LIST_ELEMENT(mytype) *pEl;
el1.value.a = 1;
el1.value.b = 2;
el2.value.a = 3;
el2.value.b = 4;
LINK_LIST_ELEMENT(mytype, &el1, &el2);
TERMINATE_LIST_AT_ELEMENT(mytype, &el2);
printf("Testing.\n");
SET_POINTER_TO_LIST_ELEMENT(mytype, pEl, &el1);
if (pEl->value.a != 1)
printf("pEl->value.a != 1: %d.\n", pEl->value.a);
ADVANCE_POINTER_TO_LIST_ELEMENT(mytype, pEl);
if (pEl->value.a != 3)
printf("pEl->value.a != 3: %d.\n", pEl->value.a);
ADVANCE_POINTER_TO_LIST_ELEMENT(mytype, pEl);
if (pEl != NULL)
printf("pEl != NULL.\n");
printf("Done.\n");
return 0;
}
答案 6 :(得分:1)
我使用void指针(void *)来表示使用structs和typedef定义的通用数据结构。下面我分享我正在进行的lib的实现。
通过这种实现,您可以考虑使用typedef定义的每个新类型,如伪类。这里,这个伪类是源代码(some_type_implementation.c)及其头文件(some_type_implementation.h)的集合。
在源代码中,您必须定义将呈现新类型的结构。注意&#34; node.c&#34;中的结构。源文件。在那里,我做了一个指向&#34; info&#34;的无效指针。属性附加伤害。这个指针可以携带任何类型的指针(我认为),但你需要支付的价格是struct(int类型)中的类型标识符,以及所有定义的每个类型的propper句柄的开关。所以,在node.h&#34;头文件,我定义了类型&#34; Node&#34; (只是为了避免每次都必须输入struct节点),而且我还必须定义常量&#34; EMPTY_NODE&#34;,&#34; COMPLEX_NODE&#34;和&#34; MATRIX_NODE&#34;。< / p>
您可以手动执行编译,使用&#34; gcc * .c -lm&#34;。
#include <stdio.h>
#include <math.h>
#define PI M_PI
#include "complex.h"
#include "matrix.h"
#include "node.h"
int main()
{
//testCpx();
//testMtx();
testNode();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "node.h"
#include "complex.h"
#include "matrix.h"
#define PI M_PI
struct node
{
int type;
void* info;
};
Node* newNode(int type,void* info)
{
Node* newNode = (Node*) malloc(sizeof(Node));
newNode->type = type;
if(info != NULL)
{
switch(type)
{
case COMPLEX_NODE:
newNode->info = (Complex*) info;
break;
case MATRIX_NODE:
newNode->info = (Matrix*) info;
break;
}
}
else
newNode->info = NULL;
return newNode;
}
int emptyInfoNode(Node* node)
{
return (node->info == NULL);
}
void printNode(Node* node)
{
if(emptyInfoNode(node))
{
printf("Type:%d\n",node->type);
printf("Empty info\n");
}
else
{
switch(node->type)
{
case COMPLEX_NODE:
printCpx(node->info);
break;
case MATRIX_NODE:
printMtx(node->info);
break;
}
}
}
void testNode()
{
Node *node1,*node2, *node3;
Complex *Z;
Matrix *M;
Z = mkCpx(POLAR,5,3*PI/4);
M = newMtx(3,4,PI);
node1 = newNode(COMPLEX_NODE,Z);
node2 = newNode(MATRIX_NODE,M);
node3 = newNode(EMPTY_NODE,NULL);
printNode(node1);
printNode(node2);
printNode(node3);
}
#define EMPTY_NODE 0
#define COMPLEX_NODE 1
#define MATRIX_NODE 2
typedef struct node Node;
Node* newNode(int type,void* info);
int emptyInfoNode(Node* node);
void printNode(Node* node);
void testNode();
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "matrix.h"
struct matrix
{
// Meta-information about the matrix
int rows;
int cols;
// The elements of the matrix, in the form of a vector
double** MTX;
};
Matrix* newMtx(int rows,int cols,double value)
{
register int row , col;
Matrix* M = (Matrix*)malloc(sizeof(Matrix));
M->rows = rows;
M->cols = cols;
M->MTX = (double**) malloc(rows*sizeof(double*));
for(row = 0; row < rows ; row++)
{
M->MTX[row] = (double*) malloc(cols*sizeof(double));
for(col = 0; col < cols ; col++)
M->MTX[row][col] = value;
}
return M;
}
Matrix* mkMtx(int rows,int cols,double** MTX)
{
Matrix* M;
if(MTX == NULL)
{
M = newMtx(rows,cols,0);
}
else
{
M = (Matrix*)malloc(sizeof(Matrix));
M->rows = rows;
M->cols = cols;
M->MTX = MTX;
}
return M;
}
double getElemMtx(Matrix* M , int row , int col)
{
return M->MTX[row][col];
}
void printRowMtx(double* row,int cols)
{
register int j;
for(j = 0 ; j < cols ; j++)
printf("%g ",row[j]);
}
void printMtx(Matrix* M)
{
register int row = 0, col = 0;
printf("\vSize\n");
printf("\tRows:%d\n",M->rows);
printf("\tCols:%d\n",M->cols);
printf("\n");
for(; row < M->rows ; row++)
{
printRowMtx(M->MTX[row],M->cols);
printf("\n");
}
printf("\n");
}
void testMtx()
{
Matrix* M = mkMtx(10,10,NULL);
printMtx(M);
}
typedef struct matrix Matrix;
Matrix* newMtx(int rows,int cols,double value);
Matrix* mkMatrix(int rows,int cols,double** MTX);
void print(Matrix* M);
double getMtx(Matrix* M , int row , int col);
void printRowMtx(double* row,int cols);
void printMtx(Matrix* M);
void testMtx();
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "complex.h"
struct complex
{
int type;
double a;
double b;
};
Complex* mkCpx(int type,double a,double b)
{
/** Doc - {{{
* This function makes a new Complex number.
*
* @params:
* |-->type: Is an interger that denotes if the number is in
* | the analitic or in the polar form.
* | ANALITIC:0
* | POLAR :1
* |
* |-->a: Is the real part if type = 0 and is the radius if
* | type = 1
* |
* `-->b: Is the imaginary part if type = 0 and is the argument
* if type = 1
*
* @return:
* Returns the new Complex number initialized with the values
* passed
*}}} */
Complex* number = (Complex*)malloc(sizeof(Complex));
number->type = type;
number->a = a;
number->b = b;
return number;
}
void printCpx(Complex* number)
{
switch(number->type)
{
case ANALITIC:
printf("Re:%g | Im:%g\n",number->a,number->b);
break;
case POLAR:
printf("Radius:%g | Arg:%g\n",number->a,number->b);
break;
}
}
void testCpx()
{
Complex* Z = mkCpx(ANALITIC,3,2);
printCpx(Z);
}
#define ANALITIC 0
#define POLAR 1
typedef struct complex Complex;
Complex* mkCpx(int type,double a,double b);
void printCpx(Complex* number);
void testCpx();
我希望我没有错过什么。
答案 7 :(得分:0)
我想用C而不是C ++编写我未来的“低级”项目......
为什么呢?您的目标是否缺少C ++编译器或C ++运行时?
答案 8 :(得分:0)
您可以签出https://github.com/clehner/ll.c
易于使用:
#include <stdio.h>
#include <string.h>
#include "ll.h"
int main()
{
int *numbers = NULL;
*( numbers = ll_new(numbers) ) = 100;
*( numbers = ll_new(numbers) ) = 200;
printf("num is %d\n", *numbers);
numbers = ll_next(numbers);
printf("num is %d\n", *numbers);
typedef struct _s {
char *word;
} s;
s *string = NULL;
*( string = ll_new(string) ) = (s) {"a string"};
*( string = ll_new(string) ) = (s) {"another string"};
printf("string is %s\n", string->word);
string = ll_next( string );
printf("string is %s\n", string->word);
return 0;
}
输出:
num is 200
num is 100
string is another string
string is a string