vector
.Nesta aula vamos falar de agregados. Em particular vamos falar das matrizes clássicas do C++ e dos chamados vectores. Vamos ver que as matrizes clássicas do C++ têm muitos problemas e que os vectores são uma excelente alternativa.
Considerem o problema de ler três inteiros do teclado e de os escrever pela ordem inversa.
Discutir solução.
É evidente que a solução pode ser:
Esta solução é simples e clara. Mas tem um problema: não é generalizável. Se em vez de três passassem a ser 100 inteiros? Será que temos de criar 100 variáveis com nomes diferentes e acrescentá-las às operações de extracção e inserção? Claramente não é prático.
#include <iostream>
using namespace std;
int main()
{
cout << "Introduza 3 inteiros: ";
int a, b, c;
cin >> a >> b >> c;
cout << c << endl
<< b << endl
<< a << endl;
}
Em todas as linguagens que se prezem há um mecanismo apropriado para resolver este problema: as matrizes.
Note-se que se usa o termo "matriz" como possível tradução do inglês "array". Do mesmo modo se irá usar o termo "vector". As nossas matrizes e vectores não têm senão remota relação com as matrizes da Álgebra!
Uma matriz é um agregado de variáveis do mesmo tipo. Uma matriz é uma variável (quase) como qualquer outra, mas que é constituída na realidade por um determinado número de elementos. Os elementos são conhecidos pelo seu índice, que funciona um pouco como o número da página do elemento.
Em C++ existem as chamadas "matrizes clássicas", que se definem como se segue:
Esta instrução constrói uma matriz chamada
#include <iostream>
using namespace std;
int main()
{
cout << "Introduza 100 inteiros: ";
int a, b, c;int m[100];
cin >> a >> b >> c;
cout << c << endl
<< b << endl
<< a << endl;
}
m
com
100 elementos do tipo int
. Esta matriz pode ser representada
por um diagrama:
Desenhar diagrama "UML" (adaptar diagrama abaixo para 100 elementos).
Repare-se que cada elemento é conhecido por m
[i],
onde i é o chamado índice do elemento.
Os índices das matrizes em C++ começam sempre em zero!
Assim, a matriz m
possui elementos com índices de 0
a 99, ou seja, de 0 à sua dimensão menos um!
A sintaxe da definição de matrizes é:
ou
tipo nome[número_de_elementos];
se se quiser inicializar os seus elementos.
tipo nome[número_de_elementos] = {lista_de_inicialização};
Se não se inicializar uma matriz com elementos de tipos básicos do C++, estes conterão inicialmente lixo. Uma inicialização faz-se indicando os valores iniciais de cada um dos elementos. Por exemplo:
Se faltarem valores iniciais, os elementos por inicializar explicitamente (sempre os últimos) são inicializados implicitamente com zero:
char vogais[5] = {'a', 'e', 'i', 'o', 'u'};
Assim, uma forma prática de inicializar todos os elementos de uma matriz com zero é escrever:
int m[3] = {1, 2};
Quando numa expressão se colocam parênteses rectos após o nome de uma matriz e, dentro destes, uma expressão com valor inteiro, está-se a usar o operador de indexação. É importante perceber que se pode indexar uma matriz com uma expressão qualquer, constante ou não! É aliás isso que permite adaptar de uma forma simples o resto do programa.
int m[3] = {};
O programa como está não funciona. Não existem
as variáveis a
, b
e c
. Como fazer a leitura dos 100 valores?
Discutir soluções. Deixar claro que a solução por extensão não nos interessa. Gostaríamos de uma solução por compreensão.
A solução passa obviamente por usar um simples ciclo.
Discutir guarda do ciclo!
O operador de indexação permitiu-nos fazer a leitura com muita facilidade. Quando a variável
#include <iostream>
using namespace std;
int main()
{
cout << "Introduza 100 inteiros: ";
int m[100];
for(int i = 0; i != 100; ++i)
cin >> m[i];
cout << c << endl
<< b << endl
<< a << endl;
}
i
tem o valor 0 é lido o primeiro valor e colocado no primeiro elemento da matriz,
quando i
tem o valor 1 acontece o mesmo para o segundo valor da
matriz, e assim sucessivamente.
O operador de indexação pode ser usado noutros contextos para obter um elemento da matriz que pode ser usado como uma outra variável qualquer:
Discutir valores iniciais das matrizes.
double m[10];
m[3] = 3.3;
double x = m[3];
É importante perceber-se que
Ordinal | Índice |
---|---|
primeiro elemento (1º elemento) | 0 |
segundo elemento (2º elemento) | 1 |
n-ésimo elemento (nº elemento) | n - 1 |
O mesmo tipo de solução se pode usar agora para fazer a escrita dos valores pela ordem inversa.
Discutir ciclo. Escrever duas alternativas.
A primeira solução é preferível, até porque é o exacto simétrico do ciclo directo.
#include <iostream>
using namespace std;
int main()
{
cout << "Introduza 100 inteiros: ";
int m[100];
for(int i = 0; i != 100; ++i)
cin >> m[i];
for(int i = 99; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = 100; i != 0; --i)
cout << m[i - 1] << endl;
}
É de notar que os índices envolvidos em ambos os ciclos são:
Suponham agora que se pretendia alterar o número de valores a inverter para 1000. Que alterações se deveria fazer?
Discutir. Concluir que não basta fazer uma substituição de 100 por 1000! Senão o 99 faltaria. Dizer que substituir 100 por 1000 é muito perigoso!
Imaginem que o 100 ocorria no programa como significando, por exemplo, 100 Km/h. Imaginem o resultado de substituir 100 por 1000 se este fosse um programa usado pela brigada de trânsito...
A solução passa por utilizar uma constante:
Agora, voltar a alterar o número de valores lidos e invertidos é trivial.
#include <iostream>
using namespace std;
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
Mas isto leva-nos a uma possível generalização do programa: e se se permitisse ao utilizador especificar o número de valores e ler e inverter? Que tal
Acontece que isto não é possível: o compilador tem de saber a dimensão exacta das matrizes construídas!
#include <iostream>
using namespace std;
int main()
{
cout << "Introduza o número de valores a ler e inverter: ";
int dimensão;
cin >> dimensão;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
Mais, uma vez estabelecida, a dimensão das matrizes é imutável!
Repare-se que não se resolve o problema com truques:
Explicar que tem de ser uma constante com valor conhecido pelo compilador!
#include <iostream>
using namespace std;
int main()
{
cout << "Introduza o número de valores a ler e inverter: ";
int n;
cin >> n;
int const dimensão = n;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
Este é um problema grave das matrizes: a sua dimensão tem de ser conhecida durante a compilação e não pode variar. Uma matriz com 1000 elementos será sempre uma matriz com 1000 elementos.
Estes e outros problemas com estas chamadas matrizes clássicas do C++ levar-nos-ão a usar uma alternativa simpática às matrizes: os vectores. A ver no final da aula...
Assim sendo, não temos outro remédio, para já, senão voltar ao código original...
Podemos agora tentar melhorar o programa de modo a usar um pouco de modularização: é sempre boa ideia...
#include <iostream>
using namespace std;
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
Precisamos de dois módulos na forma de rotinas: uma que permita obter os valores extraídos
do teclado, e outra que os escreva por ordem inversa. A segunda chamar-se-á
escreveInvertida()
e é naturalmente um procedimento:
A primeira não é tão evidente. Se for uma função, devolverá a matriz lida, pelo que se poderá chamar
#include <iostream>
using namespace std;
/**
Escreve no ecrã os elementos da matriz passada como argumento
começando no último.
*/
...
void escreveInvertida()
{
...
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
matrizLida()
.
Se for um procedimento, recebe por referência a matriz onde deve
colocar os valor lidos e deve-se chamar lêPara()
.
Na realidade, porém, o C++ não nos deixa alternativa: não é possível devolver matrizes em C++.
Neste caso nem é problemático, pois a rotina de devolver a matriz teria sempre duas funções: ler a matriz e devolvê-la. Ou a interpretávamos como uma função com efeitos laterais ou como um procedimento que devolve um valor. Ambas má ideia...
Já começamos a ver que as chamadas matrizes clássicas têm uns quantos problemas... No fim desta aula veremos os chamados vectores, que não têm nenhum dos problemas das matrizes...
Que parâmetros deverão ter os procedimentos? O primeiro, para leitura, deverá receber a matriz por referência, de modo a alterar a matriz passada por referência. O segundo deverá receber a matriz por valor. Ou seja:
#include <iostream>
using namespace std;
/**
Lê do teclado valores inteiros que coloca por ordem nos elementos da
matriz passada como argumento.
*/
void lêPara(
...)
{
...
}
/**
Escreve no ecrã os elementos da matriz passada como argumento
começando no último.
*/
...
void escreveInvertida()
{
...
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
Há aqui dois problemas. O primeiro é que não se está a distinguir entre passagem de matrizes por valor e por referência. Pois. O problema é que em C++ as matrizes são sempre passadas por referência!
#include <iostream>
using namespace std;
/**
Lê do teclado valores inteiros que coloca por ordem nos elementos da
matriz passada como argumento.
@pre
cin
.good
() ecin
permite extracção dedimensão
inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosdimensão
inteiros que
continha inicialmente e a matriz
m
contém-nos pela mesma ordem.*/
void lêPara(int m[dimensão]){
...
}
/**
Escreve no ecrã os elementos da matriz passada como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osdimensão
inteiros da matrizm
no canalcout
.@post ¬
cout
.good
() oucout
sofreu inserções dosdimensão
elementos da matriz
m
pela sua ordem inversa.*/
void escreveInvertida(int m[dimensão]){
...
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
// ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
Dizer que é uma mentirinha inocente, que será desmentida no segundo semestre... Mencionar en passant ponteiros, para quem queira perceber melhor o assunto ter pelo menos uma referência.
Isto põe-nos um problema adicional. Se todas as matrizes
são passadas por referência, como podemos deixar claro que
o procedimento escreveInvertida()
não é suposto
alterar a matriz passada como argumento? A solução
passa por dizer que a matriz passada por referência é uma
matriz de elementos constantes!
Sobrou-nos um outro problema: a constante
#include <iostream>
using namespace std;
/**
Lê do teclado valores inteiros que coloca por ordem nos elementos da
matriz passada como argumento.
@pre
cin
.good
() ecin
permite extracção dedimensão
inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosdimensão
inteiros que
continha inicialmente e a matriz
m
contém-nos pela mesma ordem.*/
void lêPara(int m[dimensão]){
...
}
/**
Escreve no ecrã os elementos da matriz passada como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osdimensão
inteiros da matrizm
no canalcout
.@post ¬
cout
.good
() oucout
sofreu inserções dosdimensão
elementos da matriz
m
pela sua ordem inversa.*/
void escreveInvertida(int const m[dimensão]){
...
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
dimensão
não
é conhecida fora da função main()
, e portanto não
pode ser utilizada nos cabeçalhos dos procedimentos... Uma
possível solução é tornar a constante global:
O problema agora tem mais uma vez a ver com a particularidade das matrizes do C++: quando uma matriz ocorre como parâmetro de uma rotina, a sua dimensão é simplesmente ignorada!
#include <iostream>
using namespace std;
int const dimensão = 1000;
/**
Lê do teclado valores inteiros que coloca por ordem nos elementos da
matriz passada como argumento.
@pre
cin
.good
() ecin
permite extracção dedimensão
inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosdimensão
inteiros que
continha inicialmente e a matriz
m
contém-nos pela mesma ordem.*/
void lêPara(int m[dimensão]){
...
}
/**
Escreve no ecrã os elementos da matriz passada como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osdimensão
inteiros da matrizm
no canalcout
.@post ¬
cout
.good
() oucout
sofreu inserções dosdimensão
elementos da matriz
m
pela sua ordem inversa.*/
void escreveInvertida(int const m[dimensão]){
...
}
int main()
{
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
Dizer en passant que isto tem mais uma vez a ver com ponteiros.
Bolas! Então tudo fica na mesma se se omitir a dimensão das matrizes usadas como parâmetros:
Falta, claro está, completar os corpos dos procedimentos e usá-los na função
#include <iostream>
using namespace std;
int const dimensão = 1000;
/**
Lê do teclado valores inteiros que coloca por ordem nos elementos da
matriz passada como argumento.
@pre
cin
.good
() ecin
permite extracção dedimensão
inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosdimensão
inteiros que
continha inicialmente e a matriz
m
contém-nos pela mesma ordem.*/
void lêPara(int m[]){
...
}
/**
Escreve no ecrã os elementos da matriz passada como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osdimensão
inteiros da matrizm
no canalcout
.@post ¬
cout
.good
() oucout
sofreu inserções dosdimensão
elementos da matriz
m
pela sua ordem inversa.*/
void escreveInvertida(int const m[]){
...
}
int main()
{
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
main()
:
Repare-se que nesta solução continua a precisar-se da constante global. Além disso, os dois procedimentos estão condenados a trabalhar com matrizes com exactamente o número de elementos dados pela constante
#include <iostream>
using namespace std;
int const dimensão = 1000;
/**
Lê do teclado valores inteiros que coloca por ordem nos elementos da
matriz passada como argumento.
@pre
cin
.good
() ecin
permite extracção dedimensão
inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosdimensão
inteiros que
continha inicialmente e a matriz
m
contém-nos pela mesma ordem.*/
void lêPara(int m[]){
assert(cin.good());
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
}
/**
Escreve no ecrã os elementos da matriz passada como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osdimensão
inteiros da matrizm
no canalcout
.@post ¬
cout
.good
() oucout
sofreu inserções dosdimensão
elementos da matriz
m
pela sua ordem inversa.*/
void escreveInvertida(int const m[]){
assert(cout.good());
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
int main()
{
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
lêPara(m);
escreveInvertida(m);
}
dimensão
.
Será que podemos flexibilizar os procedimentos para funcionarem com matrizes de dimensões arbitrárias?
Discutir. Concluir que basta passar a dimensão como argumento adicional.
Ou seja:
Repare-se que agora os procedimentos são genéricos: funcionam tão bem para matrizes com 1000 elementos como para matrizes com 3 ou 3000000 elementos. Aliás, mais do que isso, a dimensão passada como argumento a estes procedimentos não precisa de ser igual à dimensão real das matrizes: pode ser menor. Claro está que não pode ser maior, pois de outra forma tentar-se-ia aceder a elementos inexistentes da matriz.
#include <iostream>
using namespace std;
/**
Lê do teclado valores inteiros que coloca por ordem nos elementos da
matriz passada como argumento.
@pre 0 <=
dimensão
edimensão
<= dim(m
) ecin
.good
() ecin
permite extracção de
dimensão
inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosdimensão
inteiros que
continha inicialmente e a matriz
m
contém-nos pela mesma ordem.*/
void lêPara(int m[], int const dimensão){
assert(cin.good() and 0 <= dimensão);
for(int i = 0; i != dimensão; ++i)
cin >> m[i];
}
/**
Escreve no ecrã os elementos da matriz passada como argumento
começando no último.
@pre 0 <=
dimensão
edimensão
<= dim(m
) ecout
.good
() e podem-se
inserir os
dimensão
inteiros da matrizm
no canalcout
.@post ¬
cout
.good
() oucout
sofreu inserções dosdimensão
elementos da matriz
m
pela sua ordem inversa.*/
void escreveInvertida(int const m[], int const dimensão){
assert(cout.good() and 0 <= dimensão);
for(int i = dimensão - 1; i != -1; --i)
cout << m[i] << endl;
//
ou
for(int i = dimensão; i != 0; --i)
cout << m[i - 1] << endl;
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
int m[dimensão];
lêPara(m, dimensão);
escreveInvertida(m, dimensão);
}
Isto leva-nos a uma outra característica indesejável das matrizes: as indexações de matrizes não são verificadas! Que acontece quando se indexa uma matriz fora dos seus limites?
Suponha-se o seguinte código:
Que acontece quando este código é executado? Uma de quatro coisas:
int a = 0;
int m[3];
int b = 0;
m[-1] = 1;
m[3] = 2;
cout << a << ' ' << b << endl;
Porquê? É simples. As variáveis a, m
e b são provavelmente organizadas na memória como se segue:
a
|
m[2]
|
m[1]
|
m[0]
|
b
|
Logo, escrever m[3]
acaba por ser o mesmo que escrever a
,
e escrever m[-1]
dá no mesmo que escrever b
!
Contra-intuitivo e desagradável.
Conclusão: cuidado com as indexações!
Para concluir o nosso périplo pelos problemas das matrizes, falta apenas dizer que estas têm duas restrições adicionais: não se podem atribuir nem comparar matrizes! Ou seja, não é possível escrever o código:
Finalmente, falta abordar a questão das matrizes multi-dimensionais. As matrizes clássicas do C++ são unidimensionais. Não é possível definir matrizes multi-dimensionais: é conceito que não existe em C++!
double valores1[2] = {1.1, 2.2};
double valores2[2] = {3.3, 4.4};
if(
valores 1 != valores2
)
valores1 = valores2
;
Mas é possível definir matrizes de matrizes, o que acaba por ter o mesmo efeito prático. Por exemplo:
define uma matriz com três elementos, cada um dos quais é uma matriz com dois elementos do tipo
double matriz[2][3];
double
. A interpretação
é:
Desenhar pequeno diagrama simplificado.
double (matriz[2])[3];
Que sucede se se fizer a atribuição
Discutir e explicar.
matriz[1][0] = 12.0;
Alinhar os defeitos das matrizes:
Começámos esta aula dizendo que os vectores definidos pela biblioteca padrão do C++ são uma boa alternativa às matrizes. De facto, assim é.
Para fazer uso dos vectores é necessário incluir o ficheiro
de interface vector
, ou seja, tem de se colocar no início
do programa a seguinte linha:
Este ficheiro contém a definição de uma classe genérica chamada
#include <vector>
vector
. A noção de classe
será vista a partir das próximas aulas. Para já
é suficiente dizer que classes são tipos definidos pelo utilizador,
ou seja, tipos adicionados aos tipos básicos do C++.Ou seja, o ficheiro contém a definição de
um
tipo genérico chamado vector
. Porquê "tipo
genérico" e
não apenas tipo? Porque um tipo genérico representa um conjunto
parametrizável de tipos.
No caso do tipo modelo vector
, há apenas um
parâmetro a ser especificado: é o parâmetro onde se diz qual o tipo dos itens do vector! Esse tipo coloca-se entre parênteses agudos
após o nome do tipo modelo:
Esta instrução define uma variável
vector<double> v;
v
que
é um vector de itens do tipo double
. Como não
se especificou qualquer dimensão para o vector, terá dimensão
nula. Isso não é problemático porque os vectores
podem mudar de dimensão sempre que necessário.
É possível indicar uma dimensão inicial para o vector:
A dimensão inicial não precisa de ser conhecida do compilador!
vector<double> v(10);
Também é possível dizer qual o valor com que todos os itens do vector devem ser inicializados.
Infelizmente não é possível inicializar os itens com valores distintos, com se faz no caso das matrizes, pelo menos de uma forma óbvia.
vector<double> v(10, 13.0);
Esperam-se evoluções da linguagem nesse sentido!
Ao contrário do que acontece com as matrizes, os vectores:
Os vectores têm uma vantagem adicional: sabem a sua dimensão. Por exemplo:
Para saber a dimensão de um vector usa-se a operação
vector<double> v(10, 13.0);
cout << v.size() << endl;
size()
. Uma operação é como que uma rotina que se aplica a uma determinada variável.
O assunto será clarificado nas próximas aulas, bastando para
já dizer que a sintaxe de invocação de uma operação é:
A operação
variável.operação(lista_de_argumentos)
size()
devolve um inteiro sem sinal. Mas o tipo
exacto não é especificado. Pode ser um dos três tipos
aritméticos inteiros sem sinal do C++:
unsigned short int
unsigned int
unsigned long int
é um sinónimo do tipo que deve ser usado para guardar a dimensão de vectores de
vector<double>::size_type
double
.
Torna-se agora possível traduzir o código desenvolvido de modo a usar vectores.
Discutir alterações assinaladas! Explicar porque deixa de haver a primeira alternativa do ciclo inverso.
Lembram-se porque se decidiu usar um procedimento para ler o valores? Simplesmente porque não se podiam devolver matrizes! Mas vectores podem!
#include <iostream>
#include <vector>
using namespace std;
/**
Lê do teclado valores inteiros que coloca por ordem nos itens do
vector passado como argumento.
@pre
cin
.good
() ecin
permite extracção dem
.size
() inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosm
.size
() inteiros que
continha inicialmente e o vector
m
contém-nos pela mesma ordem.*/
void lêPara(vector<int>& m){
assert(cin.good());
for(vector<int>::size_type i = 0; i != m.size(); ++i)
cin >> m[i];
}
/**
Escreve no ecrã os itens do vector passado como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osm
.size
() inteiros do vectorm
no
canal
cout
.@post ¬
cout
.good
() oucout
sofreu inserções dosm
.size
() itens do vector
m
pela sua ordem inversa.*/
void escreveInvertido(vector<int> m){
assert(cout.good());
for(vector<int>::size_type i = m.size() - 1;
i != -1
; --i)
cout << m[i] << endl;
//
ou
for(vector<int>::size_type i = m.size(); i != 0; --i)
cout << m[i - 1] << endl;
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
vector<int> m(dimensão);
lêPara(m);
escreveInvertido(m);
}
Discutir solução com função:
Os vectores têm duas operações simétricas, chamadas
#include <iostream>
#include <vector>
using namespace std;
vector<int> vectorLido(int dimensão)
{
assert(cin.good());
vector<int> m(dimensão);
for(vector<int>::size_type i = 0; i != m.size(); ++i)
cin >> m[i];
return m;}
/**
Escreve no ecrã os itens do vector passado como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osm
.size
() inteiros do vectorm
no
canal
cout
.@post ¬
cout
.good
() oucout
sofreu inserções dosm
.size
() itens do vector
m
pela sua ordem inversa.*/
void escreveInvertido(vector<int> m){
assert(cout.good());
for(vector<int>::size_type i = m.size(); i != 0; --i)
cout << m[i - 1] << endl;
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
vector<int> m = vectorLido(dimensão);
escreveInvertido(m);
}
push_back()
e pop_back()
. A primeira recebe como argumento o valor de
um novo item que é acrescentado ao vector no seu final. A
segunda remove o item final de um vector. Recorrendo à primeira, pode-se escrever a função de leitura de uma forma diferente:
Discutir desvantagem: mais lento, menos claro.
#include <iostream>
#include <vector>
using namespace std;
vector<int> vectorLido(int dimensão)
{
assert(cin.good());
vector<int> m;
for(int i = 0; i != dimensão; ++i) {
int valor;
cin >> valor;
m.push_back(valor);
}
return m;
}
/**
Escreve no ecrã os itens do vector passado como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osm
.size
() inteiros do vectorm
no
canal
cout
.@post ¬
cout
.good
() oucout
sofreu inserções dosm
.size
() itens do vector
m
pela sua ordem inversa.*/
void escreveInvertido(vector<int> m){
assert(cout.good());
for(vector<int>::size_type i = m.size(); i != 0; --i)
cout << m[i - 1] << endl;
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
vector<int> m = vectorLido(dimensão);
escreveInvertido(m);
}
Discutir cópias: quer na devolução quer na passagem por valor. Regressar ao procedimento, argumentando de novo contra as rotinas dois em um!
Como evitar cópias no caso das passagens por valor? Uma possibilidade é passar a usar passagens por referência. Mas se se usar passagem por referência está-se a dar uma mensagem clara ao compilador: o procedimento pode alterar o vector! Mas isso não é verdade! Como resolver o problema? Dizendo que, apesar de a passagem ser feita por referência, o procedimento está proibido de alterar o vector:
#include <iostream>
#include <vector>
using namespace std;
/**
Lê do teclado valores inteiros que coloca por ordem nos itens do
vector passado como argumento.
@pre
cin
.good
() ecin
permite extracção dem
.size
() inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosm
.size
() inteiros que
continha inicialmente e o vector
m
contém-nos pela mesma ordem.*/
void lêPara(vector<int>& m)
{
assert(cin.good());
for(vector<int>::size_type i = 0; i != m.size(); ++i)
cin >> m[i];
}
/**
Escreve no ecrã os itens do vector passado como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osm
.size
() inteiros do vectorm
no
canal
cout
.@post ¬
cout
.good
() oucout
sofreu inserções dosm
.size
() itens do vector
m
pela sua ordem inversa.*/
void escreveInvertido(vector<int> m){
assert(cout.good());
for(vector<int>::size_type i = m.size(); i != 0; --i)
cout << m[i - 1] << endl;
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
vector<int> m(dimensão);
lêPara(m);
escreveInvertido(m);
}
Explicar bem a noção de um sinónimo restritivo!
#include <iostream>
#include <vector>
using namespace std;
/**
Lê do teclado valores inteiros que coloca por ordem nos itens do
vector passado como argumento.
@pre
cin
.good
() ecin
permite extracção dem
.size
() inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosm
.size
() inteiros que
continha inicialmente e o vector
m
contém-nos pela mesma ordem.*/
void lêPara(vector<int>& m){
assert(cin.good());
for(vector<int>::size_type i = 0; i != m.size(); ++i)
cin >> m[i];
}
/**
Escreve no ecrã os itens do vector passado como argumento
começando no último.
@pre
cout
.good
() e podem-se inserir osm
.size
() inteiros do vectorm
no
canal
cout
.@post ¬
cout
.good
() oucout
sofreu inserções dosm
.size
() itens do vector
m
pela sua ordem inversa.*/
void escreveInvertido(vector<int> const& m){
assert(cout.good());
for(vector<int>::size_type i = m.size(); i != 0; --i)
cout << m[i - 1] << endl;
}
int main()
{
int const dimensão = 1000;
cout << "Introduza " << dimensão << " inteiros: ";
vector<int> m(dimensão);
lêPara(m);
escreveInvertido(m);
}
Repare-se que se pode generalizar este programa tirando partido do facto de que a dimensão dos vectores é totalmente livre: pode ser o próprio utilizador a indicar qual o número de valores a ler e inverter. Além disso, é melhor ideia usar um nome para o vector que seja indicativo do que é guardado:
Para terminar, falta referir uma operação muito útil dos vectores, que permite alterar a sua dimensão:
#include <iostream>
#include <vector>
using namespace std;
/**
Lê do teclado valores inteiros que coloca por ordem nos itens do
vector de valores passado como argumento.
@pre
cin
.good
() ecin
permite extracção dem
.size
() inteiros sucessivos.
@post ¬
cin
.good
() oucin
já não contém os primeirosm
.size
() inteiros que
continha inicialmente e o vector
m
contém-nos pela mesma ordem.*/
void lêPara(vector<int>& valores){
assert(cin.good());
for(vector<int>::size_type i = 0; i != valores.size(); ++i)
cin >> valores[i];
}
/**
Escreve no ecrã os valores passados como argumento, começando no último.@pre
cout
.good
() e podem-se inserir osm
.size
() inteiros do vectorm
no
canal
cout
.@post ¬
cout
.good
() oucout
sofreu inserções dosm
.size
() itens do vector
m
pela sua ordem inversa.*/
void escreveInvertido(vector<int> const& valores){
assert(cout.good());
for(vector<int>::size_type i = valores.size(); i != 0; --i)
cout << valores[i - 1] << endl;
}
int main()
{
cout << "Quantos valores quer que sejam lidos e invertidos? ";
int dimensão;
cin >> dimensão;
cout << "Introduza " << dimensão << " inteiros: ";
vector<int> valores(dimensão);
lêPara(valores);
escreveInvertido(valores);
}
vector<int> v(5, 1);
v.resize(8);
v.resize(9, 2);
v.resize(2);
Discutir.