Agnor's HQ

Aula 14 - Apontadores - Parte II

Conceitos-Chave:

- variável alocada dinamicamente, variável que está alocada na memória dinâmica, cujo tamanho pode ser alterada
- new, cria uma variável na memória dinâmica e retorna um apontador para a mesma
- delete, desaloja da memória dinâmica a variável dinâmica apontada
- operações com endereço, é possível aceder à variável seguinte, muito útil para arrays

Memória dinâmica

Até agora temos alojado as variáveis e arrays estaticamente. Isto significa que quando inicializámos uma variável, esta irá possuir sempre o mesmo tamanho ao longo do programa.

Numa variável normal, não há qualquer problema, mas num array isto traz limitações. A primeira é que temos de conhecer o tamanho do array antes de começar o programa. Outra é que não podemos alterar o tamanho do array, consoante as nossas necessidades.

Uma solução para o primeiro problema é especificar um valor máximo para o array. No entanto, estaremos a gastar espaço na memória desnecessariamente e nada nos dá garantia que o espaço será suficiente (se tal não for, preparem-se para erros constantes durante a execução).

A única alternativa viável é alojá-los dinamicamente. Uma variável dinâmica só pode ser acedida através de um apontador, ou seja, ao criarmos uma variável dinâmica, não podemos ter acesso directo à mesma, apenas através de um apontador. Para alojarmos uma variável dinamicamente, teremos que usar o operador new:

int *apont = new int;

Para perceberem o que está a acontecer no exemplo acima, vejam o seguinte esquema:

Esquema:

1 - Declaramos um apontador para um inteiro (int *apont)

2 - Criamos um inteiro na memória dinâmica (através do operador new). Notem que o inteiro que criamos não tem um "nome" (identificador é o termo mais correcto). Para podermos aceder ao inteiro criado na memória dinâmica temos, então, que utilizar um apontador (neste caso é o apontador "apont").

Poderemos depois alterar o valor do inteiro criado como num apontador normal:

*apont = 20;

Não se esqueçam que estamos a afectar o valor apontado pelo apontador (uma boa maneira de ler é "valor apontado por apont será igual a 20")

Poderemos também definir um valor ao criarmos uma variável na memória dinâmica:

int *apont = new int(20);

Com a criação de memória dinâmica temos também uma enorme responsabilidade: de libertá-la.

Libertar a memória dinâmica

Enquanto que a memória estática é libertada automaticamente pelo programa (não precisamos de nos preocupar com ela), a memória dinâmica tem de ser libertada pelo programador. Se nos esquecermos de a libertar esta irá persistir sempre. Irá então utilizar recursos do sistema ou mesmo esgotar toda a memória do sistema. Moral da história: não se esqueçam nunca de libertar a memória após criarem (quando não precisarem mais dela, claro).

Para libertar a memória criada utiliza-se o operador delete.

int *apont = new int(20);

//fazer alguma coisa....

delete apont;

Simples, mas o seu esquecimento é a causa de muitos erros graves de programas.....

Nota:

Ao fazermos delete apont; estaremos a libertar a variável criada em new int(20); e não o apontador apont. Não nos temos de preocupar com ele, uma vez que é um apontador criado estaticamente e será eliminado estaticamente.

Dica:

É bastante aconselhável que façam apont = NULL; após o delete apont;.
Como expliquei nos exemplos acima, fazer delete apont; apenas limpa a variável da memória dinâmica. O apontador apont vai continuar a apontar para o mesmo endereço de anteriormente. Para evitar que o programador o utilize por engano (após o termos eliminado), ao fazer apont = NULL; estamos a fazer com que o apontador não aponte para nada, e podemos utilizar uma condição para saber se o apontador existe:

if (!apont) //o mesmo que if (apont == NULL)
//...

Alojar e libertar arrays dinamicamente

Alojar um array dinamicamente é tão simples como alojar uma variável normal:

int *array = new int[100]; // aloja um array com 100 ints

e para libertar a memória:

delete [] array;

Operações com o endereço

Para ilustrar uma simples operação com o enderenço de variáveis vejam o exemplo abaixo:

#include <iostream>
using namespace std;

int x = 20;
int y = 10;
int *pointer; //apontador para uma variável do tipo int

void printthis();

int main()
{

    pointer = &x; //o apontador assume o valor do endereço da variável

    printthis();

    cout << "Vai ser incrementado o endereco por um valor, prima ENTER." << endl;
    cin.get();

    pointer++;

    printthis();

    cin.get();
    return 0;
}

void printthis()
{
    cout << "Variavel x:" << endl;
    cout << "\nValor de x: " << x << endl;
    cout << "Endereco de x: " << &x << endl;

    cout << "\n\nVariavel y:" << endl;
    cout << "\nValor de y: " << y << endl;
    cout << "Endereco de y: " << &y << endl;

    cout << "\n\nVariavel pointer:" << endl;
    cout << "\nValor de pointer: " << pointer << endl;
    cout << "Endereco de pointer: " << &pointer << endl;
    cout << "Valor da variavel apontada por pointer (x): " << *pointer << "\n------------\n" << endl;
}
Exemplo 14.1 - operações com o endereço

Que me dá o seguinte output

Variavel x:

Valor de x: 20
Endereco de x: 0x434000

Variavel y:

Valor de y: 10
Endereco de y: 0x434004

Variavel pointer:

Valor de pointer: 0x434000
Endereco de pointer: 0x437010
Valor da variavel apontada por pointer (x): 20
------------

Vai ser incrementado o endereco por um valor, prima ENTER.

Variavel x:

Valor de x: 20
Endereco de x: 0x434000

Variavel y:

Valor de y: 10
Endereco de y: 0x434004

Variavel pointer:

Valor de pointer: 0x434004
Endereco de pointer: 0x437010
Valor da variavel apontada por pointer (x): 10

Como podem ver ao incrementarmos pointer estamos na verdade a fazer com que pointer tome o endereço da variável seguinte (4 bytes depois), podendo então interferir no valor desta.

Nota:

Este exemplo serviu apenas para explicar uma operação simples com o endereço. O próprio exemplo não é muito correcto, pois em situações reais, nada nos garante que a variável que está posicionada depois na memória é a variável que pretendemos usar.

Apontadores e arrays

Um apontador é equivalente ao endereço do primeiro elemento que aponta. Assim:

#include <iostream>
using namespace std;

int array[10];
int *pointer;

int main()
{
    pointer = array;
    array[0] = 50;

    cout << "array = " << array[0] << endl;
    cout << "pointer = " << *pointer << endl;

    cin.get();
    return 0;
}
Exemplo 14.2 - Apontadores vs Arrays (Teste I)

Daria o mesmo resultado. Poderemos então aproveitar isso para aceder aos outros elementos do array. Por exemplo, se quisermos aceder ao elemento nº 7:

#include <iostream>
using namespace std;

int array[10];
int *pointer;

int main()
{
    pointer = array;
    array[7] = 50;

    cout << "array = " << array[7] << endl;
    cout << "pointer = " << *(pointer+7) << endl;

    cin.get();
    return 0;
}
Exemplo 14.3 - Apontadores vs Arrays (Teste II)

Na verdade um array é uma espécie de apontador. Assim:

#include <iostream>
using namespace std;

int array[10];
int *pointer;

int main()
{
    pointer = array;
    array[0] = 50;

    cout << "array = " << *array << endl;
    cout << "pointer = " << *pointer << endl;

    cin.get();
    return 0;
}
Exemplo 14.4 - Apontadores vs Arrays (Teste III)

E também:

#include <iostream>
using namespace std;

int array[10];
int *pointer;

int main()
{
    pointer = array;
    array[7] = 50;

    cout << "array = " << *(array+7) << endl;
    cout << "pointer = " << *(pointer+7) << endl;

    cin.get();
    return 0;
}
Exemplo 14.5 - Apontadores vs Arrays (Teste IV)

Na verdade, ao utilizarmos os parenteses rectos ( [] ), estamos a utilizar um operador de desreferência, chamado offset operator ou em português operador de indexação. Assim podemos fazer:

#include <iostream>
using namespace std;

int main()
{
    int array[2];
    int *pointer;

    pointer = array;

    array[0] = 0;
    pointer[1] = 1;

    for (int i = 0; i < 2; i++)
    {
        cout << "Array[" << i << "] = " << array[i] << endl;
        cout << "Apontador[" << i << "] = " << pointer[i] << endl;
    }

    cin.get();
    return 0;
}

Exemplo 14.6 - Apontadores vs Arrays (Teste V)

Ou seja é equivalente fazer:

array[10] = 15;

ou fazer :

*(array+10) = 15;

Isto torna-se especialmente útil para funções. Até agora nunca utilizamos arrays como argumentos de funções, vejam como no exemplo seguinte:

#include <iostream>
using namespace std;

int limpar_array(int *array, int tamanho);  

int main()
{
    const int TAMANHO = 10; //variável constante

    int lista[TAMANHO];
    lista[0] = 4;   //só para testarmos se o array foi limpo

    cout << lista[0] << endl;

    limpar_array(lista, TAMANHO);

    cout << lista[0] << endl;

    cin.get();
    return 0;
}

int limpar_array(int *array, int tamanho)
{
    for (int i = 0; i < tamanho; i++)
    {
        array[i] = 0;
    }
}

Exemplo 14.7 - Função limpar array

Então qual é a diferença entre um apontador e um array? A diferença principal é que o tamanho de um array tem de ser constante, enquanto o tamanho de um apontador (utilizado para memória dinâmica) pode variar. Vejam o exemplo seguinte (uma versão aperfeiçoada do exemplo acima:

#include <iostream>
using namespace std;

int limpar_array(int *array, int tamanho);

int main()
{
    int tamanho;

    cout << "Insira um tamanho para o array (menor que 1000, e apenas uma sugestao)" << endl;
    cin >> tamanho;

    int *lista = new int [tamanho];

    lista[0] = 4;   //só para testarmos se o array foi limpo

    cout << lista[0] << endl;

    limpar_array(lista, tamanho);

    cout << lista[0] << endl;

    cin.get();
    cin.get();
    return 0;
}

int limpar_array(int *array, int tamanho)
{
    for (int i = 0; i < tamanho; i++)
    {
        array[i] = 0;
    }
}

Exemplo 14.8 - Função limpar array (versão 1.1)

Como podem ver, uma alteração muito pequena trouxe uma grande característica a este programa (embora continue a servir para praticamente nada :)

Final da aula 14 de C++:

Próxima aula -> C++ 15 - Classes - Parte II

Fazer o download do código fonte dos exemplos da aula WinZip
Fazer o download da aula em PDF (Brevemente)

Ir para o topo da página

Aulas de C++

Anúncios