【问题标题】:How to write general-type queue library in c?如何用c编写通用类型的队列库?
【发布时间】:2020-05-13 09:40:15
【问题描述】:

我有一个存储固定大小(15)和固定类型(整数)的结构。

typedef struct Queue Queue;
struct Queue
{
    int buffer[15];    //only store 15 integer 
    int head;
    int space;
    int tail;
    int (*isFull)(Queue_int* const me);
    int (*isEmpty)(Queue_int* const me);
    int (*getSize)(Queue_int* const me);
    void (*insert)(Queue_int* const me, int k);    //insert integer only
    int (*remove)(Queue_int* const me);
};

我想写一个通用的队列库。换句话说,任何类型和任何大小的队列结构。

我尝试使用宏来解决这个问题。也就是我为其他接口创建了一个通用接口。

/*queue.h*/

#define Queue_Define(type, size)\
  typedef struct Queue_##type Queue_##type;\
  struct Queue_##type \
  { \
    type buffer[size]; \
    int head; \
    int space; \
    int tail; \
    int (*isFull)(Queue_##type* const me); \
    int (*isEmpty)(Queue_##type* const me);  \
    int (*getSize)(Queue_##type* const me);  \
    void (*insert)(Queue_##type* const me, type k); \
    int (*remove)(Queue_##type* const me); \
  }\

但后来我遇到了另一个问题,客户端不知道如何使用 queue.h,因为结构名称是由宏创建的,而不是客户端本身。

例如:

/*queue_employee.h*/

#include "queue.h"

Queue_Define(employee, 15);

void Queue_employee_Init
(
    Queue_employee* const me,    //oops! how do client know struct name is Queue_employee?
    int (*isFullfunction)(Queue_employee* const me),    //ditto
    int (*isEmptyfunction)(Queue_employee* const me),    //ditto
    int (*getSizefunction)(Queue_employee* const me),    //ditto
    void (*insertfunction)(Queue_employee* const me, employee k),    //ditto
    int (*removefunction)(Queue_employee* const me)    //ditto
);

有什么建议吗?

【问题讨论】:

  • 请问:这是为了什么任务?
  • @Ackdari 你说的分配是什么意思?我学会了优秀的程序员编写优秀的库,我想成为一名优秀的程序员。就是这样。
  • @AndyLin 他的意思是家庭作业。
  • @Jabberwocky 不。这不是家庭作业。
  • 我真的不明白,为什么你不尝试直接在头文件中创建结构,而是使用宏?然后,在 main 中创建 struct 对象,并将它的指针传递给带有所需参数的函数

标签: c templates generics queue polymorphism


【解决方案1】:

有或多或少奇特的方式可以用宏创建通用接口,但我建议从 C 中通用编程的“老派”方式开始,这意味着将 void* 与大小变量结合使用.

首先对您当前的代码做一些说明:

  • 我会从结构中删除“成员函数”语法,因为它在 C 中根本没有意义。foo.isFull(foo) 并不比queue_isFull(foo) 更易于使用。函数指针只会增加不必要的复杂性。如果 C 支持像 C++ 一样的 this 指针,那就另当别论了。但事实并非如此,因此尝试模拟 C++ 风格的语法并没有任何收获。

  • constQueue_int* const queue 限定指针参数不是有意义的 API。调用者并不关心你的函数在内部对你的局部变量queue 做了什么。让它指向你喜欢的任何地方——来电者不在乎。我相信您实际上将这种语法误认为是 const 正确性,这是一种良好且正确的做法。然后你应该写int isFull(const Queue_int* queue),这意味着函数isFull不会修改指向队列,这很有意义。

  • 您应该考虑使用私有封装。不应允许调用者直接访问像buffer 这样的成员。如果我们开始使用void*,这将变得更加重要,因为它们是类型不安全的。我们可以通过不透明类型的概念来实现私有封装。

不透明的类型是这样的:

// queue.h
typedef struct queue_t queue_t;

这是结构的前向声明。包含 queue.h 的用户将无法访问结构内的成员,也无法声明该结构的实例。他们将只能使用指向结构的指针。 (如果熟悉这个概念,它的工作方式与 C++ 中的抽象基类完全一样。)实际的结构声明仅对私有 .c 文件可见。

接下来,您将在标题中删除一些可用函数,包括“构造函数”和“析构函数”。例如:

queue_t* queue_init (size_t item_size, size_t def_queue_size);
void queue_delete (queue_t* queue);

bool queue_set_item (queue_t* queue, size_t index, const void* item);
bool queue_get_item (const queue_t* queue, size_t index, void* item);

让调用者提供指针和大小,使其成为类型通用的。 (我这里只是做一些无意义的成员函数,忽略了这是一个队列数据类型。)

完整的标题如下所示:

// queue.h
#ifndef QUEUE_H
#define QUEUE_H

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

typedef struct queue_t queue_t;

queue_t* queue_init (size_t item_size, size_t def_queue_size);
void queue_delete (queue_t* queue);

bool queue_set_item (queue_t* queue, size_t index, const void* item);
bool queue_get_item (const queue_t* queue, size_t index, void* item);

#endif /* QUEUE_H */

然后是一个匹配的 C 文件,其中包含结构和函数的实现:

// queue.c
#include "queue.h"

struct queue_t
{
  size_t  item_size;
  size_t  queue_size;
  uint8_t data[];
};

queue_t* queue_init (size_t item_size, size_t def_queue_size)
{
  queue_t* result = malloc (sizeof *result + item_size*def_queue_size);
  if(result == NULL)
    return NULL;

  result->item_size  = item_size;
  result->queue_size = def_queue_size;
  memset(result->data, 0, item_size*def_queue_size);

  return result;
}

void queue_delete (queue_t* queue)
{
  free(queue);
}

bool queue_set_item (queue_t* queue, size_t index, const void* item)
{
  if(index >= queue->queue_size)
    return false;

  memcpy(&queue->data[index * queue->item_size], item, queue->item_size);

  return true;
}

调用者代码示例:

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

int main(void) 
{
  queue_t* int_queue = queue_init(sizeof(int), 10);
  int x = 666;
  int y;
  (void) queue_set_item(int_queue, 5, &x);
  (void) queue_get_item(int_queue, 5, &y);
  printf("%d\n", y);
  queue_delete(int_queue);

  queue_t* string_queue = queue_init(sizeof(char[10]), 10);
  char str1[10] = "Hello";
  char str2[10];
  (void) queue_set_item(string_queue, 3, str1);
  (void) queue_get_item(string_queue, 3, str2);
  puts(str2);
  queue_delete(string_queue);

  return 0;
}

这当然只是一个愚蠢的人工示例,与队列 ADT 完全不同,但希望您了解如何实现一个类型泛型的示例。

【讨论】:

  • 请您解释一下为什么在 queue_init 中您更喜欢sizeof(*result) 而不是sizeof(queue_t)
  • @AndyLin 这只是编码风格。有些人可能会争辩说,如果result 的类型稍后发生变化,它可以最大限度地减少出错的机会。任何一种风格都很好,真的。
【解决方案2】:

我的建议是使用仅包含 void* 内容的队列。如果需要,您还可以通过将底层数组替换为更大的数组来消除大小限制:

typedef struct Queue {
    void** content;
    int head;
    int space;
    int tail;
    int entries;
} Queue_t

void Queue_init(Queue_t* const queue, int init_size) {
    queue->space = init_size;
    queue->content = calloc(sizeof(void*), init_size);
    queue->head = 0;
    queue->tail = 0;
    queue->entries = 0;
}

void insert(struct Queue* const queue, void* entry) {
    if (queue->entries == queue->space) {
        // alloc new array
        int add = queue->space / 2 == 0 ? 1 : queue->space / 2;
        void** tmp = calloc(sizeof(void*), queue->space + add);

        // copy the old content to the new array
        int tmp_i = 0
        if (queue->head < queue->tail) {
            // can copy all in one go
            for (int i = queue->head; i < queue->tail; i++) {
                tmp[tmp_i] = queue->content[i];
                tmp_i++;
            }
        } else {
            // content oveflow from the end of the array to the begining
            for (int i = queue->head; i < queue->space; i++) {
                tmp[tmp_i] = queue->content[i];
                tmp_i++;
            }
            for (int i = 0; i < queue->tail; i++) {
                tmp[tmp_i] = queue->content[i];
                tmp_i++;
            }
        }

        free(queue->content);
        // set the new content and head and tail accordingly
        queue->content = tmp;
        queue->head = 0;
        queue->tail = queue->entries;
    }

    // put the new entry at it's place and adjust tail
    queue->content[queue->tail] = entriy;
    queue->entries++;
    queue->tail = (queue->tail + 1) % queue->space;
}

// all other functions ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多