Test Foro de elhacker.net SMF 2.1

Programación => Programación C/C++ => Mensaje iniciado por: HardForo en 13 Abril 2016, 23:23 PM

Título: Problema basico con listas en C
Publicado por: HardForo en 13 Abril 2016, 23:23 PM
Buenas,

   Estoy tratando de recordar algo de C y punteros y tengo un problema demas de basico con listas:  sino la creo primero no me funciona el push()


#include <stdlib.h>
#include <stdio.h>

struct Node {
int value;
struct Node* next;
};


Node* new_node(int value){
Node * n = (Node *) malloc(sizeof(Node));
n->value = value;
n->next = NULL;
return n;
}


void print_list(Node* head)
{
Node *prev;

prev =  head;
while (prev)
{
printf("%d\n", prev->value);
prev = prev->next;
}
}


/*
Mock de lista circular
*/
Node* lista_circular()
{
Node *x,*y,*z,*head;

x = new_node(1); head = x;
y = new_node(2); x->next = y;
z = new_node(3); y->next = z;
z->next = x;

return x;
}

/*
Mock de lista lineal
*/
Node* lista_lineal()
{
Node *x,*y,*z;

x = new_node(1);
y = new_node(2); x->next = y;
z = new_node(3); y->next = z;

return x;
}


/////////////////////////////////////////////////////
//
//  OPERACIONES


/*
Cuenta nodos
*/
int count(Node *lista)
{
int c = 0;
Node *e;

e = lista;
while (e){
c++;
e = e->next;
}

return c;
}


/*
Insertar al final de la lista

Solo tiene sentido si la lista es lineal *
*/
void push(Node* lista, int value)
{
Node *e,*prev,*n;
int v;

// camino hasta el final de la lista (poco eficiente, mejor tener puntero al final)
e = lista;
while (e){
prev = e;
e = e->next;
}

// creo nodo
n = new_node(value);

if (lista == NULL)
{
lista = n;
//printf("Elem: %d\n",lista->value);
//printf("Elementos: %d\n",count(lista));
}
else
// inserto al final
prev->next = n;
}


int main() { 


Node *lista = NULL;

//lista = lista_lineal();
push(lista,20);
push(lista,40);

print_list(lista);
printf("Elementos: %d\n",count(lista));
}


Me ayudan ?
Título: Re: Problema basico con listas en C
Publicado por: ivancea96 en 13 Abril 2016, 23:26 PM
¿Cómo metes cosas en una caja que no existe?

Tú mismo lo has dicho, hay que crearlo primero.
Título: Re: Problema basico con listas en C
Publicado por: HardForo en 13 Abril 2016, 23:31 PM
Amigo @ivancea96: 

     Es que al hacer el push() creo un nodo (alloco memoria, seteo el siguiente a NULL, ..)  y luego pregunto si la lista esta vacia (lista == NULL) y si es asi hago que apunte a ese nodo recien creado:

        // creo nodo
n = new_node(value);


if (lista == NULL)
{
// creo la lista
lista = n;
        }


Por eso no entiendo porque no me funciona (en que la estoy embarrando)   :-[
Título: Re: Problema basico con listas en C
Publicado por: ivancea96 en 14 Abril 2016, 00:35 AM
Oh, no leí el push.

En ese caso, pon "lista = push(lista, _)". En push retornas la lista nueva en caso de que haya sido creada, así que necesitarás asignarla.
Título: Re: Problema basico con listas en C
Publicado por: MAFUS en 14 Abril 2016, 00:36 AM
Respuesta rápida:
Los parámetros de una función son una copia local del argumento de llamada.
Respuesta algo menos rápida:
Debes pasar un puntero a lista como argumento de la función.
Te doy media solución:
void push(lista **head, int value);

A partir de aquí me detengo y te invito a pensar porqué debe ser así.
Título: Re: Problema basico con listas en C
Publicado por: HardForo en 14 Abril 2016, 00:37 AM
@MAFUS: estaba pensando justamente que es que esta pasando por valor y no por referencia pero luego me puse a pensar....... pero si es un puntero o no ?

Voy a probar lo que me dices (que lo habia pensado) pero no entiendo el fundamento.


@ivancea96:
gracias por responder, ni idea que se supone hace (_) pero me da error:

Citarerror: '_' was not declared in this scope
Título: Re: Problema basico con listas en C
Publicado por: ivancea96 en 14 Abril 2016, 00:38 AM
El _ era para que pusieras ahí lo que quisieras. El número que le pasas al push. Puse '_' porque no afectaba a la explicación xd
Título: Re: Problema basico con listas en C
Publicado por: HardForo en 14 Abril 2016, 01:00 AM
@MAFUS:

Bueno...... he reflexionado y entendido lo que me dices (el problema es que intento modificar un puntero en el caso de que no exista la linea pero pasa por valor) pero no logro hacerlo funcionar....  :-(

Serias tan amable de mostrarme como se hace ?  no soy estudiante, ni siquiera soy del area de "sistemas", es solo un hobbie pero me esta sacando chispas.

Gracias desde ya
Título: Re: Problema basico con listas en C
Publicado por: MAFUS en 14 Abril 2016, 15:53 PM
Está un poco modificado pues defino un puntero directamente, pero los argumentos de push y pop esperan un puntero a puntero.

Como pusiste un ejemplo con push supuse que era una pila, así que el ejemplo implementa una pila.


#include <stdio.h>
#include <stdlib.h>

typedef struct nodo_t {
    int valor;
    struct nodo_t* siguiente;
} nodo, *pila;

pila nueva_pila() {
    return NULL;
}

void push(pila *l, int n) {
    nodo *nuevo = malloc(sizeof(nodo));
    nuevo->valor = n;
    nuevo->siguiente = *l;
    *l = nuevo;
}

int pop(pila *l, int *dato) {
    int exito = 0;
    *dato = 0;
    nodo* a_borrar;
    if(*l) {
        *dato = (*l)->valor;
        a_borrar = *l;
        *l = (*l)->siguiente;
        free(a_borrar);
        exito = 1;
    }
    return exito;
}
   
int main() {
    pila mi_pila;
    int valor;
    int exito;
   
    mi_pila = nueva_pila();
    push(&mi_pila, 3);
    push(&mi_pila, 5);
    exito = pop(&mi_pila, &valor);
    if(exito) printf("%i\n", valor);
    exito = pop(&mi_pila, &valor);
    if(exito) printf("%i\n", valor);
    exito = pop(&mi_pila, &valor);
    if(exito) printf("%i\n", valor);
   
    return 0;
}
Título: Re: Problema basico con listas en C
Publicado por: HardForo en 14 Abril 2016, 16:14 PM
@MAFUS:  te agradezco mucho, tus ejemplos son los mejores (ya habia visto otros tuyos con punteros aqui) y me sirven para aprender.

Te puedo consultar que haces aqui ?

typedef struct nodo_t {
    int valor;
    struct nodo_t* siguiente;
} nodo, *pila;


No entiendo la parte en que llamas al struct nodo_t como nodo pero tambien puntero a pila y ni siquiera sabria como buscar eso que haces que no se como se llama  :xD

Tu ejemplo corre perfectamente y lo estoy estudiando en mas detalle.


PD: no estaba implementando una pila pero definí un push en vez de un insertar porque era de las operaciones mas simples posibles para probar con que me podia encontrar ya que habia visto este tema en Pascal pero como que los punteros pasan por referencia.
Título: Re: Problema basico con listas en C
Publicado por: MAFUS en 14 Abril 2016, 16:42 PM
Muy buenas. Muchas gracias, me alegro que mis aportaciones te sirvan.

typedef, que supongo que no sabes que es, sirve para crear alias de tipos de datos. De forma simple
typedef int entero;
ahora escribir entero es como si escribieramos int por lo que
entero num;
es lo mismo que escribir
int num;

Del mismo modo se pueden crear datos de tipo puntero
typedef int *puntero_a_int
ahora puntero_a_int se ha convertido en un int *
por lo que el siguiente fragmento es válido

typedef int *puntero_a_int;
int a = 3;
puntero_a_int = &a;


Ahora al código
typedef struct nodo_t {
    int valor;
    struct nodo_t* siguiente;
} nodo, *pila;


lo que hace es crear un nodo básico en el empleo de listas y pilas:

struct nodo_t {
    int valor;
    struct nodo_t* siguiente;
}


Podría trabajar solo con eso, sustituyendo lo que en el código aparece como nodo por struct nodo_t y lista por struct nodo_t * pero creando esos dos alias el código queda más comprensible a simple vista.

Básicamente lista y nodo son casi lo mismo, uno es un puntero y el otro la estructura. Si sigues el código verás que con esos alias el código es más fácil de seguir.

Para ver como quedaría de la otra forma haz una copia del código y sustituye lista por struct nodo_t* y nodo por struct nodo_t y verás la diferencia.
Título: Re: Problema basico con listas en C
Publicado por: HardForo en 14 Abril 2016, 18:54 PM
@MAFUS:  nuevamente gracias..... la verdad es que pensaba que typedef era un alias de struct porque siempre lo vi en ese contexto, aprendí un par de cosas más (mil gracias)

He intentado re-escribir el programa original con lo que me has enseñado pero sigo sin poder usar el doble puntero:


#include <stdlib.h>
#include <stdio.h>


typedef struct nodo_t {
int value;
struct nodo_t* next;
} node, *lista, **listaPTR;


lista new_node(int value){
node * n = (node *) malloc(sizeof(node));
n->value = value;
n->next = NULL;
return n;
}

lista new_lista(){
return NULL;
}


void print_list(lista head){
node *prev;

prev =  head;
while (prev)
{
printf("%d\n", prev->value);
prev = prev->next;
}
}


void push(lista list, int value)
{
node *e,*prev,*n;
int v;

// camino hasta el final de la list (poco eficiente, mejor tener puntero al final)
e = list;
while (e){
prev = e;
e = e->next;
}

// creo nodo
n = new_node(value);

if (list == NULL)
{
list = n;
//printf("Elem: %d\n",list->value);
//printf("Elementos: %d\n",count(list));
}
else
// inserto al final
prev->next = n;
}


int main() {  


lista l = new_lista();

push(l,20);
push(l,40);

print_list(l);

}


He intentado estos reemplazos:


void push(listaPTR listp, int value)   // aqui
{
node *e,*prev,*n;
int v;

e = &listp;  // aqui



Pero no me funciona...... y realmente necesito entender en que estoy fallando en mi intento y verlo funcionar ya que estoy muy novato.


Nuevamente gracias!
Título: Re: Problema basico con listas en C
Publicado por: MAFUS en 14 Abril 2016, 20:19 PM
Son fallos básicos de punteros pero a los que se pierde rápidamente la pista cuándo no se está acostumbrado. Con práctica los irás solucionando.

Tienes razón al escribir
void push(listaPTR listp, int value)
pues necesitas variar la dirección contenida en lista si esta apunta a NULL, pero fallas cuándo escribes e = &listp;
por el hecho de que ahora e apunta a la dirección de listp.

*** Dando una vuelta de tuerca al tratamiento de la memoria en C ***
listp debe considerarse una variable local de la función push por lo que obtener su dirección no hará que apuntes a la lista original sino a una parte de la pila de la función a la que estás.
Lo que debes obtener es el valor que hay guardado en listp que será la dirección en la que se guarda la lista.
Sé que es complicado dicho así, pero si lo llegas a entender se acabarán los problemas con los punteros. Como regla nemotécnica piensa en que vas a sacar un asterisco a tu puntero. Algo así:
Tu tienes **listaPTR
Dentro tienes *lista
Como ves hay que quitarse un asterisco de encima para obtener *lista.
En el código
node *e
haces un puntero a node. Es lo mismo que hacer
lista e
ya que node * es lo mismo que lista.
Ahora viene cuando sacamos un asterisco **listaPTR. Para ello, durante la asignación cogemos ese asterisco, lo sacamos de **listaPTR y lo dejamos a su izquierda. En código queda así:
e = *list
Ahora los dos objetos son del mismo tipo y se respeta la igualdad.

Bien. Pasemos a los argumentos de llamada de la función push.
Hemos creado nuestra lista, l, y su tipo es *lista pero el argumento espera que le entreguemos la dirección de memoria donde se guarda ésta. Otra vez podemos hacer uso de una regla nemotécnica: debemos pensar que queremos incluir un asterisco a esa variable y para ello cogemos y, ni cortos ni perezosos, se lo escribimos a mano con el símbolo & a su izquierda. De hecho & parece como si quisiéramos escribir un asterisco de forma rápida y sin levantar el lápiz del papel.
En código queda así:

push(&l,20);

Así *lista con un asterisco más digievoluciona a **listaPTR.

Tu código funcional quedaría de la siguiente forma:
#include <stdlib.h>
#include <stdio.h>


typedef struct nodo_t {
    int value;
    struct nodo_t* next;
} node, *lista, **listaPTR;


lista new_node(int value){
    node * n = (node *) malloc(sizeof(node));
    n->value = value;
    n->next = NULL;
    return n;
}

lista new_lista(){
    return NULL;
}


void print_list(lista head){
    node *prev;

    prev =  head;
    while (prev)
    {
        printf("%d\n", prev->value);
        prev = prev->next;
    }
}


void push(listaPTR list, int value)
{
    node *e,*prev,*n;
    int v; /* v no se usa */

    // camino hasta el final de la list (poco eficiente, mejor tener puntero al final)
    e = *list;
    while (e){
        prev = e;
        e = e->next;
    }

    // creo nodo
    n = new_node(value);

    if (*list == NULL)
    {
        *list = n;
        //printf("Elem: %d\n",list->value);
        //printf("Elementos: %d\n",count(list));
    }
    else
        // inserto al final
        prev->next = n;
}


int main() {
    lista l = new_lista();
    push(&l,20);
    push(&l,40);
    print_list(l);
}
Título: Re: Problema basico con listas en C
Publicado por: HardForo en 14 Abril 2016, 20:33 PM
Me funcionó perfecto y lo entendí  ;-)

Como conclusión adicional saco que:


pp = &p;  
*pp = p;


Muchisisisimas gracias!


PD: como lista es lo mismo que *nodo, hice unos cambios minimos en el typedef struct:


typedef struct node_t {
int value;
struct node_t* next;
} nodo, *nodoP, *lista, **nodoPP, **listaP;


Título: Re: Problema basico con listas en C
Publicado por: MAFUS en 14 Abril 2016, 21:51 PM
Muy bien, veo que lo has entendido.
Ahora recuerda que es mala práctica abusar de typedef, y más si typedef esconde detrás punteros. Debe usarse para esclarecer el código, no ofuscarlo. Muchas veces es mejor, para un tercero, saber que un tipo de dato es un puntero; a no ser que sea intención tuya una ocultación expresa de los interiores del tipo de dato en cuestión, simulando a lo que se hace con la programación orientada a objetos.