Professional Documents
Culture Documents
00 1 1 011 01 1 00 0
1
1
1 1
000 0 11 1111 1 10 1 010
1 0 1
1 01 1
1
1
1
11 11 0 10 0
0 00 0 01 1 10
1 0 0 1 01 1
0 1 1
0 00 0 1 0 01 0 0 110 0 0
01 1
1 11
00 0 1 101 1 1 0
10
0 1
1 0 1
0 1 0 0 1 1 1 1
11 11 11 0 01
10
0
1 0 0
01
10 0
0
1
00
1
0 11
0
1 0 0
111 0 0 11 1
1 1 0 1 10 10 1 1
1
0
1
1
10 1 110
1Introduo1 1 Programao
11 0
0 0 1
00 0 0 11 0 1 01 0
0
1
0
11 0 1 10Curso 1 em0C++
0 1 1 00
0
10 0 0
1
10
1
1
1 1
0
1
1 1 1
0
Dr. Alan
1
1
10
R. R. de Freitas
1
0
0
00 1
11
0 0
11
0
1
0
11 11 1 1 1
0 0
0 0 01
0 0 0 0 1
0 1 0 0
1 1 0 1 1 1
0 10 0 0 110 1 1
1 0 1
000
1
1
0
01 0 0 00 1 0 1 01 01
0 1
0 000
0
1 1
01 0 11
0 0 1 1 0 10 0
0 0 0 0
0 1 01 1 1 11 0 10 1 0 0
1 1
1
01 0 0 101 11 0 11
0 1 1
1
0
0 1
Copyright
c 2015 Alan R. R. de Freitas
ALANDEFREITAS . COM
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
00
1
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 11
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 1
00
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 00
0
Contedo
1
1
0
1
0
0 0
1
110 0 1
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 01 0 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
10
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 00
0 1 010 1 1 1 0 01 1 11
I Programao Estruturada
1 Introduo ao C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.1 Porqu Programar 13
1.2 Linguagem C++ 13
1.3 Compilador 14
1.4 Tipos Fundamentais de Dados 14
2 Estruturas sequenciais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.1 Primeiros programas 17
2.2 Segundo programa em C++ e espao de nomes padro 18
2.3 Comentrios 18
2.4 Criando variveis 19
3 Operadores bsicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.1 Operador de atribuio 21
3.2 Fluxo de entrada 22
3.2.1 Alteraes possveis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
3.3 Operadores aritmticos 25
3.4 Operadores de atribuio aritmticos 27
3.5 Operadores de incremento e decremento 30
3.6 Dados lgicos e comparaes 34
3.7 Operadores relacionais 34
3.8 Operadores lgicos 37
3.8.1 Resumo dos operadores lgicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.8.2 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.9 Exerccios 41
4 Estruturas condicionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.1 Condicional se 43
4.1.1 Sintaxe do se . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.1.2 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.2 Se-seno 46
4.2.1 Se-seno sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.2.2 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.3 Se-seno aninhados 47
4.3.1 Se-seno alinhados- sintaxe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.3.2 Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.4 Outros operadores condicionais 50
4.4.1 Operador ternrio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.4.2 Switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.5 Exerccios 51
6 Estruturas de repetio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.1 Laos com contadores 69
6.1.1 Controle de fluxo-For . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.1.2 Repetio controlada por contador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.2 Repeties determinadas pelo usurio 72
6.3 Repetio de processos similares 73
6.4 Alterando variveis externas 75
6.5 Aninhando estruturas de controles 77
6.6 Condies de inicializao 78
6.7 Condies de incremento 79
6.8 Percorrer arranjos 82
6.9 Laos aninhados 86
6.10 Percorrendo arranjos de arranjos 88
6.11 Utilizando apenas critrio de parada 93
6.11.1 Relao entre while e for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
6.11.2 Laos infinitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
6.12 Adiando o critrio de parada 96
6.12.1 Relao entre do-while e while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6.13 Exerccios 97
8 Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
8.1 Expresses com ponteiros 113
8.2 Aritmtica com ponteiros 113
8.3 Ponteiros para ponteiros 113
8.4 Ponteiros e arranjos 115
8.5 Exerccios 119
10 Estruturas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
10.1 Arranjos de estruturas 155
10.2 Estruturas e ponteiros 157
II Comparando Algoritmos
1 Introduo ao C++ . . . . . . . . . . . . . . . . . . 13
2 Estruturas sequenciais . . . . . . . . . . . . . . . 17
3 Operadores bsicos . . . . . . . . . . . . . . . . . 21
4 Estruturas condicionais . . . . . . . . . . . . . . 43
6 Estruturas de repetio . . . . . . . . . . . . . . 69
8 Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
10 Estruturas . . . . . . . . . . . . . . . . . . . . . . . . . . 153
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 1.0Introduo
0
1 0
1
0 ao 0C++
1
0
0 0
1
110 1
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 01 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
ingls, para C++. Uma novidade importante da linguagem C++ que ela contm uma biblioteca
padro de recursos que tambm foi sendo melhorada ao longo do tempo.
Uma caracterstica importante do C++ ser considerada de mdio nvel. Isso que dizer que
ela no to distante da linguagem da mquina, a lngua dos computadores. Ao mesmo tempo
ela tambm no to distante de uma linguagem com um alto nvel de abstrao, a tornando
acessvel a ns humanos.
Estas caractersticas tornam o C++ uma linguagem altamente eficiente e a fazem uma das
linguagens comerciais mais populares atualmente.
1.3 Compilador
Como faremos para conversar com computadores se falamos lnguas to diferentes? Esta a
tarefa de um programa de computador chamado compilador.
O compilador uma ferramenta que nos serve como uma ponte para fazer esta comunicao.
De modo breve, a comunicao funciona da seguinte maneira:
1. Escreveremos nossa mensagem para o computador em uma linguagem de programao
preferncial. Neste curso, utilizaremos a linguagem C++.
2. O compilador, por sua vez, recebe esse cdigo que , ento, transformado em linguagem
de mquina.
3. O novo cdigo em linguagem de mquina pode ser utilizado diretamente pelo computador,
finalizando assim nossa comunicao.
O tipo lgico, que possui bool como palavra chave, representa valores que so verdadeiros
ou falsos. O verdadeiro representado pela palavra-chave true e o falso representado pela
palavra-chave false.
Os nmeros inteiros, possuem short, int e long como palavras-chave. short utilizado
para nmeros inteiros menores, int, para nmeros inteiros que podem ser maiores e long, para
1.4 Tipos Fundamentais de Dados 15
nmeros que podem ser ainda maiores. Estes tipos de dados podem representar valores como 7,
-5, -1, 0, +3, -2, ou 10.
J os nmeros reais possuem as palavras-chave float e double. Da mesma maneira,
ambas as palavras indicam a capacidade do nmero. Exemplos desse tipo de dados so nmeros
como 3.32, 4.78, 7.24, -3.14 ou 0.01.
Por ltimo, temos os caracteres, que podem representar letras ou smbolos. Eles possuem
char como palavra-chave, e servem para representar dados como: h, @, C, r, e, f, #, $ ou }.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 2.0Estruturas
0
1 0 sequenciais
1
0 0 1
0
01 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Alm desta frase, uma quebra de linha tambm enviada para cout. Essa quebra de linha
representada pelo comando endl, que tambm pertence ao espao de nomes std.
Todos os comandos em C++ devem ser acompanhados de um ponto e vrgula indicando a
onde termina aquele comando.
O comando return (retornar, em ingls), indica que a funo main retorna daquele ponto,
finalizando assim o programa. Mas esse comando no s encerra o programa como tambm re-
torna um valor do tipo int, que no caso um 0. Este 0 utilizado usualmente por programadores
para indicar que o programa encerrou sem erros.
O comando int, antes da definio main() na linha 3, indica que a prpria funo main ir
retornar um int. Discutiremos o significado desse comando em lies futuras sobre funes.
Lembre-se que a funo main obrigatria em todos os programas.
Este o resultado do programa acima:
Ol, Mundo!
Ol, Mundo!
2.3 Comentrios
Em alguns casos, um cdigo pode se tornar to complexo que nem o prprio programador
consegue entend-lo. Em casos como este, bom deixar comentrios para outros programadores
ou mesmo para si mesmo.
No se preocupe, pois os comentrios so partes do cdigo que so ignoradas pelo compilador
e servem apenas para deixar lembretes ou informaes sobre o cdigo para outros programadores.
O comando usado para marcar o incio de um comentrio de uma linha so duas barras para a
direita (//). J os comandos barra-asterisco (/* e asterisco-barra (*/) so utilizados para iniciar
e encerrar um bloco de comentrios, respectivamente. Veja o exemplo abaixo:
2.4 Criando variveis 19
Ol, Mundo!
Como a linha 7 toda um comentrio, ela totalmente ignorada. Passamos ento direto para
a linha 8 do cdigo, que imprime novamente uma mensagem:
Temos em seguida nas linhas 9 e 10, todo um bloco de comentrios entre os comandos /* e
*/ que tambm ignorado. A linha 11 tambm um comentrio de uma linha inteira feito com
o comando //. O programa retorna da linha 12 com o comando return 0.
4 5.7
7.2 a
varivel
c true
false 20
Figura 2.1: Uma varivel um espao alocado na memria do computador para guardarmos
dados
11 double num_real ;
12 // criando uma vari vel do tipo bool com nome teste
13 bool teste ;
14 cout << " Este programa criou 4 vari veis ! " << endl ;
15 return 0;
16 }
Neste exemplo, temos um programa onde 4 variveis so criadas. A primeira delas, criada
na linha 5, ir guardar um nmero inteiro (int) e seu nome identificador numero. Note que o
nome que identifica a varivel numero no contm acentos.
Na linha 9 criamos uma varivel chamada letra, para dados do tipo char. Na linha 7, uma
varivel do tipo double, que pode armazenar nmeros reais. Por fim, na linha 8, uma varivel
do tipo bool, que pode guardar dados lgicos. Lembre-se dos tipos de dados apresentados na
Seo 1.4.
Por enquanto, nosso programa no utilizou as variveis criadas para armazenar dados. Por
isto elas so representadas por caixas que ainda esto fazias, j que nenhum valor foi definido
para as variveis. Aps a linha 13 do cdigo isto seria representado como na Figura 2.2.
Finalmente na prxima linha, este programa imprime uma mensagem simples, como j temos
feito at agora.
Os identificadores so usados para dar nomes s variveis ou funes que criamos. Estudare-
mos as funes mais adiante. Neste programa criamos variveis com os identificadores numero,
letra, num_real e teste.
Exitem algumas regras para identificadores:
O primeiro caractere de um identificador deve ser uma letra ou sinal de sublinha (_). Isto
quer dizer que nomes como 8nome ou @nome no so vlidos.
As letras minsculas e maisculas tambm so identificadas de formas diferentes. Ou seja,
para o C++, uma varivel x minsculo diferente de uma varivel X maisculo.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 3.0Operadores
0
1
00 1
0 01
bsicos 1
1
0
0
1
110 1
0
0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Os operadores so muito importantes em programao, pois eles alteram o valor das variveis
ou usam variveis existentes para criarem novas, como apresentado na Figura 3.2.
variavel3 9 variavel3 10
Figura 3.1: Exemplos de operadores. Operadores podem alterar o valor de uma varivel ou criar
novas variveis a partir de variveis existentes.
6 int numero ;
7 numero = 4;
8 cout << " A vari vel numero do tipo int tem valor " ;
9 cout << numero << endl ;
10 return 0;
11 }
Logo no incio do cdigo, na linha 6, criamos uma varivel chamada numero, para dados do
tipo int. Na linha 7, esta varivel recebe o valor 4 atravs do operador de atribuio. Assim
temos o seguinte estado da varivel:
numero 4
O operador = utilizado na linha 7 disse que atribuiremos varivel numero o valor 4, mas
no diz que o numero igual a 4. Se atriburmos outro valor varivel, o valor 4 ser substitudo
pelo novo valor.
Ao imprimir a mensagem, o identificador numero enviado para cout e substitudo pelo
valor da varivel numero.
varivel
Figura 3.2: O fluxo de entrada utilizado para que o usurio possa dar valores a variveis.
Esse mecanismo importante para a interao com a pessoa que utiliza o programa. Dessa
forma ele complementar ao operador de atribuio para dar valor s variveis.
Neste exemplo, temos um cdigo onde utilizamos o fluxo de entrada para dar valor s
variveis:
3.2 Fluxo de entrada 23
O comando que aparece na linha 9 imprime uma mensagem pedindo ao usurio que execute
uma ao. Repare que no h o comando endl para passar para a prxima linha. Isso quer dizer
que qualquer outro texto aparecer na mesma linha pois no houve uma quebra de linha.
O cin, aps a linha 8, usado para obter um valor que vir do usurio pelo teclado. Por isto,
o programa fica parado at que o usurio digite um valor e aperte a tecla Enter. Vamos supor que
o valor 3.14 tenha sido digitado. Como no houve quebra de linha no comando anterior, o valor
3.14 aparece em frente ao ltimo texto apresentado e o valor , ento, guardado na varivel
numero1, como indicado para o cin.
Vamos agora para o prximo passo na linha 10. Da mesma maneira, o programa espera uma
entrada de dados do usurio novamente e recebe desta vez o valor 2.7, por exemplo.
Logo aps, na linha 12, atribuido varivel soma o valor numero1 somado com o valor
numero2. De modo que a cada varivel h um valor atribudo agora.
Assim, utilizamos uma varivel a menos. O resultado da soma fica armazenado temporaria-
mente para que o cout possa utiliz-lo. Ao fazer isto, aps o comando com o cout, o resultado
da soma descartado.
varivel1 4 varivel2 7
varivel3 11
Figura 3.3: Exemplo de operador aritmtico. Os valores de duas variveis so utilizados para a
criao de uma nova varivel.
Operador Comando
Adio +
Subtrao -
Multiplicao *
Diviso /
Resto da diviso inteira %
14 r = numero1 - numero2 ;
15 cout << " Subtra o dos numeros = " << r << endl ;
16 r = numero1 * numero2 ;
17 cout << " Multiplica o dos numeros = " << r << endl ;
18 r = numero1 / numero2 ;
19 cout << " Divis o dos numeros = " << r << endl ;
20 r = numero1 % numero2 ;
21 cout << " Resto da divis o = " << r << endl ;
22 return 0;
23 }
Como usual, criamos as variveis que sero utilizadas no programa, nas linhas 5 a 7. Em
seguida, nas linhas 8 a 11, o prprio usurio atribui valores s variveis numero1 e numero2,
como j aprendemos na Seo 3.2. Neste exemplo, vamos supor que o usurio escolheu os
valores 7 e 2:
numero1 7 numero2 2 r
Veja na linha 12 o operador de adio, utilizado para fazer a soma que atribuda ar.
O comando com um cout imprime o resultado r, na linha 13, em seguida.
numero1 7 numero2 2 r 9
numero1 7 numero2 2 r 5
numero1 7 numero2 2 r 14
numero1 7 numero2 2 r 3
Repare que o resultado da diviso de 7 por 2 3 e no 3.5. Isto acontece porque r uma
varivel do tipo int, que representa apenas nmeros inteiros.
Veja enfim, na linha 20, o operador de resto da diviso inteira, que tem o seu resultado
apresentado na linha 21.
numero1 7 numero2 2 r 1
O programa apresenta o resto da diviso inteira de 7 por 2, que igual a 1. Este operador s
est disponvel para dados de tipo inteiro, como int. Isso ocorre pois no existe resto da diviso
inteira para dados que representam nmeros reais, como double.
varivel1 4
+2
varivel1 6
Figura 3.4: Exemplo de operador de atribuio aritmtico. Os valor de uma varivel transfor-
mado de acordo com uma expresso matemtica.
3.4 Operadores de atribuio aritmticos 29
numero1 8 numero2 3
Na linha 12, o comando (+=) equivale a dizer que numero1 recebe o prprio valor de
numero1 mais o valor de numero2. As linha 13 e 14 imprimem o resultado:
numero1 11 numero2 3
numero1 8 numero2 3
Em seguida, na linha 18, o numero1 recebe seu prprio valor vezes o numero2, e o resultado
impresso pelo cdigo das linhas 19 e 20:
numero1 24 numero2 3
Na linha 21, numero1 recebe seu prprio valor dividido por numero2 com a operao /=,
ficando com 8 como resultado. Os valores das variveis so impressos pelas linhas 22 e 23:
numero1 8 numero2 3
Por fim, na linha 24, numero1, que tem valor 8, recebe o resto da diviso de seu prprio
valor por numero2, que tem valor 3. O nosso valor de numero1 passa a ser 2. Isto impresso
pelo cdigo das linhas 25 e 26:
numero1 2 numero2 3
varivel1 4
++
varivel1 5
Figura 3.5: Exemplo de operador de atribuio aritmtico. Os valor de uma varivel transfor-
mado de acordo com uma expresso matemtica.
3.5 Operadores de incremento e decremento 31
O operador de incremento representado por dois sinais de adio seguidos (++) e ele serve
para incrementar em uma unidade o valor de uma varivel qualquer (x++). Como esta uma
operao comum, isto tira o trabalho de dizermos que x = x + 1 ou que x += 1. Podemos
apenas dizer x++.
A posio do operador tambm altera seu modo de funcionamento. Para utilizar e depois
incrementar valores usamos x++. Chamamos este de um operador de ps-incremento. Para
incrementar e depois utilizar valores usamos ++x. Chamamos este de um operador prefixado.
De modo anlogo, no caso do decremento, podemos utilizar x ou x. Isto ficar mais claro neste
exemplo, onde mostraremos a diferena entre os vrios operadores de incremento e decremento:
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 int x ;
7 x = 0;
8 cout << " x = " << x ++ << endl ;
9 cout << " x = " << x << endl ;
10 cout << " x = " << ++ x << endl ;
11 cout << " x = " << x << endl ;
12 cout << " x = " << x - - << endl ;
13 cout << " x = " << x << endl ;
14 cout << " x = " << --x << endl ;
15 cout << " x = " << x << endl ;
16 return 0;
17 }
Iniciamos nossa funo principal main com a criao de uma varivel x na linha 6. Esta
varivel inicia com valor 0 que lhe atribudo na linha 7 do cdigo. Assim temos a seguinte
condio das variveis:
x 0
Na linha 8, utilizamos um operador de incremento em uma varivel que est sendo en-
viada para um cout. Como utilizamos o operador de ps-incremento (x++), a varivel ser
primeiramente utilizada no comando cout. Isto imprime seu valor:
x = 0
Apenas aps sua utilizao, o operador da linha 8 incrementa o valor de x, que passa a ser 1.
x 1
x = 0
x = 1
x 1
x = 0
x = 1
x = 2
x 2
x = 0
x = 1
x = 2
x = 2
x 2
x = 0
x = 1
x = 2
x = 2
x = 2
x 1
x = 0
x = 1
x = 2
x = 2
x = 2
x = 1
3.5 Operadores de incremento e decremento 33
x 1
Por fim, na linha 14 usamos o operador de decremento prefixado x, para que x seja
decrementado e depois utilizado. Assim, x passa primeiro a valer 0 e este valor impresso em
seguida:
x = 0
x = 1
x = 2
x = 2
x = 2
x = 1
x = 0
x 0
Aps isto, na linha 15, o valor final de x impresso mais uma vez e encerramos o programa:
x = 0
x = 1
x = 2
x = 2
x = 2
x = 1
x = 0
x = 0
x 0
Como vimos, o operador prefixado incrementa (++i) ou decrementa (i) o valor da varivel
antes de usa-la. Apenas aps a alterao, ela utilizada com o valor modificado na expresso
em que aparece. O operador de ps-incremento (i++) ou ps-decremento (i) altera o valor da
varivel apenas depois que a mesma utilizada na expresso em que aparece. Um quadro geral
com os operadores de incremento e decremento apresentado na Tabela 3.5.
Nas linhas 10 e 11, imprimimos os valores destas variveis. Veja como, em vez das palavras
true e false, o cout imprime true como 1 e false como 0:
var1 = 1
var2 = 0
varivel1 3 varivel2 7
>
resultado false
Figura 3.6: Exemplo de operador relacional. O valor de duas variveis so comparados e temos
um resultado lgico, que verdadeiro ou falso.
Neste exemplo, mostraremos como a comparao de duas variveis nos retorna um valor do
tipo bool:
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 bool x ;
7 x = 2 < 9;
8 cout << x << endl ;
9 cout << (2 < 3) << endl ;
36 Captulo 3. Operadores bsicos
x true
O valor de x fica ento como true. Como no precisamos armazenar o resultado de uma
expresso neste exemplo, podemos utilizar e imprimir diretamente seu resultado como fazemos
nesta prxima impresso, na linha 9. Neste comando, perguntamos se 2 menor que 3. Como o
resultado da comparao verdadeiro, true retornado e o valor 1 impresso:
1
1
1
1
0
1
1
0
1
1
1
0
1
1
1
1
0
1
1
1
1
1
0
1
1
1
0
&&
resultado false
Figura 3.7: Exemplo de operador lgico. Estes operadores criam novos dados lgicos a partir de
outros dados lgicos.
No terceiro exemplo, temos uma expresso que pergunta se 2 < 6, o que verdadeiro. A
segunda expresso pergunta se 6 < 3, o que falso. O operador de OU lgico pergunta se
pelo menos uma das duas expresses verdadeira, o que torna toda a expresso verdadeira. Em
seguida, no quarto exemplo, perguntamos se 7 < 2 ou ao menos 6 < 3. O resultado da expresso
completa falso.
No ltimo exemplo, na expresso que utiliza o NO lgico, temos uma expresso que
pergunta se 2 < 3, o que verdadeiro. Porm o operador de NO lgico recebe este verdadeiro
e o transforma em falso.
! Em lgebra, podemos dizer que 3 < x < 7. Esta uma notao matematicamente correta.
Porm, em C++, devemos utilizar (3 < x) && (3 < 7). Precisamos disto pois estas so
duas operaes distintas de comparao.
A Tabela 3.8 apresenta a Tabela Verdade do operador OU lgico ||. Na tabela verdade
do operador OU lgico, temos uma situao onde temos false como resposta sempre que
as duas expresses sendo comparadas tambm sejam falsas. Para todos os outros casos, temos
true como resultado, j que uma das expresses sempre verdadeira.
Expresso !Expresso
true false
false true
3.8.2 Exemplo
Neste exemplo, usaremos operadores lgicos para unir expresses condicionais.
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 bool x ;
7 x = ((2 > 7) && (6 > 3));
8 cout << x << endl ;
9 x = (( true ) || ( false ));
10 cout << x << endl ;
11 cout << ((2 < 7) && (6 > 3)) << endl ;
12 cout << ((2 < 7) || (6 < 3)) << endl ;
13 cout << ((7 < 2) || (6 < 3)) << endl ;
14 x = !x;
15 cout << x << endl ;
40 Captulo 3. Operadores bsicos
x false
Em seguida, na linha 9, temos um valor booleano que representa verdadeiro e outro valor
que representa falso. O operador de OU lgico pergunta se ao menos uma das duas expresses
verdadeira, e o resultado verdadeiro. Este resultado guardado em x e impresso como 1.
0
1
x true
0
1
1
Em seguida, na linha 12, se 2 < 7 ou 6 < 3, o que tambm verdadeiro, imprimimos ento 1
0
1
1
1
A prxima comparao, linha 13, pergunta se 7 < 2 ou 6 < 3. Como as duas expresses so
falsas, o resultado falso.
0
1
1
1
0
3.9 Exerccios 41
O comando que aparece na linha 14 utiliza o operao de No-lgico para inverter o valor
de x. O valor invertido de x atribudo prpria varivel x, que passa a ser falso. A linha 15
imprime o valor de x.
0
1
1
1
0
0
x false
0
1
1
1
0
0
0
3.9 Exerccios
Exerccio 3.1 Faa um programa que leia uma altura e uma peso de uma pessoa e imprima
seu ndice de Massa Corporal (IMC), que calculado com a frmula:
peso
IMC =
altura2
Exemplo de sada:
Note que, por padro, no existe em C++ um operador "pr-definido para exponencia-
o. Assim, altura2 = altura altura.
Exerccio 3.2 Faa um programa que leia uma temperatura em Celsius e imprime esta
temperatura em Fahrenheit. Considere a frmula:
9C
F= + 32
5
42 Captulo 3. Operadores bsicos
Exemplo de sada:
Exerccio 3.3 Faa um programa que leia o raio r de um crculo e imprima sua rea A e seu
permetro p.
A = r2 p = 2 r
Exemplo de sada:
Exerccio 3.4 Faa um programa que leia dois valores a e b e imprima o resultado de
(b3 + ab) 2b + a mod b. Em notao matemtica, mod representa o resto da diviso de dois
inteiros. Note que no se calcula resto da diviso de nmeros reais.
Exemplo de sada:
Digite o valor de a: 5
Digite o valor de b: 7
f(x) = 369
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 4.0Estruturas
0
1 0 condicionais
1
0 0 1
0 1
0 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
A todo momento estamos tomando decises que dependem de condies. Se o carro est sujo,
ento precisamos lavar. Se no choveu, vamos regar as plantas. Se estiver escuro acenderemos
as luzes.
Cdigos com estruturas condicionais tambm funcionam assim. Estas estruturas nos do
capacidade para representar estas situaes.
Veja que nos cdigos que criamos at o momento, apenas estruturas sequenciais foram
utilizadas. Nestas estruturas, todos os comandos so executados de cima para baixo, como
representado na Figura 4.1.
Com as estruturas condicionais, podemos dizer que alguns trechos do cdigo nem sempre
sero executados. Estes trechos estaro condicionados a alguma condio, como representado na
Figura 4.2.
4.1 Condicional se
A instruo if (se, em ingls), uma instruo de uma nica seleo. Ela seleciona ou
ignora um grupo de aes de acordo com uma condio. Esta condio pode fazer com que o
programa ignore ou no certo bloco de comandos. Toda instruo de controle espera um dado do
tipo bool indicando se a condio true, verdadeira, ou false, falsa.
4.1.1 Sintaxe do se
Assim a organizao geral de uma instruo if:
1 if ( condi o ){
2 comandos ;
3 comandos ;
4 }
A condio entre parnteses deve ser um dado do tipo bool. Este bool pode ser sim-
plesmente true ou false, neste caso a condio seria sempre verdadeira ou falsa. Por isso,
normalmente, vamos usar uma expresso que retorna um bool, como x < 3.
44 Captulo 4. Estruturas condicionais
Comandos
Comandos
Comandos
Comandos
As chaves { e } indicam onde se inicia e termina o bloco de comandos que sero executados
apenas se a condio for verdadeira. Entre as chaves, podem ser inseridos quantos comandos
forem necessrios.
Perceba que os comandos pertencentes ao if esto alinhados mais direita do restante do
cdigo. Isto fundamental para que o cdigo seja legvel quando temos muitos comandos. Esta
organizao se chama indentao.
4.1.2 Exemplo
Neste exemplo, vamos criar um bloco do cdigo que depender de uma condio.
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 double largura ;
7 cout << " Digite a largura do quadrado : " ;
8 cin >> largura ;
9 if ( largura >= 0){
10 cout << " A rea do quadrado " ;
11 cout << largura * largura << endl ;
12 }
13 return 0;
14 }
No incio da funo principal, criamos uma varivel na linha 6 e, na linha 7, pedimos ao
usurio que digite a largura de um quadrado. Este valor ser utilizado para imprimir a rea do
quadrado. Porm, apenas valores no negativos so vlidos como largura do quadrado. Caso o
nmero digitado seja menor que 0, no queremos que o valor seja impresso, pois ele invlido.
4.1 Condicional se 45
Comandos
Condio
Comandos
Comandos
largura 5
Testamos, na linha 9, a condio largura > 0, que neste caso equivale a 5>0 e verdadeira.
Isto faz com que os comandos dentro do bloco sejam executados. Veja que como os comandos
das linhas 10 e 11 pertencem ao if, eles esto indentados mais direita. Como a condio era
verdadeira, os comandos das linhas 10 e 11 so executados e a rea do quadrado, 25, impressa:
Vamos supor agora que o programa seja executado novamente e o usurio digitou -3 como
largura do quadrado.
largura -3
A condio -3 >= 0 falsa. Assim, o bloco do if, composto pelas linhas 10 e 10, ser
ignorado e o programa ser encerrado sem a mensagem:
46 Captulo 4. Estruturas condicionais
4.2 Se-seno
Alm de fazermos tarefas que dependem de uma condio, tambm fazemos usualmente
muitas decises entre duas opes. Suponha que temos uma festa hoje. Podemos nos perguntar
se a festa ser a noite. Se sim, podemos usar um terno. Seno, podemos usar uma bermuda.
A instruo if-else (se-seno em ingls), uma instruo de seleo dupla. Ela seleciona
entre dois blocos de comandos, como representado na Figura 4.3. Diferentes aes so tomadas
em vez de apenas se ignorar um bloco. Uma condio decide qual bloco de comandos vamos
utilizar.
Comandos
Verdadeiro Falso
Condio
Comandos Comandos
Comandos Comandos
Comandos
A condio entre parnteses deve ser representada ainda por um valor do tipo bool. As
chaves { e } agora indicam onde se iniciam e onde terminam dois blocos distintos de comandos.
Um bloco, composto pelos comandos das linhas 2 e 3, executado se a condio for true e o
outro, composto pelos comandos das linhas 5 e 6, se a condio for false. Perceba tambm a
indentao, fundamental para organizao do cdigo. Os blocos so todos indentados direita
dentro dos blocos.
4.2.2 Exemplo
Neste exemplo, vamos pedir um nmero ao usurio e imprimir uma mensagem informando se
este nmero par ou mpar:
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 int numero ;
7 cout << " Digite um n mero : " ;
8 cin >> numero ;
9 if ( numero % 2 == 0){
10 cout << " O n mero par " << endl ;
11 } else {
12 cout << " O n mero mpar " << endl ;
13 }
14 return 0;
15 }
Na linha 6, criamos uma varivel numero e, na linha 7 do cdigo, pedimos ao usurio que
digite um nmero:
Digite um nmero: 7
numero 7
Digite um nmero: 7
O nmero mpar
numero 7
exibida. Se escolher saldo", uma tela 2 ser exibida. Se a sua opo for depsito", uma tela 3
ser exibida, ou se a sua opo for saque", ento uma tela 4 ser exibida.
A instruo if-else aninhada utilizada como instruo de seleo mltipla para testar
vrios casos, como representado na Figura 4.4. Ela seleciona entre vrios grupos diferentes de
comandos. Existe agora no apenas uma condio mas um grupo de condies que escolhem
entre grupos de blocos de comandos.
Comandos
Condies
Comandos
4.3.2 Exemplo
Neste exemplo, vamos determinar qual o conceito de um aluno de acordo com sua nota.
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 int nota ;
7 cout << " Digite a nota do aluno : " ;
8 cin >> nota ;
9 if ( nota >= 90){
10 cout << " O conceito do aluno A " << endl ;
11 } else if ( nota >= 80){
12 cout << " O conceito do aluno B " << endl ;
13 } else if ( nota >= 70){
14 cout << " O conceito do aluno C " << endl ;
15 } else if ( nota >= 60){
16 cout << " O conceito do aluno D " << endl ;
17 } else {
18 cout << " O conceito do aluno E " << endl ;
19 }
20 return 0;
21 }
Nas linhas 6 a 8 o usurio determina que a nota de um aluno. Neste exemplo, a nota do aluno
74, o que quer dizer que o aluno deve ter conceito C.
nota 74
Como a condio da linha 9, nota >= 90, falsa, o primeiro bloco de comandos, na linha
10, ignorado. Como nota >= 80, condio da linha 11, tambm falso, o segundo bloco de
comandos, da linha 12, tambm ignorado.
Na linha 13 temos a condio nota > 70, que verdadeira. O bloco de comandos corres-
pondente, na linha 14, ser executado. Este bloco de comandos imprime na tela que o conceito
do aluno C.
nota 74
Samos ento do bloco alinhado e vamos direto para a linha 20, onde a funo encerrada.
Repare que a nota tambm era maior que 60, mas apenas o primeiro bloco true considerado.
50 Captulo 4. Estruturas condicionais
4.4.2 Switch
O switch um recurso para estruturas mltiplas de seleo. Sua sintaxe definida da
seguinte maneira:
1 switch ( vari vel ){
2 case constante1 :
3 comandos ;
4 break ;
5 case constante2 :
6 comandos ;
7 break ;
8 default :
9 comandos ;
10 }
4.5 Exerccios 51
Temos uma varivel que ser comparada com vrias constantes. Caso a varivel seja
igual a uma destas constantes, um conjunto de comandos executado.
O switch pode ser facilmente substitudo por um if-else aninhado da seguinte forma:
1 if ( vari vel == contante1 ){
2 comandos ;
3 } else if ( vari vel == constante2 ){
4 comandos ;
5 } else {
6 comandos ;
7 }
No if-else, podemos comparar a varivel a cada uma das constantes do switch.
Neste exemplo seguinte, temos um switch para definir o que fazer de acordo com uma
opo do usurio.
1 switch ( opcao ){
2 case 1:
3 cout << " Op o 1 escolhida " << endl ;
4 break ;
5 case 2:
6 cout << " Op o 2 escolhida " << endl ;
7 break ;
8 default :
9 cout << " Op o inv lida escolhida " << endl ;
10 }
A varivel opcao enviada para o switch e comparada para cada caso. O switch um
recurso muito utilizado para apresentar um menu de opes para o usurio.
Este switch pode ser substitudo por um if-else onde comparamos opcao com cada
possibilidade de resposta:
1 if ( opcao == 1){
2 cout << " Op o 1 escolhida " << endl ;
3 } else if ( opcao == 2){
4 cout << " Op o 2 escolhida " << endl ;
5 } else {
6 cout << " Op o inv lida escolhida " << endl ;
7 }
4.5 Exerccios
Exerccio 4.1 Faa um programa que leia um nmero de usurio e uma senha numrica. O
programa deve dizer se os valores digitados so vlidos ou no. As senhas vlidas so:
Exemplo de sada:
Exerccio 4.2 Faa um programa que leia 5 nmeros e diga no final quantos nmeros eram
pares e quantos nmeros eram mpares.
Neste exerccio, apenas 2 variveis podero ser utilizadas. Uma para receber o nmero
e outra para contar quantas vezes o nmero recebido foi par ou mpar.
Exemplo de sada:
Exerccio 4.3 Faa um programa que leia um valor para uma varivel x e ento calcule f (x),
sendo que:
f (x) = x + 2x2 se g(x) > 10
f (x) = 10 se g(x) 10
g(x) = 5 se h(x) 5
g(x) = h(x) se h(x) > 5
h(x) = x2 + 3x 20
Exemplo de sada:
Digite o valor de x: 15
f(x) = 465
Exerccio 4.4 Faa um programa que leia a idade de um atleta e imprima sua categoria,
sendo que:
Exemplo de sada:
Salrio Imposto
At R$1.434,59 0,00%
De R$1.434,60 at R$2.150,00 7,50%
De R$2.150,01 at R$2.866,70 15,00%
De R$2.866,71 at R$3.582,00 22,50%
Acima de R$3.582,01 27,50%
Os benefcios so:
Exemplo de sada:
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 5.0Operador
0
1 0 de endereo
1
0 0 1 e arranjos
1
0
0 0 0
1
110 1
0
1 0
0
110 0
1
1 0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Em C++, o nome de uma varivel, como largura, retorna diretamente seu valor, que no
exemplo seria 5. O operador de endereo, representado por um e comercial"&, retorna o
endereo de uma varivel. Assim, &largura retorna o endereo 29 desta varivel que, por sua
56 Captulo 5. Operador de endereo e arranjos
num
Digite um nmero: 65
num 65
Digite um nmero: 65
O nmero 65
num 65
Agora, porm, utilizamos o operador de endereo na linha 10 em num (&num) em vez de num,
e o endereo de num na memria ser exibido.
Digite um nmero: 65
O nmero 65
O endereo de nmero 0xbf8d456c
x 5 9 3 7 2 5 8 2
O arranjo x apresentado nada mais que o endereo do primeiro elemento deste arranjo
de nmeros inteiros. Para acessar elementos especficos precisamos de utilizar x[i], onde este
i a posio no arranjo, como apresentado na Figura ??. Formalmente, este i chamado de
ndice do arranjo.
x 5 9 3 7 2 5 8 2
Repare que o primeiro ndice i 0 e o ltimo ndice i 7, ou seja, um a menos que o nmero
de elementos no arranjo. Isso ocorre porque os ndices representam quantas posies alm do
inico do arranjo est o elemento.
Os ndices so simplesmente nmeros inteiros ou uma expresso do tipo inteiro. Suponha
variveis a e b de tipo inteiro:
a 3 b 2
Podemos acessar x na posio a+b com o comando x[a+b] para retornar o elemento que
est em x na posio 5.
Os endereos dos elementos do arranjo esto em sequncia na memria. Assim, arranjo da
Figura 5.2 ficaria guardado na memria como representado na Tabela 5.2.
Para criar um arranjo inicial em branco para dez elementos, utilizamos int x[10].
1 int x [10];
Para criar um arranjo j com seus elementos utilize, ento, int x[] e uma lista de elementos
entre chaves, como no exemplo:
1 int x [] = {5 , 9 , 3 , 7 , 2 , 5 , 8 , 2};
importante entender que tentar acessar uma posio inexistente no arranjo um erro grave.
No sabemos o que h na posio e quais as consequncias de tal acesso.
Suponha que temos o arranjo apresentado na Figura 5.2 e vamos tentar acessar x[9]. Neste
caso, teramos um erro, pois de acordo com a Tabela 5.2, tentaramos acessar a posio de
memria no endereo 35. No sabemos o que existe na posio 35 e nem mesmo que tipo de
dado se encontra nesta posio.
Neste prximo exemplo, criamos um arranjo com 6 elementos e somamos elementos de
posies especficas:
a 5 7 2 1 3 5
Em seguida, na linha 7, criada uma varivel soma e, na linha 8, a mesma recebe a[0] +
a[2] + a[4], que equivale a 5 + 2 + 3, nmeros das posies 0, 2 e 4 do arranjo.
5.3 Cadeias de caracteres 59
a 5 7 2 1 3 5 soma 10
Na linha 10, quando imprimimos o valor de a sem especificar uma posio, temos o endereo
na memria onde comea o arranjo a.
Lembre-se assim que um arranjo um endereo onde comea uma sequncia de elementos
na memria.
b a b a c a t e \0
O caractere \0 mostra que ali termina a palavra. So necessrios assim 8 elementos para
uma palavra de 7 letras. Como aprendemos na seo anterior, este arranjo poderia ter sido criado
diretamente com o seguinte comando:
1 char b [] = { a , b , a , c , a , t , e , \0 };
onde o arranjo char b recebe uma lista de chars, que so separados por vrgula (,) e aspas
simples ().
Porm, existe um atalho equivalente, que atribuir a b todos os chars entre aspas duplas (").
1 char b [] = " abacate " ;
Podemos atribuir dados s cadeias com o comando cin, como em cin >> b. Isto, porm,
muito perigoso pois a pessoa pode digitar mais que 7 letras. Neste caso, haver uma tentativa de
inserir elementos alm do arranjo.
Por exemplo, em um comando cin para atribuir um valor a b, se a pessoa digita laranjada,
por exemplo, ocorre um erro por falta de espao. Isto apresentado na Figura 5.4, onde se
60 Captulo 5. Operador de endereo e arranjos
tenta atribuir a palavra laranjada ao arranjo j alocado da Figura 5.3. Como isto retornaria um
erro, preciso garantir que isto no ocorra, com arranjos que tenham espao suficiente para as
palavras possveis de acordo com a aplicao.
b l a r a n j a d a \0
b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]
Figura 5.4: necessrio se precaver em relao ao nmero de posies disponveis para palavras
no arranjo.
J o objeto cout, recebendo b, imprime a palavra abacate corretamente. Assim como cin,
cout no se preocupa com o tamanho do arranjo. So impressos todos os caracteres at que se
encontre o caractere especial \0, como no exemplo abaixo:
1 cout << b ;
abacate
Como em qualquer arranjo b[i] retorna o char de b na posio i aps o endereo inicial
do arranjo.
Neste exemplo, criaremos uma cadeia de caracteres e imprimimos um de seus elementos:
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 char nome [10];
7 cout << " Digite seu nome : " ;
8 cin >> nome ;
9 cout << " Terceira letra de seu nome " ;
10 cout << nome [2] << endl ;
11 return 0;
12 }
Para isto, criamos um arranjo de chars com 10 elementos, na linha 6. Nas linhas 7 e 8, o
usurio insere um nome nesse arranjo.
b M a r i a \0
b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]
Depois, nas linhas 9 e 10, se imprime o caractere que est na posio 2 do arranjo, ou seja, a
terceira letra do nome:
5.4 Classe string 61
O programa ento encerrado. Mas repare que este cdigo propenso a erros. Na linha 8, o
nome digitado poderia ter mais que 9 letras, e no haveria espao no arranjo. Ou o nome poderia,
na linha 10, ter menos que 3 letras e, no caso, estaramos tentando acessar posies inexistentes,
o que configura um erro grave novamente.
nome
62 Captulo 5. Operador de endereo e arranjos
Imprimimos umas mensagem na linha 8 e, quando o usurio faz sua entrada, na linha 9,
alocado um arranjo com o espao para o nome digitado:
nome M a r i a \0
Linha 0 3 4 7 5
Linha 1 4 7 4 2
Linha 2 7 8 3 6
Figura 5.5: Exemplo de arranjo de duas dimenses. Estes arranjos so teis para representar
tabelas organizadas em linhas e colunas.
Sendo assim, precisamos de dois ndices para encontrar um item dentro deste tipo de arranjo,
como representado na Figura 5.6. Estes so arranjos bidimensionais, tambm conhecidos como
2D. Para acessar uma posio especfica dentro desse arranjo x da Figura precisamos de dois
ndices entre colchetes x[i][j]. Assim como em arranjos simples, os ndices comeam em
0. Por conveno, o primeiro ndice indica a linha e o segundo a coluna. Se colocamos 0 no
primeiro ndice, acessamos elementos da primeira linha. Se colocamos 0 no segundo ndice,
acessamos elementos da primeira coluna.
Este exemplo de arranjo x pode ser criado com x[3][4], onde 3 representa o nmero de
linhas e 4 representa o nmero de colunas. Este arranjo pode receber diretamente seus elementos
atravs de trs conjuntos de nmeros entre chaves, como apresentado abaixo:
1 int x [3][4] = {{3 ,4 ,7 ,5} ,{4 ,7 ,4 ,2} ,{7 ,8 ,3 ,6}};
5.5 Arranjos multidimensionais 63
Figura 5.6: Posies em um arranjo de duas dimenses. Assim como em arranjos simples, as
posies de uma dimenso comeam em 0.
Cada conjunto de nmeros entre chaves representa uma linha do arranjo. Isto acontece
porque um arranjo multidimensional , fisicamente na verdade, um arranjo de arranjos.
Assim como um arranjo simples, um arranjo multidimensional tambm ocupa posies
contnuas na memria. O arranjo da Figura 5.5, por exemplo, seria armazenado na memria
como apresentado na Tabela 5.3.
Assim, cada valor do arranjo possui um identificador (representado aqui pelo identificador
do arranjo e sua posio em colchetes), um valor e uma posio.
Todos os valores esto em sequncia na memria justamente porque eles nada mais so do
que arranjos de arranjos. No arranjo multidimensional x, x[0] um arranjo de 4 elementos que
se inicia na posio 24, x[1] inicia na posio 28 e x[2] na posio 32.
A mesma estrutura sinttica de arranjos de arranjos, ou arranjos 2-D, pode ser utilizada para
criar arranjos com qualquer nmero de dimenses, seja 3-D ou 4-D, por exemplo, como nos
exemplos:
1 int x [3][2][5]; // Arranjo 3 - D para n meros inteiros
64 Captulo 5. Operador de endereo e arranjos
x[1][0] x[1][1]
x[2][0] x[2][1]
Neste exemplo, faremos com que cada linha da matriz represente um aluno e cada coluna
uma prova feita por ele. Alm do arranjo 2D, que encontra-se ainda vazio, criamos em seguida,
na linha 7, uma varivel que guardar os resultados dos clculos.
nome
resultado
5.5 Arranjos multidimensionais 65
Na linha 8, Pedimos ao usurio para que digite um nota. Na linha 9, guardamos o resultado
em x[0][0].
nome 7.3
resultado
Esta posio x[0][0] se refere primeira linha e a primeira coluna. Nos referimos ento, ao
aluno 0 na prova 0 para o usurio. Apenas para observao, poderamos omitir esta informao
do usurio e mostrar esta combinao como aluno 1 e prova 1. De modo anlogo, nas linhas 10 e
11, damos valores ao elemento x[0][1]:
resultado
Nas linhas 12 a 19, fazemos o mesmo para todas as combinaes de aluno e prova.
2.6 3.4
2.6 3.4
De modo anlogo, para saber qual foi a nota final do segundo aluno, somamos todos os
elementos da linha 1 (x[1][0] + x[1][1]) e dividimos por 2, que o nmero de colunas, ou
de provas.
2.6 3.4
5.6 Exerccios
Exerccio 5.1 Suponha um arranjo a com 5 elementos e outro arranjo b com 5 elementos.
Faa um programa que calcule o produto escalar de a por b (Isto , o primeiro elemento de a
multiplicado pelo primeiro elemento de b mais o segundo elemento de a multiplicado pelo
segundo de b mais . . . ).
Novo arranjo: 2 3 4 6 8
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 6.0Estruturas
0
1 0 de repetio
1
0 0 1
0
01 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Um motivo pelo qual os computadores nos auxiliam muito em tarefas porque so capazes
de repetir tarefas milhes ou bilhes de vezes sem cometer erros. Este tipo de repetio poderia
demorar sculos para ns, porm, so simples para essas mquinas.
Imagine quanto tempo levaria enviar uma carta para um milho de pessoas. Compare agora
com o tempo gasto para se enviar um e-mail para um milho de pessoas.
As estruturas de repetio nos permitem realizar instrues repetidamente enquanto uma
condio for verdadeira, como apresentado na Figura 6.1. Por retornar a um comando anterior no
cdigo, estruturas de repetio tambm so chamadas de instrues de loop, ou lao. Usualmente,
os laos so repetidos um certo nmero de vezes ou at que algo acontea.
Comandos
Comandos
Condio
Comandos
usualmente, se o contador atingiu um valor final. O incremento a onde dizemos como ser
incrementada a varivel contadora aps cada repetio. Normalmente, somamos 1 varivel
contadora.
Podemos colocar quantos comandos quisermos dentro do bloco de comandos de for. In-
clusive, podemos colocar ali outras estruturas condicionais e at mesmo outras estruturas de
repetio.
Ol mundo!
i 0
Ol mundo!
Ol mundo!
i 1
Ol mundo!
Ol mundo!
Ol mundo!
i 2
Aps as 10 iteraes deste lao, teremos o seguinte estado, com i igual a 9 e 10 mensagens
impressas:
Ol mundo!
Ol mundo!
Ol mundo!
Ol mundo!
Ol mundo!
Ol mundo!
Ol mundo!
Ol mundo!
Ol mundo!
Ol mundo!
72 Captulo 6. Estruturas de repetio
i 9
i 10
Neste prximo exemplo, temos um cdigo onde o usurio determina o nmero de repeties
do bloco:
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 int i ;
7 int n ;
8 cout << " Quantas vezes voc quer a mensagem ? " ;
9 cin >> n ;
10 for ( i = 0; i < n ; i ++){
11 cout << " Ol mundo ! " << endl ;
12 }
13 return 0;
14 }
Neste exemplo, aps criada a varivel contadora, na linha 6, criada uma varivel para o
nmero de repeties, na linha 7. Esta mesma varivel, na linha 9, recebe do usurio a quantidade
de vezes que ele deseja ver a mensagem. No caso, ele escolhe ver a mensagem 6 vezes:
i n 6
O lao que se inicia na linha 10 repete o bloco de comandos e incrementa o contador enquanto
a condio for verdadeira. A condio se torna falsa quando a mensagem impressa n vezes e
ento encerramos o programa.
i 6 n 6
Usualmente, uma mudana nas prprias condies das variveis pode alterar como a repetio
se comporta. Um exemplo disso, quando usamos o contador dentro do prprio bloco de
repetio.
Neste exemplo, criaremos uma estrutura de repetio onde cada iterao do bloco ter um
resultado diferente:
1 # include < iostream >
2
3 using namespace std ;
4
5 int main () {
6 int i ;
7 int n ;
8 cout << " Digite o n mero final : " ;
9 cin >> n ;
10 for ( i = 0; i <= n ; i ++){
11 cout << i << " " ;
12 }
13 return 0;
14 }
No exemplo, a varivel contadora ser utilizada dentro do prprio bloco do for para imprimir
os nmeros de 0 at n.
Assim que criada a varivel contadora, na linha 6, cria-se tambm a varivel com o nmero
final da sequncia impressa, na linha 7. O usurio, ento, nas linhas 8 e 9, escolhe um nmero
final para a sequncia. No caso, ele escolhe 5:
i n 5
Na linha 10, o contador i inicializado com 0 e a condio i <= n testada. Repare que
neste exemplo testamos se i <= n e no i < n. Isso ocorre porque queremos tambm imprimir
o nmero n. Na linha 11, o contador i impresso e seguido de um espao.
i 0 n 5
i 1 n 5
6.4 Alterando variveis externas 75
Neste exemplo, a condio para continuar o lao que i <= 10, pois queremos imprimir o
nmero n tambm. O contador i incrementado e ainda menor ou igual a n.
i 6 n 5
i 6 n 5
Vale lembrar que o programa pode aceitar qualquer nmero n como entrada do usurio. Caso
o nmero digitado fosse 56, teramos impresso todos os valores entre 0 e 56.
i 57 n 56
Como fizemos nos ltimos exemplos, aps criada a varivel contadora, na linha 6, criada
tambm a varivel para o nmero final da srie, na linha 7. Na linha 8, uma outra varivel
criada para guardar o somatrio dos nmeros. Neste exemplo, nas linhas 8 e 9, a varivel n para
o nmero final da srie recebe do usurio o nmero 4, enquanto soma inicializada com 0 na
linha 11:
i n 4 soma 0
i 5 n 4 soma 10
i n 100 soma 0
Perceba como estruturas de repetio nos permitem realizar tarefas muito mais complexas.
i n 100 soma 0
i 1 n 100 soma 0
5 7 9 11 99
+ + + ++
3 4 5 6 50
Para isto, podemos somar elementos calculador em uma repetio onde o contador i vai de
3 a 50. Nesta srie, a cada repetio somamos o elemento:
2i 1
i
Neste exemplo, vamos utilizar um contador i inicializado em 3 para calcularmos esta srie.
1 # include < iostream >
2
3 using namespace std ;
4
5 int main () {
6 int i ;
7 int n ;
8 double soma ;
9 double numerador ;
10 double denominador ;
11 cout << " Digite o contador final : " ;
12 cin >> n ;
13 soma = 0;
14 for ( i = 3; i <= n ; i ++){
15 numerador = 2* i -1;
16 denominador = i ;
17 soma += numerador / denominador ;
18 }
19 cout << " Somat rio = " << soma << endl ;
20 return 0;
21 }
Nas linhas 6 a 13 inicializamos os variveis necessrias pelo programa. O usurio define um
valor final para n de 50, que o ltimo nmero da srie.
numera denomi
i n 50 soma 0
dor nador
Entramos na estrutura de repetio na linha 14. Veja que a condio de inicializao que i
= 3. Fazemos isto pois 3 o primeiro valor de i em nossa srie. Nas linhas 15 a 17, a varivel
soma recebe o resultado de 5/3 na primeira iterao, quando i 3.
numera denomi
i 3 n 50 soma 1.6666 5 3
dor nador
numera denomi
i 4 n 50 soma 3.4166 7 4
dor nador
Esse processo se repete at que i seja n+1, ou 51. Neste ponto, o resultado do somatrio
impresso na linha 19:
numera denomi
i 51 n 50 soma 93.0008 99 50
dor nador
14 }
15 cout << " Somat rio = " << soma << endl ;
16 return 0;
17 }
As variveis so criadas e inicializadas nas linhas 6 a 11, onde o usurio escolhe 1000 como
um valor limite para o somatrio:
i n 1000 soma 0
i 7 n 1000 soma 7
Veja como neste exemplo utilizamos uma expresso diferente de incremento. Com a nova
expresso no precisamos testar se i multiplo de 7 pois j sabemos que apenas mltiplos de 7
sero atribudos a i.
Neste segundo exemplo, vamos imprimir todas as potncias de 2 entre 2 e um nmero n
qualquer. Por exemplo, 2, 4, 8, 16, 32, 64, 128 e assim em diante.
1 # include < iostream >
2
3 using namespace std ;
4
5 int main () {
6 int i ;
7 int n ;
8 cout << " Digite o n mero final : " ;
9 cin >> n ;
10 for ( i = 2; i < n ; i *=2){
11 cout << i << " " ;
12 }
13 return 0;
14 }
6.7 Condies de incremento 81
Nas linhas 6 e 9 so criadas e inicializadas as variveis, onde o usurio define 10000 como
um valor limite:
i n 10000
i 2 n 10000
i 4 n 10000
i 16384 n 10000
No terceiro exemplo, vamos imprimir uma sequncia de n at 1. Por exemplo: n, n-1, n-2,
n-3 e assim em diante.
1 # include < iostream >
2
3 using namespace std ;
4
5 int main () {
6 int i ;
7 int n ;
8 cout << " Digite o n mero inicial : " ;
9 cin >> n ;
10 for ( i = n ; i > 0; i - -){
11 cout << i << " " ;
12 }
13 return 0;
14 }
82 Captulo 6. Estruturas de repetio
i n 50
Na estrutura de repetio, na linha 10, i inicializado nesse primeiro item da srie, que n.
Seu valor impresso na linha 11:
i 50 n 50
O bloco de comandos se repetir enquanto i for maior que 0. Repare agora que a expresso
de incremento neste exemplo diminui o valor de i.
i 49 n 50
i 0 n 50
6 const int n = 5;
7 double notas [ n ];
8 double soma ;
9 int i ;
10 for ( i =0; i < n ; i ++) {
11 cout << " Digite a nota do aluno " << i << " : " ;
12 cin >> notas [ i ];
13 }
14 soma = 0;
15 for ( i = 0; i < n ; i ++) {
16 soma += notas [ i ];
17 }
18 cout << " M dia das notas : " << soma / n << endl ;
19 return 0;
20 }
Logo na linha 6, primeira linha da nossa funo principal main(), criamos um int que tem
valor 5. A instruo const que acompanha int indica que esta uma constante em vez de uma
varivel. O valor de uma constante no pode ser alterada ao longo do programa.
Criamos um arranjo chamado notas, na linha 7. Apenas constantes podem definir o tamanho
de arranjos em C++. A varivel soma criada na linha 8 para receber a soma de todas as notas
do arranjo. A linha 9 cria a varivel contadora i.
soma i n 5
notas
soma i 0 n 5
notas
notas[i]
Na linha 11, pedimos agora para o usurio que insira a nota do aluno do primeiro aluno, o
aluno que est na posio i, ou 0, do arranjo. O resultado digitado pelo usurio guardado em
notas[i] (que no caso notas[0]).
soma i 0 n 5
notas 6.7
notas[i]
Na expresso de incremento i++, passamos ento para o prximo aluno i. Note que com
a alterao do valor de i, notas[i] passa a representar outra posio do arranjo. Assim, para
cada posio i do arranjo, damos um valor de nota nas linhas 11 e 12.
soma i 1 n 5
notas[i]
soma i 5 n 5
notas[i]
A varivel soma, ento, na linha 14, inicializada em 0. Utilizaremos esta varivel para
fazermos um somatrio das notas no arranjo. Diferentemente dos exemplos anteriores somaremos
notas na posio i em vez de, apenas, i, o ndice.
6.8 Percorrer arranjos 85
soma 0 i 5 n 5
notas[i]
soma 6.7 i 0 n 5
notas[i]
soma 14.5 i 0 n 5
notas[i]
E ao fim de todo o processo processo de repetio, soma ter o somatrio de todas as notas
no arranjo, que 36.4, e i ter valor 5, colocando notas[i] fora do arranjo novamente. Na
linha 18, a soma das notas dividida por n nos permite imprimir a nota mdia nas provas.
soma 36.4 i 0 n 5
notas[i]
86 Captulo 6. Estruturas de repetio
Digite um nmero: 5
i j n 5
i 1 j n 5
Na linha 12, j comea em 0 no segundo lao de repetio e ele ir at i-1. Ou seja, para
cada valor de i em uma iterao no lao externo, este lao interno ocorre i vezes.
i 1 j 0 n 5
Perceba que aps o lao mais interno, na linha 15, endl passa para a prxima linha encer-
rando, assim, uma iterao do lao externo.
6.9 Laos aninhados 87
Digite um nmero: 5
1
i 1 j 1 n 5
Digite um nmero: 5
1
2
i 2 j 0 n 5
Digite um nmero: 5
1
22
i 2 j 1 n 5
A fim da segunda iterao, j passa a valer 2 e o lao interno se encerra.Veja que o lao
interno imprime sempre i vezes o valor de i. E endl passa para a prxima linha.
Digite um nmero: 5
1
22
i 2 j 2 n 5
O contador i mais uma vez incrementado para 3 e seguiremos ento, para a prxima
iterao. Nesta prxima iterao, o valor de i impresso 3 vezes no lao mais interno.
Digite um nmero: 5
1
22
333
i 3 j 3 n 5
88 Captulo 6. Estruturas de repetio
Esse processo se repete at que i seja igual a 6, interrompendo assim o lao de repetio na
linha 11.
Digite um nmero: 5
1
22
333
4444
55555
i 6 j 5 n 5
27 return 0;
28 }
Temos um lao interno nas linhas 14 a 17 e um lao mais externo nas linhas 12 a 18. Este
lao interno percorre todas as colunas j de uma linha i. A linha i definida pelo lao mais
externo.
notas
n1 10 n2 3
i j
soma
Veja que no lao mais externo, na linha 12, para cada linha i, que representa um aluno,
imprimimos uma mensagem, na linha 13, pedindo as notas do aluno. Ainda para este aluno i,
comeamos na linha 14 um lao mais interno com o contador j. Este lao mais interno, nas
linhas 15 e 16, pedem a nota do aluno i em cada prova j, ou seja, notas[i][j]. Na primeira
iterao, i tem valor 0. O lao mais interno utiliza j para percorrer ento as colunas 0, 1 e 2
deste aluno i.
n1 10 n2 3
i 0 j 3
soma
n1 10 n2 3
i 0 j 3
soma
J o conjunto de todas as iteraes no lao mais externo completa a tabela e nos envia para a
linha 19 do cdigo.
6.10 Percorrendo arranjos de arranjos 91
...
Prova 1: 6.8
Prova 2: 9.3
Digite as notas do aluno 8
Prova 0: 8.3
Prova 1: 6.9
Prova 2: 7.3
Digite as notas do aluno 9
Prova 0: 7.2
Prova 1: 9.5
Prova 2: 7.8
6.7 8.8 10
Na linha 19, temos um outro lao mais externo. O prximo lao externo utilizar jcom
contador para percorrer as colunas j da tabela. Para cada coluna calcularemos a sua mdia. A
soma desta coluna j comea, ento, em 0, como feito na linha 20. Nas linhas 21 a 23, para cada
linha i desta coluna j, incrementamos o valor de notas[i][j] a soma. Nas linhas 24 e 25,
imprimimos a mdia do somatrio desta coluna.
...
Prova 1: 6.8
Prova 2: 9.3
Digite as notas do aluno 8
Prova 0: 8.3
Prova 1: 6.9
Prova 2: 7.3
Digite as notas do aluno
Prova 0: 7.2
Prova 1: 9.5
Prova 2: 7.8
Mdia na prova 0: 7.21
92 Captulo 6. Estruturas de repetio
6.7 8.8 10
Na execuo de todo o lao externo, fazemos o mesmo com as outras colunas. Este o fim
deste programa.
...
Prova 1: 6.8
Prova 2: 9.3
Digite as notas do aluno 8
Prova 0: 8.3
Prova 1: 6.9
Prova 2: 7.3
Digite as notas do aluno
Prova 0: 7.2
Prova 1: 9.5
Prova 2: 7.8
Mdia na prova 0: 7.21
Mdia na prova 1: 7.94
Mdia na prova 2: 8.51
6.11 Utilizando apenas critrio de parada 93
6.7 8.8 10
4
5 int main () {
6 int numero = 1;
7 while ( numero != 0){
8 cout << " Digite um n mero : " ;
9 cin >> numero ;
10 cout << " N mero ao quadrado = " << numero * numero << endl ;
11 }
12 return 0;
13 }.
Na linha 6, criamos uma varivel numero que inicializada com valor 1.
numero 1
A instruo while da linha 7 diz que repetiremos o bloco de comandos enquanto o valor de
numero for diferente de 0. Como numero diferente de 0, a condio de repetio verdadeira
e executaremos os comandos das linhas 8 a 10.
Digite um nmero: 7
Nmero ao quadrado: 49
numero 7
Digite um nmero: 7
Nmero ao quadrado: 49
Digite um nmero: 5
Nmero ao quadrado: 25
numero 5
Digite um nmero: 7
Nmero ao quadrado: 49
Digite um nmero: 5
Nmero ao quadrado: 25
Digite um nmero: 0
Nmero ao quadrado: 0
numero 0
6.11 Utilizando apenas critrio de parada 95
O mesmo efeito pode ser obtido com um while, onde podemos fazer a inicializao antes
de iniciar a repetio. Aps os comandos originais do bloco, incrementamos com um comando
prprio o valor do contador.
1 i = 0;
2 while ( i < 10){
3 cout << i << endl ;
4 i ++;
5 }
Embora seja tecnicamente possvel converter um for em um while e vice-versa, a estrutura
mais apropriada deve ser utilizada em cada ocasio. Utilize for para repeties baseadas em
contadores. Utilize while para repeties dependentes de condies especficas de parada.
6.13 Exerccios
Arranjos no podem ser utilizados nestes primeiros exerccios.
Exerccio 6.1 Faa um programa que apresente um menu de opes para o clculo das
seguintes operaes entre dois nmeros: adio, subtrao, multiplicao e diviso. O
programa deve solicitar dois nmeros e possibilitar ao usurio a escolha da operao desejada,
exibindo o resultado e a voltar ao menu. O programa s termina quando for escolhida a opo
de sada.
Exemplo de sada:
[1] Adio
[2] Subtrao
[3] Multiplicao
[4] Diviso
[0] Sair
Escolha uma opo: 3
Digite um nmero: 5.2
Digite outro nmero: 7.5
5.2 * 7.5 = 39.0
[1] Adio
[2] Subtrao
[3] Multiplicao
[4] Diviso
98 Captulo 6. Estruturas de repetio
[0] Sair
Escolha uma opo: 4
Digite um nmero: 4.3
Digite outro nmero: 6.7
4.3 * 6.7 = 0.64
[1] Adio
[2] Subtrao
[3] Multiplicao
[4] Diviso
[0] Sair
Escolha uma opo: 0
Exerccio 6.2 Escrever um algoritmo que l n valores, um de cada vez, e conta quantos
destes valores so pares, escrevendo esta informao.
Exemplo de sada:
Exerccio 6.3 Escreva um programa que receba a idade de n pessoas, calcule e imprima: a
quantidade de pessoas em cada faixa etria; a porcentagem de cada faixa etria em relao ao
total de pessoas.
As faixas etrias so:
1 a 15 anos
16 a 30 anos
31 a 45 anos
46 a 60 anos
61 anos
Exemplo de sada:
Exerccio 6.4 Escreva um programa que receba um nmero inteiro e verifique se o nmero
fornecido primo ou no. O nmero primo se ele tiver apenas 2 divisores: 1 e ele mesmo.
Exemplo de sada:
100 Captulo 6. Estruturas de repetio
Exerccio 6.6 Construa um programa que leia vrios nmeros inteiros e mostre qual foi o
maior e o menor valor fornecido.
Exemplo de sada:
Dica: Quando o usurio digita seu primeiro nmero, este sempre tanto o maior quanto o
menor nmero at ento.
Exerccio 6.7 Dada uma tabela de 4 x 5 elementos, calcular a soma de cada linha e a soma
de todos os elementos. Uma estrutura de repetio deve ser utilizada para percorrer as linhas
e colunas.
Exemplo de sada:
6.13 Exerccios 101
Exerccio 6.8 Dada uma matriz A[4x4], imprimir o nmero de linhas e o nmero de colunas
nulas (com apenas 0s) da matriz. Uma estrutura de repetio deve ser utilizada para percorrer
as linhas e colunas.
Exemplo de sada:
Exerccio 6.9 Faa um programa que mostre os elementos diagonais A[i,i] de uma matriz.
Uma estrutura de repetio deve ser utilizada para percorrer as linhas e colunas.
Exemplo de sada:
Exerccio 6.10 Faa um programa que multiplique duas matrizes. Uma estrutura de repetio
deve ser utilizada para percorrer as linhas e colunas.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 7.0Escopo
0
1 0de variveis
1
0 0 1
0
0 0
1
110 1
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 01 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Como vimos em nossos exemplos, um programa composto de blocos que esto sempre
entre chaves { e }. A funo principal, main, composta tambm de um bloco. Cada estrutura
de controle tambm contm ao menos um bloco.
Escopo de bloco Uma varivel tem escopo de bloco quando a declaramos em um bloco. Neste
caso, ela pode ser apenas utilizada neste bloco e nos blocos mais internos a este bloco. O
escopo de bloco comea na declarao na varivel e termina na chave que fecha o bloco.
Escopo de arquivo Uma varivel tem escopo de arquivo quando seu identificador declarado
fora de qualquer funo ou classe. Esta uma varivel global e conhecida em qualquer
ponto do programa.
Em variveis com escopo de blocos, quando h um bloco aninhado mais interno e os dois
blocos contm uma varivel com o mesmo identificador, a varivel do bloco mais externo fica
oculta at que o bloco interno termine.
Isto o que faremos neste exemplo:
1 # include < iostream >
2
3 using namespace std ;
4
5 int main () {
6 int x ;
7 x = 5;
8 cout << " x externo = " << x << endl ;
9 for ( int i = 0; i < 1; i ++){
10 int x ;
11 x = 7;
12 cout << " x interno = " << x << endl ;
13 cout << " i interno = " << i << endl ;
14 }
15 cout << " x externo = " << x << endl ;
104 Captulo 7. Escopo de variveis
16 return 0;
17 }
Criamos e inicializamos uma varivel x, nas linhas 6 e 7, que pertence ao bloco externo, o
bloco do main. O valor desta varivel impresso na linha 8.
x externo = 5
x 5
Na linha 9, temos uma estrutura de repetio. Veja que no temos ainda uma varivel inteira
i para a estrutura de repetio. Isso ocorre porque a varivel contadora i ser agora criada no
escopo do bloco do for, atravs da instruo int na linha 9. Por isso, ela s ser existente
enquanto estivermos no bloco do for.
x 5
i 0
Na linha 10, criamos uma varivel x dentro do escopo do for. Como j temos uma varivel
x do bloco mais externo, isto faz com que a varivel x do bloco externo fique oculta. Na linha
11, quando damos valor a x, alteraes sero feitas, ento, na varivel visvel. Veja que, na linha
12, o valor impresso foi o da varivel no bloco mais interno.
x externo = 5
x interno = 7
x 5
i 0 x 7
105
x externo = 5
x interno = 7
i interno = 0
Aps a primeira iterao o valor de i passar a ser 1 e a condio de repetio i<1 no mais
atendida. Assim, o bloco do for encerrado. As variveis do bloco interno ento deixam de
existir e o x do bloco externo no est mais oculto, sendo impresso na linha 15.
x externo = 5
x interno = 7
i interno = 0
x externo = 5
x 5
Se tentssemos inserir uma linha de cdigo que imprime o valor de i, no final do programa,
teramos um erro pois no existe a varivel i neste ponto do cdigo. Isso est representado na
linha 18 do seguinte programa:
1 # include < iostream >
2
3 using namespace std ;
4
5 int main () {
6 int x ;
7 x = 5;
8 int i ;
9 cout << " x externo = " << x << endl ;
10 for ( i = 0; i < 1; i ++){
11 int x ;
12 x = 7;
13 cout << " x interno = " << x << endl ;
14 cout << " i interno = " << i << endl ;
15 }
16 cout << " x externo = " << x << endl ;
17 // O seguinte comando causaria um erro :
18 cout << " i = " << i << endl ;
19 // N o existe uma vari vel i neste ponto do c digo
20 return 0;
21 }
106 Captulo 7. Escopo de variveis
Para resolvermos este problema, portanto, precisariamos criar a varivel i no bloco externo
do programa, como feito na linha 9 do cdigo abaixo.
1 # include < iostream >
2
3 using namespace std ;
4
5 int main () {
6 int x ;
7 x = 5;
8 // criando vari vel i no bloco externo
9 int i ;
10 cout << " x externo = " << x << endl ;
11 for ( i = 0; i < 1; i ++){
12 int x ;
13 x = 7;
14 cout << " x interno = " << x << endl ;
15 cout << " i externo = " << i << endl ;
16 }
17 cout << " x externo = " << x << endl ;
18 cout << " i = " << i << endl ;
19 return 0;
20 }
Agora, como a varivel i foi declarada no bloco externo, ela pode ser acessada na linha 17,
gerando o seguinte resultado para o programa:
x externo = 5
x interno = 7
i interno = 0
i = 1
7.1 Exerccios
Exerccio 7.1 Analise o cdigo abaixo, que est incompleto. A linha 14 precisou ser
comentada, pois causava um erro no programa. Aps o for, queramos imprimir o valor final
do contador i. Conserte o cdigo para que a linha abaixo possa ser descomentada sem causar
um erro.
1 # include < iostream >
2
3 using namespace std ;
4
5 int main (){
6 // Imprime e soma as pot ncias de 2 at 1000...
7 int somatorio = 0;
8 for ( int i = 2; i < 1000; i *=2){
9 cout << i << " + " ;
10 somatorio += i ;
11 }
12 cout << " = " << somatorio << endl ;
13 // A linha abaixo precisou ser comentada .
14 // cout << " Valor final de i = " << i << endl ;
15
16 return 0;
17 }
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 8.0Ponteiros
0
1 0
1
0
1
0
0 0
1
110 01
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 01 0 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Vimos que os dados de cada varivel contm um endereo na memria que pode ser acessado
com o operador de endereo, representado por e"comercial (&). Enquanto x retorna o valor de
uma varivel x, o operador de endereo & pode ser utilizado para retornar o endereo de x na
memria.
Ponteiros so variveis que guardam endereos da memria em seus dados. So variveis
que alm de ter seus endereos, podem guardar o endereo de outras variveis. Quando um
ponteiro guarda um endereo de um dado, dizemos que ele est apontando para aquele dado.
Suponha que criamos um nmero inteiro x de valor 5 e um ponteiro que se chamar px.
Temos o seguinte trecho de cdigo e a seguinte representao das variveis:
1 int x ;
2 x = 5;
3 int * px ;
x 5 px
Tabela 8.1: Organizao de um ponteiro ainda vazio na memria. Apesar de guardar endereo,
um ponteiro uma varivel com um nome identificador, valor e endereo.
O operador de endereo & aplicado a px (&px) nos retorna o endereo de px, que 26. E o
comando px, como usual, nos retorna o valor de px, que no caso o endereo de x, ou 25.
O operador representado por um asterisco * o operador de desreferenciao. Quando
aplicamos este operador a px (*px), desreferenciamos px e retornamos o valor apontado por px.
Nesse exemplo, este tambm o valor de x, ou 5.
Sabemos que, na memria, os ponteiros fisicamente guardam outros endereos. Porm,
quando representamos graficamente as variveis isto indicado por uma seta:
111
px
x 5
px py
px py
x 5
112 Captulo 8. Ponteiros
Na linha 11, py recebe o valor de px, que o endereo de x. Assim, py aponta tambm para
x.
px py
x 5
x = 5
&x = 0x7fff571abb68
x = 5
&x = 0x7fff571abb68
&px = 0x7fff571abb68
x = 5
&x = 0x7fff571abb68
&px = 0x7fff571abb68
*px = 5
Na linha 16, imprimimos o valor apontado por py, que tambm o valor de x.
x = 5
&x = 0x7fff571abb68
px = 0x7fff571abb68
*px = 5
*py = 5
x = 5
&x = 0x7fff571abb68
px = 0x7fff571abb68
*px = 5
*py = 5
&px = 0x7fff5b810b40
&py = 0x7fff5b810b38
8.1 Expresses com ponteiros 113
! Apesar de guardarem apenas endereos na memria, a sintaxe dos ponteiros nos obriga
a dizer que tipo de dado ele aponta. No nosso exemplo, precisamos dizer que px um
ponteiro para um int.
Isto ocorre para que seja possvel a operao que retorna o valor apontado. Esta operao
depende do tipo de dado, para saber como interpretar os dados naquele endereo da
memria.
8 int * px ;
9 int ** ppx ;
10 px = & x ;
11 ppx = & px ;
12 cout << " x = " << x << endl ;
13 cout << " & x = " << & x << endl ;
14 cout << " px = " << px << endl ;
15 cout << " * px = " << * px << endl ;
16 cout << " & px = " << & px << endl ;
17 cout << " & ppx = " << & ppx << endl ;
18 cout << " ppx = " << ppx << endl ;
19 cout << " * ppx = " << * ppx << endl ;
20 cout << " ** ppx = " << ** ppx << endl ;
21 return 0;
22 }
Logo no incio do programa, nas linhas 6 a 11, criamos x, um ponteiro px para dados do tipo
int, e um ponteiro ppx para ponteiros para dados do tipo int. Inicializamos x com 5, px com o
endereo de x e ppx com o endereo de px.
ppx
px
x 5
Como vimos nos exemplos anteriores, px possui seu prprio endereo e guarda o endereo
de x. Assim as linhas 12 a 16 geram a seguinte sada:
x = 5
&x = 0x7fff55568b28
px = 0x7fff55568b28
*px = 5
&px = 0x7fff55568b20
Nas linhas 17 e 18, da mesma maneira, a varivel ppx possui seu prprio endereo e um
valor, que neste exemplo o endereo de px, o mesmo que foi impresso com o operador de
endereo em px (&px).
x = 5
&x = 0x7fff55568b28
px = 0x7fff55568b28
*px = 5
&px = 0x7fff55568b20
&ppx = 0x7fff55568b18
ppx = 0x7fff55568b20
8.4 Ponteiros e arranjos 115
Como valor apontado por ppx (*ppx), temos o valor de px, que o endereo de x. Obtendo
o valor apontado pelo valor apontado por ppx (**px), temos o valor apontado por px, que o
valor de x.
x = 5
&x = 0x7fff55568b28
px = 0x7fff55568b28
*px = 5
&px = 0x7fff55568b20
&ppx = 0x7fff55568b18
ppx = 0x7fff55568b20
*ppx = 0x7fff55568b28
**ppx = 5
5
116 Captulo 8. Ponteiros
Como x guarda elementos em posies contguas, podemos fazer aritmtica com o ponteiro
para encontrar outros elementos do arranjo. Neste exemplo, retornamos o valor no endereo que
est duas posies a frente de px.
1 int x [] = {5 , 3 , 6 , 7 , 3};
2 int * px ;
3 px = x ;
4 cout << *( px + 2) << endl ;
! Apesar da relao entre ponteiros e arranjos, um ponteiro uma varivel que guarda um
endereo na memria. J um arranjo, um endereo na memria, onde comea uma
sequncia de elementos.
px
x 6 2 8 4 9
x = 0x7fff52294b10
O trecho de cdigo seguinte, nas linhas 11 a 14, imprime todos os elementos do arranjo
atravs de seus ndices de 0 a 4.
x = 0x7fff52294b10
x[0] a x[4]: 6 2 8 4 9
x = 0x7fff52294b10
x[0] a x[4]: 6 2 8 4 9
px = 0x7fff52294b10
Como px aponta para o primeiro elemento do arranjo, o valor apontado por px retorna 6 na
linha 16.
x = 0x7fff52294b10
x[0] a x[4]: 6 2 8 4 9
px = 0x7fff52294b10
px = 6
Assim como nos arranjos, podemos retornar valores relacionados com o ponteiro com o
operador de subscrito px[i]. O operador de subscrito com um nmero i diferente de zero, pega
o elemento que estiver em i posies frente do endereo apontado por px. Isso o que ocorrre
da linha 17 a 18.
x = 0x7fff52294b10
x[0] a x[4]: 6 2 8 4 9
px = 0x7fff52294b10
*px = 6
px[0] = 6
px[3] = 4
preciso tomar cuidado com esta operao pois no podemos acessar elementos em posies
que esto fora do arranjo.
Conseguimos tambm o mesmo resultado com aritmtica de ponteiros. Na linha 19, adicio-
namos ao endereo o tamanho de 3 elementos do tipo int e chegamos a x[3].
x = 0x7fff52294b10
x[0] a x[4]: 6 2 8 4 9
px = 0x7fff52294b10
*px = 6
8.5 Exerccios 119
px[0] = 6
px[3] = 4
*(px + 3) = 4
x = 0x7fff52294b10
x[0] a x[4]: 6 2 8 4 9
px = 0x7fff52294b10
*px = 6
px[0] = 6
px[3] = 4
*(px + 3) = 4
*px++ = 6
px
x 6 2 8 4 9
Por fim, na linha 21, px[3] retorna o elemento 3 posies aps a posio apontada por px,
que agora x[4].
x = 0x7fff52294b10
x[0] a x[4]: 6 2 8 4 9
px = 0x7fff52294b10
*px = 6
px[0] = 6
px[3] = 4
*(px + 3) = 4
*px++ = 6
px[3] = 9
8.5 Exerccios
Exerccio 8.1 Analise o cdigo abaixo, que est incompleto. Coloque um cout antes de
cada linha explicado o que foi feito neste comando.
1 // biblioteca padr o de entrada e sa da
2 # include < iostream >
3
4 // espa o de nomes da biblioteca padr o ( std )
5 using namespace std ;
120 Captulo 8. Ponteiros
6
7 // fun o principal do programa
8 int main (){
9 int x ;
10 int * px ;
11 px = & x ;
12
13 cout << " Digite um valor para x : " ;
14 cin >> x ;
15
16 // Colocar um cout antes de cada linha
17 cout << x << endl ;
18 cout << & x << endl ;
19 cout << & px << endl ;
20 cout << px << endl ;
21 cout << * px << endl ;
22
23 int v [5] = {4 , 2 , 3 , 4 , 5};
24 cout << v [0] << endl ;
25 cout << v << endl ;
26 cout << & v << endl ;
27 cout << * v << endl ;
28
29 px = v ;
30 cout << px << endl ;
31 cout << & px << endl ;
32 cout << * px << endl ;
33 cout << *( px +1) << endl ;
34 cout << px [2] << endl ;
35 px ++;
36 cout << px [3] << endl ;
37 cout << (* px )++ << endl ;
38 cout << *( px ++) << endl ;
39
40 return 0;
41 }
0 1
1 0 0 0 1 1 10 0 11 1 01 1 00 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 10
1
0
1
0
0100 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 10 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
00
1
1
1 01
1
1 1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 1
00
0
1
1
1110
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 9.0Modularizao
0
1 0 1
0 0de programas
1
0
01 0
10com funes
110 1 1
0
1 0
0
110 0
1
1 0
0
000 0
0
1
1
0
0 0
00 1 00 0 0 0 01 0 01 00 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 00
0 1 01 0 1 1 1 0 01 1 11
Veja como exemplo a letra desta msica. Alguns conjuntos de estrofes so bem parecidos:
Ciranda Cirandinha
Veja agora a mesma letra de outra maneira. Os refres que se repetem esto agrupados.
Ciranda Cirandinha
Ciranda Cirandinha
Msica:
O anel que tu me destes
REFRO
Era vidro e se quebrou
O anel que tu me destes O amor que tu me tinhas
Era vidro e se quebrou
Era pouco e se acabou
O amor que tu me tinhas
Era pouco e se acabou REFRO
de maneira anloga que funes de um programa podem ser definidas e depois chamadas.
Funes so unidades autnomas de cdigo, que cumprem uma tarefa particular. Os progra-
mas em C++ so formados por funes. Por enquanto, estivemos colocando todo o cdigo em
nossa funo principal main. Porm, programas que resolvem problemas maiores so usualmente
divididos em mais funes.
Na linha 13, a funo exemplo chamada. Isso faz com que a funo main fique em espera
at que os comandos da funo exemplo sejam executados. Os comandos da funo exemplo,
nas linhas 5 a 9, imprimem um Ol Mundo! com trs comandos separados, nas linhas 6, 7 e 8.
Ao fim da execuo da funo, voltamos para a funo que a chamou. Ou seja, a funo
main sai de espera na linha 13. O prximo comando do main, na linha 14, mais uma chamada
funo exemplo. Veja que a funo executada mais uma vez.
Retornamos funo main na linha 14. Este exemplo nos mostrou como criar novas funes
com blocos de cdigo que podem ser chamados. Isso pode nos ajudar a organizar um problema
em problemas bem menores. Mas para isso, nossas funes precisam ser declaradas antes da
funo main para que possam ser reconhecidas.
124 Captulo 9. Modularizao de programas com funes
x + y = 8
main
x 2 y 6
soma
Repare que, na linha 13, a funo main chama a funo soma e fica em espera at que ela
termine. x a
A funo soma, definida nas linhas 18 a 24, tambm cria duas variveis em seu prprio
escopo, nas linhas 19 e 20. Nas linhas 21 e 22, estas variveis recebem os valores 4 e 1. Cada
uma das duas funes, main e soma, tm uma varivel chamada x. Note que no h conflito
entre os identificadores pois so escopos completamente diferentes.
main
x 2 y 6
soma
x 4 a 1
Ainda por causa do escopo separado das variveis, a funo soma no tem acesso s variveis
da funo principal main e vice-versa. Agora, na linha 23, soma imprime x + a, de acordo com
as variveis de seu escopo, resultando em 5.
x + y = 8
x + a = 5
126 Captulo 9. Modularizao de programas com funes
main
x 2 y 6
soma
Perceba que, na linha 14, x + y novamente impresso como 8, pois estamos nos referindo
x a
s variveis do main.
x + y = 8
x + a = 5
x + y = 8
main
Veja que, na linha 9, x recebe o que for retornado pela funo pi. Como usual, a funo
main fica em espera enquanto executada a funo pi.
Observe que no criamos nenhuma varivel na funo pi, que inicia na linha 15. O primeiro
comando de pi, na linha 16, j retorna o valor 3.14. Este valor, por sua vez capturado pela
funo main, que a chamou. Assim, a funo main volta a ser executada na linha 9, onde o valor
3.14, retornado por pi, atribudo a x.
main
x 3.14
O valor de pi 3.14
Em um segundo cout, na linha 11, o valor de 3.14 retornado direto da funo para o
comando de impresso.
O valor de pi 3.14
O valor de pi 3.14
Nesse exemplo, tivemos uma funo que retornava o valor de . Funes especficas
permitem a outras funes chamadoras tomar este subproblema como resolvido. Assim, s
precisamos utilizar a funo a onde a soluo do problema for esperada.
OK
Este tipo de informao pode ser passado para as funes atravs dos parmetros. Em C++,
esses parmetros so passados para a funo entre parnteses (), aps o identificador da funo.
Neste exemplo, vamos criar uma funo que eleva um nmero qualquer ao cubo. Para isto a
funo precisa saber qual nmero elevar ao cubo.
1 # include < iostream >
2
3 using namespace std ;
4
5 double cubo ( double a );
6
7 int main () {
8 int x ;
9 cout << " Digite um n mero : " ;
10 cin >> x ;
11 cout << x << " ao cubo " << cubo ( x ) << endl ;
12 return 0;
13 }
14
15 double cubo ( double a ) {
16 return a * a * a ;
17 }
Esta funo depender ento de qual nmero queremos elevar ao cubo e para isso usaremos
um parmetro na funo cubo, representado por a na linha 5. Na funo principal, nas linhas 8 a
10, criamos e inicializamos uma varivel x atravs de uma entrada do usurio.
Digite um nmero: 4
9.5 Parmetros de funo 129
main
x 4
Imprimimos agora, na linha 11, o valor de x e o valor de x ao cubo. No temos uma varivel
com o valor de x ao cubo, mas a funo cubo tem esta capacidade de fazer este clculo e retornar
o resultado.
A funo main fica em espera e a funo cubo(x) executada. A funo se inicia na linha
15, onde a varivel a uma cpia da varivel x, enviada por main ao chamar a funo.
main
x 4
cubo
a 4
Esta funo cubo retorna na linha 16 o valor de a * a * a, ou seja, 64. Com o fim da
funo cubo, main volta ser executada na linha 11, e a chamada de cubo(x) substituda pelo
valor retornado 64.
Digite um nmero: 4
4 ao cubo 64
Note que mesmo sem ter o valor de x ao cubo em nenhuma varivel, podemos dividir o
problema em subproblemas pois sabemos que h uma funo que resolve isso e retorna o que
queremos. Note tambm que para calcular o valor de x ao cubo, o valor de x precisou ser enviado
funo como parmetro.
A funo cubo, se iniciou com uma varivel a. Esta varivel um parmetro da funo. O
valor do parmetro a foi enviado pela funo chamadora, e neste caso uma cpia de x. Como
so dois escopos diferentes, note que no haveria problema algum se a tambm se chamasse x.
Repare tambm que a, nesse caso apenas uma cpia de x e qualquer alterao em a no altera
o x original.
O nico comando da funo cubo retornou a * a * a (ou a3 ). Ao trmino da funo cubo,
a funo main voltar a ser executada com o valor que foi retornado. Com o valor retornado 64,
essa foi a mensagem impressa acima.
Os parmetros aumentam muito a utilidade das funes e nos ajuda mais ainda a dividir
problemas em subproblemas.
possvel aumentar mais ainda a utilidade de funes utilizando mais de um parmetro.
Neste segundo exemplo, criaremos uma funo que eleva um nmero base a um outro nmero
que representa um expoente inteiro.
1 # include < iostream >
2
130 Captulo 9. Modularizao de programas com funes
main
x 4.4 y 5
eleva
Na linha 14, uma mensagem impressa parcialmente:
base 4.4 exp 5
Digite a base: 4.4
Digite o expoente: 5
4.4 elevado a 5 =
main
x 4.4 y 5
eleva
Na linha 20, criamos para a funo tambm uma varivel, resultado, onde ser calculado
o resultado. Ela se inicia com 1 na linha 21.
main
x 4.4 y 5
eleva
resul
1
tado
Nas linhas 22 a 24, temos uma estrutura de repetio. O resultado , a cada iterao,
multiplicado por base na linha 23. Com uma estrutura de repetio, fazemos com este processo
se repita exp vezes.
main
x 4.4 y 5
eleva
resul
1649.16
tado
Por fim, na linha 25, o valor de resultado retornado por eleva para a funo chamadora.
Com isso, a funo eleva encerrada e a funo main volta a executar com o valor retornado,
que base elevado a exp. Isso permite que a mensagem seja impressa na linha 15.
Nas linhas 8 a 10, inicializamos uma varivel chamada x e damos um valor a ela. Na linha
11, imprimimos uma mensagem parcial, sem um endl:
Digite um nmero: 4
4 ao cubo
main
x 4
soma
Como, na linha 12, cout depende de cubo, a funo main fica em espera. J dentro da
a
funo cubo, note que a , neste caso, apenas uma cpia do valor de x e alteraes em a no
alteraro x.
9.6 Passagem de parmetros 133
main
x 4
soma
a 4
Na linha 13, alteramos o valor de a pelo valor de a * a * a, ou a ao cubo. Veja como isto
no influencia o valor original de x.
main
x 4
soma
a 64
O valor de a, ou 64, ento, retornado na linha 18. A funo encerrada e main volta
execuo na linha 12 com o valor retornado.
Digite um nmero: 4
4 ao cubo 64
Digite um nmero: 4
4 ao cubo 64
x = 4
Digite um nmero: 4
4 ao cubo
main
x 4
soma
J na funo cubo, na linha 17, varivel a foi passada por referncia e por isto agora apenas
um apelido para o x, que tem como valor o 4 na funo chamadora. Se alterarmos algo em a,
a
neste momento, alteraremos tambm o x.
main
x 4
soma
a x
Como a s um apelido para o x da funo main, ento na linha 18, quando a recebe a * a
* a, isto ocorre diretamente em x.
9.7 Omitindo a varivel de retorno 135
main
x 64
soma
a x
O valor de a, na linha 19, 64, ento retornado para o cout da funo main, na linha 12. A
funo cubo encerrada e main volta execuo com o valor retornado, na linha 12.
Digite um nmero: 4
4 ao cubo 64
main
x 64
O valor de x impresso na linha 13. Note como durante a execuo do programa o valor de
x foi alterado.
Digite um nmero: 4
4 ao cubo 64
x = 4
12 cubo ( x );
13 cout << x << endl ;
14 return 0;
15 }
16
17 void cubo ( double & a ) {
18 a = a * a * a;
19 }
Declaramos a funo cubo na linha 5, onde o e"comercial (&) indica que a funo recebe a
varivel a por referncia. Dessa vez, a funo no retorna nada e isto indicado com void.
feita a criao e inicializao da varivel x nas linhas 8 a 10 de main. Na linha 11,
imprimimos uma mensagem parcial:
Digite um nmero: 4
4 ao cubo
main
x 4
soma
A funo cubo chamada na linha 12, para alterar o valor de x, enquanto a funo main fica
em espera. A chamada da funo no pode estar em um comando de impresso pois a funo
a
no retorna valor algum.
Na funo cubo, a agora uma referncia para o x da funo chamadora e alteraes em a
faro com que x seja alterado, pois a apenas um apelido para x.
main
x 4
soma
a x
Por este motivo, na linha 18, a (ou x) tem seu valor alterado para 64.
main
x 64
soma
a x
9.8 Retornando vrios valores 137
Por fim, a funo cubo encerrada e, apesar de no retornar nada, o valor de x foi alterado.
A funo main retorna na linha 13 e repare que o novo valor de x impresso ainda na mesma
linha.
Digite um nnero: 4
4 ao cubo 64
main
x 64
30 maximo = c ;
31 }
32 }
Na linha 5, antes da funo principal main, declaramos ento a funo maxmin, que tem
dois parmetros passados por referncia. Como queremos retornar dois valores da funo, as
variveis int minimo e int maximo so passadas por referncia para guardar os resultados.
Sendo assim, na linha8, criamos 5 variveis no main. Perceba que os valores de x, y e z so
dados pelo usurio nas linha 9 e 10. As variveis menor e maior ficam sem valores definidos
por enquanto pois elas guardaro os resultados.
Digite 3 nmeros: 6 3 9
main
x 6 y 3 z 9
menor maior
Os valores de menor e maior sero dados pela funo maxmin. A funo main, na linha 11,
fica ento em espera at que maxmin seja executada.
Na funo maxmin, a, b e c so recebidos por valor de x, y e z, respectivamente. Enquanto
isso minimo e maximo so referncias para menor e maior, da funo chamadora.
main
x 6 y 3 z 9
menor maior
maxmin
a 6 b 3 c 9
Nas linhas 26 e 29, comparamos c com o maior e menor valores j encontrados. Na linha 26,
a condio c < minimo falsa, o que quer dizer que minimo j tem o maior valor entre os trs
nmeros. Mas, como na linha 29 a condio c > maximo verdadeira, significa ento que c
ainda maior que a, que foi o valor atribudo a maximo na outra condio. Portanto, maximo,
recebe o valor de c na linha 30.
Neste ponto, a funo maxmin termina sem retornar nada, mas os valores de menor e maior
do main foram modificados corretamente pela funo.
main
x 6 y 3 z 9
menor 3 maior 9
maxmin
a 6 b 3 c 9
Digite 3 nmeros: 6 3 9
O menor 3
O maior 9
! Vimos vrios exemplos onde parmetros so passados por valor e por referncia para
uma funo. Do ponto de vista de resultados, se uma varivel no ser alterada dentro da
funo, as passagens por valor ou por referncia tem, exatamente, os mesmos resultados.
Porm, do ponto de vista de eficincia, se uma varivel no alterada dentro da funo, a
passagem por referncia usualmente mais eficiente pois no necessrio o procedimento
de cpia.
Na linha 5, no cabealho da funo, o arranjo ser passado por referncia, mesmo sem ter
um e"comercial (&). O tamanho n do arranjo passado por valor.
Na funo principal main, linha 8, criamos um arranjo de tamanho 4. J na linha 9, a funo
main fica em espera para a execuo da funo incrementa. Veja que, na funo incrementa, a
recebe o endereo do arranjo x por referncia e n recebe 4 por valor.
main
x 3 4 2 6
incrementa
a x n 4
main
x 4 5 3 7
incrementa
a x n 4
A funo main volta a ser executada na linha 10. Os elementos do arranjo x so impressos
no main, da linha 10 ate a 13.
4 5 3 7
Note como o arranjo foi passado por referncia sem o e comercial (&).
Digite um nmero: 5
5 ao cubo
main
num 5
Logo aps, veja que a funo main, na linha 12, fica em espera at que a funo cubo seja
executada. Note que a varivel num no pode ser enviada diretamente para cubo, pois cubo
espera um ponteiro para um int e no um int. Mas, como ponteiros guardam endereos de
variveis na memria, a funo main envia para a funo cubo o endereo da varivel num. Na
funo cubo, como x tem o endereo da varivel num, isso faz com que x aponte para num. Isso
acontece mesmo com o endereo tendo sido passado por valor.
main
num 5
cubo
Quando acessamos o valor apontado por x, como na linha 18 do cdigo, trabalhamos com a
varivel num da funo chamadora, que neste caso elevada ao cubo.
main
num 125
cubo
A funo cubo encerra sem retornar nada e a funo main retoma a execuo com o valor da
varivel num alterado. Imprimimos, enfim, na linha 13, o valor alterado de num ainda na mesma
linha.
9.11 Recurso 143
Digite um nmero: 5
5 ao cubo 125
main
num 125
A passagem de ponteiros por valor era utilizada especialmente em C. Isso ocorria, pois C
no tinha o recurso de se passar parmetros por referncia com um &. Sendo assim, este era o
nico modo de se alterar valores da funo chamadora. Em C++, este um recurso que pode
menos interessante j que precisamos saber na funo chamadora se um ponteiro esperado pela
funo chamada. De qualquer maneira, h uma grande quantidade de cdigo ainda disponvel
que utiliza este recurso, que precisa ser conhecido.
9.11 Recurso
Normalmente, organizamos um programa de maneira hierrquica, onde algumas funes
chamam outras. J uma funo recursiva, uma funo que direta ou indiretamente chama a si
mesma.
Quando funes recursivas so chamadas, usualmente, elas s sabem resolver problemas
simples, chamados de caso base. Se ela chamada para um problema mais complexo, ela o
divide em partes menores, partes que sabe resolver e partes que no sabe resolver. Para que a
recurso funcione, a diviso deve levar sempre a um problema parecido, porm pelo menos um
pouco menor. Ento, a funo chama uma cpia dela para trabalhar no problema menor, o que
chamamos de um passo de recurso.
Quando uma funo chama a si mesma, a funo fica esperando que sua cpia seja executada
para ela resolver o problema menor. O prprio passo de recurso pode levar a vrias outras
cpias da funo com problemas menores. Essa sequncia de problemas menores deve convergir,
finalmente, ao caso base, que ser resolvido.
Quando no resolvemos um problema recursivamente, dizemos que estamos resolvendo o
problema iterativamente.
Por exemplo, o valor de n! (n fatorial) pode ser calculado iterativamente como:
n! = n (n 1) (n 2) . . . 3 2 1
main
num 5
fatorial
n 5
main
num 5
fatorial
resul
n 5 1
tado
9.11 Recurso 145
Dentro de um lao de repetio, que se inicia na linha 15, resultado receber as multi-
plicaes pelos valores de n at 1. Para isso, criado no escopo do for o contador i, que ir
percorrer os valores entre n e 1.
Na linha 16, enquanto i > 1, o resultado multiplicado por i. Quando i tem valor 5:
main
num 5
fatorial
resul
n 5 5 i 5
tado
main
num 5
fatorial
resul
n 5 20 i 4
tado
main
num 5
fatorial
resul
n 5 60 i 3
tado
main
num 5
fatorial
resul
n 5 120 i 2
tado
num 5
fatorial
resul
n 5 120 i 1
tado
num 5
fatorial
resul
n 5 120
tado
A funo fatorial encerrada, suas variveis so apagadas e, na linha 9, main retoma sua
execuo com o valor retornado 120.
5! = 120
main
num 5
9.11 Recurso 147
Essa foi uma verso iterativa da funo fatorial. Veja agora um segundo exemplo onde a
soluo do mesmo problema encontrada com uma funo recursiva:
1 # include < iostream >
2
3 using namespace std ;
4
5 int fatorial ( int n );
6
7 int main () {
8 int num = 5;
9 cout << num << " ! = " << fatorial ( num ) << endl ;
10 return 0;
11 }
12
13 int fatorial ( int n ) {
14 if ( n <= 1){
15 return 1;
16 } else {
17 return n * fatorial (n -1);
18 }
19 }
Declaramos uma funo recursiva fatorial na linha 5, que retorna n!. Note que no h
diferena no modo como a funo declarada para a verso iterativa. Na linha 8, criamos e
inicializamos uma varivel num no escopo da funo main.
main
num 5
Na linha 9, nossa funo principal fica em espera at que a funo fatorial seja executada.
A funo fatorial(5), que inicia na linha 13, recebe o valor de num em n.
main fatorial(5)
num 5 n 5
Na linha 14, como n no zero, a funo fatorial tenta, na linha 17, retornar n *
fatorial(4). Mas ao tentar fazer este retorno, a funo colocada em espera do retorno
de uma outra funo fatorial, que recebe 4 como parmetro.
num 5 n 5 n 4
148 Captulo 9. Modularizao de programas com funes
A segunda funo fatorial(4), por sua vez, tem um problema menor do que o problema
original fatorial(5). Isto o que denominamos passo recursivo.
Um novo escopo para esta verso da funo fatorial(4) criado. Essa cpia da funo
tem 4 como parmetro num. Na linha 17, novamente, a funo fatorial(4) tenta retornar 4
* fatorial(3), mas isso faz com que ela tambm fique em espera de outra cpia da funo
fatorial.
num 5 n 5 n 4
fatorial(3)
n 3
Essa cpia recebe o valor 3 como parmetro e fica em espera ao tentar retornar 3 *
fatorial(2).
num 5 n 5 n 4
fatorial(3) fatorial(2)
n 3 n 2
Em seguida, a funo fatorial que recebe o valor 2, tambm entra em espera at o clculo
do fatorial(1).
num 5 n 5 n 4
n 3 n 2 n 1
Nesta ltima chamada, a funo fatorial recebe 1. Este o nosso caso base, ou seja, o
problema que nossa funo sabe resolver sem precisar enviar um problema para outra funo.
Chamamos esta pilha de cpias da funo, como as geradas at aqui, de pilha de recurso.
9.11 Recurso 149
A funo fatorial(1), na linha 15, retorna 1 para a funo que a chamou. Logo,
fatorial(2) sai de espera e pode retornar para fatorial(3) o seu resultado 2. O fatorial(3),
tambm sai de espera e pode retornar o seu resultado 6 para fatorial(4). Quando o resultado
24 de fatorial(4) retornado para fatorial(5), voltamos a nossa primeira chamada da
funo fatorial. Por fim, o fatorial(5) pode finalmente retornar seu resultado 120 para o
main, que o havia chamado na linha 9. A funo main, aps todo este processo, sai de espera e
volta a funcionar com o valor retornado, que 120.
5! = 120
! preciso tomar cuidado para garantir que as funes recursivas levem a um caso base.
Caso contrrio, pode ocorrer uma recurso infinita que ser esgotada quando acabar a
memria para a pilha de funes. Esse um problema similar a um lao infinito em uma
funo iterativa e estruturas de repetio em geral.
9.11.1 Exerccios
Exerccio 9.1 Analise o cdigo abaixo, que est incompleto.
1 # include < iostream >
2
3 using namespace std ;
4
5 // cabe alhos
6 int cubo ( int a );
7 int potencia ( int a , int b );
8 int fatorial ( int a );
9
10 // fun o principal
11 int main (){
12 int x ;
13 cout << " Digite um numero : " ;
14 cin >> x ;
15
16 cout << x << " elevado ao cubo = " << cubo ( x ) << endl ;
17
18 int y ;
19 cout << " Digite um valor para o outro n mero : " << endl ;
20 cin >> y ;
150 Captulo 9. Modularizao de programas com funes
21
22 cout << x << " elevado a " << y << " = " ;
23 cout << potencia (x , y ) << endl ;
24
25 cout << x << " ! = " << fatorial ( x ) << endl ;
26
27 return 0;
28 }
29
30 int cubo ( int a ){
31 return -1;
32 }
33
34 int potencia ( int a , int b ){
35 return -1;
36 }
37
38 int fatorial ( int a ){
39 return -1;
40 }
Exerccio 9.2 Edite a funo cubo (ela est definida aps o main) para que a linha 16 passe
a funcionar corretamente.
Exerccio 9.3 Editar a funo potencia para que o comando da linha 23 passe a funcionar.
Exerccio 9.4 Editar a funo fatorial para que o comando da linha 25 passe a funcionar.
Exerccio 9.6 Troque a funo int potencia(int x, int y) com passagem de par-
metros por valor por uma funo int potencia(int &x, int &y) com passagem por
referncia.
O que se alterou no programa do ponto de vista de resultados? Porqu?
O que se alterou no programa do ponto de vista de sua interpretaco pelo computador?
Em quais casos a diferena de passagem por valor e por referencia se torna relevante?
152 Captulo 9. Modularizao de programas com funes
Exerccio 9.7 Simule uma passagem de parmetros por referncia atravs de ponteiros
passados por valor. Para isso, crie uma nova funo potencia2 para no perder a resposta
das perguntas anteriores.
Como a passagem de parmetros por referncia pode ser simulada com ponteiros passados
por valor? Crie uma nova funo potencia2 para no perder a resposta das perguntas
anteriores.
Tendo a funo potencia2 com passagem por referncia atravs de ponteiros passados
por valor. Descreva qual a desvantagem desta abordagem do ponto de vista do programador.
Exerccio 9.8 Crie uma nova verso da funo potencia chamada potencia3. Refaa a
funo de modo que ela retorne void, a passagem de todas as variveis seja por referncia e
o resultado ser guardado diretamente em uma varivel extra passada como argumento.
Exerccio 9.9 Refaa esta funcao em uma funo potencia4 de modo que ela seja recursiva,
ou seja, a funo far uso de si mesma. Ex: x0 = 1, x1 = x x0 , x2 = x x1 , x3 = x2 x, . . . ,
xn = x(n1) x
Exerccio 9.10 Analise a funo ordena(). Veja como ela recebe um arranjo e o tamanho
deste arranjo. Crie no main um arranjo que a utilize.
Substitua os comandos da funo ordena, responsveis pela troca, por uma chamada a
uma nova funco void troca(int &a, int &b) que recebe como parmetros duas vari-
veis e troque seus contedos.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Estruturas
0
10.
1 01
0
1
0
0 0
1
110 01
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 01 0 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Sintaxe
Temos aqui um exemplo de sintaxe para criao de um estrutura:
1 struct nome_do_tipo {
2 tipo_do_membro1 nome_do_membro1 ;
3 tipo_do_membro2 nome_do_membro2 ;
4 tipo_do_membro3 nome_do_membro3 ;
5 };
O nome_do_tipo o nome da estrutura que queremos criar. Entre chaves { e }, temos uma
lista de membros de dados. Cada membro tem um tipo de dados e um identificador. Essas
estruturas completas podem ser utilizadas para criar novos tipos de dados a partir dos tipos de
dados j existentes.
Por exemplo, uma estrutura como a seguinte pode ser utilizada para representar produtos:
1 struct produto {
2 double preco ;
3 double peso ;
4 string nome ;
5 };
Com esta estrutura, declaramos um novo tipo de dados chamado produto. Esse tipo de
dados contm 2 membros do tipo double e um membro do tipo string.
O novo tipo produto j pode ser utilizado para declararmos variveis desse tipo em nosso
programa. Por exemplo, uma varivel produto que represente uma ma ou um abacaxi.
154 Captulo 10. Estruturas
Os membros dessas variveis podem ser acessados diretamente com um ponto (.) entre o
identificador da varivel e o identificador do membro, como no exemplo abaixo:
1 produto pera ;
2 pera . peso = 0.3;
3 pera . nome = " P ra Williams " ;
4 cout << " Digite o pre o : " ;
5 cin >> pera . preco ;
6 cout << " Peso da p ra : " << pera . peso << endl ;
Nas linhas 1 do exemplo, criamos uma varivel do tipo produto que com nome identificador
pera. Nas linhas 2 e 3 definimos os membros peso e nome desta varivel pera atravs do
operador de atribuio. Nas linhas 4 e 5, utilizamos um cin para que usurio possa definir
o valor do membro peso da varivel pera. Na linha 6, o valor do membro peso da pera
impresso com um cout. O acesso a membros sempre feito com um ponto (.).
Exemplo
Neste exemplo, vamos criar um novo tipo de dado e utiliz-lo em nosso programa.
1 # include < iostream >
2
3 using namespace std ;
4
5 struct produto {
6 double preco ;
7 double peso ;
8 string nome ;
9 };
10
11 int main () {
12 produto x ;
13 x . peso = 0.3;
14 x . nome = " P ra " ;
15 cout << " Digite o pre o : " ;
16 cin >> x . preco ;
17 cout << " Peso da " << x . nome << " : " << x . peso << endl ;
18 return 0;
19 }
Antes da nossa funo principal main, declarado o novo tipo de dados produto nas linhas
5 a 9. Na linha 12, criada uma varivel x do tipo produto no escopo de main. Observe que x,
apesar de ser apenas uma varivel, tem 3 membros: preco, peso e nome.
main
Com um ponto (.) entre o nome da varivel e o nome do membro, atualizamos o valor do
peso de x (x.peso) na linha 13. O peso deste produto recebe 0.3.
10.1 Arranjos de estruturas 155
main
x 0.3
main
x 0.3 Pra
Nas linhas 15 e 16, o usurio atribui um valor a x.preco atravs do fluxo de entrada, com o
nmero 2.3.
main
8 double peso ;
9 string nome ;
10 };
11
12 int main () {
13 const int n = 5;
14 produto p [ n ];
15 for ( int i =0; i < n ; ++ i ){
16 cout << " Produto " << i << endl ;
17 cout << " Digite o pre o : " ;
18 cin >> p [ i ]. preco ;
19 cout << " Digite o peso : " ;
20 cin >> p [ i ]. peso ;
21 cout << " Digite o nome : " ;
22 cin >> p [ i ]. nome ;
23 }
24 for ( int i =0; i < n ; ++ i ){
25 cout << p [ i ]. nome << " \ t " ;
26 cout << " R$ " << p [ i ]. preco << " \ t " ;
27 cout << p [ i ]. peso << " kg " << endl ;
28 }
29 return 0;
30 }
Antes de nossa funo principal, nas linhas 6 a 10, declaramos o novo tipo de dados produto.
J na funo principal, na linha 13, definimos a constante n com valor 5 e, na linha 14, criamos
um arranjo de produtos p, que tem capacidade para n produtos.
main
n 5
Note como cada elemento do arranjo tem espao para os trs membros de um produto: preo,
peso e nome. Na primeira iterao do for definido nas linhas 15 a 23, o usurio atribui valores
aos membros do primeiro produto. Para isto, indicamos uma posio do arranjo e o membros
desta posio, com p[i].preco, p[i].peso ep[i].nome.
Produto 0
Digite o preco: 2.3
Digite o peso: 0.3
Digite o nome: Pra
No conjunto de todas as iteraes, passando por todos os valores de i, teremos dado valores
a todos os membros de todos os produtos.
10.2 Estruturas e ponteiros 157
...
Digite o nome: Lima
Produto 3
Digite o preo: 6.5
Digite o peso: 2.3
Digite o nome: Ma
Produto 4
Digite o preo: 7.2
Digite o peso: 0.56
Digite o nome: Uva
main
p 2.3 0.3 Pra 8.6 0.2 Kiwi 1.5 1.2 Lima 6.5 2.3 Ma 7.2 0.56 Uva
n 5 i 5
J no outro conjunto de iteraes, definido nas linhas 24 a 28, imprimimos todos os produtos
do arranjo de maneira anloga.
...
Produto 4
Digite o preo: 7.2
Digite o peso: 0.56
Digite o nome: Uva
Pra R$2.3 0.3kg
Kiwi R$8.6 0.2kg
Lima R$1.5 1.2kg
Ma R$6.5 2.3kg
Uva R$7.2 0.56kg
6
7 struct produto {
8 double preco ;
9 double peso ;
10 string nome ;
11 };
12
13 int main () {
14 const int n = 5;
15 produto p [ n ];
16 produto * barato ;
17 for ( int i =0; i < n ; ++ i ){
18 cout << " Produto " << i << endl ;
19 cout << " Digite o pre o : " ;
20 cin >> p [ i ]. preco ;
21 cout << " Digite o peso : " ;
22 cin >> p [ i ]. peso ;
23 cout << " Digite o nome : " ;
24 cin >> p [ i ]. nome ;
25 }
26 barato = & p [0];
27 for ( int i =1; i < n ; ++ i ){
28 if ( p [ i ]. preco < barato - > preco ){
29 barato = & p [ i ];
30 }
31 }
32 cout << " Produto mais barato : " << endl ;
33 cout << barato - > nome << " \ t " ;
34 cout << " R$ " << barato - > preco << " \ t " ;
35 cout << barato - > peso << " kg " << endl ;
36 return 0;
37 }
declarado no cdigo, na linha 6, o novo tipo de dados produto. Nas linhas 14 a 16,
criamos a constante n, um arranjo p de tamanho n para dados do tipo produto e um ponteiro
barato, tambm para dados do tipo produto.
main
n 5 barato
Exatamente como no exemplo anterior, a partir da linha 16, damos valores a todos os produtos
de nosso arranjo.
10.2 Estruturas e ponteiros 159
...
Dite o nome: Lima
Produto 3
Digite o preo: 6.5
Digite o peso: 2.3
Digite o nome: Ma
Produto 4
Digite o preo: 7.2
Digite o peso: 0.56
Digite o nome: Uva
main
p 2.3 0.3 Pra 8.6 0.2 Kiwi 1.5 1.2 Lima 6.5 2.3 Ma 7.2 0.56 Uva
n 5 barato i 5
Em seguida, na linha 26, o ponteiro barato aponta para o primeiro elemento do arranjo. O
endereo do primeiro elemento do arranjo pode ser acessada tanto com &p[0] quanto com p.
main
p 2.3 0.3 Pra 8.6 0.2 Kiwi 1.5 1.2 Lima 6.5 2.3 Ma 7.2 0.56 Uva
n 5 barato
Temos entre as linhas 27 e 31 um lao que procura o elemento mais barato do arranjo. O
ponteiro barato aponta para o primeiro elemento e comearamos a busca a partir do segundo
elemento p[1].
Na linha 28, na primeira iterao do lao, se o elementop[1] fosse mais barato que o
apontado por barato, barato apontaria para p[1]. Repare como o operador de seta est sendo
usado na linha 28.
main
p 2.3 0.3 Pra 8.6 0.2 Kiwi 1.5 1.2 Lima 6.5 2.3 Ma 7.2 0.56 Uva
n 5 barato i 1
Na prxima iterao, quando i tem valor 2, como p[2] mais barato que o elemento
apontado por barato, ento ele passa a ser o elemento apontado.
160 Captulo 10. Estruturas
main
p 2.3 0.3 Pra 8.6 0.2 Kiwi 1.5 1.2 Lima 6.5 2.3 Ma 7.2 0.56 Uva
n 5 barato i 2
Ao fim do for, teremos testado isso para todos os elementos, porm nenhum foi mais barato
que p[2] neste exemplo.
main
p 2.3 0.3 Pra 8.6 0.2 Kiwi 1.5 1.2 Lima 6.5 2.3 Ma 7.2 0.56 Uva
n 5 barato i 5
Aps o fim do lao, utilizamos novamente o operador de seta, nas linhas 32 a 35, para
imprimir os dados sobre elemento mais barato do arranjo.
...
Digite o preo: 6.5
Digite o peso: 2.3
Digite o nome: Ma
Produto 4
Digite o preo: 7.2
Digite o peso: 0.56
Digite o nome: Uva
Produto mais barato:
Lima R$1.5 1.2kg
main
p 2.3 0.3 Pra 8.6 0.2 Kiwi 1.5 1.2 Lima 6.5 2.3 Ma 7.2 0.56 Uva
n 5 barato
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Alocao
0
11.
1 0 1
0 de 0memria
1
0 1 0 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
x px a[0] a[1] a[2] a[3] b[0] b[1] b[2] b[3] b[4] i c[0] c[1] c[2] c[3] c[4]
Repare na linha 15 que n uma constante. Valores de constantes no podem ser alterados
durante o programa. Repare tambm que, por ele ser constante, no existe memria alocada para
ele. No necessrio alocar memria para constantes pois sabemos que seu valor nunca se altera.
simplesmente como se todas as aparies de n no cdigo fossem substitudas por seu valor.
Assim, os dois cdigos abaixo so equivalentes:
De maneira anloga, esse trecho de cdigo pode levar a uma mensagem de erro:
1 # include < iostream >
2 # include < string >
3
4 using namespace std ;
5
6 int main () {
7 int n ;
8 cout << " Digite o tamanho desejado de vetor : " ;
9 cin >> n ;
10 int x [ n ];
11 for ( int i =0; i < n ; ++ i ){
12 x [ i ] = i +1;
13 }
14 return 0;
15 }
Isso acontece porque na linha 10, usamos uma varivel para definio do tamanho do arranjo.
Assim, o sistema no consegue determinar quanta memria ser necessria para a funo antes
de iniciar main.
funcao
x 5 px i 2
O operador new pede ao sistema para alocar dinamicamente memria para mais uma varivel
do tipoint. Essa varivel alocada fora do escopo da funo pois no est sendo alocada
automaticamente.
funcao
x 5 px i 2
Um problema com esse int alocado que no podemos acess-lo ainda pois no existe
um nome identificador para ele. Para resolver este problema, o operador new retorna tambm o
endereo onde foi alocada a memria para o int. Assim, este endereo pode ser atribuido a um
ponteiro, como em px = new int), onde o ponteiro px recebe ento o endereo deste novo int.
funcao
x 5 px i 2
Agora sim podemos acessar essa memria atravs desse ponteiro, como exemplo abaixo:
1 int x = 5;
2 int i = 2;
3 px = new int ;
4 * px = 3;
5 (* px )++;
6 cout << * px << endl ;
164 Captulo 11. Alocao de memria
funcao
x 5 px i 2
Considere agora este exemplo onde utilizamos o comando new duas vezes.
1 int x = 5;
2 int i = 2;
3 px = new int ;
4 * px = 3;
5 px = new int ;
6 * px = 4;
Temos novamente um problema, pois como px aponta ao final do cdigo para o segundo
int, no temos mais como acessar a memria alocada para o primeiro int.
funcao
x 5 px i 2
3 4
vazamento de memria
Quando uma memria fica alocada porm no temos como acess-la, dizemos que houve um
vazamento de memria. Esse um problema srio em programas, pois nossa memria um
recurso finito.
Considere agora esta funo e o estado de suas variveis:
1 void funcao (){
2 int x = 5;
3 int i = 2;
4 int * px ;
5 px = new int ;
6 * px = 4;
7 }
11.2 Alocao dinmica de memria 165
funcao
x 5 px i 2
funcao
x 5 px i 2
muito comum que a memria alocada seja utilizada apenas durante um perodo de tempo.
Por isso, o comando delete pode ser utilizado para liberar essa memria para outros pedidos de
alocao, como no exemplo abaixo:
1 void funcao (){
2 int x = 5;
3 int i = 2;
4 int * px ;
5 px = new int ;
6 * px = 4;
7 delete px ;
8 }
funcao
x 5 px i 2
Repare que o comando delete libera a memria apontada por px e no apaga a varivel px.
Apagar a varivel px nem seria possvel, pois px foi automaticamente alocada.
Em outro exemplo da utilizao do delete, px aponta para um inteiro de valor 3, e na linha
4 ele apagado para apontar para outro inteiro de valor 4:
166 Captulo 11. Alocao de memria
1 int * px ;
2 px = new int ;
3 * px = 3;
4 delete px ;
5 px = new int ;
6 * px = 4;
funcao
x 5 px i 2
3 4
Como o dado inicialmente apontado por px foi desalocado, ele pode apontar para outro
endereo na memria sem que houvesse vazamento de memria.
funcao
x 5 px i 2
Note que n agora no precisa mais ser uma constante, pois a memria est sendo dinamica-
mente alocada. Isso nos permite, at mesmo, associar o tamanho do arranjo a uma resposta do
usurio, como ocorre no cdigo abaixo:
1 int n ;
2 cout << " Quantos elementos deseja ter no arranjo ? " ;
3 cin >> n ;
4 new int [ n ];
funcao
x 5 px i 2
funcao
x 5 px i 2
4 3 5 2
No cdigo acima, ao fim da utilizao dessa memria, seria importante liber-la para que
no haja vazamento de memria. Para apagar arranjos, usamos delete[] . Mas no confunda
este operador com com o delete simples. O delete simples desaloca a memria de apenas um
elemento:
1 int n = 4;
2 px = new int [ n ];
3 delete [] px ;
funcao
x 5 px i 2
168 Captulo 11. Alocao de memria
fundamental desalocar toda a memria aps utiliz-la. Aps liberar a memria, para
que px pare de apontar para um endereo onde no h memria alocada, o fazemos apontar
para nullptr, que representa um ponteiro nulo. Isso faz com que o ponteiro no aponte para
nenhum endereo e previne erros futuros. Isso pode ser visto na ltima linha do cdigo abaixo:
1 int n = 4;
2 px = new int [ n ];
3 delete [] px ;
4 px = nullptr ;
funcao
x 5 px i 2
main
x n
main
x n
Nas linhas 8 e 9, o usurio, por sua vez, atribui o valor que deseja para n, no caso, 5.
main
x n 5
Na linha 10, alocada memria para um arranjo de tamanho 5 e x aponta para ele. Repare
que a memria foi alocada no main, mas ela no pertence a seu escopo. Apenas conseguimos
acessar o arranjo atravs do ponteiro que est em main.
main
x n 5
x n 5
1 2 3 4 5
Aps isto, na linha 14, chegamos em um ponto onde no precisamos mais do arranjo alocado.
Assim, o apagamos com a instruo delete[].
main
x n 5
1 2 3 4 5
170 Captulo 11. Alocao de memria
Na linha 15, fazemos com que x pare de apontar para aquela posio de memria tambm, j
que no temos mais o arranjo naquele endereo.
main
1 2 3 4 5
*int x
Neste programa, x um ponteiro para um arranjo de dados do tipo int e x[i] acessa a
posio i deste arranjo. Ao fim do cdigo, a memria desalocado com o comando delete[].
No exemplo abaixo alocamos dinamicamente um arranjo multidimensional como um arranjo
de arranjos:
1 // ponteiro para ponteiros de int
2 int ** x ;
3 // alocando arranjo de ponteiros para int
4 x = new * int [5];
5 // alocando arranjos
6 for ( int i =0; i <5; i ++){
7 x [ i ] = new int [105
8 }
9 // utilizar o arranjo
10 // liberando arranjos
11 for ( i =0; i <5; i ++){
12 delete [] x [ i ];
11.4 Alocao dinmica de arranjos multidimensionais 171
13 }
14 delete [] x ;
Na linha 2, criamos uma varivel x que um ponteiro para ponteiros de dados do tipo int.
Na linha 4, x aponta para um arranjo de tamanho 5. Neste exemplo, note que o comando cria um
arranjo de dados do tipo *int[5]. Ou seja, apesar da representao grfica similar, o arranjo
criado um arranjo de ponteiros para nmeros inteiros, e no um arranjo de nmeros inteiros.
Cada posio x[i] do primeiro arranjo um ponteiro que deve apontar ento para um outro
arranjo de nmero inteiros.
main
**int x
Nas linhas 6 a 8, temos uma estrutura de repetio que faz com que cada x[i] aponte para
um segundo arranjo de tamanho 5, como representado a seguir:
main
**int x
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Processamento
0
12.
1 0 1
0 0 de arquivos
1
0 10 0
0
1
110
sequenciais
1
1
0
1 0
0
110 0
1
1 0
0
000 0
0
1
1
0
0 0
00 1 00 0 0 0 01 0 01 00 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Todos os dados que guardamos nas variveis em nossos programas so temporrios. Sendo
assim, todos eles deixam de existir logo aps o fim do programa. Os arquivos em unidades
externas de armazenamento so utilizados para guardar esses dados de modo permanente em
outros dispositivos.
Os objetos cin e cout, so criados quando <iostream> includo. O fluxo desses objetos
criam um canal de comunicao do programa com dispositivos de entrada e sada, como o
monitor e o teclado. Com a insero de <fstream>, podemos criar objetos que possibilitam a
comunicao do programa com arquivos. Isto representado na Figura 12.1.
fstream
iostream
Figura 12.1: Bibliotecas iostream e fstream para fluxo de entrada e sada. A biblioteca
fstream faz fluxo de entrada e sada atravs de arquivos.
e cin: o operador de insero (<<) insere dados no arquivo; o operador de extrao (>>) l os
dados do arquivo.
Neste exemplo, faremos um programa que guarda dados em um arquivo:
1 # include < iostream >
2 # include < fstream >
3 # include < string >
4
5 using namespace std ;
6
7 int main () {
8 ofstream fout ( " pessoas . txt " );
9 string nome ;
10 int idade ;
11 int opcao ;
12 do {
13 cout << " Digite um nome : " ;
14 cin >> nome ;
15 cout << " Digite a idade de " << nome << " : " ;
16 cin >> idade ;
17 fout << nome << " " << idade << endl ;
18 cout << " Deseja continuar ? (0 = N o , 1 = Sim ): " ;
19 cin >> opcao ;
20 } while ( opcao !=0);
21 fout . close ();
22 return 0;
23 }
Como mencionado, a insero de <iostream>, na linha 1 do cdigo, cria os objetos cin e
cout. Este objetos fazem o fluxo de sada e entrada no programa. Apesar de no represent-los
graficamente, estes objetos esto constantemente disponveis ao longo de nosso programa:
cout cin
Com a insero de <fstream>, na linha 2 do programa, podemos criar outros objetos que
faam fluxo de sada e entrada com arquivos do computador.
Veja que o fluxo de relacionamento com o arquivo pode ser nas duas direes, de acordo
com a seta.
Na linha 8 do cdigo, em main, criamos um objeto fout do tipo ofstream, para sada de
dados. Esse objeto conectado a um arquivo chamado pessoas.txt. Criamos tambm, as
variveis nome, idade e opcao nas linhas 9 a 11.
main
pessoas.txt
Nas linhas 12 a 20, temos uma estrutura de repetio na qual colocaremos dados no arquivo
atravs de fout. Nas linhas 13 a 16, j dentro do lao, perguntamos ao usurio um nome e uma
idade e atualizamos as variveis. Isso feito atravs dos fluxos cin e cout.
main
pessoas.txt
Colocamos agora, na linha 17, os valores de nome e idade em nosso arquivo atravs do
objeto fout. Os dados so enviados para o arquivo de texto do mesmo modo que seriam enviados
para a tela, caso cout fosse utilizado.
176 Captulo 12. Processamento de arquivos sequenciais
main
pessoas.txt
Joo 27
O usurio, nas linhas 18 e 19, escolhe continuar com uma opo diferente de 0, que a
condio do lao.
Repetimos o processo por mais algumas vezes. Em outras iteraes do lao, o usurio insere
mais dois nomes at que decide sair.
main
pessoas.txt
Joo 27
Maria 25
Jos 30
Como a opo do usurio foi 0 na ltimo iterao, samos do lao. Na linha 21, aps o lao,
fechamos o arquivo pessoas.txt e salvamos os dados. A funo close() pertence ao prprio
objeto fout. A funo open(arquivo.txt) poderia ser utilizada para associar fout a outro
177
arquivo. Em nossa memria secundria, na pasta onde foi executado o programa, temos agora
um arquivo pessoas.txt. No arquivo temos todos os nomes e idades digitados.
Em nosso segundo exemplo, criaremos um fluxo para obter informaes de um arquivo.
Para ler um arquivo, na linha 8, primeira do main, criamos um objeto do tipo ifstream para
um fluxo de entrada atravs de arquivo. O arquivo j contm as informaes que sero lidas.
Criamos tambm, nas linhas 9 e 10, as variveis nome e idade para armazenar temporariamente
as informaes do arquivo.
main
pessoas.txt
Joo 27
Maria 25
Jos 30
Na linha 11, A funo eof() uma abreviao para end of file (ou fim de arquivo, em
ingls). Esta funo retorna um bool indicando se j estamos no fim do arquivo. No momento
ela retornar false pois acabamos de abrir o arquivo e estamos ainda no seu incio. Na linha 11,
em outras palavras, a expresso completa do while diz que repetiremos o bloco de comandos
enquanto no estivermos no fim do arquivo.
Nas linhas 12 e 13, usamos o objeto fin para ler um nome do arquivo para a varivel nome
e depois um nmero para outra varivel idade. Apesar dos dados virem do arquivo, estes
comandos funcionam da forma muito similar a um cin. Em seguida, na linha 14, imprimimos
os valores lidos e guardados nas variveis.
178 Captulo 12. Processamento de arquivos sequenciais
Joo 27
main
pessoas.txt
Joo 27
Maria 25
Jos 30
De acordo com a condio da linha 11, como estamos dentro de um lao de repetio e ainda
no estamos no fim do arquivo, vamos ler o prximo nome e a prxima idade. Imprimimos com
cout.
Joo 27
Maria 25
Mas este ainda no o fim do arquivo. Lemos o outro nome, outra idade, e imprimimos o
resultado:
Joo 27
Maria 25
Jos 30
12.1 Exerccios
Exerccio 12.1 Analise o cdigo abaixo, que est incompleto.
1 // biblioteca para manipulacao de arquivos
2 // ( entrada e saida por arquivos )
3 # include < fstream >
4 # include < iostream >
5
6 using namespace std ;
7
8 int main (){
9 double n1 , n2 , n3 ;
10 // declara o do arquivo de entrada
11 ifstream arq_entrada ;
12 arq_entrada . open ( " teste . xyz " );
12.1 Exerccios 179
Exerccio 12.2 Utilize um editor de textos para criar um arquivo teste.xyz na pasta
onde ser executado o programa. O arquivo dever conter trs nmeros reais em cada linha
separados por espao, em um formato como o apresentado abaixo:
Execute o programa.
Exerccio 12.3 Se o programa for executado corretamente, execute-o novamente, agora para
um arquivo pontos.xyz com milhares de linhas. Estes pontos sero considerados dados
de levantamento topogrfico de uma regio.
Exerccio 12.5 Altere o programa para que os dados dos pontos sejam armazenados em um
arranjo cujos elementos sejam do tipo Ponto. O arranjo deve ter tamanho 2000. O nmero
de elementos do arranjo preenchidos com elementos vlidos deve ser contado.
Exerccio 12.6 Escreva uma funo para encontrar o ponto mais alto da regio. O prottipo
da funo deve ser:
int pontoMaisAlto(Ponto pontos[], int n);
onde n o nmero de pontos no arranjo. A funo dever retornar o ndice do ponto
encontrado no arranjo.
Exerccio 12.7 Escreva uma funo para determinar os limites, no eixos x e y, da regio
ocupada pelos pontos. O prottipo da funo deve ser:
180 Captulo 12. Processamento de arquivos sequenciais
void limites(Ponto pontos[], int n, double &minX, double &maxX, double
&minY, double &maxY);
Exerccio 12.8 Refaa o programa de maneira que a tamanho do arranjo para os pontos seja
alocado dinamicamente com a instruo new.
O arquivo deve ser lido uma vez apenas para se fazer a contagem do nmero de pontos. A
alocaco ser feita ento com o nmero exato de pontos e os dados sero ento inseridos no
arranjo.
Para se retornar a leitura para o inicio do arquivo, use os comandos:
arq_entrada.clear();
arq_entrada.seekg(0, ios::beg);
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Resumo
0
13.
1 0 de comandos
1
0 0 1
1
0
0 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Nesta seo apresentamos um resumo de comandos em C++. Estes comandos podem ser
utis para recordar os conceitos deste captulo ou como referncia para leitores que j so
programadores porm no esto acostumados com a linguagem C++.
1 // Dado l gico
2 bool nome ;
3 // N meros inteiros
4 short nome ;
5 int nome ;
6 long nome ;
7 // N meros reais
8 float nome ;
9 double nome ;
10 // Caracteres
11 char nome ;
13.3 Cadeias de caracteres
2 # include string
3 // Cadeia ( tipo de dado n o fundamental )
4 string nome ( " texto " );
1 int x = valor ;
2 // operadores aritm ticos
3 x = a + b ; // soma
4 x = a - b ; // subtra o
5 x = a / b ; // divis o
6 x = a * b ; // multiplica o
7 x = a % b ; // resto
8 // operadores de incremento e decremento
9 x ++; // incremento
10 x - -; // decremento
11 // operadores de atribui o aritm ticos
12 x += a ;
13 x -= a ;
14 x *= a ;
15 x /= a ;
16 x %= a ;
13.5 Operadores relacionais
1 // maior que
2 valor1 > valor2
3 // menor que
4 valor1 < valor2
5 // maior ou igual que
6 valor1 >= valor2
7 // menor ou igual que
8 valor1 <= valor2
9 // igual
10 valor1 == valor2
11 // diferente
12 valor1 != valor2
1 // e l gico
2 dadol gico1 && dadol gico2
3 // ou l gico
4 dadol gico1 || dadol gico2
5 // nega o
6 ! dadol gico1
13.7 Estruturas condicionais
1 // sele o nica
2 if ( condi o ){
3 ...
13.8 Estruturas de Repetio 183
4 }
5 // sele o dupla
6 if ( condi o ){
7 ...
8 } else {
9 ...
10 }
11 // sele o m ltipla
12 if ( condi o ){
13 ...
14 } else if ( condi o2 ) {
15 ...
16 } else if ( condi o3 ) {
17 ...
18 } else {
19 ...
20 }
1 struct Nome {
2 tipo variavel1 ;
3 tipo variavel2 ;
4 ...
5 };
13.16 Alocao dinmica de arranjos 185
6 // ...
7 Nome x ; // Cria vari vel do tipo Nome
8 x . variavel1 = valor ; // Atribui o
9 Nome * px = x ; // Ponteiro para registro
10 px - > variavel1 = valor ; // Atribui o
13.16 Alocao dinmica de arranjos
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
00
1
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 11
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 1
00
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Anlise
0
14.
1 0 de Algoritmos
1
0 0 1
0 10 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
10
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 00
0 1 010 1 1 1 0 01 1 11
14.1 Algoritmos
Um algoritmo um procedimento de passos para clculos e o mesmo composto de
instrues que definem uma funo. At o momento vimos apenas cdigos em C++ para
descrever ideias. Um algoritmo pode descrever ideias para pessoas programarem em outras
linguagens de programao. Abaixo temos a mesma ideia descrita com um cdigo em C++ e
com um algoritmo genrico:
Veja que temos duas descries de uma sequncia de passos para encontrar o maior elemento
de um conjunto. No algoritmo direita, os passos so descritos de maneira geral, de modo
que possam ser utilizadas por qualquer pessoa. No cdigo direta, o conjunto de nmeros est
representado concretamente atravs de arranjos.
No cdigo em C++ a entrada o conjunto de nmeros inteiros representado pelo arranjo a e
a sada o maior elemento desse conjunto. A varivel m guarda o primeiro nmero do arranjo na
linha 3. Uma estrutura de repetio nas linhas 4 a 8 compara se h algum elemento a[i] maior
que m.
De maneira anloga, no algoritmo direita, a varivel m ento o primeiro elemento de A,
ou A1 . Comparamos todos os outros elementos Ai para i = 2, 3, 4, . . . , n com m. O valor de m
substitudo se Ai > m.
190 Captulo 14. Anlise de Algoritmos
8 }
9 return temp ;
10 }
Na linha 3, ele guarda o valor do primeiro elemento do arranjo temp. A varivel temp guarda
o valor do maior elemento encontrado at ento. Na linha 5, ele compara o maior elemento
encontrado at ento temp com todos os outros elementos a[i] do arranjo. O for definido na
linha 4 varia os valores de i entre 1 e n-1.
Para um arranjo de n elementos e uma funo f (n) do nmero de comparaes, temos que o
custo deste algoritmo f (n) = n 1. Todas estas comparaes so feitas na linha 5 do algoritmo.
Repare que a operao relevante desse algoritmo o nmero de comparaes. No caso geral,
no interessante medir o nmero de operaes de atribuio pois elas s ocorrem quando a
comparao da linha 5 retorna true.
qualquer valor de n acima de 4, 5 pode ser considerado um valor grande. Este valor 4, 5 a partir
do qual f (n) domina g(n) uma constante que chamaremos de m.
60 900
f(n) f(n)
g(n) 800 g(n)
50
700
40 600
500
30
400
20 300
200
10
100
0 0
0 1 2 3 4 5 6 7 8 0 5 10 15 20 25 30 35 40 45 50
n n
Figura 14.1: Uma funo f (n) que domina assintoticamente a funo g(n). Cada grfico mostra
uma faixa de valores.
Do ponto de vista de programao, suponha que f (n) e g(n) so funes de custo de
algoritmos A e B. Se para valores grandes de n, f (n) > g(n), sabemos que o algoritmo A
consome mais recursos que B.
Veja na Figura 14.2 o efeito de se multiplicar f (n) por uma constante c que pode ter diversos
valores. No grfico, f (n) foi multiplicado pelas constantes c = {1; 2; 3; 0, 5; 0, 2}. Veja como
mesmo quando multiplicamos f (n) por uma constante, f (n) ainda est acima de g(n) para
valores grandes de n. claro que ao alterar c, temos uma alterao no valor m, que define o que
um valor grande o suficiente. Contudo, f (n) continua dominando assintoticamente g(n). De
fato, se acharmos qualquer constante c para que f (n) esteja acima de g(n), podemos dizer que
f (n) domina g(n) assintoticamente.
300
f(n)
g(n)
2f(n)
250 3f(n)
0,5f(n)
0,2f(n)
200
150
100
50
0
0 1 2 3 4 5 6 7 8 9 10
n
Figura 14.2: Os valores de f (n) multiplicado por diferentes constantes. A funo f (n) domina
assintoticamente a funo g(n) mesmo quando multiplicada por uma constante.
computador 1000 vezes mais rpido para A. Porm, se 0, 001 f (n) > g(n) para valores grandes n,
o algoritmo A ainda seria pior que B. Ainda, se c f (n) > g(n) para valores grandes n e para pelo
menos alguma constante c, o algoritmo A ou pior que B ou pode ser pior que B, caso B seja
executado em um computador mais rpido.
De modo formal, o conceito de dominao assintrica pode ser definido como abaixo:
Definio 14.5.1 Dominao Assinttica. Dizemos que uma funo f (n) domina
assintoticamente outra funo g(n) se existem duas constantes positivas c e m tais que, para
n m, temos que |g(n)| c | f (n)|. Ou seja, para nmeros grandes n > m, c f (n) ser
sempre maior que g(n), para alguma constante c.
14.5.1 Notao O
A notao O utilizada para representar dominao assinttica. Quando uma funo
g(n) = O( f (n)), dizemos que:
g(n) O de f (n)
f (n) domina g(n) assintoticamente
! Quando g(n) = O( f (n)) no correto dizer que g(n) igual a O de f (n), pois a notao
O no indica uma relao de igualdade, e sim de dominao. Dizemos apenas que g(n)
O de f (n).
f (n) = O( f (n))
O mesmo vale para qualquer funo O( f (n)) multiplicada por uma constante c, pois basta
multiplicarmos f (n) por uma constante c2 grande o suficiente para que c2 > c( f (n)):
A soma de duas funes dominadas por f (n) ainda dominada por f (n) pois esta diferena
ainda pode ser compensada por uma constante:
A soma de duas funes, uma dominada por f (n) e outra dominada por g(n), ser dominada
pela maior funo que as domina:
A multiplicao de duas funes ser dominada pela multiplicao das funes que as
dominavam:
14.7 Exerccios
Exerccio 14.1 O que significa dizer que uma funo g(n) O( f (n))?
Exerccio 14.4 Se os algoritmos A e B levam tempo a(n) = n2 n + 549 e b(n) = 49n + 49.
a(n) = O(b(n))? b(n) = O(a(n))? Para quais valores A leva menos tempo para executar do
que B?
196 Captulo 14. Anlise de Algoritmos
Exerccio 14.5 Apresente o esboo de uma algoritmo para obter o maior e o segundo maior
elementos de um conjunto. Apresente uma anlise do algoritmo. Ele eficiente? Porqu?
Exerccio 14.7 Do ponto de vista assinttico, temos a seguite relao de precedncia entre
funes:
n
1 log log n log n n nc nlog n cn nn cc
Onde k 1 e 0 < < 1 < c
Indique se A O de B para os pares abaixo:
A B O
A = logk n B = n
A = nk B = cn
A= n B = nsin n
A = 2n B = 2n/2
A = nlog m B = mlog n
A = log(n!) B = log(nn )
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Busca0em arranjos
0
15.
1
1
0 0 1
0
0 0
1
110 1
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 01 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
1 struct Aluno {
2 string nome ;
3 double nota_prova1 ;
4 double nota_prova2 ;
5 int matricula ;
6 }
Nesta estrutura, cada Aluno ter um nome, uma nota para cada prova (nota_prova1 e
nota_prova2) e um nmero de matrcula (matricula). Como os nmeros de matrcula no se
repetem, eles so bons candidatos a serem o membro chave dos alunos.
Neste caso prtico, sendo a chave de um aluno o seu nmero de matrcula, o problema da
busca em arranjo consiste em encontrar em qual posio de um arranjo a est o aluno com
nmero de matrcula x. Veja o prottipo de uma funo para busca em arranjos na Figura 15.1.
Uma funo que faz busca em arranjos deve receber um valor de chave, um arranjo de elementos
a e o tamanho n deste arranjo. O problema est em fazer com que a funo retorne em qual
posio do arranjo est o elemento com a chave pedida.
Na Figura 15.2 apresentamos o prottipo e exemplo de uso de uma funo para busca em
arranjos de dados do tipo int. Considerando que o prprio nmero representa sua chave, a
funo busca deve retornar em qual posio do arranjo a (que tem tamanho n) est o nmero x,
que 9. No caso, como o elemento 9 se encontra na posio a[4], a funo dever retornar 4.
198 Captulo 15. Busca em arranjos
?
Figura 15.1: Prottipo de uma funo de busca em arranjos.
x 9 a 6 3 8 5 9 2 1
Elemento 9 est na
4 posio 4 do arranjo
Figura 15.2: Prottipo de uma funo de busca em arranjos de dados do tipo int.
O mtodo mais simples para busca , a partir do primeiro elemento, pesquisar os elementos
sequencialmente um-a-um at encontrar a chave desejada. Veja a funo que procura o elemento
chave no arranjo a, que possui tamanho n:
A funo ir retornar um int que dir em qual posio do arranjo a est o elemento chave.
A estrutura de repetio definida nas linhas 2 a 6 ocorre com i de 0 at n-1 para percorrer da
primeira at a ltima posio a[i] do arranjo a.
Na linha 3, comparamos cada elemento do arranjo com o elemento chave. Se o elemento
chave foi encontrado, o valor de i, que tem sua posio nesta iterao da repetio, ento
retornado. J se o elemento chave no foi encontrado e a comparao retornou false, passamos
para a prxima iterao de repetio.
Na linha 7, se em nenhuma das iteraes o elemento foi encontrado, samos do for e
retornamos -1. O retorno de -1 um modo de se avisar para a funo chamadora que o elemento
no foi encontrado.
15.2 Busca binria 199
15.1.1 Anlise
Temos como operao relevante da busca em arranjo o nmero de comparaes para se
encontrar o elemento chave. Em relao ao nmero de comparaes, o melhor caso acontece
quando o elemento que procuramos o primeiro do arranjo. Neste caso, faremos apenas uma
comparao e j retornaremos a posio i. Sendo assim, teramos uma funo de custo f (n) = 1
no melhor caso. Em notao assinttica, f (n) = O(1).
J o pior caso ocorre quando o elemento que procuramos o ltimo do arranjo ou no est
no arranjo. Para um arranjo de n elementos, teramos de fazer n comparaes para encontrar o
ltimo elemento ou descobrir que o elemento no est no arranjo. Assim, temos uma funo de
custo f (n) = n no pior caso. Em notao assinttica, f (n) = O(n).
No caso mdio, precisamos de uma suposio de probabilidades. Vamos supor que o
elemento chave sempre est no arranjo e que ele tem a mesma probabilidade 1/n de estar em
qualquer uma de suas posies. Se (i) encontrar o elemento da posio i tem custo f (n) = i e (ii)
a probabilidade da chave estar em uma posio i 1/n, teremos que nosso caso mdio tem custo
definido pela seguinte equao:
1 1 1 1 1 1 n n+1
f (n) = 1 + 2 + 3 . . . (n 1) + n = i =
n n n n n n i=1 2
Tabela 15.1: Custo do algoritmo de busca sequencial em suas situaes mais comuns
chave 8
a 1 2 3 5 6 8 9 i 3
a 1 2 3 5 6 8 9 i 5
a 1 2 3 5 6 8 9 n 7
a[esq] a[dir]
esq 0 i dir 6
Em seguida, entramos em uma estrutura de repetio definida entre as linhas 5 e 11, onde a
cada iterao o elemento i ser comparado. Na primeira iterao, com a execuo da linha 6, o
ndice i indica o elemento do meio do arranjo.
a 1 2 3 5 6 8 9 n 7
esq 0 i 3 dir 6
Na linha 7, comparamos a chave com o elemento a[[i] e temos que a chave maior que
a[i]. Por este motivo, pesquisaremos agora apenas na metade direita de i e, a partir de agora,
a metade esquerda ser desconsiderada pelo algoritmo. Para isto, atualizamos o valor de esq
na linha 8. Em nosso exemplo, apesar os elementos entre a[4] e a[6] sero considerado a partir
de agora:
a 1 2 3 5 6 8 9 n 7
esq 4 i 3 dir 6
Se a chave fosse menor que a a[i], o contrrio ocorreria, e a metade direita passaria a ser
desconsiderada com a atualizao de dir na linha 10.
Na linha 12, temos duas condies para continuar a estrutura de repetio. A primeira
condio que chave != a[i], ou seja, que o elemento pesquisado i ainda no seja a chave
sendo procurada. A segunda condio para continuar a repetio que esq <= dir, ou seja,
se ainda h elementos para se pesquisar. Os ndices esq e dir vo se movendo a medida que
pesquisamos mais elementos. Se o ndice esq menor ou igual a dir, a repetio deve se
continuar pois isso indica que o arranjo inteiro no foi pesquisado.
Em nosso exemplo, como as condies do lao foram atendidas, na nova iterao do lao,
na linha 6, o ndice i indica a posio o elemento do meio entre os que ainda esto sendo
considerados, ou seja, (esq + dir)/2 = 5:
202 Captulo 15. Busca em arranjos
a 1 2 3 5 6 8 9 n 7
esq 4 i 5 dir 6
Na linha 7, como a[5], ou 8, maior que a chave 6 que procuramos, alteramos dir, na linha
10, para restringir a busca ao lado esquerdo do arranjo:
a 1 2 3 5 6 8 9 n 7
a[esq] a[i]
a[dir]
esq 4 i 5 dir 4
Nas condies de repetio do lao, na linha 12, o elemento a[i] ainda no a chave
que procuramos e os ndices esq e dir no se cruzaram indicando que todo o arranjo j foi
percorrido.
Assim, na prxima iterao, na linha 6, o elemento do meio indicado por i ((esq + dir)/2
= 4) o nico elemento ainda considerado do arranjo.
a 1 2 3 5 6 8 9 n 7
No teste da linha 7, como a chave > a[i] retorna false, deslocamos o ndice dir mais
uma vez. O ndice dir estar esquerda do ndice esq indica que no que no h mais elementos
a se pesquisar.
a 1 2 3 5 6 8 9 n 7
esq 4 i 4 dir 3
15.2 Busca binria 203
Nas condies de parada, na linha 12, nenhuma das duas condies so atendidas. A condio
chave != a[i] false pois o ndice i j aponta para o elemento chave e a condio esq <=
dir tambm false pois os ndices dir e esq j se cruzaram, indicando no h mais elementos
a se pesquisar.
Nas linhas 13 a 17, testamos por qual motivo o lao foi encerrado e a funo faz o retorno. O
primeiro teste da linha 13, verifica se chave == a[i], ou se a chave foi encontrada. Se a chave
foi encontrada ao fim do lao, retornamos o valor de i na linha 14. Em nosso exemplo, teramos
retornado 4.
Se o elemento a[i] no fosse igual chave, seria porque o elemento no estava no arranjo.
Neste caso, retornaramso -1 na linha 16 do cdigo.
15.2.1 Anlise
A busca sequencial um mtodo interessante de busca pois remete a como buscamos palavras
em um dicionrio. Quando pesquisamos em um dicionrio, no passamos palavra por palavra.
Procuramos uma palavra no meio do dicionrio e verificamos se a palavra que procuramos estaria
a frente ou atrs da palavra que encontramos.
Em relao ao nmero de comparaes temos um cenrio similar busca sequencial para
a descrio dos casos. O melhor caso ocorre quando o elemento que procuramos o primeiro
que testamos, ou seja, o elemento no meio do arranjo. Sendo assim, nosso melhor caso seria
f (n) = O(1).
J o pior caso acontece quando o elemento que procuramos o ltimo comparado ou quando
o elemento no est no arranjo. Vejamos o nmero mximo de comparaes que podemos fazer
em uma busca binria. Se no primeiro passo de nossa busca consideramos n elementos, temos
n/2 elementos considerados no segundo passo, n/4 = n/22 elementos considerados no terceiro
passo e assim por diante. Ou seja, no passo k do algoritmo, consideraramos n/2k elementos em
nossa busca, at que o elemento fosse encontrado. No pior caso, faramos estes passos at que
apenas 1 elemento seja considerado.
n n n n
2 3 1
1 2 2 2
Como consideramos n/2k elementos no passo k e apenas 1 elemento no ltimo passo do
pior caso, nosso ltimo passo seria aquele no qual n/2k = 1. Invertendo a equao temos que o
nmero de passos k :
k = log n
Isso nosso pior caso teria O(k) = O(log n) passos no mximo. Se considerarmos que os
elementos esto distribudos de uma forma no tendenciosa no arranjo, nosso caso mdio tambm
ser f (n) = O(log n).
Na Tabela 15.2 temos um resumo do custo do algoritmo de busca binria em seus principais
casos. Tanto o pior caso quando o caso mdio tm custo O(log n) para encontrar um elemento.
Em notao O, mesmo os piores casos so O(log n) pois, a cada passo, o algoritmo elimina
metade do problema. Veja que isto coincide com o comportamento tpico de algoritmos de
complexidade logartmica, como listado na Seo 14.6. Isto o deixa muito mais eficiente em
relao busca sequencial O(n). Veja por exemplo que se n = 1.000.000, temos apenas que
log n = 6.
Uma desvantagem do algoritmo de busca binria que precisamos manter o arranjo ordenado.
Esta operao pode ter um custo muito alto para algumas aplicaes prticas. Isso torna este
204 Captulo 15. Busca em arranjos
Tabela 15.2: Custo do algoritmo de busca binria em suas situaes mais comuns
mtodo mais vantajoso para aplicaes pouco dinmicas, como justamente o caso de dicionrios.
Estudaremos nas Sees seguintes mtodos para os arranjos ordenados.
A Figura 15.4 apresenta o tempo gasto pelos algoritmos de busca sequencial e busca binria
para se encontrar um elemento em um arranjo. A Figura 15.4(a) apresenta resultados para
arranjos com at 3000 elementos enquanto a Figura 15.4(b) apresenta resultados para arranjos
com at 80 mil elementos. Como a busca sequencial tem custo O(n) e a busca binria tem custo
O(log n), vemos que medida que os arranjos ficam maiores, a busca binria se torna cada vez
mais vantajosa. Porm, possvel que para arranjos pequenos, a busca sequencial seja mais
vantajosa. Nestes experimentos, isto ocorre para arranjos com menos de 350 elementos.
6 5
x 10 x 10
1.8 1.5
Busca sequencial Busca sequencial
Busca binria Busca binria
1.6
1.4
1.2 1
Tempo (segundos)
Tempo (segundos)
0.8
0.6 0.5
0.4
0.2
0 0
0 500 1000 1500 2000 2500 3000 0 1 2 3 4 5 6 7 8
Tamanho do arranjo Tamanho do arranjo 4
x 10
Figura 15.4: Tempo necessrio pelos algoritmos de busca para se encontrar um elemento em
arranjos de vrios tamanhos.
15.3 Exerccios
Exerccio 15.1 Analise o cdigo abaixo, que est incompleto.
1 # include < iostream >
2
3 using namespace std ;
4
5 // busca posi o de x no arranjo a de tamanho n
6 int buscasequencial ( int a [] , int n , int x ) ;
7 // ordena o vetor a de tamanho n
8 void ordena ( int a [] , int n ) ;
9
10 int main () {
15.3 Exerccios 205
11 // Tamanho do arranjo
12 int n ;
13 cout << " Digite o tamanho do arranjo : " ;
14 cin >> n ;
15 // ponteiro para o arranjo na mem ria
16 int * v ;
17 // Alocado arranjo tamanho n
18 v = new int [ n ];
19 for ( int i = 0; i < n ; i ++) {
20 cout << " Digite o valor do elemento v [ " << i << " ]:
";
21 cin >> v [ i ];
22 }
23 int chave , posicao = 0;
24 do {
25 cout << " Digite o elemento que deseja buscar : " ;
26 cin >> chave ;
27 posicao = buscasequencial (v , n , chave ) ;
28 if ( posicao == -1) {
29 cout << " O elemento n o existe " << endl ;
30 } else {
31 cout << " Elemento na posic o v [ " << posicao <<
" ] " << endl ;
32 }
33 } while ( chave != -1) ;
34 // La o se repete at que a chave pesquisada seja -1
35 return 0;
36 }
37
38 // Procura a posi o de x no arranjo a de tamanho n
39 int buscasequencial ( int a [] , int n , int x )
40 {
41 for ( int i = 0; i < n ; i ++) {
42 if ( a [ i ]== x ) {
43 return i ;
44 }
45 }
46 return -1;
47 }
48
49 // Ordena o - M todo da Bolha
50 void ordena ( int a [] , int n )
51 {
52 int i ,j , aux ;
53 for ( j = n -1; j >0; j - -)
54 for ( i =0; i < j ; i ++)
55 {
56 if ( a [ i +1] < a [ i ])
57 {
206 Captulo 15. Busca em arranjos
Exerccio 15.2 Crie uma funo de busca binria, que busca o elemento chave em um
arranjo ordenado. Para ordenar o arranjo, use a funcao ordena(int a[], int n, int x).
Exerccio 15.3 Altere todas as funes para que elas calculem tambm o nmero de passos
necessrios para se encontrar (ou no) o elemento.
Exerccio 15.4 Altere o programa main para que sempre que um elemento chave seja
digitado, ele seja pesquisado atraves de busca sequencial e busca binaria.
Exerccio 15.5 Use a alterao para que logo em seguida seja impresso o nmero de passos
necessrios para se encontrar o elemento com cada um dos mtodos, incluindo o passo de
ordenao.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 10
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 01 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 11
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Ordenao
0
16.
1 0 1
0 de0 arranjos
1
0 1
0 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
10
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Desordenado Ordenado
Pedro Alberto
Joo Barbosa
Maria Joaquim
Roberto Joo
Manuel Jos
Jos Manuel
Barbosa Maria
Joaquim Pedro
Alberto Roberto
Tabela 16.1: A visualizao e anlise de um conjunto de elementos ordenado muito mais fcil.
Alm disto, como vimos na Seo 15.2, buscas binrias em arranjos so mais eficientes que
buscas sequenciais. Porm, estas buscas s podem ser feitas em arranjos ordenados. Imagine por
exemplo fazer uma pesquisa em um catlogo telefnico onde temos os nomes das pessoas em
ordem.
As estratgias para se ordenar um arranjo so diversas. Veremos algumas dessas estratgicas
ao longo deste livro. O processo de ordenao pode ser para colocar os itens em ordem crescente
ou decrescente.
Como no caso das buscas em arranjos, os algoritmos so usualmente estendidos em situaes
prticas para ordenar structs por seus membros-chave. Porm, para fins didticos, os algoritmos
de ordenao deste captulo sero apresentados para ordenao de arranjos de tipos de dados
fundamentais.
A Figura 16.1 mostra um exemplo de um arranjo a com dados do tipo int sendo ordenado.
208 Captulo 16. Ordenao de arranjos
a 9 3 5 3 4 4 1
a 1 3 3 4 4 5 9
Figura 16.1: Exemplo de ordenao de arranjos. Neste caso temos um arranjo de dados do tipo
int.
a 9 3 5 3 4 4 1
a 1 3 3 4 4 5 9
De outro modo, a Figura 16.3 mostra um exemplo de ordenao instvel. Note que, apesar
de termos o mesmo resultado, no houve procupao para que os elementos iguais mantivessem
sua ordem relativa.
16.1 Conceitos de ordenao de arranjos 209
a 9 3 5 3 4 4 1
a 1 3 3 4 4 5 9
Motivao
As vantagens de uma ordenao estvel ficam claras quandos fazemos a ordenao de elementos
atravs de seus membros-chave. Suponha que queremos ordenar um arranjo com as seguintes
cartas:
'
'
' '
''
As cartas deste arranjo podiam ser facilmente representadas pelo tipo de dados definido pela
seguinte estrutura:
1 struct Carta {
2 int valor ;
3 int naipe ;
4 }
Nesta estrutura, o valor seria representado por um nmero de 1 a 13 e o naipe seria repre-
sentado por um nmero de 1 a 4. Utilizando ento uma ordenao qualquer pelos nmeros,
podemos ter um resultado como este:
'
'
' '
''
As cartas esto ordenadas agora pelos nmeros, mas a ordem dos naipes arbitrria. Vamos
supor agora que queremos que as cartas estejam ordenadas tambm de acordo com seu naipe.
Precisamos ento ordenar mais uma vez as cartas, agora pelo naipe. Veja o que pode acontecer
se fizermos a segunda ordenao com um algoritmo instvel:
'
'
' '
''
210 Captulo 16. Ordenao de arranjos
Perdemos a ordem de valor das cartas ao ordenar pelos naipes. Claramente, isto no o
que queramos. Veja o que aconteceria se houvssemos ordenado as cartas com um algoritmo
estvel:
'
'
' '
''
Agora temos as cartas ordenadas por seus naipes mas tambm mantivemos a ordenao
anterior por seus valores dentro de capa naipe.
Forando estabilidade
Alguns mtodos mais eficientes de ordenao no so estveis. Porm, podemos forar um
mtodo no estvel a se comportar de forma estvel. Para forar essa estabilidade, adicionamos
um ndice a cada elemento do arranjo. O ndice indica a posio do elemento na posio original.
'
0 1 2 ' 3'
' 4 5
''
Agora, podemos utilizar o ndice como fator de desempate caso os elementos tenham a
mesma chave (ou o mesmo naipe, no nosso exemplo).
Apesar de ser possvel, forar um mtodo no estvel a se tornar estvel diminui a eficincia
dos algoritmos e faz com que o algoritmo gaste memria extra para armazenar todos os ndices.
Como o ndice quer dizer um membro a mais para cada elemento, este custo extra de memria
O(n).
Exemplo
Considere um arranjo de cartas com os seguintes elementos:
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
Este colocado na segunda posio e sabemos que os dois primeiros elementos do arranjo j
esto ordenados.
/ \ (
t
_,/
/ \
(
t
_,/
/ \
(
t
_,/
/ \
(
t
_,/
Como h apenas um elemento no subarranjo no ordenado direita, j sabemos que ele est
em sua posio correta ento podemos encerrar o mtodo.
A Figura 16.4 apresenta de forma resumida os passos deste processo de ordenao por
seleo. Temos um subarranjo ordenado esquerda, representado em amarelo, e um subarranjo
desordenado direita, representado em azul. A cada passo, trocamos o primeiro elemento do
arranjo desordenado com o menor elemento deste arranjo. Trocas esto representadas pelos
valores em vermelho. Assim, cresce a cada passo o subarranjo ordenado esquerda, representado
em amarelo.
16.2 Algoritmos simples de ordenao 213
Arranjo 7 5 4 6 9 8
Passo 1 4 5 7 6 9 8
Passo 2 4 5 7 6 9 8
Passo 3 4 5 6 7 9 8
Passo 4 4 5 6 7 9 8
Passo 5 4 5 6 7 8 9
Arranjo 4 5 6 7 8 9
Algoritmo
A ordenao por seleo para um arranjo de inteiros dada pelo seguinte cdigo:
1 void selecao ( int a [] , int n ){
2 int i , j , min ; // ndices
3 int x ; // elemento
4 // para cada posi o
5 for ( i = 0; i < n - 1; i ++){
6 // procuramos o menor entre i +1 e n e colocamos em i
7 min = i ; // m nimo o i
8 for ( j = i + 1; j < n ; j ++){
9 if ( a [ j ] < a [ min ]){
10 min = j ; // m nimo o j
11 }
12 }
13 // troca a [ i ] com a [ min ]
14 x = a [ min ];
15 a [ min ] = a [ i ];
16 a[i] = x;
17 }
18 }
Na linha 1 do cdigo, como sabemos que arranjos so passados por referncia em C++,
a ordenao ocorrer no arranjo a original e no precisamos retornar nada desta funo, o
que indicado com void. Os ndices i, j e min, declarados na linha 2, sero utilizados para
percorrermos o arranjo e procurar o menor elemento a cada passo do algoritmo.
A varivel x, declarada na linha 3, deve ser do mesmo tipo dos elementos do arranjos pois
ser uma varivel temporria para fazer as trocas entre elementos.
Nas linhas 5 a 17, temos um for que, para cada posio do arranjo, colocar em seu lugar o
elemento mnimo entre os elementos posteriores. Temos abaixo um exemplo de inicializao
214 Captulo 16. Ordenao de arranjos
a 6 3 8 5 9 2 1 n 7
i j min
Na primeira iterao do for definido na linha 5, quando i tem valor 0 procuramos pela
posio do menor elemento nas linhas 6 a 12. Para isto supomos na linha 7 que o menor elemento
deste arranjo est na posio i. Ou seja, no nosso exemplo, supomos por enquanto que o menor
elemento entre as posies 0 e n-1 est na posio 0. Assim, a[i] e a[min] indicam o primeiro
elemento do arranjo ainda desordenado.
a 6 3 8 5 9 2 1 n 7
a[i]
a[min]
i 0 j min 0
No for definido nas linhas 8 a 12, utilizamos j como contador para percorrer os elementos
entre i+1 e n-1 atualizando a posio do menor elemento. Na linha 9, para cada elemento a[i],
se este menor do que o menor que j conhecemos a[min], atualizamos a posio indicada por
min. No nosso exemplo, j percorrer as posies 1 a 6 finalizando com valor 7 e descobriremos
que o menor elemento est em a[6].
a 6 3 8 5 9 2 1 n 7
i 0 j 7 min 6
Nas linhas 13 a 16, trocamos ento os elementos da posio a[i] e a[min], ou a[0] e
a[6] em nosso exemplo. O x utilizado como uma varivel auxiliar para fazer esta troca. Com
isto, temos o fim do primeiro passo. Neste passo, procuramos ento o menor elemento entre a[0]
e a[n-1] e colocamos no lugar de a[0]. Ao fazer esta troca, sabemos agora que o primeiro
elemento j representa um arranjo ordenado, representado aqui em amarelo.
16.2 Algoritmos simples de ordenao 215
a 1 3 8 5 9 2 6 n 7
i 0 j 7 min 6
Na prxima iterao do for mais externo, atualizamos i para 1 e fazemos mais um passo.
Sabemos que o arranjo at a[i] j est ordenado.
a 1 3 8 5 9 2 6 n 7
i 1 j 7 min 6
Esta iterao, nas linhas 7 a 12, procura o menor elemento entre a[i] e a[n-1], variando o
valor de j. O valor de min, em nosso exemplo, ser 5.
a 1 3 8 5 9 2 6 n 7
i 1 j 7 min 5
Este elemento a[min] trocado com a[i] nas linhas 13 a 16, e sabemos agora que o arranjo
at a[i] (ou a[1]) j est ordenado.
a 1 2 8 5 9 3 6 n 7
i 1 j 7 min 5
Na prxima iterao do for mais externo, atualizamos novamente i para 2 e sabemos agora
que o arranjo at a[i] j est ordenado. Achamos o menor entre a[i] e a[n-1], nas linhas 6 a
12, e trocamos a[i] com a[min], nas linhas 13 a 16. Sabemos que o arranjo est ordenado at a
posio a[i].
216 Captulo 16. Ordenao de arranjos
a 1 2 3 5 9 8 6 n 7
i 2 j 7 min 5
Na prxima iterao fazemos o mesmo, tendo i valor 3. Temos o arranjo ordenado entre as
posies a[0] e a[3].
a 1 2 3 5 9 8 6 n 7
a[i] a[j]
a[min]
i 3 j 7 min 3
a 1 2 3 5 6 8 9 n 7
i 4 j 7 min 6
a 1 2 3 5 6 8 9 n 7
a[i] a[j]
a[min]
i 5 j 7 min 5
Veja que no precisamos de uma iterao para o ltimo elemento. Se h apenas um elemento,
sabemos que ele o menor. Sabemos ento que mesmo encerrando o for sem alterar o valor das
variveis, todo os arranjo est ordenado.
16.2 Algoritmos simples de ordenao 217
a 1 2 3 5 6 8 9 n 7
a[i] a[j]
a[min]
i 5 j 7 min 5
n2
f (n) = n i = O(n2 )
i=0
Assim, sabemos que o nmero de comparaes feitas pelo algoritmo O(n2 ). Isto ocorre
para qualquer caso.
Em relao ao nmero de atribuies, cada troca, definida nas linhas 13 a 16, envolve
3 atribuies. Como so feitas n 1 trocas, temos f (n) = 3(n 1) = O(n) atribuies de
movimentao.
Alm das atribuies de movimentao, se considerarmos todas as atribuies, temos a
atualizao da posio menor, na linha 10. Esta ocorre em mdia O(n log n) vezes ao longo
de todo algoritmo, sendo O(log n) vezes por iterao. Se considerarmos estas atribuies, o
custo total de atribuies O(n log n). Note contudo, que as operaes de atribuio para
movimentao so muito mais custosas em situaes prticas.
A grande vantagem da ordenao por seleo que em relao ao custo de movimentao,
temos um custo linear O(n). ento um bom algoritmo onde estas movimentaes por algum
motivo custem muito. Em relao ao nmero de comparaes, que usualmente a operao mais
relevante, o algoritmo interessante apenas para arranjos pequenos, j que tem um custo O(n2 ).
Uma desvantagem do algoritmo que se o arranjo j estiver ordenado, isto no ajuda o
algoritmo, pois o custo de comparaes continua O(n2 ). Isto quer dizer que o algoritmo no tem
adaptabilidade. Outra desvantagem da ordeo por seleo que o algoritmo no estvel pois
a troca entre elementos pode destruir a ordem relativa dos mesmos.
Suponha este exemplo de passo do algoritmo de ordenao por seleo onde trocaremos
o elemento a[i] com o elemento a[min], como fazemos usualmente nas linhas 13 a 16 do
algoritmo. Neste caso, os elementos a serem trocados so 6 e 1.
a 6 3 8 6 9 2 1 n 7
i 0 j 7 min 6
218 Captulo 16. Ordenao de arranjos
Ao fazermos esta troca, o elemento a[i], de valor 6, perde sua ordem relativa entre qualquer
qualquer elemento entre a[i+1] e a[min-1]. Neste caso, o elemento a[i] perdeu sua ordem
relativa com o elemento a[3], que tem o mesmo valor.
a 1 3 8 6 9 2 6 n 7
i 0 j 7 min 6
/ \ (
t _,/
/ \ (
t _,/
/ \ (
t _,/
16.2 Algoritmos simples de ordenao 219
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
( / \
t
_,/
( / \
t
_,/
( / \
t
_,/
( / \
t
_,/
( / \
t
_,/
A Figura 16.5 apresenta de forma resumida os passos deste processo de ordenao por
insero. Logo no passo 1, sabemos que temos um subarranjo ordenado esquerda j que
um subarranjo de apenas 1 elemento um subarranjo ordenado. O subarranjo ordenado est
representado em amarelo, e um subarranjo desordenado direita, representado em azul. A cada
passo, tiramos o primeiro elemento do arranjo desordenado e o inserimos nas posio correta
do subarranjo ordenado que temos at o momento. As trocas esto representadas pelos valores
em vermelho. Veja como pode ser necessrio movimentar vrios elementos em um passo para
colocar um elemento em sua posio correta. Neste processo, cresce em um elemento a cada
passo o subarranjo ordenado esquerda, representado em amarelo.
16.2 Algoritmos simples de ordenao 221
Arranjo 7 5 4 6 9 8
Passo 1 7 5 4 6 9 8
Passo 2 5 7 4 6 9 8
Passo 3 4 5 7 6 9 8
Passo 4 4 5 6 7 9 8
Passo 5 4 5 6 7 9 8
Arranjo 4 5 6 7 8 9
Algoritmo
A ordenao por insero para um arranjo de inteiros dada pelo seguinte cdigo:
a 6 3 8 5 9 2 1 n 7
i j
Entramos em um for na linha 5 que, para cada posio do arranjo, colocar o elementoa[i]
em seu lugar correto do arranjo ordenado de a[0] at a[n-1]. Na primeira iterao, quando
i 1, j sabemos antes de mais nada que o arranjo at a[0], de apenas um elemento, j est
ordenado.
a 6 3 8 5 9 2 1 n 7
a[i]
i 1 j
a 6 3 8 5 9 2 1 n 7
a[j] a[i]
i 1 j 0
a 6 6 8 5 9 2 1 n 7
a[j] a[i]
i 1 j -1
16.2 Algoritmos simples de ordenao 223
a 3 6 8 5 9 2 1 n 7
a[j] a[i]
i 1 j -1
a 3 6 8 5 9 2 1 n 7
a[j] a[i]
i 2 j 1
a 3 6 8 5 9 2 1 n 7
a[j] a[i]
i 2 j 1
a 3 6 8 5 9 2 1 n 7
a[j] a[i]
i 3 j 2
a 3 6 8 8 9 2 1 n 7
a[j] a[i]
i 3 j 1
O while ter mais uma iterao pois ainda no achamos a posio correta de x e nem
deslocamos todos os elementos. Deslocamos o elemento a[j] em uma posio na linha 10 e
decrementamos j na linha 11 para testar a prxima posio.
a 3 6 6 8 9 2 1 n 7
a[j] a[i]
i 3 j 0
Como o elemento x no menor que a[j], encontramos sua posio correta e saimos do
while devido segunda condio.
Na linha 13, o elemento guardado em x ento colocado em sua posio correta a[j+1].
Sabemos que o arranjo est ordenado at a[3].
a 3 5 6 8 9 2 1 n 7
a[j] a[i]
i 3 j 0
Na iterao seguinte do for, incrementamos i para 4. Assim como nas outras iteraes, nas
linhas 6 a 13, elementos maiores que a[i] sero deslocados no arranjo ordenado para a direita e
a[i] ser inserido em sua posio correta. Sabemos que o arranjo est ordenado at a[4].
16.2 Algoritmos simples de ordenao 225
a 3 5 6 8 9 2 1 n 7
a[j] a[i]
i 4 j 3
Em seguida, incrementamos i para 5 e, novamente nas linhas 6 a 13, inserimos a[i] em sua
posio correta no arranjo ordenado. Elementos maiores que a[i] sero deslocados no arranjo
ordenado e a[i] ser inserido em sua posio correta. Sabemos que o arranjo est ordenado at
a[5].
a 2 3 5 6 8 9 1 n 7
a[j] a[i]
i 5 j -1
Na ltima iterao, onde i tem valor 6. Inserimos a[i] novamente em sua posio correta
do arranjo e sabemos agora que todo o arranjo est ordenado.
a 1 2 3 5 6 8 9 n 7
a[j] a[i]
i 6 j -1
Atingimos assim nosso critrio de parada quando i chega a 7, encerrando assim a funo.
Anlise
Existem duas condies que terminam o lao mais interno do algoritmo, definido na linha 9:
Termos analisado todos os elementos
Termos encontrado a posio correta do elemento
Assim, em relao aos casos possveis para o lao interno temos o melhor caso quando
feita apenas uma comparao, pois o elemento j est em sua posio correta O(1). Temos o pior
caso, quando i comparaes so feitas at testarmos todas as posies, levando a custo O(i) para
comparar e deslocar i elementos. E o caso mdio, se supormos a mesma probabilidade de um
elemento estar em qualquer posio, temos um custo (1/i)(1 + 2 + 3 + i) = (i + 1)/2 = O(i).
J no lao mais externo, definido na linha 5, temos sempre n 1 iteraes que incluem uma
execuo do lao interno. Assim, no melhor caso, executaremos n 1 vezes o melhor caso do
lao interno O(1) e teremos uma lao externo com custo (n 1)O(1) = O(n). Se tivssemos
sempre o pior caso O(i) no lao interno, o algoritmo tem um custo n1 2
i=1 O(i) = O(n ). De modo
anlogo, no caso mdio, que tambm tem custo O(i) no lao interno, o algoritmo tambm teria
um custo total O(n2 ).
226 Captulo 16. Ordenao de arranjos
Tabela 16.2: Custo do algoritmo de ordenao por insero em suas situaes mais comuns
Algoritmo
Seleo Insero
Comparaes
Caso mdio O(n2 ) O(n2 )
Pior caso O(n2 ) O(n2 )
Melhor caso O(n2 ) O(n)
Movimentaes
Caso mdio O(n) O(n2 )
Pior caso O(n) O(n2 )
Melhor caso O(n) O(n)
Caractersticas
Estabilidade No Sim
Adaptabilidade No Sim
3.5
2
Tempo (segundos)
Tempo (segundos)
2.5
1
1.5
0.5
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.6: Tempo necessrio para se ordenar um arranjo com ordem inicial aleatria (caso
mdio).
Na Figura 16.7 temos a proporo entre o tempo necessrio para se ordenar um arranjo com
cada um dos algoritmos. A linha verde, com valor 1, representa o tempo gasto pela ordenao por
insero. A linha azul, representa quantas vezes mais tempo levou uma ordenao por seleo.
Em proporo, para alguns tamanhos de arranjo, a ordenao por seleo chega a ser quase 18
vezes mais lenta que a ordenao por insero.
J o pior caso da ordenao por insero ocorre quando os arranjos esto em ordem descres-
cente, por temos nestes casos mais deslocamentos de elementos. A Figura 16.8 apresenta o tempo
necessrio por cada algoritmo para ordenar um arranjo de elementos em ordem decrescente.
Mesmo nestes casos especficos, a ordenao por insero consegue ser mais vantajosa que uma
ordenao por seleo, apesar da diferena de desempenho entre os algoritmos ser menor.
Na Figura 16.9 temos a proporo entre o tempo necessrio para se ordenar um arranjo em
ordem decrescente com cada um dos algoritmos. A linha verde, com valor 1, representa o tempo
gasto pela ordenao por insero. A linha azul, representa quantas vezes mais tempo levou uma
228 Captulo 16. Ordenao de arranjos
6 18
Seleo
Seleo Insero
Insero 16
5
Tempo (em proporo ordenao por seleo)
4 12
10
3
8
2 6
4
1
2
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.7: Proporo entre tempo necessrio para se ordenar um arranjo com ordem inicial
aleatria (caso mdio).
4
x 10
4.5
Seleo
Insero Seleo
4 Insero
3.5
2 3
Tempo (segundos)
Tempo (segundos)
2.5
1 1.5
0.5
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.8: Tempo necessrio para se ordenar um arranjo com ordem decrescente (pior caso
para a ordenao por insero).
ordenao por seleo. Em proporo, para alguns tamanhos de arranjo, mesmo no pior caso
da ordenao por insero, a ordenao por seleo chega a ser quase 9 vezes mais lenta que a
ordenao por insero.
Por fim, o melhor caso da ordenao por insero ocorre quando os arranjos esto em
ordem crescente, pois nestes casos no temos deslocamentos de elementos. O algoritmo apenas
confere a ordenao do arranjo e encerra. A Figura 16.10 apresenta o tempo necessrio por
cada algoritmo para ordenar um arranjo de elementos em ordem decrescente. O desempenho da
ordenao por insero nestes casos muito melhor.
Na Figura 16.11 temos a proporo entre o tempo necessrio para se ordenar um arranjo em
ordem crescente com cada um dos algoritmos. A linha verde, com valor 1, representa o tempo
gasto pela ordenao por insero. A linha azul, representa quantas vezes mais tempo levou
uma ordenao por seleo. Em proporo, para alguns tamanhos de arranjo apresentados, a
ordenao por insero em seu melhor caso chega a ser quase 2.500 vezes mais rpida que a
ordenao por seleo. Esta diferena maior a medida que os arranjos sejam maiores.
16.2 Algoritmos simples de ordenao 229
8 9
Seleo Seleo
Insero Insero
7 8
Tempo (em proporo ordenao por seleo)
6
5
5
4
4
3
3
2
2
1 1
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.9: Proporo entre tempo necessrio para se ordenar um arranjo com ordem decrescente
(pior caso para a ordenao por insero).
4
x 10
5
Seleo
Seleo
Insero
4.5 Insero
3.5
2
Tempo (segundos)
Tempo (segundos)
2.5
1
1.5
0.5
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.10: Tempo necessrio para se ordenar um arranjo com ordem decrescente (melhor
caso para a ordenao por insero).
Vemos ento como a diferena real entre algoritmos para a mesma tarefa pode ser significativa
em aplicaes prticas. Como previmos com nossa medida por modelos matemticos que o
algoritmo por insero teria um melhor caso O(n), j era esperado que houvesse grande diferena
de desempenho em relao ordenao seleo, que tem custo O(n2 ) para todos os casos.
16.2.4 Exerccios
Exerccio 16.1 Analise o cdigo abaixo, que est incompleto.
1 # include < iostream >
2 # include < stdlib .h >
3 # include < time .h >
4
5 using namespace std ;
6
7 void learranjo ( int a [] , int n );
230 Captulo 16. Ordenao de arranjos
30 2500
Seleo Seleo
Insero Insero
25
Tempo (em proporo ordenao por seleo)
20
1500
15
1000
10
500
5
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.11: Proporo entre tempo necessrio para se ordenar um arranjo com ordem crescente
(melhor caso para a ordenao por insero).
39 embaralhaarranjo (v , n );
40 imprimearranjo (v , n );
41 cout << " Ordenando pelo metodo ??? " << endl ;
42 ordena1 (v , n );
43 imprimearranjo (v , n );
44
45 cout << " Embaralhando " << endl ;
46 embaralhaarranjo (v , n );
47 imprimearranjo (v , n );
48 cout << " Ordenando pelo metodo ??? " << endl ;
49 ordena2 (v , n );
50 imprimearranjo (v , n );
51
52 return 0;
53 }
54
55 void embaralhaarranjo ( int a [] , int n ) {
56 int pos ,i , aux ;
57 for ( i =0; i < n ; i ++){
58 pos = rand () % n + 0; // posicao entre 0 e n -1
59 aux = a [ i ];
60 a [ i ] = a [ pos ];
61 a [ pos ] = aux ;
62 }
63 }
64
65 void learranjo ( int a [] , int n )
66 {
67 for ( int i = 0; i < n ; i ++){
68 cout << " Digite o elemento v [ " << i << " ]: " ;
69 cin >> a [ i ];
70 }
71 }
72
73
74 void imprimearranjo ( int a [] , int n )
75 {
76 cout << " v = " ;
77 for ( int i = 0; i < n ; i ++){
78 cout << a [ i ] << " " ;
79 }
80 cout << endl ;
81 }
82
83 void ordena ( int a [] , int n )
84 {
85 int i ,j , aux ;
86 for ( j = n -1; j >0; j - -)
87 for ( i =0; i < j ; i ++)
232 Captulo 16. Ordenao de arranjos
88 {
89 if ( a [ i +1] < a [ i ])
90 {
91 // trocar a [ i ] com a [ i +1]
92 aux = a [ i ];
93 a [ i ] = a [ i +1];
94 a [ i +1] = aux ;
95 }
96 }
97 }
98
99 void ordena1 ( int a [] , int n )
100 {
101 int i , j , pos_min , aux ;
102 for ( i = 0; i < n - 1; i ++)
103 {
104 pos_min = i ;
105 for ( j = i + 1; j < n ; j ++)
106 {
107 if ( a [ j ] < a [ pos_min ])
108 {
109 pos_min = j ;
110 }
111 }
112 // troca a [ i ] com a [ min ]
113 aux = a [ pos_min ];
114 a [ pos_min ] = a [ i ];
115 a [ i ] = aux ;
116 }
117 }
118
119 void ordena2 ( int a [] , int n )
120 {
121 int i , j , aux ;
122 for ( i = 1; i < n ; i ++)
123 {
124 aux = a [ i ];
125 j = i - 1;
126 while ( aux < a [ j ] && j >= 0)
127 {
128 a [ j +1] = a [ j ];
129 j - -;
130 }
131 a [ j +1] = aux ;
132 }
133 }
16.3 Algoritmos eficientes de ordenao 233
Exerccio 16.2 Renomeie as funcoes ordena1 e ordena2 para o nome correto dos mtodos
de ordenao.
Exerccio 16.3 Crie um struct do tipo Aluno com nome e numero de matricula de cada
aluno.
Exerccio 16.4 Faa com que as funes de ordenao ordenem agora arranjos de elementos
do tipo Aluno de acordo com seus nmeros de matrcula.
Exerccio 16.5 Faa com que as funes de ordenao retornem o nmero de comparaes
realizadas em vez de void.
Exerccio 16.6 Compare o nmero de comparaes realizadas por cada mtodo de ordenao
para diferentes tamanhos de arranjo. Para que isto seja possvel em arranjo grandes, faa
com que os elementos sejam atribudos automaticamente ao arranjo. Utilize a funo rand(),
apresentada na funo embaralhaarranjo.
Exemplo
Considere um arranjo de cartas com os seguintes elementos:
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
Por definio, sabemos que cada um destes arranjos de 1 elemento um arranjo ordenado.
Iniciaremos agora a fase de intercalao destes arranjos ordenados. Os dois primeiros arranjos
ordenados so intercalados em um novo arranjo ordenado.
/ \ (
t
_,/
/ \ (
t
_,/
16.3 Algoritmos eficientes de ordenao 235
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
( / \
t
_,/
A Figura 16.12 apresenta de forma resumida os passos deste processo de ordenao com o
merge sort. Na etapa de diviso, os arranjos desordenados so divididos at que formem arranjos
ordenados de apenas 1 elemento. Estes arranjos ordenados so representados em amarelo. A
cada passo de intercalao, ou conquista, intercalamos arranjos ordenados em novos arranjos
ordenados maiores.
236 Captulo 16. Ordenao de arranjos
Diviso 7 5 3 4 6 2 9 8
7 5 3 4 6 2 9 8
7 5 3 4 6 2 9 8
7 5 3 4 6 2 9 8
Intercalao
5 7 3 4 2 6 8 9
3 4 5 7 2 6 8 9
2 3 4 5 6 7 8 9
Desordenado Ordenado
Algoritmo de intercalao
Em sua implementao mais usual, a etapa de intercalao de dois arranjos requer um arranjo
auxiliar, onde sero colocados os itens. Os elementos so intercalados em um arranjo auxiliar e
ento so transferidos de volta para o arranjo de onde vieram: o arranjo sendo ordenado. Isto
apresentado na Figura 16.13
Assim, para se intercalar n elementos, precisamos de uma memria extra O(n) para o arranjo
auxiliar.
Temos abaixo o algoritmo de intercalao:
1 void intercala ( int a [] , int n ) {
2 // arranjo tempor rio para intercala o
3 int * temp = new int [ n ];
4 // ndice que marca o meio do arranjo
5 int meio = n / 2;
6 // ndices para a estrutura de repeti o
7 int i , j , k ;
8 // ndice i para itens do primeiro arranjo
9 i = 0;
10 // ndice j para itens do segundo arranjo
11 j = meio ;
12 // ndice k para itens no arranjo tempor rio
13 k = 0;
14 // enquanto i e j n o chegam ao fim dos arranjos
15 while (( i < meio ) && ( j < n )){
16.3 Algoritmos eficientes de ordenao 237
5 7 3 4
3 4 5 7
3 4 5 7
a 3 4 5 7 2 6 8 9 n 8
meio 4
temp[k]
Na funo os elementos de a[0] at a[3] (ou a[meio-1]) sero intercalados com os elementos
de a[4] (ou a[meio]) a a[7] (ou a[n-1]). Por isto a condio de repetio na linha 15 que i
ainda no tenha chegado a meio e j no tenha chegado a n.
Na linha 17, sempre escolhemos o menor elemento entre a[i] e a[j] para guardarmos
em temp[k]. Nas linhas 18 e 19, guardamos o valor do primeiro subarranjo em temp e
incrementamos i para marcar qual o prximo elemento a ser copiado deste arranjo. Nas linhas
21 e 22, guardamos o valor do segundo subarranjo em temp e incrementamos j. O ndice k
sempre incrementado para marcar a prxima posio do arranjo temporrio. Na primeira iterao,
por exemplo, como a[j] menor que a[i], copiaremos a[j] para temp[k] e incrementaremos
os ndices j e k.
a 3 4 5 7 2 6 8 9 n 8
meio 4
temp i 0 j 5 k 1
temp[k]
16.3 Algoritmos eficientes de ordenao 239
Este processo das linhas 16 a 24 continua se repetindo at que i atinja meio ou j atinja n. O
valor 3, por ser menor que 6, inserido no arranjo temporrio temp.
a 3 4 5 7 2 6 8 9 n 8
meio 4
2 3 4 5 6 7
temp[k]
a 3 4 5 7 2 6 8 9 n 8
meio 4
temp i 2 j 5 k 3
2 3 4
temp[k]
Em nosso exemplo, a repetio se encerrar quando i atinjir o valor de meio, indicando que
todos os elementos do primeiro subarranjo ordenado j esto em temp.
a 3 4 5 7 2 6 8 9 n 8
meio 4
2 3 4 5 6 7
temp[k]
240 Captulo 16. Ordenao de arranjos
Ao sair do lao de repetio, temos um teste i == meio na linha 27. Este teste nos diz se a
condio de repetio encerrou porque o primeiro subarranjo foi todo percorrido por i (i ==
meio) ou porque o segundo subarranjo foi todo percorrido por j (j == n). Em nosso exemplo,
a primeira condio verdadeira, o que nos leva para o bloco de comandos entre as linhas 28 e
33. Isso quer dizer que os outros elementos do segundo arranjo ainda precisam ser copiados.
Para copiar os elementos restantes do outro subarranjo, temos uma estrutura de repetio
while que percorrer todas as posies a[j] guardando estes elementos em temp[k]. Ao fim
do processo, i sempre ser meio e j sempre ser n.
a 3 4 5 7 2 6 8 9 n 8
meio 4
a[meio] a[j]
a[i] a[n]
temp i 4 j 8 k 8
2 3 4 5 6 7 8 9
temp[k]
Note que se o segundo subarranjo tivesse sido todo copiado primeiro, os comandos entre as
linhas 36 e 43 seriam executados para copiar os elementos restantes do primeiro subarranjo de
maneira anloga.
Ao chegar neste ponto, o vetor temp tem todos os elementos intercalados. Ao fim do cdigo,
nas linhas 44 a 50, copiamos os elementos de temp de volta para a e desalocamos o arranjo
apontado por temp.
a 2 3 4 5 6 7 8 9 n 8
meio 4
a[i] a[n]
a[meio] a[j]
temp i 4 j 8 k 8
2 3 4 5 6 7 8 9
temp[k]
Note que importante desalocar o arranjo apontado por temp. Se o arranjo no for desalo-
cado, temos um erro de vazamento de memria, pois o arranjo foi alocado dinamicamente e no
pertence ao escopo da funo.
Nos nossos exemplos de alocao dinmica de arranjos, na Seo 11.3, fizemos com que
os ponteiros apontassem para nullptr aps desalocar a memria apontada por eles. Neste
caso, no precisamos fazer com que temp receba nullptr pois temp deixar de existir logo na
prxima linha, ao fim da funo.
16.3 Algoritmos eficientes de ordenao 241
Algoritmo de ordenao
A utilidade de funes justamente quebrar problemas em problemas menores. Usando
a funo apresentada de intercalao como uma funo auxiliar, podemos definir de maneira
simples a funo de ordenao com o merge sort. Essa funo de ordenao pode ser facilmente
expressa em termos recursivos.
Utilizando a funo de intercalao como uma funo auxiliar, esta funo recursiva ordena
um arranjo com o mtodo Merge Sort.
1 void mergeSort ( int a [] , int n ) {
2 int meio ;
3 if ( n > 1) {
4 meio = n / 2;
5 mergeSort (a , meio );
6 mergeSort ( a + meio , n - meio );
7 intercala (a , n );
8 }
9 }
Na linha 4, dividimos o arranjo ao meio. Na linha 5, usamos o prprio mtodo recursivamente
para ordenar o arranjo at a metade. Na linha 6, agora usamos o prprio mtodo recursivamente
para ordenar o arranjo da metade at o final. Na linha 7, intercalamos os dois subarranjos
ordenados em a com a funo de intercalao que j deve estar declarada.
Como vimos na Seo 9.11, sobre recurso, as funes recursivas precisam de um passo
recursivo e um caso base. O caso base garante que no entraremos em uma recurso infinita.
Veja que o passo recursivo acontece sempre que chamamos a prpria funo mergeSort para
um arranjo menor que o original. J a condio da linha 3 garante que o caso base ocorra. Esta
condio garante que o passo recursivo s ser executado para arranjos maiores que 1. Para
arranjos de tamanho 0 ou 1, temos nosso caso base, pois o arranjo j est ordenado e basta no
executar instruo alguma.
Em nosso exemplo, a funo ser executada ento nas primeira metade do arranjo a[0] a
a[3] e na segunda metade a[4] a a[8].
mergeSort(&a[0], 8)
a 7 5 3 4 6 2 9 8 n 8
meio 4
a 7 5 3 4 6 2 9 8 n 4
meio 2
a 5 7 3 4 6 2 9 8 n 2
meio 1
a 5 7 3 4 6 2 9 8 n 4
meio 2
Na linha 6, esta funo chama mergeSort para o arranjo que se inicia no endereo a + meio
e tem tamanho n-meio.
mergeSort(&a[2], 2)
a 5 7 3 4 6 2 9 8 n 2
meio 1
a 5 7 3 4 6 2 9 8 n 4
meio 2
a 3 4 5 7 6 2 9 8 n 4
meio 2
a 3 4 5 7 6 2 9 8 n 8
meio 4
a 3 4 5 7 2 6 7 9 n 8
meio 4
Basta agora intercalar as duas metades ordenadas do arranjo, o que feito na linha 7.
mergeSort(&a[0], 8)
a 2 3 4 5 6 7 8 9 n 8
meio 4
Anlise
Uma pea fundamental do algoritmo a intercalao de dois arranjos. Esta intercalao tem
um custo O(n) pois passamos 1 vez por cada elemento o colocando-o no arranjo temporrio.
Como cada etapa de intercalao custa O(n), o custo total do algoritmo merge sort depende
ento do nmero de intercalaes feitas. Para isto, analisemos todos os passos de intercalao
apresentados na Figura 16.14.
No exemplo note que houve k = 3 passos de intercalao para n = 8 elementos sendo
ordenados. Em cada passo de intercalao, temos um custo O(n). Assim, o custo total do
algoritmo O(nk). Nos resta saber o nmero k de passos.
244 Captulo 16. Ordenao de arranjos
7 5 3 4 6 2 9 8
5 7 3 4 2 6 8 9
3 4 5 7 2 6 8 9
2 3 4 5 6 7 8 9
2k = n k = lg n
Sendo assim, precisamos de k = lg n passos para encerrar o algoritmo com n elementos.
Assim, o custo total do algoritmo merge sort O(nk) = O(n log n) para qualquer caso.
Como vimos na Seo 14.6, sobre classes de algoritmo, o algoritmo merge sort um
algoritmo tpico da classe loglinear, pois um algoritmo que quebra o problema em problemas
menores, faz uma operao para cada um dos elementos e depois combina os resultados.
Sendo assim, a complexidade do mtodo O(n log n). Uma vantagem do mtodo sua
complexidade constante para o pior caso e melhor caso. Sua maior desvantagem a necessidade
de memria extra O(n) na fase de intercalao e nas chamadas recursivas da funo. Dessa
forma, esse o algoritmo a ser usado quando queremos uma ordem de complexidade baixa,
constante, mas que a memria no seja um problema. Ele tambm um algoritmo estvel, pois a
fase de intercalao no desfaz a ordem relativa entre elementos de mesma chave.
16.3.2 Quicksort
Para uma ampla variedade de situaes, o quicksort o mtodo mais rpido que se conhece.
Assim como o merge sort, o quicksort tambm definido de maneira recursiva. A cada passo do
Quicksort, o problema de ordenao dividido em dois problemas menores que so ordenados
de maneira independente.
A parte mais complexa do mtodo a partio do problema em problemas menores. Esse
processo de partio feito a partir da escolha de um piv x. Assim que escolhido o piv, um
arranjo dividido em duas partes: (i) o subarranjo da esquerda, com elementos menores ou
iguais a x, e (ii) o subarranjo da direita com elementos maiores ou iguais a x.
Exemplo
Considere um arranjo de cartas com os seguintes elementos:
/ \ (
t
_,/
16.3 Algoritmos eficientes de ordenao 245
/ \ (
t
_,/
Particionamos o arranjo de modo que elementos menores que o piv fiquem esquerda e
elementos maiores fiquem direita.
( / \
t
_,/
( / \
t
_,/
( / \
t
_,/
( / \
t
_,/
( / \
t
_,/
( / \
t
_,/
Com todos os subarranjos ordenados, sabemos que o arranjo original est ordenado.
( / \
t
_,/
Particionamento
Para fazer o particionamento de um arranjo no quicksort:
1. Escolhemos um piv x
2. Percorremos o arranjo a partir da esquerda at que a[i] >= x
3. Percorremos o arranjo a partir da direita at que a[j] >= x
4. Se i e j no tiverem cruzado, trocamos os elementos de a[i] com a[j]
5. Incrementamos i, decrementamos j e continuamos do passo 2 at que i e j se cruzem
Como exemplo, considere novamente o arranjo de cartas com os seguintes elementos:
16.3 Algoritmos eficientes de ordenao 247
/ \ (
t
_,/
/ \ (
t
_,/
/ \ (
t
_,/
a[i] a[j]
Movimentamos i para a direita at encontrar um elemento maior ou igual a x. Neste caso, o
elemento o prprio 7.
/ \ (
t
_,/
a[i] a[j]
248 Captulo 16. Ordenao de arranjos
/ \ (
t
_,/
a[i] a[j]
( / \
t
_,/
a[i] a[j]
( / \
t
_,/
a[i] a[j]
( / \
t
_,/
a[i] a[j]
( / \
t
_,/
a[i] a[j]
( / \
t
_,/
a[i] a[j]
( / \
t
_,/
a[i]
a[j]
( / \
t
_,/
a[j] a[i]
Deslocamos j at encontrar um elemento a[j] menor ou igual a x. Este elemento j o 3.
( / \
t
_,/
a[j] a[i]
Desta vez, porm, os valores de i e j se cruzaram e por isto no faremos a troca e encerramos
a funo. Os itens da esquerda at o elemento a[j] formam um subarranjo com elementos
menores ou iguais ao piv. Os itens da direita a partir do elemento a[i] formam um subarranjo
com elementos maiores ou iguais ao piv.
16.3 Algoritmos eficientes de ordenao 251
subarranjo 1 subarranjo 2
( / \
t _,/
a[j] a[i]
Em todos os exemplos at o momento, o piv foi escolhido como o elemento na posio
(i+j)/2.
A Figura 16.15 apresenta de forma resumida os passos deste processo de partio, que coloca
os elementos menores esquerda e elementos maiores direita. A cada passo percorremos da
esquerda para a direita e da direita para a esquerda procurando elementos que so maiores e
menores que o piv. Trocamos estes elementos at que os ndices se cruzem. Veja que os dois
subarranjos formados no so sempre do mesmo tamanho.
Passo 7 5 3 4 6 2 9 8
1 2 5 3 4 6 7 9 8
2 2 4 3 5 6 7 9 8
3 2 4 3 5 6 7 9 8
Movimentao Piv
Passo 7 5 3 4 6 2 9 8
1 2 4 3 5 6 7 9 8
2 2 3 4 5 6 7 9 8
3 2 3 4 5 6 7 8 9
Subarranjo Ordenado
Desordenado Piv
4 j = dir ;
5 x = a [( i + j ) / 2];
6 do {
7 while ( x > a [ i ]){
8 ++ i ;
9 }
10 while ( x < a [ j ]){
11 --j ;
12 }
13 if ( i <= j ){
14 temp = a [ i ];
15 a [ i ] = a [ j ];
16 a [ j ] = temp ;
17 ++ i ; --j ;
18 }
19 } while ( i <= j );
20 }
O prottipo da funo recebe o arranjo a e as posies esq e dir entre as quais o arranjo
deve ser particionado. Os ndices i e j so passados por referncia pois ao final da funo
devero marcar onde comeam e terminam os subarranjos criados.
O lao interno do algoritmo de partio, definido entre as linhas 7 e 18, muito simples. Por
isto, o algoritmo quicksort to rpido.
Na linha 1, a funo de partio recebe vrios parmetros. Os parmetros esq e dir indicam
qual subarranjo de a[] queremos particionar. Os parmetros i e j so passados por referncia.
Eles pertencem originalmente funo chamadora do quicksort. Ao fim do algoritmo, estas
referncias i e j diro funo chamadora quais so os sub-arranjos particionados. Todo o
arranjo a[] tambm enviado funo. Como sabemos, arranjos so endereos na memria e
por isto so apenas enviados por referncia.
16.3 Algoritmos eficientes de ordenao 253
Na linha 2, criamos ento a varivel x, para guardar o elemento piv, e uma varivel
temporria temp para fazer trocas. Nas linhas 3 e 4, os ndices i e j so inicializados nos
extremos do arranjo a ser particionado. O elemento do meio ento escolhido como piv na
linha 5. De acordo com a estratgia, outro piv poderia ter sido escolhido.
Neste exemplo, particionaremos todo um arranjo de a[0] a a[7].
a 7 5 3 4 6 2 9 8 i 0 j 7
esq 0 dir 7
x 4 temp
Neste grande lao entre as linhas 6 e 19, vamos fazer as trocas enquanto os ndices i e j no
tenham se cruzado. Nas linha 7 a 9, deslocamos o ndice i at encontrar o primeiro elemento
a[i] maior ou igual ao x. Nas linhas 10 a 12, deslocamos o ndice j at encontrar o primeiro
elemento a[j] menor ou igual a x.
a 7 5 3 4 6 2 9 8 i 0 j 5
esq 0 dir 7
a[i] a[j]
x 4 temp
Na linha 13, como nesse deslocamento os ndices no se cruzaram, as linhas 14 a 16, trocam
a[i] com a[j]. Na linha 17, deslocamos i e j em mais uma posio.
a 2 5 3 4 6 7 9 8 i 1 j 4
esq 0 dir 7
a[i] a[j]
x 4 temp 7
a 2 5 3 4 6 7 9 8 i 2 j 2
esq 0 dir 7
a[j]
a[i]
x 4 temp 5
a 2 5 3 4 6 7 9 8 i 3 j 2
esq 0 dir 7
a[j] a[i]
x 4 temp 5
Na linha 19, a segunda constatao de que os ndices j se cruzaram encerra a funo. Como
resultado, temos dois subarranjos. Um de a[0] at a[2] (ou a[esq] a a[j]) e outro de a[3] at
a[7] (a[i] a a[dir]).
Como i e j foram passados por referncia, a funo chamadora tem acesso aos valores
calculados de i e j.
Algoritmo de ordenao
No prottipo da funo, da linha 1, esta funo ordena o arranjo a[] entre as posies esq
e dir. Para ordenar todo arranjo, chamamos: ordenar(0, 7, a). Na linha 2, criamos ento
os ndices i e j, que indicaro quais so os sub-arranjos aps a partio.
16.3 Algoritmos eficientes de ordenao 255
ordenar(0, 7, a)
a 7 5 3 4 6 2 9 8 i j
esq 0 dir 7
ordenar(0, 7, a)
a 2 4 3 5 6 7 9 8 i 3 j 2
esq 0 dir 7
a[j] a[i]
Se o valor de j atingir o valor de esq, como testado na linha 4, o sub-arranjo tem apenas
um elemento e no precisa ser ordenado. Como o sub-arranjo de a[0] a a[2] tem mais de 1
elemento, usamos a prpria funo quicksort(0, 2, a) para orden-lo. A funo chamadora
vai para a pilha de funes e executamos a funo que ordenar a[] das posies 0 a 2. Os
ndices so criados e o sub-arranjo particionado.
ordenar(0, 2, a)
a 2 3 4 5 6 7 9 8 i 2 j 1
esq 0 dir 2
a[j] a[i]
Na linha 5, esta funo jogada na pilha e chamamos a funo de ordenao para o subarranjo
de a[esq] at a[j]. Os ndices so criados e o sub-arranjo particionado. Como o subarranjo
da esquerda no tem nenhum elemento, no executamos esta ordenao. E como o subarranjo da
direita tem tambm apenas 1 elemento, damos este sub-arranjo como ordenado. Ao fim desta
funo, temos o sub-arranjo de 0 at 1 ordenado e voltaremos a com a funo do topo da pilha.
ordenar(0, 1, a)
a 2 3 4 5 6 7 9 8 i 1 j -1
esq 0 dir 1
a[j] a[i]
a 2 3 4 5 6 7 9 8 i 2 j 1
esq 0 dir 2
a[j] a[i]
Voltamos para a funo ordena(0, 7, a) do topo da pilha. Esta funo, na linha 7, chama
ento a ordenao dos elementos de a[3] at a[7].
ordenar(0, 7, a)
a 2 3 4 5 6 7 9 8 i 3 j 2
esq 0 dir 7
a[j] a[i]
a 2 3 4 5 6 7 9 8 i 6 j 4
esq 0 dir 7
a[j] a[i]
Com uma chamada recursiva, ordenamos a[3] e a[4]. Criamos os ndices e particionamos.
Como nenhum dos subarranjos tm mais de um elemento, damos todos os elementos esto
ordenados. A funo que sai da pilha ainda precisa ordenar os elementos de a[6] at a[7]. Esta
funo tambm leva a dois subarranjos de tamanho 1, que por isto j esto ordenados, que por
isso j esto ordenados. Sendo assim, a funo corrente sai da pilha.
ordenar(3, 7, a)
a 2 3 4 5 6 7 8 9 i 6 j 4
esq 3 dir 7
a[j] a[i]
A funo original tambm sai da pilha, finalizando o mtodo com o arranjo original ordenado.
ordenar(0, 7, a)
a 2 3 4 5 6 7 8 9 i 3 j 2
esq 0 dir 7
a[j] a[i]
16.3 Algoritmos eficientes de ordenao 257
Um inconveniente deste mtodo que precisamos passar para a funo trs parmetros
quando queremos ordenar todo o arranjo. Para no precisamos fazer isto, criamos um mtodo
auxiliar que chamado para ordenar todo o arranjo, iniciando de a[0].
1 void quicksort ( int a [] , int n ) {
2 ordenar (0 , n -1 , a );
3 }
Deste modo, mantemos o padro com os outros mtodos de ordenao.
Anlise
O quicksort tambm um tpico exemplo de algoritmo O(n log n), ou loglinear. Algoritmos
tpicos desta classe so os que dividem um problema em dois problemas menores e depois os
juntam fazendo uma operao em cada um dos elementos.
Anlise do piv
A escolha do piv um fator determinante no desempenho do algoritmo pois dependemos dele
para realmente dividir o arranjo em dois subarranjos de tamanho similar. Imagine um algoritmo
onde o piv escolhido sempre o menor elemento do conjunto de n elementos.
a 7 5 3 4 6 2 9 8
a 2 5 3 4 6 7 9 8
a 2 3 5 4 6 7 9 8
a 2 3 4 5 6 7 9 8
Apesar de ser uma situao muito rara, com a pior escolha de piv a cada passo, precisaramos
de n passos de partio para ordenar todo o arranjo. Como a cada passo estamos achando o
menor elemento do arranjo e o colocando em sua posio correta, este pior caso do quicksort
equivale exatamente ordenao por seleo, que tem custo O(n2 ).
Algumas estratgias podem ser utilizadas para se escolher um melhor piv, melhorando a
eficincia do algoritmo e deixando o pior caso ainda menos provvel.
258 Captulo 16. Ordenao de arranjos
O piv timo para um passo de repartio seria a mediana dos valores no arranjo. Um
piv igual mediana dividiria sempre o arranjo em dois subarranjos de tamanho idntico. Porm,
obter a mediana de um arranjo desordenado seria um processo caro (mais especificamente O(n)).
Escolher a cada passo um elemento aleatrio do arranjo ou escolher trs elementos aleatrios
do arranjo e usar a mediana dos trs como piv so exemplos de estratgias comuns para se obter
melhores pivs.
Anlise geral
O quicksort um mtodo muito eficiente para ordenar dados. Devido pilha de funes
precisa de apenas um espao extra muito pequeno, que O(log n). Em termos prticos, este
custo de memria usualmente considerado muito prximo de O(1).
O algoritmo requer apenas O(n log n) comparaes em seu caso mdio. Apesar da mesma
ordem de complexidade mdia, usualmente mais rpido que o Merge sort no caso mdio.
O quicksort tem um pior caso O(n2 ) quando o pior piv possvel sempre selecionado. A
probabilidade de ocorrncia deste pior caso extremamente baixa e no chega a alterar o caso
mdio. Contudo, considerar este pior caso pode ser um fator importante em aplicaes crticas,
mesmo que com uma baixa probabilidade.
O quicksort tambm no um algoritmo estvel pois as trocas na fase de partio no
consideram a ordem relativa dos elementos.
Na Tabela 16.4 temos um resumo do custo do quicksort em seus principais casos. Apesar de
ter um pior caso O(n log n), o algoritmo tem custo O(n log n) no caso mdio e no melhor caso.
16.3.3 Exerccios
Exerccio 16.7 Analise o cdigo abaixo, que est incompleto.
Veja que inclumos as bibliotecas stdlib.h e time.h neste cdigo. Elas permitem
gerao de nmeros aleatrios e medir tempo.
Veja que criamos um arranjo dinamicamente com o tamanho pedido. (new int[n])
Veja que a funo rand() gera nmeros aleatrios que so inseridos no arranjo.
Veja que temos algumas funes que no so utilizadas ainda.
1 # include < iostream >
2 // Gera o de n meros aleat rios
3 # include < stdlib .h >
4 // Fun es relacionadas a tempo
5 # include < time .h >
6 # include < chrono >
7
8 void intercala ( int a [] , int n ) ;
9 void particionar ( int esq , int dir , int &i , int &j , int a [])
;
10
11 using namespace std ;
16.3 Algoritmos eficientes de ordenao 259
12
13 int main () {
14 // gerador de n meros aleat rios
15 srand ( time ( NULL ) ) ;
16 // tamanho do arranjo
17 int n ;
18 cout << " Digite o tamanho do arranjo : " ;
19 cin >> n ;
20 // ponteiro para o arranjo na mem ria
21 int * v ;
22 v = new int [ n ];
23
24 for ( int i =0; i < n ; ++ i ) {
25 // numero entre 1 e n *3
26 v [ i ] = rand () % ( n *3) + 1;
27 }
28
29 for ( int k =0; k < n ; ++ k ) {
30 cout << v [ k ] << " \ t " ;
31 }
32 cout << " <- Desordenado " << endl ;
33
34 // Come a a medir tempo
35 chrono :: high_resolution_clock :: time_point inicio ;
36 inicio = chrono :: high_resolution_clock :: now () ;
37
38 // Chame a sua fun o de ordena o aqui
39 // ordena (v , n ) ;
40
41 // Termina de medir tempo
42 chrono :: duration < double > duracao ;
43 duracao = chrono :: duration_cast < chrono :: duration < double
> >( chrono :: high_resolution_clock :: now () - inicio ) ;
44
45 cout << " Ordenar o arranjo demorou " << duracao . count ()
<< " segundos " << endl ;
46
47 for ( int k =0; k < n ; ++ k ) {
48 cout << v [ k ] << " \ t " ;
49 }
50 cout << " <- Ordenado " << endl ;
51
52 return 0;
53 }
54
55 void intercala ( int a [] , int n )
56 {
57 int * tmp = new int [ n ];
58 int meio = n / 2;
260 Captulo 16. Ordenao de arranjos
59 int i , j , k ;
60 i = 0;
61 j = meio ;
62 k = 0;
63 // Enquanto os ndices i e j n o tenham chegado ao
fim de seus arranjos
64 while ( i < meio && j < n ) {
65 // colocamos o menor item entre a [ i ] e a [ j ]
no arranjo tempor rio
66 if ( a [ i ] < a [ j ]) {
67 tmp [ k ] = a [ i ];
68 ++ i ;
69 } else {
70 tmp [ k ] = a [ j ];
71 ++ j ;
72 }
73 ++ k ;
74 }
75 // se o ndice i chegou ao fim de seu arranjo
primeiro
76 if ( i == meio ) {
77 // os outros elementos do segundo arranjo v
o para o arranjo tempor rio
78 while ( j < n ) {
79 tmp [ k ] = a [ j ];
80 ++ j ;
81 ++ k ;
82 }
83 // se foi o ndice j que chegou ao fim de
seu arranjo primeiro
84 } else {
85 // os outros elementos do primeiro arranjo
v o para o arranjo tempor rio
86 while ( i < meio ) {
87 tmp [ k ] = a [ i ];
88 ++ i ;
89 ++ k ;
90 }
91 }
92 // neste ponto , o arranjo tempor rio tem todos os
elementos intercalados
93 // estes elementos s o copiados de volta para o
arranjo int a []
94 for ( i = 0; i < n ; ++ i ) {
95 a [ i ] = tmp [ i ];
96 }
97 // o arranjo tempor rio pode ent o ser desalocado
da mem ria
98 delete [] tmp ;
16.3 Algoritmos eficientes de ordenao 261
99 }
100
101 void particionar ( int esq , int dir , int &i , int &j , int a [])
{
102 int x , temp ;
103 i = esq ;
104 j = dir ;
105 x = a [( i + j ) / 2];
106 do
107 {
108 while ( x > a [ i ]) {
109 ++ i ;
110 }
111 while ( x < a [ j ]) {
112 --j ;
113 }
114 if ( i <= j ) {
115 temp = a [ i ];
116 a [ i ] = a [ j ];
117 a [ j ] = temp ;
118 ++ i ;
119 --j ;
120 }
121 } while ( i <= j ) ;
122 }
Exerccio 16.10 Altere o cdigo de modo que as duas ordenaes sejam usadas e compare o
tempo entre as duas.
262 Captulo 16. Ordenao de arranjos
Algoritmo
Simples Eficiente
Seleo Insero Merge sort Quicksort
Comparaes
Caso mdio O(n2 ) O(n2 ) O(n log n) O(n log n)
Pior caso O(n2 ) O(n2 ) O(n log n) O(n2 )
Melhor caso O(n2 ) O(n) O(n log n) O(n log n)
Movimentaes
Caso mdio O(n) O(n2 ) O(n log n) O(n log n)
Pior caso O(n) O(n2 ) O(n log n) O(n log n)
Melhor caso O(n) O(n) O(n log n) O(n log n)
Memria
Caso mdio O(1) O(1) O(n) O(log n)
Pior caso O(1) O(1) O(n) O(n)
Melhor caso O(1) O(1) O(n) O(log n)
Caractersticas
Estabilidade No Sim Sim No
Adaptabilidade No Sim No Sim
Entre os mtodos eficientes, veremos que o quicksort ainda mais rpido na mdia do que o
mergesort, apesar de terem mesma ordem de grandeza O(n log n).
Se os elementos j estiverem ordenados, a ordenao por insero sempre o mtodo mais
rpido. Este melhor caso da insero, porm, pouco provvel na maioria das aplicaes.
Contudo, a ordenao por insero interessante para se adicionar alguns elementos a um
arranjo j ordenado. Assim, estaremos prximos de seu melhor caso.
Apesar de ter a mesma ordem de grandeza O(n2 ) da ordenao por seleo, a ordenao
por insero a mais lenta para arranjos em ordem decrescente. Mesmo assim, pode continuar
melhor que a ordenao por seleo. Ainda apesar da mesma ordem de grandeza O(n2 ) no caso
16.4 Comparando algoritmos de ordenao 263
mdio, a ordenao por insero tende a ser melhor que a ordenao por seleo no caso mdio
com arranjos aleatrios.
Nos casos gerais, a insero um mtodo interessante apenas para arranjos bastante pequenos,
com menos de 20 elementos.
Se os registros so grandes, as movimentaes se tornam caras e a ordenao por seleo se
torna interessante por fazer apenas O(n) movimentaes, caso no haja tantos elementos. Porm,
outra maneira de se evitar movimentos usar ordenao indireta, ou seja, ordenamos ponteiros
para elementos e s fazemos O(n) movimentaes no final. Esta estratgia, porm, utiliza uma
memria extra O(n) para armazenar estes ponteiros.
No caso mdio, quicksort a melhor opo para a maioria da situaes. Apesar de no
geral ser o melhor mtodo entre os apresentados, ele no garante estabilidade. Suas chamadas
recursivas tambm demandam um pouco de memria extra.
Apesar de haver um pior caso no quicksort, sua ocorrncia quase impossvel para qualquer
estratgia razovel de seleo de pivs. Quando os arranjos j esto quase ordenados, usar o
elemento do meio como piv melhora muito o desempenho do algoritmo. Porm, nem sempre
usar o piv do meio uma boa estratgia.
Os mtodos de insero e quicksort podem ser combinados. A insero pode ordenar
subarranjos pequenos do quicksort, o que costuma melhorar muito seu desempenho mdio.
A Figura 16.17 apresenta o tempo gasto por cada um dos quatro algoritmos apresentados
para ordenar arranjos de diferentes tamanhos.
4
x 10
5
Seleo Seleo
Insero 4.5 Insero
Merge sort Merge sort
Quicksort Quicksort
4
3.5
2
Tempo (segundos)
Tempo (segundos)
2.5
1
1.5
0.5
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.17: Tempo necessrio pelos algoritmos simples e eficientes para se ordenar um arranjo
com ordem inicial aleatria (caso mdio).
O tempo gasto pela ordenao por seleo to maior que difcil distinguir a diferena
de tempo entre os outros algoritmos para arranjos grandes. Assim, a Figura 16.18 apresenta
o tempo gasto pelos mtodos eficientes de ordenao em relao ao mtodo de ordenao por
insero. Mesmo que no consideremos a ordenao por seleo, h uma grande diferena entre
a ordenao por insero e os mtodos eficientes de ordenao. Para arranjos onde n < 800,
porm, a ordenao por insero pode ser mais eficiente que o merge sort. Para arranjos onde
n < 20, o custo da ordenao por insero pode at se confundir com o custo de um quicksort.
Como esperado, os mtodos simples de ordenao so muito mais lentos. A Figura 16.22
apresenta a proporo de custo entre todos os algoritmos. O algoritmo de ordenao por seleo
chega a ser at 700 vezes mais lento que algoritmos eficientes em arranjos grandes. Esta diferena
se tornaria ainda maior a medida que aumentamos o tamanho dos arranjos.
264 Captulo 16. Ordenao de arranjos
5
x 10
7 0.4
Insero Insero
Merge sort Merge sort
0.35
6 Quicksort Quicksort
0.3
5
0.25
Tempo (segundos)
Tempo (segundos)
4
0.2
3
0.15
2
0.1
1
0.05
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.18: Tempo necessrio pelos algoritmos eficientes e pela ordenao por insero para
se ordenar um arranjo com ordem inicial aleatria (caso mdio).
9 800
Seleo Seleo
Insero Insero
8 700
Merge sort Merge sort
Quicksort Quicksort
7
600
Tempo (em proporo ao Quicksort)
6
500
5
400
4
300
3
200
2
1 100
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.19: Proporo do tempo necessrio para se ordenar um arranjo com ordem inicial
aleatria (caso mdio).
A Figura 16.20 apresenta a proporo de custo entre os algoritmos eficientes e a ordenao por
insero. Mesmo o algoritmo de ordenao por insero chega a ser at 55 vezes mais lento que
algoritmos eficientes em arranjos grandes. Quanto maior o valor de n, piores sero os mtodos
O(n2 ) (de complexidade quadrtica) em relao aos mtodos O(n log n) (de complexidade
loglinear).
Como sabemos logicamente que os algoritmos O(n2 ) tero pior desempenho que os algorit-
mos O(n log n), a Figura 16.21 apresenta uma comparao de tempo entre os algoritmos merge
sort e quicksort. Em comparao direta entre os mtodos, o quicksort usualmente mais eficiente
que o merge sort para arranjos aleatrios.
A Figura 16.22 apresenta a proporo de tempo gasto entre os mtodos eficientes de ordena-
o. Vemos como a proporo de eficincia entre os dois mtodos se mantm constante para
diferentes tamanhos arranjo, j que os dois mtodos so O(n log n) e tm uma funo de custo
que cresce na mesma ordem.
16.5 Concluso 265
4 60
Insero
Merge sort Insero
Quicksort Merge sort
3.5
Quicksort
50
40
2.5
2 30
1.5
20
10
0.5
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.20: Proporo de tempo necessrio pelos algoritmos eficientes e pela ordenao por
insero para se ordenar um arranjo com ordem inicial aleatria (caso mdio).
5
x 10
7 0.012
5
0.008
Tempo (segundos)
Tempo (segundos)
0.006
0.004
2
0.002
1
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.21: Tempo necessrio para se ordenar um arranjo com ordem inicial aleatria (caso
mdio).
16.5 Concluso
Apesar dessa introduo ao tpico de ordenao ter sido limitada, as comparaes entre
algoritmos mostram a importncia de conceitos como: notao O, diviso e conquista, estruturas
de dados, anlise de melhor caso, pior caso e caso mdio, e, por fim, conflito entre tempo e
memria. Todas estas lies so fundamentais para quaisquer outros algoritmos.
266 Captulo 16. Ordenao de arranjos
4 4
Merge sort Merge sort
Quicksort Quicksort
3.5 3.5
3 3
Tempo (em proporo ao Quicksort)
2 2
1.5 1.5
1 1
0.5 0.5
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 16.22: Tempo necessrio para se ordenar um arranjo com ordem inicial aleatria (caso
mdio).
17 Templates . . . . . . . . . . . . . . . . . . . . . . . . . . 271
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Templates
0
17.
1 0 1
0
1
0
0 0
1
110 01
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 01 0 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
So comuns situaes em que operaes muito parecidas podem ser executadas em diferentes
tipos de dados. Isto especialmente verdade entre os tipos de dados aritmticos. Por exemplo, o
algoritmo para encontrar o mximo ou mnimo em um conjunto de dados do tipo int muito
similar ao algoritmo para os tipos de dado double, short, float ou long. Considere o cdigo
abaixo que encontra o maior elemento entre 3 nmeros inteiros:
Veja agora o cdigo que faz o mesmo para dados do tipo double.
Para que os recursos sejam teis a programadores de maneira genrica, todos os recursos da
STL so baseados em templates. Neste Captulo, estudaremos todas as partes lgicas da STL.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 10
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 01 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 11
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Containers
0
18.
1 0 1
0 0 11
Sequenciais
0
0 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
10
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
18.1 Containers
As estruturas de dados se referem a como armazenamos e organizamos dados de uma
aplicao. Elas so representadas com a STL atravs de containers. Como a analogia sugere,
dentro de um container, podemos guardar vrias variveis de qualquer tipo. Isto est representado
na Figura 18.1.
varivel3
varivel2
varivel4
varivel1
Container
varivel5
Porm, cada container tem suas prprias caractersticas pois utiliza internamente diferentes
estruturas de dados para guardar estas variveis. Ou seja, cada tipo de container possui uma
estratgia diferente para organizao interna de seus dados. Esta estratgia controlada pela
STL, de acordo com o tipo de containers.
Por isso, cada container possui vantagens e desvantagens em diferentes situaes. A Tabela
18.1 apresenta os dois tipos bsicos de containers.
Os containers so representados atravs de objetos que utilizam templates. Os templates, por
sua vez, permitem que o container seja utilizado em qualquer tipo de dado. Estudaremos objetos
274 Captulo 18. Containers Sequenciais
em um captulo futuro deste livro. A grosso modo, por ora, objetos so recursos similares aos
tipos de dados definidos com struct, que aprendemos na Seo 10. Porm, alm de ter suas
prprias variveis, os objetos possuem suas prprias funes.
Supondo um container chamado c, a Tabela 18.2 apresenta funes importantes que esto
disponveis em todos os containers. Algumas funes retornam dados lgicos, nmeros inteiros
ou iteradores. Os iteradores sero utilizados para acessar os elementos de um container, como
veremos mais adiante. Outras funes apenas alteram a estrutura do container, sem fazer retorno
algum.
Funo Retorna
c.empty() Se o container est vazio
c.size() Tamanho ou nmero de elementos
c.begin() Iterador para primeiro elemento
c.end() Iterador para fim do container
<, >, >=, <=, ==, != Operadores de comparao
c.rbegin() Iterador reverso para incio
c.rend() Iterador reverso para o fim
Funo Efeito
c.erase(i) Apaga o elemento i
c.clear() Limpa o container
c.swap(c2) Troca elementos com c2
Tabela 18.2: Funes-membro comuns aos containers. Algumas funes retornam valores e
outras alteram o container.
18.3 Vector
O vector talvez o container de sequncia mais utilizado. Esse container tem funes para
acessar, remover ou inserir elementos no fim da sequncia de elementos de maneira eficiente.
Para utilizarmos este container, necessria a incluso do cabealho <vector> no incio de
nosso programa.
18.3.1 Utilizao
Imagine a sequncia de elementos abaixo.
18.3 Vector 275
4 6 2 7
Imagine que o vector com esta sequncia se chama c. Novos elementos podem ser inseridos
ao final de c com a funo push_back.
1 c . push_back (5);
2 c . push_back (1);
3 c . push_back (9);
4 6 2 7 5 1 9
4 6 2 7 5
O primeiro e ltimo elementos de c podem ser acessados com as funes front e back.
1 cout << c . front () << " " << c . back () << endl ;
4 6 2 7 5
4 5
vector<int> c;
n 0 p pn 4
1 c . push_back (4);
vector<int> c;
n 1 p pn 4
Como o arranjo j tem espao para mais que n elementos, mais elementos podem ser
inseridos sem necessidade de se alocar mais memria. Sempre que no precisarmos de mais
espao para um elemento, este pode ser inserido no arranjo com custo constante O(1). As
posies representadas em cinza no esto sendo utilizadas at o momento.
1 c . push_back (6);
vector<int> c;
n 2 p pn 4
4 6
1 c . push_back (2);
2 c . push_back (7);
18.3 Vector 277
vector<int> c;
n 4 p pn 4
4 6 2 7
vector<int> c; c.push_back(5);
n 4 p pn 4 temp
4 6 2 7
2) Copiamos todos os elementos do arranjo apontado por p para o arranjo apontado por temp.
Naturalmente, para n elementos, este passo tem um custo O(n).
vector<int> c; c.push_back(5);
n 4 p pn 4 temp
4 6 2 7
4 6 2 7
3) Desalocamos a memria apontada por p e fazemos com que p aponte para o mesmo
arranjo de temp.
278 Captulo 18. Containers Sequenciais
vector<int> c; c.push_back(5);
n 4 p pn 4 temp
4 6 2 7
4) Atualizamos o valor de pn, inserimos o valor 5 normalmente, com custo O(1), e incre-
mentamos n. O ponteiro temp deixa de existir por ter apenas escopo de funo.
vector<int> c;
n 5 p pn 8
4 6 2 7 5
Se considerarmos todos os passos, toda a operao de insero teve custo O(n) quando houve
necessidade de se alocar mais memria. Este custo, porm, se amortizado entre vrias inseres
tem um custo mdio de O(1). O custo mdio ocorre pois s fazemos uma alocao de custo O(n)
a cada n inseres de custo O(1). Por isto a alocao prvia de memria to importante.
Existe inclusive a funo capacity() que retorna quantos elementos ainda podem ser
colocados em um vector sem se alocar mais memria.
De maneira similar, a remoo de um elemento no fim da sequncia feita com um custo
constante O(1).
1 c . pop_back ();
vector<int> c;
n 4 p pn 8
4 6 2 7 5
Com custo O(1), a funo para remover o ltimo elemento do container realizada apenas
pela atualizao do valor de n, o fazendo indicar que apenas os 4 primeiros elementos devem ser
considerados.
18.4 Deque 279
18.4 Deque
O container deque uma abreviao para double ended queue (fila com duas pontas). um
container indicado para sequncias que crescem nas duas direes pois tem a capacidade de
insero rpida de elementos no incio e no final da sequncia. Para utilizarmos um deque,
necessrio incluir o cabealho <deque> no incio de nosso programa.
18.4.1 Utilizao
Imagine a sequncia de elementos abaixo.
4 6 2 7
Imagine que o deque que contm esta sequncia se chama c. Da mesma maneira que fizemos
com um vector, novos elementos podem ser inseridos ao final de um deque c com a funo
push_back.
1 c . push_back (5);
2 c . push_back (1);
3 c . push_back (9);
4 6 2 7 5 1 9
7 5 1 9
Novos elementos podem ser inseridos eficientemente em deque no incio de c com a funo
push_front.
1 c . push_front (4);
2 c . push_front (6);
3 c . push_front (1);
1 6 4 7 5 1 9
Assim como em um vector, elementos podem ser removidos no fim do deque c com a
funo pop_back.
1 c . pop_back ();
2 c . pop_back ();
3 c . pop_back ();
280 Captulo 18. Containers Sequenciais
1 6 4 7
O primeiro e ltimo elementos de c podem ser acessados com as funes front e back.
1 cout << c . front () << " " << c . back () << endl ;
1 6 4 7
1 7
deque<int> c;
frente 0 tras 0 p pn 4
p[frente]
p[tras]
frente 0 tras 5 p pn 8
4 6 2 7 5
p[frente] p[tras]
deque<int> c;
frente 1 tras 5 p pn 8
4 6 2 7 5
p[frente] p[tras]
Quando muitos elementos so inseridos no incio do arranjo, uma estrutura toroidal utilizada
para representar os elementos do container.
1 c . push_front (3);
2 c . push_front (8);
3 c . push_front (9);
deque<int> c;
frente 6 tras 5 p pn 8
3 6 2 7 5 9 8
p[tras] p[frente]
282 Captulo 18. Containers Sequenciais
Pode parecer estranho que frente > tras, mas isto indica que os elementos do container
vo de p[6] at p[7] e depois continuam entre p[0] e p[4], formando uma estrutura circular.
18.5 List
O container list representa listas duplamente encadeadas. Assim como o deque, ele consegue
eficientemente inserir e remover elementos das primeiras posies com os mesmos comandos
(push_front, push_back, pop_front, pop_back). Assim, do ponto de vista de utilizao
destas funes, ele equivalente ao deque. Porm, sua estrutura de dados muito diferente, pois
no baseada em arranjos. Para utilizamos o list, precisamos incluir o cabealho <list> no
incio de nossos programas.
Internamente, o list tem usualmente dois ponteiros (chamados aqui de frente e tras) e
um nmero inteiro para guardar o nmero de elementos no container (chamado aqui de n).
list<int> c;
frente tras n 0
Quando inserimos um elemento no container, uma estrutura chamada clula criada para
este elemento. Cada clula contm 2 ponteiros e um elemento.
int
*celula *celula
Os ponteiros apontam para outras clulas de modo que possamos formar uma lista de clulas.
Quando utilizamos o comando para inserir um elemento, o elemento inserido em uma
clula cujos ponteiros no so utilizados por enquanto. Esta clula criada em uma posio
qualquer disponvel na memria. Como esta a nica clula da lista, os ponteiros frente e
tras apontam para ela.
1 c . push_back (4);
18.5 List 283
list<int> c;
frente tras n 1
Ao se inserir mais um elemento, criada para ele mais uma clula que entra na lista
encadeada.
1 c . push_back (5);
list<int> c;
frente tras n 2
4 5
A memria alocada para esta clula est em um local qualquer da memria. Um ponteiro da
clula aponta para o ltimo elemento da lista e o ltimo elemento da lista aponta para a nova
clula. O ponteiro tras passa a apontar para a nova clula, n incrementado e o novo elemento
est inserido.
Podemos assim inserir vrios elementos com tempo O(1). Para cada nova insero alocada
uma clula em uma posio qualquer da memria.
1 c . push_back (3);
list<int> c;
frente tras n 3
4 5 3
list<int> c;
frente tras n 2
5 3
O ponteiro frente aponta para o prximo elemento e o anterior removido, com seus
ponteiros.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Percorrendo
0
19.
1 0 1
0 0 1
1
0
containers
0
0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
19.1 Subscritos
Assim como utilizamos os subscritos [ e ] para acessar posies de arranjos, podemos utilizar
subscritos para acessar elementos dos containers de sequncia. Porm, apenas os containers de
sequncia chamados de containers de acesso aleatrio tem este recurso. Da mesma maneira
que fazemos em arranjos, o subscrito [i] utilizado para acessar o (i + 1)-simo elemento do
container.
19.1.1 Vector
Neste exemplo utilizaremos subscritos para acessar elementos de um vector. Este cdigo utiliza
subscritos para imprimir os valores do container c.
1 # include < iostream >
2 # include < vector >
3
4 using namespace std ;
5
6 int main (){
7 vector < int > c ;
8 for ( int i =1; i <6; i ++) {
9 c . push_back ( i );
10 }
11 for ( int i =0; i < c . size (); i ++) {
12 cout << c [ i ] << " " ;
13 }
14 cout << endl ;
15 }
Note na linha 2 que incluimos o cabealho vector. Nas linhas 7 a 10, criamos um vector
chamado c e inserimos em seu final os elementos 1, 2, 3, 4 e 5. Nas linhas 11 a 14, o subscrito
286 Captulo 19. Percorrendo containers
1 2 3 4 5
Como funciona
Internamente, o i-simo elemento do vector pode ser acessado ao se acessar o i-simo elemento
do arranjo p que guarda seus elementos.
vector<int> c;
n 4 p pn 4
4 6 2 7
Assim, as posies de subscrito do arranjo apontado por p so as mesmas que devem ser
retornadas pelos subscritos do container c.
19.1.2 Deque
Este cdigo utiliza subscritos tambm para imprimir os valores no deque chamado c.
1 # include < iostream >
2 # include < deque >
3
4 using namespace std ;
5
6 int main ()
7 {
8 deque < float > c ;
9 for ( int i =1; i <6; i ++) {
10 c . push_front ( i *1.1);
11 }
12 for ( int i =0; i < c . size (); i ++) {
13 cout << c [ i ] << " " ;
14 }
15 cout << endl ;
16 }
Note na linha 2 como inclumos o cabealho <deque>. O bloco de cdigo nas linhas 8 a 11
cria um deque chamado c e insere em seu incio os elementos 1.1, 2.2, 3.3, 4.4 e 5.5. No
trecho de cdigo das linhas 12 a 15, o subscrito c[i] utilizado para imprimir os elementos de
c.
Como funciona
Internamente o i-simo elemento do deque pode ser acessado ao se acessar o i-simo elemento
do arranjo p aps o elemento p[frente]. Suponha o seguinte deque:
deque<int> c;
frente 1 tras 5 p pn 8
4 6 2 7 5
p[frente] p[tras]
deque<int> c;
frente 1 tras 5 p pn 8
4 6 2 7 5
Deste modo, a localizao das posies frente e tras devem ser consideradas ao se procurar
o i-simo elemento do deque c. Neste caso, a posio do i-simo elemento do container pode
ser encontrado na posio p[frente+i] do arranjo.
Quando ciclos ocorrem, uma operao de mdulo no valor encontrado necessria para
achar a posio correta. Considere o exemplo abaixo onde ocorrem um ciclo:
deque<int> c;
frente 6 tras 5 p pn 8
3 6 2 7 5 9 8
p[tras] p[frente]
288 Captulo 19. Percorrendo containers
deque<int> c;
frente 6 tras 5 p pn 8
3 6 2 7 5 9 8
19.1.3 List
J para o container list, no possvel encontrar diretamente o i-simo elemento da sequncia,
j que os elementos no so organizados em arranjos nesta estrutura. Apenas os containers
vector e deque so de acesso aleatrio, por serem baseadas em arranjos.
Vejamos um exemplo onde queremos acessar o terceiro elemento de um list.
list<int> c;
frente tras n 5
4 5 3 7 1
19.1.4 Anlise
Como vimos, os subscritos so utilizados para acessar diretamente o i-simo elemento do
container. Este na verdade um recurso que encontra o i-simo elemento do container no arranjo
19.2 Iteradores 289
alocado para guardar os elementos. Isto possvel apenas para os containers vector e deque,
que usam arranjos e por isso alocam os elementos em sequncia na memria. Estes containers
so chamados de containers de acesso aleatrio.
Cabe aqui uma comparao entre conteiners sequenciais e arranjos. Em C++, a utilizao de
subscritos muito til pois permite at que utilizemos um container de sequncia para substituir
arranjos. Esta uma prtica comum, especialmente com containers do tipo vector. Com esta
prtica ganhamos vrias vantagens em relao a arranjos:
Containers j controlam seus prprios tamanhos
Containers contm em si mesmos algoritmos eficientes para vrias tarefas
19.2 Iteradores
Como vimos, a opo de se acessar elementos de um container atravs do operador de subscrito
restrita apenas a containers de acesso aleatrio. Para conseguirmos acessar elementos de todos
os tipos de container, precisamos de iteradores.
Os iteradores so objetos que caminham (iteram) sobre elementos de containers. Eles
funcionam como um ponteiro especial para elementos de containers: enquanto um ponteiro
representa uma posio na memria, um iterador representa uma posio em um container. So
muito importantes pois permitem criar funes genricas para qualquer tipo de container.
Supondo um iterador chamado i, a Tabela 19.1 apresenta funes comuns a iteradores.
Funo Retorna
*i Retorna o elemento na posio do iterador
++i Avana o iterador para o prximo elemento
== Confere se dois iteradores apontam para mesma posio
!= Confere se dois iteradores apontam para posies diferentes
5 3 3 6 2 3 8 5 7
begin() end()
19.2.1 Exemplo
Neste exemplo usamos um iterador para percorrer os elementos de um list.
1 # include < iostream >
2 # include < list >
3
290 Captulo 19. Percorrendo containers
c a b c d e f g h i j k l m n o p q r s t u v w x y z
Criamos agora, na linha 11, um iterador chamado pos. Repare que quando declaramos o tipo
de dado deste iterador, utilizamos list<char>::iterator para indicar que pos um iterador
para um list de dados do tipo char.
Na linha 12, quando dizemos que pos = c.begin(), inicializamos o for fazendo com que
pos seja um iterador apontando para o primeiro elemento de c. A condio de parada pos !=
c.end() que o iterador pos tenha chegado na posio c.end(), que uma posio aps o
ltimo elemento de c.
c a b c d e f g h i j k l m n o p q r s t u v w x y z
pos c.end()
Independente do tipo de container, a condio de incremento ++pos faz com que pos aponte
para o prximo elemento do container.
c a b c d e f g h i j k l m n o p q r s t u v w x y z
pos c.end()
a b
O trecho completo de cdigo das linhas 12 a 14 faz com que consigamos imprimir todos os
elementos de c. Ao fim, pos finalmente chega posio c.end().
c a b c d e f g h i j k l m n o p q r s t u v w x y z
c.end()
pos
a b c d e f g h i j k l m n o p q r s t u v w x y z
list<int> c;
frente tras n 2
4 3 7 2 5 7
292 Captulo 19. Percorrendo containers
Funo Retorna
Todos iteradores
*i Desreferencia
i = i2 Atribuio
i == i2 Compara igualdade de posio
i != i2 Compara desigualdade de posio
Iteradores unidirecionais
++i Pr-incrementa
i++ Ps-incrementa
Iteradores bidirecionais
-i Pr-decrementa
i- Ps-decrementa
Iteradores de acesso aleatrio
i += n Incrementa n posies
i -= n Decrementa n posies
i + n Aritmtica (soma) com iteradores
i - n Aritmtica (subtrao) com iteradores
i[i2] Desreferencia i2 posies aps *p
i < i2 Compara posio no container
i > i2 Compara posio no container
i <= i2 Compara posio no container
i >= i2 Compara posio no container
list<int> c;
frente tras n 2
4 3 7 2 5 7
Podemos chamar deste modo uma funo para inserir um elemento 11 na posio i do
container.
1 c . insert (i ,11);
19.2 Iteradores 293
list<int> c;
frente tras n 2
4 3 7 2 5 7
i
11
Ser alocada na memria uma nova clula para este elemento 11. A nova clula aponta
para a clula anterior da posio i e para a prpria clula da posio i. Alteramos ento os
ponteiros do elemento i e do elemento anterior a i.
Supondo que j temos um iterador para a posio que queremos, o nmero de operaes para
esta insero constante O(1). Esta exatamente a maior vantagem do container list. Fazer
com que o iterador chegue posio i, porm, pode ter custo O(n) pois um container do tipo
list no tem acesso aleatrio.
Insert - Vector
Vejamos agora a utilizao da funo insert em um vector. Considere agora a estrutura
interna da mesma sequncia sendo representada por um vector.
1 vector < int > c ;
2 c . push_back (4);
3 c . push_back (3);
4 c . push_back (7);
5 c . push_back (2);
6 c . push_back (5);
7 c . push_back (7);
vector<int> c;
n 6 p pn 8
4 3 7 2 5 7
vector<int> c;
n 6 p pn 8
4 3 7 2 5 7
A funo que insere o elemento 11 na posio i mais complicada para um vector. Temos
uma sequncia de passos:
1) A varivel n incrementada, abrindo espao para mais um elemento. Este processo tem
um custo constante O(1). Em algumas situaes, pode ser que um novo arranjo precise ser
alocado, levando a um custo O(n), como vimos na Seo 18.3.2.
vector<int> c;
n 7 p pn 8
4 3 7 2 5 7
vector<int> c;
n 7 p pn 8
4 3 3 7 2 5 7
3) O elemento 11 pode ser inserido na posio i. Este passo tem apenas um custo O(1).
19.2 Iteradores 295
vector<int> c;
n 7 p pn 8
4 11 3 7 2 5 7
Insert e Erase
De modo anlogo funo insert(i, elem), existe a funo erase(i), que remove do
container o elemento da posio i.
A funo insert pode tambm receber 2 iteradores em vez de um elemento para inserir
vrios elementos de uma vez. Neste caso, os elementos entre os dois iteradores em um segundo
container so inseridos no container.
A funo erase pode tambm receber 2 iteradores em vez de um para remover vrios
elementos de uma vez.
Funo construtura
Com iteradores, um container pode at mesmo j ser construdo com elementos de outro container
de outro tipo. No exemplo abaixo, j criamos o container c2 com uma cpia dos elementos do
container c1. Dizemos que esta a funo que constri c2.
1 vector < int > c2 ( c . begin () , c . end ());
Um container tambm pode ser construdo com os elementos de um arranjo, atravs de seus
endereos.
1 vector < int > c (a , a + n );
Funes genricas
Vimos no exemplo anterior a funo insert, que depende de iteradores para definir em qual
posio de um container ser inserido o elemento. Outra utilidade de iteradores possibilitar
funes genricas, que funcionem para qualquer tipo de container.
A funo abaixo imprime todos os elementos de um container:
1 template < typename T >
2 void imprime ( T const & x )
3 {
4 typename T :: const_iterator pos ;
296 Captulo 19. Percorrendo containers
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Anlise
0
20.
1
00 1
0dos 011
1 Sequenciais
Containers
0
1 0
0
1
1101 1
0
0
0
110 0
1
0
0
000 0
0
1
1
0
0 0
00 1 00 0 0 0 01 0 01 00 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Vimos nas ltimas Sees trs tipos de containers sequenciais e como eles podem ser acessados
com iteradores. Podemos agora fazer uma breve comparao entre estas diferentes maneiras de
representar estruturas de dados.
20.1 Vector
Quando acaba a memria, um vector cria um novo arranjo dinamicamente e copia os elementos.
Isto tem custo O(n).
Em geral, tanto a insero quanto a remoo de elementos no fim da sequncia tem custo
O(1) em um vector. preciso saber que h um custo maior O(n) quando for necessrio alocar
memria para um novo arranjo. Porm, como esta alocao no frequente, no caso mdio, o
custo de se inserir um elemento continua O(1).
Por ter seus elementos organizados em uma arranjo (com elementos em sequncia na
memria), uma insero ou remoo no meio da sequncia tem custo O(n) pois elementos
precisam ser deslocados.
Por outro lado, uma consulta em qualquer posio de um vector tem custo O(1) pois eles
so containers de acesso aleatrio. Esta uma grande vantagem do vector.
Uma outra grande vantagem de um vector o baixo gasto de memria auxiliar para
gerenciar sua estrutura. Em um arranjo, no temos gasto extra algum com ponteiros.
Sua maior desvantagem o custo O(n) de se inserir um elemento no incio ou no meio da
sequncia.
20.2 Deque
A estrutura do deque permite que elementos sejam inseridos com baixo custo O(1) tanto no
incio quanto no fim do arranjo.
Alm desta nica diferena para o vector, por ser tambm baseado em arranjos, um deque
tem os mesmos resultados assintticos do vector para (i) insero e remoo no meio da sequncia
O(n), (ii) realocao de memria O(n) e (iii) acesso aleatrio a elementos O(1).
298 Captulo 20. Anlise dos Containers Sequenciais
20.3 List
A maior vantagem dos containers do tipo list a insero ou remoo de elementos em
qualquer posio com custo constante O(1), desde que tenhamos um iterador para esta posio.
No existe realocao de arranjos nestas estruturas.
Um grande desvantagem o alto gasto de memria extra O(n) nestas estruturas pois para
cada elemento so guardados dois ponteiros. Outra desvantagem que este container no oferece
acesso aleatrio a seus elementos e gerar um iterador para um elemento pode ter custo O(n).
20.4 Comparao
A Tabela 20.1 apresenta o custo das principais operaes utilizadas nas estruturas de dados
representadas pelos containers sequenciais.
Container Sequencial
vector deque list
Insero/Remoo
Incio O(n) O(1) O(1)
Meio O(n) O(n) O(1)
Fim O(1) O(1) O(1)
Acesso
Incio O(1) O(1) O(1)
Meio O(1) O(1) O(n)
Fim O(1) O(1) O(1)
Caractersticas
Acesso Aleatrio Sim Sim No
Tabela 20.1: Comparao de custo em termos de nmero de atribuies para estruturas de dados
sequenciais. As estruturas de acesso aleatrio tem mais facilidade de acesso a elementos no meio
da sequncia. As estruturas baseadas em ponteiro tem mais facilidade de insero e remoo de
elementos no meio da sequncia.
20.4 Comparao 299
6 6
x 10 x 10
3.5 2
vector vector
deque deque
list 1.8 list
3
1.6
2.5 1.4
Tempo (segundos)
Tempo (segundos)
1.2
2
1.5
0.8
1 0.6
0.4
0.5
0.2
0 0
0 200 400 600 800 1000 1200 1400 1600 1800 2000 0 200 400 600 800 1000 1200 1400 1600 1800 2000
Tamanho do arranjo Tamanho do arranjo
20.5 Exerccios
Exerccio 20.1 Analise o cdigo abaixo, que est incompleto.
1 # include < iostream >
2 // bibliotecas com os containeres
3 # include < vector >
4 # include < deque >
5 # include < list >
6
7 using namespace std ;
8
9 template < typename T >
10 void imprimeContainer ( T const & coll ) ;
11
12 int main ()
13 {
14 // cria um vector de int chamado dados
15 vector < int > vetor ;
16
17 // inserindo dados no fim do vector , um a um
18 vetor . push_back (2) ;
19 vetor . push_back (4) ;
20 vetor . push_back (6) ;
21 vetor . push_back (9) ;
22 vetor . push_back (5) ;
23 vetor . push_back (7) ;
24 vetor . push_back (3) ;
25 vetor . push_back (8) ;
26 cout << " Elementos no vector inicial ( " << vetor . size ()
<< " ) : " << endl ;
27 imprimeContainer ( vetor ) ;
28
29 // Fa a aqui um algoritmo de inser o
30 vetor . resize ( vetor . size () +1) ;
31
32 // Imprimindo o vector novamente com o elemento
inserido
33 cout << " Elementos no vector com elemento na posicao
inicial ( " << vetor . size () << " ) : " << endl ;
34 imprimeContainer ( vetor ) ;
35
36 // Conseguindo um iterador para o meio do vetor
37 vector < int >:: iterator i_vector = vetor . begin () ;
38 i_vector += vetor . size () /2;
39
40 // Imprimindo o elemento do meio do vector
41 cout << " O elemento que esta no meio do vetor o " <<
* i_vector << endl ;
42
20.5 Exerccios 301
83
84 // Fun o insert coloca um elemento ap s a posi o
indicada
85 fila . insert ( i_fila , 91) ;
86 cout << " Elementos na fila com duas pontas com 91
inserido ( " << fila . size () << " ) : " << endl ;
87 imprimeContainer ( fila ) ;
88
89 fila . resize ( fila . size () +1) ;
90 cout << " Elementos na fila com duas pontas aumentada ( "
<< fila . size () << " ) : " << endl ;
91 imprimeContainer ( fila ) ;
92
93 // Fazer inser o no meio do deque
94
95 // Imprimindo a fila mais uma vez
96 cout << " Elementos na fila com duas pontas com elemento
inserido ( " << fila . size () << " ) : " << endl ;
97 imprimeContainer ( fila ) ;
98
99 // Opera es similares ser o feitas com uma lista
duplamente encadeada
100 list < int > lista ;
101
102 // Copiando os elementos da fila para a lista
duplamente encadeada
103 while (! fila . empty () ) {
104 lista . push_back ( fila . front () ) ;
105 fila . pop_front () ;
106 }
107
108 // Imprime a fila
109 cout << " Elementos na lista duplamente encadeada
inicial ( " << lista . size () << " ) : " << endl ;
110 imprimeContainer ( lista ) ;
111
112 // Fazer inser o no inicio da lista
113
114 // Imprimindo a nova lista
115 cout << " Elementos na lista duplamente encadeada ( " <<
lista . size () << " ) : " << endl ;
116 imprimeContainer ( lista ) ;
117
118 // Obter iterador para o meio da lista
119 list < int >:: iterator i_lista = lista . begin () ; // pegando
a posi o inicial
120
121 // Imprimindo o elemento do meio da lista
20.5 Exerccios 303
Exerccio 20.2 O container vector no tem uma funo push_front que insere um ele-
mento na primeira posio do vector. Faa na linha 29 um cdigo para inserir um elemento
qualquer na primeira posio do vector (sem usar a funo insert).
Na linha 29, a funo resize j est sendo utilizada para aumentar o tamanho do vetor em
1. A funo resize aumenta o tamanho do vector (completando com elementos de valor 0)
para caber mais elementos. Voc precisar mover todos os elementos existentes para frente e
abrir espao para o novo elemento.
Qual a dificuldade de se inserir um elemento em um vector?
Observao: A funo resize no necessariamente realoca o arranjo no vector. A
capacidade dele se mantm constante, se possvel.
304 Captulo 20. Anlise dos Containers Sequenciais
Exerccio 20.3 Faa na linha 53 um cdigo para inserir um elemento no meio do vector
sem utilizar a funo insert. Para isto, voc precisar mover elementos aps o meio em
uma posio.
Exerccio 20.4 Na linha 71, insira mais alguns elementos no incio do deque com a funo
push_front. Esta funo no estava disponvel para um vector. Porqu? Como ela feita
em um deque?
Exerccio 20.5 Na linha 93, faa um cdigo para inserir um elemento no meio do deque.
Porm, desta vez, a funo insert no dever ser utilizada.
Exerccio 20.6 O deque faz todas as operaes que o vector capaz de fazer com a mesma
complexidade em notao O e ainda tem a capacidade de inserir elementos na primeira
posio em tempo O(1). Sendo assim, qual a vantagem da utilizao de um vector em
relao a um deque?
Exerccio 20.7 Na linha 112, insira mais alguns elementos no incio da lista com a funo
push_front. Esta funo no estava disponvel para um vector mas estava disponvel para
um deque. Como ela ocorre em um list?
Exerccio 20.8 Na linha 118, voc deve conseguir um iterador para o elemento do meio da
lista duplamente encadeada. Porm diferentemente deste trecho de cdigo para vector e
deque, a operao +=list.size()/2 no pode ser utilizada. Isso ocorre pois o container
list no um container de acesso aleatrio. Faa o cdigo de maneira que funcione para
list, utilizando o operador ++ repetidamente em um for. Qual sua dificuldade? Por que
isso acontece?
Exerccio 20.9 A funo de insert deve ser utilizada para se inserir elementos no meio de
listas. No possvel fazer isto de maneira manual como fizemos para vector e deque
pois no existe a operao [] em filas. Conhecendo como implementada na verdade
a operao insert de um vector e um deque (exerccios anteriores), qual a vantagem da
operao insert para listas?
Exerccio 20.10 Em todo o cdigo, sempre que criamos um container novo, copiamos os
elementos do container antigo atravs de um loop. Altere estes trechos de cdigo de modo
os contrutores dos novos containers sejam utilizados com os elementos do container antigo.
Exemplo: vector<int> c(c2.begin(), c2.end());.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0 0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 10
1
0
1
0
010 0 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 1 0 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
00
1
1
1 01
1
1 1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 1
00
0
1
1
111 0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Containers
0
21.
1
00 1
0 01 1
1 e 1Conjuntos
Associativos
0 0 0
1 1 110 1
0
0
0
110 0
1
0
0
000 0
0
1
1
0
0 0
00 1 00 0 0 0 010 01 00 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 00
0 1 01 0 1 1 1 0 01 1 11
2 4 7
=
8 5
conjunto conjunto 2
5 7 4 8
Considere que o conjunto de nmeros inteiros abaixo est representado por um set chamado
c.
1 set < int > c ;
3 1
6
c
2 5 4
1 2 3 4 5 6
J a funo erase pode ser utilizada para apagar elementos de acordo com suas chaves.
1 c . erase (5);
2 c . erase (6);
3 1
c
2 4
A funo find procura um elemento e retorna um iterador para ele caso este seja encontrado.
1 set < int >:: iterator pos ;
2 pos = c . find (2);
21.1 Containers Associativos Ordenados 307
3 1
c
2 4
pos
3 1
c
2 4
pos
21.1.2 Multiset
O multiset um container muito similar ao set. A diferena que ele representa multiconjun-
tos, ou seja, conjuntos onde um elemento pode ocorrer mais de uma vez.
Suponha o seguinte multiset abaixo chamado c.
1 multiset < int > c ;
5 2
3
c 3 4
6
2
308 Captulo 21. Containers Associativos e Conjuntos
Ao iterar pelos elementos, estes ainda esto ordenados. As repeties de elementos ocorrem
em sequncia.
1 multiset < int >:: iterator i ;
2 for ( i = c . begin (); i != c . end (); ++ i ){
3 cout << * i << " " ;
4 }
5 cout << endl ;
2 2 3 3 4 5 6
A funo find procura a primeira ocorrncia de um elemento e retorna um iterador para ele,
caso este seja encontrado.
1 set < int >:: iterator pos ;
2 pos = c . find (2);
5 2
3
c 3 4
6
2
pos
5 2
3
c 3 4
6
2
pos
A funo erase pode receber um iterador para retirar apenas o elemento apontado do
conjunto.
1 c . erase ( pos );
5 2
3
c 3 4
6
2
pos
21.1 Containers Associativos Ordenados 309
Alm das funes usuais, a funo count pode ser utilizada em um multiset para determi-
nar quantas ocorrncias de um elemento existe em um conjunto.
1 c . insert (3);
2 c . insert (3);
3 c . insert (2);
4 c . insert (4);
5 cout << c . count (3);
3 5 2
3
c 3 3 4
6 4
2
3 5 2
3
c 3 3 4
6 4
2
primeiro ultimo
21.1.3 Map
O conteiner map utilizado para representar um tipo de dados chamado arranjos associativos.
Em um conjunto, temos vrias chaves. Em um arranjo associativo, temos vrias chaves e para
cada chave temos um registro associado. A chave e o registro no precisam ser do mesmo tipo.
Considere o arranjo associativo abaixo representado por um map chamado c.
1 map < string , double > c ;
c
310 Captulo 21. Containers Associativos e Conjuntos
ZERO 0.0
c PI 3.14
IRPF 0.15
3.14
Assim como em outros containers, a funo find utilizada para encontrar um elemento pela
chave. retornado um iterador para este elemento.
1 map < string , double >:: iterator pos ;
2 pos = c . find ( " ZERO " );
ZERO 0.0
c PI 3.14
IRPF 0.15
pos
ZERO 0.0
c PI 3.14
IRPF 0.15
pos
21.1 Containers Associativos Ordenados 311
ZERO: 0
ZERO: 0
Assim como em outros containers, ao se iterar pelos elementos, estes ocorrem no conjunto
ordenados pela chave.
1 for ( pos = c . begin (); pos != c . end (); ++ pos ){
2 cout << pos - > first << " : " << pos - > second << endl ;
3 }
IRPF: 0.15
PI: 3.14
ZERO: 0
21.1.4 Multimap
Assim como os containers set e multiset, o container map tambm contm um equivalente
que aceita entradas repetidas de um elemento: o multimap.
Todas as funes especficas para multiset, como lower_bound, upper_bound e count
podem ser utilizadas tambm no multimap.
Um recurso especificamente interessante em um multimap que rplicas de uma chave
podem ter registros diferentes, o que diferencia os elementos com chaves replicadas.
200
150 350
400 600
200 R 200
E
D R
150 350 150 350
E D
100 170 300 500 100 170 300 500
Figura 21.3: Toda clula R tem elementos menores em uma subrvore esquerda E e elementos
maiores em uma subrvore direita D.
10
Um objeto do tipo set contm um ponteiro para a clula alocada como raiz da rvore
(variavl chamada aqui de raiz) e uma varivel chamada aqui de n para contar o nmero de
elementos na estrutura.
1 set < int > c ;
set<int> c;
raiz n 0
21.1 Containers Associativos Ordenados 313
Quando os elementos so inseridos, os ponteiros das clulas apontam para os elementos raiz
das rvores esquerda e direita.
1 c . insert (200);
2 c . insert (150);
3 c . insert (350);
4 c . insert (100);
5 c . insert (170);
6 c . insert (300);
7 c . insert (500);
8 c . insert (400);
9 c . insert (600);
set<int> c;
raiz n 9
200
150 350
400 600
Para procurar um elemento, basta ir raiz da rvore e fazer uma comparao para saber em
qual subrvore est o elemento. Ao descobrirmos em qual subrvore est o elemento, vamos
a esta subrvore e repetimos o processo em sua raz. Se a raz da rvore o elemento que
procuramos, terminamos a busca. Se tal raz no encontrada, o elemento no est no conjunto.
1 pos = c . find (500);
set<int> c;
raiz n 9
200
150 350
400 600
314 Captulo 21. Containers Associativos e Conjuntos
Ao procurar o elemento 500, vamos raiz da rvore, que 200. Assim, sabemos que o
elemento, se existente, estar na subrvore direita. O mesmo ocorre repetidamente at que
encontramos o elemento 500.
Para inserir um elemento, um processo similar utilizado. Suponha que queremos inserir o
elemento 180.
1 c . insert (180);
set<int> c;
raiz n 10
200
150 350
set<int> c;
raiz n 9
200
150 350
Caso 2) Para remover um n que tem apenas um filho, temos um processo de dois passos.
Caso 2 - Passo 1 - Removemos o n.
21.1 Containers Associativos Ordenados 315
set<int> c;
raiz n 9
200
150 350
set<int> c;
raiz n 9
200
150 350
400 600
Caso 3) Para remover um n com 2 filhos temos 3 passos. Caso 3 - Passo 1 - Em vez de
remover o n, procuramos seu sucessor ou predecessor de acordo com seu valor.
316 Captulo 21. Containers Associativos e Conjuntos
set<int> c;
raiz n 9
200
150 350
400 600
Neste caso, vamos utilizar o sucessor de 350, que sempre o elemento mais esquerda da
rvore direita. O predecessor, de modo anlogo, seria o elemento mais direita da rvore
esquerda.
Caso 3 - Passo 2 - O sucessor toma a posio do item que queremos remover.
set<int> c;
raiz n 9
200
150 400
400 600
set<int> c;
raiz n 9
200
150 400
400 600
Balenceamento
Apesar da eficincia mdia das rvores apresentadas, a ordem de insero dos elementos pode
afetar o desempenho das rvores. Os elementos podem ser inseridos em uma ordem tal que a
rvore final tenha muitos nveis e seja pouco eficiente.
Suponha a rvore gerada internamente pela insero dos elementos na ordem apresentada:
1 c . insert (200);
2 c . insert (150);
3 c . insert (350);
4 c . insert (100);
5 c . insert (170);
6 c . insert (300);
7 c . insert (500);
8 c . insert (180);
9 c . insert (400);
10 c . insert (600);
set<int> c;
raiz n 10
1 200
2 150 350
100
150
170
180
200
300
350
400
500
600
400
500
500
400 600
600
Em C++, porm, estas estratgias variam muito de acordo com o compilador. No faz parte
do escopo deste livro discutir todas estas estratgias, que podem ser encontradas em qualquer
bom livro didtico sobre estruturas de dados.
21.1.6 Anlise
Em uma rvore binria, cada comparao feita pelo algoritmo que busca um elemento divide o
nmero de solues possveis pela metade, de modo similar a uma busca binria. Assim, o custo
mdio de insero, remoo e pesquisa em todos os casos O(log n).
Como a STL contm estratgias de balanceamento, o custo das operaes continua O(log n)
mesmo no pior caso. Na verdade, mesmo para rvores que no tem estratgias de balanceamento,
o custo mdio ainda O(log n) para inseres feitas aleatoriamente. Seu pior caso, porm,
O(n).
Setembro -> 30
Abril -> 30
Dezembro -> 31
Fevereiro -> 28
dezembro: 31
novembro: 30
setembro: 30
outubro: 31
agosto: 31
junho: 30
maio: 31
fevereiro: 28
abril: 30
julho: 31
marco: 31
janeiro: 31
abril: 30
agosto: 31
dezembro: 31
fevereiro: 28
janeiro: 31
julho: 31
junho: 30
maio: 31
marco: 31
novembro: 30
outubro: 31
setembro: 30
n 0 p m 7
Este arranjo alocado na memria e apontado por p chamado de Tabela Hash. nele que os
elementos sero inseridos. O tamanho inicial desta tabela depende da implementao existente
no compilador. Vamos supor aqui que a tabela hash tenha espao alocado m para 7 elementos.
Suponha agora que queremos inserir na tabela o elemento 32.
1 c . insert (32);
A funo de transformao h(x) dever transformar esta chave 32 em uma posio do arranjo.
A funo de transformao h(x) mais comum para nmeros inteiros h(x) = x%m, onde m
o tamanho da tabela hash e x o valor da chave. Em nosso exemplo, teramos ento que
h(32) = 32%7 = 4. Assim, a funo de transformao indica que o elemento 32 deve ser inserido
na posio p[4] da Tabela Hash.
unordered_set<int> c;
n 1 p m 7
32
A estratgia mais comum para tratamento de colises transformar este arranjo de elementos
apontado por p em um arranjo de ponteiros para clulas de listas encadeadas de elementos.
Por exemplo, ao se inserir o elemento 32, ele vai para a posio h(32) = 4 da tabela hash
como um elemento em uma clula de uma lista encadeada.
1 c . insert (32);
unordered_set<int> c;
n 1 p m 7
p[0]
p[1]
p[2]
p[3]
p[4] 32
p[5]
p[6]
Cada elemento fica em uma clula que composta de um elemento e um ponteiro para uma
possvel prxima clula. Por exemplo, ao se inserir o elemento 27, temos que h(27) = 27%7 = 6.
1 c . insert (27);
unordered_set<int> c;
n 2 p m 7
p[0]
p[1]
p[2]
p[3]
p[4] 32
p[5]
p[6] 27
unordered_set<int> c;
n 3 p m 7
p[0]
p[1]
p[2]
p[3]
p[4] 32 46
p[5]
p[6] 27
1 c . insert (31);
2 c . insert (22);
3 c . insert (15);
unordered_set<int> c;
n 6 p m 7
p[0]
p[1] 22 15
p[2]
p[3] 31
p[4] 32 46
p[5]
p[6] 27
21.2.3 Anlise
A maior vantagem de tabelas hash a sua eficincia em relao a custo mdio para pesquisa,
insero e remoo. Para uma tabela onde os dados esto bem distribudos o custo de qualquer
operao na tabela O(1).
O STL j inclui estratgias para que os dados fiquem sempre bem distribudos na tabela.
Estas estratgias incluem funes de hashing que garantam uma boa distribuio dos dados e
alocao de maiores tabelas caso a carga de elementos esteja muito alta.
Para a estratgia de tratamento coliso apresentada, no pior caso, se todos os elementos
forem para a mesma posio, o custo de cada operao seria O(n). Porm, alm da probabilidade
deste caso ser desprezvel, um tratamento de coliso atravs de rvores em vez de listas pode
levar este pior caso a O(log n), fazendo com que a tabela hash seja, na pior das hipteses, to
eficiente quanto uma rvore.
21.3 Anlise dos Containers Associativos 325
Mais ainda, possvel garantir de vrias formas uma boa distribuio dos dados na tabela.
Todas as tabelas hash alocam espao para tabelas maiores quando o nmero de elementos cresce.
Usualmente uma nova tabela maior criada quando o nmero de elementos maior que 70% do
tamanho da tabela. Esta proporo chamada de fator de carga.
Quando o tamanho da tabela aumenta uma operao O(n) copia os elementos para a nova
tabela, causando um custo de realocao de memria. Para evitar isto, comum manter duas
tabelas hash para representar um conjunto de dados. Sempre que um elemento novo inserido
na tabela nova, um elemento da tabela antiga transferido para a tabela nova com custo O(1).
Porm, nesta estratgia, a busca de um elemento, apesar de ser ainda O(1), sempre precisar ser
feita em duas tabelas.
A maior desvantagem das tabelas hash que os dados ficam desordenados na tabela. Assim,
para retornar todos os itens em ordem seria necessrio colocar tudo para uma outra estrutura
ordenada ou orden-los em uma estrutura de sequncia. Qualquer uma destas opes ter um
custo O(n log n). Por isto, esta uma estrutura a ser utilizada apenas quando os dados em ordem
realmente no so necessrios.
Tabela 21.1: Fatores a se considerar ao escolher uma categoria de container para representar uma
estrutura de dados.
para ordenar os elementos. O custo desta ordenao pode ser ou no compensado pela operao
que faremos nos elementos ao percorr-los.
Tabela 21.2: Custo para representao de conjuntos atravs das estruturas de dados e containers
mais comuns.
2 0.8
Tempo (segundos)
Tempo (segundos)
1.5 0.6
1 0.4
0.5 0.2
0 0
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
Tamanho do conjunto 4
x 10 Tamanho do conjunto x 10
4
desordenado especialmente mais lento pois o custo de pesquisa do elemento a ser removido
O(n). Em nossos experimentos, para conjunto pequenos, um container sequencial desordenado
mais vantajoso que um sequencial ordenado para menos de 250 elementos, que um associativo
desordenado para menos de 320 elementos e que um associativo ordenado para menos de 600
elementos. Um container sequencial ordenado tem uma remoo mais rpida que um associativo
desordenado para menos de 7000 elementos e que um associativo ordenado para menos de 7200
elementos.
J os containers associativos tm um custo baixo de remoo muito mais baixo, sendo que a
diferena entre containers associativos ordenados O(log n) e desordenados O(1) se torna maior
medida que os conjuntos se tornam muito grandes. Containers associativos ordenados tm
sempre uma eficincia menor que associativos desordenados para insero de elementos.
5 6
x 10 x 10
3 6
Sequencial Desordenado Sequencial Ordenado
Sequencial Ordenado Associativo Ordenado
Associativo Ordenado Associativo Desordenado
2.5 5
Associativo Desordenado
2 4
Tempo (segundos)
Tempo (segundos)
1.5 3
1 2
0.5 1
0 0
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
Tamanho do conjunto 4
x 10 Tamanho do conjunto 4
x 10
1.2
2.5
1
Tempo (segundos)
Tempo (segundos)
2
0.8
1.5
0.6
1
0.4
0.5
0.2
0 0
0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8
Tamanho do conjunto 4
x 10 Tamanho do conjunto x 10
4
Tabela 21.3: Custo para representao de conjuntos atravs das estruturas de dados e containers
mais comuns.
21.4 Exerccios
21.4 Exerccios 329
Exerccio 21.1 Analise o cdigo abaixo, que est incompleto. O cdigo abaixo utiliza um
container do tipo set para guardar um conjunto de siglas, que so do tipo string.
Veja como o conjunto criado com set<string>.
Veja como siglas podem ser adicionadas ao conjunto com a funo insert.
Veja como a funo imprimeContainer imprime todas as siglas de um set.
Veja como a funo pesquisaSigla utiliza a funo find para pesquisar uma sigla.
Veja como temos um lao com um menu dentro. As funes para o menu precisam ser
implementadas.
Veja que inclumos as bibliotecas stdlib.h e time.h neste cdigo. Elas permitem
gerao de nmeros aleatrios e medir tempo.
Veja que criamos um arranjo dinamicamente com o tamanho pedido. (new int[n])
Veja que a funo rand() gera nmeros aleatrios que so inseridos no arranjo.
Veja que temos algumas funes que no so utilizadas ainda.
1 # include < iostream > // biblioteca de entrada e saida
2 # include < string > // biblioteca para usar strings
3 // bibliotecas com os containeres
4 # include <set >
5 # include <map >
6 # include < unordered_map >
7 # include < algorithm >
8
9 using namespace std ;
10
11 template < typename T > // fun o similar do exerc cio
anterior
12 void imprimeContainer ( T const & x ) ;
13
14 template < typename T > // Pesquisa uma sigla no container
15 void pesquisaSigla ( T const & x ) ;
16
17
18 int main ()
19 {
20 // cria um set de strings chamado x
21 set < string > x ;
22
23 // inserindo siglas no conjunto , um a um
24 x . insert ( " INSS " ) ; // Instituto Nacional de Seguran a
Social
25 x . insert ( " CEP " ) ; // C digo de Endere amento Postal
26 x . insert ( " FUNAI " ) ; // Funda o Nacional do ndio
27 x . insert ( " IOF " ) ; // Imposto sobre Opera es de Cr dito
28 x . insert ( " IR " ) ; // Imposto de Renda
29 x . insert ( " EMBRATEL " ) ; // Empresa Brasileira de
Telecomunica es
30 x . insert ( " ONU " ) ; // Organiza o das Na es Unidas
31 x . insert ( " SPC " ) ; // Servi o de Prote o ao Cr dito
330 Captulo 21. Containers Associativos e Conjuntos
Exerccio 21.2 Implemente as funes que ainda no foram implementadas. Use a funo j
implementada como referencia.
Exerccio 21.3 Vamos agora transformar este programa em um dicionario de siglas, ou seja,
para cada sigla pesquisada, teremos retornada sua definio.
Para isto, troque a estrutura set por uma estrutura map. Os registros e as chaves desse
map devero ser do tipo string.
A insero de siglas no map feita de maneira mais conveniente com o operador de
subscrito [].
Exerccio 21.4 Com o dicionrio de siglas implementado, troque a estrutura map por
unordered_map.
necessrio fazer mais alguma alterao para que o cdigo funcione?
Qual a desvantagem e qual a vantagem de se utilizar um unordered_map?
Exerccio 21.6 Neste cenrio, onde tanto as funes de pesquisa em unordered_map e map
retornam os itens ordenados, em quais casos seria vantagem utilizar map ou unordered_map?
Exerccio 21.7 Implemente a alterao na funo imprimeContainer para que ela imprima
os itens de um unordered_map de maneira ordenada. Os elementos podem ser transferidos
temporariamente para um map simples.
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Adaptadores
0
22.
1 0 1
0 0de Container
1
0 10 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
22.1 Stack
O container stack utilizado para representar pilhas de elementos. Ele permite apenas a
insero ou remoo em uma extremidade do container de sequncia. Este adaptador de container
tem apenas as funes de push e pop para insero e remoo. Estas funes, na verdade, apenas
chamam as funes push_back e pop_back de um container de sequncia original, que est
sendo utilizado internamente para representar a pilha.
Um stack pode ser usado para implementar uma pilha com um vector, list, ou deque.
Nos casos de omisso, um deque ser utilizado por padro. Neste adaptador de container, a
funo top obtm o elemento no topo da pilha, equivalente funo back dos containers de
sequncia.
22.1.1 Utilizao
Para utilizar um stack, o cabealho #include <stack> precisa ser includo em nosso pro-
grama. O tipo de dados stack<int> cria uma pilha de nmeros inteiros. Para especificar que
queremos a pilha representada com um list, utilizamos stack<int, list<int> >.
Considere a pilha de elementos abaixo.
1 stack < int > c ;
2 c . push (7);
3 c . push (7);
334 Captulo 22. Adaptadores de Container
4 c . push (2);
5 c . push (6);
Em uma pilha, s temos acesso ao elemento do topo de uma sequncia. Neste caso, o
elemento no topo da pilha o 6.
A funo push coloca um elemento no topo da pilha.
1 c . push (4);
6
22.1 Stack 335
2 3
Para inserir mais um elemento, um arranjo com o dobro do tamanho precisa novamente ser
alocado.
1 c . push (7);
2 3 7
Agora possvel inserir mais um elemento no topo da pilha sem aumentar o tamanho do
arranjo.
1 c . push (1);
2 3 7 1
2 3 7 1 4
2 3 7 1
1 c . pop ();
2 3 7
1 c . pop ();
2 c . pop ();
3 c . pop ();
Sempre que um elemento novo inserido, o espao exato para este elemento alocado.
1 c . push (3);
2 3
1 c . push (7);
2 3 7
Para cada elemento, porm, so alocados dois ponteiros, que acabam gastando memria
extra O(n).
22.2 Queue 337
1 c . push (1);
2 c . push (4);
2 3 7 1 4
Quando um elemento removido, a memria gasta com este elemento tambm desalocada.
Assim, no temos o processo de realocao de memria O(n) necessrio para um vector ou
deque. Todas as operaes de insero tm custo constante O(1).
1 c . pop ();
2 3 7 1
1 c . pop ();
2 c . pop ();
3 c . pop ();
22.2 Queue
O adaptador queue utilizado para representar filas de elementos. O que caracterizam filas
que elas permitem inseres (push) no fim da lista e retiradas do incio (pop). Como faremos
remoes no incio da sequncia, melhor implementar filas com um list ou deque. Por
padro, o C++ utiliza um deque. Em um queue, as funes front e back do acesso aos
primeiro e ltimo elementos.
22.2.1 Utilizao
Para utilizar um queue, o cabealho #include <queue> precisa ser includo. O tipo de dado
queue<int> utilizado para criar uma pilha de nmeros inteiros. Para especificar que queremos
a fila representada com um list, usamos queue<int, list<int> >.
A Figura 22.1 representa uma fila sendo representada por um queue. As funes para
insero e remoo de elementos tm a mesma sintaxe de um container stack. Porm, com
a funo push, novos elementos so sempre inseridos no fim da fila. Com a funo pop, os
elementos no incio da fila so removidos primeiro.
Diferentemente de um stack, onde a funo top acessava o elemento no topo da pilha,
temos as funes front e back em uma fila representada por um queue. A funo back retorna
o elemento do fim da fila. A funo front retorna o elemento do incio da fila.
338 Captulo 22. Adaptadores de Container
back front
push
pop
Figura 22.1: Exemplo de uma fila sendo representada por um queue e suas principais funes.
Novos elementos so sempre inseridos no fim da fila. Os elementos no incio da fila so
removidos primeiro.
2 3
Para inserir mais um elemento, um arranjo com o dobro do tamanho precisa novamente ser
alocado.
1 c . push (7);
2 3 7
22.2 Queue 339
Agora possvel inserir mais um elemento no topo da pilha sem aumentar o tamanho do
arranjo.
1 c . push (1);
2 3 7 1
2 3 7 1 4
Agora, ao se remover um elemento, aquele elemento que est na frente da fila removido.
Como o vector no tem uma operao O(1) para remover o primeiro elemento do container,
todos os elementos do container precisam ser deslocados.
1 c . pop ();
2 3 7 1 4
Este deslocamento resulta no primeiro elemento da fila removido. Porm, isto ocorre com
um alto custo O(n), o que torna o vector uma estrutura pouco apropriada para um queue.
1 c . pop ();
3 7 1 4
7 1 4
1 4
340 Captulo 22. Adaptadores de Container
2 3 7 1 4
3 7 1 4
7 1 4
1 c . pop ();
1 4
1 c . pop ();
22.2 Queue 341
1 c . push (3);
2 3
Para cada elemento, porm, so alocados dois ponteiros, que gastam memria extra O(n).
1 c . push (7);
2 c . push (1);
3 c . push (4);
2 3 7 1 4
3 7 1 4
1 c . pop ();
2 c . pop ();
3 c . pop ();
22.3.1 Utilizao
Assim como em outros adaptadores temos as funes push e pop para insero e remoo. A
funo push insere um elemento na fila de prioridade. A funo pop retira o maior elemento
(chamado de elemento de maior prioridade). A funo top consulta o elemento de maior
prioridade.
Para utilizar um priority_queue, o cabealho #include <queue> precisa ser includo.
O tipo de dados priority_queue<int> cria uma fila de prioridade com nmeros inteiros.
11 10 7 9 5 6 4 8 2 3 1
Apesar de no estar ordenado, o primeiro elemento deste arranjo sempre o maior elemento
do container, no caso, o 11.
Este arranjo, na verdade, representa uma rvore chamada heap. O heap representado pelo
arranjo de modo que os filhos de um elemento na posio i estejam nas posies 2i+1 e 2i+2.
Ou seja, apesar dos elementos estarem no arranjo, eles representam a rvore abaixo.
11
10 7
9 5 6 4
8 2 3 1
10 7
9 5 6 4
8 2 3
1 10 7 9 5 6 4 8 2 3
O elemento que tomou o lugar do maior elemento comparado com seus filhos e troca de
posio com maior deles.
10
1 7
9 5 6 4
8 2 3
10 1 7 9 5 6 4 8 2 3
Novamente, o elemento comparado com seus filhos e troca de posio com maior deles.
10
9 7
1 5 6 4
8 2 3
10 9 7 1 5 6 4 8 2 3
O processo se repete at que o elemento esteja em uma posio que respeita a condio de
ser maior que seus filhos.
344 Captulo 22. Adaptadores de Container
10
9 7
8 5 6 4
1
8 2 3
10 9 7 8 5 6 4 1 2 3
Como o elemento pode trocar de posio uma vez para cada nvel da rvore e uma rvore
balanceada tem O(log n) nveis, o processo de remoo em uma fila de prioridade tem custo
O(log n).
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Algoritmos
0
23.
1 0 1
0 da 0STL
1
0
0 0
1
110 1
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 01 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Parte fundamental da STL so os seus algoritmos. Estes algoritmos podem ser utilizados para
executar operaes comuns a dados guardados em containers da STL como ordenao, busca,
cpia, somatrio, ou contagem.
Alm de nos poupar tempo de programao, estes algoritmos so usualmente implemen-
taes muito eficientes dos algoritmos que conhecemos. Assim, antes de implementar uma
operao bsica, sempre bom conferir se a STL j disponibiliza este algoritmo. O cabea-
lho <algorithm> inclui algoritmos comuns para containers. O cabealho <numeric> inclui
algoritmos comuns para dados numricos.
Funo Descrio
Modificadores de sequncia
copy Copia elementos
fill Preenche elementos com um valor
iter_swap Troca valores de objetos de dois iteradores
random_shuffle Embaralha os elementos
remove Remove valor da sequncia
replace Substitui valor na sequncia
reverse Reverte sequncia
rotate Rotaciona elementos da sequncia para esquerda
swap Troca o valor de dois objetos
transform Transforma sequncia com uma funo
unique Remove duplicatas em sequncia
Parties
partition Particiona sequncia em duas
Ordenao
sort Ordena elementos
stable_sort Ordena elementos com algoritmo estvel
partial_sort Ordena primeiros elementos
is_sorted Confere se elementos esto ordenados
Intercalao
merge Intercala duas sequncias ordenadas
Heap
make_heap Cria um heap da sequncia
push_heap Coloca elemento em um heap
pop_heap Retira elemento de um heap
is_heap Testa se sequncia um heap
Permutaes
next_permutation Transforma sequncia em sua prxima permutao
next_permutation Transforma sequncia em sua permutao anterior
Tabela 23.1: Lista de algoritmos modificadores e algoritmos para tarefas importantes da STL.
2.5 5
Tempo (segundos)
Tempo (segundos)
2 4
1.5 3
1 2
0.5 1
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 23.1: Tempo necessrio pelos algoritmos do STL e por uma implementao eficiente do
quicksort para se ordenar um arranjo com ordem inicial aleatria.
4 4
Quicksort Quicksort
STL Sort STL Sort
3.5 3.5
3 3
Tempo (em proporo ao STL Sort)
2.5 2.5
2 2
1.5 1.5
1 1
0.5 0.5
0 0
0 100 200 300 400 500 600 700 0 1 2 3 4 5 6 7 8 9 10
Tamanho do arranjo Tamanho do arranjo x 10
4
Figura 23.2: Proporo de tempo necessrio pelos algoritmos do STL e por uma implementao
eficiente do quicksort para se ordenar um arranjo com ordem inicial aleatria.
O algoritmo remove remove elementos que tenham o valor passado como parmetro em
toda uma sequncia de elementos.
1 # include < algorithm >
2 // ...
3 int nums [] = {32 ,71 ,33 ,45 ,33 ,80 ,53 ,33};
4 vector < int > v ( nums , nums +8);
5 // 32 71 33 45 33 80 53 33
6 // Remover as ocorr ncias de 33 entre begin () e end ()
7 remove ( v . begin () , v . end () , 33);
8 // 32 71 45 80 53
9 imprimeContainer ( v );
10 // ...
Na linha 7 do exemplo, todos os elementos com valor 33 so removidos da sequncia.
Considera-se toda a sequncia, entre os iteradores v.begin() e v.end().
O algoritmo replace substitui o valor de todos os elementos que tenham o valor passado
como parmetro em toda uma sequncia de elementos.
1 # include < algorithm >
2 // ...
3 int nums [] = {32 ,71 ,33 ,45 ,33 ,80 ,53 ,33};
4 vector < int > v ( nums , nums +8);
5 // 32 71 33 45 33 80 53 33
6 // Substitui ocorr ncias de 33 por 100
7 replace ( v . begin () , v . end () , 33 , 100);
8 // 32 71 100 45 100 80 53 100
9 imprimeContainer ( v );
10 // ...
Na linha 7 do exemplo, todos os elementos com valor 33 so substitudos por elementos de
valor 100. Considera-se toda a sequncia, entre os iteradores v.begin() e v.end().
O algoritmo random_shuffle embaralha uma sequncia de elementos.
1 # include < algorithm >
2 // ...
3 int nums [] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8};
4 vector < int > v ( nums , nums +8);
5 // 1 2 3 4 5 6 7 8
6 // Embaralha elementos entre begin () e end ()
7 random_shuffle ( v . begin () , v . end ());
8 // 6 4 8 2 3 5 1 7
9 imprimeContainer ( v );
10 // ...
Na linha 7 do exemplo, todos os elementos da sequncia so embaralhados, entre os iteradores
v.begin() e v.end().
Funo Descrio
No modificadores de sequncia
count Conta elementos com certo valor
count_if Conta elementos com certa condio
equal Testa se duas sequncias so iguais
find Procura elemento com busca sequencial
find_if Procura elemento que respeita condio
search Procura subsequncia
Min / Max
min Retorna o menor de 2 elementos
max Retorna o maior de 2 elementos
minmax Retorna o menor e o maior entre 2 elementos
min_element Retorna o menor de uma sequncia
max_element Retorna o maior de uma sequncia
minmax_element Retorna o menor e o maior de uma sequncia
Busca binria
lower_bound Busca primeira ocorrncia de elemento
upper_bound Busca ltima ocorrncia de elemento
binary_search Testa se elemento existe
10 if ( r != v . end ()){
11 // Elemento : 12
12 cout << " Elemento : " << * r << endl ;
13 // Posi o : 2
14 cout << " Posi o : " << r - v . begin () << endl ;
15 }
16 // ...
Na linha 8 do exemplo, procuramos o elemento 12 na sequncia formada entre os iteradores
v.begin() e v.end(). O resultado um iterador que aponta para o elemento procurado. Se o
elemento no for encontrado, um iterador para v.end() retornado. Na linha 10 testamos se o
elemento foi realmente encontrado. Caso sim, o elemento impresso nas linhas 12 e 14.
O algoritmo binary_search retorna se um elemento est presente em uma sequncia.
1 # include < algorithm >
2 // ...
3 int nums [] = {32 ,71 ,12 ,45 ,33 ,80 ,53 ,33};
4 vector < int > v ( nums , nums +8);
5 // 32 71 12 45 33 80 53 33
6 vector < int >:: iterator r ;
7 // Ordena os elementos entre begin () e end ()
8 sort ( v . begin () , v . end ());
9 // 12 32 33 33 45 53 71 80
10 // Se o elemento foi encontrado
11 if ( binary_search ( v . begin () , v . end () , 12)){
12 cout << " Elemento encontrado " << endl ;
13 }
14 // ...
Na linha 8 do exemplo, os elementos da sequncia so ordenados para permitir uma busca
binria. Na linha 11, procuramos o elemento 12 na sequncia formada entre os iteradores
v.begin() e v.end(). O valor retornado pela funo um bool que j utilizado para imprimir
uma mensagem. Para se encontrar um iterador para o elemento, as funes lower_bound e
upper_bound devem ser utilizadas.
O algoritmo count retorna quantos elementos com um dados valor existem na sequncia.
1 # include < algorithm >
2 // ...
3 int nums [] = {32 ,71 ,33 ,45 ,33 ,80 ,53 ,33};
4 vector < int > v ( nums , nums +8);
5 // 32 71 33 45 33 80 53 33
6 // Conta quantas ocorr ncias de 33 entre begin () e end ()
7 int r = count ( v . begin () , v . end () , 33);
8 cout << r << " n meros 33 " << endl ;
9 // 3 n meros 33
10 // ...
Na linha 7 do exemplo, contamos quantos elementos da sequncia formada entre os iteradores
v.begin() e v.end() so iguais a 33.
O algoritmo min_element retorna um iterador para o menor elemento de uma sequncia.
1 # include < algorithm >
2 // ...
23.3 Algoritmos numricos da STL 351
3 int nums [] = {32 ,71 ,12 ,45 ,33 ,80 ,53 ,33};
4 vector < int > v ( nums , nums +8);
5 // 32 71 12 45 33 80 53 33
6 // Procura o menor elemento entre begin () e end ()
7 int r = * min_element ( v . begin () , v . end ());
8 cout << " Menor elemento : " << r << endl ;
9 // Menor elemento : 12
10 // ...
Na linha 7 do exemplo, a funo retorna um iterador para o menor elemento da sequncia.
Utilizamos o operador * para atribuirmos o valor apontado por este iterador na varivel r.
De modo anlogo, o algoritmo max_element retorna um iterador para o maior elemento de
uma sequncia.
1 # include < algorithm >
2 // ...
3 int nums [] = {32 ,71 ,12 ,45 ,33 ,80 ,53 ,33};
4 vector < int > v ( nums , nums +8);
5 // 32 71 12 45 33 80 53 33
6 // Procura o maior elemento entre begin () e end ()
7 int r = * max_element ( v . begin () , v . end ());
8 cout << " Maior elemento : " << r << endl ;
9 // Maior elemento : 80
10 // ...
Funo Descrio
Numricas
accumulate Somatrio de elementos
inner_product Produto interno
partial_sum Somas parciais
adjacent_difference Diferena adjacente
iota Atribui sequncia crescente de nmeros
Como exemplo de algoritmo numrico, a funo accumulate faz um somatrio de toda uma
sequncia de valores.
1 # include < numeric >
2 // ...
3 int nums [] = {32 ,71 ,12 ,45 ,33 ,80 ,53 ,33};
4 vector < int > v ( nums , nums +8);
5 // 32 71 12 45 33 80 53 33
6 // Soma todos os elementos do vector iniciando somat rio com 0
7 int r = accumulate ( v . begin () , v . end () , 0);
8 cout << " Somat rio : " << r << endl ;
352 Captulo 23. Algoritmos da STL
23.4 Exerccios
Exerccio 23.1 Veja o cdigo abaixo. Ao complet-lo codigo abaixo, vamos implementar o
jogo de cartas 21. Neste jogo, h uma pilha com todas as cartas do baralho (stack). Cada
carta sera representada com um numero de 1 a 13 (1 = A, 11=J, 12=Q, 13=K). O naipe no
ser representado pois no relevante.
A cada passo, o jogador pode tirar uma carta da pilha e colocar em seu conjunto de cartas
(em outro container) ou parar de jogar. O somatrio dos valores das cartas em sua mo sua
pontuao mas o jogador perde o jogo se o somatrio ultrapassar 21.
Analise o cdigo abaixo, que est incompleto.
1 # include < iostream >
2 # include < string >
3 // bibliotecas com os containeres
4 # include < vector >
5 # include < stack >
6 # include < algorithm >
7
8 using namespace std ;
9
10 void imprimeCarta ( int const & x );
11
12 template < typename T > // fun o que imprime container
13 void imprimeBaralho ( T const & x );
14
15 int main ()
16 {
17 // cria um container vetor com as cartas
18 vector < int > x ;
19 // Aloca espa o para 13*4 elementos
20 x . reserve (13*4);
21 int i ;
22
23 // Coloca 4 vezes cada numero de 1 a 13 no baralho
24 for ( i =1; i <13; i ++){
25 x . push_back ( i );
26 x . push_back ( i );
27 x . push_back ( i );
28 x . push_back ( i );
29 }
30
31 cout << " Cartas do baralho : " << endl ;
32 imprimeBaralho ( x );
23.4 Exerccios 353
33
34 // Embaralha e imprime
35 cout << " Embaralhando : " << endl ;
36 random_shuffle ( x . begin () , x . end () );
37 imprimeBaralho ( x );
38
39 // Cria uma pilha vazia
40 stack < int > pilha ;
41
42 // Fa a a pilha receber as cartas do baralho aqui
43 // Use a funcao push para colocar algo na pilha
44
45 vector < int > cartas ;
46
47 // Menu com op es
48 string opcao ;
49 do {
50 cout << " Suas cartas s o : " << endl ;
51 imprimeBaralho ( cartas );
52
53 // Mostra o menu
54 cout << endl ;
55 cout << " [1] Puxar mais uma carta da pilha " << endl ;
56 cout << " [0] Sair do jogo " << endl ;
57 cout << " Digite uma op o : " ;
58
59 // L uma op o
60 cin >> opcao ;
61
62 // executa uma fun o de acordo com a op o
63 if ( opcao == " 1 " ){
64 cout << " Puxando mais uma carta " << endl ;
65 // Implemente este trecho
66 cout << " C digo n o implementado " << endl ;
67 } else if ( opcao == " 0 " ){
68 cout << " Saindo do programa " << endl ;
69 } else {
70 cout << " Opcao invalida " << endl ;
71 }
72 } while ( opcao != " 0 " );
73
74 cout << " Fim de jogo " << endl ;
75 // Implemente este trecho
76 cout << " C digo n o implementado " << endl ;
77
78 return 0;
79 }
80
81 void imprimeCarta ( int const & x ){
354 Captulo 23. Algoritmos da STL
82 if ( x ==1){
83 cout << " A " ;
84 } else if (x <11){
85 cout << x << " " ;
86 } else if ( x ==11) {
87 cout << " J " ;
88 } else if ( x ==12) {
89 cout << " Q " ;
90 } else if ( x == 13) {
91 cout << " K " ;
92 }
93
94 }
95
96 template < typename T >
97 void imprimeBaralho ( T const & x )
98 {
99 if ( x . empty ()){
100 std :: cout << " ( Vazio ) " << endl ;
101 return ;
102 }
103 // iterator que vai percorrer x
104 typename T :: const_iterator pos ;
105 // iterador que aponta para a posi o final
106 typename T :: const_iterator end ( x . end ());
107 for ( pos = x . begin (); pos != end ; ++ pos ) {
108 imprimeCarta (* pos );
109 }
110 cout << endl ;
111 }
Exerccio 23.2 Logo aps a criao de uma pilha vazia, faa com que ela receba todas as
cartas do baralho em x.
Exerccio 23.3 O vector cartas receber os elementos que o jogador retirar da pilha.
Implemente a retirada de uma carta da pilha e sua insercao no vector cartas quando o jogador
pedir.
Exerccio 23.4 Quando o jogador no quiser mais retirar cartas, ele utilizar a opcao == 0
e sair do for. Quando isto ocorrer, implemente o trecho de cdigo que mostrar a pontuao
final do jogador. Se a soma ultrapassar 21, dever ser emitida uma mensagem que ele perdeu
o jogo. Utilize a funo accummulate.
Exerccio 23.5 Qual a vantagem da utilizao de uma pilha stack em vez de se representar
a pilha de cartas com um container de sequncia?
23.4 Exerccios 355
Exerccio 23.7 No jogo real de 21, as cartas K, J, Q e 10 (representadas por 10, 11, 12, 13)
valem 10. As cartas A (representadas aqui por 1) podem valer 1 ou 11. Faa esta alterao no
programa na hora do resultado final. O jogador receber a maior pontuao possvel para seu
conjunto de cartas. Dica: Utilize a funo replace para alterar as cartas K, J e Q, que devem
valer 10. Considere que um A vale 1 e conte o nmero de As (funo count). Troque estes
As que valem 1 por As iguais a 10 enquanto tenhamos menos que 21.
IV
Programao Orientada a
Objetos
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Classes
0
24.
1 0 e Instncias
1
0 0 1
0 1 Objetos
0
de 00
1
110 1
0
1 0
0
110 0
1
1 0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
24.1 Introduo
Temos estudado at o momento o que chamamos de programao estruturada. Em programa-
o estruturada, o fluxo no qual ocorrem os comandos tem a maior importncia. O foco est na
ordem das aes. assim que temos trabalhado at agora.
A Figura 24.1 apresenta uma representao em diagrama de programas baseados em pro-
gramao estruturada. Nestes programas, seguimos um fluxo de aes, que pode ser totalmente
sequencial ou alterado por desvios e laos.
J na programao orientada a objetos (POO), a nossa preocupao com os objetos
que tm atributos e aes. Dados os objetos, nos preocupamos em determinar como eles se
comportam e no em um fluxo geral de aes. Queremos saber como cada objeto e o que faz
cada objeto.
A Figura 24.2 apresenta uma representao em diagrama de objetos utilizados em um
programa para representar animais. Nestes programas, as caractersticas dos objetos e relaes
entre objetos so mais importantes do que um fluxo de comandos.
No exemplo apresentado, temos 3 objetos: Animal, Cachorro e Ovelha. Os objetos Cachorro
e Ovelha so apenas tipos de objeto do tipo Animal. Cachorro e Ovelha tm suas caractersticas
e comportamentos especficos.
Comandos
Comandos
Comandos
Comandos
Comandos Comandos
Comandos
Comandos
Comandos
Comandos
Comandos
Cada retngulo tem dois atributos: largura e altura. Com a definio deste struct
poderamos criar dados do tipo Retangulo. Atravs da instruo class, poderamos definir o
mesmo tipo de dado da seguinte maneira:
1 class Retangulo {
2 public :
3 int largura , altura ;
4 };
Para criar um novo tipo de dados definindo uma class, devemos dizer que os membros da
classe so pblicos para que eles sejam acessveis. Isto foi feito na linha 2 com a palavra-chave
public. Sem a palavra chave public, os membros da classe seriam considerados privados e no
seriam acessveis. Isto um conceito importante em POO. Em um struct, todos os membros
so considerados pblicos por padro.
Nos objetos, alm de variveis, podemos ter funes como membros de uma classe. As
funes representam comportamentos de uma classe de objetos. Podemos ter alguns comporta-
mentos comuns a retngulos:
1 class Retangulo {
2 int largura , altura ;
3 public :
4 void set_valores ( int , int );
5 int area () { return largura * altura ;}
6 };
Nas linhas 4 e 5, definimos 2 funes pblicas relativas a retngulos. Definimos compor-
tamentos relativos a retngulos. Na linha 2, definimos atributos de um retngulo. Note que os
atributos na linha 2 no so pblicos pois so definidos antes da palavra-chave public. Podemos
ter em um objeto membros pblicos e privados ao mesmo tempo. Neste exemplo, temos membros
pblicos e privados: comportamentos pblicos e atributos privados.
24.2 Classes e Instncias de Objetos 361
Animal
branco
e marrom
branca
Cachorro Ovelha e preta
Consegue
latir
Consegue correr Consegue correr
(mais devagar) (mais rpido)
Na verdade, comum deixar explcito quais so os membros privados. Para isto usamos a
palavra-chave private.
1 class Retangulo {
2 public :
3 void set_valores ( int , int );
4 int area () { return largura * altura ;}
5 private :
6 int largura , altura ;
7 };
Com isto, deixamos os membros privados no fim da definio, o que interessante pois
as pessoas esto normalmente mais interessadas nos membros que elas podem acessar em um
objeto.
As funes do prprio objeto, claro, so as nicas que tm acesso aos membros privados
deste objeto. Como os atributos do retngulo no esto acessveis, podemos utilizar a funo
pblica set_valores para determinar os valores de largura e altura. Isto uma prtica
muito comum pois podemos usar a funo para validar os valores inseridos para largura e
altura. Podemos, por exemplo, verificar se so valores maiores que zero.
Note tambm que, na linha 3, apenas o cabealho da funo est definido no objeto. A
implementao da funo deve vir depois.
Na linha 4, temos a definio da funo area. Esta funo utiliza os atributos conhecidos
para retornar a rea do retngulo. Como esta uma funo simples, sua implementao j consta
no objeto.
Note com a funo area, como a orientao a objetos pode ser til para agregar nos
retngulos as funes que so associadas apenas a retngulos.
Inclumos no cdigo em seguida tambm a definio da funo set_valores. A sintaxe
Retangulo::set_valores utilizada para especificar fora da definio da classe que a funo
sendo implementada pertence classe Retangulo.
1 class Retangulo {
2 public :
3 void set_valores ( int , int );
362 Captulo 24. Classes e Instncias de Objetos
A rea de r 12
Note que funo area no recebe parmetros pois tudo que ela precisa dos prprios
atributos internos do retngulo r.
24.4 Ponteiros para objetos 363
A rea de a 12
A rea de b 20
12
13 void Retangulo :: set_valores ( int x , int y ) {
14 largura = x ;
15 altura = y ;
16 }
17
18 int main (){
19 Retangulo a ;
20 Retangulo * b = new Retangulo ();
21 a . set_valores (3 ,4);
22 b - > set_valores (4 ,5); // ou (* b ). set_valores (4 ,5);
23 cout << " A rea de a " << a . area () << endl ;
24 cout << " A rea de * b " << b - > area () << endl ;
25 delete b ;
26 b = nullptr ;
27 return 0;
28 }
Na linha 20, criamos um ponteiro para Retangulo chamado b. Alocamos dinamicamente
um Retangulo na memria e fazemos com que b aponte para ele.
Assim como se pode fazer em um struct, podemos utilizar o operador de seta -> em objetos
para acessar um membro de um objeto apontado. Isto feito na linha 22. Veja como o operador
de seta da linha 22 equivale a retornar o valor apontado por b com o operador * e depois acessar
um membro.
Na linha 24, utilizamos novamente o operador de seta para imprimir a rea do Retangulo
apontado por b.
A rea de a 12
A rea de *b 20
17
18 int main (){
19 Retangulo a [2];
20 a [0]. set_valores (3 ,4);
21 a [1]. set_valores (4 ,5);
22 cout << " A rea de a [0] " << a [0]. area () << endl ;
23 cout << " A rea de a [1] " << a [1]. area () << endl ;
24 return 0;
25 }
Na funo deste exemplo, trabalhamos com a[0] e a[1], que guardam dois retngulos
diferentes.
A rea de a[0] 12
A rea de a[1] 20
A rea de a[0] 12
A rea de a[1] 20
366 Captulo 24. Classes e Instncias de Objetos
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Definindo
0
25.
1 01
0 0 1
classes
0
0 0
1
110 1
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 01 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
H vrias prticas comuns e recursos para definio de objetos. Nesta seo veremos algumas
delas.
25.1 Contrutores
Quando um objeto criado, uma funo construtora chamada. Chamamos esta funo de
construtor. Esta funo constri o objeto dando valores iniciais a seus atributos. Para definir o
construtor, criamos uma funo com o mesmo nome da classe.
1 # include < iostream >
2
3 using namespace std ;
4
5 class Retangulo {
6 public :
7 Retangulo ( int , int );
8 int area () { return ( largura * altura );}
9 private :
10 int largura , altura ;
11 };
12
13 Retangulo :: Retangulo ( int a , int b ) {
14 largura = a ;
15 altura = b ;
16 }
17
18 int main () {
19 Retangulo ra (3 ,4);
20 Retangulo rb (5 ,6);
368 Captulo 25. Definindo classes
A rea de ra 12
A rea de rb 30
30 }
No primeiro construtor, implementado nas linhas 14 a 17, se nenhum parmetro passado, o
Retangulo criado com altura e largura iguais a 5. No segundo construtor, implementado
nas linhas 19 a 22, se os parmetros so passados, eles so atribudos aos membros.
Criamos ento neste exemplo um retngulo com cada construtor. Na linha 25, utilizamos
o construtor que recebe dois parmetros e, na linha 26, utilizamos o construtor que no recebe
parmetros.
A rea de ra: 12
A rea de rb: 25
Podemos criar quantas sobrecargas quisermos. Precisamos apenas que cada sobrecarga
dependa de uma combinao diferente de parmetros.
1 # include < iostream >
2
3 using namespace std ;
4
5 class Retangulo {
6 public :
7 Retangulo ();
8 Retangulo ( int );
9 Retangulo ( int , int );
10 int area () { return ( largura * altura );}
11 private :
12 int largura , altura ;
13 };
14
15 Retangulo :: Retangulo () {
16 largura = 5;
17 altura = 5;
18 }
19
20 Retangulo :: Retangulo ( int a ) {
21 largura = a ;
22 altura = a ;
23 }
24
25 Retangulo :: Retangulo ( int a , int b ) {
26 largura = a ;
27 altura = b ;
28 }
29
30 int main () {
31 Retangulo ra (3);
32 Retangulo rb = 4;
33 cout << " rea de ra : " << ra . area () << endl ;
34 cout << " rea de rb : " << rb . area () << endl ;
35 return 0;
36 }
370 Captulo 25. Definindo classes
Neste exemplo, se apenas um parmetro passado, ele atribudo tanto altura quanto
largura, como definido nas linhas 20 a 23. Nas linhas 31 e 32 utilizamos apenas este construtor
para definir novos retngulos. Como vimos, o parmetros ou os parmetros devem ser passados
para o objeto em sua criao, como fizemos na linha 31.
Construtores com apenas um parmetro so interessantes pois podem ser utilizados para
atribuir um tipo de dado a outro. Neste exemplo, atribumos um int em Retangulo na linha 32.
A converso feita pelo construtor de apenas um parmetro.
A rea de ra: 9
A rea de rb: 16
Este construtor recebe outro elemento do mesmo tipo e faz uma cpia, como definido nas
linhas 19 a 22 e utilizado na linha 26.
25.3 Sobrecarga de operadores 371
A rea de ra: 12
A rea de rb: 12
Na funo principal, nas linnhas 25 a 27, usamos estes recursos para criar um retngulo rc
como a soma de ra e rb.
rea de rc: 63
1 class Retangulo {
2 public :
3 Retangulo ( int , int );
4 void setAltura ( int x );
5 void setLargura ( int x );
6 int getAltura () { return altura ;}
7 int getLargura () { return largura ;}
8 int area () { return ( largura * altura );}
9 private :
10 int largura , altura ;
11 };
12
13 Retangulo :: Retangulo ( int a , int b ) {
14 setAltura ( a );
15 setLargura ( b );
16 }
17
18 void Retangulo :: setAltura ( int x ) {
19 if ( x >=0){
20 altura = x ;
21 } else {
22 altura = 0;
23 }
24 }
25
26 void Retangulo :: setLargura ( int x ) {
27 if ( x >=0){
28 largura = x ;
29 } else {
30 largura = 0;
31 }
32 }
Na nova classe temos 2 setters e 2 getters que servem para enviar e receber valores relativos
s variveis. Estas funes esto definidas nas linhas 4 a 7. Os getters, definidos nas linhas 6 e 7,
simplesmente retornam os valores de altura e largura.
As funes setters que do valores aos atributos so definidas nas linhas 18 a 32, conferem
se os valores so maiores do que zero pois a altura e largura de um retngulo no podem ser
valores negativos.
J a funo construtora, definida nas linhas 13 a 16, que define os valores iniciais de altura
e largura, utiliza os prprios setters para garantir que os valores sero vlidos.
Considere agora uma funo main que utiliza este Retangulo:
1 int main () {
2 Retangulo ra (3 ,2);
3 cout << " A rea de ra " << ra . area () << endl ;
4 ra . setAltura (4);
5 ra . setLargura (7);
6 cout << " A rea de ra " << ra . area () << endl ;
7 ra . setAltura (3);
8 ra . setLargura ( -5); // tentando passar um valor inv lido
374 Captulo 25. Definindo classes
25.5 Destrutores
Alm dos construtores, podemos definir quais comandos sero executados quando um objeto
for destrudo. Pode parecer estranho alterar valores do objetos ou fazer aes quando no
precisaremos mais dos objetos, mas isto especialmente importante para evitar vazamento de
memria quando destrumos objetos que haviam alocado espao na memria.
Suponha uma classe utilizada para representar listas de nmeros inteiros em um arranjo.
Como no sabemos qual ser o tamanho da lista, utilizaremos alocao dinmica de memria.
1 class Lista {
2 public :
3 Lista ( int );
4 void setElemento ( int pos , int x ) { px [ pos -1] = x ;}
5 int getElemento ( int pos ) { return px [ pos -1];}
6 private :
7 int * px ;
8 int tamanho ;
9 };
10
11 Lista :: Lista ( int n ){
12 tamanho = n ;
13 px = new int [ n ];
14 for ( int i = 0; i < n ; i ++){
15 px [ i ] = i +1;
16 }
17 }
Entre os atributos da lista, definidos nas linhas 7 e 8, um ponteiro px apontar para o arranjo
alocado na memria e um outro membro guardar o tamanho do arranjo alocado, que tambm
o tamanho da lista.
O construtor declarado na linha 3 recebe o tamanho da lista de elementos. Em sua imple-
mentao, nas linhas 11 a 17, o construtor aloca um arranjo do tamanho pedido nas linhas 12 e
13, e d valores de 1 a n para os elementos da lista, nas linhas 14 a 16.
Os setters e getters das linhas 4 e 5 retornam o elemento em uma posio da lista. A funo
considera que as posies na lista so de 1 at n.
Veja agora o que acontece em relao aos atributos do objeto quando ele construdo. Apenas
o ponteiro px e tamanho so atributos da lista. O arranjo, apesar de ser usado para representar a
lista, est alocado na memria, fora do escopo do objeto.
25.5 Destrutores 375
Lista(10)
tamanho 10 px
1 2 3 4 5 6 7 8 9 10
Lista(10)
tamanho 10 px
1 2 3 4 5 6 7 8 9 10
Para que isto no ocorra, precisamos definir na funo destrutora que o arranjo ser desa-
locado tambm sempre que o objeto for destrudo. Nesta nova definio da classe, temos uma
funo destrutura na linha 4.
1 class Lista {
2 public :
3 Lista ( int );
4 ~ Lista ();
5 void setElemento ( int pos , int x ) { px [ pos -1] = x ;}
6 int getElemento ( int pos ) { return px [ pos -1];}
7 private :
8 int * px ;
9 int tamanho ;
10 };
11
12 Lista :: Lista ( int n ){
13 tamanho = n ;
14 px = new int [ n ];
15 for ( int i = 0; i < n ; i ++){
16 px [ i ] = i +1;
17 }
18 }
19
20 Lista ::~ Lista (){
21 delete [] px ;
22 }
A funo destrutura tem o mesmo nome da classe precedido de um . A funo destrutora
no recebe parmetros e executada sempre que um objeto ser destrudo.
376 Captulo 25. Definindo classes
Na implementao do destrutor, feita nas linhas 20 a 22, podemos dizer ao objeto para
desalocar a memria apontada por px antes de excluir os atributos da lista.
Lista(10)
tamanho 10 px
1 2 3 4 5 6 7 8 9 10
Lista(10)
tamanho 10 px
1 2 3 4 5 6 7 8 9 10
0 1
1 0 0 0 1 1 10 0 11 1 01 1 0
0 1 1 1 0 1 0
10 1 0 1 10 1 1 1 00 1 0 0 11 0 0 0 11 0 0 1 10 1 0
0
1
1 1
1
0
1
0 1
1
1
0
0 0
0 1
1
0
1
11
1 1
1 1 1 0
1 00
0 0
0
10 1
1 11
0 1 1
0
1
1
0 0
0
100 10
0
0
1
1
000 0 1
1
0 0
1
0 111 0
0
00 1
0
011 101
0
1
0
0
010 1
0
1 0
1
0 111 1
0
10
0
1 0 1
1 0 1
1 1 0 0 11
0 1 1 0 1 01 1
10 1 11 1 1 0
0 1
010 1
1 0
111 0
1 0
001 1
0 0
111 0
0 0
111 1
0 1
001 0
0
1 0
1
0
1
1 1 1 1
0
1
1 11 1 01
1
10
1 1 0 0
00
0 1
1 1 0
1 10
0 0
0 1 0
1
0 0
1 0 1 1 0 0 1 0 1 0 1 0 0 1 1 1 1
11
0
0 0
1
1 0
1
0 10
1
1
0
10 0
0
00
1
0 0 1 0 1
01 1 0 1 0 11 0 1
1 0 11 0 1 1
1 100 1
0 0
001 0
1 0
000 1 0 1
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Relaes
0
26.
1 0 entre
1
0 0 objetos
1
0 1 0 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Estudaremos nas prximas sees vrias maneiras como objetos podem se relacionar.
1 class Departamento {
2 public :
3 Departamento ( string s ) { nome = s ;};
4 void contrataProfessor ( Professor x ) { docentes . push_back ( x );}
5 int getTamanho () { return docentes . size ();};
6 void imprime ();
7 private :
8 vector < Professor > docentes ;
9 string nome ;
10 };
11
12 void Departamento :: imprime (){
13 cout << nome << " \ tProfessores " << endl ;
14 vector < Professor >:: iterator i ;
15 for ( i = docentes . begin (); i != docentes . end (); ++ i ){
16 cout << i - > getCpf () << " \ t " << i - > getNome () << endl ;
17 }
18
19 }
1 int main () {
2 Professor x ( " Joao " , 123);
3 Departamento dept ( " DECOM " );
4 dept . contrataProfessor ( x );
5 Professor y ( " Jose " , 345);
6 dept . contrataProfessor ( y );
7 dept . imprime ();
8 return 0;
9 }
Assim como criamos um Departamento com vrios professores, poderamos ter criado
tambm uma classe Universidade, com vrios departamentos, e assim por diante. Podemos
perceber como composio de objetos uma ferramenta til para modelar sistemas complexos.
26.2 Herana
Quando uma classe herda de outra classe, ele pega para si todas as funes e membros desta
classe. Chamamos esta classe superior de superclasse.
26.2 Herana 381
Poligono
Retangulo Triangulo
5 }
6 };
Na linha 1, o trecho : public Poligono faz com que a classe Retangulo herde de
Poligono.
Alm das funes definidas em Poligono, a classe Retangulo tem uma funo que utiliza
seus valores de altura e largura para clculo de sua rea. Assim, apesar de todo Retangulo
ser um Poligono, ele tambm pode ter seus atributos e comportamentos especficos.
Deste modo, a definio acima de Retangulo equivalente seguinte definio:
1 class Retangulo {
2 public :
3 void set_valores ( int a , int b ){
4 largura = a ;
5 altura = b ;
6 }
7 int area (){
8 return largura * altura ;
9 }
10 private :
11 int largura , altura ;
12 };
Pela diferena entre as definies equivalentes, note como a herana facilita o reaproveita-
mento de cdigo.
De volta a nosso exemplo, podemos definir outras classes de polgonos que tenham altura
e largura. Definimos ento uma nova classe Triangulo que tambm herda os atributos e
comportamentos de Poligono.
1 class Triangulo : public Poligono {
2 public :
3 int area (){
4 return ( largura * altura )/2;
5 }
6 };
Note que cada classe que herda de Poligono pode ter tambm seus atributos e comporta-
mentos especficos. Neste caso, o clculo da rea diferente para um tringulo. Note mais uma
vez como isto facilita o reaproveitamento de cdigo.
Atravs da herana, conseguimos definir apenas com uma implementao de set_valores
os valores de qualquer objeto que herde de Poligono.
1 int main () {
2 Retangulo rect ;
3 Triangulo trgl ;
4 rect . set_valores (4 ,5);
5 trgl . set_valores (4 ,5);
6 cout << rect . area () << endl ;
7 cout << trgl . area () << endl ;
8 return 0;
9 }
Alm da atribuio de valores feita nas linhas 4 e 5, conseguimos tambm utilizar diretamente
as funes especficas destes objetos, como fizemos nas linhas 6 e 7.
26.2 Herana 383
20
10
26.2.1 Polimorfismo
Outra possibilidade interessante relacionada herana entre objetos polimorfismo. Um retn-
gulo, por exemplo, por ser tambm um polgono, pode ser tratado ento simplesmente como
Retangulo ou como Poligono. Em outras palavras, um objeto pode assumir mais de uma
forma, dependendo de sua relao de herana.
Veja este exemplo de como um retngulo ou um tringulo podem ser tratados simplesmente
como polgonos.
1 int main () {
2 Retangulo rect ;
3 Triangulo trgl ;
4 Poligono * ppoli1 = & rect ; // Retangulo um Poligono
5 Poligono * ppoli2 = & trgl ; // Triangulo um Poligono
6 ppoli1 - > set_valores (4 ,5);
7 ppoli2 - > set_valores (4 ,5);
8 cout << rect . area () << endl ;
9 cout << trgl . area () << endl ;
10 return 0;
11 }
O exemplo cria um Retangulo e um Triangulo nas linhas 2 e 3. Em seguida, nas linhas
4 e 5, so criados dois ponteiros para polgonos. Um aponta para o retngulo e outro para o
tringulo. No h problema nisto pois um retngulo e um tringulo tambm so polgonos! Eles
podem assumir esta forma.
Utilizamos nas linhas 6 e 7 os ponteiros ento para dar valores aos polgonos. Isto tambm
possvel pois set_valores uma funo comum a polgonos.
Em seguida, nas linhas 8 e 9, acessamos as funes de rea que pertencem aos objetos.
20
10
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Recursos
0
27.
1 0 de Objetos
1
0 0 1
01 0 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 0 01 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
22 }
23
24 int par :: getMenor (){
25 if ( primeiro < segundo ){
26 return primeiro ;
27 } else {
28 return segundo ;
29 }
30 }
Os nicos atributos desta classe so os dois nmeros inteiros, declarados nas linhas 12 e
13. Entre os recursos da classe podemos no s retornar o primeiro e segundo elementos mas
tambm o maior e o menor elementos, pelas funes declaradas nas linhas 7 a 10. Por serem
maiores, as funes getMaior e getMenor esto definidas fora da classe, nas linhas 16 a 30.
Veja o resultado da execuo deste programa.
1 int main () {
2 par objeto (6 , 12);
3 cout << " Par de objetos " << endl ;
4 cout << " Primeiro = " << objeto . getPrimeiro () << endl ;
5 cout << " Segundo = " << objeto . getSegundo () << endl ;
6 cout << " Maior = " << objeto . getMaior () << endl ;
7 cout << " Menor = " << objeto . getMenor () << endl ;
8 return 0;
9 }
Par de objetos
Primeiro = 6
Segundo = 12
Maior = 12
Menor = 6
Parece claro que um objeto que guarda um par de qualquer tipo de dados no muito
diferente de um objeto que guarda um par de dados do tipo int. Para isto, com a seguinte
converso, conseguimos representar a mesma classe com templates.
1 template < class T >
2 class par {
3 public :
4 par ( T a , T b ){
5 primeiro = a ;
6 segundo = b ;
7 }
8 T getPrimeiro () { return primeiro ;}
9 T getSegundo () { return segundo ;}
10 T getMaior ();
11 T getMenor ();
12 private :
13 T primeiro ;
14 T segundo ;
27.1 Templates de Objetos 387
15 };
16
17 template < class T >
18 T par <T >:: getMaior (){
19 if ( primeiro > segundo ){
20 return primeiro ;
21 } else {
22 return segundo ;
23 }
24 }
25
26 template < class T >
27 T par <T >:: getMenor (){
28 if ( primeiro < segundo ){
29 return primeiro ;
30 } else {
31 return segundo ;
32 }
33 }
Na linha 1, iniciamos a definio da classe avisando que T representar um template. Em
toda a definio da classe, at a linha 15, substitumos ento todas as ocorrncias de int pelo
tipo de dado T, representando um template.
As implementaes das funes do objeto tambm precisam ser feitas com templates. Por
isto os templates T so definidos novamente nas linhas 17 e 26, antes do incio da implementao.
Pelas definies das linhas 18 e 27, vamos que estas funes agora retornam dados do tipo T e
pertencem ao objeto par<T>.
Veja agora o resultado da execuo deste programa.
1 int main () {
2 par < int > objeto (6 , 12);
3 cout << " Par de objetos " << endl ;
4 cout << " Primeiro = " << objeto . getPrimeiro () << endl ;
5 cout << " Segundo = " << objeto . getSegundo () << endl ;
6 cout << " Maior = " << objeto . getMaior () << endl ;
7 cout << " Menor = " << objeto . getMenor () << endl ;
8 return 0;
9 }
Par de objetos
Primeiro = 6
Segundo = 12
Maior = 12
Menor = 6
Agora, claro, podemos utilizar nosso objeto com qualquer tipo de dado ou objeto.
1 int main () {
2 par < char > objeto ( g , t );
3 cout << " Par de objetos " << endl ;
4 cout << " Primeiro = " << objeto . getPrimeiro () << endl ;
5 cout << " Segundo = " << objeto . getSegundo () << endl ;
6 cout << " Maior = " << objeto . getMaior () << endl ;
7 cout << " Menor = " << objeto . getMenor () << endl ;
8 return 0;
9 }
Par de objetos
Primeiro = g
Segundo = t
Maior = t
Menor = g
1 int main () {
2 par < double > objeto (6.7 , 3.2);
3 cout << " Par de objetos " << endl ;
4 cout << " Primeiro = " << objeto . getPrimeiro () << endl ;
5 cout << " Segundo = " << objeto . getSegundo () << endl ;
6 cout << " Maior = " << objeto . getMaior () << endl ;
7 cout << " Menor = " << objeto . getMenor () << endl ;
8 return 0;
9 }
Par de objetos
Primeiro = 6.7
Segundo = 3.2
Maior = 6.7
Menor = 3.2
Para imprimir o raio, utilizamos a funo getRaio, que retorna um double. A impresso
s possvel porque o objeto cout reconhece variveis do tipo double. J o seguinte comando
levaria a um erro:
1 cout << " Roda de raio " << x << endl ;
Isto porque o objeto cout no sabe trabalhar com variveis do tipo Roda. Assim, neste
prximo exemplo, criamos um operador de converso.
1 # include < iostream >
2
3 using namespace std ;
4
5 class Roda {
6 public :
7 Roda ( double x ) { raio = x ;}
8 void setRaio ( double x ) { raio = x ;};
9 double getRaio () { return raio ;}
10 operator double (){ return raio ;}
11 private :
12 double raio ;
13 };
14
15 int main () {
16 Roda x (7.2);
17 cout << " Roda de raio " << x . getRaio () << endl ;
18 cout << " Roda de raio " << x << endl ;
19 return 0;
20 }
Este operador double(), na linha 10, define como converter uma Roda em um double,
retornando o valor de seu raio.
Na linha 18, o objeto cout agora funciona com uma Roda, j que existe um operador que
converte uma roda para um tipo de dados conhecido por ele.
390 Captulo 27. Recursos de Objetos
No arquivo Par.h temos os recursos da classe. Este o arquivo que ser consultado por um
usurio da classe.
1 // Arquivo Par . h
2 # ifndef PAR_H
3 # define PAR_H
4
5 class Par
6 {
7 public :
8 Par ( int , int );
9 int getPrimeiro () { return primeiro ;}
10 int getSegundo () { return segundo ;}
11 int getMaior ();
12 int getMenor ();
13 private :
14 int primeiro ;
15 int segundo ;
16 };
17
18 # endif
Note que todos os recursos esto nas linhas 5 a 16. Note que acrescentamos os comandos
#ifndef PAR_H na linha 2, #define PAR_H na linha 3 e #endif na linha 18. Estes comandos
garantem que a classe Par s seja definida se no estiver definida ainda. Isso acontece se
tentarmos incluir a classe mais de uma vez no cdigo.
O arquivo Par.cpp com o cdigo-fonte pode ser visto abaixo:
1 // Arquivo Par . cpp
2 # include " Par . h "
3
4 Par :: Par ( int a , int b ){
5 primeiro = a ;
6 segundo = b ;
7 }
8
9 int Par :: getMaior (){
10 if ( primeiro > segundo ){
11 return primeiro ;
12 } else {
13 return segundo ;
14 }
15 }
16
17 int Par :: getMenor (){
18 if ( primeiro < segundo ){
19 return primeiro ;
20 } else {
21 return segundo ;
22 }
23 }
392 Captulo 27. Recursos de Objetos
Inclumos o arquivo Par.h na linha 2. Isto equivale a replicar todo o cdigo de Par.h no
incio de Par.cpp. Note que as incluses de nossos arquivos so feitas com aspas ("Par.h") e
no com chaves (<Par.h>).
Para utilizar nossa classe, o usurio precisa somente do arquivo Par.h para conhecer seus
recursos.
Note como o arquivo Par.h deve ser includo em nosso arquivo main.cpp e como isto torna
nosso cdigo mais organizado.
1 // Arquivo main . cpp
2 # include < iostream >
3 # include " Par . h "
4
5 using namespace std ;
6
7 int main (){
8 Par x (2 ,3);
9 cout << " Maior : " << x . getMaior () << endl ;
10 cout << " Menor : " << x . getMenor () << endl ;
11 return 0;
12 }
Temos agora todos os recursos da classe Par em nosso arquivo principal.
Maior: 3
Menor: 2
A separao da interface e implementao muito til por vrios motivos. Pessoas diferentes
podem trabalhar em arquivos diferentes. A implementao pode ficar escondida do usurio da
classe. Se os cabealhos forem mantidos, as implementaes podem ser alteradas sem causar
problemas ao usurio da classe. Por fim, o cdigo fica mais organizado e modularizado.
27.4 Exerccios
Exerccio 27.1 Analise o cdigo abaixo, que est incompleto. Ela define o objeto Aluno
para ser usado em um sistema de cadastro de uma academia.
A classe j define como imprimir um aluno, atribuir dados, retornar dados e comparar
alunos.
A funo apresentaMenu entra em um lao que permite trabalhar com um conjunto
de alunos.
Utilizando a classe de alunos, o menu apresenta funes para cadastrar e remover
alunos.
A inteno deste exerccio fazer um sistema de matrcula de alunos.
Os alunos so cadastrados em um map que usa o nmero de matrcula como chave para
organizar os dados.
1 # include < iostream >
2 # include < string >
3 # include <map >
4
5 using namespace std ;
6
27.4 Exerccios 393
7 class Aluno {
8 public :
9 void imprime () {
10 cout << matricula << " - " << nome << endl ;
11 return ;
12 }
13
14 // Fun es modificadoras
15 void setValores ( string x , int y ) {
16 nome = x ;
17 matricula = y ;
18 }
19
20 void setNome ( string x ) {
21 nome = x ;
22 return ;
23 }
24
25 void setMatricula ( int x ) {
26 matricula = x ;
27 return ;
28 }
29
30 // Fun es n o - modificadoras
31 string getNome () {
32 return nome ;
33 }
34
35 int getMatricula () {
36 return matricula ;
37 }
38
39 bool operator <( const Aluno x ) const {
40 return ( this - > matricula < x . matricula ) ;
41 }
42
43 private :
44 int matricula ;
45 string nome ;
46 };
47
48 void apresentaMenu ( map < int , Aluno > & cadastro ) ;
49 void imprimeCadastro ( map < int , Aluno > & x ) ;
50 void insereAluno ( map < int , Aluno > & x ) ;
51 void pesquisaAluno ( map < int , Aluno > & x ) ;
52
53 int main () {
54 map < int , Aluno > cadastro ; // chave a matricula ( int ) e
registro o pr prio aluno
394 Captulo 27. Recursos de Objetos
Exerccio 27.2 Imagine que cada aluno pode agora ser cadastrado em vrios cursos da
academia. O conjunto (set) de atividades (musculacao, danca, spinning, etc...) no qual um
aluno pode estar cadastrado pode ento ser representado com um set<int>, onde o inteiro
representa o cdigo da atividade (por exemplo,1 para musculacao, 2 para danca, etc...).
Crie esta varivel privada set<int> para cada aluno.
Crie funes na classe Aluno que matriculem ou desmatriculem o aluno de uma
atividade
Crie uma funo na classe Aluno para imprimir as atividades em quais atividades ele
est matriculado
Crie uma funo na classe Aluno que retorne em quantas atividades ele est matriculado
Insira uma opo no menu para matricular o Aluno em uma atividade
Insira uma opo no menu para desmatricular o Aluno de uma atividade
Altere a funo de listar todos os alunos para que ela mostre em quantas atividades
cada Aluno est matriculado
Altere a funo de pesquisa do menu para que ela mostre tambm em quais atividades
um Aluno est matriculado
Exerccio 27.4 As funes que se iniciam com set e get so utilizadas por padro para que
o usurio utilize a classe de maneira segura sem ter acesso a variaveis privadas diretamente.
Altere a funo que imprime as atividades de um aluno para que em vez dos cdigos
das atividades sejam exibidos os nomes das atividades. Crie nomes de atividades e para
cada nmero deve ser impresso um nome.
Altere as funes que inscrevem os alunos em atividades para que apenas nmeros de
atividades vlidas sejam cadastradas. Caso no seja escolhida uma atividade vlida,
uma mensagem de erro deve ser exibida.
Suponha agora que o nmero de matricula deve ter no minimo 4 digitos (numero >
999). Altere as funes que definem nmeros de matrcula para que apenas numeros
vlidos sejam cadastrados. Caso no seja escolhido um nmero vlido, uma mensagem
de erro deve ser exibida.
V
Prticas de Programao em
C++
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Prticas
0
28.
1
001
0 0 1 de Programao
Comuns
1
0 1 1 0
0
1
1101 C++ 0
em
1
0
0
0
110 0
1
0
0
000 0
0
1
1
0
0
00 1 00 0 0 0 01 0 1 01 00 11
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Neste captulo discutiremos brevemente prticas importantes de programao que no podem ser
discutidas em uma introduo didtica aos tpicos de programao. Discutiremos recursos do
C++ que formam prticas de programao distintas para programadores especializados em C++.
A funo unique() retorna se um ponteiro o nico que aponta para aquele endereo. Os
dados apontados por um ponteiro s so desalocados quando mais nenhum ponteiro aponta para
o endereo.
Alm do ponteiro inteligente shared_ptr, existem os ponteiros inteligentes unique_ptr,
que no permitem que mais de um ponteiro aponte para o mesmo endereo, e weak_ptr, que
so utilizados para guardar ponteiros crus.
6 int main ()
7 {
8 vector < int > items v { 4 , 3 , 1 , 2 };
9 sort ( v . begin () , v . end () , comparaInt );
10 return 0;
11 }
Neste exemplo, a funo comparaInt diz se um elemento maior que o outro e este critrio
utilizado para ordenar a sequncia. Assim, se quisermos ordenar a sequncia em ordem
decrescente podemos utilizar uma funo que retorna o contrrio de a < b.
1 bool comparaInt ( int a , int b )
2 {
3 return a > b ;
4 }
5
6 int main ()
7 {
8 vector < int > items v { 4 , 3 , 1 , 2 };
9 sort ( v . begin () , v . end () , comparaInt );
10 return 0;
11 }
Em C++, um objeto com uma funo definida pelo operador () pode ser utilizada no lugar
da funo passada como argumento. Este um objeto de funo:
1 class comparaInt {
2 public :
3 bool operator ()( const int &a , const int & b ) const {
4 return a < b ;
5 }
6 };
7
8 int main ()
9 {
10 vector < int > items v { 4 , 3 , 1 , 2 };
11 sort ( v . begin () , v . end () , comparaInt );
12 return 0;
13 }
O objeto de funo tem uma funo operator() que determina como se comparar dois
nmeros inteiros. A funo sort pode utilizar ento este objeto para ordenar a sequncia de
elementos. Os objetos de funo podem ter seus prprios atributos e comportamentos, estendendo
a utilidade de funes auxiliares.
Captura Descrio
[] No captura variveis externas
[&] Captura qualquer varivel externa por referncia
[=] Captura qualquer varivel externa fazendo uma cpia
[=,&v1] Captura tudo por valor e a varivel v1 por referncia
[v1] Captura v1 fazendo uma cpia
[&v1] Captura v1 por referncia
Este recurso permite o acesso a dados externos s funes auxiliares. No exemplo a seguir,
utilizaremos capturas para remover elementos menores que um certo valor na sequncia.
1 int main ()
2 {
3 vector < int > c { 1 ,2 ,3 ,4 ,5 ,6 ,7 };
4 int x = 5;
5 remove_if ( c . begin () , c . end () , [ x ]( int n ) { return n < x ; } );
6 }
28.5 For baseado em intervalo 403
A funo remove_if, neste exemplo remove todos os elementos que so menores que x. A
funo recebe o critrio de remoo de elementos atravs de uma funo lambda que recebe
nmeros inteiros como parmetro mas definida em termos de um valor x, que externo.
Por fim, uma funo lambda pode ser guardada em uma varivel do tipo function.
1 function < int ( int ) > funcao = []( int i ) { return i +10; };
2 cout << " Resultado : " << funcao (6) << \ n ;
28.6 Tuplas
Tuplas so objetos utilizados para guardar uma combinao de elementos de diferentes tipos.
Uma maneira de se retornar mais de um tipo de dado de uma funo em C++ atravs da
utilizao de tuplas.
Neste exemplo criaremos tuplas e acessaremos seus elementos.
1 # include < iostream >
2 # include < tuple >
3
4 int main ()
5 {
6 // criamos tupla com letra c e n mero 15
7 tuple < int , char > t ( c ,15);
8 // criamos tupla com fun o make_tuple
9 auto t2 = make_tuple (6.3 , " texto " , 2 , o );
404 Captulo 28. Prticas Comuns de Programao em C++
10
11 // acessamos elemento na posi o 2 da tupla t2
12 get <2 >( t2 ) = 125;
13
14 // desempacotamos elementos da tupla em vari veis
15 int x ;
16 char y ;
17 tie (y , x ) = t ;
18 // ignorando algumas das vari veis
19 tie ( ignore , ignore , x , y ) = t2 ;
20 // com a fun o get
21 y = std :: get <3 >( bar );
22
23 return 0;
24 }
A tupla pode ser definida explicitamente ou com o comando auto, que infere o tipo de dados
pelo retorna da funo make_tuple, que cria tuplas.
A funo tie serve para desempacotar os elementos da tupla enquanto a funo get
retorna um dos elementos da tupla.
Funo Descrio
advance Avana o iterador um certo nmero de posies
distance Distncia entre 2 iteradores
prev Retorna iterador para elemento anterior
next Retorna iterador para o prximo elemento
back_inserter Constri iterador de insero no fim da sequncia
front_inserter Constri iterador de insero no incio da sequncia
inserter Constri iterador de insero em um posio determinada
make_move_iterator Constri iterador para movimentar elementos
4 // Trocando os valores
5 int temp ;
6 temp = x ;
7 x = y;
8 y = temp ;
Isto ocorre pois sempre que atribumos um valor a uma varivel, seu valor antigo perdido.
Com alguns objetos, porm, isto pode gerar um alto custo em termos de desempenho. Suponha
estarmos fazendo uma troca de contedo entre dois objetos vector<int>.
1 // Trocando os valores
2 vector < int > temp ;
3 temp = x ;
4 x = y;
5 y = temp ;
Neste exemplo, se h n elementos no container x, teremos que fazer uma cpia de todos os
elementos de x em temp, o que teria custo O(n), tanto em relao a tempo quanto a memria.
Ao fim do processo, temp descartada.
Neste caso, sabemos que tivemos um desperdcio de recurso pois um vector no um
tipo fundamental de dados. Ele depende de ponteiro que, internamente, aponta para um arranjo
alocado na memria com os elementos. Poderiamos mover todo o contedo de x para temp
apenas fazendo com que o ponteiro de temp aponte para o arranjo que apontado por x. Isso
faz com que temp represente a sequncia x em tempo O(1). Mover os elementos para temp em
vez de fazer uma cpia no gera tambm problema algum lgica do programa, j que temp
apenas uma varivel temporria.
A semntica de movimento permite que no precisemos fazer cpias desnecessrias para
trabalhar com objetos temporrios.
1 # include < utility >
2 // ...
3 // Trocando os valores
4 vector < int > temp = move ( x );
5 x = move ( y );
6 y = move ( temp );
Um construtor de movimento pode ser criado de maneira similar a como criado um
construtor de cpia para um objeto. Supondo um objeto A, este seria o prottipo de seu construtor
de movimento.
28.9 Threads 407
1 // Construtor de c pia
2 A :: A ( const A & outro );
3 // Construtor de movimento
4 A :: A ( A && outro );
Note que para containers, existe tambm a funo swap, que troca os elementos entre os
containers.
1 // Trocando os valores
2 x . swap ( y );
28.9 Threads
Atualmente, so comuns programas que executam vrias tarefas ao mesmo tempo. Para isto,
precisamos do recurso de threads. Uma thread de execuo define instrues que podem ser
executadas ao mesmo tempo pelo computador. Uma thread pode ser criada em C++ com o
objeto do tipo thread.
1 // biblioteca de threads
2 # include < thread >
3 // ...
4 void funcao ( int x )
5 {
6 cout << x * x << endl ;
7 }
8 // ...
9 int main ()
10 {
11 // come a a executar uma fun o
12 thread t1 ( funcao ,4);
13 // coma a a executar outra fun o
14 thread t2 ( funcao ,10);
15
16 // sincroniza as threads
17 // pausa at que primeira thread termine
18 t1 . join ();
19 // pausa at que segunda thread termine
20 t2 . join ();
21
22 return 0;
23 }
No exemplo, temos uma funcao que imprime um valor elevado ao quadrado. Nas linhas 11
a 14, criamos as threads t1 e t2, que executaro esta funo com diferentes parmetros (4 e 10).
Cada thread executa a funo paralelamente. Nas linhas 16 a 20, pedimos ao programa para que
espere que as funes sejam executadas nas threads.
Para problemas maiores, possvel criar arranjos ou containers com threads de maneira
anloga. Porm, preciso prestar ateno pois o tempo de criao de uma thread pode no
compensar o ganho com a execuo de problemas muito pequenos em paralelo.
408 Captulo 28. Prticas Comuns de Programao em C++
Na linha 2 deste exemplo, inclumos a biblioteca chrono, necessria para se medir tempo.
Na linha 4, indicamos que vamos utilizar recursos do espao de nomes chrono, que por sua vez
est dentro do espao de nomes std.
Na biblioteca chrono, temos 3 tipos de relgios, como os apresentados na Tabela 28.3. Todos
os relgios do acesso a pontos no tempo. Na linha 5 do cdigo, utilizamos o steady_clock
para calcular intervalos. Os recursos do relgio steady_clock esto no espao de nomes
steady_clock, que est dentro do espao de nomes chrono.
Relgio Descrio
steady_clock Utilizado para calcular intervalos
system_clock Acessa o relgio geral do sistema
high_resolution_clock Relgio de maior preciso possvel
Criamos nesta linha 4 uma varivel t1 do tipo time_point. O tipo de dado time_point
est definido no espao de nomes do relgio e utilizado para guardar pontos no tempo. A
funo now(), tambm pertencente ao espao de nomes do relgio, retorna um ponto no tempo
representando o momento atual.
Na linha 6, executamos uma funo qualquer, e na linha 7 fazemos um procedimento similar
ao da linha 5 para guardar o ponto no tempo t2 aps a execuo da funo.
Na linha 8, calculamos a durao entre os dois pontos no tempo. Dados do tipo duration
guardam a durao entre dois pontos no tempo. Estes dados guardam a diferena entre dois
pontos no tempo e a proporo entre estes pontos no tempo e uma unidade de medida de tempo,
como segundos, minutos ou horas.
O duration<double> indica que dados do tipo double so utilizados internamente para
guardar a durao. A classe duration pode receber um segundo parmetro no template indi-
cando a proporo entre pontos no tempo e unidades de tempo. Por exemplo, duration<double,
seconds> faria com que os valores da durao fossem retornados em segundos. Outras opes
esto na Tabela 28.4. Por padro, a durao ser retornada em segundos.
Ao lado direito da expresso da linha 8, a funo duration_cast transforma um tipo
de durao em outro. A operao t2 - t1 retorna uma durao que representada por
um tipo de dados correspondente ao relgio. A funo duration_cast utiliza o template
28.11 Nmeros aleatrios 409
Tipo Descrio
hours Horas
minutes Minutos
seconds Segundos
milliseconds Milisegundos
microseconds Microsegundos
nanoseconds Nanosegundos
Tabela 28.4: Tipos de proporo entre pontos no tempo e unidades de medida de tempo.
duration<double> para transformar a durao recebida como parmetro para uma durao do
tipo duration<double>.
Por fim, na linha 9, a funo d.count() retorna a durao guardada em d expressa em
segundos, como definida no segundo parmetro de seu template.
Distribuio Descrio
Uniforme
uniform_int_distribution Distribuio inteira uniforme
uniform_real_distribution Distribuio real uniforme
Tentativas de Bernoulli
bernoulli_distribution Distribuio de Bernoulli
binomial_distribution Distribuio binomial
geometric_distribution Distribuio geomtrica
negative_binomial_distribution Distribuio negativa binomial
Baseada em taxa de acerto
poisson_distribution Distribuio de Poisson
exponential_distribution Distribuio exponencial
gamma_distribution Distribuio gamma
weibull_distribution Distribuio de Weibull
extreme_value_distribution Distribuio de valores extremos
Normal
normal_distribution Distribuio normal
lognormal_distribution Distribuio lognormal
chi_squared_distribution Distribuio chi-quadrado
cauchy_distribution Distribuio de Cauchy
fisher_f_distribution Distribuio de Fisher
student_t_distribution Distribuio t de Student
Por partes
discrete_distribution Distribuio discreta
piecewise_constant_distribution Distribuio contante
piecewise_linear_distribution Distribuio linear
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
001
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 1 1
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 00
1
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1 0 Algoritmos
0
29.
1 0 1
0 0 1 0
Eficientes
0 1 0
1
110 1
0
1 0
0
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 00 0 0 001 01 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
1 0
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 000 1 010 1 1 1 0 01 1 11
Nos captulos anteriores deste livro, apresentamos verses didticas de algoritmos importantes
para busca e ordenao. As verses apresentadas destes algoritmos, naturalmente, no so
as verses mais eficientes por ser tratarem de verses escolhidas principalmente por serem
mais didticas. Este captulo apresenta uma lista de algoritmos importantes em C++ com suas
implementaes eficientes. Estes so implementaes que podem ser utilizadas em aplicaes
prticas dos mtodos descritos neste livro. So tambm os mtodos utilizados para gerao dos
grficos de comparao dos algoritmos ao longo deste livro.
29.2 Ordenao
A ordenao de elementos pode ser feita com a funo sort. No caso geral, sempre mais
conveniente utilizar a funo sort do STL. Esta usualmente uma implementao do algoritmo
introsort, que eficiente e tem custo O(n log n) para todos os casos.
1 // a fun o ordena a sequ ncia entre 2 iteradores ( ou endere os )
2 sort ( v . begin () , v . end ());
3 for ( int i = 0; i < v . size (); ++ i ){
4 std :: cout << v [ i ] << ;
5 }
6 cout << endl ;
Note que a funo sort aceita o envio de um predicado para a funo, caso seja necessrio
utilizar um critrio de ordenao diferente do operador <.
1 // a fun o ordena a sequ ncia entre 2 iteradores ( ou endere os )
2 sort ( v . begin () , v . end () , []( int a , int b ){ return b < a ;});
3 for ( int i = 0; i < v . size (); ++ i ){
4 std :: cout << v [ i ] << ;
5 }
6 cout << endl ;
Nas prximas sees temos implementaes de outros algoritmos importantes de ordenao,
apresentados neste livro. Todas as funes tem a mesma sintaxe da funo sort, recebendo
apenas dois iteradores genricos.
ou mesmo endereo na memria. A funo recebe um iterador que marca o incio da sequncia
e outro que marca uma posio aps o fim da sequncia. O template do predicado para uma
funo genrica que ser usada para indicar quando um elemento maior que outro. Caso seja
necessrio utilizar a funo com o predicado padrom que o operador <, a seguinte funo
pode ser chamada:
1 template < typename Iterador >
2 void selection_sort ( Iterador inicio , Iterador fim ) {
3 typedef typename iterator_traits < Iterador >:: value_type Tipo ;
4 insertion_sort ( inicio , fim , less < Tipo >());
5 }
Na linha 3 descobrimos o tipo do iterador e o chamamos de Tipo. Na linha 4, chamamos a
verso original da funo com o operador < para este tipo de dados.
O for do algoritmo percorre todos os elementos com o iterador i. Para cada elemento
fazemos uma troca com a funo iter_swap. A troca feita entre o elemento i e o menor
elemento entre i e fim. A funo min_element do STL utilizada para encontrar este menor
elemento.
29.2.4 Quicksort
Para uma implementao eficiente do quicksort, precisamos de uma funo auxiliar. Primeira-
mente, a seguinte funo retorna o item mediano entre 3 elementos. Esta uma funo auxiliar
importante para se encontrar um piv eficiente.
1 template < typename T >
2 T median ( T t1 , T t2 , T t3 )
3 {
4 if ( t1 < t2 )
5 {
6 if ( t2 < t3 )
7 return t2 ;
8 else if ( t1 < t3 )
9 return t3 ;
10 else
11 return t1 ;
12 }
13 else
14 {
15 if ( t1 < t3 )
16 return t1 ;
17 else if ( t2 < t3 )
18 return t3 ;
19 else
20 return t2 ;
21 }
22 }
A funo retorna o elemento mediano entre os elementos t1, t2 e t3. Podemos ento definir
a funo de ordenao como a seguir:
1 template < typename Iterador , typename Predicado >
2 void quicksort ( Iterador inicio , Iterador fim , Predicado comp ) {
3 if ( fim - inicio > 1) {
4 typedef typename iterator_traits < Iterador >:: value_type Tipo ;
5 Iterador meio = inicio + ( fim - inicio )/2;
29.2 Ordenao 415
11
1
10
1
0
1
1 10
1
1 0
0 0
1
0
1 01
0
0 0
1 1
0 10
1
00
1
1
1 01
1
1
1 0
1
1
0 10
1 0
0
0
1
0
0 1 0
0
0
0 1 0
0
1
0 1 0
0 1
1
0 0
01
0 1
0 0 11
0 11
1 0
0 11 1
1 10
1
0 1
0
1
1 0 1
1
001 1
10
0
0
1
001 0
0
1
0 0
1 1 101 0
0
00 0
0
101 1
00
0
1
1
111
0
0
1
1 0
0 1 101 0
1
00
0 1 1 1 0 1 1 0 10 1 1 0 00 0 1 0 10 0 1 1
01
1 1 0
1 0
1
0
1 01
1 1
1 01
1
1 01
0 1 0
0 0
0
0
1
01
1 0
0 1
0
1
0 00
0 1
0
0
0
1
1 11
0 1 0
1 0
1
0 0
1
1
1
1
0
1 0 1
0
1
0
00 0
1
110 0 1
0
1 0
0 1
110 0
1
1 0
0
0
000 0
0 1 1
1
0
0 0
00 1 ndice
00 0 01 0 01 0 00 0 11 1
1 1 1
1
1
0 0 0
1
0 1 1
0
0 1 1 0
000
1
1
0
10
0
101
1
0 11 0
0
100
1 1
0 0 0 1
1
0 0 0 0 1 0 1 1 0 1 1 0 0 0
10 00
1 1 10 0 0 1 0 0 11 1 10 1 10 00
0 1 010 1 1 1 0 01 1 11
Identificadores . . . . . . . . . . . . . . . . . . . . . . . . . 20
if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 N
if-else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
if-else aninhados . . . . . . . . . . . . . . . . . . . . . . . 47 Nmeros aleatrios . . . . . . . . . . . . . . . . . . . 409
Implementao . . . . . . . . . . . . . . . . . . . . . . . 390 Notao O . . . . . . . . . . . . . . . . . . . . . . . 191, 193
Indentao . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
insert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
Instncia de objetos . . . . . . . . . . . . . . . . . . . 362 O
Intercalao . . . . . . . . . . . . . . . . . . . . . . . . . . 236
Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390 Objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
Interface e Implementao . . . . . . . . . . . . . 390 Objetos de funo . . . . . . . . . . . . . . . . . . . . 400
Iteradores . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 Omitindo a varivel de retorno . . . . . . . . . 135
Categorias . . . . . . . . . . . . . . . . . . . . . . . 291 Operaes com notao O . . . . . . . . . . . . . 193
Exemplo . . . . . . . . . . . . . . . . . . . . . . . . . 289 Operador de cpia e movimento . . . . . . . . 405
420 NDICE
P
R
Parmetros de funo . . . . . . . . . . . . . . . . . 127
Parmetros mltiplos em funes . . . . . . . 129 Recurso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
Particionamento no algoritmo quicksort . 246 Recurso infinita. . . . . . . . . . . . . . . . . . . . . .149
Passagem de parmetros . . . . . . . . . . . . . . . 132 Recursos de objetos . . . . . . . . . . . . . . . . . . . 385
Passagem de parmetros por referncia . 133 Registros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Passagem de parmetros por valor . . . . . . 132 Relao entre do-while e while . . . . . . . . 97
Passo recursivo . . . . . . . . . . . . . . . . . . . . . . . 143 Relao entre while e for . . . . . . . . . . . . . . 95
Percorrendo arranjos de arranjos . . . . . . . . . 88 Relaes entre objetos . . . . . . . . . . . . . . . . . 377
Percorrendo containers . . . . . . . . . . . . . . . . 285 Repetio de processos similares . . . . . . . . 73
Percorrer arranjos . . . . . . . . . . . . . . . . . . . . . . 82 Repeties determinadas pelo usurio . . . . 72
Pilhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333 Resumo de comandos . . . . . . . . . . . . . . . . . 181
Pior caso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 Retornando vrios valores . . . . . . . . . . . . . 137
Piv timo no quicksort . . . . . . . . . . . . . . . 257 Retorno de valor . . . . . . . . . . . . . . . . . . . . . . 126
Polimorfismo . . . . . . . . . . . . . . . . . . . . . . . . . 383
Ponteiro nulo . . . . . . . . . . . . . . . . . . . . . . . . . 168
Ponteiros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 S
Ponteiros como parmetros . . . . . . . . . . . . 141
Ponteiros e arranjos . . . . . . . . . . . . . . . . . . . 115 Se . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Ponteiros e estruturas . . . . . . . . . . . . . . . . . . 157 Se-seno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
NDICE 421
Tabela verdade . . . . . . . . . . . . . . . . . . . . . . . . 38
Tabelas Hash . . . . . . . . . . . . . . . . . . . . . . . . . 321
Anlise . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Fator de carga . . . . . . . . . . . . . . . . . . . . 324
Tratamento de Colises . . . . . . . . . . . 322
Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
Templates de Objetos . . . . . . . . . . . . . . . . . 385
Tempo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
Tipos Fundamentais de Dados . . . . . . . . . . . 14
Tratamento de Colises . . . . . . . . . . . . . . . . 322
Tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403