2 SUMÁRIO. 4 Tipos Abstratos de Dados Técnicas de Programação Top-down e Bottom-up Tipos Compostos Heterogêneos (Estruturas)...

Tamanho: px
Começar a partir da página:

Download "2 SUMÁRIO. 4 Tipos Abstratos de Dados 109 4.1 Técnicas de Programação Top-down e Bottom-up... 109 4.2 Tipos Compostos Heterogêneos (Estruturas)..."

Transcrição

1 Sumário 1 Introdução Histórico Arquitetura de Computadores Memória Processador Algoritmos e Programas Técnica de Desenvolvimento de Programas Partes de um Programa Tradução de Programas Compilação Interpretação Resumo Exercícios Propostos Conceitos Básicos Variáveis e Células de Memória Identificadores Comando de Atribuição Tipos de Dados Declaração de Variáveis Tipo Inteiro Tipo Ponto Flutuante Tipo Booleano Tipo Caractere Conversão de Tipos Constantes Expressões Expressões Aritméticas Expressões Relacionais Expressões Lógicas Comando de Entrada de Dados

2 2 SUMÁRIO 2.8 Comando de Saída de Dados Comandos de Seleção Comando de seleção simples Comando de seleção dupla Comando de seleção múltipla Comandos de Repetição Comando de repetição com pré-condição Comando de repetição com pós-condição Comando de repetição condensado Problema dos Lotes Encaixantes Exercícios Resolvidos Resumo Exercícios Propostos Modularização Introdução Subprogramas Partes de um Subprograma Cabeçalho Dicionário de dados Corpo Comentários Chamada de subprogramas Passagem de parâmetros Retorno de dados Encerramento antecipado de execução Funções sem lista de parâmetros Funções sem retorno de dados Recursividade Implementação não recursiva equivalente Exercícios Resolvidos Resumo Exercícios Propostos Trabalhos Sugeridos Tipos Abstratos de Dados Técnicas de Programação Top-down e Bottom-up Tipos Compostos Heterogêneos (Estruturas) Definição Uso Tipos Abstratos de Dados Definição

3 SUMÁRIO Definição de Atributos de um TAD Definição de Operações de um TAD Uso do TAD Tipos de TADs Exercícios Resolvidos Resumo Lista de Exercícios Trabalhos Sugeridos Vetores Vetores e sua importância Representação Definição Definição do Tamanho do Vetor Operações Acesso indevido Strings TAD Implementacional Atributos Operações Exercícios Resolvidos Resumo Lista de Exercícios Trabalhos Sugeridos Matrizes Introdução Definição e Acesso Definição Acesso O TAD implementacional tmatriz Atributos Operações Exercícios Resolvidos Resumo Exercícios Propostos Trabalhos Sugeridos Tópicos Avançados

4 4 SUMÁRIO 7 Apontadores Variáveis Apontadores A Sintaxe dos Apontadores Operador Endereço de Memória O Operador Seta Acesso à Variável por Meio de Apontadores Uso de Apontadores nas Passagens de Parâmetros Alocação Dinâmica de Memória Problemas Gerados por Apontadores Apontadores Não Inicializados Objetos Pendentes Referência Pendente Programação Macarrônica TAD Implementacional Lista Encadeada tlista Definição do Tipo tno Atributos de tlista Operações de tlista Uso Exercícios Resolvidos Resumo Lista de Exercícios Trabalhos Sugeridos Arquivos Variáveis Transientes X Variáveis Persistentes Tipos de Arquivos Tipos de Arquivos - Arquivos Texto Tipos de Arquivos - Arquivos Binários Definição de arquivos Operação sobre arquivos Abertura Fechamento Operações sobre arquivos texto Leitura Escrita Operações sobre arquivos binários Leitura Escrita Outras funções úteis para arquivos feof() fseek() Exercicios Resolvidos

5 SUMÁRIO O Tipo Abstrato de Dados TDicionario Resumo Exercícios Trabalhos Sugeridos

6 Capítulo 1 Introdução Co-autor: André Boechat Objetivos: Apresentar um breve histórico da Computação; Apresentar noções sobre arquitetura de computadores, como funcionam o processador e a memória; Definir o que são algoritmos e programas, além de apresentar algumas técnicas para desenvolvê-los; Definir as partes de um programa e a importância da documentação; Introduzir o conceito de tradução de programas. Basicamente, pode-se considerar a programação de computadores como a forma que se dá o relacionamento entre a máquina e o homem. Para programar de forma correta e eficiente, muitas vezes não basta conhecer apenas os comandos de determinadas operações, é necessário saber como a máquina, um ser não pensante, faz para compreender e executá-los. Assim, este capítulo visa apresentar alguns conceitos fundamentais para o entendimento da programação, desenvolvidos juntamente com a área mais recente da ciência, a Computação. 1.1 Histórico Dado o alto grau tecnológico dos computadores atuais, pode ser difícil imaginar que os primeiros computadores eram totalmente mecânicos. Diversos tipos foram projetados e construídos ao longo da evolução, chegando aos modernos computadores digitais; porém, alguns se destacam pela inovação e complexidade que marcaram suas épocas. 6

7 1.1. HISTÓRICO 7 A primeira máquina programável que se tem notícia foi construída pelo professor de matemática da Universidade de Cambridge, Charles Babbage ( ). A máquina analítica, como ficou conhecida, era totalmente mecânica, composta basicamente por engrenagens que formavam quatro componentes: a memória, a unidade de cálculo ou computação, a unidade de entrada e a de saída. A unidade de computação recebia operandos da memória para realizar sobre eles as operações de soma, subtração, multiplicação ou divisão, e depois armazenar o resultado na memória. Como a máquina analítica executava as instruções lidas pela unidade de entrada, era possível executar diferentes seqüências de cálculos, bastando para isso que um programa diferente fosse utilizado. Assim, a primeira pessoa no mundo a programar um computador foi a jovem Ada Augusta Lovelace, contratada pelo próprio Babbage a fim de produzir o software necessário para o funcionamento da máquina. A procura por máquinas calculadoras cresceu com a Segunda Guerra Mundial, estimulando o surgimento dos primeiros computadores eletrônicos. O professor de física da Universidade da Pensilvânia, John Mauchley, junto com seu aluno de mestrado, J. Presper Eckert, construiu um computador chamado ENIAC (Electronic Numerical Integrator And Computer)[2], o qual detinava-se ao cômputo de trajetórias táticas que exigissem conhecimento substancial em matemática. O ENIAC tinha válvulas e relés, pesava 30 toneladas e consumia 140 quilowatts de energia elétrica. Para programar o ENIAC, era necessário ajustar a posição de chaves de várias posições e conectar um número imenso de soquetes por meio de uma verdadeira floresta de cabos. Este gigantesco computador só ficou pronto em 1946, após o término da guerra. Um dos pesquisadores envolvidos no projeto do ENIAC, John von Neumann, construiu para o Instituto de Estudos Avançado de Princeton (Princeton Institute of Advanced Studies IAS) a máquina IAS[3], a qual ainda é a base de praticamente todas as máquina atuais. Ele imaginou que os programas poderiam ser representados em formato digital na memória, junto com os dados. A invenção do transístor e o desenvolvimento de circuitos integrados revolucionaram os projetos de computadores do final da década de 1950, tornando obsoletos os computadores valvulados. Nas décadas de 1960 e 1970, as famílias de computadores da IBM 1 (International Business Machines), System/360, e da DEC 2 (Digital Equipament Corporation), PDP-11, dominavam o mercado. Nesse período surgiu uma das linguagens de programação mais usadas para o desenvolvimento de softwares e também adotada neste livro, a linguagem C[1]. Ela é, ainda hoje, muito utilizada para a criação de programas diversos, como processadores de texto, planilhas eletrônicas, programas para a solução de problemas de engenharia, e muitos outros. Diversos fatores, como a criação de linguagens de programação mais semelhantes à linguagem humana 3, o que facilitava a vida dos programadores, e a integração de circuitos em escala muito alta, o que aumentou o desempenho e diminuíu o tamanho das máquinas, deram início a era Essas linguagens são chamadas de linguagens de alto nível

8 8 CAPÍTULO 1. INTRODUÇÃO dos computadores pessoais. E foi nesse contexto, na década de 80, que a IBM construiu o computador mais vendido de toda a história, o Personal Computer o famoso PC. O processador utilizado no PC foi construído por uma promissora empresa da época, a Intel 4. E a versão inicial desse computador vinha com o sistema operacional MS-DOS fornecido por outra empresa, a recém-criada Microsoft Corporation. 1.2 Arquitetura de Computadores Para conhecer um pouco mais sobre o funcionamento dos computadores, é apresentada nesta Seção uma noção básica sobre processadores e memórias Memória A memória é a parte do computador onde as operações a serem executadas pelo computador (instruções) e as informações a serem processadas pelo mesmo (dados) são armazenados. Na memória, o processador lê e escreve informações ao executar as instruções de um programa. Existem dois tipos de memória: a memória principal e a secundária. A principal, conhecida também como RAM (Random Access Memory), possui as seguintes características: Armazena dados e instruções do programa em execução; Proporciona ao computador acesso rápido aos dados e instruções armazenados por ela; Só mantem as informações armazenadas enquanto o computador estiver ligado. A memória secundária geralmente possui maior capacidade de armazenamento de dados e instruções, porém o tempo necessário para acessá-los é bem maior quando comparado ao da memória principal. As informações permanecem armazenadas mesmo após o desligamento do computador. Discos rígidos e CD-ROMs são exemplos de memórias secundárias. A unidade básica da memória é o digito binário, conhecido como bit. Um bit pode assumir apenas dois valores, normalmente 0 ou 1. A memória é formada por um conjunto de células, ou posições, sendo que cada uma pode guardar uma informação e possui um número de reconhecimento, conhecido como endereço de memória. É por meio desse número de endereço que os programas podem acessar uma determinada célula. Se uma célula possuir k bits, ela poderá armazenar qualquer uma das 2 k combinações possíveis para os bits. A Figura 1.1 mostra um exemplo de memória com seis células endereçáveis, onde cada célula possui dezesseis bits Processador O processador é o cérebro do computador. Basicamente, o processador é responsável por buscar instruções na memória, decodificá-las para determinar seus operandos (dados que serão 4

9 1.3. ALGORITMOS E PROGRAMAS 9 Figura 1.1: Exemplo de uma memória de 16 bits e 6 endereços. usados ao processar a instrução) e quais operações devem ser realizadas com os mesmos, e executar tais operações. Essas tarefas compõem o processo de execução de um programa. 1.3 Algoritmos e Programas Apesar do nome pouco comum, diversos algoritmos são executados por pessoas comuns todos os dias. Ao escovar os dentes, fazer um bolo ou trocar o pneu de um carro, diversos procedimentos são feitos em seqüência, seguindo, muitas vezes, uma certa ordem lógica. Para exemplificar a execução de um algoritmo, descreve-se a seguir alguns passos necessários para fazer algo simples, cotidiano, como o ato de escovar os dentes. 1. Pegar a escova e a pasta de dentes; 2. Colocar um pouco de pasta sobre as cerdas da escova; 3. Escovar os dentes do maxilar inferior; 4. Escovar os dentes do maxilar superior; 5. Expelir da boca o excesso de espuma; 6. Bochechar um pouco de água; 7. Lavar a escova e guardá-la; 8. Enxugar o rosto. Após conhecer um exemplo comum de algoritmo, torna-se mais fácil compreender a sua definição: Algoritmo é uma seqüência de operações que deve ser executada em uma ordem definida e não-ambígua, com o propósito de solucionar um determinado problema.

10 10 CAPÍTULO 1. INTRODUÇÃO Apesar de conseguirem executar operações muito complexas, os computadores não são dotados da mesma capacidade de compreensão que os humanos. Uma seqüência de tarefas considerada simples e óbvia para uma pessoa qualquer pode ser incompreensível e pouco detalhada para um computador. Voltando ao exemplo da escovação, um computador poderia considerá-lo incompleto, pois há diversas questões sobre como algum passo pode ser executado, por exemplo: 1. Onde está a escova a ser usada? 2. Quanto, exatamente, se deve colocar de pasta sobre as cerdas? 3. O que fazer se não houver pasta? Escovar mesmo sem a pasta ou interromper o processo de escovação? Questões como essas são facilmente contornadas por um humano, decididas instantâneamente de acordo com o ambiente que o cerca. Então, para que uma máquina também possa decidir o que fazer em situações semelhantes, é necessário que se estabeleçam as condições iniciais (condições de entrada) e as finais (condições de saída) do problema. Dessa forma, ela saberá quais ferramentas poderão ser usadas para atingir a condição de saída desejada. Assim, o problema da escovação seria mais bem definido da seguinte maneira: Condições de Entrada: Dentes sujos com restos de alimentos, uma escova dental em condições de uso, 90 gramas de creme dental e 300 mililitros de água tratada. Condições de Saída: Dentes limpos (sem restos de alimentos visíveis), uma escova dental em condições de uso e 85 gramas de creme dental. Toda a quantidade de água deve ser utilizada. Portanto, para um computador, os algoritmos definem o conjunto de atividades que ele deve desempenhar para solucionar um problema. Contudo, tão importante quanto saber o que escrever para a máquina é saber como escrever. Para que um computador possa executar um algoritmo, é necessário que esse algoritmo seja traduzido para uma linguagem de programação, geralmente incompreensível para a maioria das pessoas. Ao se traduzir um algoritmo para uma linguagem de programação, obtém-se um programa. Embora os computadores modernos sejam capazes de executar operações difíceis e complexas, eles são máquinas dóceis e de uma inteligência restrita. Eles devem ser ensinados exatamente o que fazer, e as instruções devem ser feitas em uma linguagem simples, precisa e limitada que eles possam compreender. Estas instruções são mais conhecidas como programa (Darmell,1988).

11 1.4. TÉCNICA DE DESENVOLVIMENTO DE PROGRAMAS Técnica de Desenvolvimento de Programas Na maioria das vezes, o ato de programar não é uma tarefa simples e fácil. A sintaxe das linguagens de programação é bem rígida e deve ser respeitada fielmente para que o computador a compreenda. Esquecer de um ; no lugar onde é necessário, por exemplo, significa comprometer a execução de todo o programa (na verdade, o computador pode nem começar a executá-lo). Ou pior, um programa teoricamente correto, que não apresente erros de sintaxe, pode fornecer um resultado incorreto. Encontrar o erro torna-se um enorme problema à medida que o programa cresce em tamanho e complexidade. Para contornar a complexidade dos programas, os construtores de software costumam fazer uso de uma poderosa técnica de programação, conhecida como técnica dos refinamentos sucessivos 5. Ela consiste basicamente em dividir um processo complexo em processos-componentes menores, especificando apenas a entrada e a saída de cada um deles. E se, mesmo assim, alguns processos menores continuarem complexos, repete-se a divisão no interior dos processoscomponentes, gerando processos cada vez mais simplificados. Além de facilitar a depuração de erros, essa técnica pode tornar um complexo programa em uma união de programas menores e mais simples. A divisão também possibilita outra grande vantagem, o reúso de código, ou seja, o uso de um mesmo trecho de programa para realizar o mesmo tipo de tarefa em diferentes ocasiões dentro de um programa maior. Um exemplo prático do uso dos refinamentos sucessivos segue abaixo: = Algoritmo Escovação dentária : 1. Pegar a escova e a pasta de dentes; 2. Colocar um pouco de pasta sobre as cerdas da escova; 3. Escovar os dentes do maxilar inferior; 4. Escovar os dentes do maxilar superior; 5. Expelir da boca o excesso de espuma; 6. Bochechar um pouco de água; 7. Lavar a escova e guardá-la; 8. Enxugar o rosto. = Processo-componente pegar a escova e a pasta de dentes : 1. Enquanto não encontrar a escova e o tubo de pasta, continuar procurando por cada gaveta do armário; 5 Também conhecida como dividir para conquistar.

12 12 CAPÍTULO 1. INTRODUÇÃO 2. Caso tenham acabado as gavetas e não tenha encontrado a escova e o tubo, interromper a tarefa. = Processo-componente escovar os dentes do maxilar inferior : 1. Com as cerdas da escova na posição vertical, fazer movimentos de vai-e-vem na região superior dos dentes; 2. Com as cerdas na posição horizontal, escovar a região frontal dos dentes; 3. Com as cerdas na posição horizontal, escovar a região detrás dos dentes. = Processo-componente escovar a região detrás dos dentes : 1. Abrir bem a boca; 2. Afastar a língua da região a ser escovada; 3. Esfregar as cerdas da escova atrás dos dentes. O exemplo anterior mostra apenas parte do processo de refinamentos, que deve prosseguir até que cada atividade esteja suficientemente detalhada, possibilitando que o computador as reconheça e execute. 1.5 Partes de um Programa Como dito anteriormente, os programas podem ser comparados à diversas atividades do dia-adia de qualquer pessoa. Um exemplo é o de receitas culinárias. Geralmente, essas seguem uma determinada estrutura, como abaixo: Nome da receita; Ingredientes: descreve todo o material necessário para o preparo da receita; Modo de preparo: descreve a forma de trabalhar com os ingredientes para que se obtenha o resultado esperado; Comentários sobre certos procedimentos ou ingredientes a fim de detalhar alguma peculiaridade que o cozinheiro poderia não conhecer previamente. A estrutura de um bom programa segue um modelo semelhante. Basicamente, um programa deve conter quatro partes: Cabeçalho: contém informações sobre o programa, como o seu nome;

13 1.5. PARTES DE UM PROGRAMA 13 Dicionário de dados: define quais são os dados manipulados pelo programa; Corpo: define os procedimentos que o programa deve executar; Documentação: explica certos aspectos não muito claros do programa, tanto no corpo do programa quanto no cabeçalho ou no dicionário de dados. Um dos principais objetivos deste tipo de estrutura é aumentar a legibilidade do programa, ou seja, facilitar o entendimento dos diversos processos que o programa executa. Um programa deve valorizar a formatação, disposição física das linhas de código, para facilitar seu entendimento. Colocar cada comando em uma linha e inserir uma linha em branco entre blocos de comandos de finalidade comum são regras básicas para uma boa formatação. Os programas devem ser lidos e entendidos por quem os escreveu e por outras pessoas, uma vez que podem necessitar de correção, manutenção, modificação ou apenas de serem compreendidos por um simples usuário. Para ilustrar essa estrutura, apresenta-se a seguir um programa, Pseudocódigo 1.1, que efetua o cálculo das raízes reais de uma equação de segundo grau. Os dados de entrada do programa são os três coeficientes da equação e ele deverá apresentar ao usuário as raízes reais da mesma, caso existam. Pseudocódigo 1.1 Cálculo das raízes reais de uma equação de segundo grau. Descrição: programa que calcula as raízes reais de uma equação de segundo grau. Dados de Entrada: os três coeficientes da equação. Saída do Programa: raízes reais da equação. Leitura dos coeficientes da equaç~ao; Cálculo do discriminante (delta); Cálculo das raízes pelo processo componente "Cálculo das Raízes"; Utilizando a técnica dos refinamentos sucessivos, na última linha do Pseudocódigo 1.1 ocorre a execução do processo componente Cálculo das Raízes, que, a partir dos valores dos coeficientes lidos e do discriminante calculado, encontra os valores das raízes reais da equação. No geral, o processo componente Cálculo das Raízes deve conter os passos descritos no Pseudocódigo 1.2. Pseudocódigo 1.2 Processo componente para o cálculo das raízes. Processo-componente "Cálculo das Raízes": Verificar a exist^encia de raízes reais Se possui raízes reais: Exiba para o usuário os valores da raízes; Sen~ao: Exiba um alerta sobre a n~ao exist^encia de raízes reais; FIM-Processo componente "Cálculo das Raízes"

14 14 CAPÍTULO 1. INTRODUÇÃO No Exemplo 1.1, o programa para o cálculo das raízes está transcrito para a linguagem de programação C. Deve-se destacar que o texto escrito entre os símbolos /* e */ é sempre ignorado pelo computador, não alterando em nada o funcionamento do programa. Porém, esse texto serve para comentar o código e deixá-lo mais claro. Pode-se fazê-los também utilizando o símbolo //, que transforma em comentário tudo aquilo que estiver a sua direita, até o final da linha. 1 /* 2 Programa para cálculo das raízes de segundo grau. 3 Dados de entrada : os coeficientes a, b e c de uma equaç~ao 4 da forma ax ^2 + bx + c = 0 5 Dados de saída : imprime na tela as raízes reais da equaç~ao, caso existam. 6 Restriç~ao : N~ao se considera o zero como um possível valor de a. 7 */ 8 9 # include <math.h> 10 # include <stdio.h> main (){ 13 float a; // Coeficiente angular. 14 float b; // Coeficiente linear. 15 float c; // Termo independente. 16 float delta ; // Discriminante. 17 float raiz1 ; // A primeira raiz. 18 float raiz2 ; // A segunda raiz // Leitura dos coeficientes. 21 scanf ("%f %f %f",&a, &b, &c); // Cálculo do discriminante ( delta ). 24 delta = b* b - 4 * a * c; // Cálculo das raízes. 27 if( delta < 0){ 28 printf (" A equaç~ao n~ao possui raízes reais "); 29 } else { 30 raiz1 = (-b + sqrt ( delta )) / (2* a); 31 raiz2 = (-b - sqrt ( delta )) / (2* a); 32 printf ("As raízes da equaç~ao s~ao : %f e %f",raiz1, raiz2 ); 33 } 34 } Exemplo 1.1: Programa para o cálculo das raízes reais de uma equação de segundo grau. Na linguagem C, o cabeçalho dos programas sempre tem o nome main() e o início do corpo do programa é marcado pela chave {, assim como o final é marcado pela última chave }. As linhas 9 e 10 permitem o uso dos comandos sqrt e printf (o significado desses comandos é explicado no próximo capítulo).

15 1.6. TRADUÇÃO DE PROGRAMAS 15 De acordo com a estrutura de programa apresentada no início desta seção, as linhas 13 a 18 compõem o dicionário de dados e o restante compõe o corpo do programa. A documentação está inserida no programa em forma de comentários, como as linhas 1 a 7, 20, 23 e 26. Vale destacar a importância da disposição física das linhas de código para o fácil entendimento do programa. A hierarquização de elementos por meio de espaços em branco é chamada de indentação. O trecho de programa relacionado ao cálculo das raízes no Exemplo 1.1 demonstra como a indentação pode facilitar a visualização da mudança do fluxo do programa. Normalmente, linhas de código com mesma indentação são executadas de maneira contínua, uma após a outra. Uma mudança na indentação indica uma mudança nesse fluxo de execução do programa. No próximo capítulo são apresentadas diversas maneiras de mudar esse fluxo. 1.6 Tradução de Programas Apesar do que foi dito na Seção 1.3, para ser executado pelo computador, um programa ainda precisa ser traduzido para uma linguagem mais simples que as linguagens de alto nível: a linguagem de máquina. Chama-se de código fonte o arquivo que contém o conjunto de instruções escritas em uma determinada linguagem de programação, normalmente familiares ao programador. Já o arquivo que as contém traduzidas para linguagem de máquina é chamado de código objeto. Para efetuar essa tradução, é necessário aplicar um programa, ou conjunto de programas, que receba o código fonte e gere o código objeto. A seguir, descrevem-se dois métodos básicos para um programa tradutor efetuar essa tarefa: a compilação e a interpretação de um código fonte Compilação O processo de compilação efetua a tradução integral do programa fonte para o código de máquina. Uma vez traduzido, o programa em linguagem de máquina pode ser executado diretamente. A Figura 1.2 ilustra esse processo. A grande vantagem desse método de tradução é a rapidez na execução dos programas, pois não é necessário fazer qualquer tradução durante a execução. Porém, o código objeto gerado pela compilação é, geralmente, específico para um determinado tipo de arquitetura de computador. Assim, é necessário uma nova compilação do programa para cada tipo diferente de computador. A linguagem C, adotada neste livro, é um exemplo de linguagem de programação compilada Interpretação No processo de interpretação, cada instrução do código fonte é traduzida no instante imediatamente anterior à execução dela. Desse modo, em contraste com a compilação, a tradução e execução de programas interpretados podem ser vistas como um processo único (veja as Figuras 1.2 e 1.3).

16 16 CAPÍTULO 1. INTRODUÇÃO Figura 1.2: Método de compilação. Figura 1.3: Método de interpretação. Apesar de ser um processo mais lento quando comparado ao método anterior, a interpretação apresenta maior flexibilidade na construção de programas e facilidade de depuração de código. A presença do interpretador durante a execução permite, por exemplo, a execução de trechos de programas criados, alterados ou obtidos durante a própria execução. 1.7 Resumo O processador é o componente do computador responsável por executar os programas armazenados na memória. A memória é responsável por armazenar os programas e os dados. A unidade básica da memória é o bit. Algoritmo é uma seqüência de operações que deve ser executada em uma ordem definida e não-ambígua, com o propósito de solucionar um determinado problema. Ao traduzir-se um algoritmo para uma linguagem de programação, obtém-se um programa. A Técnica de Refinamentos Sucessivos consiste, basicamente, em dividir sucessivamente problemas complexos em diversos problemas menores e mais simples. Um programa bem estruturado deve conter as seguintes partes bem definidas: cabeçalho, dicionário de dados, corpo e documentação.

17 1.8. EXERCÍCIOS PROPOSTOS 17 Basicamente, existem dois mecanismos de tradução de programas para linguagem de máquina: compilação e interpretação. Na compilação, o código fonte é totalmente traduzido antes de ser executado. Já na interpretação, a tradução do código fonte e a execução do programa ocorrem quase simultaneamente, como um processo único. 1.8 Exercícios Propostos 1. Qual é o papel do processador? 2. Qual é o papel da memória? 3. Considere uma memória que possua células de 8 bits cada uma. Qual o número de possíveis informações diferentes cada célula pode armazenar? 4. Utilizando o conceito de algoritmo apresentado no capítulo, descreva, através de um algoritmo, o ato de trocar um pneu furado de um carro. Não se esqueça de estabelecer as condições iniciais do problema (por exemplo, carro com um pneu dianteiro furado, se a chave de roda está no carro, se o carro está parado em uma ladeira, etc) e as condições de saída (por exemplo, carro parado e frenado, etc). 5. Use a Técnica de Refinamentos Sucessivos para detalhar as seguintes atividades: (a) Montagem de uma barraca de camping; (b) Preparação de um bolo de aniversário; (c) Cálculo da distância entre dois pontos no plano cartesiano.

18 Capítulo 2 Conceitos Básicos Co-autores: André Boechat Bruno Pandolfi Objetivos: Apresentar o conceito de variável; Estudar os principais tipos de dados básicos e expressões válidas; Mostrar como é feita a definição de variáveis e constantes; Apresentar os comandos elementares usados em programas. A idéia de resolver problemas formalizando a solução através do emprego de algoritmos é a espinha dorsal da programação procedural, na qual as tarefas são prescritas ao computador por meio de uma seqüência de operações básicas. Este capítulo trata do estudo dos conceitos e operações elementares da programação, fornecendo uma base sólida que, mais adiante, serve para construir algoritmos progressivamente mais complexos e resolver problemas cada vez mais elaborados. 2.1 Variáveis e Células de Memória Qualquer algoritmo tem por finalidade produzir algum resultado útil para compor a solução de um problema. Os algoritmos computacionais, em sua maioria, operam sobre um conjunto de dados para produzir novos resultados de acordo com a necessidade da tarefa em questão. Quando corretamente transcritos para uma linguagem de programação, os algoritmos são chamados de programas e podem ser executados pelo computador. É necessário, todavia, armazenar as informações utilizadas pelos programas em um local organizado e seguro, para que as consultas e alterações sejam efetuadas de maneira coerente e livre de erros. Os computadores utilizam a memória para armazenar os dados de um programa em execução. 18

19 2.1. VARIÁVEIS E CÉLULAS DE MEMÓRIA 19 Como dito na Seção 1.2.1, a memória pode ser entendida como uma seqüência de células, cada célula podendo armazenar uma porção dos dados de um programa. Graças a essa ordenação seqüencial, cada célula possui um número identificador chamado endereço, que está relacionado com a posição que a célula ocupa na seqüência. Por meio dos endereços é possível acessar qualquer célula para ler ou alterar seu conteúdo. A Figura 2.1 esboça essa idéia, porém, em contraste com a Figura 1.1, omite a representação dos bits. Figura 2.1: Modelo para a estrutura da memória do computador, mostrando uma fração de seis células, seus endereços e conteúdos. As seis células de memória da Figura 2.1 podem guardar valores numéricos ou letras em seus interiores. Cada célula é apontada por um endereço único e seqüencial. A estrutura da memória favorece a execução de suas duas operações básicas: leitura e escrita. A leitura é o processo de consulta do conteúdo da célula. Essa operação não altera o valor guardado na célula. A escrita é o processo de armazenamento de um novo dado na célula indicada por um endereço conhecido. Após a execução da escrita, o valor que estava anteriormente armazenado na célula é perdido. Um algoritmo para o cálculo do valor da conta mensal de energia elétrica é um bom exemplo para compreender as operações básicas da memória. Os dados de entrada desse algoritmo são: a leitura atual do medidor, a leitura registrada no mês anterior e o preço do quilowatt-hora (kwh) unidade comercial de energia elétrica. Na prática, o que se faz para calcular o valor da conta é encontrar a diferença entre a leitura atual do medidor e a leitura do mês anterior e multiplicar esse valor pelo preço do quilowatt-hora. O algoritmo computacional, por sua vez, é descrito no Pseudocódigo 2.1. No Pseudocódigo 2.1, o computador lê os conteúdos das células 43 e 46 (que guardam os valores das leituras atual e anterior, respectivamente), realiza a subtração e o resultado é guardado na célula 41. Posteriormente, essa diferença é lida e seu valor multiplicado pelo conteúdo da célula 44, que guarda o valor do preço unitário do kwh. Finalmente, o valor da conta é guardado na célula 42. Acompanhando a descrição acima é possível chegar à situação encontrada na Figura 2.2. Nos primórdios da programação, percebeu-se que acessar a memória por meio de endereços era trabalhoso demais e causa constante de erros. Isso porque o programador deveria escolher os endereços das células com as quais iria trabalhar, tanto das células que teriam valores a serem lidos quanto das que seriam usadas para a escrita de resultados. Essa situação confusa é observável no exemplo da conta de energia elétrica. Sem o comentário

20 20 CAPÍTULO 2. CONCEITOS BÁSICOS Pseudocódigo 2.1 Cálculo da conta de energia elétrica utilizando os endereços das células de memória. Descrição: programa que calcula o valor da conta de energia elétrica. Dados de Entrada: células 43 e 46 contendo, respectivamente, a leitura atual e a anterior; e valor unitário do kwh na célula 44. Saída do Programa: valor da conta, armazenada na célula 42. Subtrair o conteúdo da célula 43 do conteúdo da célula 46 e guardar na célula 41; Multiplicar o conteúdo da célula 41 pelo conteúdo da célula 44 e guardar na célula 42; Figura 2.2: Conteúdo da memória após execução do algoritmo que calcula o valor de uma conta de energia elétrica. no início do Pseudocódigo 2.1, seria impossível ter a mínima idéia do que o programa faz em cada passo. Isso torna difícil não só a escrita, mas também a correção dos programas. Para resolver essa questão, o conceito de variável foi criado. Uma variável nada mais é do que uma abstração para o endereço de memória. Com o emprego de variáveis, as células de memória são referenciadas nos programas por meio de rótulos, definidos com ajuda do bomsenso do programador. O compilador fica encarregado do trabalho de transformar rótulos em endereços para que as operações de acesso à memória sejam realizadas. O Pseudocódigo 2.2 mostra como fica o programa que calcula o valor da conta de energia elétrica, agora escrito utilizando variáveis. Pseudocódigo 2.2 Cálculo da conta de energia elétrica utilizando variáveis. Descrição: programa que calcula o valor da conta de energia elétrica. Dados de Entrada: a leitura atual, a anterior e o preço unitário do kwh. Saída do Programa: valor da conta. Subtrair o conteúdo da variável leituraatual do conteúdo da variável leituraanterior e guardar na variável diferenca ; Multiplicar o conteúdo da variável diferenca pelo conteúdo da variável valorunitario e guardar na variável valorconta ; A partir do Pseudocódigo 2.2, um compilador pode converter os rótulos para posições quaisquer na memória, evitando que o programador tenha que se preocupar em manipulá-las. Uma

21 2.2. IDENTIFICADORES 21 Figura 2.3: Uma possível tradução para endereços de memória, a partir dos rótulos do código que utiliza variáveis. possível tradução é exibida na Figura 2.3. Como visto nos exemplos desta seção, o uso de variáveis em um algoritmo reduz a quantidade de comentários explicativos, pois sua leitura é mais simples e seu entendimento é mais fácil. Em caso de erros, a correção do programa é muito mais rápida e eficiente. Apresentados os conceitos de variável e células de memória, é válido abordar um outro significado de programa. Trata-se de considerar os programas como sendo processos de mudança de estados. Nessa abordagem, os dados de entrada, escritos em suas respectivas variáveis, são considerados o estado inicial do programa. A partir daí, realiza-se uma seqüência de operações para chegar a um estado final. A cada operação, diz-se que o programa está em um novo estado intermediário. O estado final é atingido quando a tarefa em questão é considerada como realizada. A transição do estado inicial para o estado final pode ser efetuada de diversas maneiras. Conforme pode ser visto mais adiante neste capítulo, é possível escolher entre duas ou mais seqüências de comandos, bem como repeti-las, de acordo com o estado intermediário em que o programa se encontra. 2.2 Identificadores Em geral, as linguagens de alto nível possuem dois tipos de elementos: os elementos definidos pela própria linguagem símbolos para operadores, nome de comandos etc e os elementos definidos pelo programador identificadores, comentários etc. Um identificador é um símbolo que pode representar alguma entidade criada pelo programador, como uma variável, por exemplo. Cada linguagem define uma regra para formação de identificadores. Em geral, sempre é possível utilizar uma seqüência de caracteres alfanuméricos letras ou dígitos, sem acentos e sem cedilha sendo que o primeiro caractere deve ser obrigatoriamente alfabético. Os Exemplos 2.1 e 2.2 apresentam, respectivamente, nomes corretos e nomes inválidos de variáveis na linguagem C. Algumas linguagens, como C, fazem diferenciação entre letras maiúsculas e minúsculas. Desta forma, uma variável de nome Saldo é considerada diferente de outra de nome saldo, ou, ainda, de nome SALDO. É importante ressalvar que não é uma boa prática de programação criar identificadores que apenas se diferenciem pelo formato das letras, como no exemplo do saldo, pois isso tem impacto negativo na legibilidade dos programas.

22 22 CAPÍTULO 2. CONCEITOS BÁSICOS 1 abc 2 x1 3 y2 4 letra 5 SOMA_ TOTAL 6 B_32 Exemplo 2.1: Nomes válidos de variáveis, concordando com as regras de nomenclatura. 1 fim? //? n~ao é um caractere alfanumérico 2 % percentual % // % n~ao é um caractere alfanumérico quatro // Iniciado por número 4! hola! //! n~ao é um caracter alfanumérico n~ao é um caractere alfanumérico Exemplo 2.2: Nomes inválidos de variáveis. Normalmente, em grandes projetos de software, são adotados padrões para a escrita dos identificadores a fim de que os programadores possam trocar seus códigos, entendê-los e alterálos sem grande dificuldade. Neste texto, é adotado como regra na escrita: Nomes simples: começados com letra minúscula e demais caracteres minúsculos; Nomes compostos: primeira parte é iniciada por letra minúscula e as demais partes iniciadas por letra maiúscula. Os demais caracteres são minúsculos. Como dito na Seção 1.5, um bom programa deve necessariamente ser legível e de fácil compreensão. A escolha dos nomes dos identificadores influenciam diretamente esses dois aspectos. Logo, é muito importante que os nomes sejam significativos, deixando bem clara a sua referência. A fim de contrastar com a declaração de variáveis do Exemplo 2.1, o Exemplo 2.3 ilustra a convenção proposta, apresentando nomes significativos para variáveis. Neste, o rótulo das variáveis já é suficiente para informar a funcionalidade das mesmas, tornando rápida a compreensão do programa que as utiliza e dispensando a necessidade de comentários. 1 delta 2 raiz1 3 idade 4 letra 5 percentualdelucro 6 primeiraletra 7 indicebovespa Exemplo 2.3: Nomes significativos para variáveis.

23 2.3. COMANDO DE ATRIBUIÇÃO Comando de Atribuição Os pseudocódigos da Seção 2.1 não mostram como os conteúdos das variáveis de entrada são alterados para que o cálculo seja efetuado coerentemente. Em outras palavras, não se mostra como as variáveis leituraatual, leituraanterior e valorunitario são inicializadas. O responsável pela alteração dessas variáveis, assim como de qualquer outra, é o comando de atribuição. O comando de atribuição é muito importante e, com certeza, é o mais utilizado. É por meio dele que as variáveis podem ter seus valores (conteúdos) alterados. Por isso, o comando de atribuição está diretamente ligado às operações de memória. No Exemplo 2.4 é transcrito o algoritmo do cálculo da conta de energia elétrica para o código de um programa na linguagem C. 1 main (){ 2 leituraatual = 125; 3 leituraanterior = 25; 4 valorunitario = 2; 5 diferenca = leituraatual - leituraanterior ; 6 valorconta = diferenca * valorunitario ; 7 } Exemplo 2.4: Cálculo do valor da conta de energia elétrica. No Exemplo 2.4, o símbolo = representa o comando de atribuição. Em seu lado esquerdo sempre haverá a variável destino, na qual será escrito o resultado da expressão do lado direito do comando. As expressões mais simples são as que contêm valores constantes, tais como as que ocorrem nas linhas 1, 2 e 3 do Exemplo 2.4. Essas três linhas são omitidas propositalmente do Pseudocódigo 2.2 para torná-lo mais simples. Entretanto, elas não são difíceis de compreender, pois apenas informam ao computador quais valores ele deve manipular para encontrar o resultado esperado: o valor da conta mensal de energia elétrica. Dessa forma, o comando da linha 1 do Exemplo 2.4 simplesmente realiza a atribuição do valor 125 à variável leituraatual. A partir da efetivação desse comando, o conteúdo dessa variável pode ser usado para os cálculos. O mesmo vale para as demais variáveis. As linhas 4 e 5 do Exemplo 2.4 são transcrições diretas dos dois passos do algoritmo da conta de energia. O que ocorre de diferente é que o valor a ser atribuído às variáveis do lado esquerdo é primeiramente calculado na expressão para, em seguida, ser guardado na variável correspondente. A Figura 2.4 representa graficamente essa passagem. O comando de atribuição é executado sempre da mesma maneira pelo computador. Em primeiro lugar a expressão que está do lado direito do comando é calculada e, para isso, todas as variáveis que aparecem têm seus valores lidos e lançados na expressão. Após o fim dos cálculos na expressão, o computador escreve o resultado na variável destino, no lado esquerdo. Basicamente, a forma geral do comando de atribuição é a seguinte: <variável> = <express~ao>;

24 24 CAPÍTULO 2. CONCEITOS BÁSICOS Figura 2.4: Representação gráfica da linha 4 do Exemplo 2.4. É muito importante compreender a diferença significativa existente entre o comando de atribuição e a igualdade matemática. Em muitas linguagens de programação (C, por exemplo) a atribuição é também representada pelo símbolo =, o que pode provocar interpretações equivocadas. O Exemplo 2.5 mostra um trecho de programa que geralmente causa confusão para os programadores iniciantes. 1 contador = 10; 2 contador = contador + 1; Exemplo 2.5: Incremento de uma variável usando o comando de atribuição. No Exemplo 2.5 ocorrem duas atribuições. A primeira ocorre na linha 1, sendo mais um exemplo simples de inicialização de uma variável, como já discutido anteriormente. Na linha 2, a variável contador aparece em ambos os lados da expressão, o que pode parecer um absurdo matemático. Porém, é necessário lembrar que, nesse contexto, o símbolo = atribui o valor da expressão da direita à variável que aparece à esquerda. Ou seja, avalia-se primeiro o resultado da expressão à direita ( = 11) e, posteriormente, atribui-se o valor à variável à esquerda (contador = 11). Isso é representado graficamente na Figura 2.5. Figura 2.5: Representação gráfica do incremento de uma variável. Mais uma observação é necessária: O programador deve ficar atento aos tipos de dados manipulados em um comando de atribuição, pois o tipo da expressão à direita deve ser compatível com o tipo da variável à esquerda. A manipulação dos tipos de dados é detalhada na Seção 2.4.

25 2.4. TIPOS DE DADOS Tipos de Dados Um tipo de dados delimita o conjunto de valores possíveis que uma determinada variável pode representar. Além disso, define as operações básicas possíveis para suas variáveis. Sua necessidade decorre do fato de que as células de memória, sozinhas, conseguem representar apenas conjuntos de dados muito limitados. Entretanto, a maioria dos valores de interesse pertence a conjuntos extensos, os quais não podem ser representados com o uso de apenas uma célula de memória. Os programadores, então, passaram a utilizar múltiplas células para representar um único valor. Essa atitude resolveu a dificuldade de armazenar dados complexos, mas criou um outra questão: os processos de leitura e escrita para valores de múltiplas células, bem como as demais operações sobre tais valores, exigiam códigos que, além de não-triviais, fugiam do foco do problema principal. Não demorou para que as linguagens de programação incorporassem a definição dos tipos de dados e suas operações essenciais, poupando os programadores da tarefa de defini-los em seu código e tornando-o mais enxuto e legível. Tipos de dados, portanto, abstraem a forma de implementação e disponibilizam operações sobre os elementos do conjunto para que o programador possa utilizá-las sem ter que implementálas. Os tipos básicos mais importantes são o tipo inteiro, o ponto flutuante, o booleano e o tipo caractere. Cada linguagem de programação possui certas particularidades em relação aos tipos de dados. Primeiramente, é preciso abordar um conceito imprescindível da escrita de programas em linguagem C: a declaração de variáveis. Em seguida, são descritas as características mais comuns dos tipos básicos principais Declaração de Variáveis De acordo com o problema abordado, diferentes conjuntos de dados são necessários. Conforme visto, os tipos de dados representam os conjuntos de valores possíveis para suas variáveis, assim como as operações válidas sobre elas. Também já foram abordadas a necessidade e as vantagens do uso de variáveis em um programa. Entretanto, ainda não foi mencionado como as variáveis são criadas, ou seja, como o programador declara formalmente uma variável em seu código. Na linguagem C, o ato de declarar uma variável é simples e imprescindível. Afinal, sem esse procedimento, não é possível usá-la. Quando a declaração de variáveis é esquecida, uma mensagem de erro é apresentada ao programador, e a compilação do código é interrompida. Por isso, toda variável deve ser declarada antes de ser utilizada. A declaração de variáveis tem uma estrutura simples e algumas regras básicas. Em primeiro lugar, escreve-se o nome do tipo de dados ao qual a variável pertence. Em seguida, separado por um espaço em branco, escreve-se o identificador da variável. <tipo> <identificador>;

26 26 CAPÍTULO 2. CONCEITOS BÁSICOS É possível declarar mais de uma variável em uma mesma linha, desde que todas sejam do mesmo tipo, conforme o Exemplo float valorfracionado, saldo, salariominimo ; 2 char letra, ultimaletra ; 3 int entrada1, entrada2, entrada3 ; Exemplo 2.6: Declaração múltipla de variáveis em uma mesma linha de código Tipo Inteiro O tipo inteiro, como o próprio nome sugere, representa um intervalo finito do conjunto matemático dos números inteiros. De fato, uma linguagem de programação pode definir vários tipos inteiros, cada um com intervalo diferente. Na linguagem C, por exemplo, existem vários tipos inteiros, tais como o int, long int, unsigned int, short int. Neste livro, para bem da simplicidade, usa-se apenas o tipo int. O intervalo de valores para o tipo int depende do compilador que está sendo empregado. Assumimos aqui que o intervalo do tipo int sempre varia de entre a Em C, como na maioria das linguagens, as operações sobre as variáveis do tipo inteiro já estão implementadas e à disposição do programador. As operações disponíveis são as de soma, subtração, multiplicação, divisão e resto de divisão, conforme o Exemplo 2.7. O uso dessas funções é bastante simples e intuitivo, pois há até certa relação com os símbolos da escrita matemática. No Exemplo 2.7, as variáveis x e y são relacionadas entre si e os resultados são armazenados nas variáveis de a a f. Os operadores de soma e subtração funcionam tal qual na aritmética. O operador de subtração pode ser aplicado para um único valor quando se deseja inverter seu sinal. A linha 12 do Exemplo 2.7 mostra essa situação. Um destaque deve ser dado ao operador de multiplicação. Seu único símbolo é * (não há o símbolo ou o ). Além disso, em oposição ao que ocorre na aritmética, a escrita do * não pode ser omitida. Logo, a operação da linha 13 do Exemplo 2.7 geraria erro se não estivesse comentada, pois o compilador trataria xy como uma variável não declarada. 1 main (){ 2 int x, y, a, b, c, d, e, f; 3 4 x = 10; 5 y = 3; 6 7 a = x + y; // a armazena 13 8 b = x - y; // b armazena 7 9 c = x * y; // c armazena d = x / y; // d armazena 3 11 e = x % y; // e armazena 1

27 2.4. TIPOS DE DADOS f = -a; // f armazena // c = xy; 14 } Exemplo 2.7: Operações básicas com o tipo inteiro int. Também é importante destacar o resultado da operação de divisão. No caso do Exemplo 2.7, o resultado correto da divisão de x que armazena o valor 10 por y cujo valor é 3 é a dízima periódica 3, Entretanto, como as variáveis envolvidas na linha 10 são todas do tipo inteiro, o compilador trata de truncar o resultado, desconsiderando a parte decimal e fornecendo 3 como resultado da operação. Ainda no Exemplo 2.7, deve-se notar o uso do operador de resto de divisão inteira. Seu símbolo % deve ficar entre os valores do dividendo e do divisor. A linha 11 mostra a obtenção do resto da divisão de x por y. Após a execução dessa operação, a variável e guardará o valor 1, que é o resto da divisão de 10 por Tipo Ponto Flutuante Esse tipo de dados representa um intervalo dos números racionais. Assim como o tipo inteiro, há mais do que um intervalo possível, dependendo da precisão escolhida. Na linguagem C, os tipos de ponto flutuante são o float e o double. Neste livro, apenas o tipo float é empregado. Dependendo do compilador utilizado, os tipos de dados de ponto flutuante apresentam diferentes precisões. Aqui se considera que o tipo float provê seis casas decimais de precisão. As operações pré-definidas são as mesmas que as disponíveis para o tipo int, exceto, é claro, pela inexistência do operador de resto de divisão. O Exemplo 2.8 mostra o uso de variáveis do tipo float em um programa. 1 main (){ 2 float p, q, x, z; 3 int y; 4 5 x = 10; 6 y = 4; 7 8 p = 50 / 30; // p guardará 1 9 q = 10.0 / 4; // q guardará z = x / y; // z guardará } Exemplo 2.8: Operações com variáveis do tipo float. As operações entre variáveis do tipo float são escritas com os mesmos símbolos das operações do tipo int. O Exemplo 2.8 mostra particularidades do processo de compilação que podem gerar erros nos programas.

28 28 CAPÍTULO 2. CONCEITOS BÁSICOS A linha 8 do Exemplo 2.8 possui um comentário explicando que a variável p recebe 1 como resultado da operação de divisão. Tal erro não ocorre para a divisão da linha seguinte. A justificativa para essa situação é que, conforme é mostrado na Seção 2.3, o computador primeiro calcula o resultado da expressão do lado direito do comando de atribuição para, em seguida, escrever esse valor na correspondente variável de destino. Durante a compilação, o compilador checa os tipos das variáveis envolvidas nos cálculos para realizar as operações correspondentes. Entretanto, quando há apenas valores numéricos envolvidos, o compilador considera os números escritos sem ponto decimal como sendo do tipo inteiro. É por isso que a divisão realizada na linha 8 do Exemplo 2.8 resulta em 1. O computador entende a operação 50/30 como uma divisão inteira, descartando a parte decimal do resultado. Já na linha 9, o compilador enxerga o valor 10.0 como sendo do tipo float devido à presença explícita do ponto decimal, e realiza a divisão, resultando no valor esperado. No caso da última linha do Exemplo 2.8, nenhum resultado inesperado ocorre porque o tipo mais abrangente tipo float da variável x encontrado na expressão é considerado nas operações, garantindo o resultado esperado. Caso a variável x também fosse do tipo int, então o mesmo incoveniente da linha 8 ocorreria Tipo Booleano O tipo booleano é o tipo de dados mais simples. Ele contém apenas dois valores possíveis: verdadeiro e falso. É usado principalmente quando se precisa verificar condições no programa, em expressões lógicas (Seção 2.6.3) e relacionais (Seção 2.6.2). Na linguagem C não há uma representação específica para esse tipo de dados e são utilizados valores inteiros para codificá-lo. Desta forma, todo valor inteiro diferente de zero é considerado valor verdadeiro. O valor zero é considerado falso Tipo Caractere Como o próprio nome indica, o tipo caractere registra os códigos para letras, números e caracteres especiais ($, &, etc). Normalmente, esses códigos e caracteres variam para as diversas regiões do planeta, conforme suas particularidades linguísticas. Um padrão largamente utilizado atualmente, pricipalmente no Ocidente, é o ASCII (American Standard Code of Information Interchange Padrão Americano para Intercâmbio de Informações), especificado pelo ANSI (American National Standars Institute Instituto Americano Nacional de Padrões). O padrão ASCII é uma tabela. Cada posição da tabela simboliza um caractere. Na linguagem C, o tipo char é empregado para conter o índice para a posição da tabela que codifica um determinado caractere. Originalmente, essa tabela era composta por apenas 128 símbolos, os quais incluíam todos os caracteres alfanuméricos do Inglês, sinais de pontuação e alguns outros símbolos. Os primeiros 32 caracteres eram utilizados para controle de fluxo de dados (em comunicações entre o computador e seus periféricos) e não imprimíveis. Atualmente, esses caracteres caíram em desuso. A Tabela 2.1 mostra os 128 caracteres da tabela ASCII original.

29 2.4. TIPOS DE DADOS 29 Código Caractere Código Caractere Código Caractere Código Caractere 0 NUL (null) 32 SPACE 96 ` 1 SOH (start of heading) 33! 65 A 97 a 2 STX (start of text) B 98 b 3 ETX (end of text) 35 # 67 C 99 c 4 EOT (end of transmission) 36 $ 68 D 100 d 5 ENQ (enquiry) 37 % 69 E 101 e 6 ACK (acknowledge) 38 & 70 F 102 f 7 BEL (bell) G 103 g 8 BS (backspace) 40 ( 72 H 104 h 9 HT (horizontal tab) 41 ) 73 I 105 i 10 LF (NL line feed,new line) 42 * 74 J 106 j 11 VT (vertical tab) K 107 k 12 FF (NP from feed,new page) 44, 76 L 108 l 13 CR (carriage return) M 109 m 14 SO (shift out) N 110 n 15 SI (shift in) 47 / 79 O 111 o 16 DLE (data link escape) P 112 p 17 DC1 (device control 1) Q 113 q 18 DC2 (device control 2) R 114 r 19 DC3 (device control 3) S 115 s 20 DC4 (device control 4) T 116 t 21 NAK (negative acknowledge) U 117 u 22 SYN (synchronous idle) V 118 v 23 ETB (end of trans. block) W 119 w 24 CAN (cancel) X 120 x 25 EM (end of medium) Y 121 y 26 SUB (substitute) 58 : 90 Z 122 z 27 ESC (escape) 59 ; 91 [ 123 { 28 FS (file separator) 60 < 92 \ GS (group separator) 61 = 93 ] 125 } 30 RS (record separator) 62 > 94 ˆ US (unit separator) 63? DEL Tabela 2.1: Os 128 caracteres da tabela ASCII original. Com o passar do tempo, a tabela ASCII sofreu modificações. Ela foi atualizada e incorporou mais 128 novos símbolos, os quais incluem, por exemplo, os caracteres acentuados e o cedilha, do Português. Essa nova versão é conhecida como tabela ASCII estendida. Para fazer com que uma variável do tipo char aponte para um símbolo de interesse, o programador precisa apenas atribuir a essa variável o caractere desejado, colocando-o entre aspas simples. O Exemplo 2.9 exibe essa situação. 1 main (){ 2 char letraa, letrafmaiuscula, simbolosoma ; 3 4 letraa = a ;

30 30 CAPÍTULO 2. CONCEITOS BÁSICOS 5 letrafmaiuscula = F ; 6 simbolosoma = + ; 7 } Exemplo 2.9: Uso de variáveis do tipo caractere Conversão de Tipos Agora, conhecidas algumas informações sobre os tipos de dados básicos, torna-se mais simples compreender o problema existente na atribuição entre tipos diferentes, mencionada na Seção 2.3. Considera-se que os tipos int, float e char ocupam a quantidade de memória representada na Figura 2.6. Figura 2.6: Tamanho da memória utilizada pelos tipos de dados básicos. As diferenças nos tamanhos são justificáveis, uma vez que, quanto maior o conjunto a ser representado, mais células de memória são necessárias para acomodar uma variável. Desta forma, sendo o conjunto dos caracteres o menor, é preciso apenas um byte (uma célula) para representá-lo. Já o conjunto do tipo float, que é o maior dos três, precisa de 4 bytes (4 células) para uma representação coerente de seus valores. É preciso ter em mente que, devido a essas restrições de implementação, não se deve utilizar operações de atribuição entre variáveis sem tomar algum cuidado. Se elas forem de tipos diferentes, problemas podem acontecer. Não há problemas em atribuir uma variável do tipo int a outra do tipo float. Tal qual ocorre na matemática, toda variável do tipo int é possível de ser representada por outra do tipo float, pois o conjunto dos números racionais contém o conjunto dos inteiros. Um raciocínio similar pode ser empregado para justificar as atribuições de variáveis char a variáveis de tipo int ou float, que também ocorrem sem erros. O problema é quando se tenta atribuir uma variável de um conjunto mais amplo a uma de um conjunto mais restrito. Veja o Exemplo Observando o Exemplo 2.10, um programador distraído pode pensar que houve uma atribuição correta. Entretanto, o valor armazenado pela variável teste é 125. Não havendo condições de armazenar corretamente (um código de 4 bytes não pode ser escrito num espaço de apenas 2 bytes), o compilador simplesmente descarta a parte decimal do número, armazenando apenas

31 2.5. CONSTANTES 31 sua parte inteira na variável. Logo, é preciso permanecer atento às atribuições desse tipo para não se equivocar sobre o valor armazenado por uma variável. 1 main (){ 2 int teste ; 3 4 teste = ; 5 } Exemplo 2.10: Atribuição entre tipos diferentes com perda de informação. 2.5 Constantes Em algumas situações surge a necessidade de se utilizar um determinado valor constante em diversas partes do código. Para simplificar a alteração dessas constantes e facilitar a leitura do código, é possível definir um nome signicativo para elas de maneira simples, por meio de uma estrutura específica. Essa estrutura é iniciada pela diretiva #define, seguida pelo identificador da constante e pelo seu respectivo valor, todos separados por um espaço em branco. #define <identificador> <valor> Na linguagem C, a declaração de constantes deve ser feita no início do código, antes da função main. O Exemplo 2.11 apresenta declarações válidas de constantes. 1 # define PI # define FALSO 0 3 # define VERDADEIRO 1 4 # define RAIZDEDOIS main (){ 7 float raio, comprimento, area ; 8 9 raio = 10; 10 area = PI * raio * raio ; 11 comprimento = 2 * PI * raio ; 12 } Exemplo 2.11: Definição de constantes. Na declaração de constantes, a escolha dos nomes segue as mesmas regras para a escrita dos nomes de variáveis. Geralmente, para diferenciar as constantes no código, opta-se por escrever seus nomes com todas as letras maiúsculas. O Exemplo 2.11 mostra um exemplo simples onde diversas constantes são declaradas de acordo com a convenção proposta. Durante a compilação do código, o compilador troca tocas as ocorrências da constante PI por seu respectivo valor, definido na linha 1.

32 32 CAPÍTULO 2. CONCEITOS BÁSICOS 2.6 Expressões As variáveis e constantes podem ser combinadas com os operadores associados a cada tipo de dados, gerando expressões Expressões Aritméticas A expressão aritmética é aquela cujos operadores e operandos são de tipos numéricos (int ou float, por exemplo). Nas operações aritméticas, é guardada sempre a seguinte relação de prioridade: 1. Multiplicação, divisão e resto de divisão; 2. Adição e subtração. Para se obter uma seqüência diferente de cálculos, vários níveis de parênteses podem ser usados para quebrar as prioridades definidas. Não é permitido o uso de colchetes e chaves, uma vez que estes símbolos já são utilizados com outras finalidades. O Exemplo 2.12 exibe uma série de expressões aritméticas válidas. 1 main (){ 2 int x, y, z; 3 4 x = 10 * ; // x = y = 5 * (7 + 3); // y = 50 6 z = 10 * ((x - 25) / y + (x - 25) * 2); // z = } Exemplo 2.12: Expressões aritméticas e o uso de parênteses para determinar a precedência das operações. No Exemplo 2.12, é importante notar que, no comando de atribuição à variável x, a multiplicação é executada primeiro. Na linha seguinte, a soma é executada primeiro e seu valor é, em seguida, multiplicado por 5 e atribuído à variável y. A análise da linha 6 é deixada para o leitor. Além das operações básicas já citadas, é possível usar, nas expressões aritméticas, outras operações bastante comuns na matemática, definidas dentro da linguagem na forma de funções. A sintaxe geral para o uso dessas funções é a seguinte: <nome da funç~ao>(<valor>) Algumas funções matemáticas são mostradas na Tabela 2.2. O Exemplo 2.13 exibe vários usos de funções matemáticas e expressões aritméticas.

33 2.6. EXPRESSÕES 33 Nome abs fabs sin cos sqrt pow floor ceil log exp Descrição Módulo ou valor absoluto de um valor inteiro Módulo ou valor absoluto de um valor racional Seno de um ângulo em radianos Cosseno de um ângulo em radianos Raiz quadrada de um número Potenciação Maior inteiro não maior que o número de entrada Menor inteiro não menor que o número de entrada Logaritmo neperiano Exponencial Tabela 2.2: Principais funções matemáticas já definidas na linguagem C. 1 # include <math.h> 2 3 # define PI main (){ 6 float a, b, c, delta, raiz1, raiz2 ; 7 float x, y, z; 8 9 a = 10; 10 b = 50; 11 c = 30; delta = b * b - 4 * a * c; 14 raiz1 = -b + sqrt ( delta ) / (2 * a); 15 raiz2 = -b - sqrt ( delta ) / (2 * a); x = sin (-PI / 2); 18 y = fabs (x); 19 z = pow (y,2) ; // y elevado à pot^encia 2 20 } Exemplo 2.13: Uso de funções matemáticas pré-definidas. Para o uso das funções matemáticas apresentadas no Exemplo 2.13 é necessário a inclusão da biblioteca math.h, como na linha 1. Os mesmos cálculos para a procura das raízes de uma equação de segundo grau são executados nas linhas 13 a 15, onde a função sqrt calcula a raiz quadrada do seu operando a variável delta. Já as linhas 15 a 17 apresentam cálculos avulsos: a linha 17 executa o cálculo do seno de π/2 e armazena o resultado na variável x; a linha 18 armazena, na variável y, o módulo do valor armazenado em x; e a linha 19 calcula o valor armazenado em y elevado à potência de 2 e armazena o resultado na variável z.

34 34 CAPÍTULO 2. CONCEITOS BÁSICOS Expressões Relacionais As expressões relacionais comparam valores entre si. Tal qual se faz na prática, o operador relacional trabalha com dois valores: o da expressão que está a sua direita e o da expressão que está a sua esquerda. É importante destacar que uma variável ou uma constante isolada, por si só, pode ser considerada uma expressão. A Tabela 2.3 mostra exemplos de operadores relacionais da linguagem C e alguns casos de uso. Símbolo Nome Exemplo < menor que a < 10 <= menor ou igual a x <= y == igual a 4 == 2*2 > maior que t > 0 >= maior ou igual a delta >= 0!= diferente de x!= 9+8*8 Tabela 2.3: Operadores relacionais. As expressões relacionais resultam sempre em 0 (zero) para uma comparação falsa ou 1 para uma comparação verdadeira. As variáveis do tipo char também podem ser comparadas entre si, respeitando a ordenação do padrão de codificação utilizado. É importante atentar que, embora sejam escritos de maneira parecida, há uma grande diferença entre o operador de comparação == e o comando de atribuição =. Essa semelhança é uma fonte comum de erros de programação, geralmente difíceis de detectar Expressões Lógicas As expressões lógicas são utilizadas para relacionar os resultados de um conjunto de operações relacionais. Elas realizam as principais operações da lógica booleana e, na linguagem C, têm como operadores: && (AND): Realiza o E lógico; (OR): Realiza o OU lógico;! (NOT): Realiza a negação. Os resultados obtidos das expressões lógicas também são valores do tipo inteiro: 0 para falso e 1 para verdadeiro. Para melhor entender o que cada operador realiza, são levados em conta dois valores P e Q de entrada, que podem ser verdadeiros (V) ou falsos (F). A Tabela 2.4 resume todos os casos possíveis.

35 2.7. COMANDO DE ENTRADA DE DADOS 35 P Q P && Q P Q!P!Q V V V V F F V F F V F V F V F V V F F F F F V V Tabela 2.4: Todas as combinações possíveis para os operadores lógicos. O uso dos operadores relacionais e lógicos fica bem mais claro adiante, neste capítulo, quando os comandos de seleção e repetição são estudados. 2.7 Comando de Entrada de Dados O comando de entrada de dados serve para captar do usuário do programa um ou mais valores necessários para a execução das tarefas. Na verdade, o que se faz é ler os dados de uma fonte externa, normalmente do teclado, para depois usá-los de alguma maneira dentro do programa. Por meio desse comando de leitura de dados, uma ou mais variáveis podem ter seus valores lidos durante a execução do programa. Essa característica permite criar programas mais flexíveis. Na linguagem C, a sintaxe para o uso do comando é a seguinte: scanf("<formato1> <formato2>... <formaton>", &var1, &var2,..., &varn); Essa estrutura é dividida em duas partes distintas. A primeira, colocada entre aspas duplas, contém os formatos, os quais são relacionados diretamente com os tipos das variáveis a serem lidas. A segunda parte é uma lista dos nomes dessas variáveis, com uma relação direta entre a posição nessa lista e o respectivo formato descrito na primeira parte do comando. Conforme mostra a descrição da sintaxe, os nomes das variáveis devem ser precedidos pelo caractere &. A Tabela 2.5 mostra alguns dos possíveis formatos. Código Significado %c Lê um único caracter %d Lê um inteiro decimal %f Lê um número em ponto flutuante Tabela 2.5: Códigos para formatos de dados do comando de entrada scanf. Um exemplo claro da utilidade do comando de entrada de dados é o cálculo da conta de energia elétrica. O exemplo apresentado até agora tinha os valores de consumo e do preço do quilowatt-hora escritos diretamente no código. Isso é bastante incômodo, pois, quando se quer calcular o valor de uma conta diferente, o código deve ser alterado e recompilado. Uma maneira de tornar o programa mais flexível é utilizar o comando de entrada de dados. Com ele, é possível digitar os valores desejados para as variáveis e efetuar os cálculos para

36 36 CAPÍTULO 2. CONCEITOS BÁSICOS diferentes contas, bastando, para isso, executar novamente o programa. O código modificado é exibido no Exemplo De acordo com o formato descrito no comando de leitura, os valores das variáveis leituraatual, leituraanterior e valorunitario devem ser digitados pelo usuário, nesta ordem, separados por espaços em branco. 1 # include <stdio.h> 2 main (){ 3 int leituraatual, leituraanterior, diferenca ; 4 float valorunitario, valorconta ; 5 6 scanf ("%d %d %f", & leituraatual, & leituraanterior, & valorunitario ); 7 diferenca = leituraatual - leituraanterior ; 8 valorconta = diferenca * valorunitario ; 9 } Exemplo 2.14: Emprego do comando de entrada de dados para o algoritmo do Exemplo 2.4. Observando o código do Exemplo 2.14, é importante ressaltar que não há mais a parte de inicialização das variáveis. Essa tarefa foi substituída pelo comando de entrada de dados e os valores são, agora, passados pelo usuário do programa. Esses valores podem ser alterados a cada execução do programa para que várias contas diferentes possam ser calculadas. 2.8 Comando de Saída de Dados Até agora, os exemplos de códigos e os conceitos estudados não mostravam como os programas comunicavam seus resultados ao usuário ou até mesmo a outros programas. Esse é o papel do comando de saída de dados. O comando de saída de dados serve para escrever mensagens e exibir valores de variáveis, proporcionando mais informação e legibilidade tanto para o usuário quanto para o próprio programador. Na linguagem C, a sintaxe para o uso do comando de saída é mostrada a seguir. printf("<formato1> <formato2>... <formaton>", var1, var2,..., varn); A estrutura do comando de saída de dados é bastante semelhante à do comando de entrada. A principal diferença é sobre o uso do caractere &. No comando de saída, o caractere & não deve ser escrito à frente dos identificadores das variáveis. A segunda parte do comando é opcional, conforme é mostrado logo adiante nesta seção. A Tabela 2.6 mostra alguns dos possíveis formatos a serem usados. Conforme já mencionado, o comando de saída é muito utilizado para exibir mensagens para o usuário. Não é obrigatório que as mensagens contenham apenas valores ou caracteres armazenados em variáveis, sendo possível escrever textos como mensagem. O Exemplo 2.15 demonstra essa possibilidade.

37 2.8. COMANDO DE SAÍDA DE DADOS 37 Código Significado %c Caracter %d Inteiros decimais com sinal %e Notação científica %f Ponto flutuante decimal %% Escreve o símbolo % Tabela 2.6: Códigos para formatos de dados para o comando de saída printf. 1 # include <stdio.h> 2 3 main (){ 4 printf (" Bom dia a todos!"); 5 printf (" Outra mensagem "); 6 } Exemplo 2.15: Escrevendo mensagens para o usuário do programa. Pode-se, ainda, mesclar uma mensagem de texto com valores de variáveis. O Exemplo 2.16 ilustra essa idéia. Nele, é possível notar que, para cada especificador de formato na primeira parte do comando, deve existir uma variável de tipo correspondente posicionada adequadamente na segunda parte. 1 printf (" Valor do saldo : %f", saldo ); 2 printf ("A metade de %d vale %d", numero, metade ); 3 printf (" O menor numero digitado foi % d", menor ); Exemplo 2.16: Escrevendo mensagens que incluem valores de variáveis. Uma versão mais completa do código para o cálculo da conta de energia elétrica é exibida no Exemplo Nele, o comando de saída é usado antes de cada comando de entrada de dados (linhas 5, 8 e 11), escrevendo mensagens para indicar ao usuário do programa qual valor ele deverá informar. A última linha de código utiliza o comando de saída para exibir o valor do cálculo da conta. Trata-se de uma mensagem mista, em que o especificador %f será substituído pelo valor da variável valorconta. Supondo que, após os cálculos, essa variável armazene o valor 187, a última mensagem exibida na tela seria: Valor da conta: R$ # include <stdio.h> 2 3 main (){ 4 int leituraatual, leituraanterior, diferenca ; 5 float valorunitario, valorconta ;

38 38 CAPÍTULO 2. CONCEITOS BÁSICOS 6 7 printf (" Digite o valor da leitura ATUAL : "); 8 scanf ("%d", & leituraatual ); 9 10 printf (" Digite o valor da leitura ANTERIOR : "); 11 scanf ("%d", & leituraanterior ); printf (" Digite o preco do Quilowatt - hora : "); 14 scanf ("%f", & valorunitario ); diferenca = leituraatual - leituraanterior ; 17 valorconta = diferenca * valorunitario ; printf (" Valor da conta R$: % f", valorconta ); 20 } Exemplo 2.17: Algoritmo mais interativo para o exemplo de cálculo da conta de energia elétrica. Quando se usa o comando printf, o caractere especial \n é bastante útil. Ele serve para quebrar a linha exatamente na posição onde for inserido no texto. A Tabela 2.7 explica melhor o funcionameto desse caractere. Código Resultado printf("oi.\ncomo vai?"); OI. Como vai? int x; O valor de x eh: x = 10; 10 printf("o valor de x eh:\n%d", x); int a; Primeira linha. a = 20; Segunda linha (a = 20). printf("primeira linha.\nsegunda linha (a = %d). \n\nfim.", a); FIM. Tabela 2.7: Exemplos de uso do caractere especial \n. 2.9 Comandos de Seleção O comando de seleção permite que um programa possa realizar diferentes alternativas de seqüências de instruções durante sua execução. Dependendo do valor de uma expressão ou de uma variável, o programa segue executando uma ou outra seqüência de comandos. Existem três categorias distintas: seleção simples, seleção dupla e seleção múltipla, as quais são abordadas a seguir.

39 2.9. COMANDOS DE SELEÇÃO Comando de seleção simples É possível imaginar diversas situações cotidianas que se assemelham ao comportamento do comando de seleção simples. Uma comparação clássica leva em conta a tomada de um desvio durante uma viagem. Quando se vai de carro para algum lugar, é normal se conhecer uma rota seqüenciada de lugares intermediários pelos quais se passa antes de chegar ao destino. Entretanto, é possível que ocorra algum imprevisto e seja necessário tomar um desvio. Há tarefas computacionais que também se deparam com tais cenários. Antes de exemplificar, é melhor apresentar a estrutura do comando de seleção simplificado. Ela pode ser observada a seguir. if(<express~ao lógica>){ <seqü^encia de comandos> } Não é difícil entendê-la. Quando o computador executa esse comando, o que é realizado em primeiro lugar é o teste da expressão lógica. Se ela resultar verdadeiro, então a seqüência de comandos é executada. Caso contrário, o programa segue executando normalmente as instruções posteriores. Desta forma, a decisão de desviar a execução normal do programa é condicionada ao resultado da expressão lógica escrita no início do comando. O uso do comando de seleção simples é exibido no Pseudocódigo 2.3, o qual descreve um algoritmo que faz a leitura de dois números inteiros e os imprime como foram digitados e, também, em ordem crescente. Pseudocódigo 2.3 Ordena dois números inteiros digitados pelo usuário. Descrição: Algoritmo para imprimir, em ordem crescente, dois números inteiros digitados pelo usuário. Dados de Entrada: dois números inteiros. Saída do Programa: números inteiros impressos na ordem em que foram digitados e em ordem crescente. Funç~ao Principal: Leia dois valores inteiros. Imprima os valores coletados. SE o primeiro número lido for maior que o segundo ENT~AO Ordene os valores. FIM-SE Imprima os valores ordenados. FIM-Funç~ao Principal No Pseudocódigo 2.3, a operação SE-ENTÃO é executada tal qual o comando de seleção simples if. O Exemplo 2.18 apresenta um código em linguagem C para o algoritmo do referido pseudocódigo.

40 40 CAPÍTULO 2. CONCEITOS BÁSICOS 1 # include <stdio.h> 2 main (){ 3 int num1, num2, aux ; 4 5 printf (" Digite dois numeros inteiros separados por espaços : "); 6 scanf ("%d %d", &num1, & num2 ); 7 printf (" Valores digitados : %d %d.\n", num1, num2 ); 8 9 if( num1 > num2 ){ 10 aux = num1 ; 11 num1 = num2 ; 12 num2 = aux ; 13 } 14 printf (" Valores ordenados : %d %d.\n", num1, num2 ); 15 } Exemplo 2.18: Comando if simplificado. O Exemplo 2.18 inicia exibindo uma mensagem ao usuário (linha 5), solicitando que sejam digitados dois números. Esses valores são armazenados em variáveis correspondentes (linha 6) e, em seguida, são mostrados ao usuário por meio de uma mensagem (linha 7). A expressão lógica do comando de seleção da linha 9 determina se há necessidade de inverter os números antes de exibi-los ordenadamente. É importante ressaltar que não há necessidade de ordenar os números caso o usuário já os tenha digitado na seqüência correta. Nesse caso, o programa pode seguir normalmente e exibir na tela os valores das variáveis. Por último, os valores são exibidos em ordem crescente (linha 14). Ainda no Exemplo 2.18, as linhas 10, 11 e 12 são importantes o bastante para serem destacadas. Elas tratam da operação de troca de valores entre duas variáveis, a qual é muito utilizada e deve ser bem entendida. O comando de atribuição é ideal para copiar o valor de uma variável para outra. A questão é que a variável de destino tem seu valor alterado permanentemente após a realização do comando. Ou seja, o valor que estava escrito nessa variável antes da execução da atribuição é apagado e o novo valor é escrito em seu lugar. No caso da operação de troca de valores, se fossem usadas apenas as duas variáveis envolvidas, uma delas teria seu valor perdido quando a primeira atribuição fosse realizada. Esse problema ocorre no Exemplo 2.19, no qual o valor inicial da variável var1 é perdido. 1 main (){ 2 int var1, var2 ; 3 4 var1 = 100; 5 var2 = 200; 6 7 var1 = var2 ; // var1 guarda var2 = var1 ; // var2 guarda }

41 2.9. COMANDOS DE SELEÇÃO 41 Exemplo 2.19: Tentativa equivocada de trocar os valores de duas variáveis. A solução para o problema da perda de valores é simples e pode ser encontrada nas linhas 10, 11 e 12 do Exemplo Basta usar uma variável auxiliar e copiar para ela o valor da primeira variável de destino usada nas atribuições. Naquele exemplo, o valor da variável num1 foi salvo na variável aux para não ser perdido na atribuição da linha 11. Em seguida, esse valor pôde ser copiado corretamente para a variável num2 na linha Comando de seleção dupla O comando de seleção dupla pode ser comparado com a bifurcação de um caminho. É preciso escolher uma das alternativas e segui-la. Em programação, o comando de seleção dupla serve para executar sempre uma de duas alternativas de seqüências de instruções, de acordo com a verificação de uma condição. A estrutura do comando na linguagem C é mostrada a seguir. if (<express~ao lógica>){ <Seqü^encia de comandos 1> }else{ <Seqü^encia de comandos 2> } Primeiramente, o computador testa a expressão lógica. Caso resulte verdadeiro, então a seqüência de comandos 1 é executada. Caso contrário, a seqüência de comandos 2 é realizada. Assim, apenas uma e sempre uma das seqüências de comandos é executada durante uma mesma realização do comando de seleção dupla. Após a operação de uma das seqüências de comando, o programa continua sua execução na primeira linha de código após o conjunto de instruções do comando de seleção. Para exemplificar o uso do comando de seleção dupla, o algoritmo do Pseudocódigo 2.4 mostra como se faz para decidir se um aluno está aprovado em uma disciplina, com base na média de suas notas semestrais. O comando SE-ENTÃO-SENÃO do Pseudocódigo 2.4 deve ser interpretado da mesma maneira que é o comando de seleção dupla if. No Exemplo 2.20, é listado o código em C para o algoritmo do pseudocódigo recém-mencionado. 1 # include <stdio.h> 2 main (){ 3 int matricula ; 4 float n1, n2, n3, mediaparcial ; 5 6 printf (" Digite a matricula e as notas parciais do aluno : "); 7 scanf ("%d %f %f %f", & matricula, &n1, &n2, &n3); 8 mediaparcial = ( n1 + n2 + n3) / 3.0;

42 42 CAPÍTULO 2. CONCEITOS BÁSICOS Pseudocódigo 2.4 Informa se um aluno está aprovado ou não, de acordo com a média das notas obtidas no semestre. Descrição: Algoritmo para decidir se um aluno está aprovado ou não, com base na média das notas semestrais. Dados de Entrada: matrícula do aluno, notas. Saída do Programa: mensagem dizendo se o aluno está aprovado ou não. Funç~ao Principal: Leia a matrícula e as notas do aluno. Calcule a média parcial. SE a média parcial for menor que 7 ENT~AO Imprima uma mensagem dizendo que o aluno deve fazer prova final. SEN~AO Imprima uma mensagem dizendo que o aluno está aprovado. FIM-SE. FIM-Funç~ao Principal 9 10 if( mediaparcial < 7.0) { 11 printf ("\ no aluno % d deve fazer prova final.", matricula ); 12 } else { 13 printf ("\ no aluno % d foi aprovado com media % f", matricula, media ); 14 } 15 } Exemplo 2.20: Uso do comando de seleção if para a verificação da média parcial de um aluno. No Exemplo 2.20, as linhas 3 e 4 correspondem à declaração das variáveis; a linha 7, à inicialização delas. Após o cálculo da média parcial das notas (linha 8), o comando de seleção verifica se tal média é menor que 7, 0 e, assim, executa a instrução da linha 11 ou da linha 13. Como pode ser visto no Exemplo 2.21, é possível usar comandos de seleção de forma aninhada, ou seja, pode-se colocar um comando de seleção dentro de uma das seqüências de comandos de outro. Nesse programa, compara-se dois números inteiros digitados pelo usuário, informando se foram digitados em ordem crescente, decrescente ou se são iguais. Antes, entretanto, é bom observar o Pseudocódigo 2.5, que corresponde ao código do Exemplo # include <stdio.h> 2 main (){ 3 int num1, num2 ; 4 5 printf (" Digite dois numeros inteiros quaisquer : "); 6 scanf ("%d %d", &num1, & num2 ); 7 8 if( num1 > num2 ){ 9 printf ("\ nordem decrescente.");

43 2.9. COMANDOS DE SELEÇÃO 43 Pseudocódigo 2.5 Compara dois números e informa se foram digitados em ordem crescente, decrescente ou se são iguais. Descrição: Algoritmo para decidir se dois números digitados estão em ordem crescente, decrescente ou se são iguais. Dados de Entrada: matrícula do aluno, notas. Saída do Programa: mensagem dizendo se o aluno está aprovado ou não. Funç~ao Principal: Leia dois números inteiros. SE o primeiros for maior que o segundo ENT~AO Imprima uma mensagem dizendo que é ordem crescente. SEN~AO SE o segundo for maior que o primeiro ENT~AO Imprima uma mensagem dizendo que é ordem decrescente. SEN~AO Imprima uma mensagem dizendo que os números s~ao iguais. FIM-SE. FIM-SE. FIM-Funç~ao Principal } else { 12 if( num2 > num1 ){ 13 printf ("\ nordem crescente."); } else { 16 printf ("\ nnumeros iguais."); 17 } 18 } 19 } Exemplo 2.21: Comando de seleção if aninhado em outro comando if. A estrutura dos comandos de seleção do Exemplo 2.21 permite testar todos os casos. Observe que não há uma verificação explícita para a igualdade dos números. Isso acontece porque todos os demais casos possíveis são testados antes e, se não se tratar de nenhum desses, então os números certamente são iguais. Nesse caso, basta apenas exibir a mensagem correspondente. Em muitos casos, além de ser elegante, o uso da estrutura de seleção aninhada pode ser mais eficiente. Verifica-se esse fato nos Exemplos 2.22 e Ambos os programas lêem três números digitados pelo usuário e identificam o maior deles, porém, um dos programas faz menos verificações que o outro, ganhando eficiência. 1 # include <stdio.h> 2 main (){

44 44 CAPÍTULO 2. CONCEITOS BÁSICOS 3 int n1, n2, n3; 4 5 printf (" Entre com os tres numeros : "); 6 scanf ("%d %d %d", &n1, &n2, &n3); 7 8 if (( n1 > n2) && (n1 > n3)){ 9 printf ("O maior numero digitado foi : %d.", n1); 10 } if (( n2 > n1) && (n2 > n3)){ 13 printf ("O maior numero digitado foi : %d.", n2); 14 } if (( n3 > n1) && (n3 > n2)){ 17 printf ("O maior numero digitado foi : %d.", n3); 18 } 19 } Exemplo 2.22: Programa que identifica o maior de três números sem o uso do comando de seleção aninhado. 1 # include <stdio.h> 2 main (){ 3 int n1, n2, n3; 4 5 printf (" Entre com os tres numeros separados por espacos : "); 6 scanf ("%d %d %d", &n1, &n2, &n3); 7 8 if (n1 > n2){ 9 if (n1 > n3){ 10 printf ("O maior numero digitado foi : %d.", n1); 11 } else { 12 printf ("O maior numero digitado foi : %d.", n3); 13 } 14 } else { 15 if (n2 > n3) { 16 printf ("O maior numero digitado foi : %d.", n2); 17 } else { 18 printf ("O maior numero digitado foi : %d.", n3); 19 } 20 } 21 } Exemplo 2.23: Programa que identifica o maior de três números, usando o uso do comando de seleção aninhado. No caso do Exemplo 2.22, o uso de vários comandos if isolados acarreta em perda de eficiência. Quando o primeiro número digitado pelo usuário for o maior, o programa realiza dois testes desnecessários (linhas 12 e 16). E quando o segundo número for o maior, o programa

45 2.9. COMANDOS DE SELEÇÃO 45 realiza um teste desnecessário (linha 16). No Exemplo 2.23, apenas dois testes são executados (linhas 8 e 9 ou linhas 8 e 15), diminuindo significativamente o número de comparações em relação ao Exemplo O programa do Exemplo 2.24 recebe do usuário três números inteiros e verifica se eles podem ser tamanhos dos lados de um triângulo. Caso verdadeiro, o programa ainda indica a classificação do triângulo. O Pseudocódigo 2.6 lista o algoritmo para esse exemplo. Pseudocódigo 2.6 Informa se três números podem constituir os lados de um triângulo e o tipo do triângulo que pode ser formado. Descrição: Algoritmo para calcular se três números podem representar os lados de um triângulo e, caso seja possível, o tipo do triângulo. Dados de Entrada: três valores inteiros. Saída do Programa: mensagem dizendo o tipo do triângulo ou se não pode ser formado um triângulo. Funç~ao Principal: Leia tr^es números inteiros. SE os tr^es valores forem iguais ENT~AO Imprima uma mensagem dizendo que é tri^angulo equilátero. SEN~AO SE cada valor é menor do que a soma dos outros dois ENT~AO SE os tr^es valores forem diferentes ENT~AO Imprima uma mensagem dizendo que é tri^angulo escaleno. SEN~AO Imprima uma mensagem dizendo que é tri^angulo isósceles. FIM-SE. SEN~AO Imprima uma mensagem dizendo que os valores n~ao podem ser lados de um tri^angul FIM-SE. FIM-SE. FIM-Funç~ao Principal 1 # include <stdio.h> 2 main (){ 3 int lado1, lado2, lado3 ; 4 int soma1, soma2, soma3 ; 5 6 printf (" Entre com tres numeros inteiros : "); 7 scanf ("%d %d %d", & lado1, & lado2, & lado3 ); 8 9 soma1 = lado1 + lado2 ; 10 soma2 = lado1 + lado3 ; 11 soma3 = lado2 + lado3 ; 12

46 46 CAPÍTULO 2. CONCEITOS BÁSICOS 13 if (( lado1 == lado2 ) && ( lado2 == lado3 )){ 14 printf (" Triangulo equilatero \n"); 15 } else { if (( soma1 > lado3 ) && ( soma2 > lado2 ) && ( soma3 > lado1 )){ if (( lado1!= lado2 ) && ( lado1!= lado3 ) && ( lado2!= lado3 )){ 20 printf (" Triangulo escaleno \n"); 21 } else { 22 printf (" Triangulo isosceles \n"); 23 } 24 } else { 25 printf (" Os tres lados nao formam um triangulo \ n"); 26 } 27 } 28 } Exemplo 2.24: Programa que indica se três números formam um triângulo. No Exemplo 2.24, o comando if mais externo (linha 13) confere se os números formam um triângulo equilátero, verificando se os três são iguais. Caso não formem um triângulo equilátero, na linha 17 é verificado se os três números formam realmente um triângulo, comparando a soma de dois lados com a medida do terceiro (para todas as possibilidades). Caso verdadeiro, o if mais interno confere se os três lados são diferentes um do outro, formando um triângulo escaleno; o resultado falso para esse último teste implica necessariamente na formação de um triângulo isósceles Comando de seleção múltipla O comando de seleção if é bastante poderoso e largamente utilizado. Entretanto, ele não é o único comando de seleção existente. Há um comando que, embora seja menos abrangente que o comando if, resolve muitas situações de forma clara e elegante. Trata-se do comando de seleção múltipla. O comando de seleção múltipla sempre pode ser substituído por um ou mais comandos if aninhados, mas ele torna o código muito mais claro quando se quer executar várias seqüências de comandos diferentes, sendo que cada seqüência deva corresponder a um determinado valor de uma expressão. A estrutura seguinte facilita a compreensão do comando. switch (<express~ao>){ case <valor1>: <seqü^encia de comandos 1> break; case <valor2>: <seqü^encia de comandos 2> break;.

47 2.9. COMANDOS DE SELEÇÃO 47 }.. case <valorn>: <seqü^encia de comandos N> break; default : <seqü^encia de comandos> Na estrutura do comando de seleção múltipla, a expressão normalmente é uma expressão aritmética ou uma variável de tipo numérica, e associa-se o valor da expressão ao valor de um determinado case. Assim, apenas a seqüência de comandos do bloco case correspondente será executada. É importante observar que cada seqüência de comandos é encerrada pelo comando break. A cláusula default é opcional. Ela marca a seqüência de comandos padrão, que deve ser executada quando nenhuma comparação obtiver sucesso. Se não estiver presente, nada será executado nesse caso. Na linguagem C, cada case deve servir para comparar o resultado da expressão com um valor específico. Não se permite comparações com o resultado de outras expressões. No Exemplo 2.25 usa-se o comando switch na construção de um programa que simula o funcionamento de uma urna eletrônica. O Pseudocódigo 2.7 exibe o algoritmo desse exemplo. Pseudocódigo 2.7 Algoritmo de uma urna eletrônica simplificada. Descrição: Algoritmo para simular uma urna eletrônica simplificada. Não há a opção para voto em branco. Dados de Entrada: o número do candidato. Saída do Programa: mensagem dizendo o nome do candidato escolhido ou se o voto foi anulado. Funç~ao Principal: Leia o número do candidato. CASO o numero do candidato SEJA: 1, ENT~AO imprima uma mensagem dizendo que Hort^ensia da Silva foi escolhida. 2, ENT~AO imprima uma mensagem dizendo que José dos Cravos foi escolhido. 3, ENT~AO imprima uma mensagem dizendo que Margarida S. Pereira foi escolhida. OUTRO VALOR, ENT~AO imprima uma mensagem dizendo que o voto foi anulado. FIM-CASO FIM-Funç~ao Principal O comportamento do comando CASO-SEJA do Pseudocódigo 2.7 deve ser pensado da mesma maneira que o do comando de seleção múltipla switch. 1 # include <stdio.h> 2 main (){

48 48 CAPÍTULO 2. CONCEITOS BÁSICOS 3 int numero ; 4 5 printf (" URNA ELETR^ONICA - SEU VOTO PARA PREFEITO : "); 6 scanf ("%d", & numero ); 7 8 switch ( numero ){ 9 case 1: 10 printf (" Candidato escolhido : Hort^encia da Silva "); 11 break ; 12 case 2: 13 printf (" Candidato escolhido : José dos Cravos "); 14 break ; 15 case 3: 16 printf (" Candidato escolhido : Margarida S. Pereira "); 17 break ; 18 default : 19 printf (" Número digitado inválido. Voto anulado."); 20 } 21 } Exemplo 2.25: Uso do comando switch para simular uma urna eletrônica de eleição. No programa do Exemplo 2.25, os números de 1 a 3 estão associados a determinados candidatos e, para qualquer outro número, a cláusula default é associada. Após a inicialização da variável numero com o comando de entrada de dados (linha 6), o comando de seleção compara o número digitado com os números dos três candidatos inscritos. Em cada caso, se o número digitado coincide com a constante, a seqüência de comandos correspondente ao candidato escolhido é executada. Se nenhuma comparação obtiver sucesso, então o usuário digitou um número de candidato que não existe. Nesse caso, o programa exibe uma mensagem de alerta para o usuário, avisando-lhe que o número digitado é inválido para a eleição (linha 19). Quando o comando break não termina uma seqüência de comandos cuja comparação resultou verdadeira, a seqüência de comandos seguinte é executada sem que o teste correspondente seja efetuado. Na prática, o que acontece é que quando uma comparação do switch resultar em verdadeiro, todas as seqüências de comando seguintes são executadas até que o primeiro comando break seja encontrado. É preciso, portanto, ficar atento e finalizar cada seqüência de comandos com o comando break evitando, assim, erros de programação muitas vezes difíceis de detectar Comandos de Repetição A capacidade que as máquinas possuem de repetir tarefas exaustivamente com a mesma qualidade é uma das principais razões do sucesso de sua invenção. Os computadores podem repetir uma ou mais seqüência de comandos quantas vezes for necessário, e é o programador quem decide o critério de parada das repetições.

49 2.10. COMANDOS DE REPETIÇÃO 49 As aplicações para os comandos de repetição são praticamente infinitas porque quase todas as tarefas contêm partes que devem ser executadas mais de uma vez. Na linguagem C, existem três principais estruturas de repetição. A opção pelo uso de uma ou outra depende normalmente da preferência do programador e do problema em questão, normalmente buscando-se o máximo de clareza ou facilidade de escrita do código. Essas três estruturas são abordadas a seguir Comando de repetição com pré-condição A estrutura do comando while é a mais simples das três e seu funcionamento é igualmente simples de compreender. A estrutura é a seguinte: while (<express~ao lógica>){ <Seqü^encia de comandos> } O computador inicia o comando testando a validade da expressão lógica. Se for verdadeira, então ele executa a seqüência de comandos e retorna ao teste de validação da expressão lógica, reiniciando o ciclo. O computador repete esse processo até que o resultado da expressão lógica seja falso. No caso em que o primeiro teste de validação da expressão lógica resulte em falso, a seqüência de comandos não é executada nenhuma vez. O Pseudocódigo 2.8 representa um algoritmo que recebe um número inteiro positivo n e imprime na tela os n primeiros inteiros positivos. Pseudocódigo 2.8 Imprimir os n primeiros números positivos. Descrição: programa para imprimir na tela os n primeiros números positivos. Dados de Entrada: n (quantidade de inteiros a serem impressos). Saída do Programa: os números inteiros de 1 a n. Funç~ao Principal: Leia n. Valor recebe 1. ENQUANTO Valor for menor ou igual a n FAÇA: Imprima Valor. Incremente Valor. FIM-ENQUANTO FIM-Funç~ao Principal O comando ENQUANTO do Pseudocódigo 2.8 funciona da mesma maneira que o comando de repetição com pré-condição while. O programa do Exemplo 2.26 mostra um código em C que realiza as operações do referido pseudocódigo.

50 50 CAPÍTULO 2. CONCEITOS BÁSICOS 1 # include <stdio.h> 2 main (){ 3 int n, valor ; 4 5 printf (" Digite um número inteiro positivo : "); 6 scanf ("%d", &n); 7 valor = 1; 8 9 while ( valor <= n){ 10 printf ("%d ", valor ); 11 valor = valor + 1; 12 } 13 } Exemplo 2.26: Comando de repetição while para imprimir os n primeiros números inteiros. As variáveis n e valor do Exemplo 2.26 representam a quantidade de números impressos e o próprio número, respectivamente. A variável n é inicializada pelo usuário através do comando de entrada de dados; valor é inicializada com o número 1, para posteriormente ser incrementada a cada iteração. Pela expressão lógica do while, nota-se que a condição de parada de iterações ocorre quando a variável valor atingir um número maior que o número n, ou seja, o bloco de comandos do while (linhas 10 e 11) é executado enquanto o número armazenado em valor for menor ou igual ao número armazenado em n. A condição de parada em qualquer comando de repetição é um fator muito importante que se deve escolher com bastante cuidado. É preciso garantir que o computador efetue um número finito de repetições para que, em algum momento, o programa possa seguir seu fluxo normal de execução. Além de resultados incorretos, uma condição de parada errônea pode acarretar uma execução de programa sem término (nessa situação, diz-se que o programa está em loop infinito ). A Tabela 2.8 apresenta o resultado da execução do programa do Exemplo 2.26, quando o usuário informa o valor 4 para a variável n. n valor valor <= n Tela 4 1 Sim Sim Sim Sim Não Tabela 2.8: Detalhes da execução do comando while do Exemplo O próximo programa, Exemplo 2.27, imprime os n primeiros números ímpares. Essa quantidade n é definida pelo usuário.

51 2.10. COMANDOS DE REPETIÇÃO 51 1 # include <stdio.h> 2 main (){ 3 int iterador, valor, n; 4 5 printf (" Imprimir numeros impares. Entre com a quantidade de termos : "); 6 scanf ("%d", &n); 7 8 iterador = 1; 9 valor = 1; while ( iterador <= n){ 12 printf ("\n %d",valor ); 13 valor = valor + 2; 14 iterador = iterador + 1; 15 } 16 } Exemplo 2.27: Programa que imprime os n primeiros números ímpares, utilizando o comando de repetição while. No programa do Exemplo 2.27, a quantidade de números ímpares a ser impresso é fornecido pelo usuário através do comando scanf. A variável iterador é usada para armazenar o número a ser impresso em cada iteração do comando while. O bloco de instruções do comando de repetição (linhas 11 e 12) é executado enquanto o número armazenado em iterador for menor ou igual ao número fornecido pelo usuário. Com os comandos de repetição é possível melhorar o programa da urna eletrônica proposto no Exemplo 2.25, da seção anterior. Naquele exemplo, o programa não confirma o voto do usuário, impossibilitando que um erro de digitação pudesse ser corrigido durante a execução do programa. O Pseudocódigo 2.9 lista o algoritmo melhorado. Pseudocódigo 2.9 Urna eletrônica com opção de corrigir voto. Descrição: programa para imprimir na tela os n primeiros números positivos. Dados de Entrada: número do candidato, resposta da confirmação. Saída do Programa: mensagem indicando o candidato escolhido. Funç~ao Principal: Resposta recebe zero. ENQUANTO resposta for igual a zero FAÇA: Leia o número do candidato. Imprima o nome do candidato escolhido. Leia a resposta da confirmaç~ao. FIM-ENQUANTO. FIM-Funç~ao Principal

52 52 CAPÍTULO 2. CONCEITOS BÁSICOS No código do Exemplo 2.28, o usuário tem a possibilidade de corrigir o seu voto sem que o programa termine. 1 # include <stdio.h> 2 main (){ 3 int numero, resposta ; 4 5 resposta = 0; 6 7 while ( resposta == 0){ 8 printf (" URNA ELETRONICA - SEU VOTO PARA PREFEITO : "); 9 scanf ("%d", & numero ); switch ( numero ){ 12 case 1: 13 printf (" Candidato escolhido : Hortencia da Silva "); 14 break ; 15 case 2: 16 printf (" Candidato escolhido : Jose dos Cravos "); 17 break ; 18 case 3: 19 printf (" Candidato escolhido : Margarida S. Pereira "); 20 break ; 21 default : 22 printf (" Numero digitado invalido. Voto NULO."); 23 } printf ("\ ndigite 1 para CONFIRMAR ou 0 para CORRIGIR "); 26 scanf ("%d", & resposta ); 27 } 28 } Exemplo 2.28: Comando de repetição while utilizado para melhorar o código do Exemplo O critério de parada escolhido no Exemplo 2.28 é a alteração do valor da variável resposta. Enquanto essa variável armazena o valor zero, o programa repetirá o processo de votação e perguntará ao usuário se confirma o voto ou se gostaria de corrigi-lo. Quando o valor da variável resposta é alterado, a expressão lógica da linha 7 retorna falso, encerrando o comando de repetição Comando de repetição com pós-condição Em contraste com o comando de pré-condição, o comando de repetição com pós-condição só efetua o teste da expressão lógica (condição de parada) após a primeira execução da seqüência de comandos. Logo, o bloco de comandos é executado pelo menos uma vez, independente da expressão lógica. A própria sintaxe do comando sugere essa diferença: do{

53 2.10. COMANDOS DE REPETIÇÃO 53 <Seqü^encia de comandos> } while(<express~ao lógica>); O Pseudocódigo 2.9 pode ser modificado para melhorar sua legibilidade. Com o emprego do comando FAÇA-ENQUANTO, o algoritmo fica mais simples. Pseudocódigo 2.10 Urna eletrônica com opção de corrigir voto (versão melhorada). Descrição: programa para imprimir na tela os n primeiros números positivos. Dados de Entrada: número do candidato, resposta da confirmação. Saída do Programa: mensagem indicando o candidato escolhido. Funç~ao Principal: FAÇA: Leia o número do candidato. Imprima o nome do candidato escolhido. Leia a resposta da confirmaç~ao. ENQUANTO resposta for igual a zero. FIM-Funç~ao Principal O programa do Exemplo 2.28 pode ser reescrito utilizando-se o comando do-while. Nesse caso, o uso do do-while permite ao programador ter a opção de não inicializar a variável resposta antes da execução da seqüência de comandos, responsabilizando o usuário pela inicialização (conforme se nota no Exemplo 2.29). 1 # include <stdio.h> 2 main (){ 3 int numero, resposta ; 4 /* A variável resposta n~ao é mais inicializada pelo programador */ 5 do 6 { 7 printf (" URNA ELETRONICA - SEU VOTO PARA PREFEITO : "); 8 scanf ("%d", & numero ); 9 switch ( numero ) 10 { 11 case 1: 12 printf (" Candidato escolhido : Hortencia da Silva "); 13 break ; 14 case 2: 15 printf (" Candidato escolhido : Jose dos Cravos "); 16 break ; 17 case 3: 18 printf (" Candidato escolhido : Margarida S. Pereira "); 19 break ; 20 default : 21 printf (" Numero digitado invalido. Voto NULO."); 22 }

54 54 CAPÍTULO 2. CONCEITOS BÁSICOS 23 printf ("\ ndigite 1 para CONFIRMAR ou 0 para CORRIGIR "); 24 scanf ("%d", & resposta ); 25 } while (! resposta ); 26 } Exemplo 2.29: Aplicação para o comando do-while, modificando o código do Exemplo No Exemplo 2.29, nota-se que não há mais a inicialização explícita da variável resposta. Com o comando do-while, garante-se que a variável será inicializada pelo usuário antes de seu valor ser testado na expressão lógica da linha 25, utilizada como critério de parada. Pode parecer estranho, à primeira vista, como a expressão lógica!resposta funciona. Na verdade essa espressão é equivalente ao teste resposta == 0. Para entender essa equivalência, basta lembrar que o operador lógico de negação inverte o valor lógico da variável ao qual ele é aplicado. É bom recordar também que, para os números inteiros, o valor zero equivale a falso enquanto que qualquer número diferente de zero é tomado como valor lógico verdadeiro. Assim sendo, sempre que o programador desejar testar se um valor inteiro é igual a zero, basta testar o resultado do operador de negação, pois o único caso em que ele retornará verdadeiro será quando o valor numérico testado for zero. Um outro exemplo simples para o uso do comando do-while é um algoritmo no qual o usuário informa uma seqüência de valores positivos e, em seguida, a média desses valores é apresentada. O usuário deverá digitar zero quando quiser finalizar a seqüência e saber a média. O algoritmo desse programa está listado no Pseudocódigo Pseudocódigo 2.11 Cálculo da média de números positivos digitados pelo usuário. Descrição: Algoritmo para cálculo da média de números positivos digitados pelo usuário. Dados de Entrada: números não negativos. Saída do Programa: média dos valores digitados. Funç~ao Principal: FAÇA: Leia um valor. SE valor é positivo ENT~AO Atualize a soma dos números digitados. Incremente a quantidade de números digitados. FIM-SE. ENQUANTO valor digitado for positivo. SE o usuário digitou algum valor positivo ENT~AO Calcule a média dos valores digitados. Imprima essa média. FIM-SE. FIM-Funç~ao Principal O Exemplo 2.30 exibe o programa para o Pseudocódigo 2.11.

55 2.10. COMANDOS DE REPETIÇÃO 55 1 # include <stdio.h> 2 main (){ 3 float valor, soma, media ; 4 int quantidade ; 5 6 soma = 0; 7 quantidade = 0; 8 do { 9 printf (" Informe um valor positivo ou zero para encerrar o programa : "); 10 scanf ("%f",& valor ); 11 if ( valor > 0) { 12 soma = soma + valor ; 13 quantidade = quantidade + 1; 14 } 15 } while ( valor > 0); 16 if ( quantidade > 0) { 17 media = soma / quantidade ; 18 printf (" Media dos valores digitados : % f\ n", media ); 19 } 20 } Exemplo 2.30: Programa que calcula a média dos valores de uma seqüência digitada pelo usuário. O funcionamento do programa do Exemplo 2.30 é simples. A variável soma guarda a soma dos valores digitados pelo usuário e deve ser inicializada com zero. A cada novo valor positivo digitado, seu valor deve ser atualizado. Essa atualização ocorre na linha 12. Observa-se que o comando de seleção da linha 11 impede que haja atualização das variáveis soma e quantidade quando o usuário quer encerrar o programa e digita zero. A variável valor é a condição de parada do comando de repetição. Sempre que o usuário digita zero (ou até mesmo um número negativo), o comando de repetição é encerrado. Para saber quantos números foram digitados, a variável quantidade é empregada. Ela é inicializada com zero e, sempre que o usuário digita um número positivo, ela é incrementada (linha 13). Quando o comando de repetição é encerrado, é preciso verificar se o usuário digitou algum valor. Essa verificação se deve ao fato de que o primeiro valor digitado pode ser zero, indicando que o usuário saiu do programa e não quis calcular nada. No caso de algum valor válido ter sido informado (expressão lógica da linha 16), então a média é calculada e uma mensagem com o resultado é exibida ao usuário (linhas 17 e 18) Comando de repetição condensado O comando de repetição condensado permite agrupar, em um único comando, a inicialização de uma variável, o incremento desta variável e o teste de parada. Seu uso é adequado para situações em que o número de repetições da seqüência de comandos já é conhecido. A estrutura do comando é exibida a seguir:

56 56 CAPÍTULO 2. CONCEITOS BÁSICOS for (<inicializaç~ao>; <express~ao lógica>; <incremento>){ <Sequencia de comandos> } Inicialização é um comando de atribuição usado para colocar um valor na variável de controle utilizada na expressão lógica. Assim como os demais comandos de repetição, a expressão lógica representa a condição de parada das iterações. Incremento define como a variável de controle que será modificada a cada iteração. Um exemplo de como utilizar o comando for está listado no código do Exemplo Esse programa serve para imprimir os n primeiros termos de uma PG (progressão geométrica). O usuário deve informar os valores do primeiro termo, da razão e da quantidade de termos a serem exibidos. 1 main (){ 2 float primeirotermo, razao, n, i, termo ; 3 4 printf (" Informe o valor do primeiro termo da PG: "); 5 scanf ("%f",& primeirotermo ); 6 printf (" Informe o valor da razao da PG: "); 7 scanf ("%f",& razao ); 8 printf (" Informe quantos termos devem ser impressos : "); 9 scanf ("%f",&n); 10 termo = primeirotermo ; 11 for ( i =0; i <n; i=i +1) { 12 printf ("%f ", termo ); 13 termo = termo * razao ; 14 } 15 } Exemplo 2.31: Programa para exibir os n primeiros termos de uma progressão geométrica utilizando o comando de repetição for. No Exemplo 2.31, as variáveis primeirotermo, razao e n são inicializadas pelo usuário, usando o comando de entrada de dados. A linha 10 trata da inicialização da variável termo, que guarda o valor do termo da PG a ser impresso a cada repetição (iteração) do comando for. O comando de repetição, em primeiro lugar, inicializa a variável de controle i com o valor zero. Em seguida, ocorre a verificação da expressão lógica. Caso o usuário tenha digitado zero, o comando não executa sua seqüência de instruções nenhuma vez, pois tanto i quanto n armazenam zero e tornam falso o resultado da expressão lógica. Nesse caso, nada é impresso na tela. Mas se o usuário digitou um valor maior que zero, a seqüência de comandos será repetida até que a quantidade solicitada de termos seja impressa. Para isso, o comando de saída de dados é chamado logo no início da seqüência de comandos (linha 12). No caso em que i é igual a zero (primeira iteração), o primeiro termo é exibido. A linha 13 conclui a seqüência de comandos atualizando a variável termo para guardar o próximo termo da seqüência.

57 2.10. COMANDOS DE REPETIÇÃO 57 Um passo implícito é que o comando realiza o incremento da variável i no momento após a execução da última instrução da seqüência de comandos. Feita essa atualização, a condição de parada é novamente verificada e, caso seja verdadeira, uma nova repetição é começada para imprimir outro termo da PG. A Tabela 2.9 mostra passo a passo a execução completa do comando de repetição para o programa do Exemplo Os valores informados são para uma PG de razão igual a 2, primeiro termo igual a 2 e quantidade de termos a serem impressos igual a 4. As colunas mostram as variáveis principais do comando de repetição (i, termo e n), o resultado da expressão lógica a cada iteração e o que foi impresso na tela. Os valores são referentes ao momento posterior à execução da seqüência de instruções, antes do incremento da variável de controle. i termo n i < n Tela Verdadeiro Verdadeiro Verdadeiro Verdadeiro Falso Tabela 2.9: Detalhes da execução do comando for do Exemplo O programa do Exemplo 2.32 realiza a operação de exponenciação de um número, utilizando o comando de repetição for. 1 # include <stdio.h> 2 main (){ 3 int base, expoente, resultado, i; 4 5 printf (" Informe os valores para base e expoente : "); 6 scanf ("%d %d", &base, & expoente ); 7 8 resultado = 1; 9 for ( i = 0; i < expoente ; i = i + 1){ 10 resultado = resultado * base ; 11 } printf ("%d elevado a %d = %d\n", base, expoente, resultado ); 14 } Exemplo 2.32: Programa para realizar a operação de exponenciação utilizando o comando de repetição for. No Exemplo 2.32, os valores da base e do expoente são fornecidos pelo usuário via comando de entrada de dados (linha 6). A variável de controle i é inicializada com o valor zero dentro do comando for e, a cada iteração, é incrementada de 1. Quando o valor de i atinge valor igual ao valor da variável expoente, as iterações são terminadas e o programa segue para a linha 13,

58 58 CAPÍTULO 2. CONCEITOS BÁSICOS imprimindo os valores da base, do expoente e do resultado da exponenciação. Assim como o comando while, o comando for efetua primeiramente o teste da condição de parada antes de executar a seqüência de comandos pela primeira vez Problema dos Lotes Encaixantes Com o conhecimendo dos conceitos básicos da programação, uma primeira classe de problemas pode ser estudada. Trata-se do Problema dos Lotes Encaixantes, que corresponde a situações em que se precisa executar uma varredura em uma massa de dados e extrair dela algumas informações estatísticas. O termo lotes encaixantes refere-se ao fato de que, na maioria das vezes, problemas como esse envolvem entidades complexas compostas por outras mais simples. As entidades mais simples se encaixam para formar outras com um grau de complexidade ainda maior. Assim sendo, é possível visualizar vários níveis que variam desde os mais simples e com muitos elementos até os mais complexos e com algumas dezenas de integrantes. A Figura 2.7 ajuda a explicar a idéia. Nela, os quadrados de bordas contínuas e finas representam o nível mais simples. Eles se agrupam para formar os quadrados de bordas pontilhadas, que representam um nível intermediário de complexidade. Juntos, os quadrados pontilhados compõem o nível mais complexo, representado pelo grande quadrado de borda grossa. Na prática, podem existir não apenas três, mas quantos níveis forem necessários para caracterizar os dados de um programa. Figura 2.7: Uma representação gráfica para os lotes encaixantes Considera-se, como exemplo, o problema de um professor que ministra uma disciplina para diversas turmas. Nesse caso, é interessante saber qual turma obteve melhor rendimento, o aluno

59 2.11. PROBLEMA DOS LOTES ENCAIXANTES 59 que mais se destacou em cada turma e os que menos renderam nos estudos. Os dados, nesse caso, podem ser observados em quatro níveis. O nível de notas é o mais simples. Ele é indivisível e é caracterizado pelas notas dos trabalhos e provas realizados no semestre. O nível seguinte é o das médias semestrais em que as notas são agrupadas por aluno. O terceiro agrupamento é o das turmas. Os alunos são reunidos de acordo com as turmas a que pertencem. Por último, o nível mais complexo, é a própria disciplina. Ela é a entidade mais abrangente, que é composta diretamente pelas turmas existentes no determinado semestre. Problemas de lotes encaixantes, normalmente, são resolvidos por meio do emprego de comandos de repetição aninhados. Quanto mais externo for o comando de repetição, maior o nível de complexidade das entidades que estão sendo examinadas. A Figura 2.8 exibe essa idéia, usando como exemplo o problema das notas escolares. Figura 2.8: Comandos de repetição aninhados, utilizados para resolver um problema de lotes encaixantes. O Pseudocódigo 2.12 descreve um algoritmo para realizar um levantamento estatístico do desempenho de alunos e turmas de uma disciplina hipotética num determinado semestre. O Exemplo 2.33 ilustra o código para um programa que lê os dados das notas dos alunos e exibe a média de cada um. Para cada turma, será exibida a melhor e a pior média. Ao final, exibe a turma com maior percentual de aprovações da disciplina.

60 60 CAPÍTULO 2. CONCEITOS BÁSICOS Pseudocódigo 2.12 Problema dos Lotes Encaixantes para o problema das notas escolares Descrição: programa para analisar as notas de uma disciplina escolar. Dados de Entrada: código da turma, matrículas dos alunos, notas dos alunos. Saída do Programa: média de cada aluno, matrículas dos alunos de melhor e de pior médias de cada turma, código da turma com melhor rendimento. Funç~ao Principal: Leia o código de uma turma. ENQUANTO código for diferente de -1 FAÇA: Leia o número de matrícula de um aluno. ENQUANTO o número de matrícula for diferente de -1 FAÇA: Leia uma nota do aluno. ENQUANTO a nota digitada n~ao for negativa FAÇA: Leia outra nota do aluno. Atualize os dados do aluno. FIM-ENQUANTO Atualize os dados da turma. Imprima a média das notas do aluno. Leia o número de matrícula de um aluno. FIM-ENQUANTO Atualize os dados da disciplina. Imprima os números de matrícula e as médias dos alunos com maior e menor médias da turma. Leia o código de uma turma. FIM-ENQUANTO Impria o código da turma com melhor aproveitamento. FIM-Funç~ao Principal 1 # include <stdio.h> 2 # define MEDIAMINIMA main (){ 5 int codturma, matricula, numnotasaluno, numalunosturma, alunomaiormedia, alunomenormedia, melhorturma ; 6 float nota, mediaaluno, numaprovadosturma, somanotasaluno, maiormediaturma, menormediaturma, aproveitamentoturma, melhoraproveitamento ; 7 8 melhoraproveitamento = 0; 9 10 printf (" Informe o codigo da turma : "); 11 scanf ("%d", & codturma ); 12 while ( codturma!= -1) { 13 maiormediaturma = 0; 14 menormediaturma = 10;

61 2.11. PROBLEMA DOS LOTES ENCAIXANTES numaprovadosturma = 0; 16 numalunosturma = 0; printf (" Informe o numero de matricula do aluno : "); 19 scanf ("%d", & matricula ); 20 while ( matricula!= -1) { 21 numnotasaluno = 0; 22 somanotasaluno = 0; 23 numalunosturma = numalunosturma + 1; printf (" Informe a nota do aluno : "); 26 scanf ("%f", & nota ); 27 while ( nota!= -1) { 28 somanotasaluno = somanotasaluno + nota ; 29 numnotasaluno = numnotasaluno + 1; 30 printf (" Informe a nota do aluno ou -1 para encerrar : "); 31 scanf ("%f", & nota ); 32 } mediaaluno = somanotasaluno / numnotasaluno ; if ( mediaaluno >= MEDIAMINIMA ) { 37 numaprovadosturma = numaprovadosturma + 1; 38 } if ( mediaaluno >= maiormediaturma ) { 41 maiormediaturma = mediaaluno ; 42 alunomaiormedia = matricula ; 43 } if ( mediaaluno <= menormediaturma ) { 46 menormediaturma = mediaaluno ; 47 alunomenormedia = matricula ; 48 } 49 printf (" Media do aluno : %f\n", mediaaluno ); 50 printf (" Informe o numero de matricula do aluno ou -1 para encerrar : " ); 51 scanf ("%d", & matricula ); 52 } aproveitamentoturma = ( numaprovadosturma / numalunosturma ) * 100; 55 if ( melhoraproveitamento < aproveitamentoturma ) { 56 melhoraproveitamento = aproveitamentoturma ; 57 melhorturma = codturma ; 58 } 59 printf (" O aluno % d obteve a melhor media da turma (% f)\ n", alunomaiormedia, maiormediaturma ); 60 printf (" O aluno % d obteve a pior media da turma (% f)\ n", alunomenormedia, menormediaturma ); 61 printf (" Informe o codigo da turma ou -1 para encerrar : "); 62 scanf ("%d", & codturma );

62 62 CAPÍTULO 2. CONCEITOS BÁSICOS 63 } 64 printf (" A turma % d obteve o melhor aproveitamento (% f %%) ", melhorturma, melhoraproveitamento ); 65 } Exemplo 2.33: Problema dos lotes encaixantes aplicado a notas de uma disciplina Fazendo uma comparação com a Figura 2.8 e com o Pseudocódigo 2.12, não fica complicado entender o funcionamento do código do Exemplo Após a declaração das variáveis, o programa inicializa a variável melhoraproveitamento (linha 8), que armazena o percentual de aprovações da turma de melhor aproveitamento. O valor ajustado é o menor valor possível (zero) e sua escolha será justificada adiante. Em seguida, o programa solicita ao usuário que digite um código para identificar a primeira turma que será analisada (linhas 10 e 11). O primeiro comando de repetição, na linha 12, serve para percorrer todas as turmas, uma a uma. Sempre que sua expressão lógica resultar verdadeiro, é sinal que uma nova turma terá seus dados digitados. Aqui vale destacar um artifício útil que é empregado nesse código. Todos os comandos de repetição fazem a verificação de suas variáveis de controle comparando-as com o mesmo valor 1. Ocorre que, para evitar que o usuário seja obrigado a saber, a priori, da quantidade de elementos de um conjunto de dados que será passado ao programa, utiliza-se um valor absurdo (qualquer valor que não pertença ao conjunto em questão) para servir de sinalização para o programa de que o cunjunto de dados foi inteiramente percorrido. Esse valor é, comumente, chamado de flag. No Exemplo 2.33, considera-se que os códigos das turmas e das matrículas são inteiros positivos e que as notas são valores racionais não negativos. Assim sendo, o valor 1 foi utilizado para indicar ao programa quando todas as notas de um aluno foram já digitadas. Ou que todos os dados dos alunos de uma determinada turma foram digitados. Ou ainda, que todas as turmas tiveram seus dados informados. A seqüência de comandos começa, então, com a inicialização das variáveis da turma em questão (linhas 13 a 16). As variáveis maiormediaturma e menormediaturma guardam, respectivamente, a melhor e a pior média obtidas na turma. Já numaprovadosturma e numalunosturma armazenam, respectivamente, a quantidade de alunos aprovados na turma e a quantidade total de alunos da turma. Os valores dessas duas variáveis servem para calcular o aproveitamento da turma. Nas linhas 18 e 19, o programa solicita ao usuário que informe a matrícula do primeiro aluno da turma que terá lidas suas notas. Na linha seguinte aparece o segundo comando de repetição, o qual interage com os dados dos alunos de uma turma. Assim como no comando de repetição mais externo, o comando da linha 21 inicia sua seqüência de comandos realizando a inicialização das variáveis pertinentes. As variáveis num- NotasAluno e somanotasaluno servem para calcular a média aritmética simples das notas do aluno em questão armazenando, respectivamente, a quantidade de notas do aluno e a soma delas. Na linha 23, a variável numalunosturma é incrementada, indicando a adição dos dados de mais um aluno.

63 2.11. PROBLEMA DOS LOTES ENCAIXANTES 63 As linhas 25 e 26 requerem ao usuário que ele digite o valor da primeira nota do aluno em questão. Em seguida, o programa executa o comando de repetição mais interno, que serve para registrar todas as notas de um determinado aluno. Esse comando coleta os dados das notas, solicitando ao usuário que digite o valor da nota (linhas 30 e 31) e atualizando os valores das variáveis que calculam a média do aluno no semestre. Encerrada a digitação das notas, o usuário deve digitar 1 para encerrar e, quando o faz, o programa passa para a rotina de cálculo da média (linha 34). Com o valor da média, algumas verificações são feitas para atualizar as estatísticas da turma em questão. Em primeiro lugar, é verificado se a média alcançada pelo aluno é suficiente para aprovação. Em caso afirmativo, a variável numaprovadosturma é incrementada. É verificado, em seguida (linha 40), se a média do aluno é maior que a maior média encontrada até então. Em caso verdadeiro, a variável maiormediaturma é atualizada, assim como a variável alunomaiormediaturma, que guardará a matrícula desse aluno. O comando de seleção da linha 45 é equivalente ao da linha 40. Desta vez, a intenção é atualizar os dados do aluno com pior rendimento. Vale um comentário sobre os valores de inicialização das variáveis maiormediaturma e menormediaturma. É preciso ter em mente que, para a primeira verificação de melhor e pior aluno de cada turma, os respectivos comandos de seleção devem comparar o valor da média calculada com os valores já armazenados. O problema é que, para o primeiro aluno de cada turma, essas variáveis ainda não contém valores válidos. Sendo assim, para evitar erros, a variável maiormediaturma é inicializada com o menor valor possível para a média e a variável menormediaturma, ao contrário, é inicializada com o maior valor possível. Essa inicilização garante que os dados do primeiro aluno serão corretamente assimilados como o melhor e o pior resultado encontrado. Essa condição é correta no início, pois os dados do primeiro aluno são os únicos passados ao sistema até então. O comando da linha 49 imprime os dados do aluno, informando sua matrícula e a média alcançada. Em seguida o usuário deve informar o código da matrícula do próximo aluno ou encerrar o cadastro dos dados da turma em análise, digitando 1. Caso tenha encerrado as digitações de uma turma, o programa executa o cálculo do respectivo aproveitamento (linha 54). Feito isso, um comando de seleção verifica se o aproveitamento calculado é melhor que o melhor já encontrado. Em caso afirmativo, as variáveis melhoraproveitamento e melhorturma são atualizadas para apontar para a turma recém analisada (linhas 56 e 57, respectivamente). Terminando a seqüência de instruções do segundo comando de repetição, os dados do melhor e do pior aluno da turma são exibidos (linhas 59 e 60). Por último, é perguntado ao usuário se ele deseja incluir os dados de uma outra turma, bastando, para isso, digitar seu código. Se digitar a flag 1, o comando de repetição é encerrado e o programa exibirá, em sua última linha, os dados da melhor turma (linha 64).

64 64 CAPÍTULO 2. CONCEITOS BÁSICOS 2.12 Exercícios Resolvidos 1. Faça um programa que calcule as raízes reais da equação do segundo grau ax 2 + bx + c. Pseudocódigo 2.13 Cálculo das raízes da equação do segundo grau ax 2 + bx + c. Descrição: Algoritmo para cálculo das raízes de uma equação do segundo grau ax 2 + bx + c. Dados de Entrada: coeficientes a, b e c. Saída do Programa: raízes da equação. Funç~ao Principal: Leia os coeficientes a, b e c. Calcule delta igual a b*b-4*a*c. SE delta for maior ou igual a zero ENT~AO SE delta for igual a zero ENT~AO Calcule a única raiz igual a -b/(2*a). Imprima o valor da raiz. SEN~AO Calcule a raiz1 igual a (-b + raiz quadrada de delta)/(2*a). Calcule a raiz2 igual a (-b - raiz quadrada de delta)/(2*a). Imprima os valores das raízes. FIM-SE SEN~AO Imprima uma mensagem dizendo que n~ao há raízes reais. FIM-SE FIM-Funç~ao Principal 1 main (){ 2 3 float a, b, c, delta, raizdelta, raiz1, raiz2 ; 4 5 printf (" Informe os valores das constantes a, b e c ( separados por espaços ): "); 6 scanf ("%f %f %f", &a, &b, &c); 7 8 delta = b* b - 4* a* c; 9 10 if ( delta >= 0){ 11 if ( delta == 0) { 12 raiz1 = -b / (2* a); 13 printf (" As duas raizes sao iguais a: % f.", raiz1 ); 14 } else { 15 raizdelta = sqrt ( delta ); 16 raiz1 = (- b + raizdelta ) / (2 * a); 17 raiz2 = (- b - raizdelta ) / (2 * a); 18 printf ("As duas raizes s~ao : %f e %f", raiz1, raiz2 );

65 2.12. EXERCÍCIOS RESOLVIDOS } 20 } else { 21 printf (" Nao existem raizes reais desta equacao!"); 22 } 23 } Exemplo 2.34: Programa para calcular as raízes reais de uma equação do segundo grau. 2. Escreva um programa que escreva os n primeiros termos da seqüência de Fibonacci. Essa seqüência tem zero como primeiro termo e 1 como segundo. Do terceiro termo em diante, a fórmula para obtenção é a soma dos dois anteriores. 1 main (){ 2 int n, termoatual, penultimotermo, antepenultimotermo, i; 3 4 printf (" Informe o numero de termos a serem impressos : "); 5 scanf ("%d", &n); 6 7 if (n >= 1) { 8 printf ("0 "); 9 } 10 if (n >= 2) { 11 printf ("1 "); 12 } antepenultimotermo = 0; 15 penultimotermo = 1; 16 termoatual = 2; 17 for ( i =0; i < n -2; i=i +1) { 18 printf ("%d ", termoatual ); 19 antepenultimotermo = penultimotermo ; 20 penultimotermo = termoatual ; 21 termoatual = penultimotermo + antepenultimotermo ; 22 } 23 } Exemplo 2.35: Programa para calcular as raízes reais de uma equação do segundo grau. 3. Fazer um programa para imprimir a tabuada de 1 a 9. 1 main (){ 2 int n1,n2,r; 3 4 printf ("\ ntabuada de 1 a 9\n\n"); 5 n1 =1; 6 n2 =1; 7 while (n1 <10) { 8 while (n2 <10) {

66 66 CAPÍTULO 2. CONCEITOS BÁSICOS 9 r=n1*n2; 10 printf ("%d * %d = %d\n",n2,n1,r); 11 n2=n2 +1; 12 } 13 printf ("\n"); 14 n2 =1; 15 n1=n1 +1; 16 } 17 } Exemplo 2.36: Programa para exibir a tabuada de 1 a Calcular o Maximo Divisor Comum (MDC) de dois números. O MDC de dois números pode ser obtido escolhendo o maior deles e subtraindo-lhe o valor do menor. Esta operação é repetida até que os dois sejam iguais, cujo valor será o MDC dos números iniciais: MDC = MDC = 03 1 main (){ 2 int numero1, numero2, auxiliar1, auxiliar2 ; 3 4 printf (" Digite dois numeros para calcular seu MDC : "); 5 scanf ("%d %d", & numero1, & numero2 ); 6 7 auxiliar1 = numero1 ; 8 auxiliar2 = numero2 ; 9 10 while ( auxiliar1!= auxiliar2 ) { 11 if ( auxiliar1 > auxiliar2 ) { 12 auxiliar1 = auxiliar1 - auxiliar2 ; 13 } else { 14 auxiliar2 = auxiliar2 - auxiliar1 ; 15 } 16 } 17 printf ("O MDC vale %d\n", auxiliar1 ); 18 } Exemplo 2.37: Programa para exibir o MDC de dois números.

67 2.13. RESUMO Faça um programa que converta um valor em base binária para base decimal. 1 main (){ 2 int binario, aux1, aux2, aux3, decimal ; 3 4 printf (" Digite um valor em base binaria : "); 5 scanf ("%d", & binario ); 6 7 aux1 = binario ; 8 aux2 = 1; 9 decimal = 0; 10 while ( aux1 > 0) { 11 aux3 = aux1 % 10; 12 decimal = decimal + aux3 * aux2 ; 13 aux2 = aux2 *2; 14 aux1 = aux1 /10; 15 } 16 printf (" O valor % d em base binaria equivale a % d em base decimal \ n", binario, decimal ); 17 } Exemplo 2.38: Programa para converter números de binário para decimal Resumo O computador guarda as informações dos programas na memória. A memória pode ser entendida como uma seqüência de células, cada uma identificada por um número conhecido como endereço de memória. Nas linguagens de programação, as variáveis permitem o acesso às celulas de memória sem a necessidade de manipular seus endereços diretamente. Na linguagem C, para a declaração de uma variável, o programador deve informar qual o tipo de dados que ela irá manipular. O comando de atribuição permite atribuir uma determinada informação (ou valor) a uma variável do programa. Os identificadores são nomes que identificam uma variável ou constante. Eles são formados por caracteres alfanuméricos, sendo que o primeiro deve ser obrigatoriamente alfabético. Na programação, os quatro tipos de dados mais comuns são: tipo inteiro, tipo ponto flutuante, tipo booleano e tipo caractere. Variáveis e constantes podem ser combinadas com operadores para formarem expressões. No comando de atribuição, o valor da expressão (termos à direita do comando) é atribuído à variável (à esquerda do comando). Os principais tipos de expressões são aritmética, relacional e lógica.

68 68 CAPÍTULO 2. CONCEITOS BÁSICOS Na maioria dos problemas de computação, os comandos de entrada de dados, saída de dados, seleção e repetição, são imprescindíveis. Diversos problemas computacionais encaixam-se na modelagem do Problema dos Lotes Encaixantes. Essa classe de problemas é facilmente resovida com o aninhamento de comandos de repetição, os quais destinam-se a examinar um nível específico da massa de dados Exercícios Propostos 1. Escreva um programa, em C, que recebe um valor de ângulo em graus e informa os valores do seno, do cosseno e da tangente desse ângulo. 2. Faça um programa, em C, que converta valores de temperatura de Fahrenheit para Celsius. A proporção utilizada é: T emperatura Celsius 5 = (T emperatura F ahrenheit 32) 9 3. Faça um programa que calcule a soma 4. Faça um programa que calcule a soma S = S = Um comerciante deseja aumentar suas vendas fazendo uma promoção. Os produtos que ele trabalha e seus respectivos preços e códigos são: a - anel - R$ 3,00, p - pulseira - R$ 5,00, b - brinco - R$ 5,00 e c - cinto - R$ 10,00. As promoções oferecidas são: comprar 1 unidade de cada produto, ganha-se um desconto de 10% no total da compra. comprar mais de 1 unidade de algum produto, ganha-se 5% de desconto no valor total da compra. Faça um programa para simular a venda na loja do comerciante, recebendo como dado de entrada o código do produto e o código da promoção que o freguês quiser. Imprimir o valor original da compra e o valor com desconto. Utilize o comando de seleção múltipla (switch). 6. Fazer um programa que calcule e escreva uma tabela de graus centígrados em função de graus farenheit que variam de 50 a 150 de 1 em Faça um programa para ler uma seqüência de n números inteiros positivos (um por vez), e verificar se eles são pares ou ímpares.

69 2.14. EXERCÍCIOS PROPOSTOS Suponha que a população de um país A seja de habitantes com uma taxa anual de crescimento de 3% e que a população de um país B seja, aproximadamente, de de habitantes com uma taxa anual de crescimento de 1,5%, fazer um programa que calcule e escreva o número de anos necessários para que a população de país A ultrapasse ou se iguale à população do país B, mantidas estas taxas de crescimento. 9. Uma certa firma fez uma pesquisa de mercado para saber se as pessoas gostaram ou não de um novo produto lançado no mercado. Para isso, obteve, para cada pessoa entrevistada, informações a respeito do sexo do entrevistado e sua resposta (S = Sim ou N = Não). Sabe-se que foram entrevistados 2000 pessoas, fazer um programa que calcule e escreva: O número de pessoas que responderam sim O número de pessoas que responderam não A porcentagem de pessoas do sexo feminino que responderam sim A porcentagem de pessoas do sexo masculino que responderam não 10. A fábrica de chocolates MENINO está com problemas financeiros e pretende fazer um corte na folha de pagamento. Para isso, o setor de finanças adotou o seguinte critério para reduzir a despesa com pessoal: funcionários com tempo de serviço menor que 2 anos (24 meses) serão demitidos; funcionários com tempo de serviço superior (ou igual) a 2 anos (24 meses) e menor que 10 anos (120 meses) terão seus salários reduzidos em 10%; funcionários com tempo de serviço superior (ou igual) a 10 anos (120 meses) não serão demitidos e nem terão seus salários reduzidos. Eles poderão optar por um plano de demissão voluntária com a seguinte proposta de indenização: 2 salários atuais para cada ano de serviço. Faça um programa para: (a) ler mês e ano correntes; (b) ler os dados dos funcionários que são: matrícula do funcionário, salário atual, mês e ano de ingresso na fábrica. (obs: não se sabe, a priori, o número de funcionários da fábrica). (c) aplicar o critério acima descrito; (d) imprimir para cada funcionário: a matrícula, o caso em que ele se enquadra no critério. Se o funcionário se enquadrar na redução do salário, imprimir o salário novo. Se ele se enquadrar no plano de demissão voluntária, imprimir a indenização que o funcionário receberá.

70 Capítulo 3 Modularização Co-autor: Gilberto Segundo Objetivos: Definir o que é modularização; Apresentar as vantagens de um programa modularizado; Apresentar métodos de como criar e adaptar programas modularizados; Introduzir o conceito de recursividade e como usá-la Os programas são escritos a fim de resolverem vários tipos de problemas. Alguns desses problemas exigem mais tempo e maiores esforços do programador. Por isso, é indispensável que o programador utilize de técnicas que visam uma maior organização e consequente entendimento facilitado do programa. Uma das técnicas mais utilizadas é a modularização. Este capítulo trata sobre o conceito e a prática de programas modularizados. 3.1 Introdução Quando se trabalha em qualquer problema complexo, seja em programação, seja em outra área, o ser humano sente a necessidade de dividir esse problema maior em problemas menores. Cada problema menor é então trabalhado de forma a solucionar as questões que lhe cabem e, juntandose cada uma dessas soluções, tem-se a solução do problema maior. Esse processo é chamado de modularização e baseia-se na conhecida técnica de dividir para conquistar. Pode-se aplicar a modularização em diversas áreas na vida cotidiana. Para a fabricação de um automóvel, por exemplo, são necessários diversos serviços: funilaria, montagem, estofamento, pintura, soldagem, etc. A princípio, poderia existir apenas um robô que fizesse todas as etapas da fabricação do carro, mas isso acarretaria em alguns problemas, tais como: complexidade 70

71 3.2. SUBPROGRAMAS 71 do robô, que deveria fazer todos os serviços de forma completa e eficiente; alto prejuízo em eventuais serviços de manutenção, que teria que parar todo o processo de montagem; falta de clareza no entendimento do processo de fabricação, o que prejudicaria a expansão do processo de montagem; entre outras. Uma ótima alternativa para esse problema é fazer vários robôs, cada um com sua função específica. O que se estaria fazendo na verdade é uma modularização. Pode-se facilmente mover as idéias contidas no problema apresentado para a elaboração de um programa. O programa pode ser dividido em várias partes, sendo que cada parte trata de uma determinada funcionalidade. Dessa forma, o programa tem a sua legibilidade melhorada, uma vez que fica mais fácil entender o que o programa faz por completo ou como o programa faz determinada subtarefa. A modificabilidade do programa também é melhorada, uma vez que para se alterar determinada funcionalidade, é necessário apenas alterar um pequena parte do código, não sendo necessário modificar vários pontos do código. Como as funcionalidades do programa estando separadas logicamente, o programador tem a oportunidade de reutilizar uma parte do programa para escrever um outro programa que utilize, em uma de suas tarefas, o mesmo bloco de instruções. A confiabilidade do programa também é aumentada pelo fato dele ser mais fácil de ser entendido e corrigido. Por fim, a produtividade para a elaboração de um programa também é aumentada, uma vez que cada parte do programa pode ser trabalhada por uma equipe diferente, além de que cada equipe só precisa saber o que as partes da outra equipe fazem e não como fazem. Nas seções seguintes serão mostradas técnicas de como fazer um programa modularizado utilizando a linguagem C. 3.2 Subprogramas Um subprograma é um trecho de um programa que realiza qualquer operação computacional. Ele efetua parte de uma tarefa que um algoritmo maior deveria executar, ou seja, ele é uma parte do programa, especializado em alguma funcionalidade. Na linguagem C, o uso de funções é a principal forma de modularização. Uma função matemática f(x) é uma relação que mapeia um dado valor x de um domínio em um único valor y de um conjunto imagem. Em programação, a idéia é semelhante: uma função é um conjunto de instruções que recebe alguns valores como dados de entrada e, a partir deles, produz um valor como saída. A Figura 3.1 ilustra o funcionamento de uma função matemática. Figura 3.1: Função simples.

72 72 CAPÍTULO 3. MODULARIZAÇÃO Em programação, os dados de entrada são chamados de parâmetros e o valor da saída é chamado de retorno. Durante a execução da função, os dados de entrada são manipulados de maneira a produzir o resultado esperado. Durante essa manipulação de dados a função pode chamar outras funções, que contêm rotinas que auxiliam na elaboração do resultado final. A Figura 3.2 ilustra esse processo. Figura 3.2: Função usando outras funções. Na fabricação de um automóvel, algum processo realizado por um determinado robô pode solicitar serviços de outros robôs. Por exemplo, o robô que faz a colocação das peças no carro pode solicitar por diversas vezes o serviço do robô de soldagem de peças. Após a finalização da soldagem da peça, o robô de montagem poderá colocar outra peça no carro. 3.3 Partes de um Subprograma As partes de um subprograma são as mesmas de um programa, ou seja: cabeçalho, dicionário de dados, corpo e comentário. A seguir serão comentadas cada uma dessas partes, enfocando sua importância nos subprogramas Cabeçalho O cabeçalho do subprograma é onde estão definidos o nome do subprograma, os tipos dos seus parâmetros de entrada e o tipo de dado de retorno. Os parâmetros da função são os ingredientes que ela precisa para executar as suas instruções e seus tipos devem ser fielmente respeitados. Já o tipo de retorno da função simboliza o tipo de produto gerado por ela. Se a função diz retornar um número float, quer dizer que o programador deve esperar receber apenas esse tipo de dado e tomar as medidas necessárias para manipulá-lo posteriormente. O nome do subprograma serve para identificá-lo. O programador deve escolher nomes autoexplicativos sobre a funcionalidade da função, ou seja, para que ela serve. Isso torna o código mais legível e menos susceptível a erros por parte do programador.

73 3.3. PARTES DE UM SUBPROGRAMA 73 No Exemplo 3.1, a função calculamedia possui dois parâmetros de entrada do tipo float e retorna um valor também do tipo float. O primeiro parâmetro recebe o nome a e o segundo recebe o nome b. A função retorna um dado do tipo float, ou seja, retornará um número que contém casas decimais. Mais detalhes sobre passagem de parâmetros, assim como o retorno de funções serão mostrados posteriormente. 1 float calculamedia ( float a, float b); Exemplo 3.1: Cabeçalho de um subprograma na linguagem C. Percebe-se que o nome da função já nos dá a idéia do que que ela faz: calcula a média de dois números. Porém, maiores explicações sobre essa média, se é aritmética, geométrica ou outra, devem ser explicadas nos comentários da função. Quando a quantidade de parâmetros de uma função não é respeitada, a função não tem todos os dados necessários para a realização de suas instruções. Na função do Exemplo 3.1, quando o programador chama a função calculamedia passando apenas um número, ao invés de dois, o compilador avisa o programador sobre tal erro, não deixando que o programa seja gerado. Assim, evita-se erros de execução. Também podem ocorrer erros quando a ordem ou tipo dos parâmetros não são respeitadas. No cotidiano, também ocorrem esses tipos de erros. Por exemplo, para que um carro funcione, ele precisa, entre outras coisas, de água e óleo lubrificante. Caso a água seja colocada no lugar do óleo lubrificante, ou o contrário, é previsível que o carro, em algum momento, apresente falhas na execução de suas operações, não se locomovendo. Assim, fica fácil perceber que um bom programador deve sempre verificar se está respeitando os tipos dos parâmetros das funções Dicionário de dados O dicionário de dados é onde se faz a declaração de variáveis e constantes usadas no subprograma e não declaradas no cabeçalho da função. Quando se declara variáveis em subprogramas, estas só podem ser utilizadas naquele subprograma. Qualquer outro subprograma, mesmo aquele que chamou a função, não tem acesso a essas variáveis. Assim, por existirem e serem acessíveis apenas naquele subprograma, são chamadas de variáveis locais. Considere novamente o exemplo do funcionamento da fábrica de automóveis. Enquanto o subprograma Robô de solda estiver fazendo seu trabalho (soldagem de peças), algumas informações usadas em alguns procedimentos necessitam ser criadas, tais como: quantidade de solda, qualidade da solda, método de soldagem, etc. Quando o Robô de solda termina seu trabalho, essas informações não são mais necessárias. Note que os outros robôs não precisam saber dessas informações para poderem executar seus trabalhos. Para representar em um programa o funcionamento de uma fábrica de automóveis, cada robô poderia ser representado por um subprograma. As informações seriam representadas por variáveis locais. A Figura 3.3 ilustra essa correspondência. O Robô de pintura tem duas

74 74 variáveis: quantidade de tinta e cor da tinta. robôs; em particular, para o robô de solda. CAPÍTULO 3. MODULARIZAÇÃO Essa variáveis não são visíveis para os outros Figura 3.3: Dicionário de dados Corpo O corpo do subprograma é onde estão contidas as instruções a serem executadas pelo subprograma. Nele, podem ser realizadas operações, tais como: atribuições de valores às variáveis, chamadas de outras funções, cálculos de expressões, leitura de dados e apresentação de resultados. O Pseudocódigo 3.1 descreve uma função que calcula a distância entre dois pontos no plano cartesiano. Pseudocódigo 3.1 Passos a serem relizados pela função Processo componente " distancia ": Usar o teorema de Pitágoras para calcular a dist^ancia dos pontos dados Retornar o valor da dist^ancia FIM - Processo componente " distancia " O Exemplo 3.2 mostra a implementação na linguagem C do Pseudocódigo 3.1. O corpo é composto de atribuição de valores às variáveis dx, dy e dist através de cálculos matemáticos e pelo retorno do valor contido em dist. O retorno de dados é estudado na Seção 3.6. Essa função recebe como parâmetro as coordenadas de dois ponto no plano cartesiano e retorna a distância entre esses pontos. Para fazer o cálculo dessa distância, primeiramente calcula-se a distância entre as coordenadas x (armazenando esse valor em dx) e a distância entre as coordenadas y (armazenando esse valor em dy). Posteriormente, usa-se o teorema de Pitágoras para calcular a distância entre os pontos dados.

75 3.3. PARTES DE UM SUBPROGRAMA 75 1 float distancia ( float x1, float y1, float x2, float y2){ 2 3 float dx, dy, dist ; 4 5 dx = x2 - x1; 6 7 dy = y2 - y1; dist = sqrt (dx*dx + dy*dy); 11 return dist ; 12 } Exemplo 3.2: O corpo do subprograma são as instruções contidas nele Comentários Para melhor legibilidade e entendimento do código do subprograma, é recomendável fazer comentários. Acima de cada subprograma pode-se colocar comentários com as seguintes finalidades: dizer para quê essa função serve, quais os parâmetros que ela recebe, explicitando como devem ser esses parâmetros (unidade de medida, relevância para a função, etc), restrições para aplicações, entre outras. No dicionário de dados, os comentários podem ser usados para explicar o significado de alguma variável cujo nome não é suficientemente significativo. No corpo da função, os comentários podem ser usados para explicar o que foi feito em determinado conjunto de instruções cujo entendimento não é fácil ou que não tem utilidade aparente. Na linguagem C, os comentários dos subprogramas devem ser feitos da mesma maneira usada para comentar o programa, entre /* e */ ou após //, como explicado no Capítulo 1. O Exemplo 3.3 mostra um comentário geral da função distancia, explicando os parâmetros da função e como é feito o cálculo. 1 /* 2 Funç~ao para calcular a dist^ancia entre dois pontos no plano cartesiano. 3 Dados de entrada : 4 float x1: a coordenada x do primeiro ponto. 5 float y1: a coordenada y do primeiro ponto. 6 float x2: a coordenada x do segundo ponto. 7 float y2: a coordenada y do segundo ponto. 8 9 Dados de saída : 10 dist : retorna a dist^ancia entre os pontos passados 11 */ float distancia ( float x1, float y1, float x2, float y2){ 14

76 76 CAPÍTULO 3. MODULARIZAÇÃO 15 float dx, dy, dist ; dx = x2 - x1; dy = y2 - y1; dist = sqrt (dx*dx + dy*dy); return dist ; 24 } Exemplo 3.3: Comentários em um subprograma. 3.4 Chamada de subprogramas Em programação, quando um subprograma solicita serviços de um outro subprograma dizemos que foi feita uma chamada ao subprograma. Durante a execução de um programa, podem ser feitas diversas chamadas a um mesmo subprograma. Considere o exemplo do funcionamento da fábrica de automóveis. O Robô de Solda pode ser chamado diversas vezes durante o processo de fabricação de um carro e, é claro, em pontos de montagem diferentes. Toda vez que ele é chamado, executa novamente o seu serviço, considerando as particularidades de cada caso. Em programação acontece o mesmo. Um mesmo subprograma pode ser chamado em diversos pontos do código. A função distancia, descrita no Exemplo 3.2, pode ser usada para fazer vários cálculos de distâncias entre pontos diferentes e em vários lugares diferentes de um mesmo programa. A chamada de um subprograma na linguagem C é feita digitando o nome da função e, em seguida, digitando os dados de entrada necessários entre parênteses, para que assim o subprograma execute suas instruções. No Exemplo 3.4, a função distancia é chamada duas vezes. Na primeira vez, ela é chamada para calcular a distância entre duas cidades, sendo fornecidas as coordenadas x e y em quilômetros de distância das cidades. A segunda chamada da função é feita para calcular a distância entre dois móveis de um escritório. Note que as coordenadas passadas para o subprograma em cada momento podem ser diferentes, fazendo com que o programa calcule distâncias diferentes. A interpretação física das coordenadas passadas também pode ser diferente, não alterando a funcionalidade do subprograma. 1 # include <stdio.h> 2 # include <math.h> 3 4 float distancia ( float x1, float y1, float x2, float y2){ 5 6 float dx, dy, dist ;

77 3.5. PASSAGEM DE PARÂMETROS dx = x2 - x1; 9 10 dy = y2 - y1; dist = sqrt (dx*dx + dy*dy); return dist ; 15 } main (){ 19 float xa, xb, ya, yb, dist ; printf (" Forneça as coordenadas x e y ( em quil^ometros ) das cidades A e B, respectivamente : "); scanf ("%f%f%f%f", &xa,&ya,&xb,& yb); dist = distancia (xa,ya,xb,yb); printf (" Dist^ancia em quil^ometros entre as cidades A e B: % f\ n", dist ); printf (" Forneça as coordenadas x e y ( em metros ) da cadeira e da mesa do seu escritório : "); 30 scanf ("%f%f%f%f", &xa,&ya,&xb,& yb); dist = distancia (xa,ya,xb,yb); printf (" Dist^ancia em metros entre a cadeira e a mesa do seu escritório : % f\ n ", dist ); 35 } Exemplo 3.4: Chamadas de um mesmo subprograma 3.5 Passagem de parâmetros Os parâmetros de uma função são os dados iniciais necessários que ela possa realizar o seu trabalho. Por exemplo, a função matemática: y(x) = x 2 calcula o quadrado do número x, que foi passado como parâmetro para a função. Voltando ao exemplo da fabricação de um carro, pode-se dizer que a fábrica é o programa principal. Ao final do processo, ela deve fornecer um carro pronto para o uso. Mas a fábrica, como já foi dito, não conta apenas com um robô para fazer todo o processo e sim com vários robôs, cada um com sua função específica. Cada robô deve receber uma entrada e fornecer uma saída. Para o Robô de Pintura, as entradas são: a carroceria do carro, feita por outro robô, e a cor usada para pintá-la. A partir dessas entradas, o Robô de Pintura executa as instruções estabelecidas para pintar a carroceria. Ao final da pintura, o robô está pronto para fornecer a saída: a carroceria pintada.

78 78 CAPÍTULO 3. MODULARIZAÇÃO Essa saída pode servir como entrada para outro robô. Cada parâmetro de uma função possui um tipo, declarado no cabeçalho da função, como mostrado no Exemplo 3.1, onde os parâmetros a e b são do tipo float. Quando é feita uma chamada ao subprograma, podem ser passados como parâmetro os valores de variáveis do programa principal, que obrigatoriamente devem ser do mesmo tipo dos parâmetros da função chamada. No Exemplo 3.4, são feitas várias chamadas ao subprograma distancia. Em cada uma dessas chamadas são passadas entradas distintas, nesse caso, pontos diferentes no plano cartesiano. Considere que na primeira chamada os pontos passados são: xa = 10, ya = 30, xb = 20, yb = 60. Já na segunda chamada foram passados os pontos: xa = 15, ya = 18, xb = 5, yb = 53. A Figura 3.4 ilustra esse processo. Figura 3.4: Passagem de parâmetro. Quando o programa principal chama o subprograma distancia, os valores que estão contidos nas variáveis xa, ya, xb e yb são passados para as variáveis x1, y1, x2 e y2 do subprograma. É importante perceber que, quando é feita uma passagem de parâmetros, apenas os valores das variáveis são passados para o subprograma chamado e não as variáveis em si. Esse método de passagem de parâmetro é chamado de passagem por cópia, ou então, passagem por valor. A primeira implicação dessa regra é a a não necessidade de se usar variáveis como entrada para outra função, podendo-se usar diretamente um valor. O Exemplo 3.5 ilustra essa idéia. No programa principal é feita a chamada ao subprograma distancia passando-se os valores 10, 15, 26 e 23 como parâmetros. Na função distancia, as variáveis x1, y1, x2 e y2 recebem esses valores, para só então a função iniciar suas instruções.

79 3.5. PASSAGEM DE PARÂMETROS 79 1 # include <stdio.h> 2 3 float distancia ( float x1, float y1, float x2, float y2){ 4 5 float dx, dy, dist ; 6 7 dx = x2 - x1; dy = y2 - y1; 11 dist = sqrt (dx*dx + dy*dy); return dist ; 14 } main (){ 17 int dist ; dist = distancia (10, -15, 26, -23); } Exemplo 3.5: Passagem de parâmetros. Outra implicação do fato de que apenas valores são passados como parâmetro para uma função é a impossibilidade de mudar o conteúdo das variáveis que são usadas como entrada para a função. No Exemplo 3.6, a variável a, da função dobravalor, recebe apenas o valor da variável x da função principal. Com isso, a variável x permanece com o seu valor anterior à chamada da função dobravalor, apesar da variável a dessa função ter o seu valor alterado. 1 # include <stdio.h> 2 3 int dobravalor ( int a){ 4 printf (" Número original : %d \n", a); 5 6 a = 2* a; 7 8 printf ("O seu dobro é: %d \n", a); 9 10 return a; 11 } main (){ 14 int x = 10; dobravalor ( x); printf ("O valor de x é: %d \n", x);

80 80 CAPÍTULO 3. MODULARIZAÇÃO 19 } Exemplo 3.6: Passagem de valor na chamada de subprogramas. A saída da execução do programa do Exemplo 3.6 é mostrada no Exemplo 3.7. Note que, como esperado, a variável x permaneceu com o seu valor original. 1 Número original : 10 2 O seu dobro é: 20 3 O valor de x é: 10 Exemplo 3.7: Saída da execução do programa do Exemplo 3.6. Uma outra implicação do método de passagem de valores é a não visibilidade das variáveis do programa principal no subprograma chamado, ou seja, o subprograma não pode utilizar as variáveis do programa principal ou de algum outro subprograma. No Exemplo 3.8, as variáveis x e y não podem ser acessadas dentro da função alteravalor. Com isso, o compilador emite um erro quando o programa é compilado, dizendo que as variáveis x e y não foram declaradas anteriormente e a compilação do programa é interrompida. 1 void alteravalor ( void ){ 2 x = 20; 3 y = x *5; 4 } 5 6 main (){ 7 int x = 10, y = 15; 8 9 alteravalor (); 10 } Exemplo 3.8: Visibilidade das variáveis. O que ocorre no Exemplo 3.8 também pode ser comparado com os robôs da fábrica de automóveis. O Robô de Solda não pode usar a variável local cor do automóvel do Robô de Pintura. O Robô de Solda não sabe o que é essa variável, nem que ela existe, assim, não pode usá-la. 3.6 Retorno de dados Esta seção trata da forma como os dados são retornados em cada subprograma, isto é, como se produz uma saída da função. Um subprograma produz, no decorrer das suas instruções, um valor final (uma saída), que deverá ser passado para o programa ou mesmo subprograma que o chamou. O valor retornado pelo subprograma será atribuído a alguma variável do programa que chamou essa função, ou então usado em alguma expressão.

81 3.6. RETORNO DE DADOS 81 Na linguagem C, deve-se declarar o tipo de dado retornado pela função em seu cabeçalho e usa-se o comando return para especificar a variável ou valor que deve ser retornado. Após a sua execução, a execução da função corrente termina, mesmo que existam mais instruções após o return. Na implementação da função distancia, feita no Exemplo 3.2, o valor contido na variável dist é retornado. Como a main também é uma função, também temos que especificar o tipo de dado retornado por ela. Por padrão, na linguagem C, a função main retorna um valor tipo int. Quando o programador não coloca nenhum valor para ser retornado pela main (como no Exemplo 3.9), alguns compiladores fazem com que o valor 0 seja retornado por padrão. O valor 0 também é usado para simbolizar que nada aconteceu de errado na execução do programa. Neste livro, optou-se por não explicitar o valor retornado pela função main, já que este valor não será usado no programa. Contudo, vale lembrar que alguns compiladores exigem que o retorno da main esteja escrito no código fonte, ficando a cargo do programador a percepção da obrigatoriedade de escrevê-lo. Considere um programa que calcula a média de um aluno e diz se ele foi aprovado ou não. O Pseudocódigo 3.2 mostra os passos a serem realizados pelo programa. Pseudocódigo 3.2 Programa para calcular a média de um aluno. Funç~ao Principal : Usar o processo componente " calculamedia " passando - se as 2 notas de provas do aluno como par^ametros. Verificar se a média retornada é maior ou igual a sete. Caso afirmativo : O aluno está aprovado. Caso negativo : Usar o processo componente " calculamedia " passando - se a média anterior e a nota da prova final do aluno como par^ametros. Verificar se a nova média retornada é maior ou igual a cinco. Caso Afirmativo ; O aluno está aprovado. Caso negativo : O aluno está reprovado. FIM - Funç~ao Principal Processo componente " calculamedia ": Somar os dois números passados como par^ametros e dividir o resultado por dois. Retornar o resultado acima. FIM - Processo componente " calculamedia " Uma possível implementação do Pseudocódigo 3.2 está transcrita no Exemplo 3.9. Primeiramente, o programa pede que o usuário digite os valores das notas das duas provas realizadas pelo aluno. Essas notas são armazenadas nas variáveis nota1 e nota2 respectivamente e, logo em seguida, são passadas como parâmetros para a função calculamedia. A função calcula- Media recebe dois valores do tipo float e faz a média aritmética deles, retornando, ao final da

82 82 CAPÍTULO 3. MODULARIZAÇÃO sua execução, o valor dessa média. O valor retornado é armazenado na variável resultado da função principal, que por sua vez serve de condição para a aprovação do aluno. Caso o valor armazenado em resultado seja maior ou igual a sete, o aluno está aprovado; caso contrário, o programa pede a nota da prova final do aluno, que é armazenada na variável notafinal. As variáveis notafinal e resultado são então usadas como parâmetros da função calculamedia. Caso o resultado retornado seja maior ou igual a cinco, o aluno está aprovado. 1 # include <stdio.h> 2 3 float calculamedia ( float a, float b){ 4 float media ; 5 6 media = (a+b) /2; 7 8 return media ; 9 } main (){ 12 float nota1, nota2, notafinal, resultado ; printf (" Forneça as notas das duas provas do aluno : "); 15 scanf ("%f%f", & nota1, & nota2 ); resultado = calculamedia ( nota1, nota2 ); printf (" A média do aluno foi : % f\ n", resultado ); if ( resultado >= 7){ 22 printf (" O aluno passou de semestre.\ n"); 23 } 24 else { 25 printf (" O aluno teve que fazer prova final. Forneça a nota da prova final do aluno : "); 26 scanf ("%f", & notafinal ); if ( calculamedia ( resultado, notafinal ) >= 5){ 29 printf (" O aluno passou de semestre.\ n"); 30 } 31 else { 32 printf (" O aluno n~ao obteve o rendimento mínimo para passar de semestre. Está reprovado.\n"); 33 } 34 } 35 } Exemplo 3.9: Retorno de dados. Note que, na primeira chamada da função calculamedia, o valor retornado foi armazenado em uma variável, pois precisa ser usado posteriormente. Já na segunda chamada da função, o

83 3.6. RETORNO DE DADOS 83 valor retornado é usado diretamente na condicional de aprovação ou não do aluno, não sendo necessário seu armazenamento em alguma variável. Mas atenção: caso fosse necessário usar esse valor posteriormente, deveríamos guardá-lo em alguma variável, ao invés de chamar outra vez a função calculamedia usando os mesmos parâmetros. Essa chamada sendo feita novamente acarretaria em perda de desempenho do programa, já que a função teria que recalcular valores, o que levaria um certo tempo para ser feito. Em muitos casos é interessante, ou até necessário, que o subprograma altere as variáveis criadas no programa que o chamou. No exemplo da fábrica de automóveis é isso que deve acontecer. A Figura 3.5 ilustra esse processo. Figura 3.5: Passagem de parâmetro por referência. Nesse exemplo, o carro que é passado para o robô de solda está com várias peças soltas. Então, o robô solda essas peças e passa o carro para o robô de pintura. Nesse caso, percebese que deve-se passar o mesmo carro para o próximo robô e não apenas um cópia das suas características, que simbolizam os valores das variáveis em programas. Então, o carro recebe a pintura e é passado como parâmetro para uma próxima função, ou seja, para um outro robô. Como discutido na Seção 3.5, não é possível para um subprograma que recebe o valor de uma variável de um outro subprograma, alterar diretamente o valor da variável do subprograma que o chamou. Nesse caso, pode-se retornar o valor calculado e então armazená-lo na variável desejada. A expressão geral é x = f(x). Por exemplo, pode-se fazer uma variação do uso da função dobravalor usada no Exemplo 3.6 para alterar o valor da variável do programa que a chamou. Essa alteração está descrita no Exemplo Nesse exemplo, o valor retornado pela função dobravalor agora é armazenado na variável x. 1 # include <stdio.h> 2 3 int dobravalor ( int a){ 4 printf (" Número original : %d \n", a);

84 84 CAPÍTULO 3. MODULARIZAÇÃO 5 6 a = 2* a; 7 8 printf ("O seu dobro é: %d \n", a); 9 10 return a; 11 } main (){ 14 int x = 10; x = dobravalor ( x); printf ("O valor de x é: %d \n", x); 19 } Exemplo 3.10: Atualização da variável de outra função. A saída da execução do programa do Exemplo 3.10 é mostrada no Exemplo Note que a variável x passou a ter o valor Número original : 10 2 O seu dobro é: 20 3 O valor de x é: 20 Exemplo 3.11: Saída da execução do programa do Exemplo Encerramento antecipado de execução Observando a definição de calculamedia, implementada no Exemplo 3.9, nota-se que o valor só é retornado ao final da função, quando todas as outras instruções foram executadas. No entanto, é possível ocorrer de um subprograma ter vários pontos de retorno distintos. No Pseudocódigo 3.3 são mostrados os passos realizados pela função ehdivisor, que verifica se um número é divisor do outro. Para evitar um erro matemático, tem-se que verificar se o divisor é diferente de zero. Se for igual a zero, então a execução da função deve ser interrompida e o erro deverá ser indicado de alguma forma. Nesse caso, escolheu-se retornar o valor 0. No Exemplo 3.12 é apresentada a implementação, na linguagem C, do Pseudocódigo 3.3. Note que a função apresenta três pontos distintos de retorno. No primeiro, é verificado se o divisor é zero e caso isso seja verdade é retornado o valor 0, caso contrário, a função continua executando suas instruções. No segundo, é verificado se o resto da divisão inteira de x por y é zero, ou seja, é verificado se y é divisor de x. Caso isso seja verdade é retornado o valor 1, caso contrário, é retornado o valor 1, simbolizando que y não é divisor de x. Aqui, o valor 1 foi usado para simbolizar sucesso na verificação. Note ainda que esse recurso torna a função mais eficiente e legível.

85 3.6. RETORNO DE DADOS 85 Pseudocódigo 3.3 Função que determina se um número é divisor do outro Processo componente " ehdivisor ": Verificar se o divisor é igual a zero Caso afirmativo : Retornar o valor zero Verificar se o resto da divis~ao do dividendo pelo divisor é igual a zero Caso afirmativo : Retornar o valor 1 Caso contrário : Retornar o valor -1 FIM - Processo componente " ehdivisor " 1 int ehdivisor ( int x, int y){ 2 if (y == 0){ 3 return 0; 4 } 5 6 if (x%y == 0){ 7 return 1; 8 } else { 9 return -1; 10 } 11 } Exemplo 3.12: Encerramento antecipado para evitar erros Ainda no Exemplo 3.12, o encerramento antecipado de execução foi feito para evitar um erro na execução do programa. Porém, o encerramento antecipado de execução pode ser feito naturalmente, sem o objetivo de evitar erros de execução. Considere o caso do Exemplo 3.9. O programa principal poderia ser na verdade um subprograma que retorna 1 caso o aluno esteja aprovado e 1 caso contrário. Se o aluno obtiver média 7 apenas com as duas primeiras notas, a execução do programa é interrompida e o valor 1 é retornado. Essa alteração está descrita no Exemplo Note que não é necessário o uso do else na linha 20, pois caso a o resultado da média seja maior ou igual a sete, a função irá interromper sua execução, não acarretando o cálculo inválido da média considerando a nota da prova final do aluno, que não foi feita. 1 # include <stdio.h> 2 3 float calculamedia ( float a, float b){ 4 float media ; 5 6 media = (a+b) /2; 7 8 return media ;

86 86 CAPÍTULO 3. MODULARIZAÇÃO 9 } int aprovado ( float nota1, float nota2, float notapf ){ 12 float resultado ; resultado = calculamedia ( nota1, nota2 ); if ( resultado >= 7){ 17 return 1; 18 } if ( calculamedia ( resultado, notapf ) >= 5){ 21 return 1; 22 } 23 else { 24 return 0; 25 } 26 } main (){ 29 float nota1, nota2, notapf ; 30 int passou ; printf (" Forneça as notas das duas provas do aluno e a nota da prova final. Caso o aluno n~ao tenha feito prova final, digite zero : "); 33 scanf ("%f%f%f", & nota1, & nota2, & notapf ); passou = aprovado ( nota1, nota2, notapf ); if ( passou == 1){ 38 printf (" O aluno passou de semestre.\ n"); 39 } 40 else { 41 printf (" O aluno n~ao obteve o rendimento mínimo para passar de semestre. 42 } 43 } Está reprovado.\n"); Exemplo 3.13: Encerramento antecipado natural. 3.7 Funções sem lista de parâmetros Até agora as funções mostradas continham dados de entrada, ou seja, uma lista de parâmetros. Mas há casos em que isso não é necessário, pois a função executa suas instruções sem precisar de dados de entrada. Alguns leitores podem achar que isso faz com que o subprograma retorne sempre o mesmo dado, afinal, a ausência de dados de entrada tornaria a função uma função constante. Esse pensamento estaria correto se o subprograma não pudesse coletar dados externos,

87 3.8. FUNÇÕES SEM RETORNO DE DADOS 87 usando por exemplo a função scanf. No Exemplo 3.14 a função lernumeros não tem nenhum parâmetro de entrada. Isso foi feito pois ela sempre executará a mesma rotina: lerá 5 números digitados pelo usuário e fará a soma deles. É fácil perceber que o resultado retornado pode variar a cada chamada da função. Isso acontece pois essa função usa os dados retornados por scanf para completar suas instruções. Note que em C usa-se void na lista de parâmetros para simbolizar que a função não possui parâmetros de entrada. 1 int lernumeros ( void ){ 2 int x=0, temp, i; 3 4 for (i=0 ; i <5 ; i ++) { 5 printf (" digite um numero : "); 6 scanf ("%d", & temp ); 7 x += temp ; 8 } 9 10 return x; 11 } Exemplo 3.14: Lista de parâmetros vazia 3.8 Funções sem retorno de dados Assim como há funções que não têm nenhum parâmetro, também há funções que não retornam nenhum tipo de dado. Na linguagem C, para simbolizar essa situação usa-se void como tipo de dado retornado. O Exemplo 3.15 exibe uma função que não retorna dados para a função que a chamou. A função multiplica recebe três números e exibe o resultado da multiplicação destes. Nota-se que nenhum dado é retornado, nem mesmo o valor da multiplicação. O programador pode decidir por este tratamento caso o resultado da multiplicação não seja usado posteriormente. 1 void multiplica ( float a, float b, float c){ 2 3 printf (" Resultado = %f", a*b*c); 4 5 } Exemplo 3.15: Função sem retorno. Funções sem retorno de dados podem guardar os valores gerados em algum arquivo. Caso isso seja feito, os dados podem ser recuperados lendo-se esses arquivos. A manipulação de arquivos, tanto para leitura, tanto para escrita, é discutida na capítulo 8. Há também a opção da função não ter nenhum parâmetro de entrada e nenhum dado retor-

88 88 CAPÍTULO 3. MODULARIZAÇÃO nado. O Exemplo 3.16 mostra uma função para exibir uma saudação ao usuário. Note que a função não possui parâmetros de entradas nem valor de retorno. 1 void saudacao ( void ){ 2 printf (" Bem - vindo ao programa Cálculo Eletr^onico, onde seus cálculos s~ao resolvidos rapidamente \ npara mais informaç~oes e atualizaç~oes acesse o site : www. programaemc. com.br"); 3 4 } Exemplo 3.16: Função sem parâmetro e sem retorno. 3.9 Recursividade A recursividade ocorre quando algo é definido a partir de si mesmo. Uma recursão sempre é definida a partir de uma ou mais relações recursivas (quando o próximo elemento é definido a partir do anterior), e uma ou mais bases de recursão (pontos de parada). Em programação, o método recursivo pode ser usado para definir uma função. Por exemplo, pode-se definir uma lista como sendo a união de um elemento com uma lista. Essa lista poderia ser mais um elemento com mais uma lista ou apenas uma lista vazia. Pode-se simplificar essa definição por: lista = lista vazia base da recursao lista = elemento + lista relação recursiva Por exemplo, o cálculo do fatorial de um número inteiro não negativo pode ser implementado através de uma função recursiva. Pode-se definir o fatorial de um número n como sendo o próprio número n multiplicado pelo fatorial do número n-1. A definiçao de fatorial pode ser representada por: fatorial (0) = 1 base da recursao fatorial (n) = n * fatorial (n-1) relação recursiva Nas definições recursivas anteriores são usados critérios de parada a fim de que o processo recursivo tivesse um limite. Imagine se uma lista fosse definida somente como um elemento concatenado com uma lista. Por essa definição nunca se teria uma lista finita, pois sempre novos elementos teriam que ser adicionados. Para evitar isso usa-se um critério de parada, chamado de base da recursão. No exemplo da definição da lista, a base da recursão é uma lista vazia. No exemplo do fatorial de um número, a base da recursão pode ser o número 0, ou seja, é como se dissesse: quando chegar ao número 0 pare. O Pseudocódigo 3.4 mostra os passos a serem seguidos para o cálculo do fatorial de um número de forma recursiva.

89 3.9. RECURSIVIDADE 89 Pseudocódigo 3.4 Fatorial de um número de forma recursiva. Processo componente " fatorial ": Verificar se o número passado como par^ametro é igual a zero Caso Afirmativo : Retornar o valor 1. Caso Negativo : Retornar o número passado como par^ametro multiplicado pelo valor do fatorial do seu antecessor. FIM - Processo componente " fatorial " A implementação recursiva do fatorial na linguagem C é mostrada no Exemplo O programa chama ele mesmo quantas vezes forem necessárias até que o parâmetro n seja 0. Quando isso acontece, a função retorna 1 e a execução prossegue a partir da última chamada da função, multiplicando o valor retornado (1) pelo valor 1 da chamada anterior da função e assim sucessivamente, até se obter o fatorial do número n inicial. 1 int fatorial ( int n){ 2 if (n == 0){ 3 return 1; 4 } else { 5 return n * fatorial (n -1) ; 6 } 7 } Exemplo 3.17: Implementação recursiva do fatorial. É importante o compreender que a cada chamada do programa fatorial é reservado um novo espaço de memória, ou seja, os dados do programa fatorial anterior são preservados e caso a função recursiva tivesse variáveis locais, novas instâncias dessas variáveis seriam criadas a cada chamada, sem qualquer relação com as variáveis da outra chamada do subprograma. Por isso, funções recursivas tendem a necessitar de mais memória do computador. Chamando a função fatorial passando-se como argumento o número 5, tem-se o esquema ilustrado na Figura 3.6. As setas direcionadas para baixo significam a chamada do programa apontado, exibindo o valor passado para como parâmetro. As setas direcionadas para cima significam o retorno do dado do programa chamado, mostrando o valor retornado pela função.

90 90 CAPÍTULO 3. MODULARIZAÇÃO Figura 3.6: Fatorial utilizando recursão Implementação não recursiva equivalente Em geral, toda função recursiva pode ser implementada sem recursão, através do uso de comandos de repetição. A vantagem da versão não recursiva é a eficiência. Já a vantagem da recursiva é a elegância, legibilidade e redigibilidade. O Exemplo 3.18 mostra uma forma não recursiva da implementação da função fatorial. O comando for é utilizado para multiplicar o f por i a cada valor de i, até que i assuma o valor de n, passado como parâmetro. 1 int fatorial2 ( int n){ 2 int i, f; 3 4 f = 1; 5 6 for (i=2, i <= n; i ++) { 7 f= f* i; 8 } 9 return f; 10 } Exemplo 3.18: Implementação não recursiva do fatorial.

91 3.10. EXERCÍCIOS RESOLVIDOS Exercícios Resolvidos Exercício Resolvido Soma de inteiros Faça uma função que receba um número inteiro positivo n, calcule a soma dos n primeiros números naturais e retorne esse valor. Considere os números naturais começando do 1. Não use a fórmula de P.A. (Progessão aritmética). Solução Possível: Para produzir a soma dos n primeiros inteiros deve-se gerar a série dos n primeiros valores inteiros e acumulá-los em uma variável soma. Na Figura 3.7 é mostrado um exemplo de solução para n = 4. Note que à medida que os valores de i são gerados eles são acumulados em soma. Fica claro que é necessária a realização de uma repetição para produzir os valores de i e atualizar soma. i soma Figura 3.7: Solução para n = 4. O Pseudocódigo 3.5 mostra o algoritmo que descreve os passos a serem realizados pela função. Pseudocódigo 3.5 Passos a serem realizados pela função soma. Processo componente " soma ": Inicializar soma com o valor zero Para um loop de 1 a n: Acumular em soma o valor corrente de i Retornar o valor soma FIM - Processo componente " soma " O Exemplo 3.19 mostra a implementação desse algoritmo. Note que o pseudocódigo foi seguido fielmente, acrescentado-se apenas instruções necessárias e específicas da linguagem C.

92 92 CAPÍTULO 3. MODULARIZAÇÃO 1 int soma ( int n){ 2 int i, soma ; 3 4 soma = 0; 5 6 for ( i = 1; i <= n; i = i + 1){ 7 soma = soma + i; 8 } 9 10 return soma ; 11 } Exemplo 3.19: Exercício Resolvido - Soma de inteiros. Exercício Resolvido Múltiplos Faça um programa que leia um número natural n e dois números naturais i e j diferentes de 0 e imprima em ordem crescente os n primeiros naturais que são múltiplos de i ou de j. Resolva o problema utilizando uma função que verifique se um número é múltiplo do outro. Exemplo: Para n = 6, i = 2 e j = 3 a saída deverá ser : 0,2,3,4,6,8. Solução Possível: Para exibir os n primeiros naturais múltiplos de i e j deve-se testar os números naturais um a um, começando de zero e verificando se são múltiplos de i ou de j. Caso o número natural corrente, contido na variável natural, seja múltiplo da variável i ou j, uma variável contadora cont deverá ser incrementada e a verificação continuará com o próximo número natural. A verificação deverá acabar quando n números naturais múltiplos de i ou de j forem encontrados. Essa condição será alcançada quando a variável cont for igual a variável n, que contém o número de múltiplos a serem impressos. A Figura 3.8 mostra os passos realizados pelo programa para n = 6, i = 2 e j = 3. Para saber se um número x é múltiplo de um número y basta verificar se o resto da divisão inteira de x por y é zero. O Pseudocódigo 3.6 mostra um algoritmo que descreve os passos a serem realizados pela função.

93 3.10. EXERCÍCIOS RESOLVIDOS 93 N o Natural Múltiplo de j? Múltiplo de i? N o de múltiplos de i ou j 0 sim sim 1 1 não não 1 2 não sim 2 3 sim não 3 4 não sim 4 5 não não 4 6 sim sim 5 7 não não 5 8 não sim 6 Figura 3.8: Solução para n = 6, i = 2 e j = 3. Pseudocódigo 3.6 Passos a serem relizados pela função. Funç~ao Principal : Ler os valores de n, i e j Chamar o processo componente multiplos passando - se como par^ametros n, i e j. FIM - Funç~ao Principal Processo componente " multiplos ": Inicializar a variável do número atual e a variável indicando o npumero de m ultiplos com o valor zero. Enquanto o número de multiplos for menor que n: Verificar se o número atual é múltiplo de i ou de j através do processo componente ehmultiplo Caso afirmativo : Incrementar a variável contadora de múltiplos e exibir o valor do número atual. Incrementar a variável do número atual FIM - Processo componente " multiplos " Processo componente " ehmultiplo ": Verificar se o resto da divis~ao de x por y é zero. Caso afirmativo : Retornar o valor 1, indicando sucesso. Caso negativo : Retornar o valor 0. FIM - Processo componente " ehmultiplo " O Exemplo 3.20 mostra a implementação do algoritmo do Pseudocódigo 3.6. Na função ehmultiplo usou-se como convenção o valor 1 para indicar sucesso na verificação de x ser múltiplo de y e 0 caso contrário. Note também que no comando de repetição for da função múltiplos foi omitido o terceiro parâmetro. Isso foi necessário pois a variável cont só deverá

94 94 CAPÍTULO 3. MODULARIZAÇÃO ser incrementada se natural for múltiplo de i ou j, o que nem sempre é verdade. 1 # include <stdio.h> 2 3 int ehmultiplo ( int x, int y){ 4 5 if (x%y == 0){ 6 return 1; 7 } 8 else { 9 return 0; 10 } 11 } void multiplos ( int i, int j, int n){ 14 int natural, cont ; natural = 0; printf ("Os %d primeiros múltiplos de %d ou de %d s~ao :", n, i, j); cont = 0; 21 while ( cont < n){ if ( ehmultiplo ( natural, i) == 1 ehmultiplo ( natural, j) == 1){ 24 printf (" %d", natural ); 25 cont ++; 26 } natural ++; 29 } 30 printf ("\n"); } int main (){ 35 int n, i, j; printf (" Digite 3 números naturais, o primeiro sendo a quantidade n de múltiplos que ser~ao impressos e os dois últimos sendo os números i e j que servir~ao como base para a geraç~ao dos múltiplos : "); scanf ("%d%d%d", &n, &i, &j); multiplos (i, j, n); return 0; 44 } Exemplo 3.20: Exercício Resolvido - Múltiplos.

95 3.10. EXERCÍCIOS RESOLVIDOS 95 Exercício Resolvido Triângulo Retângulo Faça uma função que dados três números naturais, verifique se eles formam os lados de um triângulo retângulo. Os números dados simbolizam o comprimento de cada lado do triângulo. Solução Possível: Na geometria, o Triângulo Retângulo é um triângulo que possui um ângulo reto e outros dois ângulos agudos. Sabe-se ainda que o quadrado da hipotenusa (o maior lado do retângulo) é igual a soma dos quadrados dos catetos. Sendo assim, para resolver esse exercício, precisamos primeiro saber qual dos três lados dados, a, b ou c, corresponde à hipotenusa. Para tanto, compara-se todos os lados, dois a dois, colocando-se o maior lado na variável a. Como o maior lado estará guardado em a é certo dizer que os lados dados correspondem a um triângulo retângulo se, e somente se, a 2 = b 2 + c 2. O Pseudocódigo 3.7 mostra um algoritmo que descreve os passos a serem realizados pela função. Pseudocódigo 3.7 Passos a serem relizados pela função. Processo componente " trianguloretangulo ": Comparar se o lado b é maior do que o lado a Caso afirmativo, trocar os valores de a e b. Comparar se o lado c é maior do que o lado a Caso afirmativo, trocar os valores de a e c. Verificar se a* a = b* b + c* c Caso afirmativo : Retornar o valor 1 Caso negativo : Retornar o valor 0 FIM - Processo componente " trianguloretangulo " O Exemplo 3.21 mostra a implementação desse algoritmo. Note que para trocar os valores das variáveis a e b teve-se que ter uma variável auxiliar, chamada aux, para guardar o valor de a para só depois atribuir o valor da variável b para a variável a e posteriormente atribuir o valor antigo da variável a (que está guardado na variável aux) para a variável b. 1 int trianguloretangulo ( int a, int b, int c) { 2 int aux ; 3 4 if (b > a){ 5 aux = a; 6 a = b; 7 b = aux ; 8 } 9 10 if (c > a){

96 96 CAPÍTULO 3. MODULARIZAÇÃO 11 aux = a; 12 a = c; 13 c = aux ; 14 } if ( a * a == b * b + c * c){ 17 return 1; 18 } 19 else { 20 return 0; 21 } 22 } Exemplo 3.21: Exercício Resolvido - Triângulo Retângulo. Exercício Resolvido Tabuada Faça um programa que dado dois números inteiros, m e n, gere uma tabuada conforme a Figura 3.9, sendo que para esse exemplo os números fornecidos foram: 22 e x 1 = 0 1 x 1 = x 1 = 37 0 x 2 = 0 1 x 2 = x 2 = x 22 = 0 1 x 22 = x 22 = 814 Figura 3.9: Solução para m = 22 e n = 37. Solução Possível: Para gerar a tabuada proposta, é preciso perceber onde são usados os números dados. Note que a tabuada representa o resultado da multiplicação de um número x por um número y. O número x assume valores desde 0 até o segundo número dado como argumento, enquanto o número y assume valores desde 1 até o primeiro número dado como argumento. É fácil perceber que é necessário o uso de comandos de repetição para solucionar o problema. Pode-se usar 2 comandos de repetição, um contido no outro, diretamente ou indiretamente, via uma função externa. O Pseudocódigo 3.8 mostra um algoritmo que descreve os passos a serem realizados pela função.

97 3.10. EXERCÍCIOS RESOLVIDOS 97 Pseudocódigo 3.8 Passos a serem relizados pela função. Processo componente " mult ": Para um loop variando de 1 a n: Guardar o resultado de cont vezes x em r e exibir o resultado FIM - Processo componente " mult " Funç~ao Principal : Coletar os valores das duas variáveis ( n e m) Para um loop com x variando de 0 a m: Chamar processo componente " mult ", passando como par^ametro n e x FIM - Funç~ao Principal O Exemplo 3.22 mostra a implementação desse algoritmo. Note que a variável a da função mult contém o valor da variável n da função main, enquanto a variável b da função mult contém o valor da variável x da função main. 1 # include <stdio.h> 2 3 void mult ( int a, int b){ 4 int r, cont ; 5 6 for ( cont =1; cont <= a; cont ++) { 7 r = b* cont ; 8 9 printf ("%dx%d=%d\n", b, cont, r); 10 } printf ("\n"); 13 } int main ( void ){ 16 int m, n, x; printf (" Entre com dois números inteiros : "); 19 scanf ("%d %d", &n,&m); for (x =0; x <=m ; x ++) { 22 mult (n,x); 23 } return 0; 26 } Exemplo 3.22: Exercício Resolvido - Tabuada. Exercício Resolvido N primeiros números primos Seja n inteiro positivo diferente de zero. Faça um programa para exibir os n primeiros

98 98 CAPÍTULO 3. MODULARIZAÇÃO números primos. Solução Possível: Para exibir os n primeiros números primos é necessário fazer primeiro uma função que verifique se um dado número x é primo ou não. Por definição: Número primo é um número natural que pode ser dividido (o resto da sua divisão é zero) apenas por dois números naturais, o 1 (um) e ele mesmo, com excessão do número 1, que não é tido como primo. Sabe-se também que o único número que é primo e par ao mesmo tempo é o número 2. Logo, qualquer número que seja par (resto da divisão por 2 igual a zero) que não seja o dois, não é primo. Então, o que se tem a fazer para saber se determinado número é primo é verificar se ele é o número dois ou se ele tem apenas 2 divisores. Mas, caso o número em questão seja o número 1, múltiplo de 2 (com excessão do zero e do dois), ou tiver mais de 2 divisores, então ele não é primo. Todos os números inteiros positivos diferentes de zero têm ao menos 2 divisores: o 1 e ele mesmo, com excessão do número 1. Logo, se for encontrado mais algum divisor, o número estudado não é primo. Então, após feitas as verificações anteriores, para encontrar algum outro divisor do número, basta ir dividindo-o por todos os números ímpares maiores ou iguais a 3 e menores ou iguais a raiz quadrada do número em questão. Caso não encontremos divisores nesse intervalo, então não existem outros divisores além do 1 e o próprio número. O Pseudocódigo 3.9 mostra um algoritmo que descreve os passos a serem realizados pela função. O Exemplo 3.23 mostra a implementação desse algoritmo. Note que o valor retornado pela função ehprimo foi convencionado. Caso o número passado seja primo, a função retornará o valor um, caso contrário retornará o valor zero. O programador tem a liberdade de convencionar o valor retornado, mas deverá indicar isso no comentário do programa.

99 3.10. EXERCÍCIOS RESOLVIDOS 99 Pseudocódigo 3.9 Passos a serem relizados pela função. Processo componente " ehprimo ": Verificar se x é igual a um Caso afirmativo : Retornar o valor zero Verificar se x é igual a 2 Caso afirmativo : Retornar o valor um Verificar se x é divisível por 2 Caso afirmativo : Retornar o valor zero Para um loop com i variando de 3 a raiz quadrada de x: Verificar se x é divisível por i Caso afirmativo : Retornar o valor zero Incrementar o valor de i em 2 unidades Retornar o valor um FIM - Processo componente " ehprimo " Funç~ao Principal : Coletar o valor da variável quant que representa o número de primos a serem impressos Iniciar numero com valor 1. Para um loop com cont variando de zero a quant : Usar a funç~ao ehprimo para verificar se cont é primo Caso afirmativo ; Imprimir cont Incrementar cont FIM - Funç~ao Principal 1 # include <stdio.h> 2 # include <math.h> 3 4 /* 5 Verifica se o número passado como par^ametro é primo. 6 Entrada : um número inteiro positivo diferente de zero 7 Saída : retorna 1 para o caso do número informado for primo e zero caso contrário 8 */ 9 int ehprimo ( int x){ int i; if (x == 1){ 14 return 0; 15 } if (x == 2){ 18 return 1;

100 100 CAPÍTULO 3. MODULARIZAÇÃO 19 } if (x % 2 == 0){ 22 return 0; 23 } for (i = 3; i <= sqrt (x) ;i= i +2) { 26 if (x%i == 0){ 27 return 0; 28 } 29 } return 1; 32 } int main (){ int quant, numero, cont ; printf (" Digite a quantidade de números primos a serem impressos :"); scanf ("%d", & quant ); printf (" Os números primos s~ao :"); for ( numero = 1, cont = 0; cont < quant ; numero ++) { 45 if ( ehprimo ( numero ) == 1){ 46 printf (" %d", numero ); cont ++; 49 } 50 } printf ("\n"); return 0; 55 } Exemplo 3.23: Exercício resolvido - N primeiros números primos. Exercício Resolvido Palíndromo Um palíndromo é uma palavra, frase ou qualquer outra sequência de unidades (como uma cadeia de ADN) que tenha a propriedade de poder ser lida tanto da direita para a esquerda como da esquerda para a direita. Sabendo-se disso, faça uma função que verifique se um número inteiro é palíndromo. Exemplo: 101 é palíndromo 1012 não é palíndromo

101 3.10. EXERCÍCIOS RESOLVIDOS 101 Solução Possível: Para fazer essa função é necessário ler o número de trás para frente. Para ler o último algarismo de um número guardado numa variável x basta guardamos numa variável m o resto da divisão inteira desse número por 10. Após isso, guarda-se em x a parte inteira da divisão de x por 10, excluindo-se assim o último algarismo do número. Nesse momento a variável m possui o último algarismo original de x, enquanto esta possui agora apenas os primeiros algarismos originais, excluindo-se apenas o último. Então, multiplicando-se m por 10 e somando-se a esse resultado o resto da divisão inteira do novo valor de x por 10, teremos em m os últimos dois algarismo de x na ordem inversa. É fácil perceber que basta fazer os passos acima recursivamente para obter em m o número x original, mas na ordem inversa. Tendo-se esse número, basta compará-lo com o valor original, guardado anteriormente em uma outra variável. A Figura 3.10 ilustra o comportamento das variáveis x e m com o decorrer das iterações para um x inicial igual a A coluna x /10 mostra a parte inteira da divisão de x por 10, enquanto a coluna x % 10 mostra o resto dessa divisão. Percebe-se que o processo iterativo deve acabar quando o x final for igual a zero. Note que no final desse exemplo a variável m contém o número inicial x do processo, mostrando que é palíndromo. Iteração x inicial x / 10 x % 10 m final x final Figura 3.10: Solução para x = O Pseudocódigo 3.10 mostra o algoritmo que descreve os passos a serem realizados pela função.

102 102 CAPÍTULO 3. MODULARIZAÇÃO Pseudocódigo 3.10 Passos a serem relizados pela função. Processo componente " palindromo ": Guardar em c o valor de x Iniciar m com zero Enquanto x for diferente de zero : Fazer m igual a multiplicaç~ao de m por 10 com a soma da divis~ao inteira de x por 10. Fazer x igual a divis~ao inteira de x por 10 Verificar se m é igual a c Caso positivo : Retornar o valor um Caso negativo : Retornar o valor zero. FIM - Processo componente " palindromo " O Exemplo 3.24 mostra a implementação desse algoritmo. Note que a variável c guarda o valor inicial de x e que esta variável é comparada com o valor de m ao final do processo. Aqui também usou-se a convenção de usar o valor de retorno 1 para indicar sucesso na verificação e zero caso contrário. 1 int palindromo ( int x){ 2 int c, m; 3 4 c = x; 5 6 m = 0; 7 8 while (x!= 0){ 9 m = m *10 + (x % 10) ; 10 x = x / 10; 11 } if (m == c){ 14 return 1; 15 } else { 16 return 0; 17 } 18 } Exemplo 3.24: Exercício resolvido - Palíndromo. Exercício Resolvido Máximo Divisor Comum Faça uma função que calcule o máximo divisor comum entre dois números naturais passados como parâmetros da função. Faça a função na forma recursiva e na forma iterativa. Solução Possível:

103 3.10. EXERCÍCIOS RESOLVIDOS 103 A apresentação mais simples consiste em comparar-se os números e colocar a diferença entre os dois (o menor subtraído do maior). Agora compara-se o resultado dessa subtração com o menor número anterior, repetindo-se o processo até que se obtenha igualdade entre os números nas duas colunas, que é o resultado procurado. Para melhor compreender o funcionamento do método, basta recorrer a um raciocínio muito simples. Com efeito, se observarmos com atenção a tabuada da multiplicação de um número qualquer, podemos ver que a diferença entre dois produtos é sempre um produto que figura na mesma tabuada. Portanto, se dois números forem múltiplos de um terceiro, então a sua diferença também é, o que nos permite substituir o maior deles por essa diferença, para efeitos de cálculo do MDC; e tudo isso tantas vezes quantas forem necessárias, até que se chegue a um ponto em que os dois números se identificam no mesmo, o que nos remete para a questão mais simples: qual é o MDC de dois números iguais? O que é precisamente a situação a que se chegou no nosso método. O Pseudocódigo 3.11 mostra o algoritmo que descreve os passos a serem realizados pela função iterativa. Pseudocódigo 3.11 Passos a serem relizados pela função. Processo componente " mdc_ Recursivo ": Verificar se a é igual a b Caso positivo : Retornar o valor de a Verificar se a é maior que b Caso positivo : Chamar a funç~ao mdc_ Recursivo passando - se como primeiro par^ametro o valor da subtraç~ao de a - b e como segundo par^ametro o b Caso negativo : Chamar a funç~ao mdc_ Recursivo passando - se como primeiro par^ametro o valor da subtraç~ao de b - a e como segundo par^ametro o a FIM - Processo componente " mdc_ Recursivo " O Exemplo 3.25 mostra a implementação desse algoritmo. 1 int mdc_ Recursivo ( int a, int b){ 2 if (a == b){ 3 return a; 4 } 5 6 if (a > b){ 7 return mdc (a-b, a); 8 } else { 9 return mdc (a, b-a); 10 } 11 } Exemplo 3.25: Exercício resolvido - M.D.C. (modo recursivo).

104 104 CAPÍTULO 3. MODULARIZAÇÃO O Pseudocódigo 3.12 mostra o algoritmo que descreve os passos a serem realizados pela função iterativa. Pseudocódigo 3.12 Passos a serem relizados pela função. Processo componente " mdc_ Iterativo ": Enquanto a for diferente de b faça : Verificar se a é maior que b: Caso positivo : Colocar em a o resultado da subtraç~ao a - b Caso contrário : Colocar em b o resultado da subtraç~ao b - a Retornar o valor de a FIM - Processo componente " mdc_ Iterativo " O Exemplo 3.26 mostra a implementação desse algoritmo. Note que nesse caso a implementação na forma iterativa é obtida facilmente a partir da forma recursiva. 1 int mdc_ Iterativo ( int a, int b){ 2 3 while (a!= b){ 4 if (a > b){ 5 a = a-b; 6 } else { 7 b = b-a; 8 } 9 } return a; 12 } Exemplo 3.26: Exercício resolvido - M.D.C. (modo iterativo) Resumo A modularização baseia-se na conhecida técnica de dividir para conquistar e pode ser definida como a divisão de um problema em várias partes. Cada uma dessas partes pode ser desenvolvida independentemente das outras. As partes principais de um subprograma são: cabeçalho, dicionário de dados, corpo e comentários. Os parâmetros de um subprograma são os dados iniciais necessários para a função poder realizar o seu trabalho. O subprograma pode também não receber nenhum parâmetro para realizar suas funções. Na linguagem C, quando a função não têm parâmetros usa-se void na declaração da função, em sua lista de parâmetros.

105 3.12. EXERCÍCIOS PROPOSTOS 105 Um subprograma poderá produzir no decorrer das suas instruções um valor que será retornado, chamado de valor de retorno. Na linguagem C, quando a função não produz nenhum valor de retorno usa-se void para simbolizar o tipo de dado retornado. Algumas vezes é interessante antecipar o encerramento da execução de programas. A programação recursiva é uma técnica muito utilizada na implementação de funções. Geralmente é possível criar versões recursivas e não recursivas de uma mesma função. As vantagens encontradas em um programa modularizado são: Legibilidade: facilidade de leitura e entendimento do código fonte; Manutenibilidade: facilidade de modificar o código fonte; Reusabilidade: facilidade de reutilizar total ou parcialmente o código fonte; Confiabilidade: confiável; como o programa é fácil de ser entendido e corrigido, torna-se mais Produtividade: o programa pode ser dividido em módulos e cada módulo pode ser trabalhado por uma equipe diferente. Além disso, uma equipe só precisa saber o que determinado módulo de outra equipe faz e não como faz; 3.12 Exercícios Propostos 1. (a) Construa uma função encaixa que, dados dois inteiros positivos a e b, verifica se b corresponde aos últimos dígitos de a. Ex.: a b =>encaixa =>encaixa =>não encaixa =>não encaixa (b) Usando a função do item anterior, faça um programa que lê dois inteiros positivos a e b e verifica se o menor deles é segmento do outro. Exemplo: a b => b é segmento de a => a é segmento de b => um não é segmento do outro

106 106 CAPÍTULO 3. MODULARIZAÇÃO 2. Faça uma função que receba como argumento os lados de um triângulo e calcule o seu perímetro e em seguida o valor da área. 3. Considere o valor de π = Construa uma função para calcular a área de um círculo tendo como dado de entrada, o valor do raio. Em seguida, fazer outra função para calcular o raio do círculo que possui como área, a metade da área calculada anteriormente. 4. Faça uma função que receba como parâmetro uma quantia em reais (valor inteiro). Calcule o número de cédulas de cada tipo (1, 2, 5, 10, 20, 50, 100), necessário para pagar a quantia. Exiba apenas o número não nulo de cédulas de cada tipo. 5. Faça uma função que receba como entrada dois valores inteiros, a e b, e exiba a sequência de números pares do intervalo (a, b), com a < b. 6. Uma loja de material de construção precisa de um programa para saber a quantidade de metros quadrados (m2) que devem ser usados para colocar piso nas casas dos clientes. É fornecido o número de cômodos, os lados dos quadriláteros de cada cômodo e o preço do piso que o cliente escolheu. Faça uma função que receba essas informações como dados de entrada e imprima a quantidade de piso (em m2 ) e o valor da compra. Obs: Todos os cômodos da casa têm a forma de quadriláteros. 7. Faça uma função que calcule e retorne o n-ésimo termo de uma PA (progressão aritmética) sendo fornecidos como entrada o número de termos, o primeiro termo e a razão. 8. Elabore uma função em C que calcule o valor aproximado de π com precisão de cinco décimos através da série: π = Considere que a precisão de cinco décimos requer que a soma dos elementos da série só deve ser interrompida quando o valor do termo é inferior a 0, Para implementar a função, use uma subfunção que, dados x e y, calcule x y.

107 3.13. TRABALHOS SUGERIDOS Trabalhos Sugeridos 1. Aplicando Conhecimentos de Cálculo Em um belo dia, um preguiçoso monitor de Cálculo teve uma simples idéia para facilitar seu trabalho. Percebendo que vários alunos tinham dúvidas sobre derivadas e integrais de polinômios, ele resolveu pedir a um programador (nesse caso, você) um algoritmo que fizesse esse trabalho. Por experiência, o monitor restringiu o programa a polinômios de até grau nove e passou as seguintes especificações ao programador: Exemplos de Dados de Entrada Uma função seria digitada da seguinte maneira: (Opção 1) +2x4+0x3-25x2+1x1-5x0= ou seja, a leitura deverá ser pelo teclado e sempre na mesma ordem: <operador> <coeficiente> <variável> <potência>. Observação: a leitura do teclado deve ser feita termo a termo, por exemplo, +2x4 [enter] +0x3 [enter]. Apenas o caractere = poderá ser lido separadamente. Uma função f(x) = 2x 4 25x 2 + x 5 seria digitada da seguinte maneira: (Opção 2) +2x4-25x2+1x-5= Ou seja, a leitura deverá ser pelo teclado, porém com algumas diferenças em relação à opção anterior. Serão omitidos a potência 1, a variável com potência 0 e os termos com coeficiente 0. Observação: nesse caso a leitura também deve ser feita termo a termo, porém, deve-se lembrar que alguns termos podem ser menores que outros ( +2x4 e 1x, por exemplo). Em ambos os casos, a flag de final de leitura será o caracter = e os coeficientes serão número reais (float). A idéia é que, se o programador optar pela segunda opção, o algoritmo deverá ser genérico ao ponto de receber as duas formas de entrada. Armazenada a função de entrada, o programa deverá imprimir, no mesmo formato de entrada, a derivada e a integral da função. Como se não bastasse a folga do monitor, ele ainda deseja que o programa imprima também as raízes das funções quando estas forem do primeiro ou do segundo grau. Exemplos de Saída Usando a função de entrada: f(x) = x 2 5x + 6 Derivada 2.00x1-5.00x0= (Opção 1) 2.00x-5.00= (Opção 2)

108 108 CAPÍTULO 3. MODULARIZAÇÃO Integral 0.33x3-2.50x2+6.00x1+C= (Opção 1) 0.33x3-2.50x2+6.00x+C= (Opção 2) Raízes (apenas para funções de primeiro e segundo grau) x1=2.00 x2=3.00 Os coeficientes e as raízes deverão ser impressos com 2 casas decimais.

109 Capítulo 4 Tipos Abstratos de Dados Co-Autores: Bruna Vello Colnago Rodrigo Lopes Batista Objetivos: Introduzir o conceito de Tipos Compostos Heterogêneos; Explicar as vantagens da utilização de Tipos Abstratos de dados; Tornar o código do programa mais compreensível; Simplificar a confecção de aplicações mais complexas. Este capítulo busca esclarecer como a criação e manipulação de novos tipos de dados permite facilitar o entendimento, a confecção e o reaproveitamento de código. 4.1 Técnicas de Programação Top-down e Bottom-up Para escrever um programa, os programadores podem adotar duas técnicas: a Top-down e a Bottom-up. A técnica Bottom-up consiste em considerar o programa como um conjunto de módulos correlacionados, implementar cada um desses módulos e depois juntá-los por meio de uma estrutura global. Já a técnica Top-down consiste em primeiramente definir a estrutura global do programa e posteriormente definir cada uma das partes que detalham as suas tarefas. Analogamente, um projetista de automóveis pode inicialmente definir como será o carro, velocidade, rapidez, rotações máximas, etc, e posteriormente confeccionar seus componentes (motor, rodas, freios, suspensão...), conforme a necessidade - Top-down - ou confeccionar cada um dos componentes separadamente, garantir seu funcionamento e depois juntá-los em um carro cujas metas sejam compatíveis com as possibilidades dos componentes - Bottom-up. 109

110 110 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS Na abordagem Bottom-up, cada módulo é implementado segundo suas funcionalidades e objetivos, independentemente do restante do programa, assim torna-se mais simples redigir o código do programa, facilitando o trabalho do programador. Além do mais, a separação do programa em módulos permite a realização de testes específicos, facilitando a depuração de erros. Outra importância dessa abordagem é a facilidade de alterar partes do programa, sendo necessário alterar apenas alguns módulos sem se importar com os demais. Por fim, o programa torna-se muito mais fácil de ser entendido, ao passo que o leitor não precisa ler todo o código do programa, bastando saber as funcionalidades de cada módulo. A implementação dos mesmos importa somente ao programador. Assim, a abordagem Bottom-up é mais vantajosa em relação à Top-down, ao passo que é muito mais intuitiva ao programador. 4.2 Tipos Compostos Heterogêneos (Estruturas) As formas de representação de dados fornecidas pelo computador são insuficientes para que o programador possa representar em sua totalidade as possíveis coleções de dados existentes. Programadores há muito tempo reconhecem o valor de se organizar itens de dados correlacionados em uma única entidade computacional. As Estruturas são entidades que representam tipos de dados e que permitem o agrupamento de várias variáveis de diversos tipos. Suponha um programa que faça o cadastro de estudantes de uma universidade, cada um possuindo um conjunto de atributos correlacionados, como por exemplo: matrícula, idade, coeficiente de rendimento e período. Utilizando as entidades de programação já conhecidas, no cadastro de um único aluno seriam necessárias quatro variáveis para representar todos os seus atributos; para dois estudantes, seriam necessárias oito variáveis; já para diversos estudantes seria necessário um conjunto de variáveis consideravelmente grande. A utilização de muitas variáveis aumenta o trabalho do programador e dificulta que outra pessoa entenda a aplicação. Para solucionar esse problema, muitas linguagens de programação, entre elas a linguagem C, possibilitam ao programador criar tipos compostos heterogêneos. Na linguagem C, os tipos compostos heterogêneos são chamados struct (estruturas). As estruturas são conjuntos de dados correlacionados, que podem ser representados em sua totalidade por uma única variável. No exemplo mencionado, cada estudante poderia ser representado por uma única variável, dessa forma, seria necessário adicionar apenas uma variável para cada novo estudante a ser cadastrado. Dada a impossibilidade de se antecipar todos os tipos de dados utilizados por um programa, uma linguagem de programação apenas implementa tipos de dados simples. Desse modo, fica a cargo do programador usar desses dados para a definição de novos tipos de dados. Além de facilitar e tornar mais clara a implementação, os tipos compostos heterogêneos têm como finalidades principais a possibilidade de retorno de mais de um valor por função e a criação de tipos abstratos de dados. Essas características são discutidas mais adiante neste capítulo.

111 4.2. TIPOS COMPOSTOS HETEROGÊNEOS (ESTRUTURAS) Definição Uma estrutura é uma coleção de variáveis, que podem ou não ser de tipos diferentes, colocadas sob um único nome para manipulá-las. As estruturas ajudam na organização do código e facilitam a vida do programador, pois juntam variáveis relacionadas e permitem que elas sejam tratadas como uma unidade maior. Na prática, uma estrutura é uma caixa onde podem ser agrupados diversos dados correlacionados. Essa caixa, na verdade, é o conjunto de alguns bytes de memória correspondente ao somatório dos tamanhos dos dados que se pretende agrupar. A Figura 4.1 ilustra uma estrutura que representa um estudante, testudante. Note que a estrutura testudante ocupa um espaço equivalente ao espaço necessário para guardar todos os seus atributos. Figura 4.1: Estrutura testudante Sintaxe Para criar uma estrutura com n atributos, deve-se obdecer à sintaxe exposta abaixo. struct <nome da estrutura>{ <tipo do atributo 1> <nome do atributo 1>; <tipo do atributo 2> <nome do atributo 2>;... <tipo do atributo n> <nome do atributo n>; }; A palavra struct indica que a entidade criada é uma estrutura e as chaves delimitam o trecho onde os atributos da estrutura são definidos. Cada atributo é definido por seu tipo seguido de um identificador. O Exemplo 4.1 apresenta a sintaxe de uma estrutura para o caso do estudante. 1 struct testudante { 2 int idade ; 3 int matricula ;

112 112 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS 4 float coeficiente ; 5 int periodo ; 6 }; Exemplo 4.1: Sintaxe da estrutura testudante Uso Como as estruturas são conjuntos de dados, existem duas formas básicas de manipulá-las, selecionando um único elemento de dado de forma seletiva, ou manipulando toda a estrutura de forma integral. Seletivo Para manipular cada um dos atributos da estrutura se utiliza o mecanismo de seleção conforme a sintaxe apresentada a seguir. <nome da variável>.<nome do atributo> O Exemplo 4.2 ilustra o uso seletivo da estrutura estudante. Nesse Exemplo, a leitura de dados é feita atribuindo-se cada valor lido a um atributo da estrutura. 1 # include <stdio.h> 2 # include < stdlib.h> 3 4 struct testudante { 5 int idade ; 6 int matricula ; 7 float coeficiente ; 8 int periodo ; 9 }; main (){ 12 struct testudante aluno ; 13 printf (" Digite os dados do estudante "); 14 scanf ("%d %d %f %d",& aluno. idade,& aluno. matricula,& aluno. coeficiente,& aluno. periodo ); 15 } Exemplo 4.2: Acesso seletivo a atributos da estrutura. Integral Quando se deseja manipular a estrutura como um todo, se utiliza simplesmente o nome da variável onde a mesma está armazenada. O uso integral em atribuições é descrito pela sintaxe a seguir.

113 4.2. TIPOS COMPOSTOS HETEROGÊNEOS (ESTRUTURAS) 113 <nome da variável 1> = <nome da variável 2> O Exemplo 4.3 ilustra o uso integral da estrutura estudante. 1 # include <stdio.h> 2 # include < stdlib.h> 3 4 struct testudante { 5 int idade, matricula, periodo ; 6 float coeficiente ; 7 }; 8 9 main (){ 10 struct testudante aluno, outro ; 11 printf (" Digite os dados do estudante "); 12 scanf ("%d %d %f %d",& aluno. idade,& aluno. matricula,& aluno. coeficiente,& aluno. periodo ); 13 outro = aluno ; 14 } Exemplo 4.3: Acesso integral à estrutura No Exemplo 4.3, ao final da execução do programa os valores dos atributos de outro são iguais aos valores dos atributos de aluno. O comando typedef pode ser utilizado para renomear tipos simples ou compostos. Para utilizar o comando typedef, deve-se obdecer à sintaxe exposta abaixo. typedef <nome do tipo simples ou composto> <novo nome>; Na prática, a renomeação de tipos facilita o entendimento e a escrita de código. O Exemplo 4.4 apresenta a utilização do typedef. 1 typedef struct testudante testudante ; 2 3 typedef float distancia ; Exemplo 4.4: Uso do comando typedef O Exemplo 4.5 apresenta a adaptação do Exemplo 4.3 para a utilização do comando typedef, permitindo que o tipo composto heterogêneo struct testudante seja utilizado através da palavra struct testudante. 1 # include <stdio.h> 2 # include < stdlib.h> 3 4 struct testudante { 5 int idade ;

114 114 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS 6 int matricula ; 7 float coeficiente ; 8 int periodo ; 9 }; typedef struct testudante testudante ; main (){ 14 testudante aluno, outro ; 15 printf (" Digite os dados do estudante "); 16 scanf ("%d %d %f %d",& aluno. idade,& aluno. matricula,& aluno. coeficiente,& aluno. periodo ); 17 outro = aluno ; 18 } Exemplo 4.5: Acesso integral à estrutura utilizando o comando typedef Simplificação na Passagem de Parâmetros e Retorno de Função Conforme comentou-se anteriormente, as estruturas são conjuntos de dados inter-relacionados, dessa forma a passagem de parâmetros de função é simplificada, pois ao invés de cada um dos atributos ser um parâmetro da função, apenas a estrutura é passada. Note a diferença entre a lista de parâmetros das duas funções representadas no Exemplo void funcao1 ( int idade, int matricula, float coeficiente, int periodo ){... } 2 3 void funcao2 ( testudante aluno ){... } Exemplo 4.6: Passagem de estrutura por parâmetro. A mesma função pode ser escrita dos dois modos apresentados no Exemplo 4.6 na funcao1 quatro variáveis relacionadas ao estudante são passadas como parâmetro; na funcao2, o próprio estudante é passado como parâmetro. A funcao2 é mais compreensível que a funcao1, pois a pessoa que ler o código da funcao1 terá que inferir que aquelas quatro variáveis referem-se a um mesmo estudante. Da mesma forma, o retorno de funções pode ser simplificado pelo uso de estruturas. Suponha que se deseje fazer a leitura de dados de um estudante. O Exemplo 4.7 mostra duas maneiras de fazer isso: utilizando funções que retornam estruturas ou funções que retornam tipos simples. 1 # include <stdio.h> 2 # include < stdlib.h> 3 4 struct testudante { 5 int idade ; 6 int matricula ; 7 float coeficiente ;

115 4.2. TIPOS COMPOSTOS HETEROGÊNEOS (ESTRUTURAS) int periodo ; 9 }; typedef struct testudante testudante ; int leperiodo (){ 14 int periodo ; 15 scanf ("%d",& periodo ); 16 return periodo ; 17 } int lematricula (){ 20 int matricula ; 21 scanf ("%d",& matricula ); 22 return matricula ; 23 } float lecoeficiente (){ 26 float coeficiente ; 27 scanf ("%f",& coeficiente ); 28 return coeficiente ; 29 } int leidade (){ 32 int idade ; 33 scanf ("%d",& idade ); 34 return idade ; 35 } testudante leestudante (){ 38 testudante aluno ; 39 scanf ("%d %d %f %d",& aluno. idade,& aluno. matricula,& aluno. coeficiente,& aluno. periodo ); 40 return aluno ; 41 } main (){ 44 testudante aluno1, aluno2 ; 45 aluno1. periodo = leperiodo (); 46 aluno1. matricula = lematricula (); 47 aluno1. coeficiente = lecoeficiente (); 48 aluno1. idade = leidade (); 49 aluno2 = leestudante (); 50 } Exemplo 4.7: Estrutura como retorno de função. No Exemplo 4.7 é realizada a leitura de dados de aluno1 sem utilizar o retorno de estruturas, e de aluno2, utilizando-o. Para realizar a leitura de aluno1 foram necessárias a declaração de quatro funções diferentes, uma para cada atributo, e quatro chamadas de função no programa principal. Para realizar a leitura de aluno2 foi necessário apenas a declaração de uma função

116 116 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS e uma chamada de função no programa principal. Assim, pode-se perceber que o retorno de estruturas torna o código mais compacto, legivel e eficiente, por ter menos chamadas de função. 4.3 Tipos Abstratos de Dados Tipos abstratos de dados (TADs) são novos tipos de dados implementados pelo programador, nos quais ele define tanto as estruturas de dados quanto as operações a elas aplicáveis, conforme suas necessidades para a resolução de um determinado problema. Os TADs podem ser considerados generalizações de tipos primitivos de dados, assim como funções são generalizações de operações primitivas, tais como adição, subtração e multiplicação. Da mesma forma que funções podem ser usadas para encapsular partes de algoritmos, o TAD pode ser usado para encapsular tipos de dados. O TAD pode ser discutido pela perspectiva do implementador e do usuário do tipo. O implementador cria as estruturas de dados, implementa as funções para manipulá-las. Já o usuário utiliza esse TAD como se fosse um tipo de dados fornecido pela linguagem de programação. Deste modo, o usuário só deve manipular os atributos do TAD através das funções definidas pelo implementador do tipo Definição Um tipo abstrato de dados é um tipo de dado definido em termos do seu comportamento e não em termos de sua representação. A idéia de tipo abstrato de dados é desvincular o tipo de dado (valores e operações) de sua implementação, ou seja, quando definimos um tipo abstrato de dados estamos preocupados com o que ele faz e não como ele faz. Os programas que usam um determinado tipo abstrato de dados são chamados clientes; e o programa que define sua estrutura e comportamento é conhecido como implementação. Um TAD pode ser definido como a composição de uma estrutura de dados e das operações definidas sobre estes dados. Exemplo da Definição de um Tipo Composto com Operações Pré-definidas Suponha que se deseja definir um tipo abstrato de dados tdata, considerando os atributos dia, mês e ano. Primeiramente, deve-se decidir as operações a serem definidos para o TAD, que podem ser as seguintes: inicializadata: função que inicializa uma data a partir de valores passados como parâmetro. ledata: função que inicializa uma data a partir de valores lidos do teclado. alteradata: função que altera uma data a partir de valores passados como parâmetro. ebissexto: função que indica se um ano é bissexto ou não. diasnomes: função que indica a quantidade de dias do mês em questão.

117 4.3. TIPOS ABSTRATOS DE DADOS 117 diaseguinte: função que altera a data para o dia seguinte. Pode-se então implementar o TAD na linguagem desejada. No Exemplo 4.8, observa-se a implementação do TAD tdata na linguagem C. 1 # include <stdio.h> 2 3 typedef struct data { 4 int dia ; 5 int mes ; 6 int ano ; 7 } tdata ; 8 9 tdata inicializarvalores ( int d, int m, int a){ 10 tdata dt; dt.dia =d; 13 dt.mes =m; 14 dt.ano =a; 15 return dt; 16 } tdata ledata (){ 19 tdata d; printf (" Entre com a data "); 22 scanf ("%d %d %d",&d.dia,&d.mes,&d. ano ); 23 return d; 24 } tdata alteradata ( int d,int m,int a){ 27 tdata dt; dt.dia =d; 30 dt.mes =m; 31 dt.ano =a; 32 return dt; 33 } int ebissexto ( tdata d){ 36 if(d. ano %400==0) { 37 return 1; 38 } else if(d. ano %100==0) { 39 return 0; 40 } else if(d. ano %4==0) { 41 return 1; 42 } else { 43 return 0; 44 } 45 } 46

118 118 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS 47 int diasnomes ( tdata d){ 48 if(d. mes ==4 d. mes ==6 d. mes ==9 d. mes ==11) { 49 return 30; 50 } else { 51 if(d. mes =2) { 52 if( ebissexto (d)){ 53 return 29; 54 } else { 55 return 28; 56 } 57 } else { 58 return 31; 59 } 60 } 61 } tdata diaseguinte ( tdata d){ 64 if(d.dia < diasnomes (d)){ 65 d. dia ++; 66 } else { 67 d. dia =1; 68 if (d.mes <12) { 69 d. mes ++; 70 } else { 71 d. mes =1; 72 d. ano ++; 73 } 74 } 75 return d; 76 } main (){ 79 } Exemplo 4.8: Definição do TAD tdata. O código do Exemplo 4.8 ilustra a definição de um Tipo Composto com Operações Prédefinidas. O tipo tdata contém os atributos de uma data. Uma variável desse tipo é inicializada através da função inicializarvalores, essa função permite inicialização dos atributos sem que estes sejam acessados. A função de leitura ledata obtém uma data do teclado e retorna uma variável do tipo tdata contendo os valores lidos. A função alteradata apresenta o mesmo código que a função de inicialização, contudo sua utilização destina-se a datas já inicializadas. A função ebissexto verifica se uma dada data está em um ano bissexto, o algoritmo considera a seguinte regra: São bissextos todos os anos múltiplos de 400, não são bissextos todos os múltiplos de 100 e não de 400, são bissextos todos os múltiplos de 4 e não múltiplos de 100, por fim, não são bissextos todos os demais anos. A função diasnomes determina o número de dias de um do mês de uma determinada data. Os meses de abril, junho, setembro e novembro possuem 30 dias. Caso a data seja do mês de fevereiro, verifica-se se o ano é bissexto, se for bissexto o mês

119 4.3. TIPOS ABSTRATOS DE DADOS 119 tem 29 dias, se não for bissexto o mês tem 28 dias. Caso a data não esteja em nenhum dos meses citados, o mês apresenta 31 dias. Importância de Definir Tipos com Operações Próprias A criação de um conjunto de operações próprias de um TAD tem como finalidade torná-lo, aos olhos do usuário, um tipo de dados da própria linguagem, ou seja, o usuário não deve se preocupar em como são implementados os TADs. Além do mais, utilizar as operações do TAD torna o código mais confiável. Suponha uma aplicação que utilize o TAD tdata conforme o Exemplo # include <stdio.h> 2 3 // A definiç~ao do TAD, bem como suas operaç~oes, está no Exemplo main (){ 6 tdata d; 7 8 d= ledata (); 9 printf (" Passou um dia!\n"); 10 d. dia ++; 11 printf (" Hoje é %d/%d/%d.\n",d.dia,d.mes,d. ano ); 12 } Exemplo 4.9: Problema de Confiabilidade no uso do TAD tdata. Observe que, no Exemplo 4.9, a intenção do programador foi adicionar um dia à data lida, entretanto, este não utilizou a função do tipo diaseguinte. Para grande parte dos valores de data, o programa funcionaria corretamente, entretanto, para datas que são o último dia do mês, esse programa gera inconsistência, pois a data obtida não será uma data válida. No decorrer da aplicação, um valor inválido de data será repassado para outras partes do programa. Assim essa inconsistência pode ter um efeito colateral que acarrete em problemas no código. Sendo assim, é altamente recomendável que se utilizem apenas as operações já definidas para manipular as estruturas do TAD e que não ocorra a manipulação de atributos. Vantagens de Usar TADs As vantagens de utilizar um TAD são: Facilidade de Manutenção: Pode-se alterar o tipo usado sem alterar a aplicação. Por exemplo, pode-se incluir novos atributos ou operações sem que o código que utilize o tipo seja alterado. Reutilização: Um TAD bem generalizado pode ser utilizado em diversas aplicações. Abstração: A abstração de informações através do TAD permite a melhor compreensão dos algoritmos e maior facilidade de programação.

120 120 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS Ocultamento: Separa o código de implementação do código de uso do TAD, que funciona como um tipo de dados fornecido pela linguagem. Integridade: A manipulação dos atribuitos por operações definidas sobre o tipo impedem a ocorrência de inconsistências. Na prática, essas vantagens tornam o código mais fácil de se escrever, mais compreensível para quem lê e mais fácil de se modificar. Problema do uso de TADs em C A idéia do TAD é esconder o código da implementação do usuário, que não deve ter acesso indiscriminado aos atributos do TAD. A linguagem C, entretanto, não oferece a funcionalidade de proteção ao acesso aos atributos e operações. Na verdade, C não implementa TAD, o que se faz é apenas uma simulação, visto que apesar de se definir as operações sobre o tipo, o usuário tem a liberdade para acessar diretamente os atributos sem nenhuma proteção, tornando o código não confiável Definição de Atributos de um TAD Os atributos de um TAD são os dados que se relacionam a ele. Por exemplo, no caso do estudante, seus atributos são: idade, matrícula, coeficiente de rendimento e período. Conforme definido na Seção Definição de Operações de um TAD As operações de um TAD são funções que utilizadas para acesso dos dados. Estas operações visam impedir o acesso direto aos dados, assim, o usuário só deve acessar os dados através dessas operações. Tipos de Operações de um TAD Um TAD não é definido por seus atributos, mas sim por suas operações. As operações são a interface do programador usuário com a própria representação interna. Existem cinco tipos diferentes de operações que podem ser realizadas sobre um TAD: Construtoras Operações construtoras são aquelas que inicializam variáveis, logo devem ser utilizadas antes de qualquer outra para garantir que o TAD foi inicializado corretamente. Observe no Exemplo 4.10 diferentes implementações de funções construtoras para o TAD estudante. 1 testudante inicializar (){ 2 testudante novo ;

121 4.3. TIPOS ABSTRATOS DE DADOS novo. idade =0; 5 novo. matricula =0; 6 novo. coeficiente =0; 7 novo. periodo =0; 8 return novo ; 9 } testudante inicializarvalores ( int idade, int matricula, float coeficiente, int periodo ){ 12 testudante novo ; novo. idade = idade ; 15 novo. matricula = matricula ; 16 novo. coeficiente = coeficiente ; 17 novo. periodo = periodo ; 18 return novo ; 19 } Exemplo 4.10: testudante. Diferentes formas de Implementação da função construtora para o TAD A função inicializar inicia com zero todos os atributos de uma variável do tipo testudante, esta variável é retornada pela função de inicialização. A função inicializarvalores exerce o mesmo papel de inicializar que a função inicializar. Contudo, esta recebe os valores a serem incializados como parâmetros da função. Analisadoras As operações analisadoras ou consultoras analisam o conteúdo de um TAD e retornam propriedades, ou seja, essas operações obtêm informações do TAD. O Exemplo 4.11 mostra uma função anasiladora. 1 int bomaluno ( testudante aluno ){ 2 if ( aluno. coeficiente >7.0) { 3 return 1; 4 } else { 5 return 0; 6 } 7 } Exemplo 4.11: Função analisadora. Observa-se, no Exemplo 4.11, que a função bomaluno é um exemplo de função analisadora para o TAD testudante, pois retorna uma propriedade do mesmo (se é um bom aluno).

122 122 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS Modificadoras As operações modificadoras, ou atualizadoras, permitem alterações de atributos do TAD. O Exemplo 4.12 mostra uma função modificadora para o TAD testudante. 1 testudante alteraidade ( testudante aluno, int novaidade ){ 2 aluno. idade = novaidade ; 3 return aluno ; 4 } Exemplo 4.12: Função modificadora. Produtoras As operações produtoras são aquelas que, a partir dos dados de um TAD, produzem uma nova informação. A função maioridade, do Exemplo 4.13, produz um resultado (a maior idade) a partir de dois estudantes dados. 1 int maioridade ( testudante est1, testudante est2 ){ 2 if ( est1. idade > est2. idade ){ 3 return est1. idade ; 4 } 5 return est2. idade ; 6 } Exemplo 4.13: Exemplo de função produtora. Destrutoras As operações destrutoras são utilizadas para liberar recursos de memória quando o TAD não é mais necessário. Exemplos e explicações sobre essa função são dados no Capítulo Uso do TAD Considere o Exemplo 4.14 que faz uso do TAD tdata. Note que o TAD é acessado apenas pelas operações pré-definidas. 1 # include <stdio.h> 2 3 // A definiç~ao do TAD, bem como suas operaç~oes, está no Exemplo main (){ 6 tdata data ; 7 int anobissexto ; 8 int ndias ;

123 4.4. EXERCÍCIOS RESOLVIDOS data = ledata (); 11 anobissexto = ebissexto ( data ); if( anobissexto == 1){ 14 printf (" Ano Bissexto "); 15 } else { 16 printf (" Ano n~ao Bissexto "); 17 } ndias = diasnomes ( data ); 20 printf (" Número de dias no m^es :",ndias ); 21 } Exemplo 4.14: Uso do TAD tdata. O Exemplo 4.14 determina se uma data digitada pelo teclado é de um ano bissexto e o número de dias no mês da data digitada. Pode-se afirmar que o programa usuário manipula o TAD somente através das operações pré-definidas pelo implementador, assim o código usuário fica mais legível. Pode-se observar que as operações separam o código usuário do código de implementação do TAD. A redigibilidade também aumenta, visto que o acesso aos dados é realizado apenas por simples operações. O código também fica mais confiável, pois o usuário não altera livremente os dados, isso só pode ser feito através das operações do TAD. E, por fim, caso a implementação do TAD precise ser alterada, o código usuário não sofrerá mudanças, a menos que os cabeçalhos das funções sejam alterados Tipos de TADs Os TADs podem se dividir em TADs de Domínio e TADs Implementacionais, conforme sua relação com o problema ou com sua solução. TADs de Domínio São aqueles que definem um tipo de dados que está no domínio do problema. Como por exemplo, os TADs testudante e tdata. TADs Implementacionais São de objetos de programação que não tem relação direta com o problema, mas sim com sua implementação. Podemos tomar como exemplos as listas, as árvores, os grafos e as filas. Alguns desses conceitos são apresentados nos próximos capítulos. 4.4 Exercícios Resolvidos Exercício Resolvido4.1 - Leitura da Estrutura tdata

124 124 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS Faça um programa que imprima de uma estrutura do tipo data, a data de nascimento 30/01/1984. Solução Possível: Não existem dados de entrada, a data de nascimento a ser informada é armazenada diretamente em uma estrutura data. A impressão da data é realizada no formato abreviado utilizando o comando printf. A saída do programa é a impressão da data 30/01/ # include <stdio.h> 2 3 struct data { // estrutura do tipo data 4 int dia ; 5 int mes ; 6 int ano ; 7 }; 8 9 int main (){ 10 struct data datanascimento ; // inicializa a estrutura data de nascimento 13 datanascimento. dia = 30; 14 datanascimento. mes = 01; 15 datanascimento. ano = 1984; 16 printf (" Data : %d/%d/%d\n", datanascimento.dia, datanascimento.mes, datanascimento. ano ); 17 return 0; 18 } Exercício Resolvido Operações sobre a Estrutura Data Defina, agora, a estrutura do exemplo anterior como um tipo e realize operações (sobre este tipo) de edição e consulta. Solução Possível: Este programa utiliza operações de alteração e consulta sobre o TAD para datas. A estrutura para datas é declara como um tipo utilizando o comando typedef. A operação alteradata recebe como parâmetros o dia, mês e ano de uma determinada data e passa, como retorno de função, a data informada. A operação consultadata realiza a impressão de uma data. Assim, pode-se observar as vantangens da utilização de TADs, através destas duas operações é necessário escrever menos, visto que as operações podem ser aproveitadas. A data é inicializada no corpo do programa em 30/01/1984, em seguida é alterada. A saída do programa é a impressão da data 16/05/ # include <stdio.h> 2

125 4.4. EXERCÍCIOS RESOLVIDOS typedef struct data { 4 int dia ; 5 int mes ; 6 int ano ; 7 } tdata ; 8 9 tdata alteradata ( int d,int m,int a){ 10 tdata dt; dt.dia =d; 13 dt.mes =m; 14 dt.ano =a; 15 return dt; 16 } void consultadata ( tdata data ) { 19 printf (" Data : %d/%d/%d\n", data.dia, data.mes, data. ano ); 20 } int main () { 23 tdata datanascimento ; datanascimento = alteradata (30,01,1984) ; 26 datanascimento = alteradata (16,05,2007) ; consultadata ( datanascimento ); 29 return 0; 30 } Exercício Resolvido Menor Data Escreva um programa em C que leia duas datas e retorne a menor data cronologicamente. Observação: As datas devem ser armazenadas em estruturas. Solução Possível: O programa que determina qual é a menor data entre duas. Agora, a inicialização das datas é realizada por leitura do teclado. O retorno da operação menordata é a menor entre duas datas, a primeira verificação é realizada quanto ao ano, caso o ano das datas sejam iguais verifica-se o mês, se os meses são iguais verifica-se os dias. 1 # include <stdio.h> 2 3 struct data { // estrutura do tipo data 4 int dia ; 5 int mes ; 6 int ano ; 7 }; 8

126 126 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS 9 struct data menordata ( struct data data0, struct data data1 ){ 10 if ( data0. ano < data1. ano ){ 11 return data0 ; 12 } else if ( data1. ano < data0. ano ){ 13 return data1 ; 14 } else if ( data0. mes < data1. mes ){ 15 return data0 ; 16 } else if ( data1. mes < data0. mes ){ 17 return data1 ; 18 } else if ( data0. dia < data1. dia ){ 19 return data0 ; 20 } else { 21 return data1 ; 22 } 23 } struct data ledata (){ 26 struct data d; printf (" Entre com a data "); 29 scanf ("%d %d %d",&d.dia,&d.mes,&d. ano ); 30 return d; 31 } main (){ 34 // declaraç~ao de variaveis 35 struct data data0, data1, menor ; // entrada das datas 38 printf (" Primeira data :\n"); 39 data0 = ledata (); 40 printf ("\ nsegunda data :\n"); 41 data1 = ledata (); // determina a menor data 44 menor = menordata ( data0, data1 ); printf ("\na menor data eh: %d/ %d/ %d.\n", menor.dia, menor.mes, menor. ano ); 47 } Exercício Resolvido TAD Tponto Defina o tipo Tponto para representar os pontos do plano cartesiano de duas dimensões. Solução Possível: Para representar um ponto, é criada uma estrutura com dois atributos, as coordenadas x e y.

127 4.4. EXERCÍCIOS RESOLVIDOS typedef struct { 2 int x; 3 int y; 4 } Tponto ; Exercício Resolvido Operação Tponto simetricoorigem (Tponto ponto) Agora, implemente a operação Tponto simetricoorigem (Tponto ponto), para determinar o ponto simétrico de um ponto em relação à origem. Solução Possível: A operação simetricoorigem tem como retorno uma variável do tipo Tponto. Para obter um ponto simétrico a outro em relação a origem, basta inverter as coordenadas do ponto dado. 1 Tponto simetricoorigem ( Tponto ponto ){ 2 ponto.x = -ponto.x; 3 ponto.y = -ponto.y; 4 5 return ponto ; 6 } Exercício Resolvido Operação int qualquadrante (Tponto ponto) Agora, implemente a operação int qualquadrante (Tponto ponto) que determina em qual quadrante pertence um ponto. Solução Possível: Dado um ponto, a operação qualquadrante retorna a qual quadrante esse ponto pertence. A função retorna 0 quando o ponto for parte do limite de quadrantes, 1 quando o ponto pertencer ao 1 o quadrante, 2 quando pertencer ao 2 o quadrante, 3 se pertencer ao 3 o quadrante e 4 caso seja do 4 o quadrante. Para determinar a que quadrante o ponto pertence, é utilizado um conjunto de if e else aninhados que verificam os sinais das coordenadas. 1 int qualquadrante ( Tponto ponto ){ 2 if ( ponto.x == 0 ponto.y == 0){ 3 return 0; 4 } else { 5 if ( ponto.x > 0) { 6 if ( ponto.y > 0){ 7 return 1; 8 } else { 9 return 4; 10 } 11 } else {

128 128 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS 12 if ( ponto.y > 0){ 13 return 2; 14 } else { 15 return 3; 16 } 17 } 18 } Exercício Resolvido Operação float distanciapontos (Tponto a, Tponto b) Defina um programa que calcule a distância entre dois pontos, implemente e use operações construtoras e destrutoras, implemente também a operação float distanciapontos (Tponto a, Tponto b), de cálculo de distância entre dois pontos. Solução Possível: Inicialmente, são definidas a estrutura Tponto e as operações aplicaveis a ela. Além das funções relacionadas ao problema, deve-se sempre definir as funções inicializadora e destrutora. A função ler() inicializa o Tponto a partir de valores fornecidos pelo usuário, a função zera() zera as coodernadas do ponto e a função distanciapontos calcula a distancia entre pontos, dada pela raiz quadrada da soma do quadrado da diferença entre as abscissas e entre as ordenadas. A função principal utiliza os métodos declarados de forma a corresponder à funcionalidade desejada. 1 # include <stdio.h> 2 3 struct ponto { 4 int x, y; 5 } 6 7 typedef struct ponto Tponto ; 8 9 // Operaç~ao construtora : 10 Tponto ler (){ 11 Tponto ponto ; printf (" Entre com as coordenadas \ n"); 14 printf ("X: "); 15 scanf ("%d", & ponto.x); 16 printf ("Y: "); 17 scanf ("%d", & ponto.y); return ponto ; 20 } // Operaç~ao destrutora : 23 Tponto zera (){ 24 Tponto origem = {0, 0};

129 4.5. RESUMO return origem ; 27 } // Operaç~ao Produtora : 30 float distanciapontos ( Tponto a, Tponto b){ 31 float distancia, deltax, deltay ; deltax = a. x - b. x; 34 deltay = a. y - b. y; distancia = sqrt ( deltax ^2 + deltay ^2) ; 37 return distancia ; 38 } main (){ 41 Tponto a, b; 42 float distancia ; a = ler (); 45 b = ler (); distancia = distanciapontos (a, b); 48 printf (" Distancia entre pontos : % f\ n", distancia ); a = zera (); 51 b = zera (); 52 } 4.5 Resumo Inicialmente foram abordadas as seguintes abordagens na programação: bottom-up: que é uma técnica que propõe considerar o programa como um conjunto de módulos correlacionados, implementar cada um desses módulos e depois juntá-los por meio de uma estrutura global. top-down: que é uma técnica que proporciona que um programa seja visto como uma descrição de um processo. O processo é dividido em subprogramas, os quais são responsáveis por cumprir partes da funcionalidade geral do processo. Por sua vez, cada subprograma ainda pode ser dividido em novos subprogramas. Utilizou-se à técnica dividir para conquistar nas duas abordagens, onde cada abordagem deve ser utilizada de acordo com as informações conhecidas do sistema. Aqui enfatizou-se a abordagem bottom-up porque ela permite que o programador usuário não se preocupe com a implementação de cada módulo, bastando saber a funcionalidade de cada módulo para que os mesmos possam ser integrados.

130 130 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS Uma estrutura agrupa várias variáveis numa só. Funciona como uma ficha pessoal que tenha nome, telefone e endereço. A ficha seria uma estrutura. A estrutura, então, serve para agrupar um conjunto de dados não similares, formando um novo tipo de dados. Estas provêem uma grande facilidade para o armazenamento de tipos não definidos pela linguagem. O uso de estruturas é considerado uma ótima prática de programação, visto que, além de permitirem uma organização melhor, possibilitam realizar atribuições ou passagem de parâmetros em uma única vez. Uma estrutura pode ser manipulada de forma seletiva (acesso a um único atributo) ou de forma integral (acesso a estrutura como um todo). Os TADS são generalizações das estruturas, pois associamos várias operações sobre a estrutura, estas podem ser dos seguintes tipos: construtoras, analisadoras, modificadoras, produtoras e destrutoras. Assim, os TADS são definidos como uma estrutura de dados e as operações sobre os dados. A importância de um TAD está na transparência que ele oferece, o usuário do tipo não precisa se preocupar em como o TAD é implementado. O uso de TADs permite inúmeras vantagens como abstração, facilidade de alteração, reutilização, ocultamento e integridade. Os Tipos Abstratos de Dados são classificados em TADs de Domínio e TADs Implementacionais. TADs de domínio são aqueles que descrevem um tipo do problema, enquanto TADs Implementacionais descrevem uma estrutura que irá ajudar a resolver o problema, mas não se refere ao problema. Exemplos de TADs de domínio são os registros de alunos, funcionários, datas, etc. Exemplos de TADs implementacionais são as pilhas, listas, etc. Contudo, é importante dizer que a idéia do TAD é que o código da implementação do tipo de dado, fique invisível para o programador que for usar o tipo. Assim, na verdade, C não implementa TAD, o que se faz é uma simulação de TDAs. No entanto, a simulação de TADs em C se torna uma boa prática de programação, pois torna o código do programa mais compreensível e simplifica a definição das estruturas de dados. Em linguagens que permitam Programação Orientada a Objetos (POO), os TADs podem ser implementados com a confiabilidade que C não permite. 4.6 Lista de Exercícios Exercício TAD tcilindro Implemente um TAD para o armazenamento dos dados de um cilindro. A entrada dos dados referentes ao cilindro será realizada via console(teclado). As possíveis operações sobre o tipo são: leitura dos dados;

131 4.6. LISTA DE EXERCÍCIOS 131 Inicializa Cilindro; Altera Altura; Altera Raio; Calcula Volume; Imprime Dimensões; Imprime Volume; Exercício Determinando a Idade Modifique o TAD tdata incluindo uma operações que determine à idade de uma pessoa, e quantos dias faltam para seu aniverário tendo como entradas a data de nascimento e a data atual. Deve-se considerar anos bissextos e meses com o número de dias conforme o calendário ocidental. Exercício TAD tponto Implemente um TAD Ponto para representar um ponto no plano com as seguintes operações: leitura: leitura via console; desloca x: realiza um deslocamento horizontal; desloca y: realiza um deslocamento vertical; atribui x y: atribui novos valores às coordenadas de um ponto; distancia: calcula a distância entre dois pontos; imprime: imprime as coordenadas do ponto. Exercício TAD tcirculo Implemente um TAD Circulo para representar um círculo (Observe que o centro é um ponto) com as seguintes operações: leitura: leitura via console;

132 132 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS atribui raio: atribui um valor r ao raio do circulo; atribui centro: atribui novo valor ao centro do circulo; area: calcula a área do círculo; interior: verifica se um dado ponto está dentro do círculo. Exercício TAD ttrapezio Implemente um TAD Trapézio para representar um trapézio com as seguintes operações: leitura: função leitura para cada lado do trapézio; atribuicao: atribui individualmente cada lado; area: calcula a área do trapézio; interior: verifica se um dado ponto está dentro do trapézio. Exercício Imobiliaria Você foi contratado para fazer algumas pesquisas no cadastro de uma imobiliária. Considere que cada imóvel possui um código de identificação, uma área total e o preço por metro quadrado do imóvel. Implemente um programa que leia os dados dos imóveis, armazene e imprima o imóvel mais caro e o de maior área em um TAD imóvel. 4.7 Trabalhos Sugeridos Trabalho Estacionamento Considere o sistema responsável pelo controle de entrada e saída de veículos de um estacionamento que funciona 24 h por dia. Um motorista ao entrar no estacionamento recebe um ticket indicando o horário e a data de entrada, na saída do estacionamento o atendente realiza a cobrança pelo uso do serviço. O preço do uso do estacionamento por hora é de R$ 1,00 nos dias úteis e R$ 2,00 em sábados, domingos e feriados.

133 4.7. TRABALHOS SUGERIDOS 133 O sistema deve ler do ticket do usuário o horário e a data de entrada, deve identificar o horário atual e a respectiva data, calcular o tempo de uso do estacionamento e o valor a ser pago. Caso o motorista permaneça no estacionamento por menos de 15 minutos ele não deverá pagar pelo uso do estacionamento. Caso o motorista permaneça por menos de uma hora ele deverá pagar pelo preço de uma hora completa. Implemente um TAD horário que armazene os horários e as datas de entrada e saída para cada veículo e que tenha operações como: ler horário de entrada; ler data de entrada; obter horário do sistema; obter data do sistema; cálculo de tempo no estacionamento; cálculo de custo; determinação do horário e data de saída para um determinado custo de uso do estacionamento considerando um determinado horário de entrada. Trabalho Banco O gerente de uma agência bancária te contratou para fazer um programa em C para extrair algumas informações das contas dos correntistas de sua agência. Para tanto leia sucessivamente os dados do número da conta (um valor inteiro positivo) e valor da operação (um valor ponto flutuante positivo para as operações de crédito e negativo para as operações de débito) realizadas no último mês. Seu programa deve usar os dados da listagem para informar: 1. As operações suspeitas, isto é, aquelas que movimentaram acima de reais 2. Os dois correntistas que produziram o maior saldo nas suas operações. 3. O saldo total das operações na agência. Nos ítens 1, 2, e 3 também devem ser apresentados os números das contas dos correntistas. Considere que os dados de cada conta são fornecidos contiguamente, isto é, são fornecidos todos os dados das operações de uma conta, depois todos os dados de outra conta e assim sucessivamente. O processamento deve ser encerrado quando for lido um número de conta igual a zero.

134 134 CAPÍTULO 4. TIPOS ABSTRATOS DE DADOS Trabalho NPD UFES O diretor do NPD da UFES te contratou para fazer um programa em C para extrair algumas informações a respeito dos cursos e dos alunos da UFES. Para tanto leia sucessivamente os dados código do curso (um valor inteiro positivo), a matrícula do aluno (um valor inteiro positivo), a carga horária da disciplina (um valor inteiro positivo) e a média final obtida em uma disciplina cursada pelo aluno (um valor ponto flutuante). Seu programa deve esses dados para informar: 1. O coeficiente de rendimento de cada aluno. 2. O melhor aluno de cada curso, isto é, o aluno de melhor coeficiente de rendimento no curso. 3. O curso mais difícil, isto é, aquele que tem o menor coeficiente de rendimento médio. 4. Os três alunos com maior número de reprovações na UFES. Nos ítens 1 e 2 devem ser apresentados o curso, a matrícula e o coeficiente do aluno. No item 3 devem ser apresentados o curso e o coeficiente de rendimento médio. No item 4, devem ser apresentados o curso, a matrícula e o número total de reprovações. Considere que os dados de cada aluno e curso são fornecidos contiguamente, isto é, são fornecidos todos os dados das disciplinas cursadas por um aluno em um curso, depois todos os dados de outro aluno deste curso e assim sucessivamente até terminar todos os dados de alunos do curso. Esse processo se repete para todos os cursos da UFES. O processamento deve ser encerrado quando for lido um código de curso igual a zero.

135 Capítulo 5 Vetores Autores: Andrezinho Thiago Paris Salviato Objetivos: Introduzir a estrutura de dados vetor e mostrar sua importância; Apresentar suas operações básicas e discutir os cuidados a serem tomados ao utilizá-las; Definir um TAD Implementacional tvetorint; Apresentar aplicações para o TAD. 5.1 Vetores e sua importância Imagine que se queira fazer um programa para manusear dados de um mesmo tipo - as idades das pessoas de uma família, ou os salários dos funcionários de uma empresa, por exemplo. Uma alternativa seria a utilização de várias variáveis ou então a leitura de valores sempre que necessários. Mas será que essas seriam as melhores soluções? E se o programa for grande e haja necessidade de que ele seja modularizado? Não ficaria ruim na hora de passar todas essas variáveis como parâmetros? Ao se refletir sobre essas perguntas, outra idéia pode vir à tona: a utilização de um TAD, já que, dessa forma, as variáveis podem ser encapsuladas numa só estrutura e apenas ela poderia ser passada como parâmetro! Boa idéia! Mas e se fossem 100 valores diferentes? Ou se houvesse a necessidade de mantê-los ordenados, por exemplo? Como já se pode perceber, nem sempre os tipos básicos de dados (int, float, char) são suficientes para exprimir estruturas de dados em algoritmos. Para ilustrar melhor o problema considere que um professor tenha uma turma de 50 alunos e deseja saber quantos deles tiveram nota acima da média da turma. Com os instrumentos apresentados até o capítulo anterior, seria necessária a leitura das notas dos alunos por duas 135

136 136 CAPÍTULO 5. VETORES vezes (ou a utilização de 50 variáveis - não tente isso em casa!): uma para calcular a média e outra para verificar quais alunos tiveram notas superiores à média. O Exemplo 5.1 mostra uma maneira de resolver o problema do professor utilizando a Linguagem C, fazendo a dupla leitura citada acima. 1 # include <stdio.h> 2 3 main (){ 4 int i; 5 int quant ; 6 float nota ; 7 float soma ; 8 float media ; 9 10 nota = 0.0; 11 soma = 0.0; 12 media = 0.0; for (i = 0; i < 50 ; i ++) { 15 printf (" Digite uma nota "); 16 scanf ("%f",& nota ); 17 soma = soma + nota ; 18 } media = soma /50; quant = 0; for (i = 0; i < 50 ; i ++) { 25 printf (" Digite uma nota \n"); 26 scanf ("%f",& nota ); 27 if( nota > media ) quant ++; 28 } printf ("\ n% d alunos obtiveram nota acima da média \ n", quant ); 31 } Exemplo 5.1: Resolução do exemplo do professor sem a utilização de vetores Todos os problemas citados até agora no capítulo poderiam ser resolvidos com a utilização de vetores. Um vetor é uma estrutura de dados composta homogênea, isto é, ele possui, em geral, mais de um elemento - por isso, composta - sendo que estes elementos são sempre de um mesmo tipo - por isso, homogênea. Resumindo, a utilização de um vetor se faz necessária sempre que for preciso armazenar uma quantidade finita de dados de um mesmo tipo, para ser manuseado durante a execução do programa. No Exemplo 5.1, por exemplo, as notas de todos os alunos da turma poderiam ter sido armazenadas num único vetor com apenas uma leitura e a partir de então eles estariam

137 5.2. REPRESENTAÇÃO 137 disponíveis para serem utilizados durante todos os cálculos. 5.2 Representação A Figura 5.1 mostra a maneira mais comum de se representar um vetor graficamente. Figura 5.1: Representação de um vetor A Figura 5.1 representa um vetor de inteiros chamado vet com os nove primeiros números primos, onde o terceiro elemento (elemento de índice igual a 2) é o número 5 e o de índice 4 é o número 11, por exemplo. É importante lembrar que todo o primeiro elemento de um vetor na linguagem C possui o índice zero. Analizando a Figura 5.1 fica evidente outras importantes características dessa estrutura: a seqëncialização e a indexação de seus elementos e a sua dimensão. Quando se declara um vetor num programa, significa que durante a sua execução será reservado um espaço de memória seqüencial com o tamanho necessário para que seja possível armazenar todos os elementos do vetor. Portanto os elementos serão armazenados seqüencialmente na memória. Essa propriedade facilita bastante o acesso do programa ao elemento desejado dentro do vetor, pois, para que o dado seja recuperado, fica sendo necessário apenas o nome do vetor, que localiza o primeiro elemento, e um índice, que indicará ao programa a que distância do início está o dado desejado. Mas para que seja feita a reserva do espaço de memória, deve ser informado no código qual o tamanho do vetor, isto é, o número máximo de elementos que ele pode armazenar. A Figura 5.2 mostra uma ilustração de como um vetor é armazenado na memória do computador. Dados os conceitos teóricos do vetor, chega a hora de discutir como essa estrutura pode ser utilizada na prática, abordando assuntos como a sua definição, as operações definidas sobre ela e outros. Isso será feito a partir de agora. 5.3 Definição O primeiro passo para uma utilização clara e efetiva dos vetores é uma declaração que deixe claro para quê a estrutura está sendo usada. Isso ajuda a dar legibilidade ao código, logo, facilida a implementação. Na linguagem C, o vetor é declarado da seguinte maneira: <tipo_dados> <nome_vetor>[<tam_vetor>];

138 138 CAPÍTULO 5. VETORES Figura 5.2: Vetores na memória do computador onde tipo dados representa o tipo dos dados dos elementos que serão armazenados no vetor, nome vetor o nome pelo qual o vetor será referenciado e tam vetor o número de elementos que cabem lá dentro. O Exemplo 5.2 mostra alguns exemplos de declaração de vetores. 1 int vet [9]; 2 3 char nome [9]; 4 5 float notas [6]; 6 7 float notasalunos [50]; Exemplo 5.2: Algumas declarações de vetores No Exemplo 5.2, o primeiro vetor declarado pode ser representado graficamente pela Figura 5.1 mostrada na seção anterior, ou seja, seu nome é vet e ele pode armazenar até nove elementos

139 5.3. DEFINIÇÃO 139 do tipo int. O segundo e o terceiro foram os representados pela Figura 5.2. Aquele, chamado nome, armazena no máximo 6 elementos do tipo char, e este, chamado notas, até seis do tipo float. Já o último poderia ser utilizado no problema do professor citado anteriormente, por exemplo. Neste vetor, chamado notasalunos, podem ser armazenados cinqüênta valores do tipo float, que poderiam representar as notas dos cinqüênta alunos Definição do Tamanho do Vetor Na linguagem C, o tamanho do vetor pode ser definido em dois momentos diferentes: em tempo de compilação e em tempo de execução; lembrando sempre que, em ambos os casos, uma vez que ele for definido e que a memória for alocada, o tamanho não poderá ser alterado. Alguns fatores devem ser considerados na hora de escolher entre essas duas abordagens, como o consumo de memória e o tipo de aplicação. Definição em tempo de compilação A definição em tempo de compilação é caracterizada pelo número inteiro entre colchetes na declaração do vetor. Todas as declarações mostradas no Exemplo 5.2 são exemplos de vetores definidos em tempo de compilação, antes da execução do programa. Então, para armazenar dados nesse tipo de vetor, é necessário que se tenha conhecimento prévio da quantidade máxima de elementos que nele será armazenado. Suponha que se queira fazer um programa para cadastrar os alunos de uma escola de ensino médio com cinco turmas por ano e fazer um acompanhamento de suas notas. Considere também que cada aluno pode cursar, no máximo, oito matérias por ano, duas delas optativas. O Exemplo 5.3 mostra uma estrutura que poderia ser utilizada para representar um aluno na linguagem C. 1 typedef struct aluno { 2 char nome [50]; 3 int rg; 4 int ano ; 5 char turma ; 6 float notas [8]; 7 } taluno ; Exemplo 5.3: Definição da Estrutura Aluno A estrutura proposta no Exemplo 5.3 possui um vetor chamado nome, do tipo char e de tamanho igual a 50 para armazenar o nome completo de um aluno; duas variáveis do tipo int chamadas rg e ano, para armazenar, respectivamente, o número do RG do aluno e seu ano (série); uma variável do tipo char chamada turma, para armazenar sua turma; e um vetor do tipo float chamado notas e de tamanho igual a 8, para armazenar as notas desse aluno nas diferentes disciplinas que ele pode cursar.

140 140 CAPÍTULO 5. VETORES Repare que as notas e o nome do aluno podem ocupar um espaço menor que o tamanho máximo do vetor. Essa característica dos vetores definidos em tempo de compilação pode acarretar em desperdício de memória, pois os espaços armazenados para eles podem não ser utilizados totalmente. Definição em tempo de execução A definição do tamanho em tempo de execução, pode ser implementada substituindo o número inteiro que é colocado entre colchetes na declaração do vetor por uma variável que só ganhará um valor durante a execução do programa. O Exemplo 5.4 mostra como isso pode ser feito na linguagem C. 1 # include < stdio.h> 2 3 main (){ 4 5 int num ; printf (" Quantas notas deseja armazenar no vetor?"); 10 scanf ("%d",& num ); float notas [ num ]; } Exemplo 5.4: Definição de um vetor em tempo de execução Repare que declarando o vetor dessa forma, o desperdício de memória pode ser eliminado, já que o vetor é utilizado por completo, desde que se tenha conhecimento prévio do número de elementos que serão armazenados e que se possa informá-lo à aplicação. É importante ressalvar que essa forma dinâmica de definição de tamanho não funciona no caso de vetores dentro de estruturas, como o vetor nota da estrutura taluno do Exemplo 5.3, por exemplo. 5.4 Operações Na linguagem C não existem operações pré-definidas para a manipulação de um vetor como um todo. Por exemplo, não é permitida a leitura de um vetor por inteiro com o comando scanf. As operações só podem ser feitas para cada elemento do vetor, individualmente.

141 5.4. OPERAÇÕES 141 Como já mencionado na Seção 5.2, para acessar um elemento de um vetor, são necessários apenas seu nome e o índice que informa sua localização. Na linguagem C a sintaxe de acesso a um elemento de um vetor é dada por: <nome_vetor> [<índice>] onde o índice pode ser tanto um número inteiro maior ou igual a zero quanto uma expressão inteira composta por variáveis e números. Sabendo acessar o elemento ele pode ser manipulado como uma variável qualquer e ser utilizado em operações das mais diversas como atribuição, operações aritméticas, etc. O Exemplo 5.5 mostra algumas dessas operações em Linguagem C. 1 # include <stdio.h> 2 3 main (){ 4 int i; 5 int vet1 [10]; 6 int vet2 [10]; 7 float notas [8]; 8 float soma ; 9 float media ; vet1 [0] = 43; for (i =0;i <5; i ++) { 14 vet1 [2* i +1] = 10; 15 } for (i =0;i <10; i ++) { 18 vet2 [ i] = 0; 19 } for (i =0;i <8; i ++) { 22 scanf ("%f",& notas [i]); 23 } for (i =0;i <10; i ++) { 26 vet2 [i] = vet1 [i]; 27 } for (i =0;i <8; i ++) { 30 soma += notas [ i]; 31 } 32 media = soma /8; 33 } Exemplo 5.5: Operações sobre elementos de um vetor

142 142 CAPÍTULO 5. VETORES No Exemplo 5.5, primeiramente são declarados a variável i, do tipo int; os vetores vet1 e vet2, ambos do tipo int e de tamanho igual a 10; o vetor notas do tipo float e de tamanho igual a 8; e as variáveis soma e media. Em seguida, o primeiro elemento do vetor vet1, ou seja, o elemento de índice 0 (zero), recebe o valor 43 (linha 11); todos os elementos de vet1 de índice ímpar recebem o valor 10 (linhas 13 a 15); todos os elementos de vet2 recebem o valor zero (linhas 17 a 19); cada uma das 8 posições do vetor notas é preenchido por um valor lido do teclado (linhas 21 a 23); os elementos do vetor vet1 são copiados para vet2 (linhas 25 a 27); e, por último, é calculada a média dos valores contidos no vetor notas e armazenada na variável media (linhas 29 a 32). É importante ressaltar a grande importância da utilização de expressões como índice, pois ela é essencial para o acesso rápido a todos os elementos do vetor. Um outro exemplo interessante para ilustrar as operações sobre os vetores é a resolução do problema do professor citado na seção 5.1. Já foi proposta uma solução fazendo uma dupla leitura das notas dos alunos (Exemplo 5.1). O Exemplo 5.6 mostra uma solução utilizando vetores. 1 # include <stdio.h> 2 3 main (){ 4 int i; 5 int quant ; 6 float notas [50]; 7 float soma ; 8 float media ; 9 10 nota = 0.0; 11 soma = 0.0; 12 media = 0.0; for (i = 0; i < 50 ; i ++) { 15 printf (" Digite uma nota "); 16 scanf ("%f",& notas [i]); 17 soma = soma + nota [i]; 18 } media = soma /50; quant = 0; for (i = 0; i < 50 ; i ++) { 25 if( notas [i] > media ) quant ++; 26 } printf ("\ n% d alunos obtiveram nota acima da média \ n", quant ); 29 } Exemplo 5.6: Resolução do exemplo do professor com a utilização de vetores

143 5.4. OPERAÇÕES 143 Repare que, dessa vez, os valores referentes às notas são lidos do teclado apenas uma vez e são, então, armazenados no vetor notas e imediatamente somados para o cálculo da média da turma, valor este armazenado na variável media. Para a contagem de alunos com nota acima da média, não é necessária uma nova leitura do teclado, apenas uma consulta ao vetor notas que já possui todos os valores armazenados. Os vetores também podem ser passados como parâmetro de funções, mas nunca como retorno. O Exemplo 5.7 mostra um código em linguagem C que resolve o problema anterior, mas com a utilização de funções que recebem um vetor como parâmetro. 1 # include <stdio.h> 2 3 float calculamedia ( float vetor [50]) { 4 int i; 5 float soma ; 6 float media ; 7 8 soma = 0.0; 9 10 for ( i = 0 ; i < 50 ; i ++) 11 soma = soma + vetor [ i]; 12 } media = soma /50; return ( media ); 17 } int alunosacimamedia ( float vetor [50], float media ){ 20 int i; 21 int quant ; quant = 0; 24 for ( i = 0 ; i < 50 ; i ++) 25 if( vetor [i] > media ) quant ++; 26 } return ( quant ); 29 } main (){ 32 int i; 33 int quant ; 34 float notas [50]; 35 float soma ; 36 float media ; nota = 0.0; 39 soma = 0.0; 40 media = 0.0;

144 144 CAPÍTULO 5. VETORES for (i = 0 ; i < 50 ; i ++) { 43 printf (" Digite uma nota "); 44 scanf ("%f",& notas [i]); 45 } media = calculamedia { notas }; quant = alunosacimamedia ( notas, media ); printf ("\ n% d alunos obtiveram nota acima da média \ n", quant ); 52 } Exemplo 5.7: Passagem de vetores para função Algumas características da linguagem C tornam a utilização de vetores passados como parâmetro perigosa quando não tomados os devidos cuidados. Por isso essa técnica não será usada agora Acesso indevido A Figura 5.2 da seção 5.2 também pode ser utilizada para ilustrar um exemplo de um problema que ocorre com certa freqüência em programas na linguagem C, já que ela não faz nenhuma restrição ao acesso de elementos fora dos limites do vetor. Portanto, se, por acidente, um programador tentasse acessar em seu programa o índice 14 do vetor nome representado na figura, por exemplo, nenhum erro seria acusado em tempo de compilação, ou de execução. O erro só poderia ser percebido em tempo de execução, quando o computador retornasse um valor inesperado no programa. Mas isso é o mínimo que pode acontecer. Se o programador utilizasse um índice diferente, ele poderia acessar e apagar alguma faixa de memória de outro programa e acabar causando danos irreversíveis. Logo, a solução mais adequada é sempre avaliar os limites de um vetor antes de manipulá-lo. 5.5 Strings O vetor de elementos do tipo char do Exemplo 5.2, chamado nome, assim como todos os vetores do tipo char, podem ser considerados de um novo tipo, o tipo string. Strings correspondem a uma seqüência de caracteres. Geralmente, são usadas para realizar a entrada e saída de dados em um programa e para armazenar dados não numéricos. É comum representar uma string como um vetor de caracteres (tipo char), dispondo das mesmas operações comuns a essa estrutura de dados. A Figura 5.3 exemplifica a representação de uma string como um vetor de caracteres. Toda string armazenada em um vetor é terminada por um caracter especial, conhecido como caracter nulo \0 ( barra-zero ). A principal função desse caracter é alertar o fim de uma string,

145 5.5. STRINGS 145 Figura 5.3: Vetor de caracteres. evitando um acesso indesejado a uma posição do vetor que se encontra vazia. O programa do Exemplo 5.8 demonstra uma forma não usual de imprimir strings, mas aplica o conceito de caracter nulo. 1 void imprime_ string ( char palavra []) { 2 int i; 3 4 for (i =0; palavra [i]!= \0 ;i ++) { 5 printf ("% c", palavra [i]); 6 } 7 printf ("\ n"); 8 } Exemplo 5.8: Subprograma para impressão de strings, utilizando diretamente o barra-zero. No Exemplo 5.8, a condição de parada do laço for é encontrar o caracter \0, e, durante cada iteração, cada caracter do vetor é impresso separadamente. A forma mais comum (e simples) de imprimir uma string é a apresentada no Exemplo void imprime_ string ( char palavra []) { 2 3 printf ("%s\n",palavra ); 4 } Exemplo 5.9: Impressão de uma string sem a utilização direta do caracter barra-zero. O programa do Exemplo 5.9 produz o mesmo resultado do Exemplo 5.8, porém, naquele o caracter \0 é verificado implicitamente pela função printf. A biblioteca padrão de C ainda oferece algumas funções muito úteis para a manipulação de strings, como a strcpy (cópia), a strlen (tamanho) e a strcmp (comparação). O Exemplo 5.10 demonstra o uso dessas funções. 1 # include <stdio.h> 2 # include < string.h> 3 4 main (){ 5 char vet1 [20] = " ola enfermeira "; 6 char vet2 [20]; 7 char vet3 [20]; 8 9 printf (" Tamanho da string : %d \n\n",strlen ( vet1 )); 10

146 146 CAPÍTULO 5. VETORES 11 strcpy (vet2, vet1 ); 12 printf ("%s \n %s \n\n",vet1, vet2 ); strcpy (vet3," sim senhor "); 15 printf ("%s \n\n",vet3 ); if( strcmp (vet1, vet2 ) == 0){ 18 printf (" As strings sao iguais "); 19 } else { 20 printf (" As strings sao diferentes "); 21 } if( strcmp (vet1, vet3 ) == 0){ 24 printf (" As strings sao iguais "); 25 } else { 26 printf (" As strings sao diferentes "); 27 } 28 } Exemplo 5.10: Programa demonstrativo de algumas funções da biblioteca padrão de C para manipulação de strings. A primeira linha dentro da função main mostra uma maneira de inicializar um vetor, no caso do exemplo o vetor chamado vet1, e já preenchê-lo com valores, no mesmo caso com o texto ola enfermeira (linha 5). Em seguida são declarados mais dois vetores de caracteres: vet1 e vet2, ambos de tamanho igual a 20. Depois disso as funções da biblioteca padrão string.h são utilizadas para, respectivamente: imprimir o tamanho da string em vet1, com a função strlen (linha 9); copiar o conteúdo de vet1 em vet2, com a função strcpy e em seguida imprimí-los na tela com a função printf da biblioteca stdio.h (linhas 11 e 12); copiar a string sim senhor para dentro do vetor vet3 e em seguida imprimir seu conteúdo na tela (linhas 14 e 15); e, finalmente, comparar o conteúdo das strings contidas em vet1 e vet2 (linhas 17 a 21), e vet1 e vet3 (linhas 23 a 27) com a função strcmp. Para o uso da função strcpy, a ordem dos parâmetros é de suma importância; o primeiro parâmetro é o vetor de destino da string que se deseja copiar, enquanto o segundo é o vetor de origem da string. Em relação a função strcmp, ela retorna o valor zero se as strings armazenadas em ambos os vetores forem iguais (letras em caixa alta são consideradas diferentes das em caixa baixa). 5.6 TAD Implementacional Como dito na seção 5.4, na linguagem C não existem operações básicas para a manipulação de vetores como um todo. Porém a manipulação individual de todos os seus elementos pode ser muito trabalhosa e muitas vezes operações comuns devem ser repetidas ao longo do código do programa comprometendo sua legibilidade, confiabilidade e modificabilidade. Por isso, as operações básicas sobre um vetor devem ser modularizadas pela utilização de subprogramas e,

147 5.6. TAD IMPLEMENTACIONAL 147 para isso, a utilização de um TAD se mostra oportuna. Nessa seção será apresentado na linguagem C um TAD de natureza implementacional, chamado tvetorint, que poderá ser usado em aplicações que necessitam manipular vetores de inteiros. Para isso será definida uma estrutura contendo um vetor e o número de elementos correntes do vetor Atributos O Exemplo 5.11 mostra os atributos da estrutura utilizada no TAD Implementacional tvetorint. 1 # define TAM typedef struct vetor { 4 int vet [ TAM ]; 5 int n; 6 } tvetorint ; Exemplo 5.11: Definição da Estrutura tvetorint O Exemplo 5.11 mostra a definição da estrutura tvetorint. Ela possui um vetor de inteiros chamado vet, de tamanho definido por TAM e um atributo n do tipo int que representa o número corrente de elementos em vet, ou seja, o número de elementos armazenados no vetor num determinado momento da execução do programa. Esse valor deve ser zero quando o vetor estiver vazio e deve ser incrementado ou decrementado sempre que se adicionar ou excluir um elemento do vetor, respectiviamente. A primeira linha do código do Exemplo 5.11 faz com que toda a palavra TAM existente no código do programa seja substituída pelo valor 100 na hora da compilação. Esse artifício é bastante utilizado quando se trabalha com vetores com tamanho máximo, já que permite que uma alteração no valor de TAM no início do código modifique os tamanhos de todos os vetores do código que possuem TAM como definição de tamanho. A Figura 5.4 mostra uma maneira simples de representar graficamente a estrutura proposta no Exemplo A figura mostra duas estruturas do tipo. Como pode ser observado, apesar de vet possuir tamanho igual a 100, ele possui apenas 9 elementos na primeira estrutura e 5 na segunda, valores representados pelo atributo n Operações Agora serão ilustradas as funções que devem ser utilizadas como operações para manipular a estrutura tvetorint. Tal como visto no capítulo anterior, essas operações se dividem em quatro categorias diferentes: contrutoras, analizadoras, produtoras e modificadoras.

148 148 CAPÍTULO 5. VETORES Figura 5.4: Representação gráfica da estrutura tvetorint. Construtoras Para o caso do TAD tvetorint, podem ser criadas duas funções dessa natureza, uma que inicializa a estrutura com um vetor vazio e outra que a inicializa já lendo para dentro do vetor um determinado número de elementos ignorando o que havia antes. Para efeito de comparação, essas duas funções são semelhantes à operação = utilizada sobre as variáveis dos tipos primitivos (int, float, char, etc). Uma dessas duas funções deve ser utilizada antes de qualquer outra operação sobre a estrutura. A função do Exemplo 5.12 é uma maneira de se implementar uma função que inicializa a estrutura tvetorint sem nunhum elemento dentro do vetor. 1 tvetorint iniciavaziovetorint ( tvetorint v){ 2 v. n = 0; 3 return (v); 4 } Exemplo 5.12: Inicialização de um tvetorint vazio Repare que a função tem como parâmetro de entrada uma estrutura do tipo tvetorint, ou seja, antes de ser utilizada, uma variável do tipo deve ser declarada no corpo principal do programa, a função main da linguagem C. O Exemplo 5.13 mostra o código da outra função construtora citada. 1 tvetorint leituravetorint ( tvetorint v, int num ){ 2 int i; 3 for (i = 0 ; i < num ; i ++) scanf ("%d",&v. vet [i]); 4 v.n = num ; 5 return (v); 6 } Exemplo 5.13: Inicialização de um tvetorint com leitura de elementos

149 5.6. TAD IMPLEMENTACIONAL 149 A função do Exemplo 5.13 recebe como parâmetro de entrada a estrutura do tipo tvetorint que se quer inicializar e o número de elementos que se quer ler para dentro do vetor. Ela, então, lê do teclado os elementos desejados e armazena-os dentro do vetor da estrutura. Analisadoras As operações analizadoras são utilizadas para que se possa obter informações relacionadas com os valores do TAD. Serão mostradas aqui três funções desse tipo. A primeira função, que pode ser visualizada no Exemplo 5.14 é para que possam ser visualizados em tela os valores de todos os elementos armazenados no vetor da estrutura. 1 void escritavetorint ( tvetorint v){ 2 int i; 3 4 printf ("\n\n"); 5 printf (" Os elementos que estao atualmente no vetor sao :"); 6 printf ("\n"); 7 for (i = 0 ; i < v.n ; i ++) printf ("%d ",v. vet [i]); 8 printf ("\n\n"); 9 } Exemplo 5.14: Escrita em tela A função simplesmente acessa todos os elementos do vetor e imprime seus valores na tela (linha 7). As outras duas funções citadas como analizadoras são ambas utilizadas para determinar a existência de um determinado elemento dentro do vetor. Essas funções, além de funcionarem como uma simples consulta dentro de um vetor, podem ser utilizadas dentro de outras funções como, por exemplo, para achar um determinado elemento que se queira apagar. A função do Exemplo 5.15 varre o vetor seqüencialmente à procura de um elemento e retorna seu índice se encontrá-lo. Caso contrário, ela retorna int pesquisasequencialint ( tvetorint v, int elem ){ 2 int i; 3 4 for (i = 0 ; i < v.n ; i ++) { 5 if(v. vet [i ]== elem ) return (i); 6 } 7 return ( -1); 8 } Exemplo 5.15: Pesquisa Seqüencial A função do Exemplo 5.15 recebe como parâmetro de entrada a estrutura do tipo tvetorint que possui o vetor que se deseja pesquisar e o elemento a ser encontrado. Ela, então, compara, a partir do primeiro, o valor de todos os elementos com o valor procurado. Se achar um igual,

150 150 CAPÍTULO 5. VETORES retorna o valor de seu íncide. Se chegar ao final do vetor e não tiver encontrado nenhum valor igual ao que se procura, a função retorna o valor -1. Repare que se existir no vetor dois elementos iguais, a função obtem apenas o índice do primeiro. Isso acontece, porque ela retorna quando o primeiro valor é encontrado. Outra característica dessa função que deve ser evidenciada é sua ineficiência quando o valor procurado está nas últimas posições ou quando ele não existe no vetor. Para resolver o problema da ineficiência citada, outras funções foram desenvolvidas para otimizar o processo de localização de elementos. O Exemplo 5.16 mostra uma delas: a Pesquisa Binária. Essa função deve ser utilizada apenas em vetores ordenados de forma crescente. Ela percorre o vetor partindo-o ao meio à procura do elemento e retorna seu índice se encontrá-lo e -1 caso contrário. 1 int pesquisabinariaint ( tvetorint v, int elem ){ 2 int inicio ; 3 int fim ; 4 int meio ; 5 6 inicio = 0; 7 fim = v. n - 1; 8 9 while ( inicio <= fim ){ 10 meio = ( inicio + fim ) /2; 11 if( elem < v. vet [ meio ]) fim = meio - 1; 12 else if( elem > v. vet [ meio ]) inicio = meio + 1; 13 else return ( meio ); 14 } 15 return ( -1); 16 } Exemplo 5.16: Pesquisa Binária A função do Exemplo 5.16 recebe como parâmetro de entrada a estrutura do tipo tvetorint que possui o vetor que se deseja pesquisar e o elemento a ser encontrado. São declaradas, então, três variáveis: inicio, fim e meio. A variável inicio recebe, o valor do índice do menor elemento do vetor, ou seja, zero, e fim recebe o valor do índice do maior elemento, ou seja, v.n 1. Enquanto o valor de inicio for menor ou igual ao valor de fim, meio recebe o índice do elemento central da região por eles delimitada. É exatamente esse elemento central que sempre será comparado ao elemento que se está procurando. Se aquele for maior que este, fim recebe o valor do índice anterior à meio. Caso contrário, inicio recebe o valor do índice posterior à meio. Se nenhuma das duas alternativa acontecerem, quer dizer que o elemento foi encontrado e seu índice é igual à meio. Se em algum momento inicio passar a ser maior que fim, o elemento não existe na lista e a função retorna -1. As Figuras 5.5 e 5.6 mostram o funcionamento do algoritmo do Exemplo 5.16 para duas pesquisas diferentes. A Figura 5.5 ilustra as etapas da busca do elemento 4 dentro do vetor vet da estrutura

151 5.6. TAD IMPLEMENTACIONAL 151 Figura 5.5: Etapas da pesquisa binária com o elemento procurado no início do vetor. do tipo tvetorint mostrada na figura. Primeiramente inicio e fim adquirem os valores 0 e 10, respectivamente. Como inicio é menor que fim, meio recebe o índice 5, e o elemento apontado por ele, o valor 10, é comparado ao valor procurado. Como 10 é maior que 4, fim recebe o índice 4. Como inicio continua menor que fim, meio recebe o índice 2. Dessa vez, o elemento apontado por meio é igual ao elemento procurado. A função retorna, então, o valor de seu índice: 2. Já a Figura 5.6 mostra a busca do elemento 23 dentro do mesmo vetor apresentado na Figura 5.5. Primeiramente inicio e fim adquirem os valores 0 e 10, respectivamente. Como inicio é menor que fim, meio recebe o índice 5, e o elemento apontado por ele, o valor 10, é comparado ao valor procurado. Como 10 é menor que 23, inicio recebe o índice 6. Como inicio continua menor que fim, meio recebe o índice 8, e o elemento apontado por ele, o valor 19, é comparado ao valor procurado. Novamente 19 é menor que 23 e inicio recebe o índice 9. Como inicio continua menor que fim, meio recebe o índice 9 também. Dessa vez, o elemento apontado por meio é igual ao elemento procurado. A função retorna, então, o valor de seu índice: 9. É importante observar que, para esta última busca, o algoritmo de pesquisa binária faz apenas três comparações enquanto que o algoritmo de pesquisa seqüencial teria de fazer dez. Produtoras As funções dessa natureza geram algum tipo de produto como resultado de operações feitas sobre dois ou mais vetores. Dentre elas podem ser citadas a Adição e o Produto Escalar. A Adição, Exemplo 5.17, soma cada elemento de mesmo índice de um vetor com outro com o mesmo número de elementos correntes e armazena o resultado em um terceiro vetor. Se os vetores a serem somados não possuirem o mesmo número de elementos correntes, a função retorna uma mensagem de erro.

152 152 CAPÍTULO 5. VETORES Figura 5.6: Etapas da pesquisa binária com o elemento procurado no final do vetor. 1 tvetorint somavetorint ( tvetorint v1, tvetorint v2, tvetorint soma ){ 2 int i; 3 4 if(v1.n!= v2.n){ 5 printf ("\n\n"); 6 printf (" Vetores com tamanhos diferentes "); 7 printf ("\n\n"); 8 soma.n = 0; 9 } else { 10 soma.n = v1.n; 11 for (i = 0 ; i < soma.n ; i ++) { 12 soma. vet [i] = v1.vet [i] + v2.vet [i]; 13 } 14 } 15 return ( soma ); 16 } Exemplo 5.17: Adição de dois vetores A função somavetorint recebe como parâmetros de entrada três estruturas do tipo tvetorint. Duas delas, v1 e v2, devem possuir os vetores que se deseja somar, e a outra, soma, o vetor onde a soma será armazenada. Primeiramente verifica-se se os vetores de v1 e v2 possuem o mesmo número de elementos correntes. Se não possuírem, uma mensagem de erro é exibida na tela.

153 5.6. TAD IMPLEMENTACIONAL 153 Caso contrário, cada elemento de mesmo índice de v1 e v2 são somados e armazenados com o mesmo índice no vetor de soma e este é retornado pela função. Já a função Produto Escalar, cujo código pode ser encontrado no Exemplo 5.18 calcula, como o próprio nome já diz, o produto escalar entre dois vetores. Para isso, os vetores devem possuir o mesmo número de elementos correntes. Caso contrário a função também retorna uma mensagem de erro. 1 int escalavetorint ( tvetorint v1, tvetorint v2){ 2 int i; 3 int prod ; 4 5 prod = 0; 6 if(v1.n!= v2.n){ 7 printf ("\n\n"); 8 printf (" Vetores com tamanhos diferentes "); 9 printf ("\n\n"); 10 } else { 11 for (i = 0 ; i < v1.n ; i ++) { 12 prod = prod + v1.vet [i] * v2.vet [i]; 13 } 14 } 15 return ( prod ); 16 } Exemplo 5.18: Produto Escalar A função Produto Escalar recebe como parâmetros de entrada duas estruturas tvetorint: v1 e v2. Primeiro o número de elementos correntes de seus vetores são verificados. Caso seja diferente, uma mensagem de erro é exibida na tela. Se forem iguais, o produto escalar de seus vetores, que vem a ser a soma dos produtos dos elementos de mesmo índice, são calculados. Modificadoras As operações a seguir são responsáveis por realizar alterações no vetor do TAD de forma a garantir a coerência da estrutura. Para excluir um elemento no meio de um vetor, por exemplo, a função de exclusão deve, além de apagá-lo, cuidar para que o número de elementos correntes do vetor seja atualizado e que todos os elementos com índice maior que o dele sejam transferidos uma posição para a esquerda. Além da operação de exclusão, serão apresentadas a seguir funções de inserção e ordenação. A função do Exemplo 5.19 mostra um algoritmo para inserção de um elemento numa posição pré-determinada. Ela insere o elemento na posição desejada considerando a posição zero como a primeira do vetor. 1 tvetorint inserevetorint ( tvetorint v, int elem, int pos ){ 2 int i; 3

154 154 CAPÍTULO 5. VETORES 4 if (( pos < 0) ( pos > v.n)){ 5 printf ("\n\n"); 6 printf (" Entrada de Dados Incorreta "); 7 printf ("\n\n"); 8 } else { 9 v.n ++; 10 for (i = v.n - 2 ; i > pos - 1 ; i - -){ 11 v. vet [i +1] = v. vet [i]; 12 } 13 v. vet [i] = elem ; 14 } 15 return (v); 16 } Exemplo 5.19: Inserção em Posição Pré-determinada A função inserevetorint recebe como parâmetros de entrada uma estrutura tvetorint, o elemento a ser inserido e a posição em que ele deve ser inserido. Se a posição for inválida, ou seja, menor que 0 ou maior que v.n, uma mensagem de erro é impressa na tela e o elemento não é inserido. Caso contrário os elementos de dentro do vetor vão sendo movidos para a direita de modo a deixar a posição desejada para a inserção livre. Finalmente, o vetor recebe o valor do elemento na posição desejada e a estrutura é retornada. A função do Exemplo 5.20 mostra um algoritmo de exclusão de um elemento. Ela percorre o vetor da estrutura sequencialmente procurando o elemento a ser excluído. Se encontrá-lo, retira-o e continua procurando outros iguais para excluir até não achar mais. 1 tvetorint excluivetorint ( tvetorint v, int elem ){ 2 int i; 3 int pos ; 4 int quant ; 5 6 pos = 0; 7 quant = 0; 8 while ( pos < v.n){ 9 if(v. vet [ pos ] == elem ){ 10 for (i = pos ; i < v.n - 1 ; i ++) { 11 v. vet [i] = v. vet [i +1]; 12 } 13 v.n - -; 14 } 15 pos ++; 16 } 17 return (v); 18 } Exemplo 5.20: Exclusão de Elemento A função excluivetorint recebe como parâmetros de entrada uma estrutura tvetorint e o elemento que se quer excluir. Ela, então, varre o vetor da estrutura da primeira à última posição

155 5.6. TAD IMPLEMENTACIONAL 155 comparando os elementos nele contidos com o valor do elemento a ser excluído. Se for igual, ele move todos os elementos seguintes para a esquerda apagando, assim, o elemento alvo. Depois disso ele continua a procura por outros elementos a que também devem ser excluídos. Quando chega ao final do vetor, ela retorna a estrutura modificada. As duas próximas funções a serem apresentadas são de ordenação. Ambas colocam em ordem crescente os elementos de um vetor, mas utilizando métodos diferentes. A função do Exemplo 5.21 mostra a ordenação pelo método do menor. 1 tvetorint ordenamenorint ( tvetorint v){ 2 int pos ; 3 int i; 4 int indice_ menor ; 5 int menor ; 6 7 for ( pos = 0 ; pos < v. n - 1 ; pos ++) { 8 menor = v. vet [ pos ]; 9 for (i = pos + 1 ; i < v.n ; i ++) { 10 if(v. vet [i] < menor ){ 11 menor = v. vet [i]; 12 indice_ menor = i; 13 } 14 } 15 v. vet [ indice_menor ] = v. vet [ pos ]; 16 v. vet [ pos ] = menor ; 17 } 18 return (v); 19 } Exemplo 5.21: Ordenação pelo método do menor A função ordenamenosint recebe a estrutura tvetorint cujo vetor se deseja ordenar. Primeiramente ela varre o vetor procurando o elemento de menor valor e o troca de posição com o primeiro elemente do vetor. Feito isso, ela procura o menor elemento no restante do vetor e o troca de posição com o segundo elemento e assim sucessivamente com o terceiro, quarto,..., v.n 1 elementos. A Figura 5.7 ilustra o funcionamento desse algoritmo. Já a função ordenabolhaint, Exemplo 5.22, utiliza o método da bolha. 1 tvetorint ordenabolhaint ( tvetorint v){ 2 int a; 3 int b; 4 int temp ; 5 6 for (a = 1 ; a < v.n ; a ++) { 7 for (b = v.n - 1 ; b >= a ; --b){ 8 if(v. vet [b -1] > v. vet [b]){ 9 temp = v. vet [b -1]; 10 v. vet [b -1] = v. vet [b];

156 156 CAPÍTULO 5. VETORES 11 v. vet [b] = temp ; 12 } 13 } 14 } 15 return (v); 16 } Exemplo 5.22: Ordenação pelo método da bolha A função ordenabolhaint também recebe como parâmetro de entrada apenas a estrutura tvetorint. Então ela percorre o vetor inteiro, do maior para o menor índice, fazendo comparações entre seus elementos e trocando-os quando o elemento de maior índice possui seu valor menor que o elemento de menor índice. Feito isso uma vez, o elemento de menor valor já fica na primeira posição do vetor. Então o algoritmo ignora esse elemento e repete o procedimento para o resto do vetor. Esse procedimento é repetido v.n 1 vezes. Finalmente, a função retorna a estrutura com o vetor ordenado. A Figura 5.8 ilustra o funcionamento desse algoritmo. Exemplo de Utilização do TAD tvetorint Será apresentado agora um exemplo de utilização do TAD tvetorint. Vale lembrar que o Exemplo 5.23 apresenta apenas o programa principal contendo a chamada das funções definidas até aqui. Para seu funcionamento correto, todas as funções dever estar contidas no arquivo do programa. 1 int main (){ 2 tvetorint vet1 ; 3 tvetorint vet2 ; 4 tvetorint vet3 ; 5 int elem ; 6 int pos ; 7 int prodescalar ; 8 int enter ; 9 10 vet1 = leituravetorint ( vet1,10) ; 11 vet2 = leituravetorint ( vet2,10) ; 12 vet3 = iniciavaziovetorint ( vet3 ); printf ("\n\n"); 15 printf (" Vet1 :"); 16 escritavetorint ( vet1 ); 17 printf (" Vet2 :"); 18 escritavetorint ( vet2 ); printf ("\ n\ nqual elemento deseja localizar utilizando a pesquisa sequencial?\ n\ n "); 21 scanf ("%d",& elem ); pos = pesquisasequencialint ( vet1, elem ); 24 if(pos >= 0) printf ("\n\ nelemento encontrado na posicao %d\n\n",pos );

157 5.6. TAD IMPLEMENTACIONAL else printf ("\n\ nelemento nao encontrado \n\n"); printf ("\ n\ nqual elemento deseja localizar utilizando a pesquisa binaria?\ n\ n"); 28 scanf ("%d",& elem ); pos = pesquisabinariaint ( vet1,3) ; 31 if(pos >= 0) printf ("\n\ nelemento encontrado na posicao %d\n\n",pos ); 32 else printf ("\n\ nelemento nao encontrado \n\n"); vet3 = somavetorint (vet1,vet2, vet3 ); printf ("\n\n"); 37 printf (" Vet1 :"); 38 escritavetorint ( vet1 ); 39 printf (" Vet2 :"); 40 escritavetorint ( vet2 ); 41 printf (" Vet3 :"); 42 escritavetorint ( vet3 ); prodescalar = escalavetorint ( vet1, vet2 ); printf ("\n\n"); 47 printf (" Vet1 :"); 48 escritavetorint ( vet1 ); 49 printf (" Vet2 :"); 50 escritavetorint ( vet2 ); printf ("\n\ nproduto escalar = %d\n\n",prodescalar ); vet3 = leituravetorint ( vet3,0) ; vet3 = inserevetorint ( vet3,5,0) ; 57 printf (" Vet3 :"); 58 escritavetorint ( vet3 ); vet3 = inserevetorint ( vet3,3,0) ; 61 printf (" Vet3 :"); 62 escritavetorint ( vet3 ); vet3 = inserevetorint ( vet3,7,1) ; 65 printf (" Vet3 :"); 66 escritavetorint ( vet3 ); vet3 = inserevetorint ( vet3,10,3) ; 69 printf (" Vet3 :"); 70 escritavetorint ( vet3 ); vet3 = excluivetorint ( vet3,3) ; 73 printf (" Vet3 :"); 74 escritavetorint ( vet3 ); 75 vet3 = excluivetorint ( vet3,7) ;

158 158 CAPÍTULO 5. VETORES 76 printf (" Vet3 :"); 77 escritavetorint ( vet3 ); 78 vet3 = excluivetorint ( vet3,10) ; 79 printf (" Vet3 :"); 80 escritavetorint ( vet3 ); 81 vet3 = excluivetorint ( vet3,5) ; 82 printf (" Vet3 :"); 83 escritavetorint ( vet3 ); vet1 = ordenamenorint ( vet1 ); 86 printf (" Vet1 :"); 87 escritavetorint ( vet1 ); vet2 = ordenabolhaint ( vet2 ); 90 printf (" Vet2 :"); 91 escritavetorint ( vet2 ); } Exemplo 5.23: Exemplo de Utilização do TAD tvetorint 5.7 Exercícios Resolvidos 5.8 Resumo Um vetor é uma estrutura de dados composta homogênea, isto é, ele possui, em geral, mais de um elemento de um mesmo tipo. São características importantes dos vetores a seqëncialização, a indexação e a sua dimensão. Deve-se tomar cuidado para não se acessar um vetor indevidamente, como, por exemplo utilizando um índice maior que seu tamanho. A dimensão de um vetor pode ser definida, em C, tanto em tempo de compilação, quanto em tempo de execução. Na linguagem C não existem operações básicas para a manipulação de um vetor como um todo. Seus elementos devem ser manipulado separadamente. Quando acessado individualmente, cada elemento de um vetor pode ser encarado como uma variável básica. A utilização de expressões como índice no acesso a elementos de um vetor é uma ferramenta poderosa e deve ser usada.

159 5.9. LISTA DE EXERCÍCIOS 159 A utilização de funções para a manipulação de vetores como um todo é importante para a legibilidade e reusabilidade do programa e deve ser usada. A utilização de TAD s Implementacionais é um artifício para facilitar a manipulação de vetores e aumentar a legibilidade e reusabilidade do programa. O TAD Implementacional tvetorint, implementado aqui na linguagem C, pode ser utilizado, sempre que necessário, para a manipulação de vetores de números inteiros. 5.9 Lista de Exercícios 1. Faça um programa em linguagem C que leia 100 números reais e os imprima na ordem inversa em que foram lidos. 2. Crie uma função em linguagem C que some os elementos de um vetor com 100 elementos inteiros. 3. Faça um programa em linguagem C que leia as notas e as matrículas dos alunos de uma turma de no máximo 50 alunos numa prova e obtenha: (a) a melhor nota e o aluno que a obteve; (b) a pior nota e o aluno que a obteve; (c) a média da turma; (d) os alunos que obtiveram nota superior à média da turma. 4. Faça um programa em linguagem C que determine os 100 primeiros números primos. Considere que: (a) um número é primo quando só é divisível por si mesmo; (b) é suficiente testar a divisibilidade de um número X por números primos que o antecedem até um limite igual a raiz de X. 5. Faça um programa em linguagem C que leia duas listas de caracteres com até 100 elementos e coloque cada um num vetor respectivo. Após ter sido realizado a leitura das duas listas, deve ser lida a ordem a partir da qual deve ser inserida a segunda lista na primeira lista de caracteres. Considere o seguinte exemplo: Primeira lista: abcdjklm Segunda lista: efghi Ordem: 5 Resultado: abcdefghijklm

160 160 CAPÍTULO 5. VETORES 6. Faça um programa em linguagem C que leia dois vetores ordenados em M e N elementos respectivamente e os intercale gerando um novo vetor U, também ordenado. Considere o exemplo: M: 5 N: 4 Primeiro vetor: Segundo vetor: Resultado: Obs.: O terceiro vetor não pode ser gerado copiando-se primeiramente os dois vetores e fazendo a ordenação posteriormente. 7. Faça um programa em linguagem C que leia N (N < 1000) números de matrículas de alunos que fazem PD1 e os coloque em ordem crescente num vetor à medida que vão sendo lidas as matrículas. Posteriormente escreva uma função que identifique se um certo conjunto de M alunos fazem PD1. Deve-se utilizar o algoritmo de pesquisa binária para fazer esta verificação. 8. Faça um programa em linguagem C que: (a) leia N números reais colocando-os num vetor de 100 elementos (considerar N < 1000); (b) ordene crescentemente os elementos de índices ímpares utilizando o método da bolha para tal(considerar apenas os N elementos); (c) escreva os N números após o ajuste do ítem (b); 9. Crie um procedimento em linguagem C que cumpra a seguinte especificação: (a) Dados de entrada: i. um vetor de caracteres com um máximo de 100 elementos; ii. o tamanho corrente do vetor (suponha que o vetor inicie na primeira posição); iii. um caracter a ser inserido; iv. a posição do vetor onde o caracter deve ser inserido. (b) Dados de saída: i. o vetor com o caracter inserido na posição; ii. o vetor deve ser mantido inalterado se a posição onde deveria ser inserido o carcter superior ao tamanho corrente do vetor ou se o vetor já estiver completo.

161 5.10. TRABALHOS SUGERIDOS Um armazém trabalha com 100 mercadorias diferentes identificadas pelos números de 1 a 100. O dono anota as quantidades de cada mercadoria vendida no mês. Ele tem uma tabela que indica para cada mercadoria o preço de venda. Escreva um algoritmo em linguagem C que calcule o faturamento mensal do armazém, onde: F aturamento = Σ(quantidade i preco i ), i = 1, 2,..., Escreva um algoritmo em linguagem C que: (a) leia um conjunto A de 100 elementos reais; (b) construa e imprima um outro conjunto B formado da seguinte maneira: i. os elementos de ordem par são os correspondentes de A divididos por 2; ii. os elementos de ordem ímpar são os correspondentes de A multiplicados por Dado um vetor contendo uma frase com 80 caracteres (incluindo brancos), escreva um algoritmo em linguagem C para: (a) contar quantos brancos existem na frase; (b) contar quantas vezes aparece a letra A ; (c) contar quantas vezes aparece um mesmo par de letras na frase e quais são eles. 13. Faça um algoritmo em linguagem C para ler um vetor de comprimento N (par menor ou igual a 50) e imprimir seu conteúdo depois de feita a troca dos elementos das posições pares com os elementos das posições ímpares. 14. Faça um programa em linguagem C que, dado um nome terminado por ponto, devolva o número de vogais nele presentes. 15. Faça um programa em linguagem C que, dada uma frase terminada por ponto, retire todos os espaços em branco da mesma e retorne a frase modificada. 16. Faça um programa em linguagem C que, dada duas listas de nomes, compare-as e retorne o número de vezes que cada palavra da segunda lista aparece na primeira lista. Assuma que cada nome seja composto de no máximo 15 caracteres Trabalhos Sugeridos

162 162 CAPÍTULO 5. VETORES Figura 5.7: Método do Menor

163 5.10. TRABALHOS SUGERIDOS 163 Figura 5.8: Método da Bolha

164 Capítulo 6 Matrizes Autores: Clebson Oliveira Julio Dalfior Objetivos: Mostrar a importância de se utilizar a estrutura implementacional de dados matrizes para resolver problemas Introduzir o conceito do tipo abstrato de dado matriz (tmatriz) Mostrar como modelar um problema através do TAD tmatriz 6.1 Introdução Uma matriz é um tipo de dado composto homogêneo na qual seus elementos são organizados em uma estrutura multidimensional. Por exemplo, uma matriz bidimensional é formada pela combinação de linhas horizontais e verticais. A figura 6.1 mostra uma matriz 5 por 3. Note que a matriz possui 5 linhas e 3 colunas. Embora matrizes possam ter mais que 2 dimensões, neste livro serão abordadas apenas matrizes bidimensionais. Uma matriz com m linhas e n colunas é chamada matriz m por n. Para mostrar que matrizes bidimensionais são muito comuns no nosso dia-a-dia podemse citar vários exemplos: um cartão de bingo, uma agenda de compromissos (6.2), etc. É interessante que haja uma maneira de representar esse tipo de estrutura, para que se possa utilizá-la na solução de problemas. Na figura 6.2 tem-se que as linhas representam os dias do mês, as colunas representam os meses do ano e os elementos da matriz representam um determinado dia do mês. Considerando que essa matriz será utilizada para representar se há ou não compromisso naquele dia do mês, temos uma matriz bidimensional porque a cada célula dessa matriz estão 164

165 6.2. DEFINIÇÃO E ACESSO 165 Figura 6.1: Uma matriz 5 por 3 Figura 6.2: Agenda de compromissos e sua representação matricial associadas duas informações (mês e dia). Por outro lado, se for necessário listar varios compromissos para cada dia do mês ter-se-á uma matriz tridimensional, porque a cada célula da matriz estarão associadas três informações (mês, dia e hora). É fato que a visualização das matrizes utilizadas facilita o entendimento deste conceito. Por exemplo, uma matriz bidimensional pode ser visualizada como um quadro e uma tridimensional pode ser visualizada como um cubo formado por vários cubos menores. Apesar de ser difícil visualizar matrizes com mais de três dimensões, vale ressaltar que elas existem e que é possível declará-las em C. Como exemplo da utilização de matrizes com mais de três dimensões pode-se utilizar o exemplo anterior supondo que se queira representar também o ano. Então, teremos uma matriz de quatro dimensões (ano, mês, dia e hora). 6.2 Definição e Acesso Para que um problema seja modelado através de uma matriz é necessário que se defina uma variável para representá-la, para que assim se possa utilizar seus elementos para guardar informações e posteriormente aplicar operações sobre essa estrutura.

166 166 CAPÍTULO 6. MATRIZES Definição Para definir uma variável do tipo matriz é necessário que se saiba o tamanho de cada dimensão. No entanto, existem duas maneiras de estabelecer o tamanho dessas dimensões: estaticamente e dinamicamente. Estaticamente No momento em que se define a matriz é necessário estabelecer um tamanho para cada dimensão, sendo que esse tamanho não poderá ser alterado durante a execução do programa. O código do exemplo 6.1 mostra como isso pode ser feito em C: 1 int matriz [5][10]; Exemplo 6.1: Definição estática O número entre o primeiro par de colchetes ([5]) define o tamanho da primeira dimensão (número de linhas). O número entre o segundo par de colchetes ([10]) define o tamanho da segunda dimensão (número de colunas), e assim por diante. No caso acima a variável matriz possui 5 linhas e 10 colunas, como se pode ver na figura 6.3. Os traços na matriz representam a informação armazenada na célula, a qual é o compartimento onde estão localizados os elementos da matriz. Figura 6.3: Matriz 5 por 10 Inicialização Vale ressaltar que na declaração de matrizes mostrada anteriormente, os valores das células da matriz não foram inicializados e isso pode gerar resultados inesperados. Uma vez que não se sabe quais valores estavam armazenados anteriormente nas áreas de memória utilizadas. Para evitar esse tipo de problema é uma boa prática de programação inicializar os elementos das matrizes antes de tentar acessar seus valores, mesmo que posteriormente esses elementos sejam configurados com outros valores. A forma mais simples de se fazer isso, em C, pode ser vista no exemplo 6.2.

167 6.2. DEFINIÇÃO E ACESSO int main (){ 2 3 int i,j; 4 int matriz [2][2] = {0}; 5 6 return 0; 7 } Exemplo 6.2: Inicialização de uma matriz Na linha 4 do exemplo 6.2 temos a inicialização de todos os elementos da matriz com o valor zero Acesso Agora que a matriz já foi inicializada, para se utilizar os dados contidos nela será necessário fazer uma operação de acesso, como no exemplo 6.3: 1 # define NMESES 12 2 # define NDIAS 31 3 # define NHORAS main (){ 6 7 int reservasala [ NMESES ][ NDIAS ][ NHORAS ] = {0}; 8 int resposta =0; resposta = reservasala [5][1][10]; if( resposta ==0) { reservasala [5][1][10]=1; 17 } } Exemplo 6.3: Acesso e atualização de um elemento A variável resposta recebe 0 ou 1 indicando, respectivamente, se às 11 horas do dia 2 de junho a sala está livre ou reservada. Note que, assim como em vetores, o primeira posição de cada dimensão da matriz tem índice 0. Após acessar um elemento da matriz chamada reservasala (linha 12) e verificar que em uma determinada data e horário a sala está livre (linha 14), atualiza-se o estado da sala de livre para ocupada (linha 16).

168 168 CAPÍTULO 6. MATRIZES 6.3 O TAD implementacional tmatriz O TAD implementacional tmatriz é usado como ferramenta para resolver problemas que possuam característica de armazenamento relacionados à matrizes. Uma de suas principais vantagens é poder simular uma matriz cujo tamanho pode ser alterado dinamicamente, o que proporciona a reutilização dessa estrutura Atributos O primeiro passo para definir o TAD tmatriz envolve escolher os seus atributos. Como a idéia é modelar o problema por uma matriz, é necessário que um dos atributos seja a própria matriz que armazenará os elementos. Além disso é necessário guardar a quantidade de elementos de cada dimensão. Apesar de ser possível definir uma matriz com um número qualquer de dimensões e que armazene um tipo qualquer de dado (int, float, char, etc), neste capítulo serão utilizadas apenas matrizes bidimensionais de inteiros, por serem comumente utilizadas. Portanto, os atributos de uma matriz bidimensional de inteiros são: Número de linhas (nlinhas): Armazena a quantidade de elementos de cada coluna. Número de colunas (ncolunas): Armazena a quantidade de elementos de cada linha. Matriz (valores): Armazena os dados da matriz propriamente dita. No exemplo 6.4 pode ser vista uma maneira de se definir o TAD tmatrizint, de forma a representar matrizes bidimensionais de inteiros. 1 # define MAX_DIM typedef struct { 4 5 int valores [ MAX_DIM ][ MAX_DIM ]; 6 int nlinhas ; 7 int ncolunas ; 8 } tmatrizint ; Exemplo 6.4: Estrutura básica do tipo tmatrizint No exemplo 6.4 a matriz valores é declarada estaticamente, sendo seu tamanho definido pela constante MAX DIM. Como os próprios nomes evidenciam, nlinhas e ncolunas são variáveis inteiras que representam o número de linhas e colunas, respectivamente, da matriz. Na figura 6.4 pode-se visualizar a estrutura da matriz dados de uma variável do tipo tmatrizint inicializada como no exemplo abaixo: 1 tmatrizint dados ;

169 6.3. O TAD IMPLEMENTACIONAL TMATRIZ int i,j; 3 4 dados. nlinhas = 3; 5 dados. ncolunas = 3; 6 7 for ( i =0; i < dados. nlinhas ; i ++) { 8 for ( j =0; j < dados. ncolunas ; j ++) { 9 dados. valores [i][j ]=0; 10 } 11 } Exemplo 6.5: Inicialização de uma estrutura tmatrizint Nas linhas 4 e 5 o tamanho da matriz é definido como 3 por 3. Na linha 7, o comando for percorre as linhas da matriz de índice zero a nlinhas-1. O segundo comando for percorre todas as colunas (para cada laço do for anterior) de índice zero a ncolunas-1. Dessa forma percorremse todos os elementos da matriz valores, dentro das dimensões passadas, atribuindo-se a eles o valor zero. A figura 6.4 representa a matriz valores da estrutura dados do exemplo 6.5. Figura 6.4: Estrutura da matriz valores pertencente à variável dados (para MAX DIM igual a 5) Apesar da matriz valores pertencente à variável dados ter 5 linhas e 5 colunas, somente os elementos pertencentes às 3 primeiras linhas e colunas foram inicializados. Isso porque definiu-se, nas linhas 4 e 5, que a matriz deveria ter somente esse número de linhas e colunas. Dessa forma, em todas as operações efetuadas sobre essa variável somente os elementos pertencetes a essa submatriz (formada pelos zeros na figura) devem ser considerados. Todos os outros (marcados com sinal - ) possuem valores não determinados, por não terem sido inicializados, e devem ser ignorados Operações Uma das vantagens de se implementar um tipo abstrato de dado é facilitar a solução de problemas, através do uso de operações que atuem sobre o tipo abstrato de dado definido. Dessa

170 170 CAPÍTULO 6. MATRIZES forma, vamos definir algumas operações sobre o tmatrizint: inicialização, atualização de valor, acessoa valor, exibição, soma dos elementos de uma linha, soma dos elementos de uma coluna e produto escalar. Inicialização Uma operação essencial para se realizar sobre tmatrizint é a inicialização. Ela deve ser a primeira a ser realizada, caso contrário as demais podem gerar resultados inesperados (como término do programa, por exemplo). Isso porque, durante a inicialização, preparam-se as estruturas internas do TAD para se adequar ao caso desejado. No caso do tipo tmatrizint proposto nesse capítulo, a operação de inicialização definirá as dimensões iniciais da matriz. O código do exemplo 6.6 mostra como isso pode ser implementado: 1 tmatrizint inicializa ( int nlinhas, int ncolunas ){ 2 3 tmatrizint resultado ; 4 5 resultado. nlinhas = nlinhas ; 6 resultado. ncolunas = ncolunas ; 7 8 return resultado ; 9 } Exemplo 6.6: Função de inicialização Na definição da função Inicializa (linha 1), os argumentos nlinhas e ncolunas servem para definir a quantidade de linhas e colunas da matriz respectivamente, e esses valores são atribuídos à estrutura tmatriz nas linhas 6 e 7. Finalmente, na linha 16 a estrutura é retornada através da variável resultado. Atualização de Valor Como não é interessante que uma matriz apresente todos os elementos com os mesmos valores, é necessário definir uma forma de atualizá-los individualmente. Isso pode ser feito através da operação de escrita definida pela função do exemplo tmatrizint atualizamatriz ( tmatrizint matriz, int linha, int coluna, int valor ){ 2 3 if( ( linha >=0 && linha < matriz. nlinhas ) && ( coluna >=0 && coluna < matriz. ncolunas )){ 4 matriz. valores [ linha ][ coluna ] = valor ; 5 } 6 7 return matriz ; 8 }

171 6.3. O TAD IMPLEMENTACIONAL TMATRIZ 171 Exemplo 6.7: Escrita em um elemento da matriz Na linha 3 é verificado se o elemento pertence à matriz(se o números de sua linha e de sua coluna estão dentro das dimensões da matriz). Caso tudo esteja correto, o valor passado é atribuído ao elemento desejado e a função retorna o número 0, informando que a operação foi realizada com sucesso. Caso contrário a função retorna o número -1 para informar que a escrita não pôde ser efetuada. Acesso a valor Tendo em vista a importância de utilizar o valor de um determinado ítem, é necessário construir uma função para acessar os dados da matriz (exemplo 6.8). 1 int acessamatriz ( tmatrizint matriz, int linha, int coluna ) 2 { 3 4 if( ( linha >=0 && linha < matriz. nlinhas ) && ( coluna >=0 && coluna < matriz. ncolunas )){ 5 return matriz. valores [ linha ][ coluna ]; 6 7 } else { 8 return -1; 9 printf ( Ítem inexistente \n ); } 12 } Exemplo 6.8: Função de acesso aos dados da matriz Na linha 4 é realizada a mesma verificação que na linha equivalente do exemplo 6.7. Se o elemento pertencer à matriz o seu valor é retornado. Caso contrário, o valor -1 é retornado e uma mensagem de erro é impressa na tela. Note que, na maioria das aplicações, não será possível verificar a execução correta da função analisando o valor de retorno, pois o valor retornado (-1) pode fazer parte do universo de valores válidos para a aplicação. Exibição Para poder visualizar todos os elemetos de uma matriz de inteiros é necessário definir uma operação de exibição (ou impressão) da matriz na tela (exemplo 6.9). 1 int exibematriz ( tmatrizint matriz ){ 2 3 int i=0, j =0; 4 5 for (i =0; i < matriz. nlinhas ; i ++) {

172 172 CAPÍTULO 6. MATRIZES 6 for (j =0; j < matriz. ncolunas ; j ++) { 7 printf (" %5d\0", matriz. valores [i][j]); 8 9 } 10 printf ("\n"); 11 } 12 } Exemplo 6.9: Exibição da matriz na tela No exemplo 6.9 tem-se, na linha 5, um comando for para varrer as linhas da matriz, e para cada linha tem-se outro comando for, na linha 6, para varrer os elementos dessa linha. Na linha 7, o trecho %5d faz com que os números sejam exibidos sempre com 5 dígitos, o que faz com que a forma de exibição seja regular, como pode ser visto na figura 6.5. Figura 6.5: Exemplo de saída da operação exibe Soma dos elementos de uma linha Como em muitas aplicações é necessário obter o valor da soma dos elementos de uma linha, será definida uma operação para efetuar esse cálculo. 1 int somalinhamatriz ( tmatrizint matriz, int linha ){ 2 3 int i=0, soma =0; 4 5 for (i =0; i < matriz. ncolunas ; i ++) { 6 soma = soma + matriz. valores [ linha ][ i]; 7 } 8 9 return soma ; 10 } Exemplo 6.10: Soma dos elementos de uma linha A variável soma é inicializada com o valor zero. O for da linha 5 percorre todos os elementos da linha da matriz e, para cada laço, adiciona o valor da célula da matriz a soma. A variável soma funciona, então, como um acumulador e, ao final dos laços, armazena o valor da soma dos elementos da linha.

173 6.3. O TAD IMPLEMENTACIONAL TMATRIZ 173 Soma dos elementos de uma coluna Anteriormante foi definida a somda dos elementos de uma linha. operação para efetuar a soma dos elementos de uma coluna. Agora será definida uma 1 int somacolunamatriz ( tmatrizint matriz, int coluna ){ 2 3 int i=0, soma =0; 4 5 for (i =0; i < matriz. nlinhas ; i ++) { 6 soma = soma + matriz. valores [ i][ coluna ]; 7 } 8 9 return soma ; 10 } Exemplo 6.11: Soma dos elementos de uma coluna A variável soma é inicializada com o valor zero. O for da linha 5 percorre todos os elementos da coluna da matriz e, para cada laço, adiciona o valor da célula da matriz a soma. A variável soma funciona, então, como um acumulador e, ao final dos laços, armazena o valor da soma dos elementos da coluna. Produto Escalar Agora, será definida uma operação de composição chamada produto escalar, que gera uma matriz M de cujos elementos são resultados de uma composição dos elementos de A e B 1 (M=A.B). Nesta operação os elementos mij da matriz M são resultantes do somatório dos produtos entre os elementos das linhas i da primeira matriz, e os elementos das colunas j da segunda matriz, para que esta operação seja executada é necessário que o número de colunas da matriz A seja igual ao número de linhas da matriz B. 1 tmatrizint produtoescalar ( tmatrizint matriz1, tmatrizint matriz2 ){ 2 3 int i=0, j=0, k =0; 4 tmatrizint produto ; 5 6 if( matriz1. ncolunas == matriz2. nlinhas ){ 7 8 for (i =0; i < matriz1. nlinhas ; i ++) { 9 10 for (j =0; j < matriz2. ncolunas ; j ++) { produto. valores [i][j ]=0; for (k =0; k < matriz1. ncolunas ; k ++) { 1 Vale ressaltar que A.B é diferente de B.A. Logo, o produto escalar entre matrizes não é comutativo

174 174 CAPÍTULO 6. MATRIZES produto. valores [i][j] += matriz1. valores [i][k] * 17 matriz2. valores [k][j]; 18 } 19 } 20 } produto. nlinhas = matriz1. nlinhas ; 23 produto. ncolunas = matriz2. ncolunas ; 24 } else { produto. nlinhas = produto. ncolunas =0; 27 } return produto ; 30 } Exemplo 6.12: Soma dos elementos de uma fila 6.4 Exercícios Resolvidos Exercício Resolvido Alocação de sala Deseja-se desenvolver um programa para controlar a reserva de uma sala ao longo de um ano. Ao usuário devem ser fornecidas as seguintes operaçoes: 1. Reservar a sala 2. Cancelar reserva 3. Dado um mês, listar dias em que a sala está disponível 4. Informar o índice de ocupação de cada mês Implemente o programa utilizando o TAD tmatrizint. Solução Possível: Para se armazenar as informações de reserva da sala será utilizada uma matriz com 31 linhas (representando os dias) e 12 colunas (representado os meses). Dessa forma, para se reservar a sala para o dia 1 o de janeiro, atribui-se ao elemento da linha 0 e coluna 0 o valor 1. Para resolver o problema será criada uma função para cada operação. 1. Reservar a sala: A primeira coisa a ser feita é verificar a reserva da sala para a data informada. Se a sala estiver livre será reservada, uma mensagem de sucesso impressa na tela e a matriz alterada será retornada. Caso contrário, a matriz é retornada inaltarada e uma mensagem de erro impressa na tela.

175 6.4. EXERCÍCIOS RESOLVIDOS tmatrizint reservasala ( tmatrizint mat, int dia, int mes ){ 2 3 if ( acessamatriz (mat, dia, mes ) == 0){ 4 printf (" Sala reservada com sucesso \ n"); 5 return atualizamatriz ( mat, dia, mes, 1); 6 } else { 7 printf (" Sala já reservada para a data informada \ n"); 8 return mat ; 9 } 10 } Exemplo 6.13: Reservar a sala 2. Cancelar a reserva: Novamente, deve-se verificar a reserva da sala para a data informada. Se a sala estiver reservada a reserva será cancelada, uma mensagem de sucesso impressa na tela e a matriz alterada será retornada. Caso contrário, a matriz é retornada inaltarada e uma mensagem de erro impressa na tela. 1 tmatrizint cancelareserva ( tmatrizint mat, int dia, int mes ){ 2 3 if ( acessamatriz (mat, dia, mes )!= 0){ 4 printf (" Reserva cancelada "); 5 return atualizamatriz ( mat, dia, mes, 0); 6 } else { 7 printf (" Sala n~ao reservada para a data informada \ n"); 8 return mat ; 9 } 10 } Exemplo 6.14: Cancelar reserva 3. Listar dias livres do mês: Deve-se verificar, em um laço, todos os dias do mês escolhido e, para cada dia, verificar a reserva da sala. Se ela estiver ocupada, então o dia será impresso na tela de modo a informar isso ao usuário. Note que o número de dia de cada mês varia e, portanto, deve haver um controle de forma que o laço termine com o número de dias correto para o mês escolhido. 1 void listadiaslivres ( tmatrizint mat, int mes ){ 2 3 int dia ; 4 int numerodiasmes [] = {31,28,31,30,31,30,31,31,30,31,30,31}; 5 printf (" Dias livres para o m^es % d\ n", mes +1) ; 6

176 176 CAPÍTULO 6. MATRIZES 7 for ( dia = 0; dia < numerodiasmes [ mes ]; dia ++) { 8 if ( acessamatriz (mat, dia, mes ) == 0){ 9 printf ("%d\n", dia +1) ; 10 } 11 } 12 } Exemplo 6.15: Listar dias livres do mês 4. índide de ocupação de cada mês: O índice de ocupação de um mês nada mais é do que o número de dias com a sala reservada naquele mês. Além disso, como a sala ocupada é representada pelo número 1 e a sala vazia pelo número 0, somando-se a coluna que representa um determinado mês obtem-se o número de dias ocupados para esse mês. Assim sendo, para resolver o problema, basta percorrer todas as colunas da matriz e, para cada uma delas, efetuar a soma de seus elementos. 1 void imprimeindiceocupacao ( tmatrizint mat ){ 2 3 int mes ; 4 int indice ; 5 6 printf (" índice de ocupaç~ao dos meses do ano \ n"); 7 8 for ( mes = 0; mes < 12; mes ++) { 9 indice = somacolunamatriz ( mat, mes ); printf (" Mes %d: %d dias ocupados \n", mes, indice ); 12 } 13 } Exemplo 6.16: ndide de ocupação de cada mês Uma vez definidas as funções que realizam as operações, falta então definir a função principal do programa onde a interação com o usuário será feita e as funções serão chamadas. 1 int main () 2 { 3 tmatrizint reservas ; 4 int codigo, dia, mes ; 5 6 reservas = inicializa (31, 12) ; 7 8 for ( mes =0; mes <12; mes ++) { 9 for ( dia =0; dia <31; dia ++) 10 reservas. valores [ dia ][ mes ] = 0; 11 }

177 6.4. EXERCÍCIOS RESOLVIDOS do { 14 printf ("\\n\\n\\n\\n\\n\\n\\n"); 15 printf ("## Reserva de Salas ##\\ n\\ nescolha uma operaç~ao :\\ n"); 16 printf (" 1- Reservar a sala \\ n"); 17 printf (" 2- Cancelar uma reserva \\ n"); 18 printf (" 3- Listar dias livres \\ n"); 19 printf (" 4- Exibir índice de ocupaç~ao \\ n"); 20 printf (" 5- Sair \\n"); 21 printf (" Digite o codido da opecaç~ao escolhida : "); 22 scanf ("%d", & codigo ); switch ( codigo ){ 25 case 1: 26 printf ("\\n\\ ndigite a data da reserva ( dia / mes ): "); 27 scanf ("%d/%d", &dia, & mes ); 28 reservas = reservasala ( reservas, dia, mes ); 29 break ; case 2: 32 printf ("\\n\\ ndigite a data da reserva a cancelar ( dia / mes ): "); 33 scanf ("%d/%d", &dia, & mes ); 34 reservas = cancelareserva ( reservas, dia, mes ); 35 break ; case 3: 38 printf ("\\n\\ ndigite o mes : "); 39 scanf ("%d", & mes ); 40 listadiaslivres ( reservas, mes ); 41 break ; case 4: 44 imprimeindiceocupacao ( reservas ); 45 break ; 46 } 47 } while ( codigo!= 5); return 0; } Exemplo 6.17: Interação com usuário Exercício Resolvido Soma de elementos Dada uma matriz bidimensional de inteiros, defina operações que realizem os seguintes cálculos: A soma dos elementos da diagonal principal A soma dos elementos da submatriz triangular

178 178 CAPÍTULO 6. MATRIZES Solução Possível: A soma dos elementos da diagonal principal Para calcular a soma dos elementos da diagonal principal de uma matriz M[i,j] é necessário notar que todos os elementos da diagonal principal apresentam índices referentes a coluna e à linha iguais (M[i,i]). Assim, varrer-se a diagonal principal da matriz através de um loop, na linha 6, onde a cada laço soma-se o valor do elemento M[i,i] à variável soma e incrementa-se o valor do índice i de uma unidade. 1 int somadiagonal ( tmatrizint matriz ){ 2 3 int soma =0; 4 int i; 5 6 for (i =0; i< matriz. nlinhas && i< matriz. ncolunas ; i ++) { 7 soma = soma + acessamatriz ( matriz, i, i); 8 } 9 10 return soma ; 11 } Exemplo 6.18: Soma da diagonal principal A soma dos elementos da submatriz triangular superior Como os elementos da matriz triangular superior são os elementos que estão situados acima dos elementos da diagonal principal, pode-se notar que para cada linha esses elementos estão situados nas colunas à direita do elemento da diagonal principal. Dessa forma, varrese a matriz triangular superior através de um loop, na linha 6, que varre todas as linhas i da matriz, e para cada linha tem-se outro loop, linha 7, para varrer as colunas de índice i+1 até ncolunas. Então, soma-se o valor de cada elemento visitado à variável soma, linha 8. 1 int somatriangular ( tmatrizint matriz ){ 2 3 int soma =0; 4 int i,j; 5 6 for (i =0; i< matriz. nlinhas ; i ++) { 7 for (j=i +1; j< ncolunas ; j ++) { 8 soma = soma + acessamatriz ( matriz, i, j); 9 }

179 6.4. EXERCÍCIOS RESOLVIDOS } return soma ; 13 } Exemplo 6.19: Soma da matriz triangular superior Exercício Resolvido Uns isolados Dada uma matriz bidimensional nxn de zeros e uns, defina uma operação para calcular o número de uns isolados dessa matriz. Considere que um número um está isolado se nenhum dos elementos adjacentes a ele, apenas na horizontal e vertical, são uns. Solução Possível: Para cada célula que contenha o valor um, será necessário checar no máximo quatro células adjacentes a ela para saber se a mesma contém um número um isolado. Os loops das linhas 5 e 7 servem para varrer a matriz completamente, e a cada célula visitada checa-se se suas células adjacentes são zeros, dependendo de quantas ela possa ter. Assim, se o elemento visitado estiver na primeira linha (linha 9) ele pode ser: o primeiro da linha (11), e então só terá adjacentes abaixo e à direita (12); o último da linha (linha 16), e então só terá adjacentes abaixo e à esquerda (17); ou caso não seja nem o primeiro e nem o último, terá adjacentes abaixo, à esquerda e à direita. Caso o elemento esteja na última linha (linha 27) ele pode ser: o primeiro da linha (linha 29), e então só terá adjacentes acima e à direita (linha 30); o último da linha (linha 34), e então só terá adjacentes acima e à esquerda (linha 35); ou caso não seja nem o primeiro e nem o último terá adjacentes acima, à esquerda e à direita (linha 39). Após checar se o elemento visitado está na primeira ou na última linha da matriz, temos o caso dele estar na primeira ou na última coluna, desconsiderando os elementos dessas colunas que estejam na primeira ou na última linha. Se ele estiver na primeira coluna (linha 45) terá adjacentes acima, abaixo e à direita (linha 46); e se ele estiver na última coluna (linha 50), terá adjacentes acima, abaixo e à esquerda (linha 51). Finalmente, se o elemento visitado não estiver na primeira linha, na última linha, na primeira coluna e nem na última coluna (linha 53); ele terá adjacentes acima, abaixo, à esquerda e à direita (linha 55). 1 int unsisolados ( tmatrizint matriz ){ 2 3 int i,j, unsisolados =0; 4 5 for (i =0;i< matriz. nlinhas ;i ++) { 6 7 for (j =0;j< matriz. ncolunas ;j ++) { 8 9 if(i ==0) { if(j ==0) {

180 180 CAPÍTULO 6. MATRIZES 12 if( acessamatriz ( matriz, i, j +1) ==0 && acessamatriz ( matriz, i+1, j ) ==0 && acessamatriz ( matriz,i,j) ==1) 13 unsisolados ++; 14 } else { if(j== matriz. ncolunas -1) { 17 if( acessamatriz ( matriz, i, j -1) ==0 && acessamatriz ( matriz, i +1, j) ==0 && acessamatriz ( matriz,i,j) ==1) 18 unsisolados ++; 19 } else { if( acessamatriz ( matriz, i+1, j) ==0 && acessamatriz ( matriz, i, j +1) ==0 && acessamatriz ( matriz, i, j -1) ==0 && acessamatriz ( matriz,i,j) ==1) 22 unsisolados ++; 23 } 24 } 25 } else { if(i== matriz. nlinhas -1) { if(j ==0) { 30 if( acessamatriz ( matriz, i, j +1) ==0 && acessamatriz ( matriz, i -1, j) ==0 && acessamatriz ( matriz,i,j) ==1) 31 unsisolados ++; 32 } else { if(j== matriz. ncolunas -1) { 35 if( acessamatriz ( matriz, i, j -1) ==0 && acessamatriz ( matriz, i -1, j) ==0 && acessamatriz ( matriz,i,j) ==1) 36 unsisolados ++; 37 } else { if( acessamatriz ( matriz, i -1, j) ==0 && acessamatriz ( matriz, i, j +1) ==0 && acessamatriz ( matriz, i, j -1) ==0 && acessamatriz ( matriz,i,j) ==1) 40 unsisolados ++; 41 } 42 } 43 } else { if(j ==0 && i >0 && i< matriz. nlinhas -1) { 46 if( acessamatriz ( matriz, i, j +1) ==0 && acessamatriz ( matriz, i +1, j) ==0 && acessamatriz ( matriz, i -1, j) ==0 && acessamatriz ( matriz,i,j) ==1) 47 unsisolados ++; 48 } else { if(j== matriz. ncolunas -1 && i >0 && i< matriz. nlinhas -1) { 51 if( acessamatriz ( matriz, i, j -1) ==0 && acessamatriz ( matriz, i+1, j) ==0 && acessamatriz ( matriz, i -1, j) ==0 &&

181 6.5. RESUMO 181 acessamatriz ( matriz,i,j) ==1) 52 unsisolados ++; 53 } else { if( acessamatriz ( matriz, i, j +1) ==0 && acessamatriz ( matriz, i, j -1) ==0 && acessamatriz ( matriz, i+1, j) ==0 && acessamatriz ( matriz, i -1, j) ==0 && acessamatriz ( matriz, i,j) ==1) 56 unsisolados ++; 57 } 58 } 59 } 60 } 61 } 62 } return unsisolados ; 65 } Exemplo 6.20: Soma da matriz triangular superior 6.5 Resumo Uma matriz é um tipo de dado composto homogêneo na qual seus elementos são organizados em uma estrutura multidimensional. É necessário inicializar a matriz para evitar situações de acesso a elementos que contenham valores indeterminados. O TAD tmatriz é composto pelos seguintes atributos: número de linhas da matriz, número de colunas da matriz e a estrutura que armazenará os elementos. Quando se tenta acessar um elemento com posição não definida na matriz, é necessário retornar uma mensagem de erro para indicar que ocorreu um erro na execução do programa. 6.6 Exercícios Propostos 1. Escreva um subprograma que determine o maior valor em uma matriz de valores inteiros com n > 0 linhas e m > 0 colunas. 2. Altere o subprograma anterior para exibir na tela todas as posições da matriz em que se encontra tal valor máximo. 3. Faça um programa que calcule a matriz resultante da soma de duas matrizes de dimensões m linhas e n colunas, com valores inteiros.

182 182 CAPÍTULO 6. MATRIZES 4. Escreva um subprograma que calcule a transposta de uma dada matriz. Considere como matriz trasposta A t de A a matriz de cujos elementos A t [i,j] são iguais a os elementos A[j][i] para 1<=i<=m e 1<=j<=n, onde m representa o número de linhas e n o número de colunas da matriz A. 5. Escreva uma função que verifica se uma matriz nxn é simétrica. Uma matriz A é simétrica se A[i,j] = A[j,i] para todo 1<=i,j<=n. 6. Faça um subprograma que calcule a soma dos termos que se situam na diagonal secundária ou abaixo dela numa matriz quadrada com elementos inteiros. Assuma que o número máximo de linhas e colunas é 100, mas que a matriz pode estar preenchida apenas parcialmente. Observação: Antes de escrever o subprograma, você deve apresentar a declaração do tipo da matriz que deve ser colocada nos programas que usarão este subprograma. 7. Faça um subprograma que receba uma matriz quadrada (dimensões N x N) totalmente preenchida com números inteiros e troque os elementos acima da diagonal principal pelos que estão abaixo dela. Atente para o fato que a matriz recebida deve ser retornada modificada e que você não pode usar uma matriz ou vetor auxiliar para fazer a troca dos elementos. Exemplo para matriz 3 x 3: antes depois 8. Uma matriz quadrada inteira é chamada de quadrado mágico se a soma dos elementos de cada linha, a soma dos elementos de cada coluna e a soma dos elementos das diagonais principal e secundária são todos iguais. Exemplo de um quadrado mágico:

183 6.6. EXERCÍCIOS PROPOSTOS 183 Escreva uma função que verifica se uma matriz de n linhas e n colunas representa um quadrado mágico. 9. Um quadrado latim de ordem n contém os números de 1 até n de forma que nenhum número é repetido na mesma linha ou coluna. Este quadrado pode ser usado, por exemplo, em alguns torneios esportivos para assegurar que cada equipe joga com todas outras equipes uma e somente uma vez. Escreva uma função booleana que recebe uma matriz quadrada e checa se ele é realmente um quadrado latim. Exemplo de quadrado latim: (número de Considere 2 matrizes quadradas do tipo tmatrizint. Faça um subprograma para cada item abaixo (note que as matrizes devem obrigatoriamente ser passadas como parâmetros aos subprogramas): 11. A matriz abaixo representa o triângulo de pascal de ordem 6: Os elementos extremos de cada linha são iguais a 1. Os outros elementos são obtidos somando-se os dois elementos que aparecem imediatamente acima e à esquerda na linha anterior. Assim 10 = Escreva um programa que, dado n, gere e exiba na tela o triângulo de Pascal de ordem n. A impressão não deve conter zeros acima da diagonal principal. 12. Considere uma matriz de inteiros. Define-se como vizinhança de uma posição da matriz a soma de todos os números que estejam em posições adjacentes. Escreva um programa que determine a posição do elemento de maior vizinhança. a)exibir uma matriz com as características definidas acima (considere que a dimensão N, isto é, seu número de linhas ou colunas, da matriz será lida no programa principal e passada como parâmetro para o subprograma); b)determinar se as duas matrizes são idênticas; c)determinar se a segunda matriz é uma rotação de 90 graus à direita sobre a primeira;

184 184 CAPÍTULO 6. MATRIZES d)determinar se a segunda matriz é uma rotação de 180 graus à direita sobre a primeira; e)determinar se a segunda matriz é a imagem espelhada vertical da primeira; f)determinar se a segunda matriz é a imagem espelhada horizontal da primeira; As figuras abaixo ilustram cada tipo de matriz com um exemplo de dimensão 3 x 3: original idêntica à direita à direita 13. Escreva ainda um programa que leia 2 matrizes e determine as relações existentes entre elas utilizando os subprogramas do exercício anterior. 14. Escreva uma função que receba uma matriz preenchida com caracteres maiúsculos do tipotmatrizchar e um vetor do tipo tvetor contendo uma palavra em letras maiúsculas e retorne o número de ocorrências da palavra na matriz. A palavra pode estar escrita na matriz de cima para baixo, da esquerda para a direita ou nas diagonais paralelas à diagonal principal ou na própria.

185 6.7. TRABALHOS SUGERIDOS Trabalhos Sugeridos 1. Caça-Palavras Considere o jogo caça-palavras que consiste em procurar uma palavra dada em uma matriz de letras. Considera-se que a palavra foi encontrada se ela estiver inteiramente disposta em uma linha, em uma coluna ou em uma diagonal da matriz. Define-se uma palavra como uma sequência de letras maiúsculas (para facilitar não considere acentuação). As palavras estão escritas na matriz sempre de cima para baixo, da esquerda para a direita ou nas diagonais paralelas à diagonal principal ou na própria diagonal. Faça um programa que tenha como entrada um conjunto de n palavras e a matriz de caracteres, ambas fornecidas pelo teclado. O programa deve ser capaz de: 1. Verificar quais palavras foram encontradas e computar o número de ocorrências de cada uma delas na matriz. 2. Escrever em um arquivo texto, a lista das palavras encontradas e a listas das palavras não encontradas. As duas listas devem estar em ordem alfabética. 3. Escrever em seguida, no mesmo arquivo texto, a lista das palavras encontradas na matriz, ordenadas pelo seu número de ocorrências (considerar ordem decrescente). Informar também nestes casos, a posição na matriz do caractere inicial de cada ocorrência da palavra, além da direção que ela se encontra na matriz. As direções são definidas como: diagonal, vertical, horizontal. 4. Informe a direção que mais palavras foram encontradas: diagonal, vertical ou horizontal. Informe também a quantidade de palavras encontradas nesta direção. O arquivo texto de saída deve seguir o formato apresentado no exemplo a seguir: Exemplo: Conjunto de palavras: CAPELA, LAPIS, FLORESTA, AULA, ALEGRIA, MANGA Matriz: Arquivo de saída: F L O R E S T A R S H J V N O A C I W Z C A P E L A P I S C I K V D I P M B S F T Y G C S A L U O P Q L K Ç A D U O L Ç A A S A R P R Y O A N E N E F T E B A U J K D D S H W L F L I O T U G J J V A A Lista de palavras encontradas em ordem alfabética:

186 186 CAPÍTULO 6. MATRIZES AULA CAPELA FLORESTA LAPIS Lista de palavras não encontradas em ordem alfabética: ALEGRIA MANGA Lista de palavras encontradas, ordenadas pelo número de ocorrências na matriz: CAPELA 2 (2, 0) horizontal (4, 3) diagonal AULA 1 (6,9) vertical FLORESTA 1 (0, 0) horizontal LAPIS 1 (2,4) horizontal Direção em que mais palavras foram encontradas: horizontal Número de palavras encontradas nesta direção: Tópicos Avançados Definição Dinâmica de uma matriz Além da definição estática abordada na seção 6.2.1, uma matriz pode ser definida dinamicamente. Nesse caso o tamanho de suas dimensões só será estabelecido durante a execução do programa, conforme é visto no exemplo 6.21: 1 # include <stdio.h> 2 3 main (){ 4 5 int n, m; 6 7 printf ( Forneça as dimens~oes da matriz : ); 8 scanf ( %d%d,&n,&m); 9 int matriz [n][m]; } Exemplo 6.21: Definição dinâmica Nesse exemplo a matriz só será definida após a execução da função scanf, onde o tamanho de suas dimensões será lido do teclado.

187 Capítulo 7 Apontadores Autores: Igor Magri Vale Douglas Almonfrey Flávio Varejão Objetivos: Definir o que são apontadores; Apresentar a sintaxe de apontadores; Mostrar a importância dos apontadores na passagem de parâmetros sem cópia, no retorno de múltiplos valores nas funções e na aloção dinâmica de memória; Apresentar os principais problemas no uso de apontadores; Definir o TAD Lista Encadeada e suas principais operações; Neste capítulo procurou-se dar ênfase a aspectos relevantes, de carácter introdutório, para que este texto seja utilizado como primeira leitura, e posterior consulta, sobre apontadores. 7.1 Variáveis Apontadores Em um programa, cada variável possui seu endereço e um conteúdo. A Figura 7.1 exibe cinco endereços de memória e seus respectivos conteúdos. Os apontadores são um tipo especial de variável, cujo conteúdo não é um simples valor, e sim um endereço de memória. Na Figura 7.2, pode ser observado que o conteúdo de uma variável apontador corresponde a um endereço de uma variável não-apontador. Neste capítulo, serão estudados os apontadores, também chamados de ponteiros. Como os apontadores são variáveis que guardam endereços, trata-se de um conceito de baixo nível, muito ligado à arquitetura de computadores. Apontadores são ferramentas poderosas na linguagem de 187

188 188 CAPÍTULO 7. APONTADORES Figura 7.1: Formato Abstrato da Memória Figura 7.2: Apontadores e Não-Apontadores programação C. Dentre diversas funções, apontadores permitem passagem de parâmetros sem cópia de muitos dados, retorno de mltiplos valores em funções, alocação dinâmica de memória e construção de listas encadeadas. No decorrer do capítulo, serão utlizados modelos de figuras ilustrativas para auxiliar na explicação. Na Figura 7.3, tem-se o modelo para representar uma variável e seu endereço. Como pode ser observado na Figura 7.3, um quadrado será usado como formato abstrato de variável de memória. Dentro deste quadrado estará representado o conteúdo da variável. Além disso, um círculo negro no seu vértice superior esquerdo representará abstratamente seu endereço. Já para representar a ligação entre um apontador e a variável que está sendo apontada por ele, será utilizado o modelo da Figura 7.4. Tal ligação é ilustrada por meio de uma seta, que sai da variável apontador, e aponta o cículo negro (endereço) da variável não-apontador. 7.2 A Sintaxe dos Apontadores Na linguagem C, o operador asterisco * é que especifica se uma variável guarda um endereço, ou seja, se é um apontador. A declaração de um apontador segue o seguinte formato:

189 7.2. A SINTAXE DOS APONTADORES 189 Figura 7.3: Formato Abstrado de uma Variável Figura 7.4: Abstração de uma Variável Apontador <tipo do apontador> * <nome da variável> No lugar do tipo do apontador, devem-se utilizar alguns dos tipos padrões da linguagem C, como char, int e float, ou até mesmo novos tipos definidos pelo programador. No Exemplo 7.1, tem-se como criar apontadores dos tipos char, int, float e taluno. 1 typedef struct { 2 char nome [30]; 3 int matricula ; 4 } taluno ; 5 6 main ( ) { 7 char *c; 8 int *p; 9 float *f; 10 taluno * x; 11 }

190 190 CAPÍTULO 7. APONTADORES Exemplo 7.1: Declaração de apontadores Assim, no código do Exemplo 7.1, c pode apontar para uma área de memória que guarda uma variável do tipo caracter, enquanto p, f e x dos tipos inteiro, ponto flutuante e taluno, respectivamente. Dois outros operadores também estão associados à sintaxe de apontadores: o operador endereço de memória e o operador seta. Eles serão discutidos nas duas próximas subseções Operador Endereço de Memória Em C, o operador unário &, quando aplicado a uma variável, retorna o endereço dela. Portanto, quando um apontador é declarado, pode-se inicializá-lo usando o operador &, que faz o apontador receber um endereço de uma variável já existente. O Exemplo 7.2 mostra o uso do operador (&) para inicializar uma variável apontador. A variável p recebe o endereço da variável d. Note que ambas variáveis são do mesmo tipo, pois é fundamental que o apontador aponte para uma variável de tipo compatível com a declaração dele. 1 int *p; 2 int d = 10; 3 p = &d; 4 printf ("%p\n%p\n", &d, p); Exemplo 7.2: Inicialização de um apontador usando o operador endereço de memória No Exemplo 7.2, serão impressos o endereço de d, e logo após, o valor do apontador p, que é o endereço de d. Portanto, dois valores iguais serão impressos na tela. O %p, utilizado no Exemplo 7.2, é usado pela função printf para identificar o tipo de valor que será impresso, neste caso %p informa que o valor de um apontador será impresso, ou seja, o endereço de uma variável O Operador Seta Outro operador associado a apontadores é a seta ( >). Quando um apontador indica uma variável do tipo estrutura, para que se acesse os atributos desta variável, ao invés do ponto (.), utiliza-se ( >). Observe o Exemplo typedef struct { 2 char nome [30]; 3 int matricula ; 4 } taluno ; 5 6 main ( ) {

191 7.3. ACESSO À VARIÁVEL POR MEIO DE APONTADORES taluno *x; 8 taluno y; 9 10 x = &y; 11 x- > matricula = 2031; printf ("%d\n", y. matricula ); 14 } Exemplo 7.3: Acesso ao atributo de uma estrutura por meio do operador seta Note que no Exemplo 7.3, x é um apontador para uma estrutura taluno e y uma variável taluno. Após fazer x apontar para o endereço de y, para acessar a matrícula de y através de x, deve ser utilizado o operador >. Já para imprimir o atual valor da matricula de y, que passou a ser 2031, basta utilizar o ponto. 7.3 Acesso à Variável por Meio de Apontadores O operador unário * tem uma outra função além de especificar uma multiplicação ou se uma variável é apontador. Este operador também pode retornar o valor armazenado na variável apontada pelo apontador. Por isso, apontadores podem ser usados para atribuir valores a outras variáveis. Observe o Exemplo int *p; 2 int c = 10; 3 int d; 4 p = &c; 5 d = *p; Exemplo 7.4: Acesso ao conteúdo de variáveis por meio de apontadores No Exemplo 7.4, p recebe o endereço de c. Através de p portanto, pode-se agora acessar o valor de c. Com esse recurso, na quinta linha do Exemplo 7.4, d recebe o valor de c por meio de p. Uma variável pode ser atribuída através do uso de apontadores. No Exemplo 7.5, a variável c tem seu valor alterado através de uma atribuição feita a p. 1 float *p; 2 float c = 15; 3 p = &c; 4 *p = *p + 1; 5 printf ("%f", c); Exemplo 7.5: Alterando valor de variáveis por meio de apontadores

192 192 CAPÍTULO 7. APONTADORES Pode ser observado no Exemplo 7.5, que p aponta para c. Assim, a expressão na linha 4 incrementa o conteúdo da variável apontada por p em uma unidade, ou seja, c passa a ter o valor 16, o qual é impresso na tela. Uma variável pode receber o valor de outra por meio de apontadores. Na linha 7 do Exemplo 7.6, a variável i apontada por v tem seu valor alterado para o de d, pois d está apontado por j. 1 float *v; 2 float *j; 3 float d = 15.0; 4 float i = 17.0; 5 j = &d; 6 v = &i; 7 *v = *j; 8 v = j; Exemplo 7.6: Alterando valor de variáveis através de apontadores Um apontador também pode ser atribuído a outro apontador. No Exemplo 7.6, linha 8, v passa a apontar para o mesmo endereço que j aponta. Por fim, esse acesso a variáveis através de apontadores é muito importante, pois é por meio deste recurso que as variáveis passadas como parâmetros de uma função são alteradas definitivamente, dentro da própia função. 7.4 Uso de Apontadores nas Passagens de Parâmetros O conceito de função em C, conforme já discutido em capítulos precedentes, é fundamental. Uma das principais vantagens de apontadores, no que diz respeito à função, é a passagem de valores sem cópia. Na passagem por cópia, os variáveis que são passadas como argumentos da função tem seus valores copiados. São essas cópias que a função utilizará no decorrer da sua execução. Assim, qualquer alteração dos valores das cópias não implicará em mudança nas variáveis originais. Por meio de apontadores, é possível alterar os valores das variáveis passadas como argumentos para uma função. Ao declarar que um parâmetro de uma função é apontador, deve-se passar um endereço de uma variável como argumento correspondente. Nesse tipo de passagem, não é copiado o valor da variável argumento, mas com o endereço dela, pode-se acessar o conteúdo e modificá-lo no decorrer da execução da função. O Exemplo 7.7 mostra a passagem por cópia e por apontadores. 1 void adicionax1 ( int x, int b) { 2 b = b + x; 3 } 4 5 void adicionax2 ( int x, int * b) { 6 *b = *b + x;

193 7.4. USO DE APONTADORES NAS PASSAGENS DE PARÂMETROS } 8 9 main ( ) { 10 int a = 0; adicionax1 (10, a); 13 printf ("a = %d\n", a); 14 adicionax2 (10, &a); 15 printf ("a = %d\n", a); 16 } Exemplo 7.7: Passagem de valor por cópia e através de apontadores Em adicionax1, no Exemplo 7.7, uma cópia do valor de a é passada com argumento da função, ou seja, b recebe uma cópia do valor 0 de a. Dentro da função, o valor de b muda para 10, mas o de a continua o mesmo. Assim, o primeiro valor impresso é 0. Já em adicionax2, o endereço de a é passado para o ponteiro b e, na execução da função, é adicionado x ao conteúdo da variável apontada por b, o que faz a ter seu valor modificado para 10. Portanto, o que impresso no segundo printf é a = 10. A Figura 7.5 ilustra as passagens de parâmetro de a para b, em adicionax1 e adicionax2. Figura 7.5: Passagem de Valor por Cópia e por Apontadores Na Figura 7.5, repare que na passagem por cópia, o valor de a é copiado para o conteúdo de b, enquanto que na passagem por apontador, b passa a apontar para o endereço de a. Uma aplicação da passagem de valor por apontadores é proporcionar que uma função retorne múltiplos valores. O Exemplo 7.8 mostra como isso é feito. 1 void troca ( int *x, int * y) { 2 int aux ;

194 194 CAPÍTULO 7. APONTADORES 3 4 aux = *x; 5 *x = *y; 6 *y = aux ; 7 } 8 9 main ( ) { 10 int a, b; 11 a = 10; 12 b = 20; troca (&a, &b); 15 printf ("a = %d, b = %d\n", a, b); 16 } Exemplo 7.8: Retorno de múltiplos valores por uma função No Exemplo 7.8, desejava-se uma função para trocar o conteúdo de a e b. Contudo, uma função pode retornar apenas um valor no return. Assim, por meio dos apontadores, os dois conteúdos são alterados e retornados no escopo da função, pois a e b são passados sem cópia, ou seja, são passados seus endereços como parâmetros da função troca. 7.5 Alocação Dinâmica de Memória Apontadores também são fundamentais no que diz respeito à alocação dinâmica, economia de memória e tempo de execução. Apontadores permitem alocação de memória em tempo de execução (alocação dinâmica). Para tanto, uma variável apontador aponta para uma área dememória livre definida durante a execução do programa. As Figuras 7.6 e 7.7 ilustram o processo de alocação dinâmica. Figura 7.6: Áreas de Memória Livres A Figura 7.6 mostra as áreas livres da memória. Em um determinado momento, um endereço,

195 7.5. ALOCAÇÃO DINÂMICA DE MEMÓRIA 195 que era livre, passa a ser apontado por uma variável apontador, podendo assim ser utilizado, o que é ilustrado na Figura 7.7. Figura 7.7: Área de Memória Alocada Dinamicamente Em C, a função malloc (abreviatura de memory allocation), da biblioteca padrão stdlib.h, aloca um bloco de bytes consecutivos na memória do computador e devolve o endereço desse bloco. O Exemplo 7.9 mostra seu uso. 1 int *p 2 p = ( int *) malloc (4*( sizeof ( int ))); Exemplo 7.9: Alocação dinâmica de quatro inteiros No Exemplo 7.9, a expressão sizeof retorna o número de bytes de um tipo passado como parâmetro, no caso int. Além disso, como a função malloc devolve um apontador do tipo void (void *) para um bloco de bytes consecutivos, esse apontador deve ser convertido em apontador para o tipo desejado, por meio do cast (int *) nesse caso, uma vez que a variável p é do tipo int, garantindo assim, a consistência de tipos. A Figura 7.8 ilustra um espaço de 4 inteiros, alocados dinâmicamente pelo Exemplo 7.9. Um espaço de memória deve ser desalocado quando não é mais necessário. Isto significa que a área de memória que foi apontada por um apontador agora passa a ser novamente livre. Somente variáveis alocadas dinamicamente podem ser desalocadas em tempo de execução. A desalocação de memória, em C, está no Exemplo int *p; 2 p = ( int *) malloc (4 * sizeof ( int )); 3 free (p); Exemplo 7.10: Utilização da função free para liberar memória No Exemplo 7.10, a função free, assim como malloc, está na biblioteca stdlib.h e desaloca uma área de memória. Note que a função free deve receber como parâmetro uma variável

196 196 CAPÍTULO 7. APONTADORES Figura 7.8: Vetor de Inteiros Alocados Dinamicamente apontador. Assim, o área de memória apontada por ele será então liberada. A Figura 7.9 ilustra a desalocação de memória. Figura 7.9: Desalocação Dinâmica de Memória Na Figura 7.9, pode-se observar a que no decorrer da execução do programa, a variável apontador deixa de apontar para área de memória, que volta para a grupo de endereços livres. O processo de alocação dinâmica pode ser usado para ler linhas de um documento de texto. Usando-se alocação dinâmica, define-se o tamanho n x m fixo da matriz limitando o tamanho do texto e da linha que podem ser lidos. Para se conseguir ler todas as linhas, define-se o tamanho m da matriz como o tamanho da maior linha que será lida e n um número alto suficiente para armazenar todas as linhas. Entretanto, esse procedimento acarreta perda de memória com linhas pequenas, que não utilizam todo o espaço alocado para elas, e tratando-se de textos com grandes quantidades de linhas essa perda torna-se considerável. Considere a Figura 7.10, que ilustra como seria o processo de alocação dinâmica de uma matriz para ler um pequeno texto de caracteres. Percebe-se facilmente para a quarta linha da matriz, que 9 espaços seriam perdidos.

197 7.5. ALOCAÇÃO DINÂMICA DE MEMÓRIA 197 Figura 7.10: Matriz que Armazena um Texto Para contornar o problema de perda de memória com espaços não utilizados, faz-se uso da alocação dinâmica. A cada necessidade de se ler uma linha com certo número de caracteres, aloca-se o exato tamanho da linha, ou seja, a quantidade de caracteres da string que será lida. O Exemplo 7.11 mostra a implementação em linguagem C de uma matriz de caracteres, dinamicamente alocada. Esta matriz possui 4 linhas de tamanhos alocados de acordo com o tamanho da string armazenada, e cada linha da matriz recebe uma linha do texto da Figura A matriz declarada no Exemplo 7.11 é simplesmente um vetor de apontadores para strings e os elementos do vetor g contém os endereços dos primeiros elementos de cada string. Esse vetor de apontadores para strings é chamado matriz de apontadores ou matriz de strings. Na Figura 7.11 está ilustrada a matriz de strings correspondente ao texto. Figura 7.11: Matriz de Strings 1 main ( ) { 2 char *g [4]; 3 g [0] = ( char *) malloc ( strlen (" Joao ama Maria ") * sizeof ( char )); 4 strcpy (g[0], " Joao ama Maria "); 5 g [1] = ( char *) malloc ( strlen (" Maria ama pedro ") * sizeof ( char )); 6 strcpy (g[1], " Maria ama pedro "); 7 g [2] = ( char *) malloc ( strlen (" Ana ama quem ama Maria ") * sizeof ( char )); 8 strcpy (g[2], " Ana ama quem ama Maria "); 9 g [3] = ( char *) malloc ( strlen (" Quem Ana ama?") * sizeof ( char )); 10 strcpy (g[3], " Quem Ana ama?");

198 198 CAPÍTULO 7. APONTADORES 11 } Exemplo 7.11: Definição de uma matriz de caracteres dinâmica 7.6 Problemas Gerados por Apontadores Apesar de serem muito importantes, quando usados inapropiadamente, apontadores são fontes de erros difíceis de serem encontrados. Apontadores não inicializados, objetos pendentes, referência pendente, e programação macarrônica são alguns desses erros Apontadores Não Inicializados Quando se trata de apontadores, um erro muito comum é utilizá-lo antes de fazê-lo apontar para algum endereço válido, ou seja, sem inicializá-lo. O Exemplo 7.12 mostra como isso pode ocorrer. O valor da variável apontada por p recebe o valor de h, mas p não foi inicializado ainda, ou seja, não apontava para nenhum espaço de memória válido. 1 float *p, h = 15.0; 2 *p = h; Exemplo 7.12: Apontador não inicializado As conseqüências da utilização de um apontador não inicializado são imprevisíveis, podendo provocar uma paralisação do sistema Objetos Pendentes Objetos pendentes ocorrem quando uma área de memória alocada fica inacessível. Veja o Exemplo A área de memória apontada por p fica inacessível, pois p passa a apontar para o mesmo endereço de g. A Figura 7.12 ilustra como ocorre objetos pendentes. Figura 7.12: Objetos pendentes

199 7.6. PROBLEMAS GERADOS POR APONTADORES int *p = ( int *) malloc ( sizeof ( int )); 2 int *q = ( int *) malloc ( sizeof ( int )); 3 p = q; Exemplo 7.13: Objetos pendentes Referência Pendente Quando é liberado um endereço de memória, que é apontado por mais de uma variável apontador, ocorre a referência pendente. Assim, algumas variáveis ficam referenciando um endereço que foi desalocado. Veja o Exemplo 7.14 que mostra um caso de referência pendente. O endereço da variável h é liberado, portanto p aponta para um espaço de memória liberado. A Figura 7.13 ilustra o problema de referência pendente. Um espaço de memória livre pode ser alocado a qualquer momento por outras variáveis do sistema. Ao se utilizar variáveis que tiveram seu espaço de memória liberado, pode-se estar acessando áreas importantes do sistema, que alocacou o espaço de memória que acabara de ser liberado, e isto pode provocar uma paralisação do sistema. Figura 7.13: Referência pendente 1 int *p; 2 int *h; 3 h = ( int *) malloc ( sizeof ( int ));

200 200 CAPÍTULO 7. APONTADORES 4 p = h; 5 free (h); Exemplo 7.14: Referência pendente Programação Macarrônica Um mesmo endereço de memória pode ser apontado por vários apontadores. Isso pode tornar confuso o entendimento do programa e a identificação de erros. O Exemplo 7.15 mostra um trecho de código difícil de entender devido ao uso indiscriminado de apontadores. 1 typedef struct { 2 int numerosala ; 3 int numeroalunos ; 4 } tsala ; 5 6 acresentaaluno ( tsala * g) { 7 g- > numeroalunos = g- > numeroalunos + 1; 8 } 9 10 main ( ) { 11 int p; 12 tsala sala ; 13 sala. numerosala = 1; 14 sala. numeroalunos = 0; 15 tsala *x; 16 tsala *y; y = & sala ; 19 x = y; 20 acrescentaaluno ( x); 21 acrescentaaluno ( y); 22 } Exemplo 7.15: Programação macarrônica causada pelo uso indiscriminado de apontadores. No Exemplo 7.15, o número de alunos da variável sala é alterado duas vezes, uma pelo apontador x, outra por meio do apontador y. Portanto, quando apontadores são usados de uma maneira indiscriminada, pode-se diminuir a legibilidade do código. 7.7 TAD Implementacional Lista Encadeada tlista Um exemplo de implementação utilizando os apontadores é o TAD Lista Encadeada. Uma lista encadeada é uma estrutura cujos elementos são acessados seqüencialmente por meio de apontadores. Nessa estrutura estão armazenados os apontadores para o começo e o fim de uma seqüência de blocos, conhecidos como nós, além de uma variável inteira que

201 7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 201 indica a quantidade atual destes na lista. Cada um dos nós, por sua vez, possuem informações armazenadas, um apontador para o próximo nó e no caso de uma lista duplamente encadeada, também um apontador para o nó anterior. Na Figura 7.14, tem-se um esquema de uma lista simplesmente encadeada, com três nós e com apontadores para o primeiro e o último elemento. Figura 7.14: Esquema de Uma Lista Simplesmente Encadeada Note que cada um dos nós possui um apontador para o próximo, com exceção do último, no qual há um apontador para null, pois não há próximo. Repare também que a estrutura da lista possui apontadores para o início e o fim da lista, além de armazenar seu tamanho corrente, no caso, três elementos. Pode-se perceber que incluir em uma lista encadeada torna-se um processo simples. Se um novo dado necessita ser incluído, basta alocar um espaço de memória para o nó, atualizar os apontadores da seqüência de nós e a quantidade deles na lista. Excluir um dado da lista é um processo semelhante, com a diferença de que, ao invés de se alocar, desaloca-se uma área da memória. Vale destacar que as estruturas da lista e de cada um dos dados armazenados em um nó são exemplos do uso de abstração, pois se encapsulam informações, seleciona-se o que deve ser apresentado de uma função à outra e possibilita-se o trabalho em níveis de implementação e uso, prática fundamental na modularização de um código. Conforme visto no capítulo 4, pode-se dizer que o tipo de abstração utilizada na lista encadeada é uma abstração de dados implementacional, por meio do uso de Tipo Abstratos de Dados - TADs. Anteriormente foi visto que TADs são conjuntos de valores com comportamento uniforme definido por operações, que são tipicamente os componentes responsáveis pela interface entre os diferentes níveis e módulos de implementação. Essa interface é responsável por encapsular e proteger os dados. A implementação do TAD lista encadeada tlista será apresentada a seguir. Também serão detalhadas várias operações fundamentais sobre a lista. Inicialmente, será definido o tipo tno, o qual é um componente importante de tlista.

202 202 CAPÍTULO 7. APONTADORES Definição do Tipo tno De acordo com o que foi dito até agora, cada nó deve ser responsável por armazenar um dado, além de conter um apontador para o próximo nó, no caso de uma lista simplesmente encadeada. Assim, segue abaixo um exemplo de como pode ser a estrutura tno: 1 typedef int tinfo ; 2 3 typedef struct no { 4 tinfo info ; 5 struct no * proximo ; 6 } tno ; Exemplo 7.16: Estrutura tno Há necessidade de se incluir o termo no após o primeiro struct porque, ao se incluir a estrutura proximo, ocorre uma definição recursiva do tipo no, ou seja, a estrutura no possui um campo do mesmo tipo que ela. Assim, caso fosse retirado o primeiro termo no, não se saberia qual o tipo de proximo. Também é importante dizer que a variável inteira info poderia ser substituída por qualquer outro tipo, tais como char, float e, até mesmo, estruturas compostas por uma variedade de tipos. Bastaria alterar o termo int na definição do tipo tinfo. A partir de agora, devido ao uso do typedef, sempre que for utilizado o termo tno antes de um identificador, significa que está sendo declarada uma estrutura como a do Exemplo Como freqüentemente será necessário incluir novos nós, torna-se importante criar uma função que aloque dinamicamente um espaço de memória para um nó. O Exemplo 7.17 mostra uma forma de implementar tal função. 1 tno * criano ( tinfo elem ) { 2 tno *no = ( tno *) malloc ( sizeof ( tno )); 3 no -> proximo = NULL ; 4 no -> info = elem ; 5 return no; 6 } Exemplo 7.17: Implementação da operação criano Após a alocação do espaço de memória, o apontador proximo é inicializado como NULL. Em seguida, o campo info do nó criado é associado ao valor do elemento passado como argumento. Por fim, é retornado um apontador para a área de memória inicializada na função Atributos de tlista Será utilizada uma estrutura para tlista com apontadores para o primeiro e último nó. Essa estrututa é chamada cabeçalho da lista e cabe ressalvar que existem diferentes implementações. A adotada aqui (ver Exemplo 7.18) é apenas uma delas.

203 7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA typedef struct { 2 tno * primeiro ; 3 tno * marcador ; 4 tno * ultimo ; 5 int tam ; 6 } tlista ; Exemplo 7.18: Estrutura tlista Repare que três atributos de tlista são apontadores para o tipo tno, o qual foi definido na seção anterior. Dois deles, primeiro e ultimo, apontam para o começo e para o fim da lista, enquanto marcador aponta para qualquer posição. A variável inteira tam é a responsável por guardar o atual tamanho, ou número de nós, da lista. O apontador marcador é responsável por apontar o nó corrente num processo de busca seqüencial. Por exemplo, na operação de imprimir a lista, que será explicada mais adiante, a informação é extraída do nó apontado por marcador e, este vai sendo posicionado desde o primeiro nó até o último Operações de tlista Como já se têm definidas as abstrações de dados, tno e tlista, necessita-se criar as operações que serão responsáveis por trabalhar com estes conjuntos de valores, concluindo assim, a implementação do TAD lista encadeada. Operações Construtoras As operações construtoras são responsáveis por garantir a alocação da estrutura tlista, além de inicializá-la com os valores desejados, geralmente com os apontadores apontando para NULL e as variáveis numéricas com valor zero. Inicialização (vazia) O Exemplo 7.19 mostra a função de inicialização de uma lista. 1 void inicialista ( tlista * lista ) { 2 lista - > primeiro = NULL ; 3 lista - > marcador = NULL ; 4 lista -> ultimo = NULL ; 5 lista -> tam = 0; 6 } Exemplo 7.19: Inicialização de uma lista Na função inicialista os apontadores primeiro, marcador e ultimo são inicializados com NULL, pois não há nós na lista e, garante-se assim, que estes apontem para uma área de memória

204 204 CAPÍTULO 7. APONTADORES indevida. Em seguida, o tamanho da lista é zerado uma vez que o atual tamanho da lista é nulo. Operações Analisadoras Em algumas situações é importante analisar a posição atual do marcador, tal como se ele está no fim da lista, e se a lista está vazia. Por exemplo, ao se percorrer a lista, uma condição de parada importante é quando o final da lista for alcançado e, ao se incluir ou excluir um elemento da lista, é necessário saber se a lista está vazia. Essas análises são importantes para que não se cometa acessos indevidos com os apontadores, um dos problemas explicados neste capitulo. Final O Exemplo 7.20 mostra a implementação da função analisadora de fim da lista. 1 int finallista ( tlista * lista ) { 2 return ( lista - > marcador == NULL ); 3 } Exemplo 7.20: Verificação de fim da lista Essa função simplesmente retorna o resultado da comparação do marcador da lista com NULL. Isso porque o último nó da lista aponta tem como próximo este valor, bem como a lista vazia, que tem seus apontadores iniciais todos apontando para NULL. Na Figura 7.15, o marcador não está apontando para NULL, assim não é o fim da lista e a função final retorna 0. Figura 7.15: Marcador Apontando para uma Posição da Lista Já na Figura 7.16 o marcador aponta para NULL, ou seja para o fim da lista. Assim, o retorno da função é 1. Vazia O Exemplo 7.21 mostra a implementação da função analisadora de lista vazia.

205 7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 205 Figura 7.16: Marcador Apontando para o Fim da Lista 1 int vazialista ( tlista * lista ) { 2 return ( lista -> tam == 0); 3 } Exemplo 7.21: Verificação se a lista está vazia Assim como na análise do fim da lista, vazialista retorna o resultado de uma comparação. Nesse caso, porém, se o tamanho da lista é igual zero. Como o tamanho da lista é inicializado com este valor e, sempre que um novo nó for adicionado, ou removido, este tamanho deve ser atualizado, a única possibilidade de ele ser zero é quando a lista estiver realmente vazia. Um exemplo da utilização de vazia é quando se deseja incluir, ou excluir um nó da lista. É necessário um tratamento especial na primeira inclusão, pois o primeiro e ultimo elemento são os mesmos, e na exclusão, uma vez que seria um erro tentar remover elementos de uma lista vazia. Operações Modificadoras Uma das características interessantes da lista encadeada é a facilidade de se incluir e excluir elementos, em tempo de execução, com praticidade, uma vez que é necessário somente alocar e desalocar nós, respectivamente. É importante, então, conhecer as funções de inserção e exclusão, tratadas como operações modificadoras, pois alteram a composição corrente da lista. Seguem, assim, os exemplos de implementação. Inserção no Final No Exemplo 7.22, o novo elemento e a lista no qual ele será inserido são passados como argumentos. A passagem do cabeçalho lista é feita por apontador, pois assim, qualquer modificação em seus elementos é feita diretamente na função, sem necessidade de passar toda lista por cópia e retorná-la ao fim da execução da função. O elemento será incluído no fim da lista, mas diferentes implementações, nas quais ele é inserido no começo, ou numa dada posição, também são possíveis.

206 206 CAPÍTULO 7. APONTADORES 1 void incluirfimlista ( tlista * lista, tinfo elem ) { 2 tno *no; 3 4 no = criano ( elem ); 5 if ( vazialista ( lista )) { 6 lista -> primeiro = no; 7 } else { 8 lista -> ultimo -> proximo = no; 9 } 10 lista -> ultimo = lista -> marcador = no; 11 lista -> tam ++; 12 } Exemplo 7.22: Função de inclusão de um novo nó na lista Como pode ser visto no exemplo acima, inicialmente o novo nó deve ser criado para armazenar o elemento a ser incluído. Para isso é utilizada a função inicializadora criano, que aloca um espaço de memória do tamanho de tno, faz a chave ser igual ao elemento passado como argumento e retorna um apontador para o espaço alocado. A Figura 7.17 mostra o estado da lista, antes da inclusão, e o novo nó a ser incluído. Figura 7.17: Lista Antes da Inclusão Agora que se já se tem o novo nó, é necessário incluí-lo na lista, ou seja, atualizar o valor de tam e os apontadores. Como a lista tem mais um nó, seu tamanho deve ser incrementado em uma unidade. Quanto à atualização dos apontadores, os apontadores ultimo e marcador devem apontar para o nó incluído, respectivamente, porque ele é incluído no fim da lista e no último nó acessado. Antes, porém, é importante verificar se a lista está vazia, pois caso esteja, quem também vai apontar para nó a ser incluído, será o apontador primeiro da lista, caso contrário, será apontador proximo do ultimo nó da lista. Vale destacar que a análise se a lista está vazia é um exemplo de uso da função vazialista, que foi explicada anteriormente. A Figura 7.18 exibe a lista após a inclusão do novo nó. Exclusão

207 7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 207 Figura 7.18: Lista Após a Inclusão O Exemplo 7.23 mostra a implementação de uma operação de exclusão. 1 void excluirnolista ( tlista * lista, int pos ) { 2 tno *auxa, * auxb ; 3 int i; 4 5 if ( pos < 0 pos >= lista - > tam ) 6 return ; 7 8 auxa = lista - > primeiro ; 9 for (i =0;i<pos ;i ++) { 10 auxb = auxa ; 11 auxa = auxa - > proximo ; 12 } 13 if ( auxa!= lista - > primeiro ) { 14 auxb -> proximo = auxa -> proximo ; 15 if ( auxa == lista - > ultimo ) { 16 lista - > ultimo = auxb ; 17 } 18 } else { 19 lista -> primeiro = auxa -> proximo ; 20 if ( lista - > ultimo == auxa ) { 21 lista -> ultimo = auxa -> proximo ; 22 } 23 } 24 lista - > marcador = NULL ; 25 lista ->tam - -; 26 free ( auxa ); 27 } Exemplo 7.23: Função de exclusão de um nó da lista

208 208 CAPÍTULO 7. APONTADORES Na função de exclusão, são passadas como parâmetros a lista e a posição do nó a ser excluído. Assim, é importante saber, inicialmente, se o valor de pos é válido e, caso não seja, a função é encerrada. São criados dois apontadores auxiliares do tipo tno: auxa e auxb. Para buscar o nó da posição pos, auxa aponta para o primeiro nó da lista e inicia-se um laço for, até que o valor de i, que inicialmente é zero, seja pos. A cada iteração desse laço, auxb vai apontar para o último nó apontado por auxa e este, por sua vez, aponta para o próximo nó da lista. Portanto, quando auxa estiver apontando para o nó a ser excluído, o anterior a ele é apontado por auxb, o que é importante na hora de se atualizarem os apontadores, conforme será explicado. Após ter encontrado o nó da posição pos, tem que se analisar quatro situações possíveis: 1) esse nó a ser excluído não ser nem o primeiro e nem o último nó da lista; 2) ser o último; 3) ser o primeiro; 4) e ser o único nó. No caso 1, basta fazer o apontador proximo do nó apontado por auxb apontar para o nó apontado pelo proximo do nó apontado por auxa. No caso 2, também se deve fazer o último nó da lista ser auxb. Vale lembrar que, no caso de auxa ser o último, ao se fazer o proximo de auxb ser o proximo de auxa, está se fazendo simplesmente auxb ter como próximo o valor NULL. Já no caso 3, coloca-se o próximo de auxa como o nó inicial e, no caso 4, faz-se também o apontador ultimo da lista apontar para o próximo de auxa. Nesse último caso, está se redirecionando os apontadores, primeiro e ultimo da lista, para NULL. Agora que já se tem os apontadores atualizados e retirado o nó que será excluído da seqüência de nós, coloca-se o marcador da lista como NULL, pois será perdido o último nó acessado. Então, diminui-se o tamanho da lista e, finalmente, libera-se o espaço de memória apontado por auxa. A Figura 7.19 ilustra a lista da Figura 7.16 após a exclusão do nó 2. Figura 7.19: Lista Após a Exclusão do Nó 2 Operações Produtoras As funções produtoras possibilitam extrair alguma informação da lista. Dividem-se em duas categorias: as que permitem posicionar o marcador no nó, cuja informação será extraída, e as que realmente obtém a informação. Serão mostradas inicialmente as operações que marcam a posição de um elemento. Apresentam-se a que posiciona o marcador no primeiro nó da lista e a que posiciona no próximo nó do marcador atual. A fim de ilustrar a utilização destas funções, será dado como exemplo o uso delas na função imprimir. O Exemplo 7.24 é a implementação da função que posiciona o marcador no primeiro nó da

209 7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA 209 lista. 1 void primeirolista ( tlista * lista ) { 2 lista -> marcador = lista -> primeiro ; 3 } Exemplo 7.24: Posicionado o marcador no começo da lista Para fazer o marcador de uma lista apontar para o primeiro elemento dela, basta igualar o marcador ao apontador primeiro. O objetivo da operação primeirolista é posicionar o marcador no início da lista, para permitir que seja feita uma busca nos elementos da lista a partir do seu início. A operação proximolista é mostrada no Exemplo void proximolista ( tlista * lista ) { 2 if ( lista - > marcador!= NULL ) { 3 lista -> marcador = lista -> marcador -> proximo ; 4 } 5 } Exemplo 7.25: Posicionado o marcador no proximo nó da lista A função proximolista é para posicionar o marcador, fazendo-o apontar para o seu próximo elemento da lista. A verificação se o marcador atual é diferente de NULL evita o acesso indevido à memória. O Exemplo 7.26 mostra a operação responsável por retornar a informação do nó apontado pelo marcador da lista. 1 tinfo obterinfo ( tlista * lista, int * x) { 2 if ( lista - > marcador == NULL ) { 3 *x = 0; 4 } 5 *x = 1; 6 return lista -> marcador -> info ; 7 } Exemplo 7.26: Função para recuperar a informação de um nó A função obterinfo tem por objetivo retornar o conteúdo do nó apontado pelo marcador. É gravado no endereço x se houve sucesso, ou não, em se obter a informação. Assim, impede-se de acessar um local indevido da memória. O Exemplo 7.27 ilustra o uso dessas informações por meio do uso de uma função que imprime todos os dados de uma lista. Para a função é passada a lista cujos elementos serão impressos. Inicialmente primeirolista é utilizada para posicionar o marcador no início da lista e, em seguida, começa um laço enquanto o final dela não é alcançado, verificação feita pela função finallista, conforme descrito anteriormente.

210 210 CAPÍTULO 7. APONTADORES 1 void imprimirlista ( tlista lista ) { 2 tinfo x; 3 int erro = 0; 4 5 primeirolista (& lista ); 6 while (! finallista (& lista )) { 7 x = obterinfo (& lista, & erro ); 8 if ( erro ) { 9 printf (" Elemento : %d\n", x); 10 } 11 proximolista (& lista ); 12 } 13 } Exemplo 7.27: Impressão de todos elementos de uma lista Para cada posição do marcador, obterinfo retorna o valor de info do nó apontado pelo marcador da lista. Esse valor é impresso caso tenha ocorrido sucesso na obtenção da informação, ou seja, a variável erro ter o valor 1. O valor a ser impresso é inteiro, visto que x é do tipo tinfo (int, como declarado anteriormente). Por fim, proximolista posiciona o marcador no próximo nó da lista e, somente quando este marcador for NULL, o laço enquanto será encerrado. Operações Destrutoras Quando a lista não é mais necessária, é importante desalocar o espaço de memória ocupado por seus nós. As funções responsáveis por desalocar a lista e seus elementos são conhecidas como destrutoras. Finalização A seguinte função para a finalização da lista é um exemplo de operação destrutora. Repare que a desalocação de memória é feita nó por nó. Note que ao final da operação, os valores dos atributos de lista são modificados para apontarem para NULL para evitar que fiquem referenciando áreas já desalocadas. 1 void destroilista ( tlista * lista ) { 2 tno * aux ; 3 4 primeirolista ( lista ); 5 while (! finallista ( lista )) { 6 aux = lista - > marcador ; 7 proximolista ( lista ); 8 free ( aux ); 9 } 10 lista - > primeiro = NULL ; 11 lista - > marcador = NULL ;

211 7.7. TAD IMPLEMENTACIONAL LISTA ENCADEADA TLISTA lista - > ultimo = NULL ; 13 lista -> tam = 0; 14 } Exemplo 7.28: Desalocação das áreas de memória ocupados pelos nós da lista Semelhante a função de imprimir, primeirolista é utilizada para posicionar o marcador no inicio da lista e, em seguida, inicia-se um laço enquanto o final dela não é alcançado. A cada iteração, um nó auxiliar aux guarda o endereço de memória apontado pelo marcador da lista. Em seguida, posiciona-se o marcador no próximo nó da lista e a região apontada por aux é liberada com o uso do free. Quando se chega ao final da lista, o laço enquanto será encerrado e os atributos de listas são incializados, assim, seus apontadores apontam para NULL e o tamanho é zero Uso A fim de ilustrar a utilização do TAD implementacional lista encadeada, será utilizado um cadastro simples de alunos na universidade. Veja o Pseudocódigo 7.1. Pseudocódigo 7.1 Uso de uma lista simplesmente encadeada. Descrição: Programa de cadastro de alunos. Dados de Entrada: nomes e as respectivas matriculas. Saída do Programa: lista impressa. Inicializaç~ao da lista; Leitura de um nome; Leitura da matricula; Validaç~ao da matricula Se a matricula for menor ou igual a zero: Parar de ler informaç~oes. Ir para "Leitura da posiç~ao"; Se n~ao for menor que zero: Continuar a ler informaç~oes. Ir para "Leitura de um nome"; Leitura da posiç~ao Validaç~ao da Posiç~ao Se a posiç~ao for maior ou igual a zero: Se a posiç~ao contiver algum nó: Excluir o nó, imprimir a lista e voltar para "Leitura da posiç~ao"; Se for menor que zero: Parar de ler informaç~oes. Ir para "Destruiç~ao da lista"; Destruiç~ao da lista. O Pseudocódigo 7.1 descreve os passos para o programa de cadastro simples, no qual apenas pode-se ler e armazenar algumas entradas. Após a leitura, alguns dados podem ser excluídos e as informações restantes serem impressas.

212 212 CAPÍTULO 7. APONTADORES No cadastro, haverá apenas o nome e o número de matrícula do aluno, mas vale lembrar que, para um registro de informaçõess mais detalhado, basta acrescentar mais atributos no exemplo de estrutura tinfo do exemplo apresentado. Observe o Exemplo 7.29, no qual se tem a transcrição do Pseudocódigo 7.1. Foram utilizadas todas as funções estudadas nesta seção, porém com algumas modificações, uma vez que o campo info, da estrutura do nó, não é mais um inteiro, e sim, uma outra estrutura. Só estão mostradas as estruturas e as operações anteriores que sofreram modificação. 1 typedef struct { 2 int matricula ; 3 char nome [30]; 4 } tinfo ; 5 6 void imprimirlista ( tlista lista ) { 7 tinfo x; 8 int erro = 0; 9 10 primeirolista (& lista ); 11 while (! finallista (& lista )) { 12 x = obterinfo (& lista, & erro ); 13 if ( erro ) { 14 printf (" Aluno : %s\n", x. nome ); 15 printf (" Matricula : %d\n\n", x. matricula ); 16 } 17 proximolista (& lista ); 18 } 19 } main ( ) { 22 tlista lista ; 23 tinfo dados ; 24 int pos ; inicialista (& lista ); do { 29 printf (" Entre com o nome do aluno : "); 30 scanf ("%s", & dados. nome ); 31 printf (" Entre com a matricula do aluno : "); 32 scanf ("%d", & dados. matricula ); 33 if ( dados. matricula <= 0) 34 break ; 35 incluirfimlista (& lista, dados ); 36 } while (1) ; imprimirlista ( lista ); printf (" Digite a posiç~ao do elemento a ser excluído : "); 41 scanf ("%d", & pos );

213 7.8. EXERCÍCIOS RESOLVIDOS while ( pos >= 0) { 43 excluirnolista (& lista, pos ); 44 imprimirlista ( lista ); 45 scanf ("%d", & pos ); 46 } destroilista (& lista ); 49 } Exemplo 7.29: Cadastro de alunos na universidade O programa implementado no Exemplo 7.29 apenas lê as informações do teclado enquanto não se digita um valor inválido, no caso matricula menor ou igual a 0. Se for digitado tal valor para matricula o comando break faz a execução do programa sair do laço infinito (a condição é sempre 1). Cada nome e matricula são armazendos na lista e, após o término de leitura de dados, eles são impressos na tela. Em seguida, pode-se apagar as informações fornecendo a posição na lista do dado a ser apagado. A cada elemento excluído, a lista é atualizada e impressa e, quando for passado um valor de posição negativo, encerra-se o laço de exclusão. Por fim, antes de se encerrar o programa, a lista é destruída. 7.8 Exercícios Resolvidos Exercício Resolvido Acesso ao Conteúdo de Variáveis por Apontadores Para o trecho de código do Exemplo 7.30, ilustre os estados das variáveis apontadores e não-apontadores. Utilize para a ilustração os padrões adotados na Figura 7.3 e Figura 7.4, para representar uma variável não apontador e apontador respectivamente. 1 int *p; 2 int *q; 3 int a; 4 int b; 5 int c; 6 7 b = 10; 8 c = 15; 9 a = c; 10 p = &b; 11 q = &c; 12 *p = *q; Exemplo 7.30: Código para o Exercício Resolvido 7.1

214 214 CAPÍTULO 7. APONTADORES Solução Possível: Inicialmente são criados dois apontadores para inteiro, p e q, e três variáveis inteiras: a b e c. Em seguida, b passa a armazenar o valor 10, enquanto c, 15. Já a rebece o valor de c, ou seja, 15. Isso é ilustrado nas três primeiras cenas da Figura Figura 7.20: Solução do Exercício Resolvido 7.1 Por fim, p e q passam a apontar b e c, repectivamente. Assim, na última linha, o conteúdo da variável apontada por p recebe o valor de c, pois este é a variável apontada por q, o que faz b receber o valor 15. Vejas os três últimos passos da Figura Exercício Resolvido Múltiplo Retorno em uma Função A função calculapot, do Exemplo 7.31, calcula a potência quadrada de um inteiro. Modifique esta função para que ela passe a retornar também a potência cúbica deste número. Faça as devidas alterações na main. 1 int calculapot ( int valor ){ 2 int potquadrada ; 3 potquadrada = valor * valor ; 4 return potquadrada ;

215 7.8. EXERCÍCIOS RESOLVIDOS } 6 7 main ( ){ 8 int x; 9 int quadrado ; scanf ("%d", &x); 12 quadrado = calculapot ( x); 13 printf (" Quadrado de %d = %d\n", x, quadrado ); 14 } Exemplo 7.31: Função calculapot Solução Possível: O dado de entrada do programa é um número inteiro, e as saídas devem ser as pontências quadrada e cúbica deste número, calculadas pela função apresentada no Exemplo 7.31, com suas devidas alterações. O Pseudocódigo 7.2 exibe as etapas para a solução. Pseudocódigo 7.2 Cálculo das potências quadradas e cúbicas de um número inteiro. Descrição: Programa que retorna as potências quadrada e cúbica de um número inteiro, calculadas por uma função apenas. Dados de Entrada: um número inteiro. Saída do Programa: potências quadrada e cúbica do valor de entrada. Leitura do número inteiro; Passagem de par^ametros para a funç~ao calculapot; Cálculo das pot^encias pela funç~ao calculapot. Para atender a etapa Cálculo das potências pela função calculapot, a função calculapot deve ser alterada para também calcular e retornar o cubo do valor. Contudo, o retorno dessa função já é o quadrado do parâmetro de entrada. Nesse sentido, a passagem por apontadores é a ferramenta para múltiplos retornos numa função, o que torna importante o passo Passagem de parâmetros para a função calculapot As modificações no Exemplo 7.31 podem ser observadas no Exemplo int calculapot ( int valor, int * potcubica ){ 2 int potquadrada ; 3 potquadrada = valor * valor ; 4 * potcubica = valor * valor * valor ; 5 return potquadrada ; 6 } 7 8 main ( ){ 9 int x; 10 int quadrado ; 11 int cubo ; 12

216 216 CAPÍTULO 7. APONTADORES 13 scanf ("%d", &x); 14 quadrado = calculapot (x, & cubo ); 15 printf (" Quadrado de %d = %d\ ncubo de %d = %d\n", x, quadrado, x, cubo ); 16 } Exemplo 7.32: Múltiplo retorno em calculapot Como pode ser observado, calculapot passa a ter um parâmetro que irá receber o endereço da variável cubo, declarada na main. Dessa forma, ao término da execução de calculapot, cubo irá conter a potência cúbica do valor. Exercício Resolvido Inserção no Início de Lista Simplesmente Encadeada Foi visto anteriormente como inserir um elemento no final da lista. Agora, pretende-se criar uma função que insere no começo de uma lista simplesmente encadeada. Solução Possível: Para inserir um novo nó, no começo da lista, os passos que devem ser seguidos pela função aparecem no Pseudocódigo 7.3, onde também pode-se observar os dados de entrada e o retorno da função. Pseudocódigo 7.3 Inserção no começo de uma lista simplesmente encadeada. Descrição: Função que insere um nó no começo de uma lista simplesmente encadeada. Dados de Entrada: a lista e o elemento a ser enserido. Saída da Função: a lista após a inserção. Criaç~ao de um nó para armazenar o novo elemento; Verificar se a lista está vazia Se estiver vazia: O novo nó também passa a ser o último da lista; Se n~ao estiver vazia: Faz o novo nó ter como seu próximo o antigo primeiro; O primeiro da lista passa a ser o nó criado; Posicionar o marcador no nó incluído; Incrementar o tamanho da lista. O Exemplo 7.33 mostra a implementação em C. Para tal função, a passagem da lista será feita por apontador, pois assim, qualquer modificação em seus elementos será feita diretamente na função e, por consegüinte, o retorno é void. A lista poderia ser passada por cópia, mas a lista modificada teria estar após um comando return. 1 void incluiriniciolista ( tlista * lista, tinfo elem ) {

217 7.8. EXERCÍCIOS RESOLVIDOS tno *no; 3 4 no = criano ( elem ); 5 if ( vazialista ( lista )) { 6 lista -> ultimo = no; 7 } else { 8 no -> proximo = lista -> primeiro ; 9 } 10 lista - > primeiro = lista - > marcador = no; 11 lista -> tam ++; 12 } Exemplo 7.33: Função de inclusão de um novo nó no começo da lista No Exemplo 7.33, um novo nó é criado com a utilização de criano. Ao incluí-lo no inicío, os apontadores primeiro e marcador devem apontar para o nó ser incluído, respectivamente porque ele é incluído no começo da lista e no último nó acessado. Contudo, antes de atualizar o apontador primeiro, é fundamental verificar se a lista está vazia, pois caso não esteja, o novo nó deve guardar, como seu próximo, o antigo primeiro da lista. Já se a lista estiver vazia, basta fazer o apontador ultimo também apontar para o novo nó. Por fim, o tamanho da lista deve ser incrementado em uma unidade. A figura 7.21 mostra a Figura 7.17, após a inclusão do novo nó no começo da lista. Figura 7.21: Lista da Figura 7.17, Após a Inclusão do Nó no Começo Exercício Resolvido Ordenação de uma Lista Simplesmente Encadeada Para ilustrar a odenação de uma lista, considere que ela é formada por nós cuja informação armazenada seja um nome e uma idade. Ulizando as estruturas e funções já apresentadas, além de novas funções, crie um programa que lê as informações do teclado até que uma idade inválida seja digitada (menor que zero). O programa deve imprimir a lista ordenada crescentemente por

218 218 CAPÍTULO 7. APONTADORES idade, após a leitura das informações. Solução Possível: De forma geral, o programa deve seguir o Pseudocódigo 7.4. Pseudocódigo 7.4 Ordenação de uma lista simplesmente encadeada. Descrição: Programa que ordena um grupo de pessoas por idade. Dados de Entrada: nomes e as respectivas idades. Saída do Programa: lista de nomes ordenados crescentemente por idade. Inicializaç~ao da lista; Leitura de um nome; Leitura da idade; Validaç~ao da idade Se a idade for menor que zero: Parar de ler informaç~oes. Ir para "Ordenaç~ao da Lista"; Se n~ao for menor que zero: Continuar a ler informaç~oes. Ir para "Leitura de um nome"; Ordenaç~ao da lista; Impress~ao da lista ordenada; Destruiç~ao da lista. O passo Ordenação da lista pode ser decomposto como segue no Pseudocódico 7.5. A idéia consiste em posicionar o marcador no começo da lista e, enquanto o final desta não é atingido, buscar o menor elemento à frente do atual marcador. Se encontrar algum, deve-se trocar as informações entre o nó marcador e o que guarda a menor idade à frente. Pseudocódigo 7.5 Processo para a ordenação de uma lista simplesmente encadeada. Processo componente "Ordenaç~ao da lista": Posicionar o marcador no começo da lista; Enquanto n~ao chegar no fim da lista: Buscar o nó que guarda uma idade menor que a do marcador; Se existir um nó com menor idade: Trocar as informaç~oes entre o marcador e o nó encontrado; Posicionar o marcador no próximo nó da seqü^encia da lista; FIM-Processo componente "Ordenaç~ao da lista".

219 7.8. EXERCÍCIOS RESOLVIDOS 219 No Exemplo 7.34, tem-se a trascrição dos Pseudocódigos 7.4 e 7.5 para C. 1 typedef struct { 2 char nome [30]; 3 int idade ; 4 } tinfo ; 5 6 void imprimirlista ( tlista lista ) { 7 tinfo x; 8 int erro = 0; 9 10 primeirolista (& lista ); 11 while (! finallista (& lista )) { 12 x = obterinfo (& lista, & erro ); 13 if ( erro ) { 14 printf (" Nome : %s\n", x. nome ); 15 printf (" Idade : %d\n\n", x. idade ); 16 } 17 proximolista (& lista ); 18 } 19 } tno * menoridade ( tlista lista ) { 22 tno *aux, * menor ; menor = lista. marcador ; 25 aux = menor - > proximo ; 26 while ( aux!= NULL ) { 27 if (aux -> info. idade < menor -> info. idade ) 28 menor = aux ; 29 aux = aux -> proximo ; 30 } 31 return menor ; 32 } void ordenaporidade ( tlista * lista ) { 35 tno * aux ; 36 tinfo x; primeirolista ( lista ); 39 while (! finallista ( lista )) { 40 aux = menoridade (* lista ); 41 if ( lista - > marcador!= aux ) { 42 x = lista -> marcador -> info ; 43 lista -> marcador -> info = aux -> info ; 44 aux -> info = x; 45 } 46 proximolista ( lista ); 47 } 48 } 49

220 220 CAPÍTULO 7. APONTADORES 50 main ( ) { 51 tlista lista ; 52 tinfo dados ; 53 int pos ; inicialista (& lista ); 56 do { 57 printf (" Entre com o nome : "); 58 scanf ("%s", & dados. nome ); 59 printf (" Entre com a idade : "); 60 scanf ("%d", & dados. idade ); 61 if ( dados. idade < 0) 62 break ; 63 incluirfimlista (& lista, dados ); 64 } while (1) ; 65 ordenaporidade (& lista ); 66 imprimirlista ( lista ); 67 destroilista (& lista ); 68 } Exemplo 7.34: Cadastro de nomes e idades Novamente, o programa implementado apenas lê as informações do teclado enquanto não se digita uma idade inválida (nesse cado, menor que 0). Se for digitado tal valor para a idade, o comando break faz a execução do programa sair do laço infinito. Cada nome e idade são armazendos na lista e, após o término de leitura de dados, eles são ordenados por ordem crescente de idade. Na função ordenaporidade, posiciona-se o marcador no começo da lista e, enquanto o final desta não é atingido, busca-se o menor elemento à frente do atual marcador. Caso seja encontrado algum, trocam-se as informações dos nós marcador e aux, o qual guarda o nó com menor idade obtido da função menoridade. A função menoridade procura e retorna um nó que guarda uma idade menor do que a que está no marcador, e que se encontra à frente deste na lista. Assim, a variável menor guarda o nó que contém a menor idade e é inicializado como sendo o atual marcador. A partir do próximo nó após ao atual marcador, enquanto o final da lista não é atingido, busca-se um nó que contenha uma idade menor do a que está armazenada em menor. Se for encontrada alguma, menor passa a apontar para o nó que contém esta menor idade. Repare que é utilizada uma variável aux para percorrer a lista, pois não se pretende alterar a posição do marcador, o qual é utilizado na função ordenaporidade. No fim da execução do programa, a lista ordena é impressa e, antes de se encerrar o programa, ela é destruída. Exercício Resolvido Media de valores de uma Lista Simplesmente Encadeada Considere uma lista de funcionários de uma empresa na qual são armazenados o nome e o salário destes. Pretende-se imprimir na tela todos os funcionários cujos salários estão acima da

221 7.8. EXERCÍCIOS RESOLVIDOS 221 média. Solução Possível: O programa principal segue os passos descritos no Pseudocódigo 7.6. Pseudocódigo 7.6 Média dos valores de uma lista simplesmente encadeada. Descrição: Programa que imprime os funcionários cujos salários estão acima da média. Dados de Entrada: nomes e os respectivos salários. Saída do Programa: lista de nomes que tem o salário acima da média. Inicializaç~ao da lista; Leitura de um nome; Leitura da idade; Validaç~ao da idade Se a idade for menor que zero: Parar de ler informaç~oes. Ir para "Verificar quem está acima da média"; Se n~ao for menor que zero: Continuar a ler informaç~oes. Ir para "Leitura de um nome"; Verificar quem está acima da média; Destruiç~ao da lista. O passo Verificar quem está acima da média pode ser descrito como o Pseudocódigo 7.7. Para achar a média, basta somar todos os salários e dividir pelo total de nós. Tendo a média, procura-se, nó por nó, quem tem o salário maior que ela. Pseudocódigo 7.7 Processo para ver quem está com salário acima da média Processo componente "Verificar quem está acima da média": Posicionar o marcador no começo da lista; Enquanto n~ao chegar no fim da lista: Somar os salários; Posicionar o marcador no próximo nó da seqü^encia da lista; Achar a média (Divide-se o total somado pelo tamanho da lista); Posicionar o marcador no começo da lista; Enquanto n~ao chegar no fim da lista: Se existir um nó com salário maior que a média: Imprimir as informaç~oes do funcionário. Posicionar o marcador no próximo nó da seqü^encia da lista; FIM-Processo componente "Verificar quem está acima da média".

222 222 CAPÍTULO 7. APONTADORES No Exemplo 7.35, o programa implementado contém uma main semelhante à do Exemplo A diferença é que agora é fornecido o salário em vez da idade. Além disso, no lugar da ordena, é chamada a função que procura e imprime os funcionários com salário acima da média. 1 typedef struct { 2 char nome [30]; 3 float salario ; 4 } tinfo ; 5 6 void imprimirlista ( tlista lista ) { 7 tinfo x; 8 int erro = 0; 9 10 primeirolista (& lista ); 11 while (! finallista (& lista )) { 12 x = obterinfo (& lista, & erro ); 13 if ( erro ) { 14 printf (" Nome : %s\n", x. nome ); 15 printf (" Salario : %f\n\n", x. salario ); 16 } 17 proximolista (& lista ); 18 } 19 } void acimamedia ( tlista lista ) { 22 tinfo x; 23 int erro = 0; 24 float total, media ; total = 0; 27 primeirolista (& lista ); 28 while (! finallista (& lista )) { 29 x = obterinfo (& lista, & erro ); 30 if ( erro ) 31 total += x. salario ; 32 proximolista (& lista ); 33 } media = total / lista. tam ; primeirolista (& lista ); 38 while (! finallista (& lista )) { 39 x = obterinfo (& lista, & erro ); 40 if ( erro ) { 41 if ( x. salario > media ) { 42 printf (" Nome : %s\n", x. nome ); 43 printf (" Salario : %f\n\n", x. salario ); 44 } 45 } 46 proximolista (& lista );

223 7.9. RESUMO } 48 } main ( ) { 51 tlista lista ; 52 tinfo dados ; 53 int pos ; inicialista (& lista ); 56 do { 57 printf (" Entre com o nome : "); 58 scanf ("%s", & dados. nome ); 59 printf (" Entre com o salario : "); 60 scanf ("%f", & dados. salario ); 61 if ( dados. salario < 0) 62 break ; 63 incluirfimlista (& lista, dados ); 64 } while (1) ; 65 acimamedia ( lista ); 66 destroilista (& lista ); 67 } Exemplo 7.35: Cadastro de nomes e salários Na função acimamedia, total é inicializado como 0 e, a partir do primeiro elemento da lista, esse total é incrementado com o salário de cada nó. Quando o final da lista é alcançado, a media de salários é computada como sendo o total obtido dividido pelo tamanho da lista, ou seja, o número de funcionários. Com a média calculada, inicia-se um nova busca na lista, desde o primeiro elemento até o final da lista, a fim de que se encontre os salários acima da média. Caso algum nó contenha um salário maior que a média, os dados do funcionário são impressos na tela. 7.9 Resumo Apontadores são um tipo de variável que guarda o endereço de outras variáveis. Trata-se de um conceito de baixo nível, ligado essencialmente à arquitetura de computadores. Em C, a declaração de uma variável apontador segue o formato: <tipo do apontador> * <nome da variável> No lugar de tipo do apontador, podem ser utilizados os tipos padrõess da linguagem C, assim como os tipos definidos pelo programador. O operador & retorna o endereço de memória de uma variável. Já o operador seta ( >) é utilizado para acessar, por meio de apontadores, os atributos de uma estrutura.

224 224 CAPÍTULO 7. APONTADORES Os apontadores podem alterar o conteúdo da variável apontado por ele. Por meio de apontadores também é possível a passagem de parâmetros sem cópia. A passagem de parâmetros por apontadores permite múltiplos retornos em uma função, evita a cópia de muitos dados e possibilita a alteração das variáveis do programa no decorrer da execução da função. Dentre os principais problemas com o uso de apontadores estão: apontadores não inicializados, objetos pendentes, referências pendentes e programação macarrônica. A alocação dinâmica é uma forma de reservar espaços de memória no decorrer da execução do programa, o que evita o despercício de recursos da máquina. Em C, as funções malloc e free são responsáveis por alocar e desalocar áreas de memória, respectivamente. O TAD Implementacional Lista Encadeada é uma forma eficiente de armazenar dados num programa, pois aloca-se e desaloca-se os espaços para os dados dinâmicamente, o que torna muito simples as operações de incluir e excluir elementos Lista de Exercícios 1. Cite três vantagens e três desvantagens de se utilizar apontadores. 2. Explique quais as funcionalidades dos operadores * e &. 3. Liste as diferenças entre variáveis apontadores e não apontadores. Faça um código em C, que realize a soma de duas variáveis float a e b, usando dois apontadores g e h, que apontem para a e b respectivamente. Quais são os tipos dos apontadores g e h? 4. No Exemplo 7.36, escrito na linguagem C, encontre os erros de sintaxe na utilização de apontadores. Justifique cada erro encontrado. 1 main ( ){ 2 int p; 3 int *d; 4 int q = 10; 5 float *j; 6 float t = 15.0; 7 j = &t; 8 p = &q; 9 d = j; 10 } Exemplo 7.36: Exercício Proposto 4

225 7.10. LISTA DE EXERCÍCIOS No Exemplo 7.37, escrito na linguagem C, encontre os problemas causados pela utilização indevida de apontadores. Justifique cada problema encontrado. 1 main ( ){ 2 int *p = ( int *) malloc ( sizeof ( int )); 3 int *q = ( int *) malloc ( sizeof ( int )); 4 int *j; 5 int *h; 6 int *v; 7 int d = 20; 8 int e = 30; 9 *q = e; 10 *j = d; 11 p = &d; 12 h = &e; 13 v = q; 14 free (q); 15 } Exemplo 7.37: Exercício Proposto 5 6. Ilustre as atribuições e referenciamentos feitos no trecho de código do Exemplo 7.6. Utilize para a ilustração os padrões adotados na Figura 7.3 e Figura 7.4, para representar uma variável não apontador e apontador respectivamente. 7. Implemente na linguagem C, uma matriz de strings alocada estáticamente e uma alocada dinamicamente. Explique um caso em que a alocação dinâmica seja importante para economia de memória. 8. Explique o que é alocação e desalocação de memória em tempo de execução e quais funções em C executam essas funcionalidades. Cite uma vantagem de se desalocar memória não mais utilizada. 9. Crie uma funcão que insere um novo elemento na lista numa dada posição. Considere que os argumentos da função são: - Um apontador para a lista no qual o elemento será inserido. - Um elemento de um tipo tinfo previamente definido. - Um inteiro que indica a posição na qual ocorrerá a inserção. Sugestão: verifique se a posição fornecida à função é válida. 10. Considere duas listas simplesmente encadeadas A e B, contendo inteiros ordenados crescentemente. Assim, pede-se implementar uma função que retorne uma lista encadeada ordenada formada pela intercalação dos elementos de A e B, considerando que a

226 226 CAPÍTULO 7. APONTADORES lista resultante não possua chaves repetidas e que A e B também não possuem chaves repetidas. 11. Numa lista duplamente encadeada, os nós possuem um apontador para o nó anterior a ele na lista, além do já apresentado apontador para o proximo. Com base nisso, implemente todas as funçãoes e estruturas discutidas no capítulo na forma de lista duplamente encadeada. 12. Pilhas são um cso particular da lista simplesmente encadeada, no qual insere-se e retira-se um elemento apenas do fim, uma política conhecida como LIFO (last in first out). Seja então P=( a(1), a(2),..., a(n) ) uma pilha. Assim, a(1) é o elemento da base da pilha; a(n) é o elemento topo da pilha; e a(i+1) está acima de a(i). As operações associadas são: - criar (P) - criar uma pilha P vazia. - push (P, x) - inserir o elemento x no topo de P (empilha). - vazia (P) - verifica se P está vazia. - topo (P) - acessa o elemento do topo da pilha, sem desempilhar. - pop (P) - elimina o elemento do topo de P (desempilha). Com base nessas informações, implemente uma pilha de dados pilha de inteiros em C, utilizando as estruturas e funções apresentadas sobre lista simplemente encadeada. 13. Duas pilha seqüênciais numéricas estão ordenadas crecentemente a partir do topo. Transfira os elementos dessas pilhas para uma terceira pilha, inicialmente vazia, de modo que ela fique ordenada decrescentemente (maior valor no topo). Suponha que não haja restrições quanto a capacidade das pilhas. 14. O problema do abre/fecha parênteses. Este problema consiste em verificar se uma expressão matemática está corretamente formada em termos de abre/fecha parênteses. Exemplos: 7-((X*((X+Y)/(J-3))+Y)/(4-2.5)) ((A+B) ) A + B ( - C (A + B)) - (C + D Numa expressão correta o número de ) deve ser igual ao número de (. Cada ) deve ser precedido por um (. Para isto, pode utilizar-se de um contador inicialmente igual a zero que, ao percorrer a expressão da direita para a esquerda, é decrementado quando se encontra um ( e incrementado quando se encontra um ). Assim, o contador no final da expressão deve ser igual a zero e, em nenhum momento, o ele deve ser menor que zero.

227 7.10. LISTA DE EXERCÍCIOS 227 Então, implemente um programa C que leia uma expressão com parênteses e verifique se ela está corretamente formada em termos de abre/fecha parênteses usando o método acima, utilizando as estruturas e funções de pilha criadas nos exercícios anteriores, mas modificadas para receber caracteres. 15. Outro caso particular da lista simplesmente encadeada é a fila. Agora, a inserção é feita apenas no fim da lista e a remoção no início. Seja então F=( a(1), a(2),..., a(n) ) uma fila. Dessa forma, a(1) é o começo da fila; a(n) é o final da pilha; e a(i+1) está atrás de a(i). As operações associadas são: - criar (F) - criar uma fila F vazia. - insere (F, x) - inserir o elemento x no final de F. - vazia (F) - verifica se F está vazia. - inicio (F) - acessa o elemento do início da fila, sem retirá-lo. - retira (F) - elimina o elemento do final de F. Com base nessas informações, implemente uma fila de dados fila de nomes em C, utilizando as estruturas e funções apresentadas sobre lista simplemente encadeada. 16. Duas filas seqüênciais de nomes estão ordenadas crecentemente a partir do início. Transfira os elementos que ocorrem nessas duas filas para uma terceira fila, inicialmente vazia, de modo que ela também fique ordenada crescentemente, ou seja, o primeiro nome em ordem alfabética no começo da fila. 17. Uma palavra é um palíndromo se tem a mesma seqüência de letras, quer seja lida da esquerda para a direita ou da direita para a esquerda (exemplo: raiar). Implemente uma solução para verificar se uma palavra é um palíndromo, usando pilha(s) e/ou fila(s). 18. Suponha uma fila de inteiros F. Mude a posição de um elemento desta fila, tendo apenas uma pilha vazia P e uma váriavel do tipo inteiro x como auxiliares. Considere apenas as operações associadas aos tipos fila e pilha. 19. Implemente uma lista encadeada que armazene as informações sobre os DVDs de uma locadoras. Tais informações são título do filme, nome do diretor, principais atores e número de cópias disponíveis na locadora. Apresente um menu e crie funções que atendam as seguintes opções: - Dado um título de filme, verificar se locadora possui em seu acervo. - Dado um título de filme, verificar se há alguma cópia disponível. - Dado um diretor, imprimir as informações sobre os filmes dirigido por ele.

228 228 CAPÍTULO 7. APONTADORES - Dado um ator, imprimir as informações sobre os filmes nos quais ele foi um dos atores principais. 20. Sabe-se que um texto é uma seqüência de caracteres contendo apenas letras, espaços em branco e sinais de pontuação. Uma palavra é definida como um segmento do texto que consiste apenas de letras. Escreva uma função que recebe um texto do teclado e imprime uma relação de todas as palavras que ocorrem no texto juntamente com o número de ocorrências de cada palavra Trabalhos Sugeridos 1. Notas de Alunos O Colegiado de Engenharia de Computação mantém uma listagem com informações sobre o desempenho de cada aluno do curso. Esta listagem contém as notas obtidas pelo aluno em cada uma das disciplinas que ele cursou e é organizada da seguinte maneira: Matrícula do Aluno Código da Disciplina Carga Horária Nota Para facilitar a consulta a esta listagem, o coordenador do colegiado precisa de um programa em C que realize algumas operações sobre os dados da lista. Ele te pediu para que você elabore um programa interativo que permita ao usuário: (a) Ler os dados da listagem e colocá-los em quatro listas. O usuário deve poder incluir tantos dados quanto quiser. A indicação de fim de entrada de dados é feita através de um aluno de Matrícula 0 (zero). (b) Mostrar as notas de um aluno numa disciplina. Lembre-se que o aluno pode ter cursado mais de uma vez uma mesma disciplina por motivo de reprovação. (c) Incluir uma nota de um aluno em uma disciplina. (d) Retificar uma nota de um aluno em uma disciplina. (e) Excluir uma nota de um aluno em uma disciplina. (f) Excluir todos os dados de um aluno jubilado. (g) Calcular o o coeficiente de rendimento (C.R.) de um aluno. Lembre-se que o C.R. é calculado da seguinte maneira:

229 7.11. TRABALHOS SUGERIDOS 229 C.R. = (Σ (Carga Horária x Nota)) / (Σ (Carga Horária)) (h) Identificar o aluno de melhor CR. (i) Identificar os alunos com CR abaixo de 5.0. (j) Identificar se um determinado aluno já cumpriu a carga horária mínima do curso (defina um valor). Exemplo de Programa Escolha Operação: 0 - Sair 1 - Ler dados 2 - Mostrar Notas 3 - Incluir Nota 4 - Corrigir Nota 5 - Excluir Nota 6 - Excluir Aluno 7 - CR de Aluno 8 - Melhor Aluno 9 - Maus Alunos 10 - Formando Opção: 1 Dados do aluno: Dados do aluno: Dados do aluno: Dados do aluno: Dados do aluno: Opção: 2 Matrícula: 18 Disciplina: 1 Notas: Opção: 3 Matrícula: 1111 Disciplina: 316 Carga Horária: 60 Nota: 7.0

230 230 CAPÍTULO 7. APONTADORES Opção: 4 Matrícula: 1111 Disciplina: 316 Carga Horária: 60 Nota: 7.0 Nova Nota: 8.5 Opção: 5 Matrícula: 1111 Disciplina: 316 Nota: 7.0 Opção: 6 Matrícula: 234 Opção: 7 Matrícula: 1111 CR: 7.2 Opção: 8 Matrícula: 234 Opção: 9 Matrículas: 194 Opção: 10 Matrícula: 1111 Não cumpriu carga horária mínima Opção: 0 Até a próxima! 2. Controlador de Tráfego Aéreo Descrição Controle de Tráfego Aéreo é um serviço prestado por controladores, em terra, que guiam aeronaves (geralmente aviões) no ar e no solo, para garantir um fluxo de tráfego seguro e

231 7.11. TRABALHOS SUGERIDOS 231 ordenado. Os controladores de tráfego áereo forencem indicações e autorizações de vôo, de acordo com as características operacionais das aeronaves e as condições de tráfego em determinado momento. Estas autrorizações podem incidir sobre a rota, altitude e/ou velocidade propostas pelo operador da aeronave para determinado vôo, devendo os pilotos cumprirem as instruções recebidas. Espaço Aéreo Em muitos países, os serviços de tráfego aéreo são prestados em toda a extensão do espaço aéreo e estes serviços são utilizados por todos os usuários (aeronaves privadas, militares e comerciais). Os espaços aéreos onde o controlador é responsável por prover separação entre as aeronaves são chamados de espaço áereo controlado em oposição ao espaço aéreo espaço áereo não controlado no qual pilotos das aeronaves são responsáveis por manter a separação entre a sua aeronave e outras. Dependendo do tipo de vôo e de classe do espaço aéreo, o controlador de tráfego aéreo pode emitir instruções que os pilotos devem seguir ou apenas informações de vôo para ajudar os pilotos operando no espaço aéreo. Em todos os casos, entretanto, o piloto tem a responsabilidade final pela segurança da aeronave, e pode não cumprir as intruções numa emergência. Os serviços de controloe de tráfego aéreo incluem o controle de rotas das aeronaves que estão no espaço áereo do aeroporto, o controle da autorização de pousos e decolagens e o controle das pistas de táxi-aéreo e pátios. O Trabalho O trabalho consiste em implementar um programa que faça o controle dos pousos em um aeroporto que apresente três portões de embarque e desembarque (A,B,C). O trabalho deve ser feito em dois módulos: controle de espaços aéreos e controle de pista, e a estrutura para armazenamento dos dados deve ser uma lista encadeada dinâmica. Controle de espaço aéreo Como o controle da torre consiste em controlar as aeronaves enquanto elas estiverem no espaço aéreo do aeroporto, pode-se dividir essa tarefa em duas subtarefas: Controle de rotas A partir do momento em que as aeronaves entram no espaço aéreo do aeroporto, o controle das suas rotas deverá ser realizado pelo seu programa. Para isso considere que a função básica do seu programa é impedir que ocorram colisões, e para isso uma das aeronaves em risco deverá ser redirecionada para a rota mais próxima a uma das

232 232 CAPÍTULO 7. APONTADORES rotas envolvidas. Existem duas ocoasiões em que ocorrerão colisões: se duas aeronaves estiverem utilizando a mesma rota ou se duas aeronaves estiverem utilizando rotas que apresentem intersecção. Considere que duas rotas apresentam intersecção se uma delas é múltipla da outra. Controle de aproximação Dado que o controle de rotas já foi realizado será necessário definir qual será a ordem das aeronaves a pousar. Para isso deverá ser considerada a seqüência de prioridades abaixo: CLASSE > ALTITUDE > VELOCIDADE Assim, define-se que existem apenas duas classes (militar e comercial) e que duas alttudes são consideradas iguais se a diferença entre elas for menor que 2000 pés. Além disso, a classe militar tem prioridade frente a classe comercial; quanto menor a altitude de uma aeronave maior será a sua prioridade e quanto maior a velocidade de uma aeronave maior será a sua prioridade. Considere que cada operação de pouso dura 5 minutos. Controle de pista Após o pouso de uma aeronave é necessário que o tráfego em solo também seja controlado. Com isso, a segunda parte do consistirá em controlar o momento em que cada aeronave deverá encostar em uma plataforma que está livre. Caso todas as três plataformas estejam ocupadas no momento em que uma aeronave pouse, esta deverá esperar no pátio de espera até que alguma das plataformas seja desocupada. Considere que cada operação de desembarque dure 30 minutos. Especificação Módulo 1 Essa primeira parte do trabalho consiste em determinar a fila de aeronaves que irá pusar e as novas rotas das aeronaves que precisam mudar de rota. Para isso, considere que quando duas aeronaves apresentem chance de colisão a aeronave que apresentar número de rota maior deverá ser redirecionada para uma rota com número igual ao primeiro número primo maior que a rota de maior número. Módulo 2

233 7.11. TRABALHOS SUGERIDOS 233 A segunda parte consiste em determinar a que horas a operação de desembarque de cada aeronave foi realizada. Entrada As informações referente às aeronaves serão fornecidas através do teclado. As informações serão as seguites: identificação da aeronave (0103, 0407, 1114,...); número inteiro que servirá para indentificar qual rota que a aeronave está percorrendo (1, 2, 4, 6,...); classe da aeronave ( comercial ou militar ); altitude na qual a aeronave se encontra no momento em que entra no espaço aéreo do aeroporto (7700, 9060, 1120,...); velocidade com qual a aeronave está (650, 680, 810,...) e a hora de entrada da aeronave no espaço áereo do aeroporto (14:30, 14:29, 14:31,...). Considere que a hora apresentará o caracter : para separar hora de minuto. Obs.: As horas de entrada no espaço aéreo do aeroporto são muito próximas, com diferença entre o menor e o maior horário de no máximo 5 minutos. Cada nova aeronave deve ser adicionada na lista já na ordem de prioridade de pouso, do mais para o menos prioritário. Assim, após todas as inserções, as colisões deve ser verificadas, aeronave por aeronave, na seqüência de pouso obtida. Exemplo: militar : comercial : militar :30

234 234 CAPÍTULO 7. APONTADORES comercial : militar :30 Saída Os dados de saída do programa devem ser exibidos no console. O formato da saída deve seguir o exemplo abaixo. Exemplo: : : : : :35

235 Capítulo 8 Arquivos Autores: Estefhan Dazzi Wandekokem Flávio Varejão Objetivos: Definir e apresentar arquivos; Definir e diferenciar variáveis transientes e variáveis persistentes; Definir e diferenciar arquivos de texto e arquivos binários; Mostrar algumas operações que podem ser feitas com arquivos; Mostrar outras funções e exemplos de programas que usam arquivos; 8.1 Variáveis Transientes X Variáveis Persistentes Quando os programas são executados, eles lidam com variáveis. Elas são a forma abstrata pela qual os programadores enxergam os dados que seus programas manipulam. Internamente, variáveis indicam células de memória. Por exemplo, numa arquitetura de 32 bits (que pode ser um Pentium da Intel), normalmente o tipo inteiro de C corresponderá a 32 bits, o que pode significar que uma variável int dessa linguagem ocupará 4 bytes (4 células de memória). A memória principal de um computador (muitas vezes chamada de memória RAM) é o local onde os programas em execução, e também as variáveis que eles utilizam, são armazenados. Chamam-se essas variáveis armazenadas na memória principal de variáveis transientes porque seu tempo de existência é limitado pelo tempo que o programa se encontra em memória. Assim que o usuário ou o Sistema Operacional decidir que o programa deve ser finalizado, todas essas variáveis deixarão de existir. Os computadores teriam sua utilidade muito reduzida se a informação só pudesse ser armazenada enquanto os programas estivessem na memória principal (e a máquina, ligada). Para 235

236 236 CAPÍTULO 8. ARQUIVOS resolver esse problema, existem as variáveis persistentes, as quais têm seu conteúdo armazenado, e possível de ser acessado, independentemente do programa que as criou estar na memória principal. Variáveis persistentes devem ser armazenadas em algum dispositivo de memória secundária, como discos rígidos, CD s, memória flash e DVD s, os quais tem a capacidade de manter por um longo tempo o valor da informação neles contida, independente do conteúdo da memória principal do computador, ou mesmo de ele estar ligado. Deve-se ressaltar que o tempo de acesso e de gravação das variáveis armazenadas em meio secundário é muito maior que o das variáveis armazenadas na memória principal, por isso essa última é tão importante. O conceito por trás das variáveis persistentes é o de arquivo, e esse será o tema desse capítulo. Por meio de arquivos, essas variáveis podem ser armazenadas e as informações que elas guardam, acessadas e processadas no futuro, tanto pelo programa que as criou quanto por outros programas. 8.2 Tipos de Arquivos Existem dois tipos de arquivos: arquivos texto e arquivos binários. Ambos têm a mesma finalidade: armazenar variáveis persistentes. Quando o programador cria um arquivo em seu programa, deve indicar a qual tipo ele pertencerá. Isso determina o formato das variáveis persistentes que ele gravará e acessará do arquivo Tipos de Arquivos - Arquivos Texto Um arquivo texto é uma seqüência de caracteres. Se um arquivo texto for aberto num editor de texto convencional, o usuário poderá ler seu conteúdo (ou ao menos ver os caracteres lá contidos), e poderá até mesmo modificá-lo de uma forma que faça sentido. Por exemplo, na linguagem C, tome uma variável int que guarde o numero 43. Uma forma de escrever essa variável num arquivo texto seria gravar 43, mas note que, com isso, dois caracteres foram gravados: o 4 seguido do 3. Isso poderia significar, por exemplo, para um arquivo numa arquitetura que salve caracteres como variáveis de 1 byte, que dois bytes foram gravados no arquivo, o primeiro armazenando o caractere 4 e o segundo armazenando o caractere 3. Figura 8.1: Exemplo de um arquivo texto.

237 8.2. TIPOS DE ARQUIVOS Tipos de Arquivos - Arquivos Binários Por meio de um arquivo binário, as variáveis armazenadas na memória principal podem ter seus bytes armazenados num arquivo físico, sem tradução para caracteres e com correspondência de um para um. Isso significa que, usando-se arquivos binários, torna-se possível criar um espelho da memória principal, mas salvo em memória secundária. A fim de exemplificar esse conceito na linguagem C, será citada a gravação de uma struct, uma das razões pela qual arquivos binários são tão úteis nessa linguagem. Já foi estudado que uma struct é um tipo especial de variável que armazena outras variáveis. Tome, por exemplo, a struct struct pessoa { char nome[50]; int idade; float salário; } Como guardar essa informação em um arquivo? Pode-se guardá-la num arquivo texto, escrevendo-se o nome da pessoa, então sua idade e finalmente seu salário. Sabendo o formato como essa informação foi escrita, o programador pode criar um programa que a lê. De modo alternativo, pode-se armazená-la num arquivo binário usando um comando (que será mostrado depois) que trata toda a struct como uma única variável. Ela pode ser lida também se usando um comando que a trata da mesma forma. Isso é possível, pois se sabe o formato como essa struct foi armazenada no arquivo binário. Esse formato poderia ser, para uma arquitetura de 32 bits de palavra, na linguagem C, de 50 bytes que armazenam cada uma das variáveis char, mais 32 bits (4 bytes) que armazenam uma variável int, e mais 32 bits (4 bytes) que armazenam uma variável float. Figura 8.2: Exemplo de um arquivo binário.

Computação I (MAB120) DCC/UFRJ

Computação I (MAB120) DCC/UFRJ Computação I (MAB120) DCC/UFRJ Aula 2: introdução a algoritmos e programas 6 de abril de 2016 Objetivos dessa aula Definir o que são algoritmos e programas Apresentar algumas técnicas para desenvolvê-los

Leia mais

Programação II. Aula 1

Programação II. Aula 1 Programação II Aula 1 Programação II Objetivo: O aluno deverá ser capaz de construir algoritmos de forma organizada segundo o programação procedural utilizando a linguagem de programação C. Introdução

Leia mais

Organização e Arquitetura de Computadores I

Organização e Arquitetura de Computadores I Organização e Arquitetura de Computadores I Evolução e Desempenho dos Computadores Slide 1 Conceitos Arquitetura do Computador Refere-se aos atributos que são visíveis para o programador. Ex: conjunto

Leia mais

- Campus Salto. Disciplina: Sistemas de Arquivos Docente: Fernando Santorsula E-mail: fernandohs@ifsp.edu.br

- Campus Salto. Disciplina: Sistemas de Arquivos Docente: Fernando Santorsula E-mail: fernandohs@ifsp.edu.br Disciplina: Sistemas de Arquivos Docente: Fernando Santorsula E-mail: fernandohs@ifsp.edu.br Sistemas de Arquivos- Parte 2 Pontos importantes de um sistema de arquivos Vários problemas importantes devem

Leia mais

Algoritmos e Programação : Conceitos e estruturas básicas. Hudson Victoria Diniz

Algoritmos e Programação : Conceitos e estruturas básicas. Hudson Victoria Diniz Algoritmos e Programação : Conceitos e estruturas básicas Hudson Victoria Diniz Relembrando... Um algoritmo é formalmente uma seqüência finita de passos que levam a execução de uma tarefa. Podemos pensar

Leia mais

Montadores e Compiladores

Montadores e Compiladores Montadores e Compiladores Prof. Idevar Gonçalves de Souza Júnior Conteúdo Programático Resumido Revisão da Arquitetura de Computadores Introdução a Teoria das Linguagens Compilação e Interpretação Análise

Leia mais

Universidade Federal de Uberlândia - UFU Faculdade de Computação - FACOM Lista de exercícios de programação em linguagem C. Exercícios: Structs

Universidade Federal de Uberlândia - UFU Faculdade de Computação - FACOM Lista de exercícios de programação em linguagem C. Exercícios: Structs Universidade Federal de Uberlândia - UFU Faculdade de Computação - FACOM Lista de exercícios de programação em linguagem C Exercícios: Structs 1. Utilizando estrutura, fazer um programa em C que permita

Leia mais

Até o momento, vimos que a estrutura de um computador segue uma seqüência: ENTRADA => PROCESSAMENTO => SAÍDA

Até o momento, vimos que a estrutura de um computador segue uma seqüência: ENTRADA => PROCESSAMENTO => SAÍDA Curso de Sistemas de Informação Disciplina: Algoritmos 1º e 2º Períodos Turmas 216071A, 216071B e 216062 Notas de Aula Aulas 07, 08 e 09. Professor: Edkallenn Aula 07 Variáveis Talvez o conceito de variável

Leia mais

Nº horas ESTRATÉGIAS RECURSOS AVALIAÇÃO

Nº horas ESTRATÉGIAS RECURSOS AVALIAÇÃO ANO: 10.º Curso Profissional Técnico de Informática de Gestão Disciplina: Linguagens de Programação ANO LECTIVO: 2008/2009 p.1/13 Módulo 1 Algoritmia 1 - Introdução à Lógica de Programação Lógica Sequência

Leia mais

Microcontroladores e Microprocessadores. Conversão de Bases Prof. Samuel Cavalcante

Microcontroladores e Microprocessadores. Conversão de Bases Prof. Samuel Cavalcante Microcontroladores e Microprocessadores Conversão de Bases Prof. Samuel Cavalcante Conteúdo Conversão de Qualquer base para Decimal Decimal para Binário Hexadecimal para binário Componentes básicos de

Leia mais

Sistemas Numéricos. Tiago Alves de Oliveira

Sistemas Numéricos. Tiago Alves de Oliveira Sistemas Numéricos Tiago Alves de Oliveira Sumário Sistemas Numéricos Binário Octal Hexadecimal Operações aritméticas binária e hexadecimal Operações lógicas binárias e decimais Representação Interna de

Leia mais

Metodologias de Programação

Metodologias de Programação Metodologias de Programação Bloco 1 José Paulo 1 Formador José António Paulo E-mail: questoes@netcabo.pt Telemóvel: 96 347 80 25 Objectivos Iniciar o desenvolvimento de raciocínios algorítmicos Linguagem

Leia mais

Introdução. Software Básico Aula 3. Prof. Dr. Rogério Vargas.

Introdução. Software Básico Aula 3. Prof. Dr. Rogério Vargas. Introdução Software Básico Aula 3 Prof. Dr. Rogério Vargas http://rogerio.in Provocação Você já se perguntou como é que os programas que você escreve são traduzidos em instruções executáveis pelas estruturas

Leia mais

Introdução à Algoritmos. Aula 11

Introdução à Algoritmos. Aula 11 Introdução à Algoritmos Aula 11 Um programa de computador é um produto resultante da atividade intelectual. Essa atividade depende de um treinamento prévio em abstração e modelagem de problemas, bem como

Leia mais

Algoritmo e Pseudo-código

Algoritmo e Pseudo-código Departamento de Sistemas de Computação Universidade de São Paulo Algoritmo e Pseudo-código Responsável Prof. Seiji Isotani (sisotani@icmc.usp.br) Objetivos do Curso Desenvolver o Pensamento Computacional

Leia mais

Linguagem C. Introdução à Programação C. Variáveis. Identificadores. Identificadores 12/03/2011 VARIÁVEIS E TIPOS DE DADOS

Linguagem C. Introdução à Programação C. Variáveis. Identificadores. Identificadores 12/03/2011 VARIÁVEIS E TIPOS DE DADOS Linguagem C Introdução à Programação C Introdução à Ciência da Computação I Prof. Denis F. Wolf Origem de C está associada ao sistema Unix Histórico: 1970: Ken Thompson desenvolve B, baseada em BCPL, para

Leia mais

O que é Microsoft Excel? Microsoft Excel. Inicialização do Excel. Ambiente de trabalho

O que é Microsoft Excel? Microsoft Excel. Inicialização do Excel. Ambiente de trabalho O que é Microsoft Excel? Microsoft Excel O Microsoft Excel é um programa para manipulação de planilhas eletrônicas. Oito em cada dez pessoas utilizam o Microsoft Excel pra trabalhar com cálculos e sistemas

Leia mais

Algoritmos APRENDENDO A PROGRAMAR COM C#

Algoritmos APRENDENDO A PROGRAMAR COM C# Algoritmos APRENDENDO A PROGRAMAR COM C# Alô Mundo AULA 01 Conhecendo o ambiente O objetivo principal desse programa não é mostrar a mensagem Alo Mundo. O objetivo é apresentar o ambiente de desenvolvimento

Leia mais

Proporcionar a modelagem de sistemas utilizando todos os conceitos da orientação a objeto;

Proporcionar a modelagem de sistemas utilizando todos os conceitos da orientação a objeto; Módulo 7 UML Na disciplina de Estrutura de Sistemas de Informação, fizemos uma rápida passagem sobre a UML onde falamos da sua importância na modelagem dos sistemas de informação. Neste capítulo, nos aprofundaremos

Leia mais

Conceitos básicos de programação

Conceitos básicos de programação Tipos de dados estruturados Tipos estruturados vectores matrizes Estruturas (registos) Vectores e matrizes são estruturas homogéneas. Uma estrutura homogénea é uma sequência linear de elementos de mesmo

Leia mais

AULA 3 Alocação dinâmica de memória: Ponteiros

AULA 3 Alocação dinâmica de memória: Ponteiros UNIP - Ciência da Computação e Sistemas de Informação Estrutura de Dados AULA 3 Alocação dinâmica de memória: Ponteiros Estrutura de Dados 1 Variáveis X Ponteiros VARIÁVEL - Estrutura para armazenamento

Leia mais

Informática Aplicada

Informática Aplicada Informática Aplicada Aula 1 Introdução Diogo Pinheiro Fernandes Pedrosa Departamento de Ciências Exatas e Naturais Universidade Federal Rural do Semi-Árido Introdução Informática informação aplicada; Pressupõe

Leia mais

Fundamentos de Arquitetura e Organização de Computadores

Fundamentos de Arquitetura e Organização de Computadores Fundamentos de Arquitetura e Organização de Computadores Dois conceitos fundamentais no estudo dos sistemas de computação são o de Arquitetura e Organização de computadores. O termo arquitetura refere-se

Leia mais

3. COMPILAÇÃO E ESTRUTURA BÁSICA DE UM PROGRAMA EM C

3. COMPILAÇÃO E ESTRUTURA BÁSICA DE UM PROGRAMA EM C 3. COMPILAÇÃO E ESTRUTURA BÁSICA DE UM PROGRAMA EM C 3.1. Compilação de um Programa C O compilador C realiza a compilação do código-fonte de um programa em cinco etapas: edição, pré-processamento, compilação,

Leia mais

Projetando um Computador

Projetando um Computador Objetivo Projetando um Computador Parte II (2) Projetando um Computador Parte II Arquitetura do Processador BIP Apresentar as noções do funcionamento de um computador através da descrição da arquitetura

Leia mais

Linguagens de Programação:

Linguagens de Programação: Capítulo I : Noções Gerais 11 Linguagens de Programação: Como comunicar com o computador? Linguagem Máquina: Conjunto básico de instruções, em código binário, características de cada computador, correspondentes

Leia mais

Universidade Federal da Paraíba Centro de Informática Departamento de Informática

Universidade Federal da Paraíba Centro de Informática Departamento de Informática Universidade Federal da Paraíba Centro de Informática Departamento de Informática Disciplina: Introdução à Programação Horário: T08002, I08002 Sala: CISB01 T10002, I10002 Sala: CI304 Número de Créditos:

Leia mais

Estruturas de Repetição

Estruturas de Repetição Estruturas de Repetição Lista de Exercícios - 04 Algoritmos e Linguagens de Programação Professor: Edwar Saliba Júnior Estruturas de Repetição O que são e para que servem? São comandos que são utilizados

Leia mais

Orientação a Objetos

Orientação a Objetos Orientação a Objetos 1. Manipulando Atributos Podemos alterar ou acessar os valores guardados nos atributos de um objeto se tivermos a referência a esse objeto. Os atributos são acessados pelo nome. No

Leia mais

Figura 8: modelo de Von Neumann

Figura 8: modelo de Von Neumann 3. ORGANIZAÇÃO DE SISTEMA DE COMPUTADORES Olá, caro aluno! Neste capítulo vamos ver como são organizados os componentes que formam um sistema computacional. O conceito é histórico, mas é aplicado até os

Leia mais

Árvores Parte 1. Aleardo Manacero Jr. DCCE/UNESP Grupo de Sistemas Paralelos e Distribuídos

Árvores Parte 1. Aleardo Manacero Jr. DCCE/UNESP Grupo de Sistemas Paralelos e Distribuídos Árvores Parte 1 Aleardo Manacero Jr. DCCE/UNESP Grupo de Sistemas Paralelos e Distribuídos Árvores uma introdução As listas apresentadas na aula anterior formam um conjunto de TADs extremamente importante

Leia mais

Oganização e Arquitetura de Computadores

Oganização e Arquitetura de Computadores Oganização e Arquitetura de Computadores Capítulo 14 e 15 Unidade de Controle Parte I Operação da Unidade de Controle 1 Micro-Operações Um computador executa um programa Ciclo: Busca/Executa Cada ciclo

Leia mais

Conceitos c++ Prof. Demétrios Coutinho INFORMÁTICA BÁSICA

Conceitos c++ Prof. Demétrios Coutinho INFORMÁTICA BÁSICA INFORMÁTICA BÁSICA Conceitos c++ Prof. Demétrios Coutinho C a m p u s P a u d o s F e r r o s D i s c i p l i n a d e O r g a n i z a ç ã o d e A l g o r i t m o s D e m e t r i o s. c o u t i n h o @

Leia mais

INTRODUÇÃO À PROGRAMAÇÃO II VARIÁVEIS COMPOSTAS HOMOGÊNEAS UNIDIMENSIONAIS

INTRODUÇÃO À PROGRAMAÇÃO II VARIÁVEIS COMPOSTAS HOMOGÊNEAS UNIDIMENSIONAIS INTRODUÇÃO À PROGRAMAÇÃO II VARIÁVEIS COMPOSTAS HOMOGÊNEAS UNIDIMENSIONAIS Material da Prof. Ana Eliza Dados e comandos, para serem processados, devem estar na memória do computador. Memória Definição:

Leia mais

O Sistema de Computação

O Sistema de Computação Departamento de Ciência da Computação - UFF O Sistema de Computação Profa. Débora Christina Muchaluat Saade debora@midiacom.uff.br O Sistema de Computação Capítulo 2 Livro do Mário Monteiro Componentes

Leia mais

números decimais Inicialmente, as frações são apresentadas como partes de um todo. Por exemplo, teremos 2 de um bolo se dividirmos esse bolo

números decimais Inicialmente, as frações são apresentadas como partes de um todo. Por exemplo, teremos 2 de um bolo se dividirmos esse bolo A UA UL LA Frações e números decimais Introdução Inicialmente, as frações são apresentadas como partes de um todo. Por exemplo, teremos de um bolo se dividirmos esse bolo em cinco partes iguais e tomarmos

Leia mais

Aula Extra. Depurador Code::Blocks. Monitoria de Introdução à Programação

Aula Extra. Depurador Code::Blocks. Monitoria de Introdução à Programação Aula Extra Depurador Code::Blocks Monitoria de Introdução à Programação Depurador - Definição Um depurador (em inglês: debugger) é um programa de computador usado para testar outros programas e fazer sua

Leia mais

1. Estrutura de Dados

1. Estrutura de Dados 1. Estrutura de Dados Não existe vitória sem sacrifício! Filme Transformers Um computador é uma máquina que manipula informações. O estudo da ciência da computação inclui o exame da organização, manipulação

Leia mais

Programação de Computadores I. Linguagem C Função

Programação de Computadores I. Linguagem C Função Linguagem C Função Prof. Edwar Saliba Júnior Fevereiro de 2011 Unidade 07 Função 1 Conceitos As técnicas de programação dizem que, sempre que possível, evite códigos extensos, separando o mesmo em funções,

Leia mais

Anterior Sumário Próximo MATRIZES, DETERMINANTES E SISTEMAS

Anterior Sumário Próximo MATRIZES, DETERMINANTES E SISTEMAS Anterior Sumário Próximo MATRIZES, DETERMINANTES E SISTEMAS Clicando em, o usuário é conduzido para uma tela onde os conteúdos estão separados por blocos, que são acessados a medida que clicamos em cada

Leia mais

UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ. Câmpus Ponta Grossa. Coordenação do Curso Superior de Tecnologia em. Automação Industrial

UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ. Câmpus Ponta Grossa. Coordenação do Curso Superior de Tecnologia em. Automação Industrial UNIVERSIDADE TECNOLÓGICA FEDERAL DO PARANÁ Câmpus Ponta Grossa Coordenação do Curso Superior de Tecnologia em Automação Industrial Jhonathan Junio de Souza Tipos de códigos Binários Trabalho apresentado

Leia mais

EXPLORANDO A RESOLUÇÃO DE PROBLEMAS COM O MAPLE

EXPLORANDO A RESOLUÇÃO DE PROBLEMAS COM O MAPLE EXPLORANDO A RESOLUÇÃO DE PROBLEMAS COM O MAPLE José Domingos Albuquerque Aguiar UFRPE domingos.aguiar@ig.com.br Polyana de Cássia Cavalcanti dos Santos UFRPE - poly.cavalcanti@ig.com.br 1. INTRODUÇÃO

Leia mais

1. Noção de algoritmo

1. Noção de algoritmo 1. Noção de algoritmo Em português, a palavra algoritmo quer dizer operação ou processo de cálculo, entre outras definições possíveis (Dicionário Brasileiro de Língua Portuguesa, Mirador Internacional,

Leia mais

números decimais Inicialmente, as frações são apresentadas como partes de um todo. Por exemplo, teremos 2 de um bolo se dividirmos esse bolo

números decimais Inicialmente, as frações são apresentadas como partes de um todo. Por exemplo, teremos 2 de um bolo se dividirmos esse bolo A UA UL LA Frações e números decimais Introdução Inicialmente, as frações são apresentadas como partes de um todo. Por exemplo, teremos de um bolo se dividirmos esse bolo em cinco partes iguais e tomarmos

Leia mais

Programação de Computadores I Estruturas de Repetição PROFESSORA CINTIA CAETANO

Programação de Computadores I Estruturas de Repetição PROFESSORA CINTIA CAETANO Programação de Computadores I Estruturas de Repetição PROFESSORA CINTIA CAETANO Introdução Existem situações onde é necessário repetir um determinado trecho de um programa um certo número de vezes. Assim,

Leia mais

BANCO DE DADOS I AULA 2. Willamys Araújo willamysaraujo7@gmail.com

BANCO DE DADOS I AULA 2. Willamys Araújo willamysaraujo7@gmail.com BANCO DE DADOS I AULA 2 Willamys Araújo willamysaraujo7@gmail.com Modelagem de Dados Modelagem de dados é o estudo das informações existentes em um contexto sob observação para a construção de um modelo

Leia mais

- Campus Salto. Disciplina: Sistemas Operacionais Docente: Fernando Santorsula E-mail: fernandohs@ifsp.edu.br

- Campus Salto. Disciplina: Sistemas Operacionais Docente: Fernando Santorsula E-mail: fernandohs@ifsp.edu.br - Campus Salto Disciplina: Sistemas Operacionais Docente: Fernando Santorsula E-mail: fernandohs@ifsp.edu.br - Campus Salto Mas o que é um Sistema Operacional??? - Campus Salto Resposta segundo Andrew

Leia mais

Determinantes. Matemática Prof. Mauricio José

Determinantes. Matemática Prof. Mauricio José Determinantes Matemática Prof. Mauricio José Determinantes Definição e Conceito Matriz de ordem 1 Dizemos que um determinante é um resultado (numérico) de operações que são realizadas em uma matriz quadrada.

Leia mais

Histórico e Evolução da Computação

Histórico e Evolução da Computação Lista de Exercícios Introdução à Informática Professor: Sérgio Salazar Histórico e Evolução da Computação O 1º computador foi o ENIAC, utilizado para montar tabelas para o cálculo de projéteis na 2ª Guerra

Leia mais

Projetando um Computador Parte II Arquitetura do Processador BIP

Projetando um Computador Parte II Arquitetura do Processador BIP Projetando um Computador Parte II Arquitetura do Processador BIP Prof. Dr. Cesar Albenes Zeferino (zeferino@univali.br) Objetivo Projetando um Computador Parte II (2) Apresentar as noções do funcionamento

Leia mais

Parte 05 - Técnicas de programação (mapas de Veitch-Karnaugh)

Parte 05 - Técnicas de programação (mapas de Veitch-Karnaugh) Parte 05 - Técnicas de programação (mapas de Veitch-Karnaugh) Mapas de Veitch-Karnaugh Montar circuitos lógicos a partir de tabela verdade, embora seja tarefa fácil, geral um circuito extremamente grande.

Leia mais

Os conceitos aprendidos são fundamentais, essenciais para escrever um programa macro. Mas o que é um programa macro?

Os conceitos aprendidos são fundamentais, essenciais para escrever um programa macro. Mas o que é um programa macro? Resumo. O uso de uma variável é necessário quando você não sabe ainda um valor ou uma função, ou seja, quando há uma situação de mudança que impede a determinação direta destes valores. Uma variável pode

Leia mais

http://www.ic.uff.br/~boeres/fac.html! Conteúdos: Debora, Edcarllos, livros! slides disponíveis!

http://www.ic.uff.br/~boeres/fac.html! Conteúdos: Debora, Edcarllos, livros! slides disponíveis! http://www.ic.uff.br/~boeres/fac.html! 1 Conteúdos: Debora, Edcarllos, livros! slides disponíveis! Unidade central de Processamento realiza operações básicas codificadas em 0s e 1s instrução contém código

Leia mais

2) Escreva um algoritmo que leia um conjunto de 10 notas, armazene-as em uma variável composta chamada NOTA e calcule e imprima a sua média.

2) Escreva um algoritmo que leia um conjunto de 10 notas, armazene-as em uma variável composta chamada NOTA e calcule e imprima a sua média. 1) Inicializar um vetor de inteiros com números de 0 a 99 2) Escreva um algoritmo que leia um conjunto de 10 notas, armazene-as em uma variável composta chamada NOTA e calcule e imprima a sua média 3)

Leia mais

Arquitetura TCP/IP. Apresentado por: Ricardo Quintão

Arquitetura TCP/IP. Apresentado por: Ricardo Quintão Arquitetura TCP/IP Apresentado por: Ricardo Quintão Roteiro Conexões Inter-redes Serviço Universal Rede Virtual (inter-rede ou internet) Protocolos para ligação inter-redes (TCP/IP) Divisão em camadas

Leia mais

3º Ano do Ensino Médio. Aula nº09 Prof. Paulo Henrique

3º Ano do Ensino Médio. Aula nº09 Prof. Paulo Henrique Nome: Ano: º Ano do E.M. Escola: Data: / / 3º Ano do Ensino Médio Aula nº09 Prof. Paulo Henrique Assunto: Funções do Segundo Grau 1. Conceitos básicos Definição: É uma função que segue a lei: onde, Tipos

Leia mais

Aula 01. Breve História dos Computadores Informatiquês O Computador Software vs. Hardware. Introdução à Informática. Prof. Fábio Nelson.

Aula 01. Breve História dos Computadores Informatiquês O Computador Software vs. Hardware. Introdução à Informática. Prof. Fábio Nelson. Aula 01 Breve História dos Computadores Informatiquês O Computador Software vs. Hardware Slide 1 de Qual é a origem etimológica da palavra COMPUTADOR? Computador procede do latim computatore. Ao pé da

Leia mais

TECNOLOGIA EM MECATRÔNICA INDUSTRIAL CONTROLADORES LÓGICOS PROGRAMÁVEIS

TECNOLOGIA EM MECATRÔNICA INDUSTRIAL CONTROLADORES LÓGICOS PROGRAMÁVEIS TECNOLOGIA EM MECATRÔNICA INDUSTRIAL CONTROLADORES LÓGICOS PROGRAMÁVEIS TECNOLOGIA EM MECATRÔNICA INDUSTRIAL CONTROLADORES LÓGICOS PROGRAMÁVEIS Autor: Prof. Heliliano Carlos Sartori Guedes prof.helilianoguedes@gmail.com

Leia mais

Árvores de Decisão Matemática Discreta

Árvores de Decisão Matemática Discreta Bruno Duarte Eduardo Germano Isolino Ferreira Vagner Gon Árvores de Decisão Matemática Discreta 28/04/2011 Serra IFES Definição de Árvores de Decisão: Arvore de Decisão é uma árvore em que seus nós internos

Leia mais

Requisitos de Software

Requisitos de Software Requisitos de Software Ian Sommerville 2006 Engenharia de Software, 8ª. edição. Capítulo 6 Slide 1 Objetivos Descrever requisitos funcionais e não funcionais Explicar como os requisitos de software podem

Leia mais

Introdução à Programação. Funções e Procedimentos. Prof. José Honorato F. Nunes honoratonunes@gmail.com

Introdução à Programação. Funções e Procedimentos. Prof. José Honorato F. Nunes honoratonunes@gmail.com Introdução à Programação Funções e Procedimentos Prof. José Honorato F. Nunes honoratonunes@gmail.com RESUMO DA AULA SUB-ROTINAS: Procedimentos Funções Escopo de variáveis Parâmetros Prof. José Honorato

Leia mais

INF1005: Programação I. Algoritmos e Pseudocódigo

INF1005: Programação I. Algoritmos e Pseudocódigo INF1005: Programação I Algoritmos e Pseudocódigo Tópicos Principais Definição de Algoritmo Exemplos Básicos Formas de representação Condicionais Exemplos com Condicionais Repetições Exemplos com Repetições

Leia mais

Circuitos Aritméticos

Circuitos Aritméticos Circuitos Aritméticos Semi-Somador Quando queremos proceder à realização de uma soma em binário, utilizamos várias somas de dois bits para poderemos chegar ao resultado final da operação. Podemos, então,

Leia mais

Teoria dos Grafos. Valeriano A. de Oliveira Socorro Rangel Departamento de Matemática Aplicada. antunes@ibilce.unesp.br, socorro@ibilce.unesp.

Teoria dos Grafos. Valeriano A. de Oliveira Socorro Rangel Departamento de Matemática Aplicada. antunes@ibilce.unesp.br, socorro@ibilce.unesp. Teoria dos Grafos Valeriano A. de Oliveira Socorro Rangel Departamento de Matemática Aplicada antunes@ibilce.unesp.br, socorro@ibilce.unesp.br Grafos e Algoritmos Preparado a partir do texto: Rangel, Socorro.

Leia mais

Universidade Paulista

Universidade Paulista Universidade Paulista Ciência da Computação Sistemas de Informação Engenharia de Software Análise Estruturada Sergio Petersen 22/4/2012 Sumário 1. Técnicas de Análise 3 2. Principais Autores 3 3. Análise

Leia mais

Aritmética Computacional. Prof. Leonardo Barreto Campos 1

Aritmética Computacional. Prof. Leonardo Barreto Campos 1 Aritmética Computacional Prof. Leonardo Barreto Campos Sumário Introdução; Representação de Números Inteiros; Aritmética de Números Inteiros; Representação de Números de Ponto Flutuante; Aritmética de

Leia mais

Informação-Prova de Equivalência à disciplina de: Aplicações Informáticas B. 1. Introdução. Ensino Secundário. Ano letivo de 2011/12

Informação-Prova de Equivalência à disciplina de: Aplicações Informáticas B. 1. Introdução. Ensino Secundário. Ano letivo de 2011/12 Informação-Prova de Equivalência à disciplina de: Aplicações Informáticas B Ensino Secundário Ano letivo de 2011/12 1. Introdução O presente documento visa divulgar as características da prova de exame

Leia mais

Avaliação e Desempenho Aula 1 - Simulação

Avaliação e Desempenho Aula 1 - Simulação Avaliação e Desempenho Aula 1 - Simulação Introdução à simulação Geração de números aleatórios Lei dos grandes números Geração de variáveis aleatórias O Ciclo de Modelagem Sistema real Criação do Modelo

Leia mais

Capítulo VI Circuitos Aritméticos

Capítulo VI Circuitos Aritméticos Capítulo VI Circuitos Aritméticos Introdução No capítulo anterior estudamos a soma e subtração de números binários. Neste capítulo estudaremos como as operações aritméticas de soma e subtração entre números

Leia mais

Microsoft Excel INTRODUÇÃO PARTE 1 SUMÁRIO

Microsoft Excel INTRODUÇÃO PARTE 1 SUMÁRIO Microsoft Excel INTRODUÇÃO PARTE 1 SUMÁRIO 01-) Barra de Ferramentas do Excel... 2 02-) Planilha... 3 a-) Linha... 3 b-) Coluna... 3 c-) Célula... 4 d-) Movimentando-se pela planilha... 4 e-) Tamanho da

Leia mais

Algoritmos e Estruturas de Dados I. Variáveis Indexadas. Pedro O.S. Vaz de Melo

Algoritmos e Estruturas de Dados I. Variáveis Indexadas. Pedro O.S. Vaz de Melo Algoritmos e Estruturas de Dados I Variáveis Indexadas Pedro O.S. Vaz de Melo Por que índices são importantes? Como uma loja de sapatos artesanais deve guardar os seus produtos? 1 2 3 4 Tamanhos entre

Leia mais

OBSERVAÇÕES: EXERCÍCIOS

OBSERVAÇÕES: EXERCÍCIOS OBSERVAÇÕES: 1. Esta lista de exercícios poderá ser resolvida individualmente ou em grupos de 2 pessoas. 2. A lista possui 25 exercícios, destes você deve responder os 5 primeiros exercícios e os outros

Leia mais

Lista 4 Introdução à Programação Entregar até 07/05/2012

Lista 4 Introdução à Programação Entregar até 07/05/2012 Lista 4 Introdução à Programação Entregar até 07/05/2012 1. Um vendedor necessita de um algoritmo que calcule o preço total devido por um cliente. O algoritmo deve receber o código de um produto e a quantidade

Leia mais

1º período. Conhecer os algarismos que compõem o SND (0, 1, 2, 3, 4, 5, 6, 7, 8, 9). Diferenciar algarismos e números. e vice-versa.

1º período. Conhecer os algarismos que compõem o SND (0, 1, 2, 3, 4, 5, 6, 7, 8, 9). Diferenciar algarismos e números. e vice-versa. 1º período Os números naturais: Sistema de Numeração Decimal. (SND) Um pouco de história: sistema de numeração dos romanos. Os números naturais Sistema de Numeração Decimal (SND). Unidades e dezenas. Unidades,

Leia mais

Introdução a Programação Aula 01

Introdução a Programação Aula 01 Introdução a Programação Aula 01 Prof. Bruno Crestani Calegaro Curso de Sistemas de Informação ELC1064 Lógica e Algoritmo 1 O que é um computador? Máquina programável genérica Constituído por: Processador

Leia mais

Documento de Requisitos do Sistema SISFOTO Sistema de gerenciamento de eventos fotográficos Versão 1.0

Documento de Requisitos do Sistema SISFOTO Sistema de gerenciamento de eventos fotográficos Versão 1.0 SISFOTO Sistema de Gerenciamento de Eventos Fotográficos do Sistema SISFOTO Sistema de gerenciamento de eventos fotográficos Versão 1.0 Histórico de Alterações Data Versão Descrição Autor 17/10/2014 1.0

Leia mais

Árvore de Decisão. 3. Árvore de Decisão

Árvore de Decisão. 3. Árvore de Decisão Árvore de Decisão 3. Árvore de Decisão A árvore de decisão consiste de uma hierarquia de nós internos e externos que são conectados por ramos. O nó interno, também conhecido como nó decisório ou nó intermediário,

Leia mais

Linux e Computação Científica

Linux e Computação Científica Linux e Computação Científica Sumário Motivação Noções de Sistemas Operacionais Ferramental Computação Científica Motivação Financeira Linux é livre Desempenho Linux gerencia melhor recursos de processdor,

Leia mais

Escola Secundária c/3º CEB José Macedo Fragateiro. Curso Profissional de Nível Secundário. Componente Técnica. Disciplina de

Escola Secundária c/3º CEB José Macedo Fragateiro. Curso Profissional de Nível Secundário. Componente Técnica. Disciplina de Escola Secundária c/3º CE José Macedo Fragateiro Curso Profissional de Nível Secundário Componente Técnica Disciplina de Sistemas Digitais e Arquitectura de Computadores 2009/2010 Módulo 2: Álgebra e Lógica

Leia mais

Excel. Profª Leticia Lopes Leite

Excel. Profª Leticia Lopes Leite Excel Profª Leticia Lopes Leite 1 Introdução A planilha eletrônica consiste em uma poderosa ferramenta de apoio à gestão e ao processo de tomada de decisão, dentro de uma empresa ou na vida particular.

Leia mais

Ciclo com Contador : instrução for. for de variável := expressão to. expressão do instrução

Ciclo com Contador : instrução for. for de variável := expressão to. expressão do instrução Métodos de Programação I 2. 27 Ciclo com Contador : instrução for identificador downto for de variável := expressão to expressão do instrução UMA INSTRUÇÃO (SIMPLES OU COMPOSTA) Neste caso o ciclo é repetido

Leia mais

PROGRAMAÇÃO ORIENTADA A OBJETO INTRODUÇÃO

PROGRAMAÇÃO ORIENTADA A OBJETO INTRODUÇÃO PROGRAMAÇÃO ORIENTADA A OBJETO INTRODUÇÃO A Programação Orientada ao Objeto deu seus primeiros passos ainda na década de 70. A sua origem vem da linguagem Simula (Simula Language) e como o nome indica

Leia mais

Representações de caracteres

Representações de caracteres Representações de caracteres Sistemas de Numeração A necessidade de contar é algo que acompanha o ser humano desde tempos imemoriais. Sistemas de Numeração Usando o polegar para indicar em cada dedo a

Leia mais

Matrizes. matriz de 2 linhas e 2 colunas. matriz de 3 linhas e 3 colunas. matriz de 3 linhas e 1 coluna. matriz de 1 linha e 4 colunas.

Matrizes. matriz de 2 linhas e 2 colunas. matriz de 3 linhas e 3 colunas. matriz de 3 linhas e 1 coluna. matriz de 1 linha e 4 colunas. Definição Uma matriz do tipo m n (lê-se m por n), com m e n, sendo m e n números inteiros, é uma tabela formada por m n elementos dispostos em m linhas e n colunas. Estes elementos podem estar entre parênteses

Leia mais

CAPÍTULO 2 SISTEMAS DE NUMERAÇÃO E CÓDIGOS

CAPÍTULO 2 SISTEMAS DE NUMERAÇÃO E CÓDIGOS CAPÍTULO 2 SISTEMAS DE NUMERAÇÃO E CÓDIGOS Código BCD; Comparação entre BCD e Binário; Circuitos Digitais para BCD; Código Gray; Código ASCII; Detecção de erros pelo método de Paridade O que é um Código?

Leia mais

Engenharia de Software

Engenharia de Software Engenharia de Software - 2ª Lista de Exercícios - Questões Discursivas Questão 1) O que você entende por processo de software e qual a sua importância para a qualidade dos produtos de software? Qual a

Leia mais

COMPUTAÇÃO. O estudante deve ser capaz de apontar algumas vantagens dentre as seguintes, quanto à modalidade EaD:

COMPUTAÇÃO. O estudante deve ser capaz de apontar algumas vantagens dentre as seguintes, quanto à modalidade EaD: COMPUTAÇÃO QUESTÃO DISCURSIVA 1 O estudante deve ser capaz de apontar algumas vantagens dentre as seguintes, quanto à modalidade EaD: (i) flexibilidade de horário e de local, pois o aluno estabelece o

Leia mais

Caminho de Dados e Controle. Prof. Leonardo Barreto Campos 1

Caminho de Dados e Controle. Prof. Leonardo Barreto Campos 1 Caminho de Dados e Controle Prof. Leonardo Barreto Campos 1 Sumário Introdução; Convenções Lógicas de Projeto; Construindo um Caminho de Dados; O Controle da ULA; Projeto da Unidade de Controle Principal;

Leia mais

1. Escreva um programa em Pascal que leia três valores inteiros e mostre-os em ordem crescente. Utilize seleção encadeada.

1. Escreva um programa em Pascal que leia três valores inteiros e mostre-os em ordem crescente. Utilize seleção encadeada. Universidade Estadual Vale do Acaraú Curso: Engenharia Civil Disciplina: Programação de Computadores Prof. Hudson Costa Instruções: as equipes de cinco componentes (ou elementos) deverão fazer apenas 30

Leia mais

Planilha Eletrônica - Microsoft Excel -

Planilha Eletrônica - Microsoft Excel - Planilha Eletrônica - Microsoft Excel - Aula do dia 20 de junho de 2012 Profª. Me. Valéria Espíndola Lessa valeria-lessa@uergs.edu.br Sobre Planilhas Eletrônicas São usadas para: Trabalhar com lista de

Leia mais

MATA49 Programação de Software Básico

MATA49 Programação de Software Básico MATA49 Programação de Software Básico Leandro Andrade leandrojsadcc.ufba.br PROF. LEANDRO ANDRADE 1 Arquitetura dos processadores Intel PROF. LEANDRO ANDRADE 2 Um pouco de história... Como sabemos

Leia mais

Linguagens e códigos digitais

Linguagens e códigos digitais 2 Linguagens e códigos digitais SUMÁRIO DO VOLUME LINGUAGENS E CÓDIGOS DIGITAIS 1. Operações com números naturais 5 2. Números positivos e negativos 10 3. Média aritmética simples e ponderada 12 4. Plano

Leia mais

Módulo IV Programação Visual Basic. Programação

Módulo IV Programação Visual Basic. Programação Módulo IV Visual Basic 1 Vamos considerar os seguintes elementos e estruturas fundamentais que são habitualmente usados em programação: Tipos de Dados Operadores Atribuições Entrada e Saída de Dados Estruturas

Leia mais

CAMPUS DE GUARATINGUETÁ FACULDADE DE ENGENHARIA. Introdução à Programação em C. Algoritmos: Estruturas de Repetição. Prof. Dr. Galeno.J.

CAMPUS DE GUARATINGUETÁ FACULDADE DE ENGENHARIA. Introdução à Programação em C. Algoritmos: Estruturas de Repetição. Prof. Dr. Galeno.J. Unesp UNIVERSIDADE ESTADUAL PAULISTA CAMPUS DE GUARATINGUETÁ FACULDADE DE ENGENHARIA Introdução à Programação em C Algoritmos: Estruturas de Repetição Prof. Dr. Galeno.J. de Sena Departamento de Matemática

Leia mais

Universidade Federal do Espírito Santo. Programação II. CT IX - Sala 201 Departamento de Informática Centro Tecnológico

Universidade Federal do Espírito Santo. Programação II. CT IX - Sala 201 Departamento de Informática Centro Tecnológico Universidade Federal do Espírito Santo Programação II Prof.ª Claudia Boeres (boeres@inf.ufes.br) Filipe Mutz (filipemtz@gmail.com) CT IX - Sala 201 Departamento de Informática Centro Tecnológico Universidade

Leia mais

Exercícios: Vetores e Matrizes

Exercícios: Vetores e Matrizes Universidade Federal de Uberlândia - UFU Faculdade de Computação - FACOM Lista de exercícios de programação em linguagem C Exercícios: Vetores e Matrizes 1 Vetores 1. Escreva um programa que leia 10 números

Leia mais

1.1. Definição do Problema

1.1. Definição do Problema 13 1 Introdução Uma das principais preocupações de área de engenharia de software diz respeito à reutilização [1]. Isso porque a reutilização no contexto de desenvolvimetno de software pode contribuir

Leia mais

LINGUAGEM SQL Linguagem usada em SGBD para: Definir estrutura de dados; Modificar dados em um banco de dados; Especificar restrições de segurança; Rea

LINGUAGEM SQL Linguagem usada em SGBD para: Definir estrutura de dados; Modificar dados em um banco de dados; Especificar restrições de segurança; Rea BANCO DE DADOS Prof. Fabiano Taguchi http://fabianotaguchi.wordpress.com fabianotaguchi@hotmail.com SQL A Structed Query Language foi desenvolvida em 1974 nos laboratório da IBM em San José na Califórnia,

Leia mais