Containers e iterators
Produzido por Gustavo BarbosaTeoria
E aí galera, aqui é o Juan do Responde Aí, e hoje vamos continuar nossos estudos de C++ aprendendo sobre containers e iterators
A Standard Template Library – STL.
Os templates, uma forma especial de preparar funções e classes para receber tipos de dados variados.
Como você pode imaginar, eles podem ser muito úteis na hora de criar bibliotecas versáteis e cheias de funções legais.
E foi exatamente isso que os criadores do C++ fizeram, eles criaram a Standard Template Library – STL, um conjunto gigantesco de templates de classes que contém estruturas de dados muito usados e funções importantes, organizada em forma de algoritmos, funções, containers e iterators.
A STL facilita muito a vida dos programadores, mas devido ao tamanho massivo dessa biblioteca, hoje vamos dar atenção a dois pontos em especial: o container e os iterators.
Os containers
Pra começar, vou te dizer o que é um container (alguns professores traduzem para contêiner): é uma estrutura que consegue armazenar dados de maneira organizada, como uma lista, pilha, conjunto, dicionário, etc.
É como se o container fosse um objeto que consegue armazenar outros objetos.
Você pode pensar nos containers como uma evolução das arrays. Os containers são programados através de templates de classes, e por isso já vêm de fábrica com vários métodos legais que os tornam muito mais poderosos do que as arrays que já estamos acostumados.
O container
O container é um template de classe que existe na STL, e que se comportam como as arrays comuns, guardando dados na memória de forma sequencial. Show de bola. Mas e daí?
Qual é a vantagem de usar esse tal de ao invés das arrays que já estamos acostumados?
A principal vantagem é que os são alocados dinamicamente por padrão! Isso quer dizer que a gente pode mudar o tamanho de um vector a vontade, sem ter que nos preocupar em fazer alocação dinâmica de memória. A desvantagem é que os consomem mais memória do que as arrays comuns, porém é o preço a ser pago pela facilidade de uso.
Então, sem mais enrolação, vamos lá usar um vector:
Para adicionar um ao nosso programa precisamos incluir o cabeçalho :
Agora, você deve pensar no como um template de classe (afinal ele é isso mesmo), então quando você for criar um vetor, basta você instanciar um objeto da classe inserindo o tipo entre as chaves:
E assim você criou um completamente vazio. Para saber o tamanho da array que a gente criou nós podemos usar o método :
Para inserir um elemento em um devemos usar o método , que adiciona um elemento no fim do vector:
Para visualizar os elementos de um vetor podemos usar os colchetes , que nem nas arrays, para qualquer elemento contido no vetor. Mas também podemos usar o método para ver o primeiro elemento e o método para ver o último elemento.
A qualquer momento podemos remover um elemento do nosso . Usando o método a gente remove o último elemento do
E se for necessário, podemos apagar todos os elementos do com o método
Viu só que legal?! Os são que nem as arrays com esteroides!
Outras formas de instanciar um
Na parte anterior a gente criou um e preencheu ele usando o várias vezes seguidas, e você deve ter notado que essa é uma maneira um pouco cansativa de fazer. Imagina, preencher 15 valores dessa forma...
Mas por sorte, a classe possui um construtor sobrecarregado, e existem várias maneiras de preencher um no momento em que ele é instanciado:
Na linha 02 criamos um totalmente vazio, como fizemos anteriormente. Na linha 03 criamos um com 5 espaços na memória, mas não definimos os valores que estão armazenados. Essa não é uma boa prática, pois os valores inicialmente contidos em vão variar de acordo com cada compilador. Na linha 04 criamos um com 7 espaços na memória, e todos eles preenchidos com o caractere . E na linha 05 criamos um que é uma cópia exata de outro
Mas e se você quiser um preenchido com diversos valores? A melhor forma é transformar uma array em um . A gente faz isso inserindo os ponteiros para a primeira e para a última posição da array no construtor do :
É importante que a array e o sejam do mesmo tipo!
Outra vantagem é que criar uma partindo de uma array é que você não é obrigado a passar a array completa. Se quiser, pode passar um ponteiro para uma posição inicial qualquer, e um ponteiro para uma posição final que desejar.
Os iterators
Os iterators (alguns professores traduzem para iteradores) são parecidos com os ponteiros, mas o iterator ajuda a gente a trabalhar com os containers. A vantagem de usar iterators é que eles funcionam com a mesma lógica em todos os containers, o que nem sempre é verdade no caso de usar ponteiros.
Antes de mais nada, para usar um iterator precisamos ter um container em funcionamento, então vamos criar um a partir de uma array:
Os iterators e containers são intimamente relacionados, e por isso devem ser do mesmo tipo então pra declarar um iterator que aponta para um fazemos:
Todo container possui ao menos dois métodos especiais que trabalham com os iteradores, o que faz o iterator apontar pro início do container, e o para apontar pro espaço vazio após final do container.
Isso é especialmente útil quando vamos usar um loop em cima dos . Por exemplo:
Na linha 12 criamos um laço que o iterador começa a contar na posição inicial do e avança de um em um até a última posição final. Na linha 13 a gente imprime o valor apontado pelo iterator: da mesma forma que um ponteiro, usamos o operador para acessar o conteúdo.
Importante: lembre-se que o valor apontado por é o “vazio” após o final do container, então quando imprimimos esses valores, encontramos lixo de memória.
Um segundo tipo importante são os que nos ajudam andar ao contrário sobre um container. Temos o aponta para o último elemento, e o que aponta para o espaço vazio antes do primeiro elemento:
No caso dos iteradores reversos, o não faz parte do intervalo. E se você imprimir o valor apontado por ele, vai encontrar lixo de memória:
Erros comuns
- Tentar inicializar um usando como se fosse uma array:
- Tentar ampliar o tamanho do vector usando colchetes ao invés do :
- Usar o operador de decremento ao invés de com os iteradores reversos também é um erro comum. Mesmo que a gente esteja andando “de trás pra frente” com os iteradores reversos, nós devemos usar o operador
Dicas:
Na hora de preencher um vetor usando array precisamos saber o tamanho dessa array. Mas pode haver situações em que o tamanho da array é variável ou então é desconhecido.
Pra contornar isso, podemos usar uma expressão para calcular o tamanho da array automaticamente:
E vamos poder gerar um vector da seguinte forma:
Resumo do básico sobre o container e seus iterators básicos
E por hoje é isso pessoal. Vamos ver agora uns exercícios de fixação.