Curso de C
Ponteiros


  Foi visto até aqui, que as variáveis armazenam dados na memória e esses dados podem ser de diversos tipos. Elas localizam-se em uma área separada do código executável, onde cada variável ocupa o número de bytes correspondente ao tipo de dado que ela armazena.
  As varáveis que foram apresentadas até o presente momento são do tipo estáticas. Entretanto, elas podem ser de dois tipos: estáticas e dinâmicas.
  Uma variável do tipo estática é uma variável que possui um nome que a referencia, é criada em tempo de compilação e esta está sempre no mesmo lugar da memória. Já a variável do tipo dinâmica não possui um nome associado a ela e é criada/destruída em tempo de execução do programa.
  Uma vez que a variável dinâmica não possui um nome associado a ela, ela é referenciada por outro tipo de variável: o ponteiro. O objetivo principal de um ponteiro é armazenar um endereço de memória para um determinado tipo de dado. Uma vantagem desse tipo de variável é que podemos "navegar" pela memória.


  Ponteiros

  Um ponteiro é uma variável que armazena um endereço de memória de um dado, para que seja possível referenciá-lo quando necessário. Além disso, ele define o tipo de dado que está armazenado naquele endereço.

  Observe a ilustração abaixo:
     ┌─────────┬──────┐
     │ Memória │ Dado │
     ├─────────┼──────┤
 p → │  1000H  │ 05H  │ i
     ├─────────┼──────┤
     │  1001H  │ 13H  │
     └─────────┴──────┘
  De acordo com a ilustração acima, temos:   No exemplo acima, a variável "i" estará sempre na posição &H1000. Entretanto, a variável "p" poderá ter seu valor alterado e passar a "apontar" para a posição &H1001.
     ┌─────────┬──────┐
     │ Memória │ Dado │
     ├─────────┼──────┤
     │  1000H  │ 05H  │ i
     ├─────────┼──────┤
 p → │  1001H  │ 13H  │
     └─────────┴──────┘

  A declaração de uma variável do tipo estática é feita utilizando-se o tipo de dado, seguido do nome da variável. Ex:
 int i;
  "i" é uma variável "int" do tipo estática.

  A declaração de uma variável do tipo ponteiro é feita de maneira semelhante. Devemos apenas acrescentar o sinal de asterisco "*" antes do nome da variável.

  Exemplo:
 int *p;
  "p" é um ponteiro que referencia um dado do tipo "int".

  A delcaração de um ponteiro também define o tipo de dado que ele está referenciando. Por exemplo, um ponteiro do tipo "int" referencia um valor inteiro, onde são relevantes os dois bytes consecutivos a partir do ponteiro. Um ponteiro do tipo "float" são relevantes os quatro bytes.
          int *p;                  float *q;
     ┌─────────┬──────┐       ┌─────────┬──────┐
     │ Memória │ Dado │       │ Memória │ Dado │
     ├─────────┼──────┤       ├─────────┼──────┤
 p → │  1000H  │      │   q → │  1000H  │      │
     ├─────────┼──────┤       ├─────────┼──────┤
     │  1001H  │      │       │  1001H  │      │
     └─────────┴──────┘       ├─────────┼──────┤
                              │  1002H  │      │
                              ├─────────┼──────┤
                              │  1003H  │      │
                              └─────────┴──────┘

  Vejamos agora como reproduzir o exemplo da primeira figura da página.
#include <stdio.h>

main()
{
  short int i=5;
  short int *p;

  p = &i;

  printf("Valor de i: %d\n",i);
  printf("Valor de p: %x\n",p);
}
  Saída:
  Valor de i: 5
  Valor de p: 1000

  Obs: o símbolo "&" retorna o endereço de memória de uma variável do tipo estática. Assim:
...
  printf("Valor de p: %x\n",p);
  printf("Valor de &i: %x\n",&i);
...
  são equivalentes e imprimirão o mesmo resultado, ou seja, 1000.

  Quando não atribuímos qualquer valor a uma variável, o valor inicial dela é aleatório. Ex:
int i;

printf("Valor de i: %d\n",i);
  Saída:
  Valor de i: 28816

  Com o ponteiro, acontece a mesma coisa. Ele contém um valor inicial de um endereço qualquer.
  No exemplo acima, o ponteiro "p" recebeu o endereço de memória da variável "i". Se não tivéssemos feito isso, "p" estaria apontando para qualquer outra posição de memória.

  É possível ler o valor do dado apontado pelo pelo ponteiro "p"?
  Sim. O símbolo "*" é utilizado juntamente com o ponteiro para ler o conteúdo do endereço referenciado.
  O tipo de dado lido nessa operação é aquele no qual o ponteiro foi definido. Nesse caso, ele retorna um "short int". Assim:
...
  printf("Valor de i: %d\n",i);
  printf("Valor de *p: %d\n",*p);
...
  são equivalentes e imprimirão o mesmo resultado, ou seja, 5.

  Relembrando:   O conceito de ponteiro é necessário para compreender a criação, uso e destruição de variáveis dinâmicas, que será abordado na seção seguinte.


  Alocação de memória

  A criação de variáveis dinâmicas é feita através da alocação dinâmica de memória. As funções malloc, calloc e realloc são utilizadas para isso.
  A localização dos dados alocados é aleatória, e é feita automaticamente pelo sistema operacional em tempo de execução do programa.

  Sintaxes:
 ponteiro = malloc(tamanho_do_vetor * tamanho_da_variavel);
 ponteiro = calloc(tamanho_do_vetor, tamanho_da_variavel);
 ponteiro = realloc(ponteiro_antigo, tamanho_do_vetor * tamanho_da_variavel);

  Conforme ja foi dito, a variável dinâmica não possui qualquer nome que a referencie. Assim, quando são criadas, devemos utilizar ponteiros para armazenar sua localização na memória. Exemplo:
 short int *p;
 p = (shot int *) malloc(1);
  Nesse exemplo, uma variável com o tamanho de um byte é criada, e sua localização é armazenada no ponteiro "p" do tipo "short int".

  Será chamado de "célula" o bloco consecutivo de bytes que armazena o conteúdo de um determinado tipo de variável.
  É possível alocar apenas uma célula de informação, que será a variável dinâmica, ou um conjunto de células em posições contíguas de memória, que será o vetor dinâmico.
  Nas listas encadeadas (mais adiante), são alocadas células individuais para compor uma lista, conforme a necessidade. Nesse caso, elas estarão em posições aleatórias de memória.

  O tamanho do espaço alocado depende do produto do tamanho do dado pelo o número total de células do vetor.
 tamanho_do_vetor x tamanho_da_variavel
  Por exemplo, cada célula de um dado do tipo "short int" necessita de 1 byte na memória. Já o "int" necessita de 2 bytes. Assim, um vetor com 10 posições de "int" terá 10x2 ou 20 bytes alocados.


  Alocação de variáveis

  Assim como a variável comum, armazena somente 1 informação para um determinado tipo de dado.

  Exemplo de uso do malloc para uma variável do tipo "int":
 int *p;
 p = (int *) malloc(2);
  Aloca 2 bytes para uma variável inteira, devolvendo a referência de memória para o ponteiro "p".

  Podemos utilizar a função sizeof() para calcular o tamanho de um tipo variável. Ex:
 p = (int *) malloc(sizeof(int));
  Obs: é necessário fazer o casting para o tipo de ponteiro declarado. No exemplo anterior, é a expressão: "(int *)".

  Graficamente:
  ┌─────────┬──────┐
  │ Memória │ Dado │
  ├─────────┼──────┤
  │  153FH  │ 00H  │ ← p 
  ├─────────┼──────┤
  │  1540H  │ 00H  │
  └─────────┴──────┘
  A diferença entre malloc e calloc é que calloc atribui a todos os bytes o valor igual a zero.
  Já a função realloc aloca uma nova área de memória, reajustando o tamanho do vetor antigo. Ele copia os dados.

  Obs: as funções malloc, calloc e realloc necessitam da biblioteca "stdlib.h" do C.

  No exemplo a seguir, serão alocadas na memória duas células do tipo int, uma utilizando o malloc e outra o calloc.
#include <stdio.h>
#include <stdlib.h> /* Necessário para o malloc, calloc e realloc */

main()
{
  int *p;

  p = (int *) malloc(1 * sizeof(int));
  printf("Valor do conteúdo de p: %d\n", *p);
  printf("Posição de p: %x\n", *p);

  p = (int *) calloc(1, sizeof(int));
  printf("Valor do conteúdo de p: %d\n", *p);
  printf("Posição de p: %x\n", *p);
}
  Saída:
  Valor do conteúdo de p: 0
  Posição de p: 17CC
  Valor do conteúdo de p: 0
  Posição de p: 17D2

  Cada variável dinâmica do tipo "int" ocupa 2 posições aleatórias de memória, conforme mostra a ilustração abaixo.
  ┌─────────┬──────┐
  │ Memória │ Dado │
  ├─────────┼──────┤
  │  17CCH  │ 00H  │ 
  ├─────────┼──────┤
  │  17CDH  │ 00H  │
  ├─────────┼──────┤
  │  17CEH  │      │
  ├─────────┼──────┤
  │  17CFH  │      │
  ├─────────┼──────┤
  │  17D0H  │      │
  ├─────────┼──────┤
  │  17D1H  │      │
  ├─────────┼──────┤
  │  17D2H  │ 00H  │ ← p
  ├─────────┼──────┤
  │  17D3H  │ 00H  │
  └─────────┴──────┘
  Atenção: observe que "p" passa a apontar para outro endereço de memória e o primeiro dado ficou "perdido", ou seja, sem referência para ele. Se a posição da primeira célula alocada não for armazenada em outro ponteiro, não teremos mais como acessar esse dado.

  Para atribuir valor a um espaço alocado, deve-se utilizar o "*" junto com o ponteiro. Ex:
#include <stdio.h>
#include <stdlib.h>

main()
{
  int *p;

  p = (int *) malloc(1 * sizeof(int));
  *p = 4;
  printf("Valor do conteúdo de p: %d\n", *p);
}
  Saída:
  Valor do conteúdo de p: 4

  Cada vez que um espaço na memória é alocado, este espaço fica reservado e indisponível para novos dados. Nesse caso, quando um certo dado não for mais utilizado, ele deverá ser removido da memória, liberando espaço.
  O comando que libera espaço em memória para um recurso alocado por malloc ou calloc é o free. Ex:
  free(p);


  Alocação de vetores

  Para alocar um vetor, basta multiplicar o tamanho da variável pelo número de elementos do vetor e passar como parâmetro para o malloc.
  O programa a seguir aloca um vetor do tipo "short int" com 3 elementos. O valor retornado ao ponteiro é sempre o da primeira posição do vetor.
#include <stdio.h>
#include <stdlib.h>

main()
{
  short int *p;

  p = (int *) malloc (3 * sizeof(short int));
}
  ┌─────────┬──────┐
  │ Memória │ Dado │
  ├─────────┼──────┤
  │  2000H  │ xxx  │ ← p 
  ├─────────┼──────┤
  │  2001H  │ xxx  │
  ├─────────┼──────┤
  │  2002H  │ xxx  │
  └─────────┴──────┘


  Ponteiros para tipos diferentes

  Podemos referenciar tipos de dados diferentes do declarado no ponteiro. Para isso, deve-se utilizar o recurso de casting. Veja o exemplo a seguir.
#include <stdio.h>
#include <stdlib.h>

main()
{
  int i=4;
  unsigned char *p;

  p = (unsigned char *)&i; /* Casting - conversão entre tipos diferentes */
}

  Graficamente:
   ┌─────────┬──────┐
   │ Memória │ Dado │
───├─────────┼──────┤─────
   │  17CCH  │ 04H  │  ← p
 i ├─────────┼──────┤─────
   │  17CDH  │ 00H  │
───├─────────┼──────┤
   │  17CEH  │      │
   └─────────┴──────┘
  A variável "i' ocupa 2 bytes, enquanto que a variável "p" referencia 1 byte.
  Este recurso é interessante para ler byte a byte o conteúdo de qualquer tipo de variável.


  Deslocando os ponteiros

  É possível deslocar os ponteiros na memória, utilizando-se os operadores soma "+" e subtração "-". Veja o exemplo a seguir:
  short int *p, *q, *r;

  q = p+2;
  r = p-1;

  Graficamente:
   ┌─────────┬──────┐
   │ Memória │ Dado │
   ├─────────┼──────┤     
   │  2345H  │      │  ← r
   ├─────────┼──────┤     
   │  2346H  │      │  ← p
   ├─────────┼──────┤
   │  2347H  │      │
   ├─────────┼──────┤
   │  2348H  │      │  ← q
   └─────────┴──────┘

  O deslocamento do ponteiro em bytes depende do tipo de dados definido para ele. Veja o exemplo a seguir.
#include <stdio.h>

main()
{
  int i;
  float *p;

  for (i=0; i<3; i++)
    printf("Posição atual: %x\n", p+i);
}
  Saída:
  Posição atual: 3340
  Posição atual: 3344
  Posição atual: 3348

  O exemplo a seguir irá navegar pelos caracteres de uma string, utilizando os operadores sobre o ponteiro.
#include <stdio.h>
#include <string.h>

main()
{
  int i;
  char nome[6];
  unsigned char *p;

  p = (unsigned char *) &nome;

  strcpy(nome, "Bianca");

  for (i=0; i<7; i++)
    printf("%04x - %02x - %c\n", p+i, (short int) *(p+i), *(p+i));
}
  Saída:
  D5EF - 42 - B
  D5F0 - 69 - i
  D5F1 - 61 - a
  D5F2 - 6E - n
  D5F3 - 63 - c
  D5F4 - 61 - a
  D5F5 - 00 -

  O ponteiro "p" recebe o endereço do primeiro caractere da string. Em seguida, a operação "p+i" vai deslocando ponteiro pela string. Observe que o valor "00" marca o fim da string.

  Importante:

  A expressão *(p+1) difere da expressão *p+1.
  A primeira obtém o conteúdo de memória apontado por "p+1".
  A segunda obtém o conteúdo de memória apontado por "p" e soma mais 1.
  Veja o exemplo a seguir.
   ┌─────────┬──────┐
   │ Memória │ Dado │
   ├─────────┼──────┤     
   │  2345H  │ 01H  │  ← p
   ├─────────┼──────┤     
   │  2346H  │ 04H  │
   ├─────────┼──────┤
   │  2347H  │ 06H  │
   ├─────────┼──────┤
   │  2348H  │ 08H  │
   └─────────┴──────┘
  Valor de *(p+1): 4
  Valor de *p+1: 2


  Alocando vetores, parte II

  Uma vez aprendido o conceito de deslocamento dos ponteiros, podemos inserir e ler dados dentro de um vetor alocado. O exemplo a seguir ilustra como fazer isso.
#include <stdio.h>
#include <stdlib.h>

main()
{
  int i, *p;

  p = (int *) malloc(4 * sizeof(int));

  /* Insere dados no vetor */
  for (i=0; i<4; i++)
    *(p+i) = i*5;

  /* Percorre o vetor lendo os dados */
  for (i=0; i<4; i++)
    printf("M[%d] = %d\n", i, *(p+i));
}
  Saída:
  M[0] = 0
  M[1] = 5
  M[2] = 10
  M[3] = 15


  Structs como variáveis dinâmicas

  Podemos também criar variáveis dinâmicas para estruturas (structs) do C, do mesmo modo que fizemos com os outros tipos de dados.
  Para isso, precisamos:   Utilize a expressão a seguir para calcular o tamanho de cada "célula" da estrutura na memória.
 sizeof(struct nome_da_estrutura)

  A sintaxe básica é:
struct nome
{
  ...
} *ponteiro;

main()
{
  ponteiro = (struct nome *) malloc(sizeof(struct nome)); 
}

  Foi visto que a referência a uma variável de uma "struct" do tipo estática é feita através de um ponto ".". Por exemplo:
struct aluno
{
  char nome[20];
  int idade;
} alu01;

main()
{
  alu01.nome = "Maria";
  alu01.idade = 02;
}

  No caso de um ponteiro, utiliza-se a expressão "->". Veja o exemplo a seguir.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct ficha
{
  char nome[20];
  int idade;
} *p;

void main(void)
{
  p = (struct ficha *) malloc(sizeof(struct ficha));

  strcpy(p->nome, "Gloria");
  p->idade = 20;

  printf("Nome: %s.\n", p->nome);
  printf("Idade: %d anos.\n", p->idade);
}
  Saída:
  Nome: Gloria.
  Idade: 20 anos.


  Listas encadeadas

  Os vetores sempre alocam uma quantidade fixa de memória. Quando criamos aplicações onde os dados crescem sob demanda, como por exemplo um cadastro de clientes, às vezes o espaço alocado é insuficiente para armazenar os dados ou até mesmo muito grande. Para se adequar ao dinamismo dos dados, é necessário sempre criar um novo vetor com o tamanho necessário e copiar os dados da antiga para lá.
  As listas encadeadas são uma solução para esse problema, pois permite a alocação de células de dados conforme o necessário.
  Entretanto nem tudo são flores. Enquanto que no vetor os dados estão em posições contíguas na memória, nas listas encadeadas precisamos controlar a localização de cada célula criada.
  Foi visto anteriormente que a alocação de memória utilizando o mesmo ponteiro faz com que o dado anterior referenciado seja "perdido". Para resolver esse problema, basta armazenar em cada célula o endereço da célula seguinte (ou até mesmo da seguinte e anterior), além é claro de armazenar o endereço da primeira célula criada.

  Graficamente:
    Célula 1      Célula 2         Célula N
   ┌───────┬─┐   ┌───────┬─┐      ┌───────┬─┐
   │       │─┼──>│       │─┼──>...│       │─┼──┐
   └───────┴─┘   └───────┴─┘      └───────┴─┘  ⏚
    ↑
  inicio

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

struct no
{
  int valor;
  struct no *prox;
} *p, *q, *inicio;

main()
{
  p = (struct no *) malloc(sizeof(struct no));
  p->valor=0x15;
  inicio = p;
  q=p;

  p = (struct no *) malloc(sizeof(struct no));
  q->prox = p;
  p->valor=0x67;
  q=p;

  p = (struct no *) malloc(sizeof(struct no));
  q->prox = p;
  p->valor=0x44;
  p->prox=NULL;
}
  A variável "inicio" é utilizada para armazenar o endereço da primeira célula, enquanto que a variável "q" é utilizada para armazenar o endereço da célula anterior, uma vez que é necessário indicar à esta célula o endereço da célula atual. Para isso, utiliza-se a expressão "q->prox = p".

  Graficamente:
 Após alocar e escrever na primeira célula:

   ┌─────────┬──────┐
   │ Memória │ Dado │
   ├─────────┼──────┤     
   │  1234H  │ 15H  │  ← p = inicio
   ├─────────┼──────┤     
   │  1235H  │ 00H  │
   ├─────────┼──────┤
   │  1236H  │ xx   │
   ├─────────┼──────┤
   │  1237H  │ xx   │
   └─────────┴──────┘
 Após alocar e escrever a segunda célula:

   ┌─────────┬──────┐
   │ Memória │ Dado │
   ├─────────┼──────┤     
   │  1234H  │ 67H  │  ← inicio = q
   ├─────────┼──────┤     
   │  1235H  │ 00H  │
   ├─────────┼──────┤
   │  1236H  │ 10H  │  Passa a ter o endereço &H1810
   ├─────────┼──────┤
   │  1237H  │ 18H  │
   ├─────────┼──────┤
   │  .....  │ ...  │
   ├─────────┼──────┤     
   │  1810H  │ 0FH  │  ← p
   ├─────────┼──────┤     
   │  1811H  │ 00H  │
   ├─────────┼──────┤
   │  1812H  │ xx   │
   ├─────────┼──────┤
   │  1813H  │ xx   │
   └─────────┴──────┘
 Após alocar e escrever a terceira célula:

   ┌─────────┬──────┐
   │ Memória │ Dado │
   ├─────────┼──────┤     
   │  1234H  │ 67H  │  ← inicio
   ├─────────┼──────┤     
   │  1235H  │ 00H  │
   ├─────────┼──────┤
   │  1236H  │ 10H  │
   ├─────────┼──────┤
   │  1237H  │ 18H  │
   ├─────────┼──────┤
   │  .....  │ ...  │
   ├─────────┼──────┤     
   │  1810H  │ 0FH  │  ← q
   ├─────────┼──────┤     
   │  1811H  │ 00H  │
   ├─────────┼──────┤
   │  1812H  │ 00H  │  Passa a ter o endereço &H2000
   ├─────────┼──────┤
   │  1813H  │ 20H  │
   ├─────────┼──────┤
   │  .....  │ ...  │
   ├─────────┼──────┤     
   │  2000H  │ 44H  │  ← p
   ├─────────┼──────┤     
   │  2001H  │ 00H  │
   ├─────────┼──────┤
   │  2002H  │ 00H  │  Passa a ter o endereço 0 (NULL)
   ├─────────┼──────┤
   │  2003H  │ 00H  │
   └─────────┴──────┘

  A última célula é apontada para 0, ou seja, um valor nulo (NULL).

  Para percorrer a lista de 3 células criadas, temos que apontar "p" para "inicio" e utilizar os endereços contidos em cada célula para encontrar a próxima.
  p = inicio;

  while (p!=NULL)
  {
    printf("Valor: %x\n", p->valor);
    p = p->prox;
  }

  A inserção ou remoção de dados é bem simples nas listas encadeadas. Diferente dos vetores, onde às vezes é necessário deslocar os dados, aqui temos somente que criar/destruir o dado e redefinir os endereços.
  Veja graficamente o processo de inserção de um elemento entre a célula 2 e 3:

  Estado inicial:
    Célula 1      Célula 2      Célula 3
   ┌───────┬─┐   ┌───────┬─┐   ┌───────┬─┐
   │       │─┼──>│       │─┼──>│       │─┼──┐
   └───────┴─┘   └───────┴─┘   └───────┴─┘  ⏚

  Criação de um novo dado:

                         Célula X 
                        ┌───────┬─┐
                        │       │ │
                        └───────┴─┘

    Célula 1      Célula 2      Célula 3
   ┌───────┬─┐   ┌───────┬─┐   ┌───────┬─┐
   │       │─┼──>│       │─┼──>│       │─┼──┐
   └───────┴─┘   └───────┴─┘   └───────┴─┘  ⏚

  Redefinição dos apontamentos:
    Célula 1      Célula 2      Célula X      Célula 3
   ┌───────┬─┐   ┌───────┬─┐   ┌───────┬─┐   ┌───────┬─┐
   │       │─┼──>│       │─┼──>│       │─┼──>│       │─┼──┐
   └───────┴─┘   └───────┴─┘   └───────┴─┘   └───────┴─┘  ⏚

  Veja graficamente o processo de remoção da célula X:

  Estado inicial:
    Célula 1      Célula 2      Célula X      Célula 3
   ┌───────┬─┐   ┌───────┬─┐   ┌───────┬─┐   ┌───────┬─┐
   │       │─┼──>│       │─┼──>│       │─┼──>│       │─┼──┐
   └───────┴─┘   └───────┴─┘   └───────┴─┘   └───────┴─┘  ⏚

  Removendo a célula X:
    Célula 1      Célula 2                    Célula 3
   ┌───────┬─┐   ┌───────┬─┐                 ┌───────┬─┐
   │       │─┼──>│       │─┼──>              │       │─┼──┐
   └───────┴─┘   └───────┴─┘                 └───────┴─┘  ⏚

  Religando:
    Célula 1      Célula 2                    Célula 3
   ┌───────┬─┐   ┌───────┬─┐                 ┌───────┬─┐
   │       │─┼──>│       │─┼────────────────>│       │─┼──┐
   └───────┴─┘   └───────┴─┘                 └───────┴─┘  ⏚

  O exemplo a seguir recria a estrutura "ficha" para armazenar o nome de três pessoas.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct ficha
{
  char nome[20];
  int idade;
  struct ficha *prox;
} *p, *q, *inicio;

void main(void)
{
  int i;

  /* Insere dados */
  for (i=0; i<3; i++)
  {
    printf("\nFicha %d:\n",i+1);
 
    p = (struct ficha *) malloc(sizeof(struct ficha));
    printf("Nome: ");
    scanf("%s", p->nome);
    printf("Idade: ");
    scanf("%d", &p->idade);

    if (i==0)
      inicio = p;
    else
      q->prox = p;

    q=p;

    if (i==2)
      p->prox=NULL;
  }

  /* Imprime */
  printf("\n\nLista de pessoas:\n\n");
  p = inicio;

  while (p!=NULL)
  {
    printf("Nome: %s\nIdade: %d\n\n", p->nome, p->idade);
    p = p->prox;
  }
}
  Saída:

  Ficha 1:
  Nome: Gloria
  Idade: 20

  Ficha 2:
  Nome: Tadeu
  Idade: 31

  Ficha 3:
  Nome: Carla
  Idade: 21


  Lista de pessoas:

  Nome: Gloria
  Idade: 20

  Nome: Tadeu
  Idade: 31

  Nome: Carla
  Idade: 21



<< Anterior Linguagem C Próxima >>


/MARMSX/CURSOS/C