Italo Info


Ponteiros de memória

Quando precisamos manipular uma lista de variáveis, utilizamos o array. No entanto, se declaramos um array de 100 posições e apenas 5 forem utilizadas, 95 posições na memória foram alocadas sem necessidade. Para resolver esse problema, a linguagem C permite a alocação dinâmica de memória que permite que a memória para as variáveis seja alocada conforme necessário.

Para alocação dinâmica de memória, é necessário criar variáveis de referência com um * conforme o exemplo abaixo:


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

int main() {
    int *num;
    
    num = malloc( sizeof( int ) );  
    *num = 10;
    
    printf( "Numero= %d", *num );
    return 0;
}

No exemplo acima, foi criada uma variável ponteiro no início do programa principal e, logo depois, a função malloc foi chamada para alocar memória dinamicamente para armazenar um valor inteiro. Repare que foi passada para a função malloc o resultado de outra função: a função sizeof que retorna a quantidade de bytes necessários para armazenar um valor de um determinado tipo de variável. Ex: sizeof( int ) retorna a quantidade de memória necessária para armazenar um número inteiro. A função malloc recebe a quantidade de bytes a serem alocados dinamicamente e depende da inclusão da biblioteca stdlib.h. Repare que para utilizar o conteúdo da variávei ponteiro é necessario um asterisco antes dela. Veja o exemplo abaixo:


#include <stdio.h>

int main() {
    int *num;
    
    *num = 10; // Isso gera um comportamento estranho
               // porque nao foi alocada memoria para 
               // esta variavel ponteiro.
               // eh necessario utilizar a funcao malloc 
               // ou calloc primeiro
    
    return 0;
}


Lista encadeada

Através de ponteiros de memória, é possível criar uma lista em que a memória vai sendo alocada conforme a lista cresce. Para tanto, cada elemento da lista tem que ter uma referência (Ponteiro de memória) para o próximo elemento da lista. Isso tras uma desvantágem da lista encadeada, em comparação com vetores. Cada elemento da lista encadeada precisa de memória para armazenar o endereço de memória do próximo elemento. Isso significa que, por exemplo, uma lista encadeada de 3 elementos gasta mais memória que um array de 3 elementos. Abaixo um exemplo de estrutura que permite encadear elementos em uma lista:


typedef struct st_numero {
    int num;
    struct st_numero *prox;
} numero;

Repare na estrutura acima que é criada uma variável prox que é uma referência a próxima posição de memória. Ou seja, para manipulação de uma lista encadeada, é necessário que cada elemento da lista, tenha uma referência que aponta para o próximo elemento. Para utilizar a variável num da estrutura é necessário fazer conforme abaixo:


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

typedef struct st_numero {
    int num;
    struct snum *prox;
} numero;

int main() {
    numero *p;
    p = malloc( sizeof( numero ) );
    
    (*p).num = 10;             
    
    printf( "Numero= %d", (*p).num );   
    
    return 0;
}

Perceba que p é do tipo ponteiro mas, *p e (*p) são do tipo estrutura (numero) e (*p).num é do tipo inteiro (int). Inclusive, (*p).num é o mesmo que p->num mas, para essa aula, vou utilizar apenas a primeira notação.

A tabela abaixo é uma representação de uma lista encadeada de números:

Endereço de memória Número Próximo
001 4 003
003 2 004
004 7 006
006 10 NULL

Perceba na tabela acima que quanto não há nenhum elemento a ser referenciado como próximo, a variável proximo recebe NULL.

Abaixo uma estrutura (struct) de pessoa para uma lista encadeada:


typedef struct st_pessoa {
    int id;
    char nome[100];
    char tel[100];
    int idade;
    float altura;
    struct st_pessoa *prox;
} pessoa;

Preste atenção na variável prox da estrutura acima. Ela é do tipo struct st_pessoa* e, para implementação de uma lista encadeada, é necessário que esta variável referencie o próximo elemento (tipo pessoa) da lista. Em um registro que é o ultimo da lista, a variável prox deve ser igual a NULL.

Abaixo um exemplo de função para inserir na lista simplesmente encadeada:


void insere( pessoa **lista, int id, char nome[], char tel[], int idade, float altura ) {
    pessoa *novo, *perc;
    novo = malloc( sizeof( pessoa ) );
    (*novo).id = id;
    strcpy( (*novo).nome, nome );
    strcpy( (*novo).tel, tel );
    (*novo).idade = idade;
    (*novo).altura = altura;
    (*novo).prox = NULL;
    
    if ( *lista == NULL ) {
        *lista = novo;
    } else {
        perc = *lista;
        while( (*perc).prox != NULL )
            perc = (*perc).prox;
        (*perc).prox = novo;
    }
}

Na função acima, a variável novo é o ponteiro do elemento a ser inserido na lista. A função malloc é chamada e é alocada memória para o ponteiro novo. Após isso, os dados da pessoa são carregados no registro apontado por novo (perceba que, inicialmente, todo registro inserido na lista tem a variável prox igual a NULL. Logo depois vem a varificação. Se o conteúdo apontado por **lista (que é acessado via *lista) for igual a NULL, significa que não tem nenhum registro inserido na lista e, então, a variável *lista deve apontar para a variável novo que, nesse caso passa a ser o único elemento da lista. caso a lista não esteja vasia, então a variável perc é posicionada no ultimo registro da lista para, então, fazer sua variável prox apontar para o novo registro (referenciado pela variável novo). Ou seja: (*perc).prox = novo.

Abaixo uma função para imprimir os elementos da lista encadeada:


void imprime_lista( pessoa *lista ) {
    pessoa *perc = lista;
    while( perc != NULL ) {
        printf( "\n" );
        printf( "\nPessoa ID(%d)", (*p).id );
        printf( "\n  Nome= %s", (*p).nome );
        printf( "\n  Telefone= %s", (*p).tel );
        printf( "\n  Idade= %d", (*p).idade );
        printf( "\n  Altura= %.2f", (*p).altura );
                
        perc = (*perc).prox;
    }
}

A função acima é mais simples que a função de inserção na lista encadeada. Na função imprime_lista acima, uma variável apontador perc é declarada e inicializada com a variável lista, passando assim a apontar para o mesmo endereço de memória que lista. Logo após, vem um laço cuja condição de parada é perc == NULL, ou seja, enquanto perc for diferente de NULL, o registro de pessoa apontado pelo ponteiro perc é impresso na tela e perc passa a apontar para o próximo registro da lista.

Abaixo, uma função para buscar um registro de pessoa por ID:


pessoa* busca( pessoa *lista, int id ) {
    pessoa *perc = lista;
    
    while( perc != NULL ) {
        if ( (*perc).id == id )
            return perc;    
        
        perc = (*perc).prox;        
    }
    
    return NULL;
}

No exemplo acima, a cada iteração do laço while é verificado se o registro atual apontado por perc tem ID igual ao ID buscado. Se sim, o apontador do registro é retornado pela função, se não, perc passa a apontar para o próximo registro da lista até que o ID seja encontrado ou o valor perc receba valor NULL.

Abaixo um exemplo de exclusão da lista por ID:


...

typedef int boolean;

...

boolean deleta( pessoa** lista, int id ) {
    pessoa *aux = *lista;
    pessoa *ant = *lista;
    boolean achou = FALSE;
    
    while( !achou && aux != NULL ) {
        if ( (*aux).id == id ) {
            achou = TRUE;
        } else {
            ant = aux;
            aux = (*aux).prox;
        }
    }
    
    if ( achou ) {
        if ( aux == *lista ) {      
            *lista = (*aux).prox;
        } else {        
            (*ant).prox = (*aux).prox;
        }
        free( aux );        
    }
    
    return achou;
}

No exemplo acima, uma busca é feita pelo ID passado como parâmetro. Caso exista algum registro com o ID buscado, a variável aux é apontada para esse registro e a variável ant é apontada para o registro anterior. Feito isso, se o registro foi encontrado, então a função remove-o da lista encadeada. Para tanto, é necessário verificar se o registro a ser removido é o primeiro da lista. Se sim, então a variável lista deve apontar para o próximo do registro a ser excluido e, caso contrário, a variável próximo do registro anterior (apontado por ant) recebe o próximo do registro encontrado (apontado por aux). Depois disso, o registro é removido com uso da função free.

Abaixo o exemplo agenda com ponteiros e alocação dinâmica de memória com as funções já mostradas:


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

#define TRUE 1
#define FALSE 0

typedef int boolean;

typedef struct spessoa {
    int id;
    char nome[100];
    char tel[100];
    int idade;
    float altura;
    struct spessoa *prox;
} pessoa;

void insere( pessoa **lista, int id, char nome[], 
                             char tel[], int idade, float altura ) {
    pessoa *novo, *perc;
    novo = malloc( sizeof( pessoa ) );
    (*novo).id = id;
    strcpy( (*novo).nome, nome );
    strcpy( (*novo).tel, tel );
    (*novo).idade = idade;
    (*novo).altura = altura;
    (*novo).prox = NULL;
    
    if ( *lista == NULL ) {
        *lista = novo;
    } else {
        perc = *lista;
        while( (*perc).prox != NULL )
            perc = (*perc).prox;
        (*perc).prox = novo;
    }
}

void imprime_pessoa( pessoa *p ) {    
    printf( "\nPessoa ID(%d)", (*p).id );
    printf( "\n  Nome= %s", (*p).nome );
    printf( "\n  Telefone= %s", (*p).tel );
    printf( "\n  Idade= %d", (*p).idade );
    printf( "\n  Altura= %.2f", (*p).altura );
}

void imprime_lista( pessoa *lista ) {
    pessoa *perc = lista;
    while( perc != NULL ) {
        printf( "\n" );
        imprime_pessoa( perc );
                
        perc = (*perc).prox;
    }
}

pessoa* busca( pessoa *lista, int id ) {
    pessoa *perc = lista;
    
    while( perc != NULL ) {
        if ( (*perc).id == id )
            return perc;    
        
        perc = (*perc).prox;        
    }
    
    return NULL;
}

boolean deleta( pessoa** lista, int id ) {
    pessoa *aux = *lista;
    pessoa *ant = *lista;
    boolean achou = FALSE;
    
    while( !achou && aux != NULL ) {
        if ( (*aux).id == id ) {
            achou = TRUE;
        } else {
            ant = aux;
            aux = (*aux).prox;
        }
    }
    
    if ( achou ) {
        if ( aux == *lista ) {        
            *lista = (*aux).prox;
        } else {        
            (*ant).prox = (*aux).prox;
        }
        free( aux );        
    }
    
    return achou;
}

void menu() {
    system( "cls" );
    printf( "*** Agenda ***" );
    printf( "\n 1 - Inserir" );
    printf( "\n 2 - Listar" );
    printf( "\n 3 - Buscar" );
    printf( "\n 4 - Remover" );
    printf( "\n 0 - Sair" );
    printf( "\n**************" );
    printf( "\n\n" );
}

int main() {
    pessoa *lista = NULL;
    pessoa *p = NULL;
    int tam = 0;
    int id;
    char nome[100];
    char telefone[100];
    int idade;
    float altura;
    char ch = '\0';
    
    do {        
        menu();
        printf( "Informe a opcao: " );
        fflush( stdin );
        fflush( stdout );
        ch = getchar();
        printf( "\n" );

        switch( ch ) {
            case '1':
                printf( "Informe o nome: " );
                scanf("%s",nome);
                printf( "Informe o telefone: " );
                scanf("%s",telefone);
                printf( "Informe a idade: " );
                scanf("%d",&idade);
                printf( "Informe a altura: " );
                scanf("%f",&altura);
                
                insere( &lista, ++tam, nome, telefone, idade, altura );
                
                printf( "\nPessoa inserida com sucesso!" );
                fflush( stdin );
                fflush( stdout );
                getchar();                
                break;
            case '2':
                printf( "***** Lista de pessoas *****" );
                imprime_lista( lista );
                fflush( stdin );
                fflush( stdout );
                getchar();
                break;
            case '3':
                printf( "Informe o ID: " );
                scanf("%d",&id);
                
                p = busca( lista, id );
                
                if ( p == NULL ) {
                    printf( "\nPessoa nao encontrada..!" );
                } else {                
                    printf( "\n" );
                    imprime_pessoa( p );
                }
                fflush( stdin );
                fflush( stdout );
                getchar();
                break;
            case '4':
                printf( "Informe o ID: " );
                scanf("%d",&id);
                
                boolean removeu = deleta( &lista, id );                
                if ( removeu ) {
                    printf( "\nPessoa removida com sucesso...!" );
                } else {
                    printf( "\nPessoa nao encontrada..!" );
                }
                fflush( stdin );
                fflush( stdout );
                getchar();                
                break;
        }
    } while ( toupper( ch ) != '0' );  
}

No exemplo acima, repare que há necessidade de inicializar a variável lista com NULL. Isso por causa da função insere que verifica se a lista passada como parâmetro aponta para NULL.

Repare também que, assim como na aula de arquivos, foi necessário o uso da função fflush para limpar os buffers de entrada e saída padrões antes de cada leitura do teclado.


E assim chegamos ao final deste curso de programação em C. No futuro pretendo produzir um curso em Java, onde, parte do que foi estudado nesse curso, facilitará muito o aprendizado da linguagem Java. Isso porque as duas linguagens têm muito em comum, já que a linguagem Java é derivada da linguagem C++ que é derivada da linguagem C.

Volte sempre e até a próxima!