Ir para o conteúdo

Referências II

Continuando com o estudo de ponteiros, vamos ver como utilizá-los para referenciar estruturas e arranjos.

Ponteiros para Structs

Se você recordar-se da anologia apresentada na seção, em que a memória é comparada a um grande vetor e cada tipo de dados tem um tamanho pré-definido, é fácil entender como uma estrutura é representada na memória.

Considere a seguinte estrutura e responda: qual o tamanho desta estrutura na memória do computador?

typedef struct
{
  int i;
  char c;
  float j;
} exemplo1;

Para responder a esta pergunta precisamos entender que uma estrutura ocupa espaço equivalente à soma do espaço ocupado por cada um de seus campos. Desta forma, o espaço total ocupado por uma variável do tipo exemplo1 será a soma dos tamanhos de 1 int, 1 char e 1 float, ou seja, \(4 + 1 + 4 = 9\) bytes.

Assim, e você cria uma variável exemplo1 v[10], o compilador deve reservar um espaço de \(10 × 9 = 90\) bytes.

Assim como nos referimos a variáveis dos tipos primitivos (inteiro, real, caractere) utilizando ponteiros, podemos também utilizar ponteiros para acessar uma variável do tipo struct e seus campos. Veja o exemplo para a estrutura declarada anteriormente.

exemplo1 a = {-5, 'z', 0.89}, b;
exemplo1 *p;
p = &a;
cout << (*p).i << endl;
p = &b;
cin >> (*p).c;

Este trecho mostra na linha 1 a criação de duas variáveis do tipo exemplo1 e na linha 2 a criação de uma variável do tipo ponteiro para exemplo1. As linhas 3 e 5 atualizando o ponteiro, fazendo com que referencie as variáveis a e b, respectivamente. Desta maneira, a linha 4 irá imprimir o valor de a.i, enquanto a linha 6 irá ler a partir do teclado um valor para b.c.

Quando usamos ponteiros e estruturas, aparece o incoveniente de termos que digitar toda vez entre parênteses a variável do tipo ponteiro ((*p).c). Para simplificar a utilização de ponteiros no acesso a campos de uma estrutura, a linguagem C permite escrever esta mesma instrução de uma maneira mais fácil de digitar: p->c. O código anterior pode ser reescrito como:

exemplo1 a = {-5, 'z', 0.89}, b;
exemplo1 *p;
p = &a;
cout << p->i << endl;
p = &b;
cin >> p->c;

Por fim, a passagem de estruturas por referência em funções auxiliares é feita da mesma forma que vimos no capítulo anterior:

  void inverte_structs(exemplo1 *s1, exemplo2 *s2) {
    exemplo1 aux
    aux.i = s2->i;
    s2->i = s1->i;
    s1->i = aux.i;
    aux.c = s2->c;
    s2->c = s1->c;
    s1->c = aux.c;
    aux.j = s2->j;
    s2->j = s1->j;
    s1->j = aux.j;
  }

  int main()
  {
    exemplo1 a = {1, 'a', 1.0}, b = {2, 'b', 2.0};
    inverte_structs(&a, &b);
    cout << a.i << ", " << a.c << "," << a.j << endl;
    cout << b.i << ", " << b.c << "," << b.j << endl;
    return 0;
  }

A função auxiliar troca os valores de duas variáveis do tipo exemplo1 passadas por referência. Após a invocação da função auxiliar a partir da main os valores iniciais das variáveis a e b serão trocados.

Arranjos e Ponteiros

Até agora vimos vetores sem nenhuma relação com ponteiros. No fundo, quando criamos um vetor com a instrução int a[10], estamos dizendo ao compilador para reservar na memória espaço para 10 inteiros (40 bytes) e armazenar o endereço em que começa o vetor na variável a. Em outras palavras, a variável a é um ponteiro para o primeiro elemento do vetor.

Por esta razão, a relação entre vetores e ponteiros é mais direta e mais fácil de utilizar. Uma vez que a variável que usamos contém o endereço do começo do vetor, um ponteiro pode receber diretamente seu valor, sem necessidade do operador &. Considere o exemplo a seguir:

float a[10];
float *p;
p = a;
cout << "Digite 10 numeros reais: ";
for(int i = 0; i < 10; i++)
  cin >> a[i];
cout << "Num. digitados: ";
for(int i = 0; i < 10; i++)
  cout << p[i];

Observe a linha 3 e perceba como a atualização da referência do ponteiro p para o vetor a não utilizou o &. Isto porque a também é um ponteiro. A diferença entre p e a é que a primeira pode ter sua referência atualizada, enquanto a segunda não. A partir da linha 3, o vetor pode ser acessado tanto pela variável a (linha 6) quanto pela variável p (linha 9).

É por este motivo que vetores sempre são passados por referência em funções auxiliares, sem a necessidade do & e do *.

Percorrendo vetores com ponteiros

Já sabemos a relação entre ponteiros e vetores. Veremos a seguir algumas maneiras diferentes de acessar os elementos de um vetor utilizando ponteiros. Considere o trecho de um programa a seguir:

char str[100];
char *s;
int totalletras = 0;
s = str;
cout << "entre uma frase com letras minusculas" << endl;
cin.getline(s,100);
for(int i = 0; i < strlen(s); i++)
{
  if(s[i] >= 'a' && s[i] <= 'z')
  {
    totalletras++;
  }
}
cout << "O total de letras da frase eh " << totalletras;

Neste trecho utilizamos o ponteiro s como o vetor str, pois a partir da linha 3 ele "aponta" para o vetor. Este mesmo trecho pode ser reescrito:

char str[100];
char *s;
int totalletras = 0;
s = str;
cout << "entre uma frase com letras minusculas" << endl;
cin.getline(s,100);
for(int i = 0; i < strlen(s); i++)
{
  if( *(s+i) >= 'a' && *(s+i) <= 'z' )
  {
    totalletras++;
  }
}
cout << "O total de letras da frase eh " << totalletras;

Observe a linha 9 de cada um dos dois trechos anteriores e veja a diferença na notação. Quando utilizamos ponteiro, podemos acessar um vetor pelo índice (colchetes) ou por um deslocamento sobre o valor do endereço inicial do vetor armazenado em s, ou seja, a notação *(s+i), significa o caractere localizado i caracteres a partir do caractere na primeira posição do vetor.

O mesmo trecho pode ser reescrito ainda de uma terceira forma:

char str[100];
char *s;
int totalletras = 0;
s = str;
cout << "entre uma frase com letras minusculas" << endl;
cin.getline(s,100);
for(int i = 0; i < strlen(str); i++) //Observe que aqui usamos str e nao s. Por que?
{
  if( *s >= 'a' && *s <= 'z' )
  {
    totalletras++;
  }
  s++;
}
cout << "O total de letras da frase eh " << totalletras;

Qual a diferença entre esta versão e as versões anteriores? Qual a diferença entre s++ e (*s)++? Qual o valor de s[0] em cada versão? E de *s? E de str[0]?

Laboratório

Laboratório

Crie uma estrutura com os campos nome, idade e salario dos tipos string, inteiro e real, respectivamente.

Em seguida, crie uma função que receba referências para duas variáveis de tal tipo e uma variável correspondente a uma porcentagem de aumento que deve ser aplicada sobre o campo salário de cada estrutura. A função principal deve ler cada campo das duas variáveis e imprimir o novo salário.

Finalmente, crie uma função que recebe duas variáveis de tal tipo e que troque os valores de todos os campos das duas estruturas. A função principal deve agora imprimir as estruturas antes e depois da troca.

Laboratório

Execute as três versões do programa da seção e veja se há diferença no resultado. Responda às perguntas no final da seção.