paulo1205
(usa Ubuntu)
Enviado em 03/05/2018 - 12:07h
O Phoemur foi muito feliz ao salientar que C não é a melhor linguagem para fazer esse tipo de coisa porque, embora seja
possível fazê-las, vai dar tanto trabalho, e o código vai ficar tão cheio de artifícios (macros e conversões de tipos de ponteiros pra todo lado) que tende a ficar terrível de se ler e manter.
Então, eu realmente concordo que é melhor aprender a usar C++ ou alguma outra linguagem com suporte a programação genérica se você quiser poder trabalhar com programação genérica de um modo que seja eficiente para sua produtividade.
Um ponto que eu gostaria de acrescentar é que você chamou o tipo de
fila , deu a entender que ele é implementado por meio de uma lista (OK), mas está preocupado com uma função que trata a fila com uma cara parecida com a de um
array , ao permitir acesso a um elemento no meio da fila. Isso é um tanto atípico, e eu não estou certo de que sua API de fila deveria oferecer tal função.
Se, em vez de fila, você dissesse que o seu tipo é uma lista, aí sim tal função poderia fazer um pouco mais de sentido, porque a lista, ao contrário da fila, não implica que as operações terão de ser feitas apenas nos extremos, mas podem ocorrer em qualquer elemento. Mesmo assim, não sei se essa abordagem com jeito de
array seria apropriada para a API.
Ainda sobre API, possivelmente seria interessante você ter tipos diferentes para representar nós (como os de uma lista), e a fila em si, que poderia guardar, de forma agregada, informações como ponteiros distintos para o início e o fim da fila, comprimento atual e outras informações que você julgasse interessantes.
Tendo feito essas colocações (que acho sinceramente que você deveria considerar com carinho), pergunto: você precisa que as funções que você quer implementar tenham exatamente a forma que você mostrou? Porque se você mudar um pouco essa forma, as coisas podem ficar bem mais fáceis.
Eu acho que, se eu tivesse de implementar uma lista genérica como a sua, mais cedo ou mais tarde eu acabaria criando
front-ends para cada especialização da lista. Os tipos especializados continuariam usando a lista genérica como
back-end , mas permitiriam meios mais convenientes de ter acesso aos dados de cada tipo específico.
Para começar, poderíamos criar dentro de
fila.h uma macro que facilitasse especializações, num jeito mais ou menos como vai abaixo (ATENÇÃO: não testei nada desse código).
#define QUEUE_SPECIALIZATION(data_type, new_type_name) \
/* Define o tipo “new_type_name” como uma estrutura anônima contendo um único */ \
/* campo, de modo que um ponteiro para a estrutura coincida numericamente com */ \
/* um ponteiro para o campo, permitindo converter um no outro facilmente. */ \
typedef struct { Node n; } new_type_name; \
\
/* Inicializa um objeto do tipo “new_type_name”. Note a conversão de tipo de ‘Node *’ */ \
/* para ‘new_type_name *’, que se vale da coincidência numérica da declaração acima. */ \
/* O operador de concatenação do preprocessador, “##“, é usado para gerar uma função */ \
/* cujo nome será “new_type_name _init”. Note ainda que, como esse será um tipo para */ \
/* dados específicos (“data_type”), a função de inicialização não precisa receber ar- */ \
/* gumentos, mas usa a designação de tipo de dado informada na macro. */ \
new_type_name *new_type_name##_init(void){ \
return (new_type_name *)fila_init(sizeof(data_type)); \
} \
\
/* Função para adicionar um novo elemento na fila especializada. Note que ele recebe */ \
/* um ponteiro para o tipo especializado, não para a lista genérica, mas chama a função */ \
/* genérica para realizar a adição. Note também que: */ \
/* (1) fila_add() deve sempre fazer uma cópia do objeto que recebe, a fim de evitar */ \
/* problemas caso o objeto original tenha um tempo de vida menor que o da fila; */ \
/* (2) o segundo argumento de fila_add() deve ser do tipo ‘const void *’ (e isso, de */ \
/* certo modo, reforça a necessidade de fazer cópia do dado); */ \
/* (3) não é necessária a conversão explícita do ponteiro para o dado para ‘const */ \
/* void *’, pois o C especifica que tal conversão é sempre possível e válida; */ \
/* (4) o tipo de retorno de fila_add() é int, a fim de indicar se a adição foi feita */ \
/* com sucesso ou se falhou, e esse valor é reencaminhado por esta função. */ \
int new_type_name##_add(new_type_name *start, const data_type *data){ \
return fila_add(&start->n, data); \
} \
\
/* Assumindo que fila_get() retorne o valor do campo ‘st’ no registro do tipo ‘Node’, */ \
/* esta função converte tal valor, que é um ponteiro do tipo ‘void *’, num ponteiro */ \
/* do tipo ‘data_type *’. Note que não é necessária conversão explícita, porque con- */ \
/* versões de “void *’ para qualquer outro tipo de ponteiro são automáticas em C. */ \
data_type *new_type_name##_get(new_type_name *start, size_t index){ \
return fila_get(&start->n, index); \
} \
\
/* Implementa especializações das demais funções de manipulação da fila, que você */ \
/* mesmo elencou (e.g. fila_first(), fila_last(), fila_del() etc.), seguindo o modelo */ \
/* de transformações usado nas funções acima. */ \
\
/* Uma versão um pouco mais segura da função ..._get() mostrada acima, que retorna */ \
/* apenas uma cópia do dado, em lugar de um ponteiro diretamente para ele. */ \
data_type new_type_name##_get_copy(new_type_name *start, size_t index){ \
return *new_type_name##_get(start, index); \
} \
/* Fim da macro. */
(Continua mais tarde...)