1 Programação Básica em C++ 

Estas notas de aula apresentam os conceitos básicos da Linguagem C++  e se propõe a abordar apenas o que é importante para a compreensão básica de programas de computadores. Assim, conceitos de C++ como objetos, classes, templates e outros conceitos relacionados à programação orientada a objetos não são abordados aqui.

1 Programas C++ 

Essencialmente, um programa C++ consiste de uma ou mais partes chamadas funções1. Além disso, um programa em C++  deve definir pelo menos uma função chamada main. Esta função marca o ponto de início de execução do programa.

Programas C++ tem a seguinte estrutura geral:


#include $<$iostream$>$ 

using namespace std;

definição de constantes

funções

int main()
{
declaração de variáveis
....
sentenças
....
}


1.1 Sentenças: simples e compostas

Cada instrução em C++ é chamada de sentença. Sentenças simples são terminadas com um ponto e vírgula. Usando chaves, podemos agrupar sentenças em blocos, chamados de sentenças compostas.

Exemplos de sentenças incluem:

O corpo da função main() é um exemplo de sentença composta.

1.2 Variáveis em C++ 

Uma variável é uma informação que você pode usar dentro de um programa C++ . Esta informação está associada com um lugar específico da memória (isso é feito pelo compilador). O nome da variável e o endereço da memória onde a informação está armazenada estão associados. O nome e o endereço não mudam. Mas, o valor da informação pode mudar (o valor do que está dentro da caixa pode mudar, embora o tipo seja sempre o mesmo). Cada variável tem um tipo associado. Alguns tipos de variáveis que discutiremos incluem int, char e float.

Cada variável usa uma determinada quantidade de armazenamento em memória. A maneira como sabemos quantos bytes são utilizados é pelo tipo da variável. Variáveis do mesmo tipo utilizam o mesmo número de bytes, não interessando qual o valor que a variável armazena.

Um dos tipos utilizados para armazanar números é o int. Ele é usado para armazenar números inteiros.

Outro tipo é o char, usado para armazenar caracteres. Um caracter é um símbolo (uma letra do alfabeto, um dígito, um símbolo de pontuação, etc). Um char é armazenado em 1 byte de memória. Cada caracter é associado com um valor entre 0 e 255. O compilador C++ faz a tradução para você, portanto você não precisa saber estes números. Em C++ , um caracter é representado entre apóstrofes ('). Por exemplo, 'C', 'a', '5', '$'. Note que '5' é um caracter, e não o inteiro 5.

\includegraphics[scale=0.5]{variable}

A figura acima mostra como um int e um char são armazenados na memória.

Outro tipo existente é o float, usado para armazenar números reais (números com o ponto decimal). Este números são armazenados em duas partes: a mantissa e o expoente. Eles são armazenados de uma maneira que se assemelha a notação exponencial. Por exemplo, o número $6.023 \times 10^{23}$ é escrito como $6.023e23$. Neste caso, a mantissa é 6.023 e o expoente 23.

Estes números são armazenados de uma forma padrão, tal que a mantissa tem apenas um dígito para a esquerda do ponto decimal. Desta forma, 3634.1 é escrito como 3.6341e3, e 0.0000341 é escrito 3.41e-5. Note também que a precisão é limitada pela mantissa. Somente os 6 dígitos mais significativos são armazenados. Em Code::Blocks um float ocupa 4 bytes de memória. Há muitos outros tipos ( short, long, double), que serão descritos no futuro.

1.3 Definição de Variável em C++ 

Se você usa variáveis no programa, você deve defini-las. Isto envolve especificar o tipo da variável e o seu nome. As regras para formar nomes de variáveis em C++ são:


Tabela 1: Palavras Reservadas da Linguagem C++ 
           
auto break case char const continue
default do double else enum extern
float for goto if int long
main register return short signed sizeof
static struct switch typedef union unsigned
void volatile while      


É sempre uma boa idéia ter certas regras (para você mesmo) para nomear variáveis para tornar o programa mais legível:

Os tipos básicos de dados existentes em C++ são:

Tipo de Dado Bits Faixa de Valores
     
char 8 -128 a 127
bool 8 true ou false
int 32 -2.147.483.647 a 2.147.483.647
float 32 7 dígitos significativos
double 64 15 dígitos significativos

Abaixo está um exemplo de um programa com diversas definições de variáveis:


int main() 

{
int pera;
char qualidade;
float peso;

pera = 3;
qualidade = 'A';
peso = 0.653;
...
}

Quando variáveis são definidas, elas não possuem valores ainda. Nós damos valores às variáveis usando o operador de atribuição (=). Variáveis também podem ser inicializadas para conter valores quando são definidas. Usando esta forma, o program acima ficaria:


int main() 

{
int pera = 3;
char qualidade = 'A';
float peso = 0.653;

...
}

Para resumir: quando um programa é executado, uma variável é associada com:

1.4 Constantes

Em C++ , além de variáveis, nós podemos usar também números ou caracteres cujos valores não mudam. Eles são chamados de constantes. Constantes não são associados a lugares na memória.

Assim como variáveis, constantes também têm tipos. Uma constante pode ser do tipo int, char, etc. Você nao tem que declarar constantes, e pode utilizá-las diretamente (o compilador reconhece o tipo pela maneira que são escritos). Por exemplo, 2 é do tipo int, e 2.0 é do tipo double. Por convenção, todas as constantes reais são do tipo double.

1.5 Caracteres Constantes

Um constante caracter é escrita entre apóstrofes, como em 'A'. Todas as letras, números e símbolos que podem ser impressos são escritos desta forma em C++ . Às vezes precisamos de caracteres que não podem ser impressos, por exemplo, o caracter de ``nova linha'', que não tem uma tecla específica no teclado. Neste caso, usa-se caracteres de escape. Tais caracteres são escritos não somente como um símbolo entre apóstrofes, mas como um sequência de caracteres entre apóstrofes. Por exemplo, '\n' é o caracter para nova linha (uma sequência que inicia com a barra invertida é chamada de sequência de escape). Se quisermos representar o caracter de barra invertida, temos que escrever '\\'. Note que \n é o caracter de nova linha - embora use-se dois símbolos para representá-lo. A barra invertida é chamada de escape. Ele diz ao compilador que o n que segue não é a letra n, mas que a sequência completa de caracteres deve ser interpretada como o caracter de ``nova linha''.

Cada caracter constante tem um valor inteiro igual ao seu valor numérico do seu código ASCII. Por exemplo, considere a constante 'A', que tem código ASCII 65, e 'B' que tem código 66. Nós podemos usar a expressão 'A' + 1. O resultado é o valor 66. E se o tipo da expressão resultante for char, então o resultado da expressão é 'B'.

1.6 Entrada e Saída

Se quisermos que um programa C++ mostre alguns resultados, ou se quisermos que o programa peça ao usuário que entre com alguma informação, nós podemos usar os elementos cout e cin2. Se você quiser usar estes elementos em seu programa, voce deve incluir as seguintes linhas no início do seu código fonte:

#include $<$iostream$>$

using namespace std;

Isto faz com que o arquivo header chamado iostream seja incluído no seu arquivo fonte durante a compilação. Este arquivo contém definições de diversas funções e classes (por exemplo, cout e cin). Ele declara ao compilador o nome das funções e algumas informações adicionais necessárias para que as instruções sejam executadas corretamente.

1.6.1 Exibindo informações na tela: cout

cout pode ser utilizado para imprimir mensagens e valores em uma variedade de formatos. Por enquanto, cout é melhor descrito através de exemplos.



cout $<$$<$  "Alô todo mundo"  $<$$<$  endl;

Imprimirá Alô todo mundo em uma linha na tela do computador. O valor endl representa a mudança de linha.

Para o comando cout fazer o que deve, nós devemos especificar o que será impresso. Nós devemos dar ao comando o que chamamos de argumentos. No exemplo acima, Alô todo mundo e endl são argumentos para cout.

Os argumentos de cout podem ser uma variável, uma expressão ou um string (uma série de caracteres entre aspas (")).

Nós também podemos colocar caracteres de escape no string para imprimir caracteres especiais. Por exemplo, colocando \n no string causa que o restante do string seja impresso na linha seguinte. Outros caracteres de escape serão apresentados no futuro.

Considere o seguinte programa:



#include $<$iostream$>$
using namespace std;
#define PRECO 1.99

int main()
{
int pera = 3;
char qualidade = 'A';
float peso = 2.5;

cout $<<$  "Existem "  $<<$  pera  $<<$  " peras de qualidade "  $<<$  qualidade
$<<$  " pesando "  $<<$ peso  $<<$  " quilos."  $<<$  endl;
cout $<<$  " O preco por quilo eh R$"  $<<$ PRECO 
$<<$  ", o total eh R$"  $<<$ peso * PRECO $<<$  endl;
}

A saída do programa será:

Existem 3 peras de qualidade A pesando 2.5 quilos.
O preco por quilo eh 1.99, o total eh 4.975


A linha #define PRECO 1.99 no início do programa define uma macro. Ou seja, definimos que PRECO é um sinônimo para 1.99 e, portanto, toda ocorrência de PRECO no programa é substituído por 1.99 antes que ele seja compilado.

1.6.2 Lendo informação: cin

cin pode ser usado para ler valores digitados no teclado.

Considere o seguinte programa:


#include $<$iostream$>$ 

using namespace std;

int main()
{
int idade;

cout $<<$  " Entre sua idade: ";
cin $>>$  idade

cout $<<$  "Voce tem " $<<$  idade $<<$  "anos." $<<$  endl;
}

Este programa mostrará no monitor: Entre sua idade: e aguardará que um número seja digitado e a tecla ENTER. Depois disso, a variável idade conterá o valor digitado pelo usuário.

Mais de um valor pode ser lido por um mesmo cin. Considere o seguinte exemplo:


#include $<$iostream$>$ 

using namespace std;

int main()
{
int dia, mes, ano;

cout $<<$  "Entre com a data do seu aniversario (dd mm aa): ";
cin $>>$  dia $>>$  mes $>>$  ano;

cout $<<$  "Voce nasceu em " $<<$  dia $<<$  "/" $<<$  mes $<<$  "/" $<<$  ano $<<$ endl;
}

Este exemplo funciona exatamente como o exemplo anterior. Um único cin lê os 3 números quando estes números são separados por espaços (espaços em branco, tabulação, novas linhas). Então você pode teclar ENTER depois de cada número, ou colocar espaços ou tabulações entre os números. Os espaços são ignorados pelo cin.

1.7 Algoritmo X Programa

   ALGORITMO PERIMETRO_AREA

     /* Calcula o perímetro e a area de uma circunferencia 
        de raio R (fornecido pelo usuario) */

     /* Definir variaveis */
           int Raio;
           float Perim, Area, PI;   
           PI = 3.14159;

     /* Obter Raio da circunferencia */
           Escreva("Entre com o valor do raio:");
           Leia(Raio);

     /* Calcular Perimetro do Circulo */
           Perim = 2 * PI * Raio;

     /* Calcular Area da Circunferencia */
           Area = PI * Raio ** 2;

     /* Exibir Resultados */ 
           Escreva("O perimetro da circunferencia de raio", Raio, "eh", Perim);
           Escreva("e a area eh ",Area);

     /* Terminar Programa */

   FIM_ALGORITMO PERIMETRO_AREA

Programa em C++ 

/* programa que calcula o perímetro e a área de uma
   circunferência de raio R (fornecido pelo usuário) */

#include <iostream>  /* inclui diretivas  de     entrada-saída */
#include <cmath>  /*  inclui diretivas das funções matemáticas */

using namespace std;

#define  PI  3.14159

int main( )
{
     /* Definir variaveis */
        int Raio;
        float  Perim, Area;

     /* Obter Raio da circunferencia */
        cout << "Entre com o valor do raio: ";
        cin >> Raio;

     /* Calcular Perimetro do Circulo */
      Perim = 2 * PI * Raio;

     /* Calcular Area da Circunferencia */
        Area  = PI * pow(Raio, 2);

     /* Exibir Resultados */ 
        cout << "O perimetro da circunferencia de raio " << Raio 
             << " eh  " << Perim << endl;
        cout << "e a area eh " << Area << endl;
}

2 Operações Aritméticas e Expressões.
Operações Relacionais.

2.1 Operações Aritméticas

Em C++ , nós podemos executar operações aritméticas usando variáveis e constantes. Algumas operações mais comuns são:

+
adição
-
subtração
*
multiplicação
/
divisão
%
resto (módulo)

Estas operações podem ser usadas como mostram os exemplos abaixo, assumindo que as variáveis necessárias já estão declaradas:

     celsius = (fahrenheit - 32) * 5.0 / 9.0;

     forca =  massa * aceleracao;

     i = i + 1;

2.1.1 Precedência de Operadores

Em C++ , assim como em álgebra, há uma ordem de precedência de operadores.

Assim, em $(2 + x)(3x^{2} + 1)$, expressões em parêntesis são avaliadas primeiro, seguidos por exponenciação, multiplicação, divisão, adição e subtração.

Da mesma forma, em C++ , expressões entre parêntesis são executadas primeiro, seguidas de *, / e % (que tem todos a mesma precedência), seguido de + e - (ambos com a mesma precedência).

Quando operações adjacentes têm a mesma precedência, elas são associadas da esquerda para a direita. Assim, a * b / c * d % e é o mesmo que ((((a * b) / c) * d) % e).

2.1.2 A Operação de Resto (%)

Esta operação é usada quando queremos encontrar o resto da divisão de dois inteiros. Por exemplo, 22 dividido por 5 é 4, com resto 2 ( $4 \times 5 + 2 = 22$).

Em C++ , a expressão 22 % 5 terá valor 2.

Note que % só pode ser utilizados entre dois inteiros. Usando ele com um operando do tipo float causa um erro de compilação (como em 22.3 % 5).

2.1.3 Expressões e Variáveis

Expressões aritméticas podem ser usadas na maior parte dos lugares em que uma variável pode ser usada.

O exemplo seguinte é válido:

     int raio = 3 * 5 + 1;

     cout << "circunferencia = " << 2 * 3.14 * raio << endl;

Exemplos de lugares onde uma expressão aritmética NÃO pode ser usada incluem:

     int yucky + 2 = 5;

     cin >> oops * 5;

Este exemplo é ilegal e causará erro de compilação.

2.2 Operadores Relacionais

Em C++ , há operadores que podem ser usados para comparar expressões: os operadores relacionais.

Há seis operadores relacionais em C++ :

$<$
menor que
$>$
maior que
$<=$
menor ou igual que ($\leq$)
$>=$
maior ou igual que ($\geq$)
==
igual a
!=
não igual a ($\not=$)

Os resultados deste operadores é 0 (correspondendo a falso), ou 1 (correspondendo a verdadeiro). Valores como esses são chamados valores booleanos. Algumas linguagens de programação como Pascal tem um tipo de variável distinto para valores booleanos. Este não é o caso do C++ , onde valores booleanos são armazenados como variáveis numéricas tais como o int.

Considere o seguinte programa:

   int main()
   {
       int idade;

       idade = 17;
       cout << "Pode tirar carteira de motorista? " << (idade >= 18) << endl;
       idade = 35;
       cout << "Pode tirar carteira de motorista? " << (idade >= 18) << endl;
   }

A saída deste programa será:

     Pode tirar carteira de motorista? 0
     Pode tirar carteira de motorista? 1

Na primeira linha, idade é 17. Logo, 17 >= 18 é falso, que é 0.

Depois disso, idade é 35. Logo, 35 >= 18 é verdadeiro, que é 1.

Note também que o operador de igualdade é escrito com ``sinais de igual duplo'', ==, não =. Tenha cuidado com esta diferença, já que colocar = no lugar de == não é um erro sintático (não gera erro de compilação), e não significa o que você espera.

2.2.1 Precedência dos operadores relacionais

Operadores aritméticos tem precedência maior que os operadores relacionais. Por exemplo, a expressão 3 + 5 < 6 * 2 é o mesmo que (3 + 5) < (6 * 2).

Se por alguma razão você quer que o resultado do uma operação relacional em uma expressão aritmética, é necessário usar parêntesis. Por exemplo, a expressão score + (score == 0) será sempre igual ao valor de score, exceto quando o valor de score seja 0. Neste caso, o valor da expressão é 1 (porque (score == 0) é igual a 1).

Uma observação sobre valores booleanos - embora você possa assumir que o valor de uma operação relacional é 0 ou 1 em C++ , qualquer valor diferente de zero é considerado verdadeiro. Falaremos sobre isso mais tarde durante o curso.

2.3 Revisão de Expressões:

O que é impresso pelos dois programas abaixo?

  #include <iostream>
  using namespace std;

  int main() {
    int score = 5;

    cout << 5 + 10 * 5 % 6;         // 7
    cout << 10 / 4;                 // 2
    cout << 10.0 / 4.0;             // 2.5
    cout << 'A' + 1                 // B
    cout << score + (score == 0);   // 5
  }

  #include <iostream>
  using namespace std;

  int main() {
    int n1, n2, n3;

    cout << "Entre com um numero inteiro: ";
    cin >> n1;
    n2 = n1 / 5;
    n3 = n2 % 5 * 7;
    cout << n2 << " " << n3 << " " << (n2 != n3 + 21) << endl;
  }

Como é a seguinte expressão completamente parentizada ?

  a * b / c + 30 >= 45 + d * 3 ++e == 10

2.4 Exemplo de programas

Exemplo 1: escreva um programa que leia um número inteiro e imprima 0 se o número for par e 1 se o número for ímpar.

  #include <iostream>
  using namespace std;

  int main() {
    int numero;

    cout << "Entre com um numero inteiro: ";
    cin >> numero;
    cout << "\nPar? " << numero % 2 << endl;
  }

Exemplo 2: escreva um programa que leia 3 números inteiros e calcule a soma, média, e produto.

  #include <iostream>
  #include <iomanip>     // necessario para usar setw() e setf() em cout
  using namespace std;

  int main() {
    int n1, n2, n3;
    int soma;

    cout <<  "Entre com 3 numeros inteiros: ";
    cin >> n1 >> n2 >> n3;
    soma = n1 + n2 + n3;
    cout <<  "Soma = " << soma << endl;
    cout.setf (ios::fixed | ios::showpoint); // reais em ponto fixo
    cout.precision(2);                       // 2 casa decimais

    // setw(8) fixa tamanho da representação em 8 digitos 
    cout << "Media = " << setw(8) << soma / 3.0 << endl;
    cout <<  "Produto = " << (unsigned) n1 * n2 * n3 << endl;
  }

2.5 Precedência e associatividade de operadores

 
Operador Associatividade
   
() esquerda para direita
- (unários) direita para esquerda
* / % esquerda para direita
+ - esquerda para direita
< <= > >= esquerda para direita
== != esquerda para direita

3 Expressões como valores

Em C++ , todas as expressões são avaliadas. O resultado da avaliação é um valor e pode ser usado em quaisquer lugares.

3.1 Expressões aritméticas, relacionais e lógicas

Como você já sabe, expressões usando operadores aritméticos, relacionais e lógicos3 são avaliados. O valor resultante é um número. Para os operadores relacionais e lógicos, este número pode ser 0 (que significa falso) ou 1 (que significa verdadeiro). Por exemplo:

3 + 5 * 4 % (2 + 8)   tem valor 3;
3 < 5   tem valor 1;
x + 1   tem valor igual ao valor da variável x mais um;
(x < 1) || (x > 4)   tem valor 1 quando o valor da variável x é fora do intervalo [1,4], e 0 quando x está dentro do intervalo.

3.2 Expressões envolvendo o operador de atribuição (=)

O formato do operador de atribuição é:


\begin{displaymath}
lvalue = expressao
\end{displaymath} (1)

Um $lvalue$ (do inglês ``left-hand-side value'' - valor a esquerda) é um valor que se refere a um endereço na memória do computador. Até agora, o único ``lvalue'' válido visto no curso é o nome de uma variável. A maneira que a atribuição funciona é a seguinte: a expressão do lado direito é avaliada, e o valor é copiado para o endereço da memória associada ao ``lvalue''. O tipo do objeto do ``lvalue'' determina como o valor da $expressao$ é armazenada na memória.

Expressões de atribuição, assim como expressões, têm valor. O valor de uma expressão de atribuição é dado pelo valor da expressão do lado direito do =. Por exemplo:

x = 3   tem valor 3;
x = y+1   tem o valor da expressão y+1.

Como consequência do fato que atribuições serem expressões que são associadas da direita para esquerda, podemos escrever sentenças como:

i = j = k = 0;

Que, usando parênteses, é equivalente a i = (j = (k = 0)). Ou seja, primeiro o valor 0 é atribuído a k, o valor de k = 0 (que é zero) é atribuído a j e o valor de j = (k = 0) (que também é zero) é atribuído a i.

Uma característica muito peculiar de C++ é que expressões de atribuição podem ser usados em qualquer lugar que um valor pode ser usado. Porém você deve saber que usá-lo dentro de outros comandos produz um efeito colateral que é alterar o valor da variável na memória. Portanto, a execução de:

    int quadrado, n = 2;

    cout << "Quadrado de " << n << " eh menor que 50? " << ((quadrado = n * n) < 50) << endl;

causa não apenas que o valor 4 seja impresso, como a avaliação da expressão relacional dentro do cout faz com que o número 4 seja copiado para o endereço de memória associado com a variável quadrado. Note que é necessário usar parênteses em quadrado = n * n já que = tem menor precedência que o operador relacional <.

Agora compare o exemplo anterior com o próximo, no qual o valor 4 é impresso, mas sem nenhum efeito colateral:

    int quadrado, n = 2;

    cout << "Quadrado de " << n << " eh menor que 50? " << (n * n < 50) << endl;

Note que agora não há necessidade de parênteses para a expressão n * n porque * tem maior precedência que o operador relacional <.

4 Ordem sequencial de execução de sentenças
o comando condicional: if e if - else

A execução de um programa C++ começa com a função main(). Em todos os exemplos que vimos até este momento, sentenças são executadas sequencialmente. A ordem sequencial de execução de senteças pode ser alterada se certas condições forem satisfeitas durante a execução do programa. Isto é chamado desvio condicional.

Todas as linguagens de programação oferecem comandos para o desvio condicional. O mais simples é a senteça if. Em C++ , ele tem o formato:

if ($expressao$)

$corpo do desvio$

O corpo do desvio, por sua vez, pode ser uma sentença simples ou composta (veja Seção 1.1).

Quando uma sentença if é encontrada em um programa,

  1. O teste na $expressao$ em parênteses é avaliada.
  2. Se o valor da expressão de teste for DIFERENTE de zero, as sentenças que compõem o corpo do desvio que segue a expressão de teste são executadas.

Figura 1: O comando if
\includegraphics[scale=1.0]{if}

Considere o seguinte exemplo que converte uma fração digitada pelo usuário (numerador e denominador) em decimal e imprime o resultado:

#include <iostream>
using namespace std;

int main( ){

  int a, b;
  
  cout << "Entre com uma  fracao (numerador and denominador): ";
  cin >> a >> b;
  
  cout << "A fracao em decimal eh " << 1.0 * a / b << endl;
}

No exemplo acima, escrevemos 1.0 * a / b, já que a e b são do tipo int, e portanto a / b é uma divisão de inteiros e a parte fracional do resultado seria truncado, o que certamente não é o que desejamos.

Voce vê algo errado neste programa ? Uma coisa a ser notada é que se o usuário digitar um denominador igual a 0, nós teremos um erro de execução, já que o programa tentaria executar uma divisão por zero. O que é necessário fazer é testar se o denominador é igual a zero e dividir só no caso dele for diferente de zero. Poderíamos reescrever o programa acima da seguinte forma:

Exemplo 1:

#include <iostream>
using namespace std;

int main( ){

  int a, b;
  
  cout << "Entre com uma  fracao (numerador e denominador): ";
  cin >> a >> b;
  
  if (b != 0)
    cout << "A fracao em decimal eh " << 1.0 * a / b << endl;
}

Exemplo 2:

Programa que lê dois números e ordena o par caso o primeiro número digitado for maior que o segundo.

#include <iostream>
using namespace std;

int main( ){

  int num1, num2, aux;
  
  cout << "Entre com dois numeros inteiros: ";
  cin >> num1 >> num2;

  if (num1 > num2) {
    aux = num1;
    num1 = num2;
    num2 = aux;
    cout << "Trocou \n";
  }

  cout << "Os numeros ordenados: " << num1 << " " << num2 << endl;

}

O programa do Exemplo 1 acima ficaria ainda melhor se ao invés de não fazer nada no caso do denominador ser zero, imprimirmos uma mensagem de erro ao usuário, explicando o que há de errado.

A sentença em C++ que permite fazermos isso é o if - else. O formato do if-else é:

if ($expressao$)

$sentenca_{1}$

else

$sentenca_{2}$

Figura 2: O comando if-else
\includegraphics[scale=1.0]{if-else}

Primeiro, a $expressao$ (que usualmente chamamos de condição) é avaliada. Caso a condição seja verdadeira (o que é equivalente a dizer que o valor é diferente de zero), entao a $sentenca_{1}$ é executada. Caso contrário, a $sentenca_{2}$ é executada.

Note que uma sentença pode ser simples ou composta. Se você quiser agrupar diversas sentenças para serem executadas, você pode colocá-las entre chaves ({ e }).

Por hora, vamos continuar com nosso exemplo simples e torná-lo mais explicativo:

Exemplo 3:

#include <iostream>
using namespace std;

int main( ){

  int a, b;
  
  cout << "Entre com uma fracao (numerador and denominador): ";
  cin >> a >> b;
  
  if (b != 0) 
    cout << "A fracao decimal eh " << 1.0 * a / b << endl;
  else 
    cout << "Erro: denominador zero!\n";
}

Exemplo 4:

Considere agora o exemplo já visto que pede que um usuário entre com um número e verifique se o número é par. Porém agora, queremos que o programa imprima ``o numero e par'' ou ``o numero e impar''.

#include <iostream>
using namespace std;

int main( ){

  int num;
  
  // obtem um numero do usuario
  cout << "Entre com um inteiro: ";
  cin >> num;
      
  // imprime uma mensagem dizendo se o numero e par ou impar
  if (num % 2 == 0)
    cout << "O numero eh par.\n";
  else
    cout << "O numero eh impar.\n";      

}

4.1 Um erro comum

É muito frequente utilizar o operador relacional == em expressões condicionais da sentença if. Por exemplo:

    int  saldo = 2000;

    if (saldo == 1)
       cout << "Voce esta quebrado! " << endl;
    else 
       cout << "Seu saldo eh " << saldo << endl;

Como a sentença saldo = 2000 inicializa o valor da variável saldo com 2000, a expressão saldo == 1 tem valor 0. Portanto, a senteça que segue o else será executada, e a mensagem

Seu saldo e 2000

será impressa.

Agora, suponha que, devido a um erro, você tenha colocado = ao invés de ==:

    int saldo = 2000;

    if (saldo = 1)
       cout << "Voce esta quebrado! " << endl;
    else 
       cout << "Seu saldo eh " << saldo << endl;

Agora, a expressão saldo = 1 tem valor 1. Portanto, a sentença que segue o if será executada, e a mensagem

Voce esta quebrado!

será impressa. Além disso, a atribuição causará um efeito colateral, e alterará o valor de saldo para 1.

Tal uso do operador de atribuição não é ilegal, e não será detectado pelo compilador como erro. Portanto, tome cuidado com o uso de atribuição no lugar de igualdade. Tal erro é muito comum, e não é fácil de achar.

Como regra geral, NÃO utilize atribuições dentro de outras sentenças.

5 Aninhando senteças if e if-else

Como era de se esperar, é possível colocar uma sentença condicional dentro de outra. Por exemplo, se quisermos imprimir uma mensagem apropriada caso um número seja positivo ou negativo e par ou ímpar, nós poderíamos escrever o seguinte:

#include <iostream>
using namespace std;

int main( ){

  int num;
  
  // Obtem um numero do usuario
  cout << "Entre com um inteiro: ";
  cin >> num;
  
  // Imprime uma mensagem dizendo se o numero e positivo ou negativo,
  // positivo ou negativo.
  if (num >= 0) {
    if (num % 2 == 0)
      cout << "O numero e par e positivo\n";
    else
      cout << "O numero e impar e positivo\n";      
  }
  else {
    if (num % 2 == 0)
      cout << "O numero e par e negativo\n";
    else
      cout << "O numero e impar e negativo\n";      
  }
}

5.1 A ambigüidade do else

O aninhamento de sentenças if-else sem usar chaves ({ e }) para delimitar o bloco de senteças a ser executado pode trazer efeitos indesejados.

Há uma regra simples para determinar qual if está associado a qual else.

Regra de associação:

Um else está associado com a última ocorrência do if sem else.

O exemplo seguinte está errado porque associa o else ao if "incorreto":

#include <iostream>
using namespace std;

int main( ){

  int num;
  
  // Obtem um numero do usuario
  cout << "Entre com o numero de peras: ";
  cin >> num;
  
  // Imprime uma mensagem dizendo se o numero de peras e 0 ou 1
  //  (*** isto esta' errado !!  ***)
  if (num != 0)
    if (num == 1)
      cout << "Voce tem uma pera.\n";
  else
    cout << "Voce nao tem nenhuma pera.\n";
}

Neste exemplo, o if tem o seguinte significado, segundo a regra de associação:

#include <iostream>
using namespace std;

int main( ){

  int num;
  
  // Obtem um numero do usuario
  cout << "Entre com o numero de peras: ";
  cin >> num;
  
  // Como a sentenca if e' vista pelo compilador
  if (num != 0)
    if (num == 1)
      cout << "Voce tem uma pera.\n";
    else
      cout << "Voce nao tem nenhuma pera.\n";
}

Para evitar este problema, chaves ({ e }) devem ser usadas para tirar a ambiguidade. O exemplo abaixo mostra como as chaves podem ser inseridas para corrigir o programa acima.

#include <iostream>
using namespace std;

int main( ){

  int num;
  
  // Obtem um numero do usuario
  cout << "Entre com o numero de peras: ";
  cin >> num;
  
  // Como corrigir o problema (este programa funciona)
  if (num != 0) {
    if (num == 1)
      cout << "Voce tem uma pera.\n";
  }
  else
    cout << "Voce nao tem nenhuma pera.\n";
}

Exercício 1:

Faça um programa que leia 3 números e imprima o maior.

/usr/local/lib/tex/inputs///Programas/Exercicio_04-1.cpp

6 Operadores Lógicos

Todos os programas até agora consideraram if com condições de teste simples. Alguns exemplos de testes simples: b != 0, contador <= 5. Estas expressões testam uma condição. Portanto, quando mais de uma condição precisa ser testada, precisamos usar sentenças if e if-else aninhadas.

A linguagem C++ , assim como a maioria das linguagens de programação de alto nível suportam operadores lógicos que podem ser usados para criar operações lógicas mais complexas, combinando condições simples. O valor de uma expressão lógica é ou VERDADEIRO ou FALSO. Lembre que não há constantes lógicas VERDADEIRO e FALSO em C++ ; em expressões lógicas 0 é interpretado como FALSO, e qualquer valor diferente de zero é interpretado como VERDADEIRO.

Os operadores lógicos são

! NÃO lógico, operação de negação (operador unário)

&& E lógico, conjunção (operador binário)

|| OU lógico, disjunção (operador binário).

Por exemplo, se quisermos testar se um número num é positivo e par, e imprimir uma mensagem como no exemplo anterior, podemos escrever:

      if (num >= 0)
         if (num % 2 == 0)
            cout << "Numero par nao negativo." << endl;

Com os operadores lógicos isso pode ser simplificado:

      if ((num>=0) && (num%2 == 0))
          cout << "Numero par nao negativo." << endl;

A operação de negação, !, pode ser usado da seguinte forma:

!expressão lógica: O valor é a negação lógica da expressão dada. Por exemplo:

!0 é 1

!1 é 0

Nós podemos usar o operador de negação lógica e escrever o exemplo acima como:

      if (num>0 && !(num%2))
            cout << "Numero par nao negativo." << endl;

Os dois operadores binários operam sobre duas expressões lógicas e tem o valor 1 (verdadeiro) or 0 (falso). Os exemplos abaixo mostram o seu uso:

a==0 && b==0 (verdadeiro se ambos a == 0 e b == 0, portanto se a e b são 0)

a==0 || b==0 (verdadeiro se pelo menos uma das variáveis a or b for 0)

Uma expressão usando && é verdadeira somente se ambos os operadores forem verdadeiros (não zero).

Uma expressão usando || é falsa somente se ambos os operadores forem falsos (zero).

Verifique na Tabela 2 o resultado do uso de operadores lógicos:


Tabela 2: Resultado de uso de Operadores Lógicos
\begin{table}\centering\begin{displaymath}
\begin{array}{\vert c\vert c\vert c\...
...so & falso & falso & falso \\ \hline
\end{array} \end{displaymath}
\end{table}


A precedência do operador de negação lógica é a mais alta (no mesmo nível que o ``-'' unário). A precedência dos operadores lógicos binários é menor que a dos operadores relacionais, e mais alta que a operação de atribuição. O && tem precedência mais alta que o ||, e ambos associam da esquerda para a direita (como os operadores aritméticos).

Como a precedência dos operadores lógicos é menor que a dos operadores relacionais, não é necessário usar parênteses em expressões como:

x >= 3 && x <= 50

x == 1 || x == 2 || x == 3

A Tabela 3 mostra o quadro completo de precedência de operadores aritméticos, relacionais e lógicos.


Tabela 3: Precedência e associatividade de operadores
Operador Associatividade
   
() esquerda para direita
! - (unários) direita para esquerda
* / % esquerda para direita
+ - esquerda para direita
< <= > >= esquerda para direita
== != esquerda para direita
&& esquerda para direita
|| esquerda para direita


No próximo exemplo, o programa verifica se as três variáveis lado1, lado2, e lado3, podem ser lados de um triângulo reto. Nós usamos o fato que os três valores devem ser positivos, e que o quadrado de um dos lados deve ser igual a soma dos quadrados dos outros lados (Teorema de Pitágoras) para determinar se o triângulo é reto.

#include <iostream>
using namespace std;

int main( ){

  int lado1, lado2, lado3;
  int s1, s2, s3;
  
  cout << "Entre com o tamanho dos lados do triangulo: ";
  cin >> lado1 >> lado2 >> lado3;
  
  // calcula o quadrado dos lados
  s1 = lado1*lado1;
  s2 = lado2*lado2;
  s3 = lado3*lado3;
  
  // testa a condicao para um triangulo reto
  
  if ( lado1>0 && lado2>0 && lado3 > 0 ) {
    if (s1==s2+s3 || s2==s1+s2 || s2==s1+s3) ) {
    cout << "Triangulo reto!\n";
  }
  else {
    cout << "Nao pode ser um triangulo!\n";
  }
} 

Na utilização de expressões lógicas, as seguintes identidades são úteis. Elas são chamadas de Lei de DeMorgan:

!(x && y) é equivalente a !x || !y

e

!(x || y) é equivalente a !x && !y

7 Exemplos

7.1 IF - ELSE

Assuma as seguintes declaraçõoes de variáveis:

  int x = 4;
  int y = 8;

O que é impresso pelos seguintes programas ?

  1.   if (y = 8)
          if (x = 5)
              cout <<  "a ";
          else
              cout <<  "b ";
      cout <<  "c ";
      cout <<  "d" << endl;
    
      ==> a c d
    

  2. mude = para ==
      ==> b c d
    

  3. altere o programa acima para produzir a seguinte saida:

7.2 Operadores lógicos

O que é impresso pelas seguintes sentenças?

  1. Assuma x = 5 e y = 8.
       if (x == 5 && y == 8)
           cout <<  "a" << endl;
       else
           cout <<  "b" << endl;     ==> a
    

  2. Assuma x = 4 e y = 8.
       if (x == 5 || y == 8)
           cout <<  "a" << endl;
       else
           cout <<  "b" << endl;     ==> a
    
    
       if !(x == 5 || y == 8)    // equiv. (x != 5 && y != 8)
           cout <<  "a" << endl;
       else
           cout <<  "b" << endl;     ==> b  
    
    
       if !(x == 5 && y == 8)   // equiv. (x != 5 || y != 8)
           cout <<  "a" << endl;
       else
           cout <<  "b" << endl;     ==> a
    

  3. Precedência: ! > && > ||
       if (x == 5 ||  y == 8 && z == 10) 
    
    equiv. 
    
       if (x == 5 || (y == 8 && z == 10))
    

8 A construção else-if

Embora ela não seja um tipo diferente de sentença, a seguinte construção é bastante comum para programar decisões entre diversas alternativas:

if ($expressao_{1}$)

$sentenca_{1}$

else if ($expressao_{2}$)

$sentenca_{2}$

else if ($expressao_{3}$)

$sentenca_{3}$

$\vdots$

else if ( $expressao_{n-1}$)

$sentenca_{n-1}$

else

$sentenca_{n}$

As expressões lógicas são avaliadas em ordem, começando com a $expressao_{1}$. Se uma das expressões for verdadeira, a sentença associada será executada. Se nenhuma for verdadeira, então a sentença, $sentenca_n$, do último else será executada como opção default. Se a opção default não for necessária, então a parte

else

$sentenca_{n}$

pode ser removida.

\includegraphics[scale=1.0]{else-if}

Exemplo 9:

O seguinte exemplo mostra um else-if de três opções. O programa lê dois números e diz se eles são iguais ou se o primeiro número é menor ou maior que o segundo.

#include <iostream>
using namespace std;

int main( ){

  int num1, num2;
  
  // obtem 2 numeros do usuario
  cout << "Entre um numero: ";
  cin >> num1;
  cout << "Entre com um outro numero: ";
  cin >> num2;
  
  // mostra a mensagem de comparacao
  if (num1 == num2)
    cout << "Os numeros sao iguais\n";
  else if (num1 < num2)
    cout << "O primeiro numero e menor\n";
  else
    cout << "O primeiro numero e maior\n";
}

No programa acima, se (num1 == num2) for verdadeiro, então os números são iguais. Senão, é verificado se (num1 < num2). Se esta condição for verdadeira, então o primeiro número é menor. Se isso não for verdadeiro, então a única opção restante é que o primeiro número é maior.

Exemplo 10:

Este programa lê um número, um operador e um segundo número e realiza a operação correspondente entre os operandos dados.

#include <iostream>
using namespace std;

int main( ){

  float num1, num2;
  char op;
  
  // obtem uma expressao do usuario
  cout << "Entre com numero operador numero\n";
  cin >> num1 >> op >> num2;
  
  // mostra o resultado da operacao
  if (op == '+')
    cout << " = " << setprecision(2) << num1 + num2;
  else if (op == '-')
    cout << " = " << setprecision(2) << num1 - num2;
  else if (op == '/')
    cout << " = " << setprecision(2) << num1 / num2;
  else if (op == '*')
    cout << " = " << setprecision(2) << num1 * num2;
  else 
    cout << " Operador invalido.";
  cout << endl;
}

Exemplos da execução deste programa:

   Entre com numero operador numero:
   5 * 3.5
    = 17.50

   Entre com numero operador numero:
   10 + 0
    = 10.00

   Entre com numero operador numero:
   10 x 5.0
    Operador invalido.

9 Funções

9.1 Funções: o que são e por que usá-las

Quando queremos resolver um problema, em geral tentamos dividi-lo em subproblemas mais simples e relativamente independentes, e resolvemos os problemas mais simples um a um. A linguagem C++ dispõe de construções (abstrações) que auxiliam o projeto de programas de maneira top-down. Uma função cria uma maneira conveniente de encapsular alguns detalhes de ``processamento'', ou seja, como algum resultado é obtido. Quando esta ``computação'' é necessária, a função é chamada, ou invocada. Desta forma, quando uma função é chamada o usuário não precisa se preocupar como a computação é realizada. É importante saber o que a função faz (qual o resultado da execução de uma função) e também como se usa a função. Criando funções, um programa C++ pode ser estruturado em partes relativamente independentes que correspondem as subdivisões do problema.

Você já viu algumas funções: cin.get(), sqrt(). Elas são funções de uma biblioteca padrão (do C++ ). Você não sabe como elas foram escritas, mas já viu como utilizá-las. Ou seja, você sabe o nome das funções e quais informações específicas você deve fornecer a elas (valores que devem ser passados para as funções) para que a função produza os resultados esperados.

Quando nos referirmos a uma função neste texto usaremos a maneira frequentemente utilizada que é o nome da função seguido de ().

Tomemos como exemplo o programa abaixo, que recebe 2 conjuntos de 3 números e soma o maior valor de cada conjunto:

/usr/local/lib/tex/inputs///Programas/somamaiores.cpp

Observe que o código que verifica o maior valor dentre 3 números teve que ser reproduzido dentro do programa por duas vezes (para descobrir o maior valor de dois conjuntos diferentes de 3 números).

Um dos benefícios mais óbvios de usar funções é que podemos evitar repetição de código. Em outras palavras, se você quiser executar uma operação mais de uma vez, você pode simplesmente escrever a função uma vez e utilizá-la diversas vezes ao invés de escrever o mesmo código várias vezes. Outro benefício é que se você desejar alterar ou corrigir alguma coisa mais tarde, é mais fácil alterar em um único lugar.

O exemplo acima poderia ser simplificado pela criação de uma função chamada maior, que dados três números $a$, $b$, e $c$, dá como resultado o maior valor dentre os três valores fornecidos:

/usr/local/lib/tex/inputs///Programas/maior.cpp

O exemplo pode ser então alterado e simplificado com o uso da função maior():

/usr/local/lib/tex/inputs///Programas/somamaiores-func.cpp

Como pode ser observado, sejam quais forem os conjuntos de 3 números fornecidos, não precisa escrever um código similar ao mostrado na função maior acima para cada número. Basta chamar a função maior(), passar os valores necessários para verificar o maior valor de cada conjunto, e utilizar os resultados.

Evitar repetição de código é a razão histórica que funções foram inventadas (também chamado de procedimento ou subrotinas em outras linguagens de programação). A maior motivação para utilizar funções nas linguagens contemporâneas é a redução da complexidade do programa e melhoria da modularidade do programa. Dividindo o programa em funções, é muito mais fácil projetar, entender e modificar um programa. Por exemplo, obter a entrada do programa, realizar as computações necessárias e apresentar o resultado ao usuário pode ser implementado como diferentes funções chamadas por main() nesta ordem.

Funções podem ser escritas independentemente uma da outra. Isto significa que, em geral, variáveis usadas dentro de funções não são compartilhadas pelas outras funções. Assim sendo, o comportamento da função é previsível. Se não for assim, duas funções completamente não relacionadas podem alterar os dados uma da outra. Se as variáveis são locais a uma função, programas grandes passam a ser mais fáceis de serem escritos. A comunicação entre funções passa a ser controlada - elas se comunicam somente através pelos valores passados as funções e os valores retornados.

9.2 Definindo funções

Um programa C++ consiste de uma ou mais definições de funções (e variáveis). Há sempre uma função chamada main. Outras funções também podem ser definidas. Cada uma pode ser definida separadamente, mas nenhuma função pode ser definida dentro de outra função. Abaixo, mostramos um exemplo simples de um programa que consiste de duas funções: main() e alo(). Quando executado, este programa imprimirá a mensage Alo! três vezes.

     #include <iostream>
     using namespace std;

     // definicao da funcao alo()
     void alo(void)
     {
        cout << "Alo!" << endl;
     }

     // definicao da funcao main()
     int main ()
     {
        int i;
  
        i = 1;
        while (i <= 3)
        {
            alo();
            i = i + 1;
        }
     }

Todas as funções devem ser declaradas ou definidas antes de serem usadas. As funções da biblioteca padrão, tais como cin.get(), são pré-definidas, mas mesmo assim devem ser declaradas (deve ser anunciado ao compilador que elas existem). É por isso que incluímos a linha #include <iostream> no início do código fonte.

O formato geral da definição de uma função é

tipo-do-resultado nome-da função (lista-de-argumentos)

{

declarações e sentenças

}

A primeira linha da definição é o cabeçalho da função. Ela têm três partes principais: o nome da função, o tipo do resultado (que é um valor) que a função computa e retorna, e entre parênteses uma lista de parâmetros (também chamado de argumentos formais). Se a função não retorna nenhum valor, o tipo é chamado de void, e esta palavra é escrita no cabeçalho na frente do nome da função. Se a função não tiver argumentos formais, a palavra void pode ser escrita no lugar da lista de argumentos formais entre os parênteses. Para simplificar a exposição, falaremos sobre o tipo do retorno e os argumentos formais mais tarde. Eles servem para permitir que as funções troquem informações entre si.

9.3 Funções simples

Para começar, vamos utilizar funções na seguinte forma:

void nome-da-função(void)

{

declarações e senteças (corpo da função)

}

O primeiro void significa que esta função não tem tipo de retorno (não retorna um valor), e o segundo significa que a função não tem argumentos (ela não precisa de nenhuma informação externa para ser executada). Isso não significa que a função não faz nada. Ela pode realizar alguma ação, como imprimir uma mensagem. O exemplo abaixo mostra um programa que usa uma função como essa:

     #include <iostream>
     using namespace std;

    // DEFINIÇÃO da função alo()
    void alo(void)
    {
        cout << "Alo." << endl;
    }

    // Programa Principal
    int main()
    {
        alo();
    }

Neste exemplo, o programa consiste de duas funções, main() e alo().

A função alo() imprime a mensagem Alo. quando chamada. A sentença cout é o corpo da função. Dentro da função main() há uma chamada a função alo(). A função é chamada pelo seu nome seguido de () (já que a função alo não tem argumentos, nenhuma expressão é escrita dentro dos parênteses). A função alo() não retorna um valor, ela é chamada simplesmente para realizar uma ação (imprimir a mensagem). A chamada de função é uma sentença válida em C++ , portanto deve ser terminada por ponto e vírgula (;).

alo();

Observe que a ordem em que as funções são definidas dentro do código-fonte é importante, sendo que uma função deve sempre ser definida ANTES das funções em que ela é CHAMADA. No nosso exemplo, como a função alo() é chamada pela função main(), então a DEFINIÇÃO da função alo() deve vir antes da definição da função main(). O uso de protótipos pode ser usado para definir as funções em qualquer ordem dentro do código-fonte, o que será visto na Seção 9.9.

Outra coisa que você deve ter notado é que main() também é uma função. A função main() não difere em nada das demais funções, com a exceção de que contém o programa principal, isto é, ao se executar um programa, ela á a primeira função a ser executada. As demais funções são executadas somente quando chamadas a partir da execução da função main().

9.3.1 Argumentos

Nosso próximo exemplo pede que o usuário digite suas iniciais, e então chama a função cumprimenta() para imprimir a mensagem ``Ola'' junto com as iniciais digitadas. Estas iniciais (seus valores) são passadas para a função cumprimenta(). A função cumprimenta() é definida de forma que ela imprimirá a mensagem incluindo quaisquer iniciais passadas.
     #include <iostream>
     using namespace std;

     void cumprimenta(char inic1, char inic2)
     {
         cout << "Ola, " << inic1 << inic2 << "!" << endl;
     }

     int main()
     {
        char primeiro, segundo;

        cout << "Entre com duas iniciais (sem separacao): ";
        cin >> primeiro >> segundo ;
        cumprimenta(primeiro, segundo);
     }

A função main() chama a função cumprimenta(). Ao fazer esta chamada, main() passa para cumprimenta() os valores dos dois caracteres para serem impressos. Veja um exemplo de execução do programa:

Entre com duas iniciais (sem separacao): YK
Alo, YK!
Note que há uma correspondência entre a quantidade, a ordem e tipo dos valores que main() passa (estes são chamados de parâmetros reais ou argumentos reais) e os argumentos listados no cabeçalho da função cumprimenta() (denominados argumentos formais).

9.4 Funções que retornam um valor

Funções que não retornam nenhum valor (como alo(), main()) possuem tipo void.

Além de executarem ações (como imprimir) uma função também pode retornar um valor para o programa que o chamou. Uma função que retorna um valor tem no cabeçalho o nome do tipo do resultado. O valor retornado pode ser de qualquer tipo, incluindo int, float e char (é claro que uma vez definida, a função só é de um tipo específico). Uma função que retorna um tipo diferente de void executa alguns cálculos, e retorna o resultado (que é um único valor) para quem a chamou. A função chamadora pode então usar o resultado. Para retornar um valor para a função chamadora, a função usa a sentença return.

O formato da sentença return é a seguinte:

return expressão;

A expressão é avaliada e o seu valor é convertido ao tipo de retorno da função (o tipo da função é dado no cabeçalho da função antes do nome da função).

Considere o seguinte exemplo. O programa consiste de duas funções: main() e quadrado. O programa pede que o usuário digite três números e verifica se eles podem ser os lados de um triângulo reto.

     // programa que verifica se 3 numeros podem ser os lados de um
     // triangulo reto.
     //
     #include <iostream>
     using namespace std;

     // funcao que calcula o quadrado de um numero
     int quadrado(int n)
     {
         return n * n;
     }


     int main()
     {
         int s1, s2, s3;

         cout << "Entre tres inteiros: ";
         cin >> s1 >> s2 >> s3;

         if ( s1 > 0 && s2 > 0 && s3 > 0 && 
            (quadrado(s1) + quadrado(s2) == quadrado(s3) ||
             quadrado(s2) + quadrado(s3) == quadrado(s1) ||
             quadrado(s3) + quadrado(s1) == quadrado(s2)) )
         {
               cout << " " << s1 << " " << s2 << " " << s3 
                    << " podem formar um triangulo reto\n";
         }
         else
         {
               cout << " " << s1 << " " << s2 << " " << s3 
                    << " nao podem formar um triangulo reto\n";
         }
}

Note que quando chamamos a função quadrado() passamos o valor no qual desejamos executar o cálculo, e também usamos o valor retornado pela função em expressões. O valor de quadrado(s1) é o valor que a função quadrado() retorna quando chamado com o valor do argumento sendo igual ao valor da variável s1.

Os valores retornados pelas chamadas de funções podem ser usados em todos os lugares onde valores podem ser usados. Por exemplo,

y = quadrado(3);
Aqui quadrado(3) tem o valor 9, portanto 9 pode ser atribuído a variável y;

x = quadrado(3) + quadrado(4);
atribuirá 25 a variável x, e

area = quadrado(tamanho);
atribuirá a variável area o valor da variável tamanho elevado ao quadrado.

O próximo exemplo tem uma função chamada cinco:

     #include <iostream>
     using namespace std;

     int cinco(void)
     {
         return 5;
     }

     int main()
     {
         cout << "cinco = " << cinco() << endl;
     }

A saída do programa será

cinco = 5

porque o valor de cinco() dentro da sentença cout é 5. Olhando na sentença return, 5 é a expressão retornada para o chamador.

Outro exemplo:

     #include <iostream>
     using namespace std;

     int obtem_valor(void)
     {
         int valor;
  
         cout << "Entre um valor: ";
         cin >> valor;

         return valor;
     }


     int main()
     {
         int a, b;

         a = obtem_valor();
         b = obtem_valor();

         cout << "soma = " << a + b << endl;
     }

Este programa obtém dois inteiros do usuário e mostra a sua soma. Ele usa a função obtem\undvalor() que mostra uma mensagem e obtém o valor do usuário.

Um exemplo de saída deste programa é:

    Entre um valor: 15
    Entre um valor: 4
    soma = 19

9.5 Mais sobre o return

Quando uma função return é executada, a função imediatamente acaba - mesmo que haja código na função após a sentença return. A execução do programa continua após o ponto no qual a chamada de função foi feita. Sentenças return podem ocorrer em qualquer lugar na função - não somente no final. Também é válido ter mais de um return dentro de uma função. A única limitação é que return retorna um único valor.

O seguinte exemplo mostra uma função (uma versão para int da função obtem\undvalor) que pede para usuário um valor e se o usuário digitar um valor negativo, imprime uma mensagem e retorna um valor positivo.

   int obtem_valor_positivo(void)
   {
      int valor;

      cout << "Entre um valor: ";
      cin >> valor;

      if (valor >= 0)
         return valor;
   
      cout << "Tornando o valor positivo..." << endl;
 
      return -valor;
   }

Em uma função void, return; (só com ;) pode ser usado para sair de uma função. O exemplo seguinte, pede instruções ao usuário. Se o usuário reponder nao, a função termina. Do contrário, ele imprime as instruções e depois termina.

  void instrucoes(void)
  {
     int ch;

     cout << "Voce quer instrucos? (s/n): ";
     ch = cin.get();

     /* Termina se resposta for n */
     if (ch == 'n' || ch == 'N')
        return;

     /* Mostra instrucoes */
     cout << "As regras do jogo sao . . . ";
       .
       .
       .
     return;
  }

O return final (antes de fechar as chaves do corpo da função) na função é opcional. Se omitido, a função atingirá o final da função e retornará automaticamente. Note que o return é opcional somente para funções void.

9.6 Mais sobre Argumentos

A comunicação entre uma função e o chamador pode ser nas duas direções. Argumentos podem ser usados pelo chamador para passar dados para a função. A lista de argumentos é definida pelo cabeçalho da função entre parênteses.. Para cada argumento você precisa especificar o tipo do argumento e o nome do argumento. Se houver mais de um argumento, eles são separados por vírgula. Funções que não possuem argumentos tem void como lista de argumento. No corpo da função os argumentos (também chamados de argumentos formais ou parâmetros formais) são tratados como variáveis. É erro defini-los dentro do corpo da função porque eles já estão definidos no cabeçalho. Antes da execução da função os valores passados pelo chamador são atribuídos aos argumentos da função.

Considere o seguinte programa com a função abs() que calcula o valor absoluto de um número.

     #include <iostream>
     using namespace std;

     /* Definicao da funcao abs */
     int abs(int x)
     {
        if (x < 0)
           x = -x;

        return x;
     }


     int main()
     {
         int n;

         cout << "Entre um numero: ";
         cin >> n;

         cout << "Valor absoluto de " << n << " eh " << abs(n) << endl;
     }

A função abs() tem um argumento do tipo int, e seu nome é x. Dentro da função, x é usado como uma variável x.

Uma vez que abs() tem um único argumento, quando ela é chamada, há sempre um valor dentro do parênteses, como em abs(n). O valor de n é passado para a função abs(), e antes da execução da função, o valor de n é atribuído a x.

Aqui está um exemplo de uma função que converte uma temperatura de Farenheit para Celsius:

   float fahr_para_cels(float f)
   {
       return 5.0 / 9.0 * (f - 32.0);
   }

Como você pode ver, esta função tem somente um argumento do tipo float. Um exemplo de chamada desta função poderia ser:

fervura = fahr_para_cels(212.0);

O resultado da função fahr\undpara\undcels(212.0) é atribuído a fervura. Portanto, depois da execução desta sentença, o valor de fervura (que é do tipo float) será 100.0.

O exemplo seguinte possui mais de um argumento:

   float area(float largura, float altura)
   {
       return largura * altura;
   }

Esta função possui dois argumentos do tipo float. Para chamar uma função com mais de um argumento, os argumentos devem ser separados por vírgula. A ordem em que os argumentos são passados deve ser na mesma em que são definidos. Neste exemplo, o primeiro valor passado será a largura e o segundo a altura. Um exemplo de chamada seria

tamanho = area(14.0, 21.5);

Depois desta sentença, o valor de tamanho (que é do tipo float) será 301.0.

Quando passar os argumentos, é importante ter certeza de passá-los na ordem correta e que eles são do tipo correto. Se isso não for observado, pode ocorrer erro ou aviso de compilação, ou resultados incorretos podem ser gerados.

Uma última observação. Os argumentos que são passados pelo chamador podem ser expressões em geral e não somente constantes e variávies. Quando a função é chamada durante a execução do programa, estas expressões são avaliadas, e o valor resultante passado para a função chamada.

9.7 Chamada por valor

Considere novamente a função quadrado(). Se esta função é chamada de main() como

p = quadrado(x);

somente o valor (não o endereço) de x é passado para quadrado. Por exemplo, se a variável tem valor 5, para a função quadrado(), quadrado(x) ou quadrado(5) são o mesmo. De qualquer forma, quadrado() receberá somente o valor 5. quadrado() não sabe se na chamada da função o 5 era uma constante inteira, o valor de uma variável do tipon int, ou alguma expressão como 625/25 - 4 * 5. Quando quadrado() é chamado, não interessa qual a expressão entre parênteses, ela será avaliada e o valor passado para quadrado().

Esta maneira de passar argumentos é chamada de chamada por valor. Argumentos em C++ são passados por valor. Portanto, a função chamada não pode alterar o valor da variável passada pelo chamador como argumento, porque ela não sabe em que endereço de memória o valor da variável está armazenado.

9.8 Variáveis locais

Como você provavelmente já reparou em alguns exemplos, é possível definir variáveis dentro de funções, da mesma forma que temos definido variáveis dentro da função main(). A declaração de variáveis é feita no início da função.

Estas variáveis são restritas a função dentro da qual elas são definidas. Só esta função pode ``enxergar'' suas próprias variáveis. Por exemplo:

     #include <iostream>
     using namespace std;

     void obtem_int(void)
     {
         int x;
  
         cout << "Entre um valor: ";
         cin >> x;

         cout << "Obrigado!\n";
     }


     int main()
     {
         obtem_int();

         /* **** Isto esta' errado **** */
         cout << "Voce digitou " << x << endl;
     }

A função main() usou um nome x, mas x não é definido dentro de main; ele é uma variável local a get\undint(), não a main(). Este programa gera erro de compilação.

Note que é possível ter duas funções que usam variáveis locais com o mesmo nome. Cada uma delas é restrita a função que a define e não há conflito. Analise o seguinte programa (ele está correto):

     #include <iostream>
     using namespace std;

     int obtem_novo_int(void)
     {
         int x;
 
         cout << "Entre um valor: ";
         cin >> x;

         cout << "Obrigado!\n";
         return x;
     }

     int main()
     {
         int x;

         x = obtem_novo_int();

         /* ****Isto nao esta errado !! **** */
         cout << "Voce digitou " << x << endl;
     }

A função obtem\undnovo\undint() usa uma variável local chamada x para armazenar o valor digitado e retorna como resultado o valor de x. main() usa outra variável local, também chamada de x para receber o resultado retornado por obtem\undnovo\undint(). Cada função tem sua própria variável x.


9.9 Protótipos

Os protótipos servem para dar ao compilador informações sobre as funções. Isso para que você possa chamar funções antes que o compilador tenha a definição (completa) das funções. O protótipo de uma função é idêntico ao cabeçalho da função, mas o nome dos argumentos podem ser omitidos e ele é terminado com um ponto e vírgula. Protótipos declaram uma função ao invés de defini-las. O formato de um protótipo é:

tipo-de-retorno nome-da-função(lista-dos-tipos-dos-argumentos);

Definindo protótipos, você não precisa se preocupar com a ordem em que define as funções dentro do código-fonte do programa. A principal vantagem de definir protótipos é que erros de chamada de funções (como chamar uma função com o número incorreto de argumentos, ou com argumentos de tipo errado) são detectados pelo compilador. Sem protótipos, o compilador só saberia que há erro depois de encontrar a definição da função.

Abaixo, mostramos a definição de duas funções e seus respectivos protótipos:

   float volume(float, float, float);
   float dinheiro(int, int, int, int);


   float volume(float comprimento, float largura, float altura)
   {
       return comprimento * largura * altura;
   }
 
   float dinheiro(int c25, int c10, int c5, int c1)
   {
        return c25 * 0.25 + c10 * 0.10 + 
               c5 * 0.05 + c1 * 0.01;
   }

9.10 Documentação de funções

Você deve documentar as funções que escreve. Na documentação você deve especificar as seguintes informações:

Ação - o que a função faz

Entrada - descrição dos argumentos passados para a função

Saída - descrição do valor retornado pela função

Suposições - o que você assume ser verdade para que a função funcione apropriadamente

Algoritmo - como o problema é resolvido (método)

Estas informações devem ser colocadas como comentário antes da definição da função.

9.11 Comentários

Você pode colocar comentários no seu programa para documentar o que está fazendo. O compilador ignora completamente o que quer esteja dentro de um comentário.

Comentários em C++ são textos que começam com // em cada linha, ou são delimitados por /* e */. Os símbolos // não possuem espaço entre si. Alguns exemplos:

    // Este é um comentário sem graça


    // Este é um comentário um pouco
    // maior que tem diversas linhas


   /* Este é um outro comentário maior
      ainda e que tem várias linhas
      explicando qualquer aspecto mais detalhado
      do programa
    */

Regras para comentário

É sempre uma boa idéia colocar comentários em seu programa das coisas que não são claras. Isto vai ajudar quando mais tarde você olhar o programa que escreveu já há algum tempo ou vai ajudar a entender programas escritos por outra pessoa.

Um exemplo de comentário útil:

    /* converte temperatura de farenheit para celsius */
    celsius = (fahrenheit - 32) * 5.0 / 9.0;

O comentário deve ser escrito em português e não em C++ . No exemplo abaixo

    /* usando scanf, obter valor de idade e multiplicar por 365 para
     * obter dias */
    cin >> idade;
    dias = idade * 365;

o comentário é basicamente uma transcrição do código do programa. Em seu lugar, um comentário como

    /* obtem idade e transforma em numero de dias */

seria mais informativo neste ponto.

Em outras palavras, você deve comentar o código, e não codificar o comentário.

Você também deve evitar comentários inúteis ou óbvios. Por exemplo:

    // Incrementa i
    i = i + 1;

Não há necessidade de comentários já que i = i + 1 já é auto explicativo.

Abaixo está um exemplo de como você deve comentar uma função.

  /* função instrucoes()
   *  acao:        mostra instrucoes do programa
   *  entrada:     nenhuma
   *  saida:       nenhuma
   *  suposicoes:  nenhuma
   *  algoritmo:   imprime as instrucoes
   */
  void instrucoes(void)
  {
     /* mostra instrucoes */
     cout << "O processo de purificacao do  Uranio-235 e' . . .  ";
       .
       .
  }


10 Estruturas de Repetição

A linguagem C++ possui comandos para repetir uma sequência de instruções. Estas estruturas de repetição, também conhecidas como laços (do inglês loops). Nesta seção veremos a estrutura while, Sendo que as demais estruturas de repetição em C++ , for e do ... while serão vistas na Seção 19.


10.1 O comando de repetição while

O comando de repetição while tem duas partes: a expressão de teste e o corpo da repetição. O formato do while é:

while (expressão teste )

corpo da repetição

A expressão teste é inicialmente avaliada para verificar se o laço deve terminar. Caso a expressão seja verdadeira (isto é, diferente de 0 (zero)), o corpo da repetição é executado. Depois desta execução, o processo é repetido a partir da expressão teste. O corpo do laço, por sua vez, pode ser uma sentença simples ou composta (veja Seção 1.1).

\includegraphics[scale=1.0]{while}

O exemplo abaixo mostra o uso do comando de repetição while:

             #include <iostream>
             using namespace std;

             int contador;

             contador = 0;
             while( contador < 5 )
             {
                 cout << "contador = " << contador << endl;
                 contador = contador + 1;
             }

             cout << "ACABOU !!!!" << endl;

Saída:

             contador = 0
             contador = 1
             contador = 2
             contador = 3
             contador = 4
             ACABOU !!!!

Neste exemplo, a expressão de teste é contador < 5, e o corpo do laço é a sentença cout.

Se examinarmos cuidadosamente este exemplo, veremos que a variável contador é inicializada com 0 (zero). Depois disso, a expressão de teste é verificada e, como 0 < 5 é verdadeiro, o corpo da repetição é executado. Assim, o programa imprime contador = 0, e incrementa contador. Em seguida, a expressão de teste é verificada novamente e todo o processo se repete até que contador seja 4 e contador = 4 seja impresso.

Depois disso, contador é incrementado para 5 e o teste é executado. Mas desta vez, 5 < 5 é falso, então a repetição não continua. A execução do programa continua na sentença que segue o laço (no caso, imprimir a frase ACABOU !!!).

Imediatamente após a execução do while, a variável contador tem valor 5.

O exemplo seguinte mostra um uso mais apropriado do comando while: Em situações onde o número de repetições não é conhecido antes do inicío do comando while.

Exemplo 1:

Este programa pede números ao usuário até que a soma de todos os números digitados for pelo menos 20.

#include <iostream>
using namespace std;

int main( ){

  int total, num;
  
  total = 0;
  while( total < 20 ) {
    cout << "Total = " << total << endl;
    cout << "Entre com um numero: ";
    cin >> num;
    
    total = total + num;
  }
  
  cout << "Final total = " << total << endl;
}

Exemplo de saída:

   Total = 0
   Entre com um numero: 3
   Total = 3
   Entre com um numero: 8
   Total = 11
   Entre com um numero: 15
   Final total = 26

Inicialmente, é dado o valor 0 à variável total, e o teste é verdadeiro ( 0 < 20). Em cada iteração, o total é impresso e o usuário digita um número que é somado a total. Quanto total for maior ou igual a 20, o teste do while torna-se falso, e a repetição termina.

10.2 Estilo de formatação para estruturas de repetição

A regra principal é ser consistente. Assim, seu programa será mais legível.

10.2.1 Colocação das chaves

Há três estilos comuns de colocar as chaves:

   while (expressao)
      {
      sentenca;
      }

   while (expressao)
   {
      sentenca;
   }

   while (expressao) {
      sentenca;
   }

APENAS UM DESTES ESTILOS deve ser consistentemente usado para as sentenças de repetição ( for, while e do ... while). Use o estilo com o qual você se sentir mais confortável.

10.2.2 Necessidade ou não das chaves

Foi mencionado anteriormente que o corpo da repetição pode ser uma sentença composta (conjunto de sentenças delimitadas por chaves ( { e }) ou ums sentença simples. Por exemplo:

   while( i < 5 )
      i = i + 1;

Embora as chaves possam ser omitidas, há uma única razão para colocá-las sempre. Considere o caso simples abaixo:

   while( i < 5 ) {
      i = i + 1;
   }

Quando você adicionar algo ao programa, você poderá adicionar uma sentença para um laço com apenas uma sentença. Se você fizer isso, é vital que você também adicione chaves. Se você não fizer isso, a segunda sentença do laço não será considerada como parte do laço. Por exemplo:

   while( i < 5 )
      i = i + 1;
      j = j + 1;

é na verdade o mesmo que:

   while( i < 5 )
      i = i + 1;
   j = j + 1;

enquanto a intenção era na realidade:

   while( i < 5 ) {
      i = i + 1;
      j = j + 1;
   }

10.2.3 Uso de espaço em branco

A outra questão de formato é se deve ser colocado um espaço em branco depois do while e antes do abre parênteses ( (). Por exemplo:

 while (i<5)

ou

     while (i<5)

ou

     while( i < 5 )

Isto também é uma escolha pessoal. Porém seja consistente em sua escolha !

10.2.4 Laços aninhados

É possível colocar um laço dentro de outro (laço aninhado).

Exemplo 2:

#include <iostream>
#include <iomanip>  // Necessário para se usar a função setw() em cout

using namespace std;

int main( ){
  
  int linha, coluna;
  
  linha = 1; 
  while (linha < 5)
  {
    coluna = 1; 
    while (coluna < 5)
    {
      cout << setw(3) << linha * coluna;
      coluna = coluna + 1;
    }
    linha = linha + 1;
  }
  cout << endl;
}

Saída:

      1  2  3  4
      2  4  6  8
      3  6  9 12
      4  8 12 16

No exemplo acima, para cada iteração do laço externo, o laço interno imprime uma linha com números e depois pula de linha.

Exemplo 3:

Este exemplo é parecido com o anterior, exceto que o cout que produz a mudança de final de linha é colocado dentro do laço interno. Como era de se esperar uma nova linha é impressa após cada valor ao invés de ser depois de 4 valores.

#include <iostream>
#include <iomanip>  // Necessário para se usar a função setw() em cout

using namespace std;

int main( ){
  
  int linha, coluna;
  
  linha = 1; 
  while (linha < 5)
  {
    coluna = 1; 
    while (coluna < 5)
    {
      cout << setw(3) << linha * coluna;
      cout << endl;
      coluna = contador + 1;
    }

    linha = linha + 1;
  }
}

Saída:

            1
            2
            3
            4
            2
            4
            6
            8
            3
            6
            9
           12
            4
            8
           12
           16

Exemplo 4:

Este exemplo imprime um triângulo de asteriscos, de forma que a quantidade de asteriscos em uma linha é igual à ordem da linha (na linha 1, 1 asterisco, na linha 2, 2 asteriscos, etc.)

#include <iostream>

using namespace std;

int main( ){
  
  int linha, coluna;
  
  cout << endl;
  linha = 1;
  while (linha < 8)
  {
    cout << "\t";
    coluna = 1;
    while (coluna < linha)
    {
      cout << "*";
      coluna = coluna + 1;
    }
    cout << endl;
    linha = linha + 1;
  }
}

Saída:

                *
                **
                ***
                ****
                *****
                ******
                *******
                ********


11 Mais sobre funções: Quando return não é suficiente

Considere o programa abaixo que pede ao usuário dois inteiros, armazena-os em duas variáveis, troca seus valores, e os imprime.

#include <iostream>
using namespace std;

int main()
{
  int a, b, temp;
  
  cout << "Entre dois numeros: ";
  cin >> a >> b;
  
  cout << "Voce entrou com " << a << " e " << b << endl;
  
  /* Troca a com b */
  temp = a;
  a = b;
  b = temp;
  
  cout << "Trocados, eles sao " << a << " e " << b << endl;
}

Aqui está um exemplo de execução do programa:

     Entre dois numeros: 3 5
     Voce entrou 3 e 5
     Trocados, eles sao 5 e 3

O seguinte trecho do programa executa a troca de valores das variáveis a e b:

    temp = a;
    a = b;
    b = temp;

É possível escrever uma função que executa esta operação de troca? Considere a tentativa abaixo de escrever esta função:

#include <iostream>
using namespace std;

void troca(int x, int y)
{
    int temp;

    temp = x;
    x = y;
    y = temp;
}

int main()
{
  int a, b;
  
  cout << "Entre dois numeros: ";
  cin >> a >> b;
  
  cout << "Voce entrou com " << a << " e " << b << endl;
  
  // Troca a com b
  troca(a, b);
  
  cout << "Trocados, eles sao " << a << " e " << b << endl;
}

Se você executar este programa, verá que ele não funciona:

     Entre dois numeros: 3 5
     Voce entrou 3 e 5
     Trocados, eles sao 3 e 5

Como você já viu nas notas anteriores, em C++ os argumentos são passados por valor. Uma vez que somente os valores das variáveis são passados, não é possível para a função troca() alterar os valores de a e b porque troca() não sabe onde na memória estas variáveis estão armazenadas. Além disso, troca() não poderia ser escrito usando a sentença return porque podemos retornar APENAS UM valor (não dois) através da sentença return.

11.1 Usando referência

A solução para o problema acima é ao invés de passar os valores de a e b, passar uma referência às variáveis a e b. Desta forma, troca() saberia que endereço de memória escrever, portanto poderia alterar os valores de a e b.

Lembre-se que em C++ a cada variável está associado: (i) um nome; (ii) um tipo; (iii) um valor; e (iv) um endereço. Assuma que existam as seguintes definições de variáveis.

    int i = 5;
    char c = 'G';

Na memória, eles podem estar armazenados da forma indicada na Figura 11.1:

Figura 3: Variáveis em memória
\includegraphics[scale=0.5]{ptr1}

A variável inteira i está armazenada no endereço 1342. Ela usa dois bytes de memória (quando um objeto usa mais de um byte, seu endereço é onde ele começa - neste caso, 1342 e não 1343). A variável do tipo char c está armazenada no endereço 1346 e usa um byte de memória. O compilador é que controla do local de armazenamento destas variáveis em memória.

11.2 Argumentos por referência em funções

Considere novamente o exemplo da função troca(). Quando a e b são passados como argumentos para troca(), na verdade, somente seus valores são passados. A função não podia alterar os valores de a e b porque ela não conhece os endereços de a e b. Mas se referências para a e b forem passados como argumentos ao invés de a e b, a função troca() seria capaz de alterar seus valores; ela saberia então em que endereço de memória escrever. Na verdade, a função não sabe que os endereços de memória são associados com a e b, mas ela pode modificar o conteúdo destes endereços. Portanto, passando uma variável por referência (ao invés do valor da variável), habilitamos a função a alterar o conteúdo destas variáveis na função chamadora.

A definição da função troca() deve ser alterada, e a lista de parâmetros formais deve ter argumentos não do tipo int, mas referências para int, ou seja, int & . Quando chamamos a função troca(), nós continuamos passando parâmetros reais a e b, mas desta vez, o compilador sabe que o que será passado para a função troca() são as referências a estas variáveis, e não seus valores. Dentro da função troca() não deverá haver mudanças. Uma vez que agora os parâmetros formais são referências, o acesso aos objetos deve ser escrito normalmente, mas deve-se ter em mente que qualquer alteração nos valores dos parâmetros formais da função implica em alterar o valor dos argumentos passados para a função no momento de sua chamada. Assim, a função troca() é capaz de alterar os valores de a e b ``remotamente''.

O programa abaixo é a versão correta do problema enunciado para a função troca():

#include <iostream>

using namespace std;

/* função troca(px, py)
 *  ação:        troca os valores inteiros apontados por px e py
 *  entrada:     apontadores px e py
 *  saida:       valor de px e py trocados na origem da chamada da função
 *  suposições:  px e py sao apontadores validos
 *  algoritmo:   primeiro guarda o primeiro valor em um temporario e
 *               troca
 */
void troca(int & px, int & py)
{
    int temp;

    temp = px;
    px = py;
    py = temp;
}

int main()
{
  int a, b;
  
  cout << "Entre dois numeros: ";
  cin >> a >> b;
  
  cout << "Voce entrou com " << a << " e " << b << endl;
  
  // Troca a com b -- passa argumentos por referencia
  troca(a, b);
  
  cout << "Trocados, eles sao " << a << " e " << b << endl;
}

A saída deste programa é:

     Entre dois numeros: 3 5
     Voce entrou com  3 e 5
     Trocados, eles sao 5 e 3

Basicamente, se a função precisa alterar o valor de uma variável no ponto de chamada da função, então passamos o nome da variável como parâmetro real, e escrevemos a função indicando os parâmetros como sendo de SAìDA ou PASSADOS POR REFERÊNCIA.

12 O pré-processador

O pré-processador é um programa que faz alguns processamentos simples antes do compilador. Ele é executado automaticamente todas as vezes que seu programa é compilado, e os comandos a serem executados são dados através de diretivas do pré-processador.

Estas diretivas são colocadas em linhas que contém somente a diretiva (elas não são código da linguagem C++ , portanto as regras para elas são um pouco diferentes). As linhas que começam com um # são comandos para o pré-processador. A linha inteira é reservada para este comando (nenhum código C++ pode aparecer nesta linha e comandos do pré-processador não podem estar separados em diversas linhas).

12.1 A diretiva #define

Uma diretiva que é usada frequentemente é o #define. Esta diretiva é usada para fazer substituição de macros. Por enquanto, mostraremos uma utilização simples do #define, que é simplestemente uma substituição no texto.

O uso mais frequente desta diretiva é dar nomes simbólicos a uma constante (você já viu outra maneira de definir contantes que é colocar a palvavra const antes da definição de uma variável). Por exemplo, seria conveniente usar PI em seus programas ao invés de digitar 3.1415926535 toda hora. Como outro exemplo, se você quiser escrever um programa sobre estudantes de uma turma de 81 alunos, você poderia definir NUM_ALUNOS como 81. Assim, se o número de alunos mudar, você não precisaria modificar todo o seu programa onde o número de alunos (81) é utilizado, mas simplesmente alterar a diretiva #define. Estas duas diretivas são definidas da seguinte forma:

#define PI 3.1415926535

#define NUM_ALUNOS 81

Por convenção, nomes introduzidos por um #define são geralmente em letra maiúscula (e variáveis são em letra minúscula, ou uma mistura de letras minúsculas e maiúsculas). Assim, quando você vê um nome em um programa, você sabe se o nome refere-se a uma variável ou um nome definido por um #define.

Considere o seguinte programa exemplo que usa PI:

   #define PI        3.14159265

   int main()
   {
      double raio;

      cout << "Entre com o raio: ";
      cin >> raio;

      cout << "Circunferencia = " << 2.0 * PI * raio << endl;
   }

Lembre-se que o nome PI não é um nome de variável. Ele é um nome que o pré-processador substituirá pelo texto especificado pelo #define (mais ou menos da mesma forma que o comando pesquisa-e-substitui do editor de texto). O compilador nunca vê ou sabe sobre PI. O compilador vê o seguinte cout do programa acima depois do pré-processador ser executado:

      cout << "Circunferencia = " << 2.0 * 3.14159265 * raio << endl;

12.2 A diretiva #include

Agora imagine que estamos escrevendo uma biblioteca geométrica: um conjunto de funções para calcular a área de cilindros, cones, esferas. Se diferentes pessoal estão escrevendo cada uma das funções, eles provavelmente colocarão suas funções em diferentes arquivos. Mas todas as funções usam o numero $\pi$, e algumas outras constantes podem ser necessárias também. Ao invés de colocar o #define no início de cada arquivo, um único arquivo geom.h pode ser criado. Este arquivo conterá a linha

#define PI 3.14159265

Assim, se todos os arquivos de funções geométricas puderem enxergar geom.h, eles compartilharão as mesmas definições. é para isso que usamos a diretiva #include, para incluir em seu programa, informações que estão em outro arquivo. Estas diretivas geralmente estão no início do programa fonte, antes da definição de funções e varáveis. Por exemplo, a diretiva

#include "geom.h"

colocada nos arquivos fontes que contêm as funções geométricas fará com que todos eles usem o nome simbólico PI ao invés de 3.14159265. O fato do nome do arquivo estar em aspas significa que o arquivo geom.h está no mesmo diretório que os arquivos fontes (ao invés do diretório onde se encontram as bibliotecas padrão de C++ ).

A diretiva

#include <iostream>

é colocada no início do programa fonte para incluir informações (como protótipos de funções) que são necessários quando cout e cin são chamados dentro do programa. O arquivo entre < > está em algum diretório padrão conhecido pelo pré-processador. Este arquivo iostream é comum a todas as implementações da linguagem C++  e contém infomações necessárias para executar operações de entrada e saída da entrada e saída padrão (teclado e monitor).

12.3 Comentários

De um modo geral, o pré-processador dos compiladores existentes remove todos os comentários do arquivo fonte antes do programa ser compilado. Portanto, o compilador nunca vê realmente os comentários.

13 Vetores ou Arrays

Considere o seguinte programa. Este programa pede ao usuário notas de 4 estudantes, calcula a média e imprime as notas e a média.

   int main()
   {
      int nota0, nota1, nota2, nota3;
      int  media;

      cout << "Entre a nota do estudante 0: ";
      cin >> nota0;
      cout << "Entre a nota do estudante 1: ";
      cin >> nota1;
      cout << "Entre a nota do estudante 2: ";
      cin >> nota2;
      cout << "Entre a nota do estudante 3: ";
      cin >> nota3;

      media = (nota0 + nota1 + nota2 + nota3) / 4;

      cout << "Notas: " << nota0 << " " << nota1 << " " << nota2 << " " 
           << nota3 << endl;
      cout << "Media: " << media << endl;
  }

Este programa é bem simples, mas ele tem um problema. O que acontece se o número de estudantes aumentar ? O programa ficaria muito maior (e feio !!). Imagine o mesmo programa se existissem 100 estudantes.

O que precisamos é uma abstração de dados para agrupar dados relacionados. Este é o objetivo de arrays em C++ .

Um array é uma coleção de um ou mais objetos, do mesmo tipo, armazenados em endereços adjacentes de memória. Cada objeto é chamado de elemento do array. Da mesma forma que para variáveis simples, damos um nome ao array. O tamanho do array é o seu número de elementos. Cada elemento do array é numerado, usando um inteiro chamado de índice. Em C++ , a numeração começa com 0 e aumenta de um em um. Assim, o último índice é igual ao número de elementos do array menos um.

Por exemplo, podemos definir um array nota de tamanho 100 para armazenar as notas dos cem estudantes:

  int nota[100];
Quando o compilador encontra esta definição, ele aloca 200 bytes consecutivos de memória (dois bytes - referente a cada int - para cada nota). Cada nota pode ser acessada dando o nome do array e o índice entre colchetes: como nota[0] (para a primeira nota), nota[1] para a segunda nota, e assim por diantes, até a última nota, nota[99].

13.1 Definindo arrays e acessando seus elementos

A definição de arrays é muito parecida com a definição de variáveis. A única diferença é que em array é necessário especificar seu tamanho (quantos elementos ele tem).

Os colchetes [ e ] são usados na definição do tamanho, como mostra os exemplos a seguir:

        int total[5];

        float tamanho[42];

O primeiro exemplo é um array de 5 inteiros (o tipo int) com o nome total. Como a numeração de arrays começa com 0, os elementos da array são numerados 0, 1, 2, 3 e 4.

O segundo exemplo é um array de 42 elementos do tipo float com índices de 0 a 41.

Cada elemento do array total é do tipo inteiro e pode ser usado do mesmo jeito que qualquer variável inteira. Para nos referirmos a um elemento do array, usamos colchetes também ([ e ]). O valor dentro dos colchetes pode ser qualquer expressão do tipo inteiro. Quando um array é definido, armazenamento suficiente (bytes contínuos na memória) são alocados para conter todos os elementos do array.

Note na tabela de precedência abaixo que [ ] tem precedência maior que todos os demais operadores.

Operador Associatividade
   
() [] esquerda para direita
! - & direita para esquerda
* / % esquerda para direita
+ - esquerda para direita
< <= > >= esquerda para direita
== != esquerda para direita
&& esquerda para direita
|| esquerda para direita
= direita para esquerda
, esquerda para direita

Verifique se você entende as sentenças do programa abaixo.

         int i, x, sala, total[5];
         float area;
         float tamanho[42];
 
         x = total[3];

         i = 4;

         total[i] = total[i-1] + total[i-2];

         total[4] = total[4] + 1;

         tamanho[17] = 2.71828;

         sala = 3;

         area = tamanho[sala] * tamanho[sala];

         cin >> tamanho[41];

Agora, podermos reescrever o programa que calcula a média de uma classe de 4 alunos:

   int main()
   {
      int indice, nota[4];
      float total;

      indice = 0;
      while (indice < 4)
      {
         cout << "Entre a nota do estudante " << indice << ": ";
         cin >> nota[indice];
         indice = indice + 1;
      }

      cout << "Notas:  ";

      total = 0;
      indice = 0;
      while (indice < 4)
      {
         cout << nota[indice] << " ";
         total = total + nota[indice];
         indice = indice + 1;
      }
      cout << endl << "Media: " << total / 4 << endl;
   }

Exemplo de Saída:

   Entre a nota do estudante 0: 93
   Entre a nota do estudante 1: 85
   Entre a nota do estudante 2: 74
   Entre a nota do estudante 3: 100
   Notas:  93 85 74 100
   Media: 88

O código-fonte do programa é consideravelmente mais curto.

O único problema é que ainda não é fácil modificar o programa para cem alunos porque 4 está em vários pontos do programa. Nós podemos usar o #define para manter o tamanho do array como uma constante simbólica ao invés de utilizar uma constante numérica.

   #define ESTUDANTES 4

   int main()
   {
      int indice, nota[ESTUDANTES];
      float total;

      indice = 0;
      while (indice < ESTUDANTES)
      {
         cout << "Entre a nota do estudante " << indice << ": ";
         cin >> nota[indice];
         indice = indice + 1;
      }

      cout << "Notas:  ";

      total = 0;
      indice = 0;
      while (indice < ESTUDANTES)
      {
         cout << nota[indice] << " ";
         total = total + nota[indice];
         indice = indice + 1;
      }
      cout << endl << "Media: " << total / ESTUDANTES << endl;
   }

13.2 Inicialização de arrays

Os arrays podem ser inicializados quando são definidos. Se o array não for inicializado, então ele contem valores indefinidos (também conhecidos como lixo).

Para inicializar um array, um valor para cada elemento deve ser especificado. Estes valores devem estar entre chaves ({ e }) e são separados por vírgula (,). Alguns exemplos:

      int valor[4] = { 1, 42, -13, 273 };

      // o tamanho do array pode ser omitido 
      int peso[] = { 153, 135, 170 };

No primeiro exemplo, valor é um array de 4 inteiros onde valor[0] e' 1, valor[1] e' 42, valor[2] e' -13, e valor[3] e' 273.

Note que no segundo exemplo, o tamanho do array foi omitido. Neste caso, o compilador calcula o tamanho como sendo o número de elementos listados. Quando um array é definido, se ele não for inicializado, o tamanho do array deve ser especificado. Se o array for inicializado, o tamanho pode ser omitido. O segundo exemplo acima é equivalente a

      int peso[3] = { 153, 135, 170 };

Se o tamanho não for omitido, o número de elementos presentes não deve exceder o tamanho. Se exceder, o compilador gerará uma mensagem de erro. Se houver menos elementos na lista de inicialização, então os elementos dados são usados para inicializar os primeiros elementos do array. Qualquer elemento não inicializado conterá lixo.

Note que este tipo de inicialização só é válido no contexto onde o array é definido. Uma sentença como a seguinte produzirá um erro do compilador, uma vez que arrays só podem ser inicializados quando definidos.

      int erro[5];

      erro = { 2, 4, 6, 8, 10 };      // ISTO ESTA' ERRADO

Há mais uma restrição na inicialização de um array. Os valores devem ser todos constantes - nenhuma variável ou expressão é permitida. O seguinte trecho de programa produz um erro porque um dos valores de inicialização é uma variável:

      int x = 21;
      int yy[3] = { 1, 2, x };   // ISTO ESTA' ERRADO

13.3 Verificação de Limite

Quando um array é definido, é alocado espaço em memória para conter todos os elementos do array (não mais). O tamanho do array é dado explicitamente escrevendo o tamanho, ou implicitamente, inicializando o array. Embora arrays tenham tamanhos específicos, é possível que um programa tente acessar endereços de memória de elementos fictícios, ou seja, endereços de memória que não pertencem ao array. Isto acontece quando usamos um índice que não esteja entre 0 e n-1 para um array de tamanho n. O compilador não gera nenhum aviso quando isto acontece. Quando executamos um acesso ``fora dos limites'' do array, o resultado pode ser desastroso. Isto siginifica que o programa pode não fazer nada, cancelar a execução, travar o computador, entrar em um loop infinito, etc.

Se você executar uma atribuição a um elemento do array fora do seu limite, você estará escrevendo em um endereço de memória que pode conter algo importante, destruindo-o. Em geral, erros como estes são difíceis de encontrar, já que o programa pode até executar, só que faz algo ``estranho''. Se você estiver usando o Code::Blocks , você poderá ver uma mensagem como ``Esta aplicação violou a integridade do sistema devido a execução de uma instrução inválida e será cancelada.''. Não entre em pânico !! Você provavelmente terá que reinicializar o seu computador e examinar o seu programa cuidadosamente para achar acessos a array fora do seu limite. É claro que ao reinicializar o seu computador, você perderá todo o seu trabalho se não tiver salvado antes. MORAL: depois que seu programa compilar com sucesso, salve o seu programa em disco antes de executá-lo.

Por exemplo, considere o seguinte programa:

    #include <iostream>

    using namespace std;

    #define TAM 10

    int main()
    {
        int ops[TAM], i;

        // Acesso fora dos limites quando i == TAM
        i = 0;
        while (i <= TAM)
        {
            ops[i] = 0;
            i = i + 1;
        }
    }

Este programa inicializa cada elemento do array com 0. O problema ocorre quando i tem o valor 10. Neste ponto, o programa coloca 0 em ops[10]. Isto pode produzir resultados indefinidos (e desastrosos) embora o compilador não gere nenhum erro. O problema está na condição do while, que não deveria permitir que o corpo da repetição fosse executado quando i assumisse o valor 10.

13.4 Arrays como argumentos de funções

Para passar um array como argumento (com todos os seus elementos) de uma função passamos o nome do array. Considere o exemplo abaixo:

/usr/local/lib/tex/inputs///Programas/array_max.cpp

Aqui está um exemplo de execução deste programa

   Entre um inteiro: 73
   Entre um inteiro: 85
   Entre um inteiro: 42
   Entre um inteiro: -103
   Entre um inteiro: 15
   O maior e' 85

Em main() a chamada para array\undmax() tem valor como seu argumento, que é copiado para o parâmetro formal a, que é um array de inteiros. Note que o tamanho não foi especificado, somente o nome do array, a. Porém é também correto incluir o tamanho (isto é uma questão de estilo - escolha o que você preferir):

   int array_max(int a[TAMANHO])
   {
     ...
   }

A inclusão do tamanho de um array unidimensional na definição da função é somente por razões de legibilidade.

Até este ponto, parece que não há diferença entre passar uma variável simples e um array como argumento para uma função. Mas há uma diferença fundamental: QUANDO DEFINIMOS UM ARRAY COMO ARGUMENTO FORMAL, ALTERAÇÕES NO ARRAY FEITAS DENTRO DA FUNÇÃO ALTERAM O CONTEÚDO DO ARRAY PASSADO COMO PARÂMETRO REAL NA CHAMDA DA FUNÇÃO. EM OUTRAS PALAVRAS, QUANDO SÃO PARÂMETROS DE FUNÇÕES, ARRAYS SÃO PASSADOS POR REFERÊNCIA (sem a necessidade do & na definição do parâmetro formal, como foi visto na Seção 11).

Para ilustrar este conceito, considere o exemplo seguinte:

/usr/local/lib/tex/inputs///Programas/troca_01.cpp

A saída deste programa é:

x=10 
v[0]=60 v[1]=70 v[2]=80

O valor da variável x do programa principal não se altera porque como já vimos nas notas de aula 7, quando a função troca é chamada, o valor do argumento real x é avaliado, que é 10, este valor é copiado para o parâmetro formal a da função troca e a função então é executada. O parâmetro a da função é tratada como variável local, portanto quando atribuímos 20 a a, estamos atribuindo 20 a uma variável local. Terminada a função, a execução retorna ao programa principal, que imprime o valor de x, que não foi alterado, ou seja, imprime x=10.

Quando a função troca_vet é chamada, o array v é passado como argumento e ``copiado'' para o parâmetro formal vet. A função é então executada, e os elementos do array são alterados para 60, 70, 80. Como mencionado anteriormente, quando passamos um array como parâmetro, as alterações feitas no array dentro da função alteram o array passado como parâmetro. Portanto, quando a função termina e a execução continua no programa principal com a impressão dos valores dos elementos de v, será impresso 60, 70, 80, os novos valores alterados de dentro da função troca_vet.

Vamos entender por que quando passamos só o nome do array como argumento as alterações afetam o array passado como parâmetro real. Como já mencionamos anteriormente, quando um array é definido, como v no programa principal acima, é alocado espaço suficiente na memória para conter todos os elementos do array. Na ilustração abaixo, são alocados 6 bytes de memória a partir do endereço 1342 para conter o array. O array como um todo não tem um valor, mas cada elemento do array tem (neste caso, foram inicializados com 30, 40, 50). O nome do array, na verdade, contém o endereço onde começa o array, neste caso, o endereço 1342.

Portanto, quando passamos o nome do array como argumento para uma função estamos na realidade passando como argumento o endereço de memória onde começa o array. No exemplo anterior, 1342 é passado como argumento para o parâmetro formal vet da função troca_vet. Portanto, da mesma forma que no caso da variável simples, o valor de v, que é o endereço 1342, é copiado para o parâmetro vet de troca_vet. Então, quando a função troca_vet é executada, vet é um array de elementos do tipo int que começa no endereço 1342. Quando atribuímos o valor 60 a vet[0], estamos atribuindo 60 ao primeiro elemento do array que começa no endereço 1342. Como este é o mesmo endereço onde começa o array v do programa principal, quando a função troca_vet termina, o array v ``enxergará'' o valor dos elementos do array que começa no endereço 1342, que foram alterados pela função.

\includegraphics[scale=0.5]{ptrarr}

Quando passamos variáveis simples como argumento para uma função estamos passando somente o valor da variável, portanto, de dentro da função não é possível saber qual o endereço da variável para poder alterá-la.

Lembre-se que o endereço só é passado para a função quando passamos o array COMO UM TODO (ou seja, o nome do array, sem ser indexado por um elemento). Se passarmos como argumento apenas um elemento do array, o comportamento é o mesmo que se passássemos uma variável simples. Ou seja, o nome do array indexado por um valor entre colchetes refere-se ao valor do elemento do array, enquanto o nome do array sozinho refere-se ao endereço onde começa o array. Assim, no programa abaixo:

/usr/local/lib/tex/inputs///Programas/troca_02.cpp

A saída do programa é:

v[0]=30 
v[0]=60 v[1]=70 v[2]=80

Outro exemplo: a função inicializaArray abaixo inicializa todos os elementos do array valor com um valor passado como argumento pelo programa principal.

/usr/local/lib/tex/inputs///Programas/inicializa_array.cpp

Como as alterações feitas por inicializaArray são vistas do programa principal, depois da função inicializaArray ser executada, no programa principal todos os elementos do array valor terão o valor 42.

13.5 Exemplo: pesquisa linear de um array

Pesquisar (procurar) em um array um determinado valor (chamado de chave) é um problema muito comum em programação. Ele tem diversas aplicações. Por exemplo, podemos pesquisar um array de notas para verificar se algum aluno tirou 100 na prova. Há diversos algoritmos de pesquisa: cada um com suas vantagens e desvantagens. Nestas notas de aula, discutiremos um algoritmo simples, chamado de pesquisa linear. A pesquisa é feita usando uma repetição e examinando cada elemento do array a cada repetição e comparando o elemento com a chave que buscamos. A pesquisa termina quando um elemento do array que ``casa'' com a chave é encontrada, ou quando o array todo é percorrido e a chave procurada não é encontrada.

13.5.1 O Problema

Escreva uma função pesquisa\undlinear que tem como argumento de entrada: um array de inteiros a ser pesquisado, o tamanho do array, e uma chave (um valor inteiro) a ser procurado. A função retorna um inteiro: o índice do elemento do array (se a chave for achada) ou -1 caso contrário.
  1. Protótipo:
    int pesquisa_linear(int [], int, int);
    
  2. Definição:
    /* Procura uma chave em um array
     * entrada: array a ser pesquisado (arr ), tamanho do array (tam),
     *          chave a ser procurada (chave)
     * saida: o indice do elemento que e' igual a chave ou -1 caso nao ache
     * suposicao: nao assume que o array esteja ordenado
     */
    int pesquisa_linear(int arr[], int tam, int chave)
    {
      int i;    
    
      i = tamanho - 1;
      while (i >= 0)
      {
          if (arr[i] == chave)
          {
             return i;
          }
    
          i = i - 1;
      }
      return -1;
    }
    

13.6 Exemplo: somar os elementos de dois arrays

13.6.1 O Problema

Escrever uma função que some dois arrays de floats, do mesmo tamanho. Dar o resultado em um terceiro array. O tamanho dos arrays é também passado para a função.
  1. Protótipo:
      void soma_array( float [], float [], float [], int );
    

  2. Definição de soma\undarray():

      void soma_array( float arr1[], float arr2[], float arr3[], int tam )
      {
         int i;
    
         i = 0
         while ( i < tam )
         {
             arr3[i] = arr1[i] + arr2[i];
             i = i + 1;
         }
      }
    

13.7 Exemplo: Ordenação de um vetor - Versão 1

Um outro programa muito popular com arrays é ordená-lo de acordo com algum critério. Por exemplo, um array de inteiros pode ser ordenado em ordem crescente ou decrescente. O apresentado a seguir é um algorítmo básico e nem um pouco eficiente, denominado Select sort.

Ele usa o fato simples de comparar cada elemento de um array com o restante deste. Quando se acha o menor, ocorre uma troca de valores entre o elemento sob análise e o outro elemento do array que é o menor.

Por exemplo, se começarmos com um array: 9 5 2 7 3 8 1 4 6, (o primeiro elemento é 9 e o último elemento é 6) isto é o que acontece com os elementos do array depois de cada passagem sobre ele (e consequente troca de valores):

passagem   conteudo do array depois da passagem
~~~~       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 -->       9  5  2  7  3  8  1  4  6

1 -->       1  5  2  7  3  8  9  4  6

2 -->       1  2  5  7  3  8  9  4  6

3 -->       1  2  3  7  5  8  9  4  6

4 -->       1  2  3  4  5  8  9  7  6

5 -->       1  2  3  4  5  8  9  7  6

6 -->       1  2  3  4  5  6  9  7  8

7 -->       1  2  3  4  5  6  7  9  8

8 -->       1  2  3  4  5  6  7  8  9

Note que mesmo que se começássemos com um array ordenado de 9 elementos, ainda assim o algoritmo dado faz 8 passagens sobre o array.

13.7.1 Protótipo da função e definição

  1. Protótipo
    void selectSort(int [], int);
    
  2. Definicao
    /usr/local/lib/tex/inputs///Programas/ordena_array_02.cpp
    

13.8 Exemplo: Ordenação de um vetor - Versão 2

O algoritmo abaixo é ligeiramente melhor que o anterior e é chamado Bubble sort. Ele é bastante simples, porém ainda não muito eficiente.

Basicamente, o algoritmo funciona da seguinte forma:

Por exemplo, se começarmos com um array: 9 5 2 7 3 8 1 4 6, (o primeiro elemento é 9 e o último elemento é 1) isto é o que acontece com os elementos do array depois de cada passagem sobre ele (e troca de valores adjacentes):

passagem   conteudo do array depois da passagem
~~~~       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 -->       9  5  2  7  3  8  1  4  6

1 -->       1  9  5  2  7  3  8  4  6

2 -->       1  2  9  5  3  7  4  8  6 

3 -->       1  2  3  9  5  4  7  6  8 

4 -->       1  2  3  4  9  5  6  7  8 

5 -->       1  2  3  4  5  9  6  7  8 

6 -->       1  2  3  4  5  6  9  7  8 

7 -->       1  2  3  4  5  6  7  9  8 

8 -->       1  2  3  4  5  6  7  8  9

Note que, também aqui, mesmo que se começássemos com um array ordenado de 9 elementos, ainda assim o algoritmo dado faz 8 passagens sobre o array.

Isto pode ser melhorado da seguinte forma: Antes de começar cada passagem, inicializamos uma variável ordenado com 1. Se durante a passagem uma troca de valores ocorrer, trocamos o valor da variável para 0. Assim, se depois da passagem, o valor da variável continuar sendo 1, isso significa que nenhuma troca ocorreu e que o array está ordenado.

13.8.1 Algoritmo Bubble Sort otimizado

Enquanto o array nao estiver ordenado
  1. inicializar ordenado com 1
  2. comparar pares adjacentes do array
    troque seus valores se estiver fora de ordem
    ordenado = 0.

13.8.2 Protótipo da função e definição

  1. Protótipo
    void bubbleSort(int [], int);
    
  2. Definicao
    /usr/local/lib/tex/inputs///Programas/ordena_array_01.cpp
    

13.9 Comentários Finais

Neste curso, um dos únicos lugares que veremos o nome do array sem estar indexado é quando passamos o array (como um todo) para uma função. Para outras finalidades, veremos sempre o array indexado. Por exemplo, o seguinte trecho de programa está errado:

  int main(){
    int arr1[4] = {10, 20, 30, 40};
    int arr2[4];

    arr2 = arr1;       // ERRADO: NÃO copia arr1 em arr2 
                       // tem que copiar elemento por elemento 

    if( arr1 == arr2 ) // ERRADO: NÃO podemos comparar arrays inteiros  
      cout << "X";     // tem que comparar elemento por elemento 
}

14 Matrizes ou Arrays Multidimensionais

Nas notas de aula anteriores, apresentamos arrays unidimensionais. Em C++ , é possível também definir arrays com 2 ou mais dimensões. Eles são arrays de arrays. Um array de duas dimensões podem ser imaginado como uma matriz (ou uma tabela).

Como você deve ter imaginado, para definir e acessar arrays de dimensões maiores, usamos colchetes adicionais ([ e ]). Por exemplo:

      int tabela[3][5];

Define um array bidimensional chamado tabela que é uma matriz 3 por 5 de valores do tipo int (15 valores no total). Os índices da primeira dimensão vão de 0 a 2, e os índices da segunda dimensão vão de 0 a 4.

Abaixo apresentamos um programa que imprime os elementos de um array bidimensional.

#include <iostream>
using namespace std;

#define ALTURA 5
#define LARGURA 5

int main()
{
   int x;                            //  numero da coluna 
   int y;                            //  numero da linha 
   char matriz [ALTURA] [LARGURA];   //  array 2-D [num_lins, num_cols] 
 

   //  preenche a matriz com zeros  

   y = 0;
   while(y < ALTURA)
   {
     x = 0;
     while(x < LARGURA)
     {
        matriz[y][x] = 0;
        x= x + 1;
     }
     y= y + 1;
   }

   //  Imprime a matriz com zeros e a coordenada escolhida com 1

   cout << endl << "Entre coordenadas na forma \"y x\"." << endl;
   cout << "Use valores negativos para sair do programa." << endl;

   cout << "Coordenadas: ";
   cin >> y >> x;

   while (x >= 0 && y >= 0)
   {
      matriz[y][x] = 1;      //  coloca 1 no elemento escolhido 

      y = 0;
      while (y < ALTURA)     // imprime o array todo
      {
        x = 0;
        while (x < LARGURA)
        {
             cout << matriz[y][x] << " ";
             x = x + 1;
        }
        cout << endl << endl;
        y = y + 1;
      }

      cout << endl;
      cout << "Coordenadas: ";
      cin >> y >> x;
   }
}
Neste exemplo, matriz é um array bidimensional. Ela tem número de elementos igual a ALTURAxLARGURA, sendo cada elemento do tipo int.

O exemplo abaixo preenche os elementos de um array bidimensional com os valores que representam a taboada e imprime a matriz.

//  Exemplo de array 2-D - taboada 

#include <iostream>
#include <iomanip>

using namespace std;

#define LIN 10
#define COL 10

int main()
{
  int x;                          //  numero da coluna 
  int y;                          //  numero da linha  
  int tabela[LIN] [COL];          //  tabela de taboada  
  
  //  preenche a tabela 
 
  y = 0;
  while (y < LIN)
  {
    x = 0;
    while (x < COL)
    {
      tabela[y][x] = y*x;
      x = x + 1;
    }
    y = y + 1;
  }

  cout << endl << "         Tabela de Multiplicacao" << endl;
  
  //  Imprime o numero das colunas 
  
  cout << setw(6) << 0;
  x = 1;
  while (x < COL)
  {
    cout << setw(3) << x;
    x = x + 1;
  }

  cout << endl;
  
  //  Imprime uma linha horizontal 
  cout << "   ";
  x = 0;
  while (x < 3*COL)
  {
    cout << "-";
    x = x + 1;
  }

  cout << endl;
  
  //  Imprime as linhas da tablea.
  //  Cada linha a precedida pelo indice de linha e uma barra vertical 
  
  y = 0;
  while (y < LIN)
  {
    cout << setw(2) << y << "|";
    x = 0;
    while (x < COL)
    {
      cout << setw(3) << tabela[y][x];
      x = x + 1;
    }
    cout << endl;
    y = y + 1;
  }
}

A saída do programa é:

         Tabela de Multiplicacao
     0  1  2  3  4  5  6  7  8  9
   ------------------------------
 0|  0  0  0  0  0  0  0  0  0  0
 1|  0  1  2  3  4  5  6  7  8  9
 2|  0  2  4  6  8 10 12 14 16 18
 3|  0  3  6  9 12 15 18 21 24 27
 4|  0  4  8 12 16 20 24 28 32 36
 5|  0  5 10 15 20 25 30 35 40 45
 6|  0  6 12 18 24 30 36 42 48 54
 7|  0  7 14 21 28 35 42 49 56 63
 8|  0  8 16 24 32 40 48 56 64 72
 9|  0  9 18 27 36 45 54 63 72 81

14.1 Inicialização

Arrays multidimensionais podem ser inicializados usando listas aninhadas de elementos entre chaves. Por exemplo, um array bidimensional tabela com três linhas e duas colunas pode ser inicializado da seguinte forma:
double tabela[3][2] = { {1.0,  0.0},      //  linha 0 
                        {-7.8, 1.3},      //  linha 1 
                        {6.5,  0.0}       //  linha 2 
                      };
Quando o array é inicializado, o tamanho da primeira dimensão pode ser omitido. A definição de array abaixo é equivalente a dada anteriormente.
double tabela[][2] = { {1.0,  0.0},      //  linha 0 
                       {-7.8, 1.3},      //  linha 1 
                       {6.5,  0.0}       //  linha 2 
                     };

14.2 Arrays Multidimensionais - arrays de arrays

O formato da definição de um array de dimensão $k$, onde o número de elementos em cada dimensão é $n_0, n_1, \ldots,
n_{k-1}$, respectivamente, é:

\begin{displaymath}
nome\verb+_+tipo \ nome\verb+_+array[n_0][n_1]...[n_{k-1}];
\end{displaymath}

Isto define um array chamado $nome\_array$ consistindo de um total de $n_0\times n_1\times\ldots\times n_{k-1}$ elementos, sendo cada elemento do tipo $nome\verb+_+tipo$.

Arrays multidimensionais são armazenados de forma que o último subscrito varia mais rapidamente. Por exemplo, os elementos do array

int tabela[2][3];
são armazenados (em endereços consecutivos de memória) como
tabela[0][0], tabela[0][1], tabela[0][2], tabela[1][0], tabela[1][1], tabela[1][2].
Um array de dimensão k, onde o número de elementos em cada dimensão é $n_0,n_1,\ldots,\ldots,n_{k-1}$, respectivamente, pode ser imaginado como um array de dimensão $n_0$ cujos elementos são arrays de dimensão $k-1$.

Por exemplo, o array bidimensional tabela, com 20 elementos do tipo int

int tabela[4][5] = { {13, 15, 17, 19, 21},
                     {20, 22, 24, 26, 28},
                     {31, 33, 35, 37, 39},
                     {40, 42, 44, 46, 48} };
pode ser imaginado como um array unidimensional de 4 elementos do tipo int[], ou seja, arrays de int; cada um dos 4 elementos é um array de 5 elementos do tipo int:
tabela[0]  ---> {13, 15, 17, 19, 21}
tabela[1]  ---> {20, 22, 24, 26, 28}
tabela[2]  ---> {31, 33, 35, 37, 39}
tabela[3]  ---> {40, 42, 44, 46, 48}

14.3 Arrays Multidimensionais como argumento para funções

Quando o parâmetro formal de uma função é um array multidimensional (um array com dimensão maior que um), todas as dimensões deste array, exceto a primeira, precisa ser explicitamente especificada no cabeçalho e protótipo da função.

\begin{displaymath}
tipo\verb+_+do\verb+_+resultado \ \, nome\verb+_+da\verb+_+f...
...\,nome\verb+_+do\verb+_+array[\ ][n_1]...[n_{k-1}],... ...\, )
\end{displaymath}

Quando uma função com um parâmetro formal do tipo array é chamada, na chamada da função somente o nome do array é passado como parâmetro real. O tipo (e portanto a dimensão) do array passado como parâmetro real deve ser consistente com o tipo (e portanto a dimensão) do array que é o parâmetro formal. O programa abaixo mostra o exemplo da tabela de multiplicação escrita usando funções.

//  Exemplo de array 2-D - tabela de multiplicacao 

#include <iostream>
#include <iomanip>
using namespace std;

#define LIN 10
#define COL 10

//  Inicializa o array com a tabela de multiplicacao 

void inicializa_arr (int arr[][COL])
{
  int x;                          //  numero da coluna 
  int y;                          //  numero da linha     
  
  //  preenche o array 
  
  y=0;
  while (y < LIN)
  {
    x=0;
    while(x < COL)
    {
      arr[y][x] = y*x;
      x = x + 1;
    }
    y = y + 1;
  }
}

//  imprime um array LIN x COL 

void imprime_arr(int arr[][COL])
{
  int x;                          //  numero da coluna 
  int y;                          //  numero da linha     
  
  //  imprime o numero das colunas 
  
  cout << setw(6) << 0;
  x = 1;
  while (x < COL)
  {
    cout << setw(3) << x;
    x = x + 1;
  }

  cout << endl;

  //  imprime uma linha horizontal 
  cout << "   ";
  x = 0;
  while (x < 3*COL)
  {
    cout << "-";
    x = x + 1;
  }

  cout << endl;
  
  //  imprime as linhas do array.  cada linha e' precedida
  //  pelo numero da linha e uma barra vertical 
  
  y = 0;
  while (y < LIN)
  {
    cout << setw(2) << y << "|";
    x = 0;
    while (x < COL)
    {
      cout << setw(3) << arr[y][x];
      x = x + 1;
    }
    cout << endl;
    y = y + 1;
  }
}

int main()
{
  int  tabela[LIN] [COL];   
  
  inicializa_arr(tabela);
  
  cout << endl << "         Tabela de Multiplicacao" << endl;
  
  imprime_arr(tabela);
}

Outro exemplo com funçoes de manipulação de arrays bidimensionais:

//  funcoes com argumentos tipo array 2-D 

#include <iostream>
using namespace std;

#define ALTURA 7
#define LARGURA 7

// *** DEFINICAO DE FUNCOES *******

//  funcao que imprime um array 2-D ALTURA X LARGURA 
void imprime_matriz(char matriz[][LARGURA])
{
  int x,y;
  
  y = 0;
  while (y < ALTURA)
  {
    x = 0;
    while (x < LARGURA)
    {
      cout <<  matriz[y][x] << " ";
      x = x + 1;
    }

    cout << endl << endl;
    y = y + 1;
  }

  cout << endl;
}

//  funcao que preenche uma matriz ALTURA X LARGURA com pontos 
void pontos( char matriz[][LARGURA])
{
  int x,y;

  y = 0;
  while (y < ALTURA)
  {
    x = 0;
    while (x<LARGURA)
    {
      matriz[y][x] = '.';
      x = x + 1;
    }
    y = y + 1;
  }
}

/*  funcao que preenche os elementos selecionados da matriz com um
 * quadrado e imprime a matriz
 */
void seleciona_elem(char matriz[][LARGURA])
{
  int x, y;
  
  cout << endl << "Entre com as coordenadas na forma \"y x\"." << endl;
  cout << "Use numeros negativos para terminar." << endl;
  
  cout << "Coordenadas: ";
  cin >> y >> x;    
  while (x >= 0 && y >= 0) 
  {
    matriz[y][x]='@';        // preenche o elemento com quadrado
    imprime_matriz(matriz);   // imprime a matriz
    cout << "Coordenadas: ";
    cin >> y >> x;    
  }
}

/*  funcao que marca todos os elementos abaixo da diagonal principal de
 * um array ALTURA X LARGURA com quadrados
 */
void marca_triang(char matriz[][LARGURA])
{
  int x, y;
  
  cout << "Triangulo" << endl;
  pontos(matriz);

  y = 0;
  while (y < ALTURA)
  {
    x = 0;
    while (x <= y)
    {
      matriz[y][x] = '@';
      x = x + 1;
    }
    y = y + 1;
  }
}

//  funcao que rotaciona  ('flip') cada linha array tendo  o elemento da
//  diagonal principal como centro da rotação
void flip(char matriz[][LARGURA])
{
  int x, y;
  int temp;
  
  cout << "Flip ao longo da diagonal principal." << endl;
  y = 0;
  while (y < ALTURA)
  {
    x = 0;
    while (x <= y)
    {
      temp = matriz[y][x];
      matriz[y][x] = matriz[x][y];
      matriz[x][y] = temp;
      x = x + 1;
    }
    y = y + 1;
  }
}

//  funcao que espera ate que uma tecla qualquer seja digitada 
void pausar() {
  char c;
  cin.get(c);
}

// ********* MAIN ***********

//  alguns exemplos de chamadas de funcoes com argumentos array 2-D 
int main()
{
  char matriz [ALTURA] [LARGURA];
  
  pontos(matriz);
  seleciona_elem(matriz);
  pausar();
  
  flip(matriz);
  imprime_matriz(matriz);
  pausar();
  
  marca_triang( matriz);
  imprime_matriz( matriz);
  pausar();
  
  flip( matriz);
  imprime_matriz(matriz);
  pausar();
}



Notas de rodapé

... funções1
Na verdade, um programa C++ é composto pela definição de funções e de elementos estruturais denominados classes. Estes são tema de estudo em cursos avançados de programação orientada a objetos.
... cin2
cout e cin são na verdade objetos das classes ostream e istream. Mas este detalhe não é abordado nestas notas de aula. Será visto apenas o uso destes objetos como primitivas simples para Entrada e Saída de dados.
... lógicos3
Operadores lógicos && e || serão vistos na próxima aula.


Subsecções
Armando Luiz Nicolini Delgado
2013-10-21