Aula 15: sobre variáveis e funções

Traduzido por Ana Paula Costa

Nesta aula:

Alcance ("scope") das variáveis: Globais ou locais


Variáveis globais são variáveis que podem ser utilizadas em qualquer parte de um programa. 

Variáveis locais estão apenas definidas dentro da função onde são declaradas. 
Com esta nova informação, torna-se muito mais claro saber quando se pode utilizar cada variável. Dentro das funções podemos usar as variáveis globais e as locais. A função main não pode usar a variável da função modulo1, int y. Modulo1 não pode usar a variável da função main, int z. Contudo, as duas funções podem usar a variável global x.
Aviso: Deve-se evitar a utilização de variáveis globais em funções tanto quando possível. O motivo é simples. Se quisermos copiar a função para outro programa será mais difícil, porque o novo programa provavelmente não terá as mesmas variáveis globais. Utilizando apenas variáveis locais nas funções é muito melhor. Se queremos utilizar as variáveis globais, elas devem ser passadas como parâmetros para a função. Idealmente, uma função é uma unidade independente.

 
Mais uma regra, tipicamente para linguagens como o C (compiladores "single-pass"): As variáveis só podem ser utilizadas DEPOIS de serem declaradas no programa, assim, se colocamos a declaração de uma variável depois de uma função, essa função não poderá usar a variável, mesmo se se tratar de uma variável global.
Vamos ver alguns exemplos. Comecemos pelo programa da aula 13:

#include <stdio.h>

 /* declare global variables x and y */
double x, y;

double square(double r)
{
 /* declare local variable localr */
  double localr;

 /* using the local variable localr */
  localr = r*r;
 /* using the global variable x */
  x = localr;
  return(x);
}

void main()
{
  x = 4.0;
  y = square(x);
}


Prioridade


A variável x é uma variável local e global.
Dentro da função, será usada
a variável local.
Quando existem variáveis globais e variáveis locais com o mesmo nome, a variável local tem prioridade mais elevada e será portanto utilizada dentro da função. De qualquer forma isto é confuso, e então devemos sempre evitar utilizar o mesmo identificador novamente

Algumas linguagens não fazem distinção entre variáveis locais e globais (por exemplo BASIC). Isto significa que não podemos usar o mesmo nome para variáveis duas vezes.


Passagem por valor ou passagem por referência

Quando passamos parâmetros para as funções, podemos fazê-lo de duas formas diferentes: passagem por valor ou passagem por referência.
Passagem por valor:
Até agora vimos apenas este tipo de passagem. Desta forma, apenas um valor é passado para a função. Qualquer coisa que se faça com esse valor na função não terá nenhum efeito no valor original da variável que foi usada na chamada da função. Para ficar bem claro, eis um exemplo. Vamos assumir que temos uma função que escreve o quadrado de um parâmetro p. Para calcular o quadrado atribuímos um novo valor a p, (p*p). O valor de p irá ser alterado dentro da função
  void write_square(double p)
  {
    p = p*p;
    printf("%f", p);
  }
Quando chamamos a função com a variável x, o valor desta variável não irá ser alterado com a chamada da função. No programa principal:
  void main()
  {
    double x;

    x = 2.0;
    write_square(x);
    printf("%f", x);
  }
Após correr a função, o valor de x não foi alterado. O resultado completo do programa de cima será
  4.0
  2.0

Passagem por referência:
Por outro lado, se queremos alterar o valor da variável utilizada para chamar a função, podemos fazê-lo passando o endereço da variável para a função. Todas as alterações ao conteúdo deste endereço são permanentes.
  void write_square(double *p)
   /* p is a pointer-to-double */
  {
      /* change the contents of address p: */
    *p = *p * *p;
      /* show the contents of address p: */
    printf("%f", *p);
  }

  void main()
  {
    double x;

    x = 2.0;
    /* now we have to pass the address (&) of the variable x: */
    write_square(&x);
    printf("%f", x);
  }
Se se correr o programa, o resultado será
  4.0
  4.0
porque o valor de x foi alterado simultaneamente com o conteúdo do endereço p.
 

 
Passagem por valor
Passagem por referência
Na alanogia das caixas a representar variáveis: passagem por referência é entregar a caixa (variável) para a funçao que poderá então utilizar e alterar o valor dentro da caixa, enquanto passagem por valor é equivalente a abrir a caixa, copiar o valor lá contido e entregar apenas esse valor à função. Obviamente, que assim o valor original fica na caixa.
Um outro exemplo: Se eu disser quanto tenho na minha conta bancária, podem utilizar esse valor para calcular quanto representa em dólares, ou posso dar-lhes o direito de alterarem a quantia da minha conta bancária, e neste caso, a quantia irá provavelmente ser alterada.

scanf() revisitado

Com estes conceitos bem presentes, vamos ver de novo a função scanf() que recebe input do utilizador. De recordar que temos sempre que usar a forma
  scanf("%d", &i);
Ou, por outras palavras, temos sempre que fornecer à função scanf o endereço (&) da variável (i). Agora já faz sentido. Nós queremos alterar o valor de i com a função scanf. Portanto, temos que utilizar a técnica de passagem por referência. Assim sendo, temos que fornecer a scanf o endereço de i, em vez do valor de i.

Example: Carthesian to polar coordinates

As we know, functions can only return a single value. What if we want to return more than one value to the calling part of the program? Take for example the conversion from Carthesian to polar coordinates. As input we have a coordinate (x, y) and as output we have a Carthesian coordinate (r, q). These are two values, one value for the radius (r) and one for the angle (q). The following solution shows how we can pass this information without using global variables:
 
#include <stdio.h>
#include <math.h>

void convert_to_polar(float x, float y, float *r, float *theta)
/**********************************************************
 * function to convert carthesian coordinate (x,y) to     *
 * polar coordinate (r,theta)                             *
 * parameters:                                            *
 *    x,y: floats, passed by value                        *
 *    r, theta: (pointers to) floats, passed by reference *
 *                  (changes are permanent)               *
 **********************************************************/
{
  *r = sqrt(x*x + y*y);
  *theta = atan(y/x);
}

void main()
{
  float rl, thetal;

  convert_to_polar(1.0, 1.0, &r1, &theta1);
  printf("polar coordinate is (%f, %f)\n", rl, thetal)
}
output:
  polar coordinate is (1.4142, 0.78539)


Type mixing

When passing information to functions we have to take even more care that we don't do any mixing of types, especially when we use pointers. As a (bad) example:
  void double10(double *dp)
  {
    *dp = 10.0;
  }

  void main()
  {
    float y=10.0;
    float x=10.0;

    double10(&x);
    printf("%f %f\n", x, y);
  }
A double occupies 8 bytes. Assigning a value to the contents of a pointer-to-double will therefore write in 8 consecutive bytes of memory. The pointer passed to the function is of type pointer-to-float, however. We have (by declaring the variable x of type float) only reserved 4 bytes of space in memory. The instruction *dp = 10; will now write in these 4 bytes and the 4 bytes next to it (that don't belong to x but to y).
The output of the program above (Borland C++ 3.01 for MS-DOS):
  0.000000 2.562500
Always avoid mixing of type:

Give to the function what the function wants

Teste Rápido:

Para testar os conhecimentos sobre o que aprendeu nesta aula, prima aqui para fazer um teste on-line. De notar que este Não é o formato que será utilizado no teste final!

Peter Stallinga. Universidade do Algarve, 19 November 2002