Baixe o app para aproveitar ainda mais
Prévia do material em texto
www.esab.edu.br Estrutura de DadosSISTEMAS DE INFORMAÇÃO Estrutura de Dados Vila Velha (ES) 2014 Escola Superior Aberta do Brasil Diretor Geral Nildo Ferreira Diretora Acadêmica Beatriz Christo Gobbi Coordenadora do Núcleo de Educação a Distância Beatriz Christo Gobbi Coordenadora do Curso de Administração EAD Rosemary Riguetti Coordenador do Curso de Pedagogia EAD Claudio David Cari Coordenador do Curso de Sistemas de Informação EAD David Gomes Barboza Produção do Material Didático-Pedagógico Delinea Tecnologia Educacional / Escola Superior Aberta do Brasil Diretoria Executiva Charlie Anderson Olsen Larissa Kleis Pereira Margarete Lazzaris Kleis Conteudista Marcelo Medeiros Coordenação de Projeto Andreza Regina Lopes da Silva Supervisão de conteúdo Renata Oltramari Liderança Técnica Design Gráfico Fernando Andrade Liderança Técnica Revisão Gramatical Tiago Costa Pereira Laís Gonçalves Natalino Designer Educacional Aline Batista Revisão Gramatical Bárbara Seger Zeni Érica Valduga Laís Gonçalves Natalino Designer Gráfico Laura Rodrigues Neri Gonçalves Ribeiro Diagramação Dilsonir José Martins Junior Karina Silveira Equipe Acadêmica da ESAB Coordenadores dos Cursos Docentes dos Cursos Copyright © Todos os direitos desta obra são da Escola Superior Aberta do Brasil. www.esab.edu.br Av. Santa Leopoldina, nº 840 Coqueiral de Itaparica - Vila Velha, ES CEP 29102-040 Apresentação Caro estudante, Seja bem-vindo à disciplina de Estrutura de Dados, um complemento aos conceitos fundamentais de programação vistos nas disciplinas que envolvem lógica de programação, aliado à discussão e apresentação de novas técnicas para o desenvolvimento de programas de computadores mais eficientes no uso de memória principal e tempo de processamento. Adotaremos como base para validação das técnicas apresentadas a linguagem de programação C, porém, os conceitos e técnicas desenvolvidas nesta disciplina podem ser codificados em qualquer linguagem de programação atual, como Java. Hoje é fundamental para o profissional que deseja ser um especialista em Tecnologia da Informação ter habilidades e competências que lhe permitam maximizar o uso dos recursos do computador. E você, como nosso aluno, deve estar interessado nessa área, em saber como planejar e desenvolver rotinas computacionais que aperfeiçoem os resultados dos algoritmos, evitando o desperdício de memória principal criando rotinas de alocação, pesquisa e classificação dos dados. Nesta disciplina, tendo como base os autores Forbellone et al.(2005), Cerqueira, Celes e Rangel Netto (2004), Ascencio e Araújo (2010) e Guimarães (1994), você conhecerá os conceitos e as técnicas de como implementar os tipos de alocação de memória, os algoritmos de classificação e pesquisa de dados, as estruturas de dados avançadas, como: Filas, Pilhas, Listas e Árvores. Assim, esta disciplina possibilitará que você entenda, projete, desenvolva e valide o uso de estruturas de dados mais eficientes e que possibilitem o aproveitamento máximo dos recursos do computador. Bom estudo! Objetivo Analisar, avaliar e implementar as principais técnicas de representação e manipulação de dados, possibilitando aos alunos a capacidade de escolher as estruturas de dados mais adequadas aos problemas que envolvam a ordenação e recuperação de informações por meio de algoritmos de organização e busca. Habilidades e competências • Analisar e implementar estruturas de dados para solução de problemas computacionais. • Projetar estruturas de dados para resolução de problemas complexos. • Avaliar as melhores estruturas de dados para cada tipo de problema computacional. • Codificar estruturas de dados. • Analisar as diferentes estruturas de dados. • Aplicar adequadamente estruturas de dados. Ementa Conceitos básicos e apontadores. Representação de estruturas simples e encadeadas (listas lineares, pilhas, filas, árvores). Procedimentos e funções de acesso, remoção e inserção de nós nas estruturas. Métodos de classificação e ordenação de dados. Pesquisa de dados. Sumário 1. Introduzir o aluno aos conceitos de Estruturas de Dados .................................................7 2. Funções – parte I ..........................................................................................................15 3. Funções – parte II .........................................................................................................22 4. Variáveis compostas homogêneas: vetores ...................................................................33 5. Alocação dinâmica de memória ....................................................................................40 6. Alocação dinâmica em C ...............................................................................................49 7. Tipos de passagens de parâmetros ................................................................................58 8. Tipos estruturados – parte I ..........................................................................................68 9. Tipos estruturados – parte II .........................................................................................80 10. Matrizes – conceituação ...............................................................................................92 11. Matrizes – codificação ................................................................................................101 12. Matrizes – exercícios ...................................................................................................109 13. Tipos abstratos de dados – parte I ..............................................................................120 14. Tipos abstratos de dados – parte II .............................................................................126 15. Listas encadeadas .......................................................................................................133 16. Listas encadeadas simples – conceituação .................................................................141 17. Listas encadeadas simples – codificação ....................................................................154 18. Lista encadeada simples – exercícios ..........................................................................166 19. Listas circulares – conceituação ..................................................................................175 20. Listas circulares – codificação .....................................................................................182 21. Listas circulares – exercícios ........................................................................................190 22. Listas duplamente encadeadas – conceituação ..........................................................197 23. Listas duplamente encadeadas – codificação .............................................................206 24. Listas circulares duplamente encadeadas – conceituação ...........................................214 25. Listas circulares duplamente encadeadas − codificação .............................................226 26. Pilhas – conceituação .................................................................................................237 27. Pilhas – codificação – parte I ......................................................................................244 28. Pilhas – codificação – parte II .....................................................................................253 29. Filas – conceituação ....................................................................................................258 30. Filas – codificação .......................................................................................................264 31. Filas – exercícios .........................................................................................................27132. Fila dupla ....................................................................................................................277 33. Árvores – parte I .........................................................................................................284 34. Árvores – parte II ........................................................................................................292 35. Árvores – parte III .......................................................................................................300 36. Árvores – parte IV .......................................................................................................308 37. Árvores – parte V ........................................................................................................316 38. Métodos de pesquisa e ordenação: conceituação ........................................................324 39. Métodos de ordenação: bubble sort – parte 1 ............................................................331 40. Métodos de ordenação: bubble sort – parte 2 ............................................................338 41. Métodos de ordenação: quick sort – parte 1 ...............................................................345 42. Métodos de ordenação: quick sort – parte 2 ...............................................................352 43. Métodos de ordenação: merge sort .............................................................................361 44. Métodos de ordenação: merge sort .............................................................................368 45. Métodos de pesquisa: busca em vetor .........................................................................375 46. Métodos de pesquisa: árvore binária de pesquisa .......................................................382 47. Métodos de pesquisa: árvore balanceada ....................................................................388 48. Exercícios de fixação ....................................................................................................395 Glossário ............................................................................................................................405 Referências ........................................................................................................................414 www.esab.edu.br 7 1 Introduzir o aluno aos conceitos de Estruturas de Dados Objetivo Introduzir o aluno aos conceitos de estruturas de dados. Nesta unidade serão apresentados os conceitos fundamentais da estrutura de dados: o que é estrutura de dados, a diferença entre informações e dados e as áreas de aplicação da estrutura de dados e seus benefícios. Inicialmente podemos entender a estrutura de dados como qualquer forma de armazenamento e manipulação de dados no computador, como variáveis, vetores, matrizes e arquivos. Tal pensamento não está errado, porém, nesta unidade, você estudará que a estrutura de dados é muito mais do que armazenamento de informações, há um gerenciamento de memória associado ao processo de criação e destruição dessas estruturas, assim como normas e procedimentos para seu uso. Para elaboração desta unidade foram utilizadas as referências de Guimarães (1994) e Ascencio e Araújo (2010). 1.1 O que é estrutura de dados? A definição de estrutura de dados é um conceito amplo e requer um conjunto de conhecimentos que a fundamentam. Vamos começar pela compreensão da abstração de dados, que consiste na análise de um problema identificando os processos finitos que devem ser realizados para se chegar ao resultado desejado. Por exemplo, no desenvolvimento de um sistema acadêmico durante a etapa de análise do problema pode-se chegar à conclusão que as informações a serem armazenadas para cada aluno são: matrícula, o nome e o sexo. Já em outro contexto, um sistema acadêmico durante a mesma etapa de análise do problema pode especificar que cada aluno www.esab.edu.br 8 será identificado pela matrícula, nome, idade, sexo, data de nascimento, nome do pai e da mãe. Nos dois casos os requisitos avaliados foram os dados cadastrais de um aluno, porém no processo de abstração do que é um “aluno”, os dois sistemas a serem desenvolvidos possuem definições diferentes, mesmo se tratando de um aluno, e cabe ao analista fazer o levantamento de acordo com o cenário que lhe é apresentado. Assim como os dados cadastrais, as funcionalidades de cada sistema, as regras de negócio e uso do sistema também dependerão da abstração de cada um dos contextos apresentados. A Figura 1 representa a abstração de dados. Entrada Processamento Saída Figura 1 – Abstração de Dados. Fonte: Elaborada pelo autor (2013). A entrada refere-se aos dados que são colhidos no mundo real, ou seja, externo ao computador, e a saída à sua transformação na informação desejada. O processamento representa uma sequência finita de operações a serem realizadas a partir dos dados de entrada, a fim de transformá- los em informações. A abstração de dados se constitui pela análise dos valores manipulados, os métodos e os procedimentos que devem ser aplicados aos dados de entrada com a finalidade de resolver determinados problemas, representados pela saída desejada. Por exemplo, um sistema que deve converter uma medida em metros para centímetros requer uma sequência ordenada de procedimentos e operações que possibilitam ao computador realizar essa tarefa. Para tanto, é necessário avaliar o problema e abstrair as regras importantes a serem transferidas para o algoritmo, como a definição dos valores de entrada e saída como números reais, já que as medidas manipuladas pelo sistema (metros e centímetros) são números que podem conter casas decimais. Para conversão da medida, será necessária a operação de divisão da medida em metros por 100, já que cada um metro corresponde a 100 centímetros. Por fim, será necessária a utilização de procedimentos para interagir com o usuário para que ele informe os dados que serão processados (entrada de dados) e para a apresentação do resultado final da operação de conversão (saída de dados). www.esab.edu.br 9 Para sua reflexão A definição de quais métodos e procedimentos serão utilizados para resolução de um problema na forma de um sistema computacional não é imediata, é construída a partir de testes e validação dos resultados, por meio de uma técnica de análise e refinamento dos processos. Requerem também técnicas de representação do problema, como fluxogramas, diagramas, algoritmos, todos como uma documentação formal e roteiros de validação da solução, aliados à expertise de cada executor. Por isso, deve-se ter em mente que há diversas estratégias para se chegar à solução de um determinado problema, apresentando vantagens e desvantagens em relação a cada uma delas, mas todas devem garantir a resolução do problema. Note que mesmo que seja apresentado um problema, como o problema anterior de conversão de metros para centímetros para um conjunto de programadores e analistas de sistemas, cada um apresentará uma lógica e estratégia que pode não ser a mesma para todos já que a abstração do problema e de como resolvê-la é uma visão subjetiva de cada um, mas todos devem garantir que o resultado da conversão será o mesmo independentemente da solução, já que somente essa condição garante que o sistema funcionará de acordo com o esperado. O executor ou analista deve ser capaz de escolher aquela estratégia que melhor se adéqua para resolução do problema, levando em consideração a eficiência da solução, as necessidades de armazenamento dos dados e o tempo de processamento. Note que as características de armazenamento de dados, de processamento e de eficiência da solução determinam o processo de informatização da solução, que é a transferência para o computador de uma tarefaque era realizada no mundo real, a partir de uma sequência www.esab.edu.br 10 finita de processamentos. Estes, por sua vez, são fundamentados nas definições de como o sistema deverá funcionar, requisitos, testes e validações especificadas pelos especialistas em tecnologia da informação. Por exemplo, no desenvolvimento de um sistema computacional que deverá controlar a temperatura de uma caldeira, é fundamental que o processamento das informações aconteça de forma rápida e eficiente. Pois, além de monitorar os dados, é preciso que a resposta em caso de temperaturas elevadas ou situações de riscos seja rápida, e o sistema possa garantir em tempo hábil a tomada de decisões por parte dos envolvidos. Esses requisitos e especificações fazem parte do processo de abstração dos dados realizados pelos especialistas em tecnologia da informação. A elaboração de um algoritmo, que representa a solução de um problema, a partir de uma linguagem de programação chama-se implementação. Sua finalidade é transformar uma abstração de dados em um processo concreto, formal, com a execução de métodos e procedimentos já projetados na forma de algoritmos. Para Ascencio e Araújo (2010, p. 1), Durante o processo de computação de sua saída, um algoritmo manipula dados obtidos de sua entrada. Quando dados são dispostos e manipulados de forma homogênea, constituem um tipo abstrato de dados. Nesse processo de abstração de dados, quando uma série finita de instruções é manipulada de forma homogênea, a sua formatação em algoritmos e a sua posterior execução pelos computadores representa a aplicação da estrutura de dados e da sua conceituação. Podemos entender a estrutura de dados como um recurso que vai além do armazenamento de dados, pois exige a identificação e desenvolvimento de procedimentos, métodos e técnicas que permitem a solução do problema, utilizando o espaço de armazenamento de forma otimizada, com processamento eficiente e garantia de integridade das informações. Ou seja, mais do que armazenar os dados em memória, o www.esab.edu.br 11 acesso e processamento dos dados devem ocorrer de forma a atender às necessidades da solução em tempo hábil e com garantia que os resultados processados estão corretos. Portanto, uma estrutura permite que os dados se mantenham organizados por alguma lógica. Além disso, o usuário pode manipular os dados disponibilizados pelas operações. Ou seja, a estrutura de dados, por meio de sequências finitas de operações, gerencia os dados que podemos entender por informações armazenadas. Por exemplo, no desenvolvimento de um sistema que realize a busca dos dados de um funcionário, por meio de um vetor pelo valor da sua matrícula (que é única, dois funcionários não podem ter a mesma matrícula), é importante que quando acontecer a localização dessa matrícula, o sistema encerre a busca, pois não haverá outro funcionário com essa matrícula. Caso isso não seja definido como uma regra da aplicação, o sistema continuará a pesquisa desnecessariamente. Ao final o resultado será o mesmo, a matrícula será localizada, porém o tempo de processamento será maior devido à pesquisa em toda a tabela de matrículas. Portanto, a estrutura de dados não se limita apenas ao armazenamento de dados, mas permite também otimizar os processos de armazenamento , organização e acesso aos dados. Para Guimarães (1994, p. 2), as estruturas de dados “[...] são usadas nos algoritmos para representar as informações do problema a ser resolvido”. Ainda, para Ascencio e Araújo (2010, p. 2), [...] na representação do tipo abstrato de dados, emprega-se uma estrutura de dados. [...] uma estrutura de dados é um meio para armazenar e organizar dados com o objetivo de facilitar o acesso às modificações.” Diz ainda que “[...] as estruturas de dados diferem-se uma das outras pela disposição ou manipulação de seus dados, de modo que não se pode separar as estruturas de dados e os algoritmos associados a elas. Segundo Guimarães (1994, p. 2), “[...] o processo de construção de programas, a formulação do algoritmo e a definição das estruturas de dados a serem usadas estão intimamente ligadas”. www.esab.edu.br 12 Observe que a definição de estrutura de dados está associada ao armazenamento dos dados, mas não apenas isso. A forma de armazenamento dos dados, como serão manipulados e sua organização estão relacionados com as atividades e problemas do mundo real. Portanto, levá-la com suas regras de negócios e formas de manipulação para o computador requer do especialista a habilidade de avaliar, compreender e modelar todo esse universo por meio da estrutura de dados mais adequada. Então podemos entender a estrutura de dados como a forma pela qual os dados serão armazenados, organizados e manipulados no computador; associada às operações, pode ser executada com esses dados, visando sempre evitar o desperdício de memória alocada e otimizando o processamento dos dados. 1.2 Dados versus Informação Observe que na seção anterior em alguns momentos os conceitos de dados e informação se confundem, e essa é uma dúvida comum, mas fácil de ser resolvida. De forma geral, podem-se entender dados como a informação pura, não processada, no seu estado inicial, que pode representar vários significados e isoladamente não transmite conhecimento. Já a informação pode ser compreendida como o dado já processado, útil, com alguma finalidade. Por exemplo, o conjunto de letras a seguir pode ser entendido como dados, pois ele não transmite conhecimento ou informação útil: BESA. Mas, se processarmos esse conjunto de dados de forma a organizá-los, obtemos a informação: ESAB. www.esab.edu.br 13 1.3 Utilização e benefícios Como visto nas seções anteriores, a conceituação de estrutura de dados está diretamente relacionada ao desenvolvimento de algoritmos e sua posterior implementação. Para criar e utilizar um algoritmo, é necessário avaliar seu desempenho e a utilização dos recursos disponibilizados, como alocação de espaços na memória principal ou secundária, e tempo gasto para execução de determinada rotina. Nesse sentido, a estrutura de dados pode ser aplicada em algoritmos simples, de entrada, processamento e saída de dados, ou mais complexos, como algoritmos de busca e classificação de dados. Em muitos casos as estruturas de dados devem ser utilizadas para a construção de conjunto de dados, ou coleções, na forma de vetores estáticos ou dinâmicos. Os vetores estáticos são utilizados para o armazenamento de dados em que se conhece a quantidade de dados a serem processados. Já os vetores dinâmicos não possuem uma quantidade de elementos predefinidos, alocando memória à medida que cada elemento é inserido na estrutura de dados, e liberando memória, à medida que um elemento é removido, tornado o uso mais eficiente. Para sua reflexão Para o desenvolvimento de programas de computadores é muito commum que o programador não conheça o número de dados que serão manipulados pelo sistema, sendo uma informação dinâmica e muito variável.Por isso a importância em conhecer as estruturas de dados. Vamos refletir sobre, por exemplo, um sistema que precisa gerar um relatório de produtos comercializados num determinado período. Os dados da pesquisa precisam ser armazenados em uma estrutura de dados para depois serem apresentados ao usuário. Porém, não se sabe previamente a quantidade de elementos que serão armazenados, o tamanho da estrutura para esse armazenamento, e que se pode variar de período para período. Uma solução www.esab.edu.br 14 seria criar uma estrutura de dados com um tamanho absurdamente grande que garantiria o armazenamento dos dados, mas haveria um desperdício de memória do computador caso a quantidade de elementos do relatório fosse muito baixa. Dessa forma, a melhorsolução é a criação de uma tabela com a quantidade exata de elementos que a pesquisa retornou, mas isto só poderá ocorrer em tempo de execução do programa, pois somente no momento em que a rotina que realiza a pesquisa dos produtos é executada é que se terá a quantidade de elementos da tabela. Em alguns casos as estruturas de dados são utilizadas para solução de problemas bem específicos como a implementação de filas de atendimento em bancos, nas quais cada cliente é identificado por uma senha, e que devem garantir que o indivíduo que chegou mais cedo tenha prioridades nos atendimentos. Ou para elaboração de um programa de computadores que possa calcular e mostrar o menor caminho entre dois pontos de uma cidade, a fim de diminuir o custo de combustível e o tempo para realizar o deslocamento. A escolha da estrutura de dados mais adequada deve analisar algumas características do problema a ser solucionado. Conforme Guimarães (1994), essas características são: • as propriedades relevantes do objeto real que queremos representar; • as operações a serem efetuadas sobre elas; • os resultados de acordo com os fenômenos do mundo real. Em resumo, as estruturas de dados devem ser definidas de forma a atender as operações que serão realizadas e das características (abstração) do mundo real que ela deverá representar. Nesta unidade você pôde conhecer o conceito de estrutura de dados e sua relação com a abstração de dados, passando pela contextualização de tipos abstratos de dados. Vimos também a diferenciação entre dados e informações. Além desses tópicos iniciais, foi discutida a forte relação entre algoritmos e estrutura de dados. Por fim, foram apresentadas algumas aplicações para a estrutura de dados na elaboração de métodos de pesquisa, ordenação e construção de estruturas específicas. Na próxima unidade serão apresentadas aplicações da estrutura de dados, começando pela construção de funções, suas propriedades e características de funcionamento. www.esab.edu.br 15 2 Funções – parte I Objetivo Apresentar a definição de funções, funcionamento da pilha de execução e visibilidade de variáveis. Na unidade anterior você pôde se familiarizar com o conceito de estrutura de dados e abstração de dados e as vantagens e benefícios da sua aplicação. Nesta unidade será apresentada a técnica de refinamentos sucessivos na forma de módulos ou funções, destacando seu papel na redução da complexidade dos algoritmos e a consequente facilidade da manutenção e validação. Para tanto, serão abordados temas como a decomposição de problemas, a construção e parametrização de módulos e a aplicação da estrutura de dados responsável pelo gerenciamento da execução dos módulos em um programa de computador. Esta unidade está fundamentada nos trabalhos de Guimarães (1994) e Forbellone et al. (2005). A resolução de um problema na forma de algoritmos com uso das estruturas de dados é uma tarefa complexa, que exige a análise do problema, das possíveis soluções e dos requisitos que precisam ser atendidos, a fim de garantir a plenitude da solução. Diante disso, uma técnica muito eficiente para estruturação de um algoritmo é dividir o problema complexo em problemas menores (FORBELLONE et al., 2005). Essa estratégia possibilita uma visão mais específica de cada rotina a ser construída e validada. Já Guimarães (1994, p. 155), sobre o mesmo tema, afirma que “[...] a metodologia de refinamentos sucessivos parte do princípio de que resolver um problema complexo é mais fácil se não precisarmos considerar todos os aspectos do problema simultaneamente”. Portanto, a decomposição de um problema, a partir de um refinamento sucessivo, em problemas menores e mais simples, resulta na redução da sua complexidade. Essa ideia vem ao encontro de Forbellone et al. (2005, p. 127) quando nos diz que www.esab.edu.br 16 [...] a decomposição de um problema é fator determinante para a redução da complexidade. [...] quando decompomos um problema em subproblemas, estamos invariavelmente dividindo também a complexidade e, por consequência, simplificando a resolução. Na prática, a decomposição ou modularização se dá na codificação e no uso de funções. Forbellone et al. (2005, p.128) citam a relação de modularização e algoritmos com o seguinte discurso: “[...] depois de decompor um problema complexo em subproblemas, podemos construir subalgoritmos ou módulo para cada subproblema”. Esses subalgoritmos são conhecidos como funções ou métodos. 2.1 Definição As funções são subprogramas que decompõem as grandes tarefas em subtarefas. Um programa de computador geralmente consiste de várias funções. A criação de funções visa à redução do código e à consequente simplificação dos processos de validação e testes, uma vez que rotinas que se repetem no código devem ser transformadas em funções que, então, serão chamadas diversas vezes. As funções uniformizam “[...] determinado conjunto de ações afins, que obedecem à mesma estruturação de um algoritmo, com o objetivo de representar um bloco lógico em especial” (FORBELLONE et al., 2005, p. 130-131). As funções são executadas quando chamadas no programa ou em outras funções, resultando em um desvio na execução do programa. Para que esse procedimento seja executado de forma organizada, é necessária uma estrutura de dados que gerencie as chamadas e encerramentos de cada função em execução. Essa estrutura de dados é conhecida como pilha de execução. www.esab.edu.br 17 2.2 Pilha de execução Como estudado na seção anterior, a modularização transforma um problema em um conjunto de soluções, então, o próximo passo é compreender como esses módulos são interligados, manipulados e executados. A execução ou ativação de um módulo ocorre quando em algum ponto do algoritmo há uma chamada para a função, pelo nome definido na declaração da função. A execução do módulo se dá por um desvio no fluxo de execução do algoritmo para a função chamada, retornando somente após a execução de todas as instruções da função, sempre para a próxima linha após a chamada. A Figura 2 representa um algoritmo fazendo a chamada para duas funções hipotéticas, Função A e Função B, observe: Linhas 1 2 3 4 5 6 7 A B Algoritmo Linhas 1 2 3 4 5 Função B Linhas 1 2 3 Função A Figura 2 – Ativação de Funções. Fonte: Elaborada pelo autor (2013). O algoritmo principal possui 7 linhas com comandos quaisquer, e sua execução começa na linha 1 e se encerra na linha 7. Durante a execução, como na linha 2, há uma chamada para a função “Função A”, por meio da instrução “A”, que é o nome da função, haverá um desvio para a primeira linha dessa função, que será executada da linha 1 até a linha 3, que encerra a função. Após o encerramento da função, o fluxo retorna para a próxima linha após a chamada, linha 3 do algoritmo principal, e segue a execução normalmente. Como na linha 6 do algoritmo há uma chamada para a www.esab.edu.br 18 “Função B”, novamente haverá um desvio, executando todas as instruções da linha 1 a 5 da função B, e retornando para o algoritmo na linha 7, dando sequência na execução até o seu encerramento. Todo esse processo é possível devido a uma estrutura de dados que gerencia todos os desvios entre o algoritmo e as funções, chamada de pilha de execução. A cada execução de um método ou função, a sua chamada é inserida no topo de uma estrutura de dados chamada de pilha, que possui duas operações, a inserção no topo e a remoção do topo, de forma que o último elemento inserido será o primeiro a ser removido. A Figura 3 representa a pilha de execução para o exemplo anterior em que um algoritmo chama as funções A e B, observe: Antes da execução 1 Algo Algoritmo inserido no topo da linha. Algoritmo em execução 2 Algo Execuçãoretorna para o algoritmo. 5 Algo Execução retorna para o algoritmo. 8 Fim da execução do algoritmo. 9 Algo A Função A em execução. Método inserido na pilha. 3 Variáveis e parâmetros Algo Função A terminou. Método removido da pilha. 4 A Função B é chamada e entra em execução. Algo B Método B inserido no topo da pilha. 6 Algo Função B terminou. Método é removido da pilha. 7 B Figura 3 – Pilha de execução. Fonte: Elaborada pelo autor (2013). www.esab.edu.br 19 Observe que no passo 1 a pilha estava vazia, não havia função em execução. No passo 2 o algoritmo entrou em execução, sendo inserido na pilha de execução. Assim que a “função A” foi chamada no algoritmo, ela foi inserida no topo da pilha (passo 3) de execuções, mantendo-se ali até que a função se encerre. No passo 4 a “Função A” foi encerrada, sendo retirada da pilha e devolvendo a execução para o algoritmo (passo 5). No momento em que a “Função B” é chamada e entra em execução, esta é inserida no topo da pilha (passo 6), mantendo-se até que sua execução se encerre. No encerramento da “Função B”, ela é retirada da pilha (passo 7), devolvendo a execução para o algoritmo (passo 8) até que ele termine sua execução e seja retirado da pilha (passo 9). Durante a execução das funções, as variáveis declaradas e utilizadas nelas passam a ser criadas à medida que a função entra em execução e destruídas quando a função se encerra. O acesso às variáveis a partir do seu local de declaração é chamado de Visibilidade de Variáveis. 2.3 Visibilidade de variáveis Observando a pilha de execução pode-se notar que apenas uma função é executada por vez, sendo que a função anterior só retorna à sua execução quando a função que está no topo da pilha é encerrada (apenas a função do topo está em execução naquele instante), e consequentemente, retirada da pilha. Portanto, a cada execução a função é inserida no topo da pilha. Se já existe alguma função em execução, esta não será mais executada até que a função que está no topo da pilha se encerre, retornando o processamento para a função que estava logo abaixo, e assim sucessivamente para todas as funções empilhadas, inclusive o algoritmo que chamou as funções. Nesse contexto, quando são declaradas variáveis dentro dessas funções, chamadas de variáveis locais (FORBELLONE et al., 2005), elas passam a existir somente no momento em que a função é executada. www.esab.edu.br 20 Por isso essas variáveis não podem ser acessadas por outras funções, uma vez que a execução das outras funções só terá sequência quando a função no topo da pilha se encerrar. No momento em que isso ocorre, as variáveis locais também saem da pilha, assim como a função, e deixam de existir. Quando uma função ou procedimento é chamado, as variáveis locais e parâmetros são copiados para a pilha com a função. Quando a função termina, as variáveis e parâmetros são desalocados da memória, no mesmo momento em que a função se encerra. Por essa razão, as variáveis locais fora das funções não podem ser acessadas por outras funções ou pelo programa principal (algoritmo). Guimarães (1994, p. 133) destaca: “[...] uma variável declarada dentro de um bloco só é conhecida dentro deste bloco”. Para que os dados armazenados no algoritmo possam ser acessados por qualquer método ou função, esta deve ser declarada no algoritmo, por isso chamada de variáveis globais (FORBELLONE et al., 2005). No caso da linguagem de programação C, as variáveis globais são declaradas fora das funções, inclusive da função main() que é obrigatória e responsável pela execução do programa, é a primeira função executada por um programa em C. Caso uma variável seja declarada na função main() ela não estará acessível as outras funções. A Figura 4 apresenta um programa escrito em C com duas variáveis, observe: www.esab.edu.br 21 Figura 4 – Declaração de variáveis em C. Fonte: Elaborada pelo autor (2013). A variável sexo foi declarada fora das funções teste() e main(). Sendo assim, se trata de uma variável global, acessível às duas funções. Já a variável idade foi declarada dentro da função main(), dessa forma sendo visível apenas a essa função. Na função teste(), na linha 9, foi indicado um erro (quadrado em vermelho), indicando que a variável idade não existe. Todas as instruções, da linha 1 a linha 18 correspondem ao algoritmo em C, assim temos nesse algoritmo as bibliotecas stdio e stdlib, a variável global sexo, as funções teste() e main(), que possui a variável local idade. O escopo de abrangência de uma variável é chamado de visibilidade da variável. Forbellone et al. (2005, p. 136) destaca que [...] em alguns casos, uma determinada variável é utilizada apenas por um módulo específico, o que não justifica uma definição global, pois somente se fazem necessários o conhecimento e a utilização dessa variável dentro dos limites deste bloco lógico. As variáveis locais devem ser utilizadas para a definição de dados que serão manipulados somente naquele método, garantindo que o espaço de memória seja alocado dinamicamente, pois ocorre durante a execução da função, e após a sua execução seja liberado, otimizando o uso de www.esab.edu.br 22 memória principal para criação e armazenamento de variáveis. Por outro lado, caso os dados devam ser acessados pelas funções desenvolvidas pelo programador e pelo método main(), que é o método principal e obrigatório em todo programa desenvolvido em C, as variáveis devem ser declaradas fora das funções, tornando-se assim globais e visíveis a todas as estruturas de dados do algoritmo. Porém, mesmo que não sejam utilizadas, ocupam espaço de memória enquanto o programa estiver em execução. Estudo complementar Para saber mais sobre os conceitos e exemplos de funções em C, acesse aqui. Nesta unidade estudamos sobre a modularização de algoritmos com o uso de funções e como se dá o funcionamento e gerenciamento dos blocos de instruções quando chamados pelo algoritmo, por meio da pilha de execução que interfere diretamente na visibilidade e escopo das variáveis. Na próxima unidade daremos sequência ao estudo das funções com foco na recursividade e endereçamento de variáveis. www.esab.edu.br 23 3 Funções – parte II Objetivo Apresentar a definição de ponteiros e conhecer a técnica de recursividade e como utilizar variáveis estáticas dentro das funções. Caro aluno, na unidade anterior você pôde estudar sobre a pilha de execução das funções e a sua relação com a visibilidade das variáveis. Nesta unidade serão apresentados os conceitos de endereçamento de memória na forma de ponteiros, o uso da recursividade, que consiste em uma função que faz uma chamada para ela mesma e o uso de variáveis estáticas, que utilizam um único endereçamento no seu armazenamento na memória principal. Para abordar esses assuntos, foram utilizadas as referências de Guimarães (1994) e Forbellone et al. (2005). Nos estudos anteriores você conheceu o conceito e forma de funcionamento da pilha de execução que armazena as chamadas das funções e suas variáveis. Esse armazenamento se dá na memória principal do computador, em endereços específicos, conhecidos como ponteiros. 3.1 Ponteiro de variáveis Pode-se entender a variável como uma caixa que armazenará algum conteúdo definido pelo programador, no momento de sua inicialização ou em uma atribuição. A variável possui quatro importantes propriedades: o nome, também conhecido como identificador; o tipo de dados; o valor armazenado; e a sua localização na memória principal, chamado de endereçamento. Para Guimarães (1994, p. 20), “[...] podemos imaginar uma variável como o nome de um local onde se pode colocar qualquer valor do conjunto de valores possíveis do tipo básico associado”.O nome da variável é utilizado pelo programador para identificar no algoritmo a variável que será manipulada, por isso chamada de identificador. O valor da variável pode ser alterado durante a execução do programa, sempre respeitando a definição do tipo de www.esab.edu.br 24 dados que podem ser: números (inteiros e reais), caracteres (símbolo ou conjunto de símbolos) e lógicos (verdadeiro ou falso). A criação de uma variável se dá pela instrução de declaração da variável, que na linguagem de programa C/C++ possui a semântica apresentada na Figura 5, observe: Tipo de Dado Identi�cador ; ; Figura 5 – Declaração de Variável em C/C++. Fonte: Elaborada pelo autor (2013). Observe que a variável em C pode ter apenas um tipo de dados, e mais de uma variável podem ser declaradas para o mesmo tipo, desde que os nomes (identificadores) sejam separados por vírgula. São exemplos de declaração de variáveis em C. • int idade; • float peso, altura, media; • char nome[30], cidade[30]; A declaração tem como finalidade criar as variáveis que serão utilizadas no programa de computador, mas na prática o resultado é a alocação de espaço na memória principal, na área chamada de Heap. Cada variável declarada será alocada em um bloco de memória com endereço único. Mesmo que a variável não seja utilizada, esse espaço não será liberado até que o programa se encerre. O código a seguir apresenta a declaração de variáveis em C/C++, observe: Algoritmo 1 – Exemplo de declaração de variáveis em C 01 int idade; 02 float media; 03 char sexo; Fonte: Elaborado pelo autor (2013). www.esab.edu.br 25 No algoritmo declaram-se três variáveis: idade como inteiro, na linha 1, media como float, na linha 2, e sexo como char na linha 3. Antes da execução do programa, essas instruções solicitam ao sistema operacional um espaço de memória para cada variável. Essa ação é chamada de alocação de memória e resulta na criação física da variável e da associação a um endereço. A Figura 6 representa a alocação das variáveis do algoritmo 1 na memória principal, veja: Emq1 idade Memória Heap Memória Principal Emq3 sexo Emq2 media endereço valor identi�cador Figura 6 – Alocação de variáveis do algoritmo 1. Fonte: Elaborada pelo autor (2013). Observe que na memória Heap, um espaço da memória principal, foram alocados espaços para cada variável. Além de alocar o espaço, associa- se um endereço à variável, representado pelo termo Emq (endereço de memória qualquer), sendo que cada variável possui o seu endereço exclusivo, ou seja, duas variáveis não podem utilizar o mesmo endereço. Note que cada variável é representada pelo seu nome, um valor e um endereçamento de memória. Assim, na Figura 6, Emq1, Emq2 e Emq3 são os endereços de memória de cada variável. Na Figura 7 está representada uma atribuição à variável idade, observe. www.esab.edu.br 26 Emq1 idade Memória Heap Memória Principal Emq3 sexo Emq2 media idade = 43; Emq1 43 43 Figura 7 – Atribuição de valor à variável. Fonte: Elaborada pelo autor (2013). Note que foi realizada uma atribuição utilizando o identificador da variável “idade”, usando a sintaxe: idade = 43; Para a linguagem de programação a variável idade corresponde ao ponteiro Emq1, que é o seu endereço na memória Heap, e para que a atribuição ocorra há um desvio para esse endereço, localizando a variável na memória e gravando o valor 43 nesse endereço. Caso o identificador referenciado não exista na memória Heap, a linguagem de programação indicará um erro de compilação informando que o identificador não foi declarado. Observe que de acordo com o que foi estudado até aqui, toda variável possui um identificador, um valor, um endereço de memória e um tipo de dado. Mas há um tipo diferenciado de variável que é chamado de ponteiro cuja finalidade é armazenar o endereço de uma variável na memória do computador. A Figura 8 representa uma variável do tipo ponteiro, observe: endXYZ idade Endereço:endXYZ nome Figura 8 – Variável do tipo ponteiro. Fonte: Elaborada pelo autor (2013). www.esab.edu.br 27 A variável nome possui um endereço hipotético endXYZ, que por sua vez está sendo atribuído à variável ponteiro que o armazenará. Caso seja executada a instrução para imprimir o valor da variável ponteiro, o resultado será a impressão do endereço armazenado, porém, caso seja solicitada a impressão do valor nesse endereço, o resultado impresso será o mesmo valor que está armazenado na variável nome. Para Cerqueira, Celes e Rangel (2004, p. 46), “[...] os ponteiros servem para armazenamento e manipulação de valores de endereços de memória”. Assim como a Heap gerencia a criação de cada variável declarada no programa, as chamadas das funções também são monitoradas por essa estrutura, armazenando o endereço de cada função chamada, das variáveis locais e globais. Mas há um tipo específico de chamada de funções na qual uma função faz uma chamada a ela mesma no código, e a Heap precisa gerenciar cada endereço dessas chamadas, garantindo a execução correta do programa. 3.2 Recursividade A recursividade consiste em uma função fazer a chamada dela mesma em seu código, e a sua finalidade é tornar algoritmos mais complexos em rotinas mais simples e com menos linhas de codificação. Para Guimarães (1994, p. 141): “[...] existem casos em que um procedimento ou função chama a si próprio. Diz-se então que o procedimento ou função é recursivo”. O código do Algoritmo 2 apresenta uma função recursiva de pesquisa de um determinado valor inteiro em um vetor. Algoritmo 2 – Pesquisa recursiva 01 int pesquisar(int chave, int posicao,int tamanho){ 02 if(posicao == tamanho) return 0; //não existe 03 else{ 04 if(dados[posicao] == chave) return 1;//achou 05 else{ 06 posicao++; 07 return pesquisar(chave,posicao,tamanho); 08 } 09 } } Fonte: Elaborado pelo autor (2013). www.esab.edu.br 28 As regras de funcionamento do processo recursivo são: se a posição visitada do vetor for igual ao tamanho do vetor, instrução da linha 2, então todas as posições já foram visitadas e o valor não existe no vetor, não há mais posições a serem pesquisadas e o método retornará um 0 (falso). Na regra da linha 4, compara-se o vetor na posição de pesquisa com a chave, que é o valor procurado, e se for igual a função retornará o valor 1 (verdadeiro), indicando que o valor existe. Caso o vetor não tenha todas as posições visitadas e o valor não tenha sido achado, na linha 7 a função é chamada recursivamente recebendo como parâmetro o valor a ser pesquisado (chave), a nova posição de pesquisa, pois esta foi incrementada na linha 6, e o tamanho do vetor. O código a seguir implementa a pesquisa no vetor com valores predefinidos. Algoritmo 3 – Pesquisa recursiva no vetor 01 #include <stdio.h> 02 #include <stdlib.h> 03 int dados[] = {1,3,15,70,19,101,2,20,23,4,127,88}; 04 int pesquisar(int chave, int posicao,int tamanho){ 05 if(posicao == tamanho) return 0; //não existe 06 else{ 07 if(dados[posicao] == chave) return 1;//achou 08 else{ 09 posicao++; 10 return pesquisar(chave,posicao,tamanho); 11 } 12 } 13 } 14 int main (void){ 15 int valor,resultado; 16 puts("Informe um Valor para Pesquisa:"); 17 scanf("%d",&valor); 18 resultado = pesquisar(valor,0,12); 19 if(resultado == 1) puts("Valor existe no vetor"); 20 else puts("Valor não existe no vetor"); 21 system("pause"); 22 return 0; 23 } Fonte: Elaborado pelo autor (2013). www.esab.edu.br 29 O algoritmo solicita ao usuário um valor a ser pesquisado. Como o vetor possui 12 elementos, na linha 18 é chamada a função pesquisar com os parâmetros valor (valor a ser localizado no vetor), 0 (primeira posição de pesquisa) e 12 (tamanho do vetor). A cada interação da função, caso o valor não sejalocalizado e o vetor não tenha sido todo visitado, a função pesquisar é reexecutada com o mesmo valor de pesquisa, o mesmo tamanho de vetor, porém com a próxima posição de pesquisa. O Quadro 1 apresenta a sequência de pesquisa do valor 70 no vetor: Número de Chamadas do Método Valor Posição Tamanho Valor do vetor Dados [posição] Linhas Executadas do Método Primeira - 1 70 0 12 1 6, 7, 8, 9, 10, 11,12 2 70 1 12 3 6, 7, 8, 9, 10, 11,12 3 70 2 12 15 6, 7, 8, 9, 10, 11,12 4 70 3 12 70 6, 7, 8,9 retorna 1 Quadro 1 – Simulação da Função Pesquisar. Fonte: Elaborado pelo autor (2013). Observe que as três primeiras chamadas executam do início da função até a linha 10 quando a função é reexecutada com o mesmo valor de pesquisa, mesmo tamanho e a próxima posição do vetor que foi incrementada na linha 9. Na quarta execução da função, na linha 7, o valor do vetor será igual ao valor pesquisado e, portanto, a função executará o comando return 1, e se encerrará, já que o comando return termina a execução de uma função. A Figura 9 apresenta a sequência de execução de cada uma das chamadas função pesquisar buscando o valor 70. 4 3 2 1 Figura 9 – Sequência de execução do método pesquisar buscando o valor 70. Fonte: Elaborada pelo autor (2013). www.esab.edu.br 30 Observe que a chamada número 1 da função fará uma nova chamada para a mesma função, assim como a chamada número 2, chamada número 3 e chamada número 4. Todas as chamadas recursivas serão executadas no comando return pesquisar (chave, posição, tamanho). Cada chamada é inserida na pilha de execução e fica aguardando para retornar à execução normal quando a chamada acima encerrar. Dessa forma, a chamada 1 aguarda a chamada 2, que por sua vez aguarda a chamada 3, que aguarda a chamada 4. Como a chamada 4 retornará 1, pois a condição if(vetor[posicao] == chave) será verdadeira, essa chamada será encerrada, dando a vez da execução da chamada 3, que parou na instrução return pesquisar (). Assim, a chamada 3 retornará o valor 1 devolvido pela chamada 4, e será encerrada, transferindo a execução para chamada 2, que também aguarda o return pesquisar(). Devolverá então o valor 1 retornado pela chamada 3 e portanto se encerrará, passando a vez para chamada 1 que está aguardando no return pesquisar(), que retornará 1 da chamada 2 e se encerrará. Saiba mais Para se aprofundar no conhecimento de métodos recursivos, você pode acessar um conjunto de endereços com blogs que tratam do assunto clicando aqui. Bem, até agora estudamos as funções, as chamadas recursivas e a visibilidade das variáveis. Em princípio as variáveis locais têm seus dados perdidos com o encerramento da função na qual foram declaradas. Digo em princípio, pois é possível implementar variáveis locais que não perdem os dados após a execução das funções. Essa técnica é chamada de variáveis estáticas. www.esab.edu.br 31 3.3 Variáveis estáticas O uso das variáveis estáticas está associado ao contexto de visibilidade das variáveis, que podem ser locais e globais. Como estudado na unidade anterior na seção de visibilidade das variáveis, as variáveis locais só podem ser acessadas durante a execução do método, sendo visível somente nesse método e que no seu encerramento todas as variáveis são desalocadas e os dados são perdidos. Já as variáveis globais podem ser acessadas por qualquer método do programa e é destruída somente ao final da sua execução. O papel das variáveis estáticas declaradas nas funções é garantir que o valor da variável seja mantido mesmo após o encerramento da execução do método. O código a seguir apresenta o método chamado contar que declara e usa a variável contador de forma estática. O método main() faz duas chamadas a esse método, observe: Algoritmo 4 – Função com variável estática 01 #include <stdio.h> 02 #include <stdlib.h> 03 void contar(){ 04 static int contador =0; //declara uma variável como estática 05 contador++; //incrementa a variável contador em 1 06 printf("Contador vale:%d",contador); //imprime a variável na tela 07 } 08 int main() 09 { 10 system("cls"); 11 printf("\nExecutando a Funcao Contar\n"); 12 contar(); 13 printf("\nExecutando a Funcao Contar\n"); 14 contar(); 15 system(“pause”); 16 return 0; 17 } Fonte: Elaborado pelo autor (2013). www.esab.edu.br 32 Note que a cláusula static, utilizada na linha 4, é que define uma variável como estática, e deve ser usada na sua declaração. Cada vez que o método contar() é executado, a variável contador é incrementada em 1 e impressa na tela. No método main() a função contar é chamada duas vezes, a primeira chamada na linha 12 e outra na linha 14. Na chamada da linha 12, a variável contador começará em 0, será incrementada em 1, portanto, seu valor atual será 1. Mesmo após o encerramento do método, o valor da variável não será perdido, e na segunda chamada do método, na linha 14, a variável contador, por ser estática, iniciará de 1, não mais de 0, depois será incrementada em 1, mudando seu valor atual para 2 e posteriormente será impresso na tela. A Figura 10 apresenta o resultado do programa após a sua execução: Figura 10 – Resultado da execução do Algoritmo 4. Fonte: Elaborada pelo autor (2013). Observe que a variável contador não perdeu seus dados após a primeira execução, isso só é possível porque as variáveis estáticas são armazenadas na memória principal em blocos de memória diferentes do espaço utilizado pelas funções, e no encerramento da função todas as variáveis são desalocadas com o método. Mas as variáveis estáticas não, pois estão em outro endereçamento de memória. A Figura 11 apresenta a alocação de funções com variáveis locais e variáveis estáticas, veja: www.esab.edu.br 33 variáveis locais Função / Método variáveis estáticas Função / Método Figura 11 – Alocação de variáveis locais e variáveis estáticas. Fonte: Elaborada pelo autor (2013). Note que a Figura 11 representa que as variáveis locais ficam no mesmo bloco de memória da função que as cria, por isso, quando uma função é encerrada, as variáveis são perdidas. Já as variáveis estáticas são alocadas em outro bloco de memória, por isso quando a função se encerra, a variável se mantém alocada. Nesta unidade foi apresentado o conceito de ponteiros, que é o endereço da variável na memória principal, na área chamada de Heap. Esse endereçamento é resultado da instrução de declaração da variável. Duas variáveis não podem ter o mesmo endereço, pois se trata da forma pela qual a linguagem identifica a variável que está sendo manipulada no programa. Vimos também que a recursividade consiste na técnica de criar funções que chamam a si própria. Por fim, você pôde estudar o uso de variáveis estáticas declaradas em funções e como possibilitam o acesso aos seus dados mesmo após o encerramento da função. Na próxima unidade estudaremos as variáveis compostas homogêneas, conhecidas como vetores. www.esab.edu.br 34 4 Variáveis compostas homogêneas: vetores Objetivo Apresentar o conceito, a aplicação e a manipulação de vetores. Nesta unidade será apresentada a estrutura de dados vetor com ênfase no seu funcionamento e armazenamento na memória principal. Para isso, vamos utilizar as principais ideias dos trabalhos de Cerqueira, Celes e Rangel Netto (2004). Na unidade anterior você pôde estudar a declaração de variáveis e pôde verificar que a variável é formada por um nome ou identificador, valor, endereço de memória e o tipo de dados. Mas existem casos em que há a necessidade de um armazenamento de vários dados do mesmo tipo, porém, a declaração de inúmeras variáveis torna a solução do problema mais complexa do que realmente é. Por exemplo, uma empresa necessita cadastrar a média de vendas de um determinado produto nos doze mesesdo ano. Uma solução seria criar 12 variáveis do tipo real para armazenamento dessas médias e posterior processamento no algoritmo. Note que a solução é bem simples, entretanto, utilizar 12 variáveis torna o processo mais complexo, pois gerenciar 12 variáveis de forma correta não é tarefa fácil, o que pode gerar inconsistências na solução do problema. A estrutura de dados que pode ser utilizada para facilitar a solução do problema é o uso de vetores. 4.1 Conceito de vetores O vetor permite criar uma variável dividida em inúmeras posições, todas do mesmo tipo de dados. Para Cerqueira, Celes e Rangel Netto (2004, p. 59), “[...] o vetor é a forma mais simples de estruturar um conjunto de dados”. www.esab.edu.br 35 Para contextualizar, vamos analisar o problema de armazenamento das médias de vendas de um determinado produto nos 12 meses do ano. Em vez de declarar 12 variáveis, é possível a criação de uma variável com 12 posições. A declaração de vetor em C é realizada utilizando-se a seguinte sintaxe: tipo de dado identificador[n], em que n se refere à quantidade de posições do vetor, começando em 0 e terminando na posição n-1. A instrução a seguir apresenta a declaração da variável “vendas”, como um vetor de reais com 12 posições, veja: float vendas[12]. A instrução criará a variável “vendas”, com um único endereço de memória, porém com 12 posições para serem manipuladas. Observe na Figura 12 a estrutura do vetor vendas: vendas 0 Emq1 1 2 3 4 5 6 7 8 9 10 11 Figura 12 – Vetor de Vendas. Fonte: Elaborada pelo autor (2013). Note que o vetor “vendas” possui um único endereço de memória, representado pela expressão (Emq1 – endereço de memória qualquer), e um conjunto de 12 posições para armazenamento e manipulação dos dados, começando na posição 0 e terminando na posição 11. Como o tipo de dados float ocupa 4 bytes, e o vetor possui 12 posições, serão alocados na memória 48 bytes contíguos para esse vetor, conforme representado na Figura 13 a seguir, observe: vendas Emq1 12 X 4 = 48 bytes Figura 13 – Alocação de Memória para o vetor de vendas. Fonte: Elaborada pelo autor (2013). Analisando a Figura 13 pode-se notar que o bloco de memória não possui as posições ou a divisão. E isso é verdade: na memória será alocado um bloco único de 48 bytes, resultante do cálculo da quantidade de elementos do vetor multiplicado pelo espaço ocupado pelo tipo de dados www.esab.edu.br 36 do vetor. Porém no uso do vetor para armazenamento ou leitura dos dados, o programador deverá informar qual a posição a ser utilizada. Mas como isso é possível? Como a variável possui um único endereço de memória, e o vetor armazena o tipo float (real), e cada real ocupa 4 bytes, para que seja possível gravar na posição indexada no vetor, basta multiplicar a posição por 4. Por exemplo, para atribuir o valor 128.99 na posição 3 do vetor, a instrução em C é: vendas [3] = 128.99, mas na execução do programa, como será realizada essa operação pelo sistema operacional de forma que na posição 3 seja realmente armazenado o valor 128.99? A Figura 14 representa o processo para se chegar à posição 3 do vetor: vendas 0 Emq1 Ponto de partida Endereço da variável Deslocamento: quantidade de posições X 4 bytes 1 2 3 128.99 4 5 6 7 8 9 10 11 Posições deslocadas ponteiro Bloco de memória ocupado pelo tipo de dados Figura 14 – Localizando a posição no vetor. Fonte: Elaborada pelo autor (2013). Como a variável possui um endereço de memória, esse ponto representa o início do bloco que foi alocado contiguamente, ou seja, cada bloco de 4 bytes do tipo float foi alocado um ao lado do outro. Para chegar à posição desejada é preciso calcular em quanto será o deslocamento na estrutura, cujo resultado é multiplicar o tamanho do bloco pelo número de posições. Nesse caso, a posição 3 resulta em um deslocamento de 12 bytes a partir do endereço inicial do vetor. Esse deslocamento posicionará o ponteiro do vetor 12 bytes à frente do endereço de memória de todo o bloco. Como cada bloco possui 4 bytes, os próximos quatro bytes a partir do ponteiro se refere à posição indexada pela instrução vendas[3] = 128.99. Nesse bloco será gravado o valor informado. www.esab.edu.br 37 Saiba mais Para aprofundar os estudos sobre os tipos de dados em C e o espaço ocupado por cada um deles, acesse o endereço clicando aqui. Agora que vimos que os vetores nos permitem criar uma variável dividida em inúmeras posições, veremos a seguir como os vetores são aplicados e manipulados. 4.2 Aplicação e manipulação A aplicação de vetores está muito associada à necessidade de armazenamento de vários dados na forma de uma tabela. Para tanto, na sua declaração é necessário que seja explicitamente informada a quantidade de elementos dessa tabela, que é formada por uma única linha e “N” colunas. Para inserir, alterar ou consultar um determinado valor do vetor é necessária a referência da posição do vetor que se deseja acessar, utilizando a seguinte sintaxe: identificador[p], em que “p” se refere à posição que varia de 0 ao tamanho do vetor -1. Caso a posição informada seja maior ou igual ao tamanho do vetor, ocorrerá um erro fatal que encerrará a execução do algoritmo, já que a posição não pertence ao bloco previamente alocado. A Figura 15 representa o vetor de vendas com 12 posições e o resultado da instrução que intenciona gravar na posição 12 do vetor, observe: vendas 12 X 4 = 48 bytes 0 1 2 3 4 5 6 7 8 9 10 11 12 Figura 15 – Manipulando a posição fora do vetor. Fonte: Elaborada pelo autor (2013). www.esab.edu.br 38 Note que a posição 12 do vetor é um bloco de memória que não lhe pertence, por isso quando acessada pelo programa de computador resultará em um erro fatal, grave, que resultará no encerramento da aplicação. Para atribuição de um valor no vetor, a sintaxe é: identificador[p] = valor. Caso nessa posição já exista um valor, este será sobrescrito, uma vez que em cada posição do vetor somente um valor pode ser armazenado. Dessa forma essa instrução possibilita tanto a inserção de um valor no vetor quanto a sua alteração. Para consulta de um valor, basta referenciar o vetor e a posição de acesso por meio da sintaxe: identificador[p]. O algoritmo a seguir apresenta as instruções de inserção, alteração e consulta no vetor. Algoritmo 5 – Manipulação de vetores 01 #include <stdio.h> 02 #include <stdlib.h> 03 float vendas[12]; 04 int main(){ 05 puts("Informe a media de vendas do Primeiro Mes:"); 06 scanf("%f",&vendas[0]); //inserção de dados 07 printf(" A venda do primeiro mes foi:%.2f",vendas[0]);//consulta 08 puts("\nInforme NOVAMENTE a media de vendas do Primeiro Mes:"); 09 scanf("%f",&vendas[0]); //alteração de dados 10 printf(" A venda do primeiro mes foi:%.2f",vendas[0]);//consulta 11 system(“pause”); 12 return 0; 13 } Fonte: Elaborado pelo autor (2013). Observe no algoritmo que na linha 4 será gravado um valor informado pelo usuário na primeira posição do vetor. Na linha 5 será mostrado o valor informado na entrada de dados da instrução da linha 4. Na linha 7 será cadastrado um novo valor na primeira posição do vetor, alterando o antigo valor para esse novo valor que será informado pelo usuário do sistema. Na linha 8 será mostrado o valor atual da primeira posição do vetor. www.esab.edu.br 39 Uma característica importante do vetor é que uma vez definida a sua dimensão (quantidade de elementos) na declaração do vetor, durante a execução do programa o bloco de memória alocado não poderá ser gerenciado, independentemente da inserção ou não de itens no vetor. Cerqueira, Celes e Rangel Netto (2004) destacam o uso da alocação dinâmica para os casos em que a dimensão do vetor é desconhecida. Quando sabemos a dimensão, é preferível ouso de vetores. Mesmo que dados não sejam armazenados no vetor, o espaço é pré- alocado a fim de garantir que o bloco de memória esteja disponível durante a execução do programa, sendo liberado após o seu encerramento. Tal contexto pode representar um grande problema no uso dessa estrutura de dados, uma vez que havendo necessidade de mais ou menos posições, não há como modificá-lo, pois se trata de uma alocação estática, aquela que ocorre antes da execução do programa. No caso do vetor de vendas, por exemplo, caso o usuário deseje cadastrar as vendas de um período superior a 12 meses, não será possível, pois o vetor durante a execução do programa permitirá somente 12 posições de armazenamento. Caso o usuário do sistema cadastre apenas as vendas dos seis primeiros meses, o vetor ocupará o espaço referente às 12 posições, independentemente de quantas já foram cadastradas, sendo que as demais posições serão preenchidas com zero. Dessa forma, o uso de vetores deve estar associado a casos em que se conhece exatamente quantos dados serão cadastrados, sem a possibilidade de variações nesse número de informações a serem gerenciadas. Nesta unidade estudamos sobre a estrutura de dados vetor que possui como principal característica o uso de índices ou posições para referenciar o local de inserção ou busca dos dados. Por se tratar de uma estrutura de dados de alocação estática, que não permite a otimização e gerenciamento da sua dimensão, é recomendado para os casos em que o número de dados a serem gerenciados é conhecido e não se modifica durante a execução do programa. Na próxima unidade você estudará o conceito de alocação dinâmica, uma técnica que possibilita a criação de estruturas de dados com tamanhos alterados durante a execução do programa. www.esab.edu.br 40 5 Alocação dinâmica de memória Objetivo Apresentar o conceito de alocação dinâmica de memória. Vimos na unidade anterior o conceito de vetores, também como são aplicados e manipulados. Já nas unidades anteriores, em especial a unidade 3 estudamos sobre a definição de ponteiros, o uso da recursividade e variáveis estáticas. Nesta unidade, vamos apresentar a alocação dinâmica de memória, por meio do aporte teórico de Cerqueira, Celes e Rangel Netto (2004) que se fundamenta nos conceitos de vetores, recursividade e variáveis estáticas. Conforme estudamos na unidade 3, as variáveis são formadas por um nome, um tipo de dado, os valores que são armazenados e um endereço de memória. O ciclo de vida de uma variável começa no momento em que é declarada. Sem a sua declaração uma variável não pode ser reconhecida pelo compilador em uso e, consequentemente, não pode ser alocada na memória principal. Entretanto, a instrução de declaração da variável não representa a sua criação. A alocação de memória para a variável pode ocorrer antes da execução do programa, quando o programa é carregado na memória do computador, na Heap, de forma que o espaço de memória para variável é alocado e não pode ser gerenciado. Mesmo que não seja atribuído valor algum à variável, esse espaço de memória se manterá reservado até que o programa se encerre. Esse processo de alocação de memória no carregamento do programa ocorre, por exemplo, na declaração das variáveis globais, aquelas visíveis a todas as funções implementadas no programa. Esse processo de alocação de memória no carregamento do programa é chamado de alocação estática. Cerqueira, Celes e Rangel Netto (2004, p. 12), destacam: “[...] a declaração de uma variável reserva um espaço na memória para armazenar um dado do tipo da variável e associa o nome da variável a esse espaço de memória”. www.esab.edu.br 41 O Quadro 2 a seguir apresenta as vantagens e desvantagens da alocação estática, observe: Vantagens Desvantagens O gerenciamento da memória é realizado exclusivamente pelo sistema operacional que é responsável por alocar e desalocar o espaço de memória solicitado. O espaço de memória alocado não pode ser alterado durante a execução do programa. Caso a definição do espaço solicitado não atenda às necessidades do programa de computador, não há como gerenciá-lo de forma a corrigir o problema. A definição de alocação de memória é simples, realizada pela instrução de declaração de variável. Caso o espaço solicitado na declaração das variáveis seja maior que o espaço disponibilizado pelo sistema operacional, o programa de computador não será iniciado, indicando um problema de Overflow. A sua implementação é simples e atende aos problemas triviais de armazenamento de dados que exigem pouco espaço de armazenamento, principalmente quando se conhece a quantidade de dados a serem armazenados e manipulados. É inviável para solução de problemas em que não se conhece a quantidade de dados que serão armazenados ou o espaço a ser alocado é muito maior do que o disponibilizado pelo sistema operacional. Quadro 2 – Vantagens e desvantagens da alocação estática. Fonte: Elaborado pelo autor (2013). Note que um grande problema da alocação estática está na definição de quanto de espaço de memória será alocado e reservado para a execução do programa no que se refere às variáveis. Essa é uma realidade no desenvolvimento de softwares no dia a dia. Por exemplo, uma escola que possui um cadastro de alunos e deseja gerar um relatório com os que moram em determinado bairro: quantos alunos atendem a essa condição? Haverá necessidade de criação de uma estrutura de dados para armazenar e listar esses dados: como dimensioná-la? Para resolver esse problema da alocação estática, em que o espaço de memória é alocado sem a possibilidade de gerenciamento por parte do programa, por exemplo, aumentando ou diminuindo esse espaço à medida que se faz necessário, com criação de novas posições num vetor, ou diminuição de posições do vetor com a remoção de dados não serão mais utilizadas é que a maioria das linguagens de programação disponibiliza a alocação dinâmica. www.esab.edu.br 42 5.1 Definição Na alocação dinâmica as variáveis são declaradas, porém, o espaço de memória será alocado durante a execução do programa por meio de instruções específicas por parte do programador, garantindo que ele possa alocar e liberar espaço de memória a qualquer momento. Isso possibilita ao programador a construção de estruturas de dados que tenham tamanhos variados, de acordo com a necessidade de momento, evitando- se o desperdício de memória. Um exemplo clássico de desperdício de memória é quando declaramos variáveis globais no programa que não são utilizadas. Esse procedimento não gerará erro algum de compilação e o programa será executado sem restrições. Entretanto, todas as variáveis globais ocupam um espaço de memória no carregamento do programa, e mesmo que não sejam utilizadas, o espaço alocado não é liberado até que o programa se encerre. Por isso é muito importante uma revisão do programa verificando se variáveis globais foram declaras e não foram utilizadas. Outra técnica muito comum é a criação de vetores com quantidade de elementos muito alta, pois não se conhece previamente essa quantidade em tempo de execução do programa. Por exemplo, para armazenar os nomes dos alunos de uma sala é criado um vetor com 60 posições, estimando-se que uma turma possa ter no máximo essa quantidade de alunos, entretanto, caso a turma tenha 20 alunos, as outras 40 posições foram previamente alocadas e não usadas, representando um desperdício de memória considerável. Se cada nome no vetor for definido com 35 caracteres, estamos desperdiçando 1.400 bytes de memória (40 posições vezes 35 bytes de cada nome). Para Cerqueira, Celes e Rangel Netto (2004, p. 64), “[...] a alocação dinâmica é um meio de requisitar espaços de memória em tempo de execução. [...] a principal vantagem daalocação dinâmica é fazer a alocação sem desperdício de memória”. www.esab.edu.br 43 O Quadro 3 apresenta as vantagens e desvantagens da alocação dinâmica, observe: Vantagens Desvantagens O espaço de memória é definido em tempo de execução sem desperdiço de memória, já que o espaço pode ser alocado e liberado em tempo de execução. Pode ser aplicado em soluções triviais ou em problemas complexos que exigem um espaço de memória considerável. É implementado a partir de ponteiros, que são variáveis que apontam para um endereço de memória. Como se trata de um bloco de memória criado em tempo de execução, toda a responsabilidade em alocar e liberar o espaço de memória é do programador. Caso ele libere um espaço de memória em um momento inadequado, todos os dados serão perdidos. Cabe ao programador saber o momento de executar a instrução que aloca o espaço de memória, bem como o momento em que o espaço é liberado. Quadro 3 – Vantagens e desvantagens da alocação dinâmica. Fonte: Elaborado pelo autor (2013). Note que no caso da alocação dinâmica, a principal vantagem é alocar e liberar espaço de memória a qualquer momento da execução do programa, porém, cabe ao programador saber o momento exato em que essas operações devem ser realizadas. Enquanto a alocação estática era gerenciada pelo sistema operacional e o programador “usava” as variáveis, na alocação dinâmica cabe ao programador declarar, alocar e liberar as variáveis, aumentando a complexidade dos algoritmos. A alocação dinâmica é a base para implementação de estruturas de dados mais avançadas como Pilhas, Filas, Listas e Árvores, que são basicamente estruturas de armazenamento em que não se conhece previamente o número de elementos que serão manipulados. Para sua reflexão O espaço de memória alocado para um programa de computador ou funções é gerenciado pelo sistema operacional do computador, porém, não podemos imaginar que todo o espaço de memória do computador está disponível. Uma grande parte dessa memória é utilizada pelo próprio sistema operacional para execução e gerenciamento de suas tarefas. www.esab.edu.br 44 5.2 Uso da memória Basicamente o uso da memória pode ocorrer de forma estática ou dinâmica. A forma mais comum de definição do espaço de memória se dá pela declaração de variáveis globais, sendo uma alocação estática, e o espaço alocado se mantém o mesmo durante toda execução do programa. As variáveis globais são declaradas no programa e acessíveis a todos os demais blocos de programas, sejam eles funções ou procedimentos. Outra forma muito utilizada são as variáveis locais, declaradas dentro dos blocos de memória e visíveis somente a esses blocos. Nesse caso, as demais funções e procedimentos, bem como o próprio programa de computador, não têm acesso a essas variáveis já que são criadas durante a execução do bloco e liberadas após seu encerramento. Como essas variáveis são criadas em tempo de execução do método, são alocações dinâmicas. Uma nova forma de alocação e uso de memória é definida pelo próprio programador por meio de instruções específicas que alocam ou liberam um espaço de memória, existente na maioria das linguagens de programação. Assim como as variáveis locais e globais usam do espaço de memória, todos os comandos, funções, procedimentos, ou seja, as estruturas de dados utilizadas no programa também ocupam um espaço da memória principal do computador. Quando um programa de computador é executado, um espaço de memória é alocado para o código do programa, para as variáveis globais e variáveis que serão alocadas dinamicamente. Caso o programa necessite de mais espaço do que há disponível pelo sistema operacional, o programa é encerrado. A Figura 16 representa a distribuição de memória principal por parte do sistema operacional para que seja possível a execução do programa, observe: www.esab.edu.br 45 Variáveis alocadas dinamicamente Variáveis globais Código do programa Figura 16 – Uso da memória. Fonte: Elaborada pelo autor (2013). É possível notar na Figura 16 que o espaço de memória destinado às variáveis locais e código do programa é fixo, uma vez alocado, não é mais disponibilizado até que o programa se encerre. Já o espaço de memória para alocações dinâmicas é flexível, seu espaço pode aumentar ou diminuir em função das variáveis que são criadas ou destruídas durante a execução do programa. A estrutura de dados representada na Figura 16 representa uma pilha, as variáveis são inseridas e removidas pelo topo, de forma que o último elemento inserido é sempre o primeiro a ser removido. A alocação da memória para armazenamento do código do programa e variáveis globais é estática, o tamanho não varia durante a execução do programa, já o espaço ocupado pelas variáveis globais é flexível. Depende das operações de alocação e liberação de memória que serão executadas durante o programa, na chamada de funções e procedimentos com variáveis locais e procedimentos ou nas instruções explícitas definidas pelo programador requisitando ou liberando espaços de memória. Observe que as variáveis estáticas são alocadas antes da execução do programa durante a carga do programa na memória principal, por isso sua alocação é mais simples, em blocos de memória contíguos e únicos. Já as variáveis dinâmicas são alocadas em tempo de execução à medida que o programa requisita mais espaço ou as desaloca. Quando um espaço de memória é requisitado dinamicamente, o sistema operacional procura por um espaço disponível no tamanho do bloco requisitado em toda a memória. www.esab.edu.br 46 5.3 Alocação contígua e alocação dispersa Quando uma variável estática é alocada na memória pelo sistema operacional os blocos são alocados contiguamente, ou seja, um bloco do lado do outro, de forma organizada. Um vetor com oito posições, por exemplo, terá os oito blocos de memória que o constituem alocados um ao lado do outro, facilitando dessa forma o acesso e manipulação da informação. A Figura 17 apresenta a alocação do vetor de dez posições, sendo uma alocação estática, veja: Memória vendas Figura 17 – Alocação estática de vetor. Fonte: Elaborada pelo autor (2013). Isso é possível, pois o sistema operacional pode alocar o espaço todo antes mesmo da execução do programa, já que cada item do vetor possui o mesmo espaço de memória. Por exemplo, se for um vetor de “char”, cada posição do vetor ocupa 1 byte, resultando em um bloco total de 8 bytes. Na alocação dinâmica, o espaço só é conhecido em tempo de execução quando, explicitamente ou por uso de variáveis locais, as variáveis são criadas e liberadas, e cada bloco é alocado dispersamente, como pode ser observado na Figura 18. www.esab.edu.br 47 Memória Figura 18 – Alocação dinâmica de vetor. Fonte: Elaborada pelo autor (2013). Observe que cada posição do vetor está sendo alocada à medida que uma nova posição se faz necessária. Caso se desejasse alocar todo o vetor dinamicamente, ou seja, alocar todas as posições no mesmo momento, o resultado seria o mesmo da Figura 17, um único bloco de memória alocado durante a execução do programa, com a vantagem que a quantidade de posições é conhecida em tempo de execução. Como se trata de uma alocação dinâmica, de cada posição do vetor, e não da estrutura toda, o vetor alocou quatro elementos, podendo ter mais ou menos elementos em tempo de execução. Como a alocação não é contígua, exige que cada elemento alocado saiba qual o seu próximo elemento, pelo seu endereço na memória, observe na Figura 19. Memória e1 e5 e10 e15 Figura 19 – Alocação Dinâmica de Estruturas de Dados com encadeamento. Fonte: Elaborada pelo autor (2013). www.esab.edu.br 48 Cada elemento possui um endereço de alocação na memória, além disso, é armazenado
Compartilhar