Создание динамического массива, который принимает любой тип в C

Я пытаюсь найти способ сделать структуру, чтобы динамический массив, который может работать с любым типом данных (включая пользовательские типы данных), так это то, что я придумал.

#define Vector(DATATYPE) struct {   DATATYPE* data; size_t size; size_t used; }

typedef Vector(int) int_Vector;

int main(int argc, char* argv[]){
    int_Vector vec;
    return 0;
}

пока это работает, мне было интересно, это хорошая практика? Я должен делать что-то подобное или есть лучший способ? Также есть способ реализовать это без typedef Vector(int) int_vector часть. В основном способ, который позволил бы мне использовать массив так же, как c++ использует шаблоны, где он будет выглядеть примерно так:

#define Vector(DATATYPE) struct {   DATATYPE* data; size_t size; size_t used; }

int main(int argc, char* argv[]){
    Vector(int) vec;
    return 0;
}

в основном, чтобы избежать так много типов, и все это под одним названием.

5 ответов


Ну нет, у C нет системы шаблонов, поэтому вы не можете ее использовать.

вы можете имитировать эффекты с помощью макросов, как вы это сделали (довольно умное решение), но это, конечно, немного нестандартно и требует от пользователей вашего кода изучить макрос и его ограничения.

обычно код C не пытается, так как это так неудобно.

самый "общий" типичный вектор-это что-то вроде glib GArray, но это не претендует на знание типа каждый элемент. Вместо этого пользователю остается заботиться о доступе, а массив просто моделирует каждый элемент как n байт.

здесь _Generic() в C11, который может немного помочь, я, честно говоря, не очень опытен в этом.


второй пример не будет работать, потому что две переменные определены как разные типы, даже если их члены одинаковы. Почему это так, покрыто моим существующий ответ.

однако синтаксис может быть сохранен таким же, используя немного другой подход:

#include <stdlib.h>

#define vector(type)    struct vector_##type

struct vector_int
{
    int* array;
    size_t count;
} ;

int main(void)
{
    vector(int) one = { 0 };
    vector(int) two = { 0 };

    one = two;
    ( void )one ;

    return 0;
}

использование удивительно похоже на C++ ' S vector<int> и полный пример можно посмотреть здесь:

#include <stdlib.h>

#define vector_var(type)    struct vector_##type

struct vector_int
{
    int* array;
    size_t count;
};

void vector_int_Push( struct vector_int* object , int value ) 
{
    //implement it here
}

int vector_int_Pop( struct vector_int* object ) 
{
    //implement it here
    return 0;
}    

struct vector_int_table
{
    void( *Push )( struct vector_int* , int );
    int( *Pop )( struct vector_int* );

} vector_int_table = { 
                         .Push = vector_int_Push ,
                         .Pop = vector_int_Pop 
                     };

#define vector(type)   vector_##type##_table

int main(void)
{
    vector_var(int) one = { 0 };
    vector_var(int) two = { 0 };

    one = two;

    vector(int).Push( &one , 1 );
    int value = vector(int).Pop( &one );
    ( void )value;

    return 0;
}

Vector(DATATYPE) struct { DATATYPE* data; size_t size; size_t used; } Не для указателей на функции.

void* достаточно и хорошо определено для указателя на любой объект, но не так для указателя на функцию.

C позволяет сохранить указатель на функцию одного типа в качестве указателя на функцию другого типа. Используя union из двух ниже код имеет достаточно места для сохранения указателя на любой тип. Управление тем, какой тип и какой член используется, остается открыть.

union u_ptr {
  void *object;
  void (*function)();
}

Не плохо. И я не вижу никаких недостатков. Просто чтобы объяснить другой метод, чаще всего используемый в этом случае, используйте union:

typedef union { int i; long l; float f; double d; /*(and so on)*/} vdata;
typedef enum  {INT_T,LONG_T,FLOAT_T, /*(and so on)*/} vtype;
typedef struct 
{
    vtype t;
    vdata data
} vtoken;
typedef struct
{
    vtoken *tk;
    size_t sz;
   size_t n;
} Vector;

Так что это возможно. Перечисление типа данных можно избежать с помощью typedefs, но если вы используете mixed (например: sum long, to double, to float и т. д.), Вы должны использовать их, так как int + double не равно double+int; это также причина, потому что легче увидеть, как союзы выполняют эту работу. Вы оставляете все арифметические правила нетронутыми.


расширения ответ что касается решения полиморфизма, мы также можем включить в него типы указателей или пользовательские типы. Основным преимуществом этого метода является избавление от перечисления "тип данных", а вместе с ним и от переключателя проверки времени выполнения заявления.

вариант.h

#ifndef VARIANT_H
#define VARIANT_H

#include <stdio.h>
#include <stdint.h>

typedef void print_data_t (const void* data);
typedef void print_type_t (void);

typedef struct 
{
  void* data;
  print_data_t* print_data;
  print_type_t* print_type;
} variant_t;

void print_data_char    (const void* data);
void print_data_short   (const void* data);
void print_data_int     (const void* data);
void print_data_ptr     (const void* data);
void print_data_nothing (const void* data);

void print_type_char        (void);
void print_type_short       (void);
void print_type_int         (void);
void print_type_int_p       (void);
void print_type_void_p      (void);
void print_type_void_f_void (void);

void print_data (const variant_t* var);
void print_type (const variant_t* var);

#define variant_init(var) {                \
  .data = &var,                            \
                                           \
  .print_data = _Generic((var),            \
    char:  print_data_char,                \
    short: print_data_short,               \
    int:   print_data_int,                 \
    int*:  print_data_ptr,                 \
    void*: print_data_ptr,                 \
    void(*)(void): print_data_nothing),    \
                                           \
  .print_type = _Generic((var),            \
    char:  print_type_char,                \
    short: print_type_short,               \
    int:   print_type_int,                 \
    int*:  print_type_int_p,               \
    void*: print_type_void_p,              \
    void(*)(void): print_type_void_f_void) \
}


#endif /* VARIANT_H */

вариант.c

#include "variant.h"

void print_data_char    (const void* data) { printf("%c",  *(const char*)  data); }
void print_data_short   (const void* data) { printf("%hd", *(const short*) data); }
void print_data_int     (const void* data) { printf("%d",  *(const int*)   data); }
void print_data_ptr     (const void* data) { printf("%p",  data); }
void print_data_nothing (const void* data) {}

void print_type_char        (void) { printf("char");          }
void print_type_short       (void) { printf("short");         }
void print_type_int         (void) { printf("int");           }
void print_type_int_p       (void) { printf("int*");          }
void print_type_void_p      (void) { printf("void*");         }
void print_type_void_f_void (void) { printf("void(*)(void)"); }


void print_data (const variant_t* var)
{
  var->print_data(var->data);
}

void print_type (const variant_t* var)
{
  var->print_type();
}

main.c

#include <stdio.h>
#include "variant.h"

int main (void) 
{
  char c = 'A';
  short s = 3;
  int i = 5;
  int* iptr = &i;
  void* vptr= NULL;
  void (*fptr)(void) = NULL;

  variant_t var[] =
  {
    variant_init(c),
    variant_init(s),
    variant_init(i),
    variant_init(iptr),
    variant_init(vptr),
    variant_init(fptr)
  };

  for(size_t i=0; i<sizeof var / sizeof *var; i++)
  {
    printf("Type: ");
    print_type(&var[i]);
    printf("\tData: ");
    print_data(&var[i]);
    printf("\n");
  }

  return 0;
}

выход:

Type: char      Data: A
Type: short     Data: 3
Type: int       Data: 5
Type: int*      Data: 000000000022FD98
Type: void*     Data: 000000000022FDA0
Type: void(*)(void)     Data:

недостатки _Generic для этой цели он блокирует нас от использования частной инкапсуляции, так как он должен использоваться в качестве макроса для передачи информации о типе.

С другой стороны, "вариант" в этом случае должен поддерживаться для всех новых типы один приходит с, так что это не все, что практические или общие.

тем не менее эти трюки хорошо знать для различных подобных целей.