Todos nós, ou pelo menos os mais organizados, já fizeram listas de afazeres. Por exemplo:
Estudar POO.
Comprar CD dos Gotan Project
Fazer trabalho de SO
Combinar ida ao concerto dos Da Weasel
Lista como sequência de itens em que a ordem é relevante mas não é dada pelos itens em si: é determinada por uma entidade exterior à lista que insere os itens pela ordem que entende.
Pedir para eles dizerem operações que se realizam com uma lista.
Ir listando e discutindo até surgir:
Suponham que a lista continha:
1 2 3 11 20 0 345 2 3 45 12 34 30 4 4 23 3 77 4 -1 -20 45
E suponham que o próximo item a inserir era 111.
Escolher um deles (da primeira fila e em frente ao quadro) e pedir-lhe para escolher mentalmente uma posição. Dizer-lhe para me referenciar a posição. Discutir com eles a melhor forma de o referenciar. Se necessário chamá-lo ao quadro.
Para além da lista em si, temos agora algo a que poderíamos chamar.... dedo.... Não... Eu podia referenciar com o cotovelo... Um bom nome poderia ser cursor, ou referência, ou indicador, mas o que vamos usar é o nome típico destas coisas: Iterador.
Conclusão: podem-se associar iteradores a listas!
Que operações se podem realizar com iteradores?
Discutir e chegar à seguintes:
A resposta é nim. Sim, é o que é desejável, sempre que possível. Não, a nossa primeira versão invalida os iteradores existentes...
Discutir: qual é o primeiro iterador de uma lista vazia?
Suponham que se pretende inserir um item numa lista ordenada:
1 3 4 6
Suponham que o item é 5. Que fazer?
Discutir algoritmo.
Suponham que o item é 7...
Discutir. Concluir que dá jeito que os iteradores possam referenciar itens fictícios! Chamar-lhes final e inicial (fim e início são os respectivos iteradores).
Faltam pois duas operações com iteradores:
ListaDeInt
e
à classe dos seus iteradores Iterador
.
Discutir passo a passo!
A classe que representa as listas propriamente ditas:
Cria-se um sinónimo para simplificar a tarefa de criar listas para itens de outros tipos:
class ListaDeInt {
public:
Declara-se uma classe embutida que implementa o conceito de iterador (serve para percorrer e manipular listas). Discutir vantagem de ser embutida! O conceito de iterador faz sentido para listas mas também para outros tipos de contentor. Assim evitam-se colisões de nomes...
typedef int Item;
O construtor da classe (que cria uma lista vazia):
class Iterador;
Inspectores para saber os itens na frente e atrás da lista:
ListaDeInt();
Inspector que devolve o comprimento da lista:
Item const& frente() const;
Item const& trás() const;
Inspectores para saber se a lista está vazia ou cheia:
int comprimento() const;
Cheia? Pois... Esqueci-me de dizer que a nossa primeira versão das listas tem um limite fixo para o número de itens.
bool estáVazia() const;
bool estáCheia() const;
Modificadores para inserir e retirar itens da lista dos seus locais de mais fácil acesso:
Modificador para inserir um item imediatamente antes da posição referenciada pelo iterador, fazendo com que o iterador continue a referenciar o mesmo item. Discutir necessidade de passagem por referência!void põeNaFrente(Item const& novo_item);
void põeAtrás(Item const& novo_item);
void tiraDaFrente();
void tiraDeTrás();
Modificador para remover o item referenciado pelo iterador
void insereAntes(Iterador& iterador, Item const& novo_item);
iterador
,
ficando o iterador a referenciar o item logo após o item removido.
Modificador para esvaziar a lista:
void remove(Iterador& iterador);
Métodos para devolver iteradores referenciando os locais cabalísticos da lista :
void esvazia();
São inspectores ou modificadores?
Iterador primeiro();
Iterador ultimo();
Iterador inicio();
Iterador fim();
private:
Já lá vamos!
Dá-se à classe de iteração acesso às listas. Discutir porque é que isto é boa ideia! Explicar que o conceito de lista é representado na realidade por duas classes:ListaDeInt
e Iterador
. Quando assim
é não devemos ter receio de usar as amizades, pois não
são promiscuidades: são intimidades perfeitamente legítimas...
friend class Iterador;
};
A classe para representar iteradores que referenciam itens em listas ListaDeInt
:
O construtor da classe. Constrói um iterador associado à lista lista e põe-no a referenciar o seu primeiro item. Discutir
class ListaDeInt::Iterador {
public:
explicit
.
Para não escrevermos i == lista
sem que o compilador dê
erro!
Inspector que devolve o item referenciado pelo iterador:
explicit Iterador(ListaDeInt& lista_associada);
Os operadores de comparação entre itens.
Item& item() const;
Incrementação e decrementação prefixa e sufixa de iteradores (avança e recua para o próximo/anterior item):
bool operator==(Iterador const& outro_iterador) const;
bool operator!=(Iterador const& outro_iterador) const;
Iterador& operator ++();
Iterador& operator--();
Iterador operator++(int);
Iterador operator--(int);
Já lá vamos!
private:
A classe ListaDeInt
tem acesso a todos os membros da classe Iterador
.
Notar que a amizade é recíproca! E que não é
nenhuma promiscuidade! É um casamento! Por amor,
e não por conveniência...
Usando estas classes, como mostrar os itens de uma lista no ecrã?
friend class ListaDeInt;
};
Usar uma lista que ainda esteja no quadro e explicar informalmente o algoritmo. Discutir condição de paragem.
Usando estas classes, como inserir um novo item com valor
ListaDeInt
l;
...//
aqui inserem-se vários itens.for(ListaDeInt::Iterador i = l.primeiro(); i != l.fim(); ++i)
cout << i.item() << endl;
n
numa lista
lista
por ordem?
Usar uma lista que ainda esteja no quadro e explicar (de novo) informalmente o algoritmo. Discutir condição de paragem.
Mostrar que funciona bem mesmo que o item tenha de ser inserido no fim da lista. Mostrar que funciona bem mesmo que o item tenha que ser inserido numa lista vazia...
ListaDeInt
lista;
...//
aqui inserem-se vários itens.int n;
cin >> n;
ListaDeInt
::Iterador i = lista.primeiro();
while(i != lista.fim() && i.item() < n)
++i;
lista.insere(i, n);
Falta-nos discutir a implementação disto... Comecemos
pela classe ListaDeInt
. Onde guardar os itens?
Discutir e concluir que, se a lista tiver um número finito constante de itens, então os itens podem ser colocados numa matriz. Dizer que podia ser um vector, mas dá-nos jeito que seja uma matriz para as próximas aulas...
Qual a condição invariante de instância desta classe?
class ListaDeInt {
...
private:static int const número_máximo_de_itens = 100;
Item itens[número_máximo_de_itens];
int número_de_itens;
};
Discutir. Concluir que é:
Pode-se pôr um método privado para verificar o invariante!CIC: 0 <=
número_de_itens
<=número_máximo_de_itens
.
E a classe
class ListaDeInt {
...
private:static int const número_máximo_de_itens = 100;
Item itens[limite];
int número_de_itens;
bool cumpreInvariante() const;
};
bool ListaDeInt::cumpreInvariante() const
{return 0 <= número_de_itens and número_de_itens <= número_máximo_de_itens;
}
Iterador
? Como implementá-la?
Discutir que os iteradores têm de saber: a posição do item, a lista onde estão! Pode haver mais do que uma lista! Necessidade de referência!
Qual a condição invariante de instância desta classe?
class ListaDeInt::Iterador {
...
private:ListaDeInt& lista_associada;
int indice_do_item_referenciado;
};
Discutir. Deixar claro que os iteradores podem ficar inválidos... Concluir que é:
Pode-se pôr um método privado para verificar o invariante!-1 <=
índice_do_item_referenciado
.
Faltam as operações! Comecemos pelo construtor da lista:
class ListaDeInt::Iterador {
...
private:ListaDeInt& lista_associada;
int indice_do_item_referenciado;
bool cumpreInvariante() const;};
bool ListaDeInt::Iterador::cumpreInvariante() const
{return -1 <= índice_do_item_referenciado;
}
Simples, não?
inline ListaDeInt::ListaDeInt()
: número_de_itens(0)
{assert(cumpreInvariante());
}
Vamos agora para o põeNaFrente()
.
Discutir algoritmo: empurrar itens para a frente. A lista não
pode estar cheia! Discutir assert()
. Lembrar #include
<cassert>
.
Verificar se cumpre condição invariante!
void ListaDeInt::põeNaFrente(Item const& novo_item)
{
Só se pode inserir se ainda houver espaço:
assert(cumpreInvariante());
Inserir no início implica rearranjar todos os itens já na lista:
assert(not cheia());
Agora já há espaço:
for(int i = comprimento(); i != 0; --i)
itens[i] = itens[i - 1];
E convém incrementar o contador de elementos...
itens[0] = novo_item;
Confirmar que no final também cumpre invariante!
++número_de_itens;
Vamos agora ao procedimento
assert(cumpreInvariante());
}
tiraDeTrás()
. Sugestões?
É simples:
Saltámos umas quantas funções e procedimentos que ficam para a aula prática. Mas vamos agora ver o procedimento de inserção antes de um iterador. A ideia é parecida com a inserção no início, só que não é preciso, em geral, rearranjar todos os itens...
inline void ListaDeInt::tiraDeTrás()
{assert(cumpreInvariante());
assert(!vazia());
--numero_de_itens;
assert(cumpreInvariante());
}
Não se pode inserir se o iterador referir o elemento antes do primeiro!
void ListaDeInt::insereAntes(Iterador& iterador, const Item& novo_item)
{assert(cumpreInvariante());
assert(not cheia());
Há que rearranjar todos os itens a partir do referenciado pelo iterador:
assert(iterador != inicio());
Agora já há espaço. Discutir revalidação do iterador!
for(int i = comprimento(); i != iterador.indice_do_item_referenciado; --i)
itens[i] = itens[i - 1];
E outros métodos simples:
itens[iterador.índice_do_item_referenciado++] = novo_item;
assert(iterador.cumpreInvariante());
++número_de_itens;
assert(cumpreInvariante());
}
Agora vamos a funções mais interessantes! Que tal a que devolve o primeiro iterador? Como resolver?
inline int ListaDeInt::comprimento() const
{assert(cumpreInvariante());
return numero_de_itens;
}
inline bool ListaDeInt::estáVazia() const
{assert(cumpreInvariante());
return comprimento() == 0;
}
Discutir construtor do iterador.
ou simplesmente:
inline ListaDeInt::Iterador ListaDeInt::primeiro()
{assert(cumpreInvariante());
Iterador iterador(*this);
return iterador;
}
E o último? É parecido, mas temos de alterar o índice!
inline ListaDeInt::Iterador ListaDeInt::primeiro()
{assert(cumpreInvariante());
return Iterador(*this);
}
O iterador deve referenciar o item na traseira da lista:
inline ListaDeInt::Iterador ListaDeInt::ultimo()
{assert(cumpreInvariante());
Iterador iterador(*this);
E o mesmo para as outras...
iterador.indice_do_item_referenciado = comprimento() - 1;
assert(iterador.cumpreInvariante());
return iterador;
}
O iterador deve referenciar o item fictício antes da frente da lista:
inline ListaDeInt::Iterador ListaDeInt::inicio()
{assert(cumpreInvariante());
Iterador iterador(*this);
O iterador deve referenciar o item fictício após o último item da lista:
iterador.indice_do_item_referenciado = -1;
assert(iterador.cumpreInvariante());
return iterador;
}
inline ListaDeInt::Iterador ListaDeInt::fim()
{assert(cumpreInvariante());
Iterador iterador(*this);
Já só faltam os procedimentos e funções da classe
iterador.indice_do_item_referenciado = comprimento();
assert(iterador.cumpreInvariante());
return iterador;
}
Iterador
. Comecemos pelo construtor.
Discutir índice 0. Discutir necessidade de lista de inicializadores.
E agora o operador de decrementação, que serve para recuar na lista.
inline ListaDeInt::Iterador::Iterador(ListaDeInt& l)
: lista_associada(l), indice_do_item_referenciado(0)
{assert(cumpreInvariante());
}
Discutir recuo para aquém do primeiro: válido. E para aquém do início: inválido!
inline ListaDeInt::Iterador& ListaDeInt::Iterador::operator -- ()
{assert(cumpreInvariante());
assert(*this != lista_associada.inicio());
--indice_de_item_referenciado;
assert(cumpreInvariante());
return *this;
}
Falta uma função importante: a comparação entre iteradores. Quando são dois iteradores iguais?
Discutir e concluir que são iguais quando se referem ao mesmo item. E que além disso não faz sentido comparar iteradores associados a listas diferentes! Mas como comparar as listas? Fica p'ra' próxima...
E há outra interessante: saber qual o item referenciado pelo iterador. Quando é tal operação possível?
inline bool ListaDeInt::Iterador::operator == (const Iterador& outro_iterador)
{assert(cumpreInvariante() and outro_iterador.cumpreInvariante());
// assert(
iteradores associados à mesma lista...);
return indice_do_item_referenciado == outro_iterador.indice_do_item_referenciado;
}
inline bool ListaDeInt::Iterador::operator != (const Iterador& outro_iterador)
{assert(cumpreInvariante() and outro_iterador.cumpreInvariante());
// assert(
iteradores associados à mesma lista...);
return indice_do_item_referenciado != outro_iterador.indice_do_item_referenciado;
}
Discutir!
ListaDeInt
::Item& ListaDeInt::Iterador::item()
{assert(cumpreInvariante());
assert(*this != lista_associada.inicio() and
*this != lista_associada.fim());
return lista_associada.itens[indice_do_item_referenciado];
}