Type-safe generic data structures in plain-old C?

后端 未结 10 2126
庸人自扰
庸人自扰 2020-12-04 08:23

I have done far more C++ programming than \"plain old C\" programming. One thing I sorely miss when programming in plain C is type-safe generic data structures, which are p

相关标签:
10条回答
  • 2020-12-04 08:53

    I use void pointers (void*) to represent generic data structures defined with structs and typedefs. Below I share my implementation of a lib which I'm working on.

    With this kind of implementation, you can think of each new type, defined with typedef, like a pseudo-class. Here, this pseudo-class is the set of the source code (some_type_implementation.c) and its header file (some_type_implementation.h).

    In the source code, you have to define the struct that will present the new type. Note the struct in the "node.c" source file. There I made a void pointer to the "info" atribute. This pointer may carry any type of pointer (I think), but the price you have to pay is a type identifier inside the struct (int type), and all the switchs to make the propper handle of each type defined. So, in the node.h" header file, I defined the type "Node" (just to avoid have to type struct node every time), and also I had to define the constants "EMPTY_NODE", "COMPLEX_NODE", and "MATRIX_NODE".

    You can perform the compilation, by hand, with "gcc *.c -lm".

    main.c Source File

    #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;
    }
    

    node.c Source File

    #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);
    }
    

    node.h Header File

    #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();
    

    matrix.c Source File

    #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);
    }
    

    matrix.h Header File

    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();
    

    complex.c Source File

    #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);
    }
    

    complex.h Header File

    #define ANALITIC 0 
    #define POLAR    1 
    
    typedef struct complex Complex;
    
    Complex* mkCpx(int type,double a,double b);
    void printCpx(Complex* number);
    void testCpx();
    

    I hope I hadn't missed nothing.

    0 讨论(0)
  • 2020-12-04 08:53

    Your option 1 is what most old time c programmers would go for, possibly salted with a little of 2 to cut down on the repetitive typing, and just maybe employing a few function pointers for a flavor of polymorphism.

    0 讨论(0)
  • 2020-12-04 09:00

    You could check out https://github.com/clehner/ll.c

    It's easy to use:

    #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;
    }
    

    Output:

    num is 200
    num is 100
    string is another string
    string is a string
    
    0 讨论(0)
  • 2020-12-04 09:03

    Option 1, either using void * or some union based variant is what most C programs use, and it may give you BETTER performance than the C++/macro style of having multiple implementations for different types, as it has less code duplication, and thus less icache pressure and fewer icache misses.

    0 讨论(0)
提交回复
热议问题