[Resolvido] Tipos definidos com structs em C.

Iniciado por mhkgalvez, 16 de Junho de 2010, 13:24

tópico anterior - próximo tópico

mhkgalvez

Olá pessoal, estou desenvolvendo em C um código que cria novos tipos de dados, mais avançados, por exemplo, algo semelhante à ArrayList do C# e List do Java, etc. Inicialmente, criei um tipo chamado IntList, que é uma lista de inteiros. Criei, então, funções para tratar este tipo de dado, sendo alguma delas:

IntList *prepare(IntList *list);
IntList *add(IntList *list, int value);


A primeira função recebe o ponteiro que representa a lista, prepara-o para uso e devolve-o pronto. Assim, se eu criasse uma lista no meu código principal, poderia declará-la assim:

IntList *lista = prepare(lista);

E para adicionar um valor, poderia usar:

lista = add(lista, 150);

Até aí tudo funcionou perfeitamente. O problema está quando eu passo a querer criar outros tipos de listas, por exemplo, uma FloatList. Neste caso, se eu criar uma nova função "add", o C retornará um erro, já que nele a função é definida apenas pelo nome, impedindo uma sobrecarga, como em C++. A solução lógica inicial - e que eu implementei - foi criar uma função para cada tipo de lista, assim, para listas de inteiros eu criei a função "prepareIL" e para listas de floats, a função "prepareFL". Só que eu pretendo criar vários tipos diferentes de listas e isso tornaria os nomes das funções muito complicados.

A minha pergunta é se existe alguma forma de eu criar uma única função, por exemplo, a função "prepare" que receba uma lista e verifique qual tipo de lista é, seja IntList, FloatList, etc. Uma coisa eu percebi por acidente: se na função IntList *prepareIL(IntList *list) eu passar uma FloatList ele vai até receber o parâmetro sem erro de tipo, mas vai tratá-la como IntList, o que não é o que desejo.

Alguém pode me ajudar?

p.s.: O código da struct IntList e FloatList segue abaixo:

typedef struct {
   char *ready;
   int *values;
   int size;
   int lenght;
   int maxInd;
   int capacity;
} IntList;

typedef struct {
   char *ready;
   float *values;
   int size;
   int lenght;
   int maxInd;
   int capacity;
} FloatList;


"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

Darcamo

Eu preferiria manter funções separadas. Isso deixa as coisas mais modulares e fáceis de manter, além de deixar claro que tipo de lista você espera como argumento da função (já que o compilador não vai checar isso pra você).

Talvez você possa criar uma enumeração com um valor para cada tipo de lista. Um elemento do tipo dessa enumeração seria o primeiro elemento da lista e você poderia usar esse elemento para saber qual o tipo de lista. O ruim é que teria que mudar essa enumeração a cada tipo novo de lista, mas suspeito que você não terá muitos tipos diferentes certo?

mhkgalvez

Sim, não muitos tipos. Mas poderia exemplificar essa solução das enumerações, por favor? :-[
"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

Darcamo

A ideia é ter o primeiro atributo na lista indicando que tipo de lista ela é. Pode ser um inteiro também. Ex: se for 0 quer dizer que é uma lista de ints, se for 1 quer dizer que é uma lista de floats, etc.. Mas uma enumeração deixa as coisas mais claras.

Mas tem um problema. Não tem como setar o valor padrão de uma struct em C. Em C++ você pode usar o construtor para isso (uma struct em C++ é quase a mesma coisa que uma classe), mas em C não.

Veja o código abaixo
#include "stdio.h"

typedef enum {
    ListaDeInts,
    ListaDeFloats
}  TiposDeLista;


typedef struct {
    TiposDeLista tipo;
    char *ready;
    int *values;
    int size;
    int lenght;
    int maxInd;
    int capacity;
} IntList;

typedef struct {
    TiposDeLista tipo;
    char *ready;
    float *values;
    int size;
    int lenght;
    int maxInd;
    int capacity;
} FloatList;




int main(int argc, char *argv[])
{
    IntList lista_de_ints = {ListaDeInts};
    FloatList lista_de_floats = {ListaDeFloats};
    printf ("Lista é: %d\n",lista_de_ints.tipo);
    printf ("Lista é: %d\n",lista_de_floats.tipo);
    return 0;
}


Note que quando declaro uma variável de cada lista eu explicitamente seto o primeiro valor que é o tipo (você pode setar os demais separando por vírgula).
Sua função poderá testar esse primeiro elemento da lista para saber que lista ela é. Não é muito elegante, mas é C puro.

Outra coisa, sua função "prepara" realmente precisa receber uma lista como argumento? Ao invés de inicializar uma lista como eu fiz você pode criar funções que retornam uma nova lista corretamente preenchida. Algo como
IntList lista_de_ints = newListInt();
FloatList lista_de_floats = newFloatList();


mhkgalvez

 :) É verdade, fui precipitado na função "prepare". Realmente não há necessidade de passar a lista como argumento.

Bom, quanto a essa idéia da lista acho que é a melhor forma de implantar mesmo. Eu pensei nisso mas queria saber se o que você havia pensado era diferente.

Só mais uma coisa: Se eu quisesse transformar esse projeto para C++, usando Orientação a Objetos haveria um modo mais compacto de se fazer isso? Ou o C++ já tem listas nativas?

Na verdade, o modo mais compacto é o modo usado, por exemplo, pelo Java e C#, onde você declara uma lista assim:

List<tipo de dados> lista = new List(); Nesse caso, a própria assinatura do construtor desta classe tem algo semelhante a isto <T>, e que indica o tipo passado (não sei o nome dessa funcionalidade).

De qualquer forma, obrigado.
"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

Darcamo

C++ não força você a usar o. Você pode escrever um programa praticamente sem usar o se quiser, e ainda assim se beneficiar de algumas coisas em relação ao C puro.

Essa funcionalidade que você mencionou se chama "Templates" e é um recurso muito importante em C++.

Você pode usar os diversos tipos de containers que a STL (Standard Template Library) fornece em C++. Veja no código abaixo

#include <list>
#include <iostream>

int main(int argc, char *argv[])
{
    std::list<int> lista;
    std::list<double>* lista2 = new std::list<double>();

    // Pode usar as listas aqui

    std::cout << "Hello World" << std::endl;

    // Tem que deletar lista2 porque foi alocada dinamicamente
    delete lista2;
    return 0;
}


Vale lembrar que criar uma variável com o operador "new" vai alocar memória para ela durante a execução do programa e você deve manualmente liberar a memória com delete. C++ não tem garbage colector, mas tem algumas coisas que você pode usar para auxiliar nessa tarefa.

Não vou postar links sobre stl aqui porque eu postaria apenas os dois primeiros em uma busca por "stl" no google de qualquer maneira.  :P

mhkgalvez

O fato de não ter GC significa que se eu não usar o delete a memória continuará ocupada após o fechamento do programa?
"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

Darcamo

A memória "vaza" durante a execução do programa, mas quando o processo for finalizado tudo volta para o sistema operacional.
Mas esse vazamento só ocorre se você usar alocação dinâmica de memória e nunca liberar essa memória.
Por exemplo, se você rodar o programa abaixo ele vai começar a comer mais e mais memória RAM até que você mate o processo com Ctrl+C no terminal ou fique sem memória RAM disponível e o próprio sistema operacional mate o processo por falta de memória.

int main(int argc, char *argv[])
{
    while(true)
    {
        int* x = new int;
    }
    return 0;
}

Isso ocorre porque a cada iteração do loop você cria um novo inteiro sem nunca liberar o da iteração anterior. Claro que quando o programa for morto a memória volta a ficar disponível para o sistema operacional alocar para outro programa qualquer, mas se você tiver esses vazamentos de memória (memory leak) o seu programa vai usar mais memória do que o necessário e em casos extremos onde isso é repetido várias vezes isso pode ser fatal.

Se você não precisa de alocação dinâmica então declare a variável sem ser ponteiro como no exemplo abaixo.
int main(int argc, char *argv[])
{
    while(true)
    {
        int x;
    }
    return 0;
}

Assim a memória será automaticamente desalocada quando a variável sair de scope. Tendo dito isso, ponteiros e alocação dinâmica são muito úteis e você só precisa saber o que está fazendo quando usá-los.  ;D

mhkgalvez

Para finalizar o tópico, pois já fugimos um pouco do propósito original, rs, só queria uma confirmação: eu sempre uso ponteiros com o asterisco à esquerda do nome da variável, assim:

int *x;

Seu exemplo é a mesma coisa que a linha acima? Ou seja, int *x = int* x?

Até a próxima e valeu pela ajuda!
"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."

fpissarra

Citação de: mhkgalvez online 19 de Junho de 2010, 10:42
Para finalizar o tópico, pois já fugimos um pouco do propósito original, rs, só queria uma confirmação: eu sempre uso ponteiros com o asterisco à esquerda do nome da variável, assim:

int *x;

Seu exemplo é a mesma coisa que a linha acima? Ou seja, int *x = int* x?

Até a próxima e valeu pela ajuda!

Ambas as declarações 'int *x' e 'int* x' são válidas (acho). É uma prática de quem trabalha com C++, já que em C++ a declaração de referências é 'int& x'. Acredito que a declaração 'int &x' é inválida.

Pessoalmente prefiro 'int *x' para declarar ponteiros, o que é bem diferente de 'int& x' para declarar referências.

rudregues

Citação de: mhkgalvez online 19 de Junho de 2010, 10:42
Para finalizar o tópico, pois já fugimos um pouco do propósito original, rs, só queria uma confirmação: eu sempre uso ponteiros com o asterisco à esquerda do nome da variável, assim:

int *x;

Seu exemplo é a mesma coisa que a linha acima? Ou seja, int *x = int* x?

Até a próxima e valeu pela ajuda!
não é aconselhável declarar assim:
int* x;

pois se você for declarar mais de uma variável  vai confundir:
int* x,y,z;      --->>> ponteiro x e inteiros y e z  (significa que apenas x é ponteiro)

pra não confundir(coloca-se o * logo antes da variável):
int *x,y,*z;         --->>>ponteiros x e z   e inteiro y

no seu caso o melhor é escrever assim:
int *x;

[ ]'s
Gentoo — Controle total sobre o sistema.

mhkgalvez

"A quem vencer, eu o farei coluna no templo do meu Deus, e dele nunca sairá; e escreverei sobre ele o nome do meu Deus, e o nome da cidade do meu Deus, a nova Jerusalém, que desce do céu, do meu Deus, e também o meu novo nome."