Linguagem de Programação II Carlos Oberdan Rolim Ciência da Computação Sistemas de Informação STL Standard Template Library Introdução É uma biblioteca disponibilizada pelos ambientes de desenvolvimento C++ contendo um conjunto de classes de coleções (estruturas de dados) e de algoritmos de coleções, organizados na forma de gabaritos (templates). O uso de STL pretende oferecer os seguintes benefícios: reutilização de código – como é baseado em gabaritos (templates), as classes de STL podem ser adaptadas a tipos distintos sem mudança de funcionalidade (type safe collections). portabilidade e facilidade de uso Introdução Foi desenvolvida nos laboratorios da HP e liberada em Agosto de 1994 Fornece ao programador estruturas de dados comuns e úteis como vetores, listas, deques, sets e maps e um conjunto de algoritmos para manipula-los como pesquisa e inserção Não seria implementada sem o conceito de templates Templates permitem que a STL opere com os tipos de dados prédefinidos de C++ e tambem com os tipos definidos pelo programados Objetiva a reutilização de código O termo genérico significa criar código que seja independente do tipo de dado Componentes básicos As classes implementadas pela STL podem ser categorizadas em três grandes grupos: Containers – classes utilizadas para armazenamento de dados, implementando uma coleção. Exemplos: vetores, listas, filas projetados para armazenar tipos predefinidos (int, float, etc), objetos e tipos criados pelo programador Iteradores (iterators) – classes que permitem a varredura pelos elemento de uma coleção seguindo uma determinada regra. Atuam da mesma forma que um ponteiro perconrrendo arrays. São a ligação entre algoritmos e containers fornecendo aos algoritmos as operações para acesso aos containers. Normlamente são utilizados para se morevem sequencialmente de um elemento a outro em um container Algoritmos – classes que implementam métodos para realização de algoritmos comuns de estruturas de dados sobre as coleções como pesquisa, inserção e classificação Containers Correspondem às coleções de elementos de um determinado tipo, na forma de gabaritos (templates) de classe. Os conteiners definidos pela STL são: vector: elementos organizados na forma de um array que pode crescer dinamicamente. Acesso aleatório rápido por meio de um índice numérico. Lento para inserção e retirada no meio. Rápido para inserção e retirada no fim List: elementos organizados na forma de uma lista duplamente encadeada. Acesso aleatório lento Rápido para inserção e retirada em qualquer posição Acesso rápido aos extremos Containers deque: elementos organizados em sequência, permitindo inserção ou remoção no inícis valores do tipo ou no fim sem necessidade de movimentação de outros elementos. Acesso rápido por meio de um índice numérico Lento para inserção e retirada no meio Rápido para inserção e retirada nos extremos map: cada elemento é um par <chave, elemento> sendo que a chave é usada para ordenação da coleção. Não permite chaves duplicadas set: coleção ordenada na qual os próprios elementos são utilizados como chaves para ordenação da coleção. Armazena apenas valores do tipo chave, não permitindo chaves duplicadas Containers multimap: Associa uma chave a um valor permitindo chaves duplicadas multiset: Armazena apenas um valor do tipo chave, permitindo chaves duplicadas Métodos comuns a containers empty: determina se a coleção está vazia size: determina o número de elementos na coleção begin: retorna um iterador para a frente, apontando para o início da coleção end: retorna um iterador para a frente, apontando para o próximo elemento após o fim da coleção rbegin: retorna um iterador para trás, apontando para o fim da coleção rend: retorna um iterador para trás, apontando para o elemento anterior ao início da coleção. clear: apaga todos os elementos de uma coleção. erase: apaga um elemento da coleção. Métodos de adição e remoção em deque e vector push_back: adiciona elemento ao fim da coleção push_front: adiciona elemento ao início da coleção back: obter uma referência para elemento no fim da coleção front: obter uma referência para elemento no início da coleção pop_back: remove um elemento do fim da coleção push_back: remove um elemento do início da coleção Operações de acesso a elementos em vector, map e deque: utiliza-se o operador [ ] como em um array comum. Iteradores Correspondem a objetos que podem ser utilizados para acessar os elementos de um container. Três categorias de iteradores são suportadas: iterator: permite percurso dos elementos do início para o fim da coleção reverse_iterator: permite percurso dos elementos na ordem inversa (do fim para o início) da coleção. random_access: acesso aleatório. Definido para vector. Os iteradores podem ser incrementados com o operador ++ para apontarem para o próximo elemento e de-referenciados pelo operador * obtendo o valor do elemento que apontam Algoritmos Correspondem a gabaritos de funções que implementam algoritmos de estruturas de dados (definidos na biblioteca algorithm). As funções são divididas em 3 categorias: sequência: implementam operações de busca e alteração da sequência de elementos do container classificação: implementam classificação dos elementos do container numéricos: implementam funções numéricas comuns sobre os elementos do container. Algoritmos de sequencia copy(start, end, dest): copia os elementos entre os iteradores start e end para dest. fill(start, end, val): atribui val a todos os elementos entre os iteradores start e end remove(start, end, val): remove todos os elementos de valor val entre os iteradores start e end. replace(start, end, old_value, new_value): substitui os elementos iguais a old_value por new_value entre os iteradores start e end. reverse(start, end): inverte a ordem dos elementos entre os iteradores start e end rotate(start, middle, end): rotaciona os elementos entre os iteradores start e end de tal maneira que o iterador middle fique posicionado onde antes estava o iterador start. Algoritmos de sequencia equal (start1, end1, start2): retorna true se os elementos entre os iteradores start1 e end1 são iguais aos da faixa de mesmo tamanho iniciado em start2. find(start, end, val): retorna um iterador para a primeira ocorrência do elemento val entre os iteradores start e end. search(start1, end1, start2, end2): procura o subconjunto dos elementos entre os iteradores start2 e end2 dentro do conjunto dos elementos entre start1 e end1. Os algoritmos operam sobre os containers utilizando somente os iteradores dos containers. Algoritmo Iterador Containers #include <iostream> # include <vector> using namespace std; int main() { unsigned int k; vector<int> pares; // poderia ser usado vector <int> pares(10) para definir // tamanho, mas não é obrigatório pares.push_back(10); pares.push_back(2); pares.push_back(8); pares[3] = 7; for (k = 0; k < pares.size(); k++) cout << pares[k]; return 0; } Exemplo de como criar uma estrutura de dados do tipo vector #include <iostream> # include <list> using namespace std; int main() { list<float> notas; // lista do tipo float list<float>::iterator iterfloat; // iterador para a lista notas.push_back(6.5); notas.push_back(7.5); notas.push_back(8.5); notas.push_back(9.5); Iterator deve ser do mesmo tipo da lista for (iterfloat = notas.begin(); iterfloat != notas.end(); iterfloat++ ) cout << *iterfloat << “ “; return 0; } Exemplo de uma lista Algoritmos de classificação Alguns algoritmos de classificação: sort(start, end): classifica em ordem crescente os elementos entre os iteradores start e end, utilizando o algoritmo introsort, sem garantia de ordem entre os elementos de mesmo valor stable_sort(start, end): semelhante a sort porém mantém a ordem original dos elementos que são iguais. sort_heap(start, end): transforma o heap entre os iteradores start e end em um heap classificado (para criar um heap a partir de um container sequencial pode-se utilizar make_heap(start, end). #include <iostream> # include <vector> using namespace std; int main() { unsigned int k; vector<int> pares; // poderia ser usado vector <int> pares(10) para definir // tamanho, mas não é obrigatório pares.push_back(10); pares.push_back(2); pares.push_back(8); pares[3] = 7; Iterator que aponta para o inicio do container Iterator que aponta para o fim do container sort(pares.begin(), pares.end()); for (k = 0; k < pares.size(); k++) cout << pares[k]; return 0; } Exemplo de como classificar um vector usando o algoritmo sort() #include <vector> #include <algorithm> #include <iostream> using namespace std; int main(int argc, char* argv[]) { int elemento; vector <int> teste; //mostrando o tamanho do container cout << "O container tem tamanho " << teste.size() << endl; //inserindo no final do container for (int i = 0; i < 10; i++) { cout << "Digite o próximo elemento: "; cin >> elemento; teste.push_back(elemento); } //mostrando a capacidade cout << "A capacidade atual do vector é " << teste.capacity() << endl; //criando iteradores na direção original vector<int>::iterator inicio = teste.begin(); vector<int>::iterator fim = teste.end(); //ordenando os elementos sort(inicio, fim); cout << "Elementos ordenados: " << endl; for (vector<int>::const_iterator fwd = teste.begin(); fwd < teste.end(); fwd++) cout << *fwd << " "; return 0; } //percorrendo de trás para a frente Exemplo mais complexo com vector for (vector<int>::reverse_iterator rev = teste.rbegin(); rev < teste.rend(); rev++) cout << *rev << " "; Algoritmos numéricos accumulate(start, end, val): acumula e retorna o valor dos tipo val com todos os valores entre os iteradores start e end. count(start, end, val): conta quantos valores entre os iteradores start e end são iguais a val. #include <vector> #include <algorithm> #include <numeric> #include <iostream> void accumulate_count_test(){ int elementos[] = {3, 8, 4, 7, 2, 1}; vector<int> teste; for (int i = 0; i < 6; i++){ teste.push_back(elementos[i]); } using namespace std; int main(int argc, char* argv[]) { accumulate_count_test(); //accumulate the elements vector<int>::iterator inicio = teste.begin(); vector<int>::iterator fim = teste.end(); return 0; } int valor = accumulate(inicio, fim, 0); cout << "Valor acumulado dos elementos”; cout << valor; //counts the number of 7's inicio = teste.begin(); fim = teste.end(); int contagem = count(inicio, fim, 7); cout << endl << "Contagem de 7's: "; cout << contagem; Exemplo com acumulador e count } Adaptadores de containers A idéia dos adaptadores de containers é prover funcionalidade específica sem no entanto implementar a estrutura onde os dados são armazenados. A estrutura é fornecida por um dos containers definidos na STL e o seu comportamento é adaptado por um adaptador de container. São definidos 3 adaptadores na STL: stack: fornece funcionalidade de pilha (estrutura LIFO, last in first out). queue: fornece funcionalidade de fila FIFO (first in first out). priority_queue: fornece a funcionalidade de uma fila FIFO porém com prioridades de inserção. Stack Um adaptador stack utiliza, por padrão, um container deque para sua implementação. Métodos: push: empilha pop: desempilha empty: verifica se está vazia top: obtém valor do topo da pilha #include <vector> #include <algorithm> #include <stack> #include <iostream> void stack_test(){ int elementos[] = {3, 8, 4, 7, 2, 1}; using namespace std; //monta pilha sobre vetor stack <int, vector<int> > pilha; int main(int argc, char* argv[]) { cout << "Empilhando: "; for (int i = 0; i < 6; i++){ cout << elementos[i] << " "; pilha.push(elementos[i]); } stack_test(); return 0; } //desempilhamento cout << "Desempilhando: "; while (!pilha.empty()){ cout << pilha.top() << " "; pilha.pop(); } Exemplo de stack } Queue Um adaptador queue utiliza, por padrão, um container deque para sua implementação. Pode também ser implementado com list. Métodos: push: enfileira pop: desenfileira empty: verifica se está vazia Front: obtém primeiro elemento da fila back: obtém último elemento da fila #include <vector> #include <algorithm> #include <stack> #include <queue> #include <iostream> void queue_test() { int elementos[] = {3, 8, 4, 7, 2, 1}; //monta pilha sobre vetor queue <int> fila; using namespace std; cout << "Enfileirando: "; for (int i = 0; i < 6; i++) { int main(int argc, char* argv[]) { queue_test(); cout << elementos[i] << " "; fila.push(elementos[i]); return 0; } } //desenfileiramento cout << "Desenfileirando: "; while (!fila.empty()) { cout << fila.front() << " "; fila.pop(); } Exemplo com queue }