Disciplina: Iniciação à Computação Prof. Augusto Antonio Pinheiro Neto Curso de Licenciatura em Matemática – UFPBVIRTUAL [email protected] Ambiente Virtual de Aprendizagem: Moodle www.ead.ufpb.br Site da UFPBVIRTUAL www.virtual.ufpb.br Telefone UFPBVIRTUAL (83) 3216 7257 Carga Horária: 60 horas Créditos: 04 Ementa • • Componentes básicos de um computador Linguagem de programação Descrição Nesta disciplina apresentaremos a configuração básica de um computador, de modo que o aluno tenha uma compreensão geral de sua arquitetura. Estudaremos em seguida uma linguagem de programação imperativa, permitindo capacitar o aluno na programação de resoluções de problemas numéricos. Objetivos Ao final da disciplina o aluno deverá ser capaz de: compreender a arquitetura básica de um computador, programar as operações de entrada\saída, programar operações básicas como: atribuição de valores a variáveis ou constantes, permutação de valores entre duas variáveis, uso de acumuladores de soma e de produtos, programar os diversos tipos de decisão e de repetição (laços), compreender os principais tipos de dados inerentes à linguagem estudada, trabalhar com funções (elemento de programação) elaborar algoritmos de resolução de diversos tipos de problemas numéricos, programar esses algoritmos 197 Unidades Temáticas Integradas Unidade I • Componentes básicos de um computador Unidades funcionais básicas • Unidades de Entrada e/ou Saída • Dispositivos de Entrada de Dados • Dispositivos de Saída de Dados • Dispositivos de Entrada e Saída de Dados • Memória principal • Tipos de memória • Unidade Central de Processamento o Unidade de Controle o Unidade de Aritmética e Lógica o Registradores o Clock • Sistemas Operacionais • Os arquivos em informática Unidade II Algoritmos • Algoritmos • Abordagem dividir para conquistar • Algoritmo TrocarPneuFurado o Refinamento do passo 1 o Refinamento do passo 1.5 • Algoritmo TrocarLâmpadaQueimada • Exercícios • Solucionando um problema • o Fases de resolução o Fase de implementação Atitude é tudo Unidade III Introdução à programação Conceitos básicos • Memória e variáveis • Dando nome às variáveis • Tipos de variáveis • Declarando uma variável • Atribuindo valor a uma variável 198 • Constantes • Exibindo o conteúdo de uma variável • Recebendo uma entrada • Teste Unidade IV Operações aritméticas básicas Primeiros passos • A divisão • O módulo • Cálculo entre variáveis • Abreviações: incremento e decremento • Outras abreviações • A biblioteca matemática • Teste Unidade V Praticando entrada e saída Algumas considerações sobre a função scanf • Algumas considerações sobre a função printf Unidade VI • Estruturas de controle: condicionais A estrutura if ... else o O teste if o O teste else o O teste else if • Alguns erros comuns • Para melhor compreender as condições • A estrutura switch • Condições condensadas • Teste Unidade VII Estruturas de controle: repetições • O laço while o O laço do .. while o O laço for o Teste 199 Unidade VIII Funções • Qual o objetivo de uma função? • A estrutura de uma função • Criando uma função • Chamando uma função • Mais exemplos • o Conversão Celsius/Fahrenheit o Transformar segundos em horas, minutos e segundos o Área de um retângulo o Um menu Teste Unidade IX • Ponteiros Um pouco mais sobre as funções o o • A memória e os endereços Endereço e valor Utilização de ponteiros o Criar um ponteiro o Enviar um ponteiro a uma função o Outro modo de enviar um ponteiro a uma função • Um problema estranho? • Resumo • Teste Unidade X Arrays • Os arrays e a memória • Declarando um array • Como acessar um elemento • Atribuindo valores a um array • Arrays e ponteiros • Listando um array • Inicializando um array • Passagem de arrays como parâmetros • Arrays de duas ou mais dimensões • Teste 200 Unidade I – Componentes básicos de um computador 1. Situando a temática Os computadores estão cada vez mais presentes no dia-a-dia dos cidadãos. Mesmo aqueles que não os utilizam diretamente têm essas máquinas ligadas às suas vidas. Basta nascer para que as informações referentes ao novo cidadão passem a fazer parte de algum banco de dados, por exemplo, o do hospital onde a criança nasceu. Ao registrá-la, o pai (a mãe) estará contribuindo para alimentar outro banco de dados, o do cartório de registros civis. Ao iniciar seus estudos oficiais, novas informações serão transferidas para os computadores, desta vez os da escola. E assim será por toda a vida, imagino. A informação deve ser armazenada e disseminada no momento necessário. Seria bom então entender um pouco sobre essas máquinas que nos acompanham por toda a vida. 2. Problematizando a temática Os computadores são sistemas eletrônicos de processamento de dados, compostos de uma parte física, denominada hardware e de uma parte lógica, denominada software. O hardware é formado por um conjunto de funções lógicas, geralmente associadas a um ou mais equipamentos físicos, capazes de executar determinadas tarefas. Iniciamos os nossos estudos sobre programação apresentando uma noção sobre a configuração básica de um computador. 3. Conhecendo a temática 3.1 Unidades funcionais básicas As funções lógicas associadas ao hardware de um computador são geralmente agrupadas em blocos chamados de unidades funcionais. Um computador possui as seguintes unidades funcionais básicas, compostas de circuitos eletrônicos específicos: UC Entrada Teclado Mouse Leitor de disquete Scanner Tela sensível Leitor de CD, DVD UAL CPU Saída Registradores Monitor (Vídeo) Impressora Gravador de disquete Gravador de CD, DVD Memória Principal 3.2 Unidades de Entrada e/ou Saída As unidades de Entrada são blocos funcionais compostos por circuitos eletrônicos com a finalidade específica de transmitir dados do meio exterior para a memória do computador. As unidades de Saída realizam a operação inversa, transmitem dados da memória do computador para o meio exterior. A transmissão física desses dados se dá por meio de dispositivos especiais. Na figura acima são listados alguns dispositivos que têm a finalidade de realizar essas tarefas. Os dispositivos que realizam tarefas relacionadas à função de entrada são conhecidos como dispositivos de entrada de dados. De modo análogo, os dispositivos que realizam tarefas relacionadas à função de saída são conhecidos como dispositivos de saída de dados. Alguns dispositivos são capazes de executar entrada e saída de dados. 3.3 Dispositivos de Entrada de Dados Exemplos de alguns dispositivos de entrada de dados: teclado, mouse, scanner. Existem outros, como microfone (dispositivo de entrada de som), monitores de vídeo sensíveis ao toque, light-pen, joystick, etc. 201 Teclado Mouse Scanner 3.4 Dispositivos de Saída de Dados Os dispositivos mais comuns de saída de dados são as impressoras e os monitores de vídeo. Entretanto, existem outros dispositivos nesta classe como plotters (traçadores de gráfico), projetores, caixas de som, etc. projetor Impressora Monitor 3.5 Dispositivos de Entrada e Saída de Dados Alguns dispositivos possuem as funções de entrada e saída. Eles são bem conhecidos e podemos citar, por exemplo, leitor/gravador de discos (disquetes, CD’s, DVD’s, etc), Leitor/Grava dor de CD’s Leitor/Grava dor de Leitor/Grava dor de HD’s 3.6 Memória Principal Por memória, compreende-se todo dispositivo capaz de guardar qualquer dado ou informação. A memória principal de um computador é o local onde estão armazenados os programas e dados que são utilizados durante um processamento. Todo dado ou programa a ser processado deve estar na memória principal do computador. Caso esses dados estejam armazenados em disquetes, Cd’s ou HD’s, será necessário, primeiro, transferi-los para a memória principal. A memória principal do computador possui a característica de ser volátil, isto é, caso o computador seja indevidamente desligado (falta de energia elétrica, por exemplo), todo o seu conteúdo será perdido, ou seja, tornar-se-á inacessível. A memória principal de um computador existe na forma de “pentes de memória” como mostrado nas figuras a seguir. Atualmente, os pentes de memória, mais encontrados, instalados nos microcomputadores são os de 256 MBytes, 512 MBytes, ou 1GBytes. Memória Memória 256 MB 3.7 Tipos de memória As memórias estão representadas em duas grandes categorias de chips: RAM (random access memory): são chips de memória que podem ser lidos e/ou gravados pela CPU. A memória principal é deste tipo. 202 ROM (read only memory) : são chips de memória que podem apenas ser lidos pela CPU. Uma memória ROM é permanente e sua gravação é feita pelo fabricante do computador, ou pelo fabricante de memórias. Um conjunto específico de programas, necessários à inicialização de um computador, denominado BIOS, localizado na placa mãe do computador, está encapsulado em uma memória deste tipo. 3.8 Unidade Central de Processamento A unidade central é o elemento funcional central de todo computador. É o “coração”, ou melhor, o “cérebro” do computador. Nesta unidade são realizadas todas as operações. Todas as informações tratadas pelo computador transitam por esta unidade funcional. A unidade central é também conhecida por CPU (Central Processing Unit), Unidade Central de Processamento e é dividida classicamente em três partes: I - Unidade de Controle, responsável pela extração das instruções da memória do computador e por sua análise. Essa unidade controla dois registros especiais denominados CONTADOR DE INSTRUÇÃO (PC) que contém o endereço de memória da próxima instrução a ser executada e REGISTRO DE INSTRUÇÕES que contém a instrução extraída da memória. É a unidade de controle que gerencia todos os eventos necessários à operação do computador. II - Unidade de Aritmética e Lógica (UAL) controla um conjunto de registros que devem conter os códigos dos operandos e do operador, necessários à realização de uma operação aritmética ou lógica. Exemplos de operações aritméticas: adição, subtração, multiplicação ou divisão de números. Exemplos de operações lógicas: comparação de dois valores alfabéticos, operações envolvendo operadores os booleanos, AND, OR e NOT. Nos dias atuais, uma CPU está completamente embutida em uma pastilha, denominada chip, de dimensões reduzidas. As primeiras CPUs integradas em um único chip foram a CPU 4004 e a CPU 8008. Hoje esses chips são mais conhecidos no mercado pelo nome de processador ou microprocessador. Em 1974 a Intel fabricou o 8080, o primeiro microprocessador a ser usado em larga escala nos chamados "computadores pessoais". Antes dele, os microcomputadores eram usados apenas em laboratórios científicos, em fábricas e em universidades. III - Registradores, memórias especiais, de alta velocidade, localizadas no interior de um microprocessador, enquanto a memória principal é externa a este. Cada registrador possui uma função específica. Dentre os registradores destacam-se os seguintes: Contador de programa (PC - Program Counter), que aponta para a próxima instrução a executar. Registro de instrução (IR - Instruction Register) que armazena a instrução em execução. Outros registros que permitem o armazenamento de resultados intermediários. Clock Clock é um circuito oscilador que tem a função de sincronizar a velocidade de transferência de dados entre duas partes durante um processamento. Por exemplo, a transferência de dados entre o processador e a memória principal. Essa velocidade de transferência (freqüência) é medida em ciclos por segundo, ou Hertz. A velocidade de acesso dentro do processador é maior que na memória principal. Os processadores Pentium100, Pentium II-300, acessam a memória principal a 66 MHz. 3.9 Sistemas Operacionais Sistemas operacionais são conjuntos de programas que permitem explorar (fazer funcionar) o computador. Atualmente os sistemas operacionais mais conhecidos são Windows e Linux. Um computador é um conjunto de materiais inertes. São as diversas camadas de software que fazem com que ele funcione. Quando ligado à rede elétrica, uma primeira camada de software, sempre a mesma, é posta em ação. Esta camada foi gravada diretamente no hardware: é a BIOS (Basic Input/Output System). Esta camada realiza certo número de verificações e de testes (presença de diversos periféricos, volume de memória, etc). Após isso o sistema operacional assume o controle. É o sistema operacional que permite aos humanos dialogar com os computadores. Segundo o grau de convivialidade do sistema, esse diálogo será mais ou menos amigável, podendo ser textual, gráfico ou uma 203 mistura desses. A partir do sistema operacional o usuário pode então lançar os aplicativos (programas específicos). 3.10 Os arquivos em informática Todo arquivo é visto e tratado do mesmo modo pelo sistema operacional em vista de seu armazenamento em disco. Tecnicamente, um arquivo é um conjunto de bits formando uma entidade identificada por um nome e uma extensão seguindo a forma: nome.ext. Somente a sua extensão é capaz de determinar a natureza e assinalar ao sistema operacional o que esse arquivo é capaz de fazer. Assim, um arquivo .doc é reconhecido como um programa Word, um arquivo .xls é reconhecido como um arquivo do Excel e esses softwares são carregados na memória para execução. Alguns arquivos de imagens (.jpeg ou .jpg, .gif, .png, etc.) necessitam de um visualizador de imagens instalado no computador para que essas imagens possam ser exibidas na tela. Por padrão, está previsto que esses arquivos podem ser abertos no Internet Explorer, capaz de exibi-los a partir de páginas da Internet. Dependendo do sistema operacional, existem regras com mais ou menos restrições para dar nomes aos arquivos. No MS-DOS, antigo sistema operacional, os nomes de arquivo deveriam ter no máximo 8 caracteres com uma extensão de três caracteres. A partir do Windows 95 os nomes dos arquivos passaram a ter até 256 caracteres (incluindo o caminho onde se encontra o arquivo, por exemplo, C:\programas\C\programa1.exe). 204 Unidade II - Algoritmos 1. Situando a temática Programar é uma ciência ou uma arte? A computação é uma ciência, porem programar está mais para arte. A programação requer certo dom. Mas isto podemos desenvolver com esforço e paciência. Assim como aprendemos a pintar, também podemos aprender a programar. Alguns requisitos são necessários, é verdade. Para desenvolvermos programas que realizem algumas « proezas » matemáticas, é necessário que tenhamos algum conhecimento para matemático, senão, como programá-las? Qualquer que seja a natureza de nossos programas, existem algumas regras básicas a serem seguidas. Primeiramente, é preciso entender o enunciado do problema a ser resolvido e então partirmos para encontrar uma solução. O estudo do domínio de um problema é conhecido no mundo da computação como análise. A apresentação de uma solução para esse problema faz parte do que se chama projeto. Como os domínios (álgebra, geometria, trigonometria, etc) são vastos e variados, procuramos seguir uma metodologia de resolução de problemas. É o que faremos a seguir no estudo dos algoritmos. 2. Problematizando a temática Suponhamos o seguinte problema: trocar o pneu furado de um carro (você já passou por este tipo de problema?). Parece que estamos diante de um problema simples, mas tentemos explicar a um robô como proceder para realizar tal tarefa. Veremos um enfoque denominado “dividir para conquistar”, que nos ajudará bastante nessa e outras tarefas. 3. Conhecendo a temática 3.1 Algoritmos Definição: conjunto finito, ordenado e não ambíguo de passos necessários para realizar uma tarefa. Esse conjunto apresenta as seguintes características: • possui um ponto de parada, isto é, sua execução é finita; • recebe dados de entrada e, em função desses, produz dados de saída. Algoritmo ≅ Receita de bolo Um algoritmo pode ser correto ou não. Um algoritmo correto produz uma saída correta e pára, enquanto que um algoritmo incorreto produz uma saída incorreta ou não pára. 3.2 Abordagem dividir para conquistar Essa abordagem, muito utilizada em informática, consiste em dividir o problema inicial em vários subproblemas, recursivamente. Cada subproblema é, logicamente, mais simples que o problema inicial. Esse procedimento de divisão continua até que sejam encontrados subproblemas, que possam ser resolvidos de forma simples ou trivial. 3.3 Algoritmo Trocar Pneu Furado 1. 2. 3. 4. 5. pegue o macaco e levante o carro retire o pneu furado pegue o estepe, coloque-o na roda e aperte os parafusos abaixe o carro e reaperte os parafusos guarde o pneu furado e o macaco Refinamento do passo 1 1.1 remova o macaco do porta-malas 1.2 coloque o macaco sob o carro, próximo ao pneu furado 1.3 desaperte os parafusos da roda 1.4 insira a manivela no macaco (ou monte-o de acordo com o modelo do macaco) 1.5 coloque um calço sob o carro para impedi-lo de se mover 1.6 levante o carro com o macaco até que haja espaço suficiente para colocar o estepe 205 Refinamento do passo 1.5 1.5.1 se o carro estiver em uma ladeira, de frente para o topo desta, então coloque o calço atrás de um pneu em bom estado caso contrário, coloque o calço na frente de um pneu em bom estado O passo 1.6 revela a existência de um procedimento repetitivo: enquanto não houver espaço suficiente para colocar o estepe, faça o seguinte: levante o carro com o macaco. Outro procedimento que pode ser associado ao passo 1.6 é: repita o levantamento do carro com o macaco até que haja espaço suficiente para colocar o estepe. 3.4 Algoritmo Trocar Lâmpada Queimada 1. Remova a lâmpada queimada 2. Coloque a nova lâmpada Refinamento 1. 2. 3. 4. 5. 6. 7. Posicione uma escada em baixo da lâmpada queimada Escolha uma nova lâmpada com a mesma voltagem da queimada Suba na escada até que a lâmpada possa ser alcançada Gire a lâmpada queimada no sentido anti-horário até que ela se solte Posicione a nova lâmpada no soquete Gire-a no sentido horário até que ela se firme Desça da escada 1. Posicione uma escada em baixo da lâmpada queimada 2. Selecione uma nova lâmpada para a substituição 2.1. Se a voltagem não for a mesma da lâmpada queimada, repita os passos abaixo até encontrar uma que sirva 2.1.1. Descarte a lâmpada selecionada 2.1.2. Selecione uma nova 3. Repita até que a lâmpada possa ser alcançada 3.1.1. Suba um degrau da escada 4. Repita até que a lâmpada fique livre do soquete 4.1.1. Gire a lâmpada no sentido anti-horário 5. Posicione a nova lâmpada no soquete 6. Repita até que a lâmpada esteja firme 6.1.1. Gire a lâmpada no sentido horário 7. Desça da escada Esses passos e refinamentos seriam importantes para a realização dessas tarefas por uma máquina. Um ser humano faz essas coisas intuitivamente, mas as máquinas... 3.5 Exercícios 1 – Elabore um algoritmo para fazer pipoca em uma panela de fogão, usando manteiga, sal e milho. 2 – Elabore um algoritmo para realizar uma chamada telefônica local. 3 – Elabore um algoritmo para realizar uma chamada telefônica de longa distância (interurbana). 4 – Elabore um algoritmo que simule sua saída de casa pela manhã. Comece com o passo “dormindo na cama” e inclua todas as suas atividades matinais. 5 – Elabore um algoritmo para calcular as raízes reais de uma equação do 2º grau. Se a equação não tiver raízes reais, isto deve ser mencionado como resposta. 206 3.6 Solucionando um problema Fase de resolução do problema Problema Passo único Algoritmo Fase de implementação da solução Programa de computador Fase de resolução – elaboração de um algoritmo para resolver o problema proposto, incluindo testes para verificar se a solução é boa. Fase de implementação – ao encontrarmos o algoritmo adequado à solução do problema, precisamos codificá-lo em uma linguagem de programação. Geralmente, essa codificação é fácil de ser feita desde que o programador compreenda bem a sintaxe e a semântica dos comandos da linguagem escolhida para a implementação. 3.7 Atitude é tudo Luis é o tipo de cara que você gostaria de conhecer. Ele estava sempre de bom humor e sempre tinha algo de positivo para dizer. Se alguém lhe perguntasse como ele estava, a resposta seria logo: Se melhorar estraga. Ele era um gerente especial em um restaurante, pois seus garçons o seguiam de restaurante em restaurante apenas pelas suas atitudes. Ele era um motivador nato. Se um colaborador estava tendo um dia ruim, Luis estava sempre dizendo como ver o lado positivo da situação. Fiquei tão curioso com seu estilo de vida que um dia lhe perguntei: Você não pode ser uma pessoa positiva todo o tempo. Como faz isso? Ele me respondeu: A cada manhã, ao acordar, digo para mim mesmo: Luis, você tem duas escolhas hoje: Pode ficar de bom humor ou de mau humor. Eu escolho ficar de bom humor. Cada vez que algo ruim acontece, posso escolher bancar a vítima ou aprender alguma coisa com o ocorrido. Eu escolho aprender algo. Toda vez que alguém reclamar, posso escolher aceitar a reclamação ou mostrar o lado positivo da vida. Certo, mas não é fácil - argumentei. É fácil sim, disse-me Luis. A vida é feita de escolhas. Quando você examina a fundo, toda situação sempre oferece escolha. Você escolhe como reagir às situações. Você escolhe como as pessoas afetarão o seu humor. É sua a escolha de como viver sua vida. Eu pensei sobre o que o Luis disse e sempre lembrava dele quando fazia uma escolha. Anos mais tarde, soube que Luis cometera um erro, deixando a porta de serviço aberta pela manhã. Foi rendido por assaltantes. Dominado, enquanto tentava abrir o cofre, sua mão tremendo pelo nervosismo, desfez a combinação do segredo. Os ladrões entraram em pânico e atiraram nele. Por sorte foi encontrado a tempo de ser socorrido e levado para um hospital. Depois de 18 horas de cirurgia e semanas de tratamento intensivo, teve alta ainda com fragmentos de balas alojadas em seu corpo. Encontrei Luis mais ou menos por acaso. Quando lhe perguntei como estava, respondeu: Se melhorar estraga. Contou-me o que havia acontecido perguntando: Quer ver minhas cicatrizes? Recusei ver seus ferimentos, mas perguntei-lhe o que havia passado em sua mente na ocasião do assalto. A primeira coisa que pensei foi que deveria ter trancado a porta de trás, respondeu. Então, deitado no chão, ensangüentado, lembrei que tinha duas escolhas: poderia viver ou morrer. Escolhi viver! Você não estava com medo? Perguntei. Os para-médicos foram ótimos. Eles me diziam que tudo ia dar certo e que ia ficar bom. Mas quando entrei na sala de emergência e vi a expressão dos médicos e enfermeiras, fiquei apavorado. Em seus lábios eu lia: "Esse aí já era". Decidi então que tinha que fazer algo. O que fez? Perguntei. Bem, havia uma enfermeira que fazia muitas perguntas. Perguntou-me se eu era alérgico a alguma coisa. Eu respondi: "sim". Todos pararam para ouvir a minha resposta. Tomei fôlego e gritei: - "Sou alérgico a balas!" Entre risadas lhes disse: - "Eu estou escolhendo viver, operem-me como um ser vivo, não como morto." Luis sobreviveu graças à persistência dos médicos, mas também graças à sua atitude. Aprendi que todo dia temos opção de viver plenamente. Afinal de contas, "ATITUDE É TUDO". 207 Agora você tem duas opções: 1. Após ler estas aulas, esquecê-las. 2. Dar o melhor de si, com todas as dificuldades que possam aparecer e escolher aprender. Boa escolha e bons estudos. (Texto de autor desconhecido, obtido na Internet.) 4. Avaliando o que foi construído Pensar por etapas, foi o que vimos. Não adianta querer que os computadores se comportem como os humanos. Eles fazem, realmente, operações aritméticas e de comparações, muito mais rápido do que nós o fazemos, mas é preciso dizer-lhes, felizmente, o deve ser feito, como e quando. Os algoritmos se resumem nisso, informar ao computador de modo inequívoco essas formas e temporalidade. 5. Referências 1. Salvetti, Dirceu D., Barbosa, Lisbete M., Algoritmos. Pearson Education do Brasil, 1998 2. Pinheiro Neto. Augusto A., Notas de aula, 2001. 208 Unidade III - Iniciação a Programação 1. Situando a temática Poderíamos dizer que a palavra programar vem do Latim programmeus. Você acredita? Pois bem, é verdade. Mas o que nos interessa é que a palavra programar, em nosso contexto, significa simplesmente: escrever programas de computador. Os programas solicitam aos computadores a realização de ações. Veremos como dialogar com um computador, procurando ensinar-lhe como realizar essas ações, ou seja, vamos aprender a codificar em uma linguagem de programação um algoritmo apresentado como solução para um problema. 2. Problematizando a temática O computador é uma máquina estranha, é o mínimo que se pode dizer. Ele entende apenas aquilo que está codificado somente com 0’s e 1’s. Esse tipo de linguagem é denominado linguagem de máquina, ou de baixo nível. Imaginemos, por exemplo, que a operação de somar seja representada por 11001100 e que desejemos somar 2 com 3. Para transmitir essa ordem a um computador, temos que escrever uma instrução equivalente a esta: 11001100 00000010 00000011. Felizmente, as linguagens de programação atuais estão bem mais próximas das linguagens naturais, aquelas que os homens utilizam para se comunicarem. Existem diversas linguagens de programação que podem ser utilizadas na implementação de algoritmos. Estudaremos a seguir uma das mais importantes hoje em dia, a linguagem C. 3. Conhecendo a temática 3.1 Conceitos básicos Linguagens de alto nível As linguagens atuais com as quais se escreve programas para computadores são estruturadas (possuem estruturas de controle), mais próximas da linguagem natural e com certo formalismo matemático. Essas linguagens são chamadas de linguagens de Alto Nível. Exemplos de linguagens de alto nível. C, C++, Java, Pascal, Visual Basic, Delphi, PHP, etc... Programa fonte e programa executável Os programas escritos em linguagens de alto nível, aqueles escritos pelo programador, são geralmente chamados de programas fonte, pois ali são codificados os conhecimentos necessários para a solução de determinada tarefa. Uma vez traduzido para a linguagem de máquina, aquela dos 0’s e 1’s, o programa resultante recebe o nome de programa executável. Tradutores Tradutores são programas especiais que traduzem um programa fonte para linguagem de máquina. Alguns programas são ditos compilados e outros interpretados. O que é isso? Existem tradutores que, quando da tradução do programa fonte, traduzem cada linha e a executam imediatamente. Nesse caso diz-se que houve uma interpretação do programa fonte e esse tipo de tradutor é denominado interpretador. Quando a tradução se dá por inteiro e tão somente após seu término o programa é executado, diz-se que ocorreu uma compilação e esse tipo de tradutor é denominado compilador. Programas compilados são mais rápidos do que programas interpretados. Importante! Existe um compilador diferente para cada linguagem de alto nível. Isso é lógico. As linguagens sendo diferentes, não se traduz C do mesmo modo que Delphi, por exemplo. 209 Fazendo um esquema gráfico do que acabamos de dizer temos: Programa escrito em linguagem de Alto Nível Programa Fonte Programa de tradução para linguagem binária Programa traduzido em linguagem de máquina Programa Executável Compilador/ Interpretador Fonte Veremos em seguida que mesmo para uma única linguagem como C, por exemplo, existem vários compiladores diferentes. Há compiladores escritos pela Microsoft, GNU, etc. Felizmente esses compiladores são quase idênticos. Linguagens de altíssimo nível São fáceis de utilizar, possuem vários elementos de linguagem que podem ser arrastados para a área do programa e o seu código é então gerado automaticamente, digamos "grande público", como Visual Basic. Essas linguagens apresentam alguns inconvenientes: elas custam caro e são limitadas à plataforma. Se o compilador for voltado à plataforma Windows, por exemplo, não espere fazer um programa desenvolvido com ele funcionar sob Linux ou Macintosh. Enfim, não podemos fazer tudo que desejarmos com esse tipo de linguagem. Perceba que estamos de certa forma limitados. As linguagens de alto nível são um pouco mais difíceis que Visual Basic, mas com uma linguagem deste tipo, como C ou C++, aprendemos muito mais sobre programação e funcionamento dos computadores. Após aprendermos uma linguagem deste tipo, somos capazes de aprender mais facilmente uma outra linguagem de programação. O programador torna-se então mais autônomo. Em particular C e C++ são linguagens muito populares. Elas são utilizadas para programar uma grande parte dos softwares mais conhecidos. Enfim, programando em C ou C++, o programador está livre de comprar softwares caríssimos. Programar em C ou C++ é, geralmente, gratuito. Qualidades de um programador Um programador deve ter certas qualidades: • paciência: se um programa não funcionar da primeira vez, é preciso saber perseverar! • bom senso: não é preciso ser tão forte em matemática, mas isso não impede de se ter que refletir! • Calma: não se dá tapa no computador . Isso não faz o programa funcionar. Em resumo, não são necessários conhecimentos especiais para programar. Alguém fraco em matemática pode até se sair bem, o essencial é a paciência para refletir. O mínimo que deve estar disponível para um programador: • • • Um editor de texto para escrever o código fonte do programa. Em teoria um software como o Bloco de Notas do Windows, “edit” em linha de comando do DOS ou o “vi” em Linux é suficiente. O ideal é dispor de um editor de texto inteligente que colore o código, o que permite acompanhá-lo mais facilmente. Um compilador para transformar (compilar) o código fonte em binário. Um depurador (debugger) para ajudar a encontrar alguns erros no programa, erros de utilização da memória do computador (erros de lógica do programador não são detectados). É comum utilizarmos um programa 3-em-1 que combina editor de texto, compilador e depurador. Esses programas são também chamados de IDE "Integrated Development Environment". Exemplos de IDE para programar em C: Bloodshed Dev-C++ e Code::Blocks. Em nosso curso escreveremos os programas fonte no bloco de notas (notepad) se o sistema operacional for Windows ou no KEdit (ou outro editor de texto que acompanhe a versão instalada) se o sistema operacional for Linux. Isto nos restringe aos conhecimentos da linguagem de programação, o que é mais interessante 210 para quem está iniciando. Após conhecermos bem a sintaxe e a semântica da linguagem, usar uma interface tipo IDE será fácil. Acredite. 3.2 Memória e variáveis Para que tenhamos uma idéia, eis aqui diferentes tipos de memória existentes dentro de um computador, da mais rápida para a mais lenta: 1. Os registros: uma memória ultra-rápida situada dentro do processador. 2. A memória cache: faz a ligação entre os registros e a memória RAM. 3. A memória RAM: a memória principal com a qual trabalhamos geralmente. 4. O disco rígido: que você conhece seguramente, é onde guardamos os arquivos para posterior uso. Ao dizermos que uma memória é lenta, estamos considerando a escala de tempo de um computador. Afinal, 8 milissegundos são quase imperceptíveis aos humanos, mas é muito tempo para acessar um disco rígido, por exemplo! A memória principal Fotografando de perto a memória principal (RAM) de um computador, não vemos grandes coisas. É mais importante sabermos como ela funciona internamente. Vejamos o esquema abaixo. Ele é bastante simples, mas é do necessitamos saber para programar. Endereço Valor 0 145 1 3.8028322 2 0.8272555 3 39014768 ... ... n-1 940.5118 Como podemos ver é preciso distinguir duas coisas: • Endereços: um endereço é um número que permite ao computador se orientar na memória. Tudo começa pelo endereço 0 (justamente no início da memória) e termina no endereço 2n-1, onde n é o tamanho do barramento de endereços. Quanto mais memória existir no computador mais endereços existirão e mais coisas poderão ali ser armazenadas. • Em cada endereço podemos armazenar um único valor (um número): o computador armazena na memória esses números para poder recuperá-los depois, quando necessário. O endereçamento de memória é uma técnica que permite ao processador acessá-la. A interface utilizada para isto é, na maioria das vezes, chamada barramento (BUS), um conjunto de fios dedicados a uma utilização particular. No caso do acesso à memória, esse barramento é chamado de barramento de endereços. A memória RAM só pode armazenar números. Como fazer então para armazenar outros caracteres (palavras, por exemplo)? Boa pergunta. Na verdade, as letras não passam de números para os computadores! Uma frase é uma simples sucessão de números. Existe uma tabela, denominada ASCII, que faz a correspondência entre 211 os números e as letras e outros caracteres. Essa tabela diz, por exemplo, que o número 65 corresponde à letra A. Variáveis Em C, uma variável é constituída de dois atributos: • Um valor: o número que ela armazena, por exemplo, 25. • Um nome: que permite reconhecê-la. Programando em C não é preciso lembrar o endereço de memória que guarda certo valor (ufa!), basta lembrar o nome da variável que contém aquele valor. O compilador fará a conversão entre o nome da variável e o endereço onde ela está armazenada (ainda bem). Isso parece um pouco confuso no momento (qual o interesse de armazenar um número se é preciso lembrar o seu endereço), mas, tudo vai tomar sentido em seguida. 3.3 Dando nome às variáveis Em C cada variável deve ter um nome. Se uma variável guarda o valor da vida média de um cidadão, nós bem que gostaríamos de chamá-la “vida média”. Infelizmente isso não é possível. Existem certas restrições. Veja a seguir as regras que devemos seguir ao darmos um nome a uma variável. • • • • Só podem existir letras (minúsculas ou maiúsculas), dígitos e “sublinha” _. O nome de uma variável deve começar por uma letra. Espaços são proibidos Acentos não são permitidos (ãóôéàê etc). Enfim, e isto é muito importante, a linguagem C (como C++) faz diferença entre as letras maiúsculas e minúsculas. Então Brasil, bRasil e brasil são nomes que representariam três variáveis distintas. Veja alguns exemplos de nomes válidos de variáveis: Vidamedia, vida_media, vida_Media, VidaMedia, num_de_telefone, bola215, etc Cada programador tem sua maneira própria de nomear as variáveis. Uma maneira bastante utilizada é: • Começar os nomes de variáveis por maiúsculas. • Se houver várias palavras no nome da variável, colocar uma maiúscula no início de cada palavra. VidaMedia, por exemplo. 3.4 Tipos de variáveis Um computador como se pode constatar nada mais é do que uma grande máquina de calcular. Ele só sabe trabalhar com números. O problema é que existem vários tipos de números. • Números inteiros positivos: 45; 578; 2457 • Números inteiros negativos: -87; -513 • Números decimais, isto é, números com vírgula: 1,7741; 9810,7 • Números decimais negativos: -98,45; -1,0045 Nosso pobre computador precisa de ajuda. Quando pedimos a ele para armazenar um número, devemos dizer-lhe de que tipo é o número. Não é que ele não seja capaz de reconhecê-lo, mas isso o ajuda a organizarse e não perder tempo e espaço de memória com algo que poderia ser evitado. Quando declaramos uma variável em um programa devemos então indicar o seu tipo. Vejamos os principais tipos de variáveis existentes em C: Nome do tipo Valores que podem ser armazenados Char -128 a 127 Int -2 147 483 648 a 2 147 483 647 Long -2 147 483 648 a 2 147 483 647 Float -3.4 x 1038 a 3.4 x 1038 Double -1.7 x 10308 a 1.7 x 10308 Esses não são todos os tipos, mas já dá pra começar. 212 Os três primeiros tipos permitem armazenar números inteiros (1, 2, 3, 4...). Os dois últimos permitem armazenar números decimais (13.8, 16.911...). Os tipos float e double permitem armazenar números bastante grandes. Se você não está habituado com as potências de dez, então imagine que o tipo double permite armazenar o número 1 seguido por 308 zeros, isto é, 100000000000000.... (eu não vou aqui escrever 308 zeros ) Observemos que int e long são parecidos. Antes não era assim (um int era menor que um long), mas hoje as memórias evoluíram e temos bastante espaço para escrever números grandes sem grandes preocupações com a memória. Por razões de compatibilidade, a linguagem C guarda os dois tipos. Na prática utilizamos principalmente os tipos char, long e double. Você verá que na maior parte do tempo manipularemos números inteiros (tanto melhor, pois são mais fáceis de utilizar). Atenção com os números decimais. O computador não conhece a vírgula decimal. Em seu lugar utilizaremos o ponto. Não devemos escrever 273,45, mas 273.45! Isso ainda não é tudo! Além dos tipos que armazenam números inteiros (char, int, long,...) já vistos, existem outros ditos « unsigned » (sem sinal) que podem armazenar apenas números positivos. Para utilizá-los basta escrever a palavra unsigned antes do nome do tipo. unsigned char 0 a 255 unsigned int 0 a 4 294 967 295 unsigned long 0 a 4 294 967 295 A vantagem de utilizar tipos unsigned é que podemos armazenar números duas vezes maiores (char pára em 128 enquanto unsigned char vai até 255, por exemplo). Por que criar três tipos para os números inteiros? Um só tipo não seria suficiente? Sim, mas no início foram criados vários tipos para economizar memória. A tabela a seguir mostra o espaço necessário para armazenar os tipos. Nome do tipo Número de bits Char 8 Int 32 Long 32 Float 32 double 64 3.5 Declarando uma variável Uma declaração de variável é muito simples, agora que você sabe tudo que é necessário. Basta indicar, na ordem: 1. 2. 3. 4. O tipo da variável que vai ser criada Teclar espaço Indicar o nome desejado para a variável Enfim, não esqueça o ponto-e-vírgula Por exemplo, se desejamos criar uma variável de nome TotalDePontos do tipo long, basta digitar a linha seguinte: long TotalDePontos; Isso é tudo! Outros exemplos: long NotaDeFisica; double TotalRecebido; unsigned long NumeroDeEspacosNaLeituraDeUmTextoLongo; Atenção! A declaração das variáveis deve ser feita no início das funções. O texto no quadro abaixo é um código de programa escrito em C. Um programa em C é formado por vários trechos de código com um 213 formato especial denominado função. Todo programa possui uma função principal chamada main, essa é a única no programa abaixo. Não se preocupe, aprenderemos aos poucos a escrever um programa. #include <stdio.h> int main(int argc, char *argv[]) { // Início da função long TotalDePontos; system("PAUSE"); return 0; // Fim da função } Se executarmos este programa ficaremos pasmos ao ver que ele não faz nada. Algumas explicações Na verdade se passa alguma coisa, mas não é possível vê-la. Quando a execução do programa chega à linha de declaração da variável TotalDePontos ele « solicita » ao computador permissão para utilizar um espaço de sua memória principal. Se tudo vai bem, o computador responde « pois não, faça como se estivesse em sua casa ». O único problema que poderia acontecer seria não haver mais espaço disponível na memória do computador, mas isso será um caso extremo. É possível declarar mais de uma variável em uma mesma linha do programa. Por exemplo, long a, b, c; Isso criará três variáveis de tipo long denominadas a, b e c. 3.6 Atribuindo valor a uma variável É tudo que há de mais simples. Para atribuir o valor 8 à variável TotalDePontos basta escrever: TotalDePontos = 8; De modo geral, um comando de atribuição tem a forma: variável = expressão; Nosso programa completo se parece agora com #include <stdio.h> int main(int argc, char *argv[]) { // Início da função long TotalDePontos; TotalDePontos = 8 ; system("PAUSE"); return 0; // Fim da função } Nada é exibido na tela do computador até agora. Em algum lugar da memória do computador o valor 8 é armazenado, esse lugar é conhecido no programa pelo nome da variável TotalDePontos e isso é tudo. O valor de uma nova variável Eis uma questão muito interessante. Quando se declara uma variável, qual o seu valor inicial? De fato, quando o computador lê a linha long TotalDePontos; ele reserva uma pequena parte da memória para armazenar um valor do tipo long. Mas que valor é esse? Existe um valor padrão (0, por exemplo)? Bem, a resposta é não. Não existe um valor padrão. O computador reserva um espaço na memória para armazenar um novo valor, mas não apaga o que já existia naquele espaço. Imediatamente a variável recebe o valor que se encontrava no espaço que foi reservado, que pode ser qualquer coisa. Assim, o melhor é inicializar as variáveis quando de suas declarações, sempre que possível. Procedemos assim: long TotalDePontos = 8; Agora a variável foi declarada e recebeu imediatamente o valor 8. Foi apagado o que poderia haver na posição de memória que lhe foi alocada e colocado ali o valor 8. 3.7 Constantes Ocorre às vezes que se tem a necessidade de armazenar um mesmo valor durante toda a execução de um programa. O valor de pi (3.14), por exemplo. Isso quer dizer que uma vez declarada e inicializada, a variável deverá conservar o seu valor impedindo que qualquer pessoa possa modificá-lo. Essas variáveis particulares recebem o nome de constantes, por motivos óbvios. Para declarar uma constante em um programa deve-se utilizar a palavra “const” antes do tipo e obrigatoriamente, inicializar a variável. Exemplo de declaração de uma constante: const long PI = 3.14; Não é uma obrigação, mas por convenção, escreve-se os nomes das constantes com letras maiúsculas. Isso nos permite distinguir facilmente os nomes de constantes dos de variáveis. Fora isso uma constante é utilizada do mesmo modo que uma variável. 214 Ao tentar modificar o valor de uma constante no corpo de um programa o compilador acusará um erro (por quê?). 3.8 Exibindo o conteúdo de uma variável Utilizamos a função printf para exibir valores na tela do computador. printf("Total de pontos = %d ", TotalDePontos); O símbolo especial % seguido da letra d é um especificador de formato. Este especificador permite a exibição de um número inteiro na tela do computador. Existem vários especificadores de formato que serão visto mais tarde. Por enquanto usaremos os especificadores para números inteiros e números decimais. Símbolo Significado %d Número inteiro (ex: 56) %f Número decimal (ex: 13.75) Por enquanto saiba que, se quisermos exibir um número inteiro, precisamos utilizar o especificador %d e para exibir um número decimal precisamos utilizar o especificador %f. Os especificadores de formato são geralmente utilizados dentro de um texto explicativo, como no exemplo anterior. É preciso, entretanto indicar para a função printf qual o valor a ser exibido. Para isso é preciso escrever o nome da variável que contém esse valor após o texto contendo o especificador, após uma vírgula. Vejamos outros exemplos: printf("O valor de a eh %d ", a1); printf("Foram encontrados %d resultados ", r); No primeiro exemplo %d indica que um valor inteiro deve ser exibido na tela do computador e esse valor está armazenado em a1. No segundo exemplo esse valor está armazenado na variável r. Por enquanto veremos os programas prontos e aprenderemos cada vez mais sobre suas estruturas. Pelo que temos visto podemos tirar algumas conclusões: • Um programa começa sempre com a instrução #include<stdio.h>, isto indica que uma biblioteca de funções de nome stdio deve ser incluída automaticamente no programa e isto é feito, não se preocupe. • O cabeçalho da função principal será sempre int main(). • Após // vem um comentário, que não é executado, serve apenas de documentação. • system("PAUSE"); fixa a tela de modo que possamos ver a resposta. • return 0 indica um fim normal do programa. #include <stdio.h> int main() { long TotalDePontos = 5; // No início o alunos possui 5 pontos printf("Voce possui %d pontos \n", TotalDePontos); printf("**** H U M M M ****\n"); // O aluno errou uma questão TotalDePontos = 4; // Ele acaba de perder um ponto! printf("Ah, agora voce so tem %d pontos!\n\n", TotalDePontos); system("PAUSE"); return 0; } Este programa produz a seguinte saída: Voce possui 5 pontos **** H U M M M **** Ah, agora voce so tem 4 pontos! Pressione uma tecla para continuar... 215 Exibir várias variáveis em um único printf É possível exibir o valor de várias variáveis em um único printf. Para tanto, é suficiente indicar %d ou %f onde se deseja que os valores sejam impressos e em seguida indicar as variáveis que contêm esses valores, obedecendo à ordem em que os especificadores foram indicados, separadas por vírgula. Exemplo. printf("Voce tem %d pontos e faltam %d perguntas", TotalDePontos, p); 3.9 Recuperando uma entrada As variáveis vão começar a ficar mais interessantes agora. Vamos solicitar ao usuário (aquele que está operando o computador) que digite um número. Vamos aprender a recuperar essa entrada e a armazenar em um determinado lugar na memória principal. Para solicitar ao usuário que entre com uma determinada informação pelo teclado utiliza-se a função scanf. Essa função se parece com a função printf. Deve-se colocar um %d ou %f, entre aspas, para indicar um inteiro ou um decimal a ser digitado. Veja um exemplo. scanf("%d", &idade); Coloca-se apenas %d (ou %f) entre as aspas. Não esquecer de colocar o símbolo & antes do nome da variável que vai receber o valor. E por que esse & antes do nome da variável? É preciso que você acredite em mim. Eu explicarei isso mais tarde. Não o faço agora para não embolar o meio de campo. Quando o programa encontra um scanf ele faz uma pausa e espera que o usuário digite um valor. Esse valor será armazenado na variável idade. Veja um pequeno exemplo. #include <stdio.h> int main() { long idade = 0; // inicializa a variável com 0 printf("Qual a sua idade? "); scanf("%d", &idade); // Solicita a entrada de idade com scanf printf("Ah ! Voce tem %d anos!\n\n", idade); system("PAUSE"); return 0; } Saída: Qual a sua idade? 20 Ah! Voce tem 20 anos! Pressione uma continuar... tecla para Pronto! Você compreendeu o princípio. Graças à função scanf o computador pode interagir com o usuário. Observe que nada nos impede de digitar outra coisa que um número inteiro: • Se digitarmos um número decimal como 34.56, por exemplo, ele será truncado imediatamente e só a parte inteira é armazenada. Neste caso, 34 é armazenado na variável idade. • Se digitarmos letras, a variável não muda de valor. Permanece então com o valor 0 (valor com que foi inicializada). Se a variável não tivesse sido inicializada, o programa imprimiria qualquer coisa. Percebem a importância da inicialização das variáveis? 3.10 Testes 1. Quando se declara uma variável, qual memória é utilizada? ( ) Registros ( ) Memória cache ( ) Memória RAM ( ) Disco rígido 2. Qual memória não é “apagada” quando o computador é desligado? ( ) Registros ( ) Memória cache ( ) Memória RAM ( ) Disco rígido 216 3. Qual desses não é um nome válido de variável? ( ) scanf ( ) coordenadaDaJanela ( ) desconto_total 4. Qual dos tipos de dado abaixo permite armazenar o número 15.3? ( ) char ( ) long ( ) double ( ) int 5. Qual dos tipos abaixo permite armazenar o número - 1458 ? ( ) long ( ) unsigned int ( ) unsigned double 6. Se a variável “SaldoBanco” é um long que vale 150345 reais (pensemos grande), o que a linha de código printf("Voce tem %d reais na conta ", saldoBanco); exibirá na tela? ( ) Voce tem %d reais na conta ( ) Voce tem 150345 reais na conta ( ) Voce tem d reais na conta saldoBanco 7. Para armazenar um número decimal digitado no teclado, qual das linhas de código abaixo é a boa? ( ) scanf("%f", num); ( ) scanf("%d", num); ( ) scanf("%f", &num); ( ) scanf("%d", &num); 4. Avaliando o que foi construído Foram apresentados alguns conceitos básicos necessários para a construção de um programa: memória, variáveis e constantes, além das instruções capazes de realizar as operações de entrada e saída desses elementos de programação. Uma operação de entrada consiste em receber um valor via teclado e armazenálo na memória principal do computador. A operação correspondente à saída consiste em exibir, geralmente na tela do computador, um valor que esteja armazenado em uma variável. Vimos também que as linguagens utilizadas pelos programadores atualmente são classificadas em um nível tão mais alto quanto mais afastadas estiverem da codificação binária. Assim é que linguagens como C, Pascal, Ada, Java, PHP, etc., são denominadas de alto nível. 5. Referências 1. Ascencio, Ana F G., Campos, Edilene A V., Fundamentos da Programação de Computadores: Algoritmos, Pascal, C/C++ e Java, 2ª edição, Pearson Education, 2007. 2. Hutchison, Robert C., Just, Steven B., Programming using the C language. MGraw-Hill Book Company, 1988. 3. Kernighan, Brian W., Ritche, Dennis M., Le langage C, Masson, 1986. 217 Unidade IV - Operações Aritméticas Básicas 1. Situando a temática Nesta unidade aprenderemos a realizar a maior parte dos cálculos que um computador pode executar. A idéia é retomar o conceito de variáveis visto na unidade anterior e realizar as operações básicas que as envolvem como somar, multiplicar, permutar, etc. 2. Problematizando a temática Já sabemos que o computador é uma calculadora muito potente com a qual podemos realizar as seguintes operações básicas com muita simplicidade: • Adição • Subtração • Multiplicação • Divisão • Módulo (eu explicarei o que é isto) Quando desejamos fazer outras operações mais complicadas, temos que as programar, isto é, explicar ao computador como fazê-las. Felizmente existe uma biblioteca de funções matemáticas já prontas que acompanha a linguagem C. Neste caso não é necessário programar as operações, mas simplesmente conhecer a interface das funções correspondentes, ou seja, saber como utilizá-las. 3. Conhecendo a temática 3.1 Primeiros passos Vamos começar pela adição. Para fazer uma adição se utiliza o símbolo + (sem brincadeira). O resultado de um cálculo, geralmente, deve ser armazenado em uma variável. Atenção! Na maioria das vezes, representaremos apenas a parte do programa fonte que estiver sendo discutida. Lembre-se de completar o código do programa começando por #include <stdlib.h> #include <stdio.h> .............................. Vejamos um exemplo de um trecho de programa que realiza uma soma. .............................. long resposta = 0; .............................. resposta = 13 + 7; Não é necessário ser um gênio em matemática para saber que a variável “resposta” armazenará o valor 20 após a execução desse trecho de programa. Claro que nada é exibido na tela como resposta. Para ver o resultado na tela, como já sabemos, é necessário utilizar a função printf. Em algum lugar do programa poderíamos ter printf(“17 + 3 = %d”, resposta);. O resultado na tela seria 17 + 3 = 20. Isso é tudo para a adição. Para as outras operações o procedimento é o mesmo apenas o símbolo do operador muda. Os operadores são representados pelos símbolos: • Adição: + • Subtração: • Multiplicação: * • Divisão: / • Módulo: % As três primeiras operações são realizadas de modo natural, conhecido por quase todos. Apenas as operações de divisão e de módulo merecem um pouco de reflexão, pois existem algumas considerações específicas relacionadas com os tipos envolvidos. 218 3.2 A divisão As divisões funcionam normalmente em um computador quando não sobram restos. Por exemplo, 8/2 é 4, mas 7/2 que deveria ser 3.5 é 3 e sobra um resto 1. Observe................................. long resposta = 0; ............................. resposta = 7/2; printf ("7 / 2 = %d", resposta); .............................. Saída. 7/2=3 Eis aqui um problema. Sabemos que 7/2=3.5, mas o resultado apresentado foi 3. Ocorre que a variável “resposta” é do tipo long, portanto inteiro, por isso o computador truncou o resultado ficando apenas com a sua parte inteira. Se “resposta” tivesse sido declarada como float ou double o resultado seria 3.500000. Faça as modificações no programa fonte e execute-o novamente para ver o resultado. Se quisermos que o computador mostre o bom resultado (3.5) será necessário transformar os números 7 e 2 para decimais. Isso é feito escrevendo-se 7.0 e 2.0 (são os mesmos números, mas agora o computador vai entendê-los como números decimais e, portanto efetuará uma divisão de decimais). Na verdade, basta escrever um dos números no formato decimal (com ponto decimal). A linguagem C reconhecerá a expressão como sendo do tipo decimal e efetuará a operação corretamente. ..................................... double resposta = 0; .................................... resposta = 7.0 / 2.0; printf ("7 / 2 = %f", resposta); ...................................... Saída 7 / 2 = 3.500000 Observe que o especificador de formato utilizado foi %f, para números decimais. Mas atenção, às vezes se deseja realmente que o resultado seja um valor inteiro, nesse caso o resultado inicial (3) estaria correto. Esta propriedade dos números inteiros é muito importante. Tenha sempre em mente que 5/2=2, 8/5=1, 6/7=0, etc. Para obter um resultado decimal é necessário que os operandos sejam números decimais: 5.0/2.0=2.5, 8.0/5.0=1.6, 6.0/7.0=0.857143, etc. De fato, na divisão 5/2, o computador responde à questão “quantas vezes o número 2 está dentro do número 5?” Resposta, 2 (duas vezes). Você agora deve estar se perguntando “como fazer então para recuperar o resto da divisão”? Esse é o nosso próximo passo. 3.3 Primeiros passos O módulo é uma operação matemática que permite se obter o resto da divisão de dois números inteiros. Talvez seja uma operação menos conhecida que as anteriores, mas de grande importância nos cálculos realizados por computadores. Como mostrado anteriormente, o operador de módulo é representado por %. Veja os exemplos: • 7%2=1 ler: o resto da divisão de 7 por 2 • 28 % 5 = 3 ler: o resto da divisão de 28 por 5 • 10 % 2 = 0 ler: o resto da divisão de 10 por 2 Ao executar a operação 7%2, o computador calcula o resto da divisão de 7 por 2, que é 1. A operação 10%2 fornece como resultado 0, pois esse é o resto da divisão de 10 por 2, ou seja, a divisão é exata. 219 3.4 Cálculo entre variáveis O interessante, agora que sabemos as cinco operações básicas, é utilizá-las entre várias variáveis. Nada nos impede de escrever, resposta = num1 + num2, por exemplo, onde num1, num2 e resposta são variáveis declaradas. Esta linha, obviamente faz a soma das variáveis num1 e num2 e coloca o resultado na variável resposta. Agora as coisas começam a ficar mais interessantes. Você já é capaz de fazer coisas espantosas, como uma calculadora. Creia-me. Imagine um programa que solicita ao usuário a entrada de dois números. Esses dois números deverão ser armazenados em variáveis. Em seguida escreva uma expressão que calcule a soma desses números e armazene o resultado em uma variável chamada resposta. Então exiba o resultado obtido na tela do computador. Experimente escrever esse programa sozinho. Uma possível resposta seria: #include <stdio.h> #include <stdlib.h> int main() { long resposta = 0, num1 = 0, num2 = 0; // ** solicitar num1 e num2 ao usuário ** printf("Entre com o valor de num1: "); scanf("%d", &num1); printf("Entre com o valor de num2: "); scanf("%d", &num2); // ********* Calcular ************ resposta = num1 + num2; // ** Mostrar o resultado na tela ** printf ("%d + %d = %d\n", num1, num2, resposta); system("PAUSE"); return 0; } Saída Entre com o valor de num1: 52 Entre com o valor de num2: 30 52 + 30 = 82 Experimente agora seguir o mesmo procedimento para que a sua calculadora execute as outras operações básicas, ou seja, modifique o programa acima para que ele execute as operações de subtração, multiplicação e divisão. 3.5 Abreviações: incremento e decremento Existem em C técnicas que permitem abreviar a escrita de certas operações. Essas abreviações têm por objetivo agilizar a digitação e tornar o código mais conciso. Incremento Quase sempre, um programador se depara com um problema bem simples, mas de grande importância: aumentar de 1 o valor de certa variável. Imagine que um programa trabalhe com uma variável denominada Total e que se deseje aumentar de 1 o valor dessa variável. Como fazê-lo se o seu valor atual não é conhecido? Solução: Total = Total + 1; O que se passa em um computador ao encontrar uma instrução como esta? Bem, ele sempre avaliará a expressão que se encontra do lado direito da instrução de atribuição (representada pelo símbolo =) e armazenará o seu resultado na variável que se encontra do lado esquerdo. Assim, na unidade de aritmética e lógica (UAL), ele somará 1 ao valor atual da variável Total e substituirá o valor armazenado em Total pelo valor da expressão calculada. Na memória Na UAL Na memória total total Antes 30 30+1 220 Depois 31 Observe que nessa instrução escreve-se duas vezes o nome de uma mesma variável (Total). Os programadores achando isso muito cansativo resolveram então abreviá-la. A instrução seguinte faz exatamente a mesma coisa que a anterior e escreve-se menos: Total++; Este recurso é muito usado nos programas escritos em C. Você terá oportunidade de comprovar este fato. Decremento Esta operação é o inverso daquela denominada incremento. Trata-se de retirar 1 do valor de uma variável. Então, de modo análogo à operação de incremento tem-se: Total = Total - 1; equivalente a Total--; 3.6 Outras abreviações Existem outras abreviações que funcionam sob o mesmo princípio que o de incremento (decremento) visto anteriormente, porem de forma mais geral. Essas abreviações funcionam para todas as operações básicas. A tabela abaixo mostra essas abreviações. Operação total = total +5 total = total -5 total = total *5 total = total /5 total = total %5 Abreviação total += 5 total -= 5 total *= 5 total /= 5 total %= 5 Exemplo. Supor total = 5 inicialmente. long total = 5; total += 4; // total vale 9 total -= 3; // ... total vale 6 total *= 5; // ... total vale 30 total /= 3; // ... total vale 10 total %= 3; // ... total vale 1(pois 10=3*3+1) A vantagem aqui é que podemos utilizar todas as operações básicas e podemos somar, multiplicar, etc por qualquer número. Essas abreviações são importantes quando temos operações repetitivas com essa estrutura em um programa. Todavia, o incremento é a abreviação mais utilizada. 3.7 A biblioteca matemática Em C existe um conjunto de funções já prontas para uso bastando apenas ao usuário compreender as suas interfaces, o meio de comunicação do programa com essas funções. Esse conjunto de funções foi escrito por outros programadores e servem, de certo modo, para evitar que os programadores atuais reinventem a roda a cada novo programa. Já utilizamos as funções printf et scanf da biblioteca stdio.h. Existem outras bibliotecas de programas, dentre elas uma denominada math.h, que contém diversas funções matemáticas já prontas, isto é, já programadas. Tenha em mente, por exemplo, que a linguagem C não sabe calcular potência. Se escrevermos 43 o computador não saberá executar essa operação, pois ele não sabe o que isso significa. Para a realização desse cálculo é necessário indicar no início do programa a inclusão de uma biblioteca que contenha a função responsável por ele. Essa indicação é feita por: #include <math.h> Uma vez feita essa indicação, podemos utilizar todas as funções dessa biblioteca. As principais funções existentes na biblioteca math são: 221 fabs Esta função retorna o valor absoluto de um número, isto é, |x| (sua notação matemática). O valor absoluto de um número é o seu valor positivo: • Ao passar o valor -78 para a função, ela retornará 78. • Ao passar o valor 175 para a função, ela retornará 175. ........................................................ double absoluto = 0, x=-42; absoluto = fabs(x); // absoluto receberá o valor 42 ........................................................ A função fabs retorna um valor double por isso devemos declarar a variável absoluto (aquela que vai receber o retorno da função) como sendo do tipo double. Existe uma função similar a fabs na biblioteca "stdlib.h", é a função abs. Essa função funciona do mesmo modo que fabs, mas somente para valores inteiros. Devemos então declarar a variável que vai receber o retorno da função como sendo do tipo int. ceil Esta função retorna o menor número inteiro maior que o número decimal que lhe é passado como argumento. Por exemplo, ao passar 18.33 como argumento para a função ela retorna 19. Esta função retorna um valor do tipo double. ......................................................... double teto = 0, valor1 = 18.33; teto = ceil(valor1); // teto valerá 19 //Programa floor, ceil #include <stdio.h> floor Esta função funciona como a int main(void) precedente, mas retorna o { maior inteiro menor do que o double base=0, valor2=18.99; valor decimal passado como float teto=0, valor1=18.33; argumento. Por exemplo, ao teto=ceil(valor1); passar 18.99 como argumento base=floor(valor2); para a função ela retorna 18. printf("Base = %f\n", base); Vejamos um programa que printf("Teto = %f\n", teto); exemplifica as duas funções. printf("Tamanho de base (em bytes) = %d\n", sizeof(base)); printf("Tamanho de teto (em bytes) = %d\n", sizeof(teto)); getch(); return 0; } Outras funções. função pow(n, p) sqrt(n) argumentos (double|float|int, double|float|int) (double|float|int) sin(x) cos(x) tan(x) asin(x) acos(x) atan(x) exp(x) em radianos em radianos em radianos double double double double log(x) log10(x) double double retorna double|float|int (np) double ( n ) double (seno de x) double (cosseno de x) double (tangente de x) double (arco seno de x) double (arco cosseno de x) double (arco tangente de x) double ( e x ) double (loge x) double (log10 x) 3.8 Testes 1. Qual o símbolo de multiplicação na linguagem C? ( )* ( )+ ( )/ ( )- 222 ( )% 2. Qual o resultado da operação 17 % 5? ( )0 ( )1 ( )2 ( )3 ( )4 ( )5 ( ) 15 3. Qual o valor da variável resultado após a instrução resultado = (8 / 3) - 2;? ( ) -2 ( ) 0 ( )1 ( )2 4. Qual o nome da operação seguinte em programação? x++; • ( ) incremento • ( ) argumento • ( ) suplemento 5. Qual o valor da variável “numero” após as operações seguintes? long numero = 4; numero--; numero *= 4; numero %= 12; numero += 1; ( ( ( ( ( )0 )1 )4 ) 12 ) 14 6. Qual das funções abaixo se deve utilizar para que 13.25 seja arredondado para 14? ( ) pow ( ) ceil ( ) floor ( ) sqrt 4. Avaliando o que foi produzido Essas são as operações e funções que provavelmente todos os programadores mais utilizam ao trabalharem com as operações aritméticas. A sua compreensão é importantíssima, pois elas formam a base de programas mais complexos. Teremos oportunidade na seqüência deste curso de utilizá-las como operações elementares. 5. Referências 1. Ascencio, Ana F G., Campos, Edilene A V., Fundamentos da Programação de Computadores: Algoritmos, Pascal, C/C++ e Java, 2ª edição, Pearson Education, 2007. 2. Hutchison, Robert C., Just, Steven B., Programming using the C language. MGraw-Hill Book Company, 1988. 3. Kernighan, Brian W., Ritche, Dennis M., Le langage C, Masson, 1986. 223 Unidade V- Praticando Entrada e Saída 1. Situando a temática As funções que realizam as operações de entrada ou saída não pertencem à linguagem C. A biblioteca padrão de entrada/saída “stdio” contém tudo que é necessário para as operações de entrada ou saída em um programa C. Essa biblioteca é formada por um conjunto de funções que fornecem à linguagem um sistema de interfaces cômodas para a realização das operações de entrada ou saída. 2. Problematizando a temática As funções mais utilizadas para entrada ou saída são: scanf printf Lê valores de variáveis a partir da entrada padrão, geralmente o teclado. Escreve sobre a saída padrão. Na ausência de indicação em contrário, essa saída é a tela do computador. Sempre que um programa precisar de dados iniciais vindos do mundo exterior, esses deverão ser inseridos no programa por uma função de leitura. É isso que faz a função scanf. De modo análogo, os dados produzidos como resultado de processamento interno são exibidos, enviados para o mundo exterior, via a função printf. Vejamos alguns detalhes sobre essas funções. 3. Conhecendo a temática 3.1 Algumas considerações sobre a função scanf Consideremos o comando scanf(“%d”, &n);. Trata-se aqui da leitura de um valor que deverá ser armazenado na variável n. Para administrar corretamente a variável n, a função scanf precisa conhecer o seu tipo. No exemplo acima, o primeiro parâmetro da função, representado pela seqüência de caracteres “%d” indica ao compilador C que a variável é de tipo inteiro (int). Se a variável n pertencesse a um outro tipo, outro caractere seria usado no lugar de d. Essa seqüência de caracteres é conhecida pelo nome “especificador de formato”. O segundo parâmetro da função (&n) indica o endereço na memória do computador, onde a variável n será armazenada. O computador se encarrega de calcular esse endereço. O programador apenas deve escrever o parâmetro &n para que o computador saiba que no programa o valor armazenado naquele endereço será conhecido por n. O endereço de uma variável x é representado, portanto, pela expressão &x. 3.2 Algumas considerações sobre a função printf O primeiro argumento dessa função é sempre uma seqüência de caracteres. Explicamos o funcionamento dessa função com a ajuda de alguns exemplos. 1. printf(“bom dia”); • Quando essa instrução é encontrada, a função printf se encarrega de escrever na tela do computador a seqüência bom dia. 2. printf("Eh agradavel escrever um programa " "em C, \nquando o utilizamos \"corretamente\".\n"); • As aspas no fim da primeira linha e no início da segunda permitem escrever uma seqüência de caracteres em mais de uma linha. Esse comando produz como saída: Eh agradavel escrever um programa em C, quando o utilizamos "corretamente". • A seqüência de caracteres \n diz ao compilador para executar um salto de linha (passar para a próxima linha da tela) • A seqüência de caracteres \" indica o uso de aspas. Nesse caso as aspas farão parte da saída que será impressa. 224 3. printf(“%d”, 5); • A função printf substitui o especificador de formato %d pelo primeiro parâmetro situado após a vírgula, neste caso o inteiro 5. Lembre que o caractere d em um especificador de formato representa um número inteiro. Seja o programa #include <stdio.h> int main() { int altura=185; float peso=80.5; char nome[10]="Joao"; printf("%s mede %d cm e pesa %f kg", nome, altura, peso); getch(); return 0; } 4. printf("%s mede %d cm e pesa %f kg", nome, altura, peso); • Supor que as variáveis nome, altura e peso refiram-se a uma pessoa e valham a seqüência de caracteres “João”, o inteiro 185 e o número real 80.5, respectivamente. A função printf substitui os especificadores de formato respeitando a ordem em os mesmos se encontram na instrução. Assim %s é substituído pelo primeiro parâmetro situado após a vírgula, neste caso a seqüência João, %d é substituído por 185 e %f por 80.5. Note que %s é o especificador de formato para seqüência de caracteres e %f o especificador de formato para números em ponto flutuante (números com ponto decimal). Observemos o seguinte comando: 5. printf("os valores sao \n%5d\n%5d\n%.3f\n", v1, v2, v3); • v1 e v2 são duas variáveis de tipo int que contêm os valores 4 e 256 e v3 é uma variável de tipo float contendo o valor 7.54756, por exemplo. As indicações numéricas permitem precisar o modo como esses valores são mostrados na tela. • %5d: exibe o valor correspondente, um valor inteiro, ocupando 5 posições preenchidas a partir da direita. • %.3f: exibe o valor correspondente como uma variável de tipo float com 3 casas decimais, com arredondamento. A saída é então: 00004 00256 7.548 getch(): aguarda a próxima tecla ser pressionada. Isto permite ao usuário ver na tela o resultado exibido pelo programa. A instrução return 0; ao fim da função main permite enviar o valor 0 ao sistema o que significa, por convenção, que o programa terminou normalmente. O envio desse valor é obrigatório em C, pois a função main é declarada como sendo do tipo int, logo espera um valor desse tipo. 3.3 Vamos praticar um pouco Digitemos o programa abaixo. Antes de executá-lo, procure entender como serão a sua entrada e a sua saída. O objetivo é que ele seja auto-explicativo. 225 /* Este programa lê 3 valores inteiros */ #include<stdio.h> int main() { int a,b,c; printf("digite 3 numeros inteiros separados por espaco\n"); scanf("%d %d %d",&a,&b,&c); printf("\n valor de a = %d",a); printf("\n valor de b = %d",b); printf("\n valor de c = %d\n ",c); getch(); return 0; } Alguns comentários. - algumas palavras estão sem acentuação, pois isto não é permitido na linguagem C, salvo nos comentários; - o texto entre os símbolos /* e */ é um comentário e, portanto, não será compilado; - a primeira chamada à função printf serve para orientar o usuário sobre o que ele deve fazer quando da execução deste programa (o que ele deve fazer mesmo?). Experimente retirar esse comando do programa. Ele funcionará corretamente, mas o usuário saberá o que o computador estará esperando como entrada de dados? - na instrução scanf("%d %d %d",&a,&b,&c); os especificadores de formato (%d) podem ser separados por espaço ou não, isto não tem influência no programa; - a cada especificador de formato na função scanf corresponde, em ordem, um argumento a ser lido do teclado; - as próximas chamadas à função printf servem para imprimir (exibir na tela do computador) os valores das variáveis a, b e c; - no início de cada chamada a printf há uma ordem para o computador mudar de linha (\n); - getch() permite que o usuário veja o resultado na tela (fixa a tela de execução do programa). Experimente retirar esse comando do programa e tire suas próprias conclusões. Na plataforma Moodle você encontrará vários exercícios que permitirão consolidar a aprendizagem sobre entrada e saída de dados. Essas operações são importantíssimas e só a prática leva um programador a se sentir à vontade em sua utilização. Então, mãos à obra. 4. Avaliando o que foi construído As operações de entrada e saída são básicas no desenvolvimento de um programa. São elas que implementam a interface entre o homem e a máquina. Se por um lado é preciso alimentar o computador com dados do mundo exterior a fim de que ele possa realizar alguns processamentos indicados no algoritmo implementado como solução de um problema, por outro precisamos ver os resultado obtidos. Bem formatar esses procedimentos de entrada e saída implica em apresentar um trabalho legível e compreensível. De nada adianta um programa que tenha um bom desempenho nos seus procedimentos internos se não somos capazes de alimentá-los de forma adequada e amigável, com facilidade. 5. Referências 1. Ascencio, Ana F G., Campos, Edilene A V., Fundamentos da Programação de Computadores: Algoritmos, Pascal, C/C++ e Java, 2ª edição, Pearson Education, 2007. 2. Hutchison, Robert C., Just, Steven B., Programming using the C language. Mcgraw-Hill Book Company, 1988. 3. Kernighan, Brian W., Ritche, Dennis M., Le langage C, Masson, 1986. 226 Unidade VI - Estrutura de Controle Condicionais 1. Situando a temática Vimos anteriormente que existem várias linguagens de programação. Algumas delas se parecem muito. A linguagem PHP, por exemplo, foi inspirada na linguagem C. A propósito, a linguagem PHP é muito utilizada na criação de páginas dinâmicas na Internet. Não é o caso da linguagem C. Dando continuidade aos conceitos básicos de programação, veremos agora os condicionais, também conhecidos como « condição » ou « seleção ». Esse conceito é utilizado para selecionar que trecho de um programa deve ser executado sob certas condições. Sem as condições os programas de computador teriam um comportamento "retilíneo", isto é, não seria possível a realização de determinadas operações que envolvem testes de variáveis. 2. Problematizando a temática Uma condição é o resultado de um teste de comparação. Imaginemos que um programa deve executar a função A ou a função B de acordo com o valor da média aritmética calculada a partir de três notas. Se o valor da média for maior ou igual a 7, então deve ser executada a função A, caso contrário deve ser executada a função B. Assim, em que condição deve ser executada a função A? Obviamente essa função deverá ser executada se o resultado da comparação entre a média calculada e o valor 7 for "a média é superior ou igual a 7". Os símbolos de comparação utilizados na linguagem C são listados na tabela abaixo. Símbolo Significado == é igual a > é superior a < é inferior a >= é superior ou igual a <= é inferior ou igual a != é diferente de Atenção especial é necessária para a comparação de igualdade. Note que o símbolo de comparação é formado por dois símbolos de atribuição (==). Um erro comum entre os iniciantes consiste em fazer essa comparação utilizando um único símbolo de atribuição, o que evidentemente não é um teste de igualdade em C. As condições são representadas em C por estruturas de controle ditas estruturas de seleção, implementadas pelas instruções if e switch. 3. Conhecendo a temática 3.1 A estrutura if ... else O teste if Este teste consiste em realizar uma operação de comparação e indicar o que deve ser feito quando o resultado da comparação é verdadeiro. Isto é codificado assim: onde expr é uma expressão de comparação. A parte do programa escrita entre as chaves { e } é denominada um bloco de instruções, também chamada simplesmente bloco. A semântica dessa instrução é: se o valor da expressão expr for True (verdadeiro), então o computador deverá executar o bloco de instruções pertencente à instrução e, em seguida, executar a próxima instrução após o if. Caso o valor da expressão condicional seja False (falso), a próxima instrução após o if será executada. Graficamente, a semântica deste teste é representada assim: if (expr) { Bloco de instruções } próxima instrução ..... expr True False próxima instrução 227 Bloco Problema1. Testar a variável idade, correspondente à idade de uma pessoa. Se o valor dessa idade for maior do que ou igual a 18, imprimir uma mensagem informando esse fato. if (idade>=18) { printf(˝ voce eh maior de idade ˝) ; } próxima instrução; Observação. Quando há somente uma instrução entre as chaves, estas se tornam facultativas. Pode-se então escrever: if(idade>=18) printf(˝ voce eh maior de idade ˝) ; próxima instrução; #include <stdio.h> int main() { long idade = 25; if (idade >= 18) { printf("Voce eh maior de idade!\n"); } system("PAUSE"); // ou getch() return 0; } Eis ao lado um código completo (código1) que pode ser testado. Este código pode servir de base para testar os exemplos dos próximos testes. Não deixe de fazê-los. Uma questão de legibilidade A disposição das chaves não é muito importante. O programa funcionará também se você escrever assim if(idade>=18) {printf("Voce eh maior de idade!\n");}, mas vemos claramente que a disposição anterior é mais legível. Essa é a recomendação do estilo C. O teste else – o uso de else para dizer "caso contrário" Agora que sabemos fazer um teste simples, é hora de ir um pouco mais longe. Se o teste não funcionar, isto é, se o resultado da avaliação da expressão de comparação for False e a solução que está sendo codificada exigir que seja dito explicitamente ao computador o que fazer, trata-se de outro tipo de teste: if ... else. Para codificar este teste basta adicionar após o bloco correspondente ao resultado True, outro bloco, agora precedido da palavra reservada else. Esse bloco é conhecido como bloco else. Por exemplo, Se o resultado da expressão de comparação for verdadeiro (True), será executada a instrução printf("voce eh maior de idade") e a execução do programa fará um salto para "próxima instrução". Se o resultado for falso (False), será executada a instrução printf("voce eh menor de idade") e a execução do programa continuará em "próxima instrução". Observe que apenas um dos blocos de instruções é executado. A execução de um impede a execução do outro. Relembrando! De modo geral, o que é executado quando o resultado da expressão de comparação é verdadeiro denomina-se Bloco True e o que é executado quando esse resultado é falso, denomina-se Bloco False. Um bloco é constituído por uma ou mais instruções. Quando um bloco possui apenas uma instrução, o uso das chaves é facultativo. if (idade>=18) { printf("voce eh maior de idade ") ; } else { printf("voce eh menor de idade") ; } próxima instrução; Graficamente a semântica deste teste é representada assim: .... expr True False Bloco False próxima instrução 228 Bloco True O teste else if ... – para dizer "caso contrário, se ..." Quando o primeiro teste não funcionar e a situação exigir, Bloco False poderá conter outro if ... else. Vejamos o exemplo a seguir. if (idade >= 18) { printf("voce eh maior de idade"); } else if ( idade >= 12 ) { printf("voce eh um adolescente"); } else { printf("Paciencia, voce ainda eh uma criança"); } próxima instrução O computador faz os testes em ordem. 1. se a expressão (idade>=18) é avaliada True, a instrução printf("voce eh maior de idade"); é executada e o controle da execução passa para ˝próxima instrução˝. 2. se a expressão (idade>=18) é avaliada False, o Bloco Else é executado. Notar que esse bloco é um if ... else. Assim, a segunda expressão de comparação (idade > 12) é avaliada. Se True, printf("voce eh um adolescente"); é executada. Se False, printf("Paciencia, voce ainda eh uma criança"); é executada. Em qualquer dos casos, após a execução de printf, “próxima instrução” será executada. Atenção! O ˝else˝ e o ˝else if˝ não são obrigatórios, mas geralmente facilitam o modo de escrever e tornam o programa mais legível. Graficamente a semântica deste teste é representada assim: ..... expr True Bloco True True Bloco True False expr False Bloco False próxima instrução Problema2. Ler um valor inteiro n e imprimir a mensagem "n pertence ao intervalo i", onde i = 1, 2, 3 ou 4 de acordo com a tabela ao lado. Uma solução pode ser o código abaixo. Observar o modo como é feito o teste de pertinência a um intervalo. Este não é o único modo, logo serão vistos outros. Procure entender bem o funcionamento desses testes, pois esse entendimento é indispensável para a seqüência do curso. 229 i 1 2 3 4 intervalo n<0 0 ≤ n < 20 20 ≤ n <100 n >= 100 #include <stdio.h> int main() { long n; printf("Digitar um valor inteiro. \n"); scanf("%d",&n); printf("valor de n = %d\n",n); /**** testar a pertinência a um intervalo ****/ if(n<0) // n pertence ao intervalo 1? {printf("%d pertence ao intervalo 1\n",n);} else if(n<20) // n pertence ao intervalo 2? {printf("%d pertence ao intervalo 2\n",n);} else if (n<100) // n pertence ao intervalo 3? {printf("%d pertence ao intervalo 3\n",n);} else {printf("%d pertence ao intervalo 4\n",n);} printf("Saida do IF\n"); system("pause"); // ou getch(); return 0; } Teste com várias condições Para realizar testes com várias condições, é necessário o emprego de alguns conectivos lógicos. Consideremos uma afirmação que, sem ambigüidade, apenas pode ser verdadeira ou falsa. Uma afirmação desse tipo é denominada uma proposição. Pois bem, os conectivos lógicos são empregados na realização de operações sobre proposições. Alguns exemplos de proposições: • 3 é maior do que 2 • 5 é maior do que 10 (não precisa ficar assustado, esta é uma proposição falsa) • Há vida fora da Terra (mesmo que não se consiga provar, o resultado dessa afirmação só pode ser verdadeiro ou falso, logo se trata de uma proposição) Existem algumas afirmações que não são proposições, por exemplo, “esta afirmação é falsa”. As proposições são objetos de estudo de um ramo da matemática denominado Cálculo Proposicional. Não há necessidade de preocupação neste momento com a teoria das proposições, pois as que serão vistas neste texto são de compreensão natural e imediata, geralmente são afirmações matemáticas corriqueiras. As tabelas abaixo mostram os principais conectivos dessa lógica. Conectivo NOT (símbolo: !) A !A True False False True A True True False False Conectivo AND (símbolo: &&) B A && B True True False False True False False False Conectivo OR (símbolo: ||) A B A || B True True True True False True False True True False False False Essas tabelas se tornarão mais compreensíveis com os exemplos a seguir. 230 Teste AND Para saber se o valor de n pertence ao intervalo 2 do problema anterior (0 ≤ n < 20) basta escrever: if(n>=0 && n<20) ... Em português se diria: se n é maior do que ou igual a 0 e menor do que 20 ... Trata-se de uma condição composta. Ela será verdadeira apenas se as duas condições envolvidas forem verdadeiras. Teste OU Para saber se uma pessoa possui mais de 20 anos ou se o seu saldo bancário é superior a 5000 reais basta escrever: if(idade>20 || saldo>5000) ... Observe que se pelo menos uma das condições for verdadeira então a condição composta também o será. Teste NOT Para saber se uma pessoa não é menor de idade basta escrever: if(!(idade>=18)) ... Observe que se a condição (idade>=18) é verdadeira a sua negação (!(idade>=18)) é falsa. A comparação !(idade>=18) corresponde em português à expressão “idade não é maior ou igual a 18”. 3.2 Alguns erros comuns • • Uso de = em teste de igualdade. Para testar se uma pessoa tem exatamente 18 anos, devemos escrever if(idade == 18) Uso de ponto-e-vírgula no fim da linha que contém um if. Lembrar que if é um teste de condição. Coloca-se ponto-e-vírgula no fim de uma instrução e não de um teste de condição. 3.3 Para melhor compreender as condições Acompanhe o código abaixo. #include <stdio.h> int main() { if (0) { printf("falso"); } else { printf("verdadeiro"); } system(“pause”); } A execução deste código com as modificações indicadas para if(expr) fornecerá os seguintes resultados: expr resultado 0 falso 1 verdadeiro 2 verdadeiro 5 verdadeiro Resumindo, quando a expressão comparação vale 0 o resultado de sua avaliação é False e para qualquer outro valor dessa expressão a sua avaliação é True. Por isso os resultados obtidos acima. De fato, toda vez que a linguagem C realiza um teste, ela devolve o valor 1 quando o resultado é verdadeiro e 0 quando o resultado é falso. Atribuir o resultado de uma comparação a uma variável Acompanhe o código abaixo. #include<stdio.h> int main() { int p = 14; int b = 10; b = (p >= b); printf("b = %d\n", b); system(“pause”); } A comparação p >= b retorna o número 1, pois ela é verdadeira. Assim, b passa a valer 1, como resultado da atribuição. Isso pode ser verificado com o resultado exibido por printf. Experimente mudar o valor de b para 20 no código ao lado. O resultado exibido deve ser 0, pois desta vez o teste é falso. Um valor boolean Importante! A variável b do código acima representa um valor boolean. Diz-se que uma variável que só pode receber os valores 0 ou 1 é do tipo boolean. Na verdade, em C não existe este tipo. Há uma convenção na linguagem C de modo que 0 é associado ao boolean False e qualquer outro valor é associado ao boolean 231 True. Em C++, a linguagem C orientada a objeto, um novo tipo foi criado especialmente para conter esses valores. Em C, geralmente as variáveis que guardarão valores boolean são declaradas do tipo int. Os valores Booleans nas condições Geralmente fazemos testes sobre variáveis booleans. Se a variável "maior" vale 1 ou qualquer outro valor diferente de 0 e temos a instrução if(maior) {printf(“maior de idade”)}; ... o resultado exibido é a expressão ”maior de idade”, pois a variável "maior" é diferente de zero, portanto True. Quando é sabido que a variável pode conter um número fazemos o teste na forma “if(variável == 1)”. Se a variável só pode conter os valores 0 ou 1, fazemos o teste na forma “if(variável)”. Sempre que possível, é bom encurtar os caminhos. 3.4 A estrutura switch A condição "if... else" é o tipo de condição mais utilizada. Entretanto, quando existem muitas comparações a serem feitas podemos utilizar outro tipo de estrutura: switch. Analise o seguinte trecho de programa … if (idade == 2) { printf("Ola bebe!");} else if (idade == 6) { printf("Oi crianca!");} else if (idade == 12) { printf ("Oi jovem!");} else if (idade == 16) { printf ("Oi adolescente!");} else if (idade == 18) { printf ("Salve adulto!");} else if (idade == 68) { printf ("Ola vovo !");} else { printf ("????????????? ");} … Existem seis comparações a serem feitas. Para não repetir essas comparações, nova forma de representá-las foi desenvolvida. O trecho de programa abaixo mostra essa nova forma. … switch (idade) { case 2: printf ("Ola bebe!"); break; case 6: printf ("Oi crianca!"); break; case 12: printf ("Oi jovem!"); break; case 16: printf ("Oi adolescente!"); break; case 18: printf ("Salve adulto!"); break; case 68: printf ("Ola vovo!"); break; default: printf ("?????????????"); break; } … 232 Sempre que possível, é bom escrever utilizando o comando switch, é mais prático. É necessário colocar uma instrução break, obrigatoriamente ao fim de cada caso. Esquecer de fazê-lo faz com que o computador continue a executar as instruções seguintes, o que não faz parte da lógica da instrução switch. Enfim, o caso "default" corresponde ao else já visto. Se a variável não vale nenhum dos valores testados acima o computador executa esta opção. Exemplo de um menu O programa abaixo pode ser utilizado como modelo para a construção de outros menus. // em um restaurante #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { long menu; printf("=== Menu ===\n\n"); printf("1. Pao com queijo\n"); printf("2. Pao com manteiga\n"); printf("3. Cuscus\n"); printf("4. Ovo frito\n"); printf("\n Sua escolha? "); scanf("%d", &menu); printf("\n"); switch (menu) { case 1: printf("Voce escolheu Pao com queijo!"); break; case 2: printf("Voce escolheu Pao com manteiga!"); break; case 3: printf("Voce escolheu Cuscus!)"); break; case 4: printf("Voce escolheu Ovo frito!"); break; default: printf("Voce não fez a escolha correta!"); break; } printf("\n\n"); system("PAUSE"); } 3.5 Condições condensadas Existe ainda um terceiro modo de programar uma comparação, ainda que pouco utilizado. Trata-se da condição condensada. É como um if … else só que tudo em uma mesma linha. Observar atentamente os dois trechos de programa abaixo: 233 Um teste if ... else Se a variável maior for avaliada como True, isto é, se for diferente de zero, então a variável idade receberá o valor 18, caso contrário receberá o valor 17. if (maior) idade = 18; else idade = 17; O mesmo teste, condensado A semântica desta instrução é idêntica à do comando if ... else. Se maior for diferente de 0 (True), idade recebe o valor 18. Se maior possui o valor 0, então idade recebe o valor 17. Trata-se apenas de uma forma condensada de escrever a estrutura if ... else. idade = (maior) ? 18 : 17; 3.6 Teste 1. O que significa o símbolo <= ? ( ) Superior ( ) Inferior ( ) Superior ou igual ( ) Inferior ou igual ( ) Diferente de 2. Se a variável n vale 0, o que é exibido pelo trecho de programa abaixo? if (n != 0) printf ("Siga em frente."); else printf ("Aguarde um pouco."); ( ) Siga em frente. . ( ) Aguarde um pouco 3. Observe o trecho de programa abaixo. O que será impresso? int temDinheiro = 0, maior = 0; long dinheiroEmBolso = 10000, idade = 19; ... temDinheiro = dinheiroEmBolso > 10000; maior = !(idade < 18); if (temDinheiro && maior) printf ("Pode abrir conta em banco!"); else printf ("Impossivel abrir conta"); ( ) Pode abrir conta em banco! ( ) Impossivel abrir conta 4. Qual o problema na instrução abaixo? switch (k) { case 5: printf ("Bom dia"); case 12: printf ("Boa tarde"); default: printf ("Ate logo"); } ( ) faltam as instruções break ( ) falta um ponto-e-vírgula no fim de switch ( ) faltam as chaves em cada "case" ( ) é "case default" e não "default" 234 5. Quanto valerá a variável resp após as instruções abaixo? A variável a vale 200 e b é um Boolean que vale False. resp = (b || a >= 200) ? 20 : 30; resp = (resp == 20 && (b && a >= 200)) ? 40 : 50; ( ) 20 ( ) 30 ( ) 40 ( ) 50 4. Avaliando o que foi construído Compreender bem as estruturas de seleção é uma condição indispensável para escrever bons programas. Parece que tudo é indispensável, não? Mas é assim mesmo. Existe uma seqüência lógica no aprendizado de uma linguagem de programação. É como na matemática. É praticamente impossível calcular uma derivada, conscientemente, se não conhecemos os conceitos de função, limites e de continuidade. Como veremos a seguir, as estruturas de repetição trarão embutidas estruturas de seleção. 5. Referências 1. Ascencio, Ana F G., Campos, Edilene A V., Fundamentos da Programação de Computadores: Algoritmos, Pascal, C/C++ e Java, 2ª edição, Pearson Education, 2007. 2. Hutchison, Robert C., Just, Steven B., Programming using the C language. MGraw-Hill Book Company, 1988. 3. Kernighan, Brian W., Ritche, Dennis M., Le langage C, Masson, 1986. 235 Unidade VII - Estruturas de Controle (Repetições) 1. Situando a temática Após termos visto como realizar uma operação de seleção é chegado o momento de entendermos as repetições ou "laços". O que é um laço? Trata-se de uma técnica que permite repetir as mesmas instruções várias vezes. Da mesma forma que para as comparações, existem várias formas de realizar os laços. Veremos três dessas formas. • while • do ... while • for Os laços, em geral, funcionam assim: 1. O computador executa as instruções de cima para baixo (como de hábito) 2. Ao atingir o fim do laço, retorna para a primeira instrução deste. 3. Pode haver « saltos » para fora do laço 4. Existe uma condição embutida nos laços 5. Essa condição controla a repetição das instruções e a saída do laço instruções instruções .................. instruções A existência da condição nos laços é necessária para impedir que as instruções fiquem sendo executadas indefinidamente. Então, ao criarmos um laço, criamos também, implicitamente, uma condição. A condição significa: « repetir este laço enquanto (até que) a condição seja verdadeira ». 2. Problematizando a temática Suponhamos que necessitemos imprimir todos os números naturais de 1 até 100. Uma maneira de fazê-lo é chamarmos 100 vezes a função printf tendo como argumento em cada chamada o número natural desejado. É uma solução, mas com certeza a menos indicada. Imagine se tivéssemos de imprimir os 1000 primeiros números naturais. As repetições nos oferecem um modo confortável de realizarmos esta tarefa, como veremos. 3. Conhecendo a temática 3.1 O laço while A estrutura de um laço while é while ( <condição> ) { Bloco de instruções } Sua interpretação é muito simples: enquanto a condição for avaliada True, o bloco de instruções do laço é executado. Quando a condição for avaliada False, o controle do programa passa para a próxima instrução, fora do laço. Evidentemente, pode ser que as instruções do laço não sejam executadas nenhuma vez. Por quê? Exemplo1. Façamos um pequeno teste: escrever um programa que leia um número inteiro a partir do teclado. Enquanto esse número não for negativo, voltar a ler um número inteiro a partir do teclado. 236 Ao executar este programa entremos com os seguintes números: #include<stdio.h> main() { int n=0; while (n>=0) { printf("Digitar um numero positivo ou nulo: "); scanf("%d", &n); } printf("Obrigado! \n") ; system(" pause ") ; return 0 ; } Digitar um numero positivo ou nulo: 13 Digitar um numero positivo ou nulo: 21 Digitar um numero positivo ou nulo: 143 Digitar um numero positivo ou nulo: -3 Obrigado! O programa continuará executando as instruções do laço enquanto o valor digitado for positivo ou nulo e sairá do mesmo tão logo seja digitado um número negativo. Graficamente, a semântica da instrução while pode ser representada assim: condição próxima instrução false true Bloco de instruções Semântica While Vamos fazer algo mais interessante, executar um laço certo número de vezes. Para isso deve-se criar uma variável que "contará" o número de vezes que o laço será executado. Essa variável deverá valer 0 antes do laço e ser incrementada dentro deste cada vez que o mesmo for executado. A operação "incremento" como sabemos, consiste em aumentar de 1 o valor de uma variável. Por exemplo, x++ incrementa o valor da variável x de uma unidade. Exemplo2. Imprimir (exibir na tela) os números inteiros a partir de 0 até 9. Acompanhe o trecho de programa abaixo. Completando este trecho de programa, teremos a seguinte saída (experimente fazê-lo): .................... long cont = 0; while (cont < 10) { printf("cont = %d\n ",cont); cont++; } ..................... cont = o cont = 1 cont = 2 cont = 3 cont = 4 cont = 5 cont = 6 cont = 7 cont = 8 cont = 9 237 Procure explicar como isto aconteceu. Ao compreendermos o que se passou poderemos nos divertir aumentado o valor do limite 10 para 100, 1000, etc. Ampliando o seu Conhecimento Atenção com os laços infinitos Ao criarmos um laço temos que ter a certeza de que ele irá parar em algum momento. Observemos que neste tipo de laço se a condição é sempre verdadeira, o programa nunca pára. Exemplo de um laço infinito. while (1) { printf("laco infinito\n"); } Lembremo-nos dos booleans: 1 = True, 0 = False. Aqui a condição é sempre verdadeira, portanto o programa imprimirá a frase "laço infinito" sem parar. Na verdade a intervenção humana pode parar este programa bastando teclar Ctrl + C. Evitemos a todo custo cairmos em um laço infinito, a menos que este seja o nosso propósito. Às vezes esses laços são necessários. Na programação de jogos, por exemplo. 3.2 O laço do ... while Este tipo de laço é bastante similar ao laço while, porém menos utilizado. A diferença entre as duas instruções está na colocação física da condição dentro dos laços. Neste tipo de laço a condição aparece após o bloco enquanto que em while a condição é colocada antes do bloco. Isto quer dizer que as instruções internas ao laço do ... while são executadas pelo menos uma vez. Graficamente, a semântica da instrução do ... while pode ser representada assim: Bloco de instruções true condição false próxima instrução Semântica Do ... While Exemplo3. Resolver o mesmo problema de Exemplo2, agora utilizando a instrução do ... while. ..................... long cont = 0; do { printf("cont = %d\n",cont); cont++; } while (cont<10); ..................... Uma solução para este problema é dada pelo trecho de programa ao lado. Complete-o e verifique que a sua saída é igual à do Exemplo1. Qual a diferença operacional entre este programa e aquele do Exemplo2? Muito simples, no programa do exemplo2 o bloco de instruções poderá nunca ser executado bastando para isto que a condição seja falsa no início. Caso o valor de cont seja 238 inicializado com 30, por exemplo, a condição será falsa na primeira vez em que for avaliada. No programa do exemplo3 o bloco de instruções será executado pelo menos uma vez. É responsabilidade do programador gerenciar bem a execução dos laços. Para não esquecer! Não devemos esquecer de colocar ponto-e-vírgula no fim da instrução do ... while. Este esquecimento é um erro comum entre os principiantes. 3.3 O laço for Em teoria, o laço while permite realizar todas as repetições que alguém deseje programar. Entretanto, como para as condições, existe uma forma condensada, mais rápida para escrever: o laço for. Os laços for, que serão vistos nesta seção, e os laços while são os mais utilizados em programação. Estudaremos o laço for a partir de um exemplo. Exemplo4. Resolver o mesmo problema de Exemplo2, agora utilizando a instrução for. Uma solução para este problema é dada pelo trecho de programa ao lado. Complete-o e verifique que a sua saída é igual à do Exemplo2. ............................. long cont; for (cont = 0; cont < 10; cont++) { printf("cont = %d\n",cont); } ............................... O laço for é outra forma de escrever um laço while. Algumas observações • A variável cont não foi inicializada a zero quando de sua declaração, mas poderia ter sido. • Não há mais incremento cont++ dentro do bloco de instruções Vejamos o que há entre os parênteses pois é lá que se encontra todo o interesse do laço for. Na verdade, existem três instruções, separadas por vírgulas, dentro desses parênteses: • A inicialização: esta instrução é utilizada para preparar a variável cont. Em nosso caso, ela foi inicializada com zero. • A condição: como para o laço while, é a condição que diz se o bloco deve ser repetido. Enquanto a condição for verdadeira o bloco é executado. • O incremento: esta última instrução é executada ao fim de cada execução do bloco para atualizar a variável de controle. Às vezes é utilizado um decremento no lugar de um incremento. Às vezes também, se utiliza outras operações como cont += 2; para avançar de 2 em 2, por exemplo. Enfim, o laço for será muito útil. Saibamos utilizá-lo bem. 3.4 Teste 1. Qual destes laços não existe em C? ( ) for ( ) repeat ( ) do.. while ( ) while 239 2. Quantas vezes a mensagem "Estude Matematica" será exibida? long cont = 12; do { printf ("Estude Matematica\n"); cont++; } while (cont < 13); ( ) 0 vezes ( ) 1 vez ( ) 2 vezes ( ) 12 vezes ( ) 13 vezes 3. Quantas vezes a mensagem "Sistemas operacionais" será exibida? long cont = 10; while (cont < 11) { printf ("Sistemas operacionais\n"); } ( ) 0 vezes ( ) 1 vez ( ) 14 vezes ( ) 15 vezes ( ) infinitas vezes 4. Qual dos laços for poderia exibir as mensagens seguintes? ( ( ( ( Linha n°1 Linha n°3 Linha n°5 Linha n°7 ) for (cont = 1; cont < 9 ; cont += 2) ) for (cont = 1; cont <= 7 ; cont ++) ) for (cont = 0; cont < 9 ; cont += 2) ) for (cont = 1; cont < 8 ; cont ++) E para concluir, um lembrete que todos compreenderão, com certeza. #include<stdio.h> int main() { int cont = 0; for(cont=1; cont<=100; cont++) printf("Eu devo entregar minhas tarefas em dia."); } 4. Avaliando o que foi construído As estruturas de repetição facilitam bastante a realização das tarefas, como visto. É preciso que o programador esteja familiarizado com essas estruturas, pois elas são necessárias na maioria dos programas que são desenvolvidos. Com os conhecimentos adquiridos até aqui o aluno é capaz de programar a solução de boa parte dos problemas numéricos. Esses recursos serão complementados na próxima unidade com o estudo das funções. 5. Referências 1. Ascencio, Ana F G., Campos, Edilene A V., Fundamentos da Programação de Computadores: Algoritmos, Pascal, C/C++ e Java, 2ª edição, Pearson Education, 2007. 2. Hutchison, Robert C., Just, Steven B., Programming using the C language. MGraw-Hill Book Company, 1988. 3. Kernighan, Brian W., Ritche, Dennis M., Le langage C, Masson, 1986. 240 Unidade VIII - Funções 1. Situando a temática Concluímos a parte dos conhecimentos básicos da linguagem C com esta noção fundamental que são as funções. Todos os programas em C se baseiam sobre este princípio. Aprenderemos a estruturar um programa em pequenas partes. Um programa grande em C é feito de pequenas partes que são “encaixadas” adequadamente formando um todo. Essas partes são as funções. Lembremos a estrutura de um programa em C. # include < stdio.h > ⎫ ⎬Diretivas de pré − processador # include < stdlib.h >⎭ int main(int argc, char * argv[])⎫ ⎪ { ⎪ ⎪⎪ ⎬função system("pause" ); ⎪ ⎪ return 0; ⎪ ⎪⎭ } As diretivas de pré-processador começam sempre por # e são geralmente colocadas no início do arquivo fonte. Em seguida aparece a função main, a função principal de todo programa. Nas unidades anteriores permanecemos sempre dentro desta função, ou seja, todos os cálculos e outros processamentos foram feitos no interior da função main. Na prática, quase nenhum programa é escrito unicamente dentro da função main. Vamos então aprender a dividir a solução de um problema em várias partes, cada uma dessas partes será uma função. 2. Problematizando a temática Um programa que realiza várias tarefas pode ser escrito como uma única função, a função principal, main. Entretanto, essa estratégia torna o programa difícil de ser lido e mesmo desenvolvido. Por isso, é costume dos analistas subdividirem um problema complexo em subproblemas, menos complexos obviamente, e resolver cada um desses por meio de uma função. Esse método é conhecido como dividir para conquistar. Aprenderemos a programar essas unidades de programa agora. 3. Conhecendo a temática 3.1 Objetivo de uma função Uma função executa ações e retorna um resultado. As ações realizadas por uma função dependem, geralmente, de alguns parâmetros iniciais denominados de “entrada” e o resultado produzido é denominado “saída”. Esquematicamente, podemos representar uma função assim: entrada saída Processamento Função Uma função é então um elemento lógico de programação que comporta três etapas: 241 1. Entrada: informações necessárias ao processamento são passadas à função. 2. Processamento: operações realizadas, geralmente a partir das entradas. 3. Saída: terminado o processamento, a função retorna um resultado. Imaginemos uma função que calcule a soma de dois valores inteiros. Podemos representá-la esquematicamente como abaixo, onde a e b são valores inteiros. a a Somar a e b a+b Em geral as funções são mais complexas que o exemplo acima, mas esses são os conceitos a conservar: entrada de parâmetros, processamento a ser realizado e saída do resultado produzido. 3.2 A estrutura de uma função A estrutura de uma função é bem simples. Vejamos um modelo. tipo nomedaFunção(parâmetros) Algumas considerações: • { instruções } • • • tipo: tal como as variáveis, toda função possui um tipo. O tipo de uma função indica o tipo do resultado que ela retornará. Mas existe função que não retorna valor algum para o programa que a chamou. Neste caso a função deverá ser de um tipo especial “void” significando esse fato; nomedaFunção: é o nome da função, evidentemente, e deve obedecer às mesmas regras que os nomes de variáveis; parâmetros: uma lista de parâmetros entre parênteses, separados por vírgula, que contém as entradas necessárias ao processamento da função. Uma função pode conter qualquer número de parâmetros, dependendo da lógica e do bom senso do programador. { ... }: após o cabeçalho da função (tipo, nome e parâmetros), segue um par de chaves que contém o corpo da função, as instruções que a compõem. 3.3 Criando uma função Vamos escrever a função que soma dois valores inteiros, vista anteriormente e dar-lhe o nome soma2. Eis a nossa primeira função. O que vemos nessa função? long – tipo dos parâmetros e de retorno da função, por isso a variável “resposta” também é desse tipo soma2 – é o nome dado à função return – instrução que indica à função para retornar um resultado ao programa que a chamou. Esta instrução geralmente se encontra no fim da função. Como a variável resposta é declarada dentro do corpo da função, seu escopo será a própria função. O escopo de uma variável é o seu campo de abrangência (onde ela é válida). Uma variável declarada assim é chamada local e não pode ser utilizada fora da função onde é declarada. A variável resposta é então uma variável local à função soma2. A função soma2 definida acima, será “chamada” de dentro da função principal (main) de um programa. long soma2(long p1, long p2) { long resposta = 0; resposta = p1+p2; return resposta; } 242 3.4 Chamando uma função #include<stdio.h> long soma2(long p1,long p2) { long resposta = 0; resposta = p1+p2; return resposta; } int main() { long a,b, soma; printf("\n digite um valor para a = "); scanf("%d",&a); printf("\n digite um valor para b = "); scanf("%d",&b); soma=soma2(a,b); printf(" soma = %d\n", soma); system("pause"); ret rn 0; Para chamar uma função a partir da função main ou de qualquer outra função, basta escrever o seu nome em uma expressão ou como um parâmetro em um comando printf. No programa ao lado a função soma2 está sendo chamada a partir da função main. Observe que o nome da função aparece em uma expressão, do lado direito de um comando de atribuição. O nome de uma função nunca pode aparecer do lado esquerdo em uma atribuição. Lembre que uma atribuição é do tipo variável = expressão. Uma função pode chamar outra, que pode chamar outra, etc. Cabe ao programador não perder o domínio sobre a execução do código. Os parâmetros declarados no cabeçalho de uma função são ditos parâmetros formais, pois servem apenas para dizer ao compilador o tipo e a ordem em que os parâmetros efetivos serão transmitidos à função. Parâmetros efetivos são aqueles que aparecem na chamada da função. No programa acima os parâmetros efetivos são a e b e os parâmetros formais são p1 e p2. Os parâmetros efetivos e os formais podem ter os mesmos nomes, porém representam identificadores distintos. Ampliando o seu Conhecimento Protótipo de uma função. O protótipo de uma função é o seu cabeçalho seguido de ponto-e-vírgula. Exemplo. long soma2(long p1,long p2); Um protótipo pode também indicar apenas os tipos dos parâmetros: Exemplo. long soma2(long,long); Na definição da função os parâmetros formais serão explicitados. Uma função pode ser definida também após a função principal. Neste caso, o seu protótipo deve ser declarado antes da função main. Após a função main coloca-se então a função que está sendo definida. Usar ou não os protótipos é uma decisão do programador. O seu uso indica ao compilador que a função por ele representada será definida (codificada) após a função main. Caso uma função seja codificada após a função main, sem o uso de protótipo, o compilador acusará um erro, pois quando o nome da função aparecer, pela primeira vez, na função main, ele não o reconhecerá como nome de uma função e o tratará como uma variável não declarada acusando, portanto erro por falta de declaração de variável. 243 O exemplo anterior ficaria assim: #include<stdio.h> /* declaração do protótipo */ long soma2(long p1, long p2); int main() { long a,b, soma; printf("\n digite um valor para a = "); scanf("%d",&a); printf("\n digite um valor para b = "); scanf("%d",&b); soma=soma2(a,b); printf(" soma = %d\n", soma); system("pause"); return 0; } /* definição da função soma2 */ long soma2(long p1,long p2) { long resposta = 0; resposta = p1+p2; return resposta; } Ampliando o seu Conhecimento Não somos obrigados a armazenar o resultado produzido por uma função em uma variável. Podemos retornar esse resultado na forma indicada de uma expressão. Vejamos, #include <stdio.h> long soma2(long p1, long p2) { return p1+p2; } Neste caso, não foi necessário declararmos a variável “resposta” uma vez que não a utilizamos. Também é possível chamar uma função a partir de um comando printf. printf(" soma = %d\n", soma2(a,b)); Observe que acima a função soma2 está sendo chamada a partir da lista de parâmetros da instrução printf. 3.5 Mais exemplos I. Conversão Celsius/Fahrenheit Lembremo-nos que a relação entre as escalas Celsius e Fahrenheit é dada por: C F − 32 . Então, de = 100 180 posse dessa informação vamos escrever uma função que transforme uma temperatura dada em Celsius para Fahrenheit. Escreveremos apenas a função. Escreva o programa principal (a função main). /* função conversão: Celsius em Fahrenheit */ Antes de escrever a função principal, responda ao pequeno questionário a seguir. float converte(long C) { long F = 0; F = 180*C/100+32; return F; } 1. 2. 3. 4. Qual o parâmetro formal, na função converte? Quantos parâmetros efetivos devem existir na chamada da função converte? Qual o tipo do parâmetro efetivo? Como será feita a chamada à função converte (comando de atribuição ou parâmetro na função printf)? Pronto! Agora é só escrever a função main. 244 II. Transformar segundos em horas, minutos e segundos A função abaixo transforma n segundos em horas, minutos e segundos. Essa função não retornará nenhum valor. Ela imprimirá o resultado obtido sem a necessidade de retorná-lo para a função main. /* função tempo: segundos em horas, minutos e segundos */ void tempo(int s) { int ss, mm, hh, t; ss = s%60; // resto da divisão de s por 60 t = s/60; // quociente inteiro de s por 60 mm = t%60; // resto da divisão de t por 60 hh = t/60; // quociente inteiro de t por 60 printf(“%d segundos = %d horas, %d minutos e %d segundos”, s,hh,mm,ss); } Antes de escrever a função principal, responda ao pequeno questionário a seguir. 1. 2. 3. 4. 5. Qual o tipo da função tempo? Qual o parâmetro formal, na função tempo? Quantos parâmetros efetivos devem existir na chamada da função tempo? Qual o tipo do parâmetro efetivo? Como será feita a chamada à função tempo (expressão ou parâmetro na função printf)? Pronto! Agora é só escrever a função main. Uma dica: a chamada à função tempo pode ser assim tempo(n);. III. Área de um retângulo A área de um retângulo é dada por: largura * altura. Nossa função AreaRetangulo deve receber dois parâmetros, largura e altura e retornar a área calculada. /* função AreaRetangulo */ Escreva a função main(). float AreaRetangulo(float larg, float alt) { return larg * alt; } Modifique a função AreaRetangulo para que ela imprima a área calculada e não precise retorná-la à função main. IV. Um menu Acompanhe o exemplo abaixo. Trata-se da elaboração de um menu, onde podemos escolher qual operação será executada: o cálculo da área de um círculo, de um quadrado, de um retângulo ou de um trapézio. Este roteiro poderá ser utilizado para a elaboração de outros menus. Basta fazer as adaptações que se fizerem necessárias. /* função menu */ long menu() { int escolha= 0; while (escolha < 1 || escolha> 4) { printf("Menu :\n"); printf("1 : Para calcular a area de um circulo\n"); printf("2 : Para calcular a area de um quadrado\n"); printf("3 : Para calcular a area de um retangulo\n"); printf("4 : Para calcular a area de um trapezio\n"); printf("Sua escolha ? "); scanf("%d", &escolha); } return escolha; } 245 Texto da função menu int main(int argc, char *argv[]) { float r,l, la, lb, area; switch (menu()) { case 1: printf("Raio do circulo ="); scanf("%f",&r); printf("\nArea = %f\n", 3.14*r*r); break; case 2: printf("Lado do quadrado ="); scanf("%f",&l); area=l*l; printf("\nArea = %f\n",area); break; case 3: printf("Lados do retangulo\n"); printf("lado a = "); scanf("%f",&la); printf("lado b = "); scanf("%f",&lb); printf("\nArea = %f\n",la*lb); break; case 4: printf("Bases do trapezio\n"); printf("base maior a = "); scanf("%f",&la); printf("base menor b = "); scanf("%f",&lb); printf("altura l = "); scanf("%f",&l); printf("\nArea = %f\n",(la+lb)*l/2); break; } system("PAUSE"); return 0; } Texto da função main 3.6 Teste 1. O que acontece após um return? • • • ( ) A função pára e retorna o resultado indicado ( ) A função continua e retorna o resultado indicado ( ) A função continua e não retorna resultado 2. Em que caso a instrução return não é obrigatória? • • • ( ) Quando a função não possui parâmetro de entrada ( ) Quando a função é de tipo void ( ) Quando a função deve retornar zero 3. O que são os parâmetros de uma função? • • • ( ) Indicações sobre o nome da função ( ) Indicações sobre o valor que ela deve retornar ( ) Variáveis que lhe são enviadas para serem usadas internamente 4. Qual das afirmações é falsa? • • • ( ) Uma função não é obrigada a retornar um valor ( ) Uma função pode retornar um valor de qualquer tipo de variável ( ) Uma função pode retornar vários valores pela instrução return 246 5. O que há de errado na função abaixo? long quadrado(long num) { long resposta = 0; resposta = num * num; } • • ( ) A função não retorna nenhum valor ( ) Falta um ponto-e-vírgula em algum lugar da função 4. Avaliando o que foi construído As funções em linguagem de programação, como você já percebeu, surgiram para facilitar a programação de problemas considerados complexos. Esses problemas requerem uma solução longa o que dificulta o seu desenvolvimento e sua compreensão. Os programas são entes abstratos, frutos da imaginação. Por isso mesmo não são fabricados, no sentido em que o são os produtos das engenharias convencionais, como uma casa, um navio, etc. Assim, quando nos referimos a um programa de computador, sempre usamos o termo desenvolvimento em lugar de fabricação. De fato, desenvolvemos uma solução para um dado problema, um algoritmo. É nessa fase do projeto de solução que o problema é “quebrado” em subproblemas. A solução de cada subproblema é então implementada como uma função. Mais uma vez, só as repetições, os exercícios, conduzem ao domínio da técnica. 5. Referências 1. Ascencio, Ana F G., Campos, Edilene A V., Fundamentos da Programação de Computadores: Algoritmos, Pascal, C/C++ e Java, 2ª edição, Pearson Education, 2007. 2. Hutchison, Robert C., Just, Steven B., Programming using the C language. MGraw-Hill Book Company, 1988. 3. Kernighan, Brian W., Ritche, Dennis M., Le langage C, Masson, 1986. 247 Unidade IX - Ponteiros 1. Situando a temática Nesta unidade estudaremos um novo tipo de dado, os ponteiros. Veremos como eles funcionam e para que servem. É quase impossível hoje em dia escrever programas em C sem fazer uso dos ponteiros. Mesmo sem saber nós já o utilizamos quando realizamos leituras com a função scanf. A noção de ponteiros é importante para lidarmos com outro tipo de dado muito utilizado denominado array. Mesmo que o escopo deste texto não cubra outros tipos de dados abstratos de grande utilização na computação, é importante saber, principalmente para aqueles que pretendem avançar mais no estudo de resolução de problemas com o uso de computador, que vários desses tipos de dados são implementados à base de ponteiros. A título de ilustração sobre esses tipos citamos: árvores e listas encadeadas. 2. Problematizando a temática Um problema estranho Escrever uma função que retorne 2 ou mais valores. Isso parece ser "Impossível"! Sabemos que uma função tem as seguintes características: • Retorna um único valor ou não retorna valor algum • Se uma função é do tipo T então ela retorna um valor do tipo T Vamos adquirir os conhecimentos necessários para solucionar problemas deste tipo. 3. Conhecendo a temática 3.1 Um pouco mais sobre funções Retomemos o problema de transformar segundos em horas, minutos e segundos, apresentado na unidade IX. /* função tempo: segundos em horas, minutos e segundos */ void tempo(int s) { int ss, mm, hh, t; ss = s%60; // resto da divisão de s por 60 t = s/60; // quociente inteiro de s por 60 mm = t%60; // resto da divisão de t por 60 hh = t/60; // quociente inteiro de t por 60 printf(“%d segundos = %d horas, %d minutos e %d segundos”, s,hh,mm,ss); } Observamos que a função é do tipo void, isto é, não retorna valor algum. De fato, o resultado calculado é impresso na própria função. Por que não foi possível retornar esses valores para a função main? Quando passamos uma variável para uma função, como argumento, uma cópia dessa variável é feita e passada para a função. Assim, se na função main existir uma variável de nome s, que represente o número de segundos a ser transformado, a variável s na função tempo não é a mesma que aquela de main. Trata-se apenas de uma cópia. Tudo se passa da mesma forma caso as variáveis correspondentes tenham nomes distintos. 248 De modo geral, a transmissão de parâmetros se passa assim: int main() int a,b,c ..................... x = f(a,b,c); int f(int x, int y, int z) ………………. …………… cópia de c cópia de b cópia de a Observemos que na função main, ao ser executada a instrução x = f(a,b,c); são criadas cópias dos parâmetros reais (as variáveis a, b e c) e essas cópias são passadas para a função f. Na função f, as cópias transmitidas recebem os nomes x, y e z, respectivamente, parâmetros formais. Assim, se alguma alteração ocorrer nos valores de x, y ou z essa alteração não afetará os valores de a, b ou c, pois isso ocorreu apenas na cópia dessas variáveis ficando os seus originais intactos. A solução do problema proposto, como veremos, é apenas um exemplo da utilidade dos ponteiros. A memória e os endereços Endereço Valor 0 145 1 3.8028322 2 0.8272555 É mais ou menos assim que podemos representar a memória principal (RAM) de um computador. A memória é composta de células. Cada célula possui um número, seu endereço. A memória possui um grande número de endereços, começando sempre por 0. Em cada endereço podemos armazenar UM e UM SÓ número. Observemos também que na memória podemos armazenar apenas números. Ela não pode armazenar letras, frases, etc. Para contornar esse problema foi inventada uma tabela, denominada ASCII, já vista na unidade II. Endereço e valor Ao criarmos uma variável “soma” do tipo long, por exemplo, escrevemos: ... ... long soma = 300; e o programa solicita ao sistema operacional permissão para utilizar a memória. Como resposta o sistema operacional n-1 940.5118 indica em qual endereço o programa pode armazenar um valor de tipo long para a variável soma. Aliás, este é um dos papéis principais de um sistema operacional, gerenciar a alocação de memória para os programas. Voltemos à nossa instrução. O valor 300 é armazenado em alguma parte da memória, digamos no endereço 1540. A partir de então, a palavra “soma” no programa é substituída, via compilador, pelo endereço 1540. A cada ocorrência da palavra “soma” no programa, o computador verifica então o endereço 1540. Para o programador tudo é bastante transparente. Basta escrever “soma” em uma expressão aritmética, em uma instrução de leitura (scanf) ou impressão (printf). Vejamos, 3 39014768 printf("A variavel soma vale: %d", soma); Resultado na tela: A variável soma vale: 300 Sabemos exibir o valor de uma variável, mas poderíamos exibir o seu endereço? Para exibir o endereço de uma variável deve-se utilizar o especificador de formato %p (p da palavra ponteiro) na instrução printf. De outro modo, enviamos à função printf não a variável soma, mas seu endereço. E para fazer isso devemos colocar o símbolo & antes do nome da variável. Já fazemos isto habitualmente quando utilizamos a função scanf. Então para imprimir o endereço da variável “soma” devemos escrever 249 printf("O endereço da variavel soma eh: %p", &soma); Resultado na tela: O endereço da variável soma eh: 0023FF74 O que aparece na tela é um número sim. Um número hexadecimal, no lugar dos números decimais aos quais estamos acostumados. Esse é o endereço da variável “soma” na memória do computador, designado pelo sistema operacional como explicado acima. Se trocarmos %p por %d na instrução printf acima obtemos um número decimal como saída. Entretanto, %p foi implementado especialmente para exibir endereços, por isso preferimos esta notação. Atenção! Ao executarmos um programa em computadores diferentes, o endereço de uma variável será certamente diferente de um computador para outro. Isso depende dos programas que estão carregados na memória dos computadores. Não esqueçamos que é o sistema operacional que se encarrega de gerenciar a memória. É, portanto impossível saber a priori em que endereço uma variável será alocada. Em resumo, • soma: exibe o VALOR da variável. • &soma: exibe o ENDEREÇO da variável. Com “soma” o computador lê o valor da variável na memória e reenvia esse valor. Com “&soma”, o computador nos dirá em que endereço se encontra a variável. 3.2 Utilização de ponteiros Até este ponto utilizamos variáveis apenas para conter números. A partir de agora vamos aprender a criar variáveis que conterão endereços: é exatamente a isto que chamamos de ponteiros. Ponteiro é uma variável que contém um endereço. Poderíamos nos perguntar, mas os endereços não são números também? Sim, mas neste caso esses números têm um significado particular: eles representam o endereço de outra variável na memória. I. Criar um ponteiro Para criar uma variável de tipo ponteiro, devemos colocar o símbolo * no início do nome da variável. Exemplo. Podemos também escrever: long *nomeDoPonteiro; long *p1, *p2, *p3; long* p1, p2, p3; Como temos visto ao longo deste curso, é muito importante iniciarmos as variáveis quando de suas declarações. É ainda mais importante fazê-lo com os ponteiros. Para iniciar um ponteiro devemos escrever: A constante NULL indica que o ponteiro p1 não contém nenhum endereço. Essa instrução reservará um espaço na memória do computador como se estivéssemos criando uma variável normal. A diferença entre uma variável ponteiro e as outras variáveis já vistas é que o conteúdo desta será o endereço de outra variável. Exemplo. A primeira linha significa: "Criar uma variável de tipo long com valor 300". A segunda linha significa: "Criar uma variável long soma = 300; de tipo ponteiro cujo valor é o endereço da variável soma". long *pointSoma = &soma; Observação. Não existe um tipo ponteiro como existem os tipos int, long, etc. Então, não podemos escrever, por exemplo, ponteiro pointSoma; em seu lugar escrevemos long *pointSoma; como visto acima. Isto quer dizer que a variável pointSoma é um apontador para uma variável de tipo long. Se a variável soma fosse de tipo int, então pointSoma deveria ser um apontador para uma variável de tipo int e escreveríamos assim: int *pointSoma; long *p1 = NULL; 250 Representação gráfica. soma pointSoma Endereço Valor 0 145 … 3.8028322 135 300 … 39014768 15648 135 n-1 940.5118 No esquema ao lado verificamos que a variável soma está alocada na posição 135 da memória enquanto que a variável pointSoma esta alocada na posição 15648 dessa memória. Observemos que o conteúdo da posição 15648 é exatamente o endereço da variável soma, isto é, 135. Esses valores variam de computador para computador e a cada execução do programa. Bem, acabamos de entrar no mundo maravilhoso dos ponteiros. Estudemos o programa fonte abaixo: /* ponteiros1*/ #include<stdio.h> main() { int *pontx = NULL; int x; printf("Entre com um valor inteiro x: "); scanf("%d",&x); printf("\nendereco de x = %p",&x); printf("\nvalor de x = %d",x); pontx=&x; printf("\nendereco de x = %d",pontx); getch(); return 0; } • • • pontx é um ponteiro para uma variável do tipo int &x é o endereço da variável x pontx=&x; o endereço de x é armazenado em pontx (variável ponteiro) A atribuição do endereço de x a pontx poderia ter sido feita assim também: int *pontx = &x; Execute o programa ponteiros1. E como faríamos para imprimir o conteúdo da variável apontada por pontx? Experimente incluir a instrução abaixo no código do programa ponteiros1. printf("\nvalor de x = %d", *pontx); Como podemos verificar pela execução do programa ponteiros1, o valor impresso é o mesmo que o de x. Então a expressão *variavelp onde variavelp é uma variável ponteiro, indica o conteúdo da variável apontada por variavelp. Em resumo, dado o trecho de programa abaixo, temos: x ...................... int x=100, *p=NULL; p=&x; …………… 35846 100 p 16315 35846 • x é uma variável de tipo int [valor de x = 100] • &x é o endereço da variável x [endereço de x = 35846] • p é uma variável que aponta para uma variável de tipo int, seu valor é um endereço [valor de p = 35846] • &p é o endereço da variável p [endereço de p = 16315] 251 • *p indica o conteúdo da variável apontada por p, isto é, o valor de x [valor de *p = 100] II. Enviar um ponteiro a uma função O grande interesse dos ponteiros, mas não o único, consiste em enviá-los a uma função, como parâmetro claro, de modo que a função possa modificar diretamente uma variável em memória e não apenas sua cópia. Como isto funciona? Vejamos um primeiro exemplo. void triplicar(long *ponteiroN); int main(int argc, char *argv[]) { long numero = 20; triplicar(&numero); // Passa o endereço de número para a função printf("%d", numero); // Exibe o valor de número getch(); return 0; } void triplicar(long *ponteiroN) { *ponteiroN *= 3; // Multiplica por 3 o valor da variável numero } A função triplica possui um parâmetro de tipo long* (isto é, um ponteiro sobre o tipo long). Vejamos o que se passa. 1. Uma variável "numero" é criada na função main e lhe é atribuído o valor 20. 2. A função triplicar é chamada e lhe é passado o endereço da variável numero. 3. A função triplicar recebe esse endereço na variável ponteiroN. O conteúdo da variável ponteiroN passa então a ser o endereço de "numero". 4. Como a função triplicar possui um ponteiro para a variável numero, poderá modificar diretamente o valor dessa variável na memória. Para isto basta utilizar *ponteiroN para designar a variável numero. Na função triplicar utilizamos uma operação de multiplicação *ponteiroN *= 3; para multiplicar por 3 o valor de numero. 5. A instrução printf("%d", numero); na função main imprimirá 60 como o novo valor de numero. O interesse na utilização de ponteiros se justifica pelo fato de podermos modificar o valor de várias variáveis na memória do computador, o que na verdade é uma simulação do retorno de vários valores por uma função. Então, não estamos mais limitados a escrever funções que retornem um único valor. Interessante não? Enfim, tudo depende do programa que escrevemos. Às vezes é necessário retornar um valor por uma função, um código que deverá ser testado na função main, por exemplo. III. Outro modo de enviar um ponteiro para uma função No código fonte que vimos não havia ponteiro na função main. Entretanto, é possível passar um ponteiro para uma função em sua chamada, como um parâmetro real. Vejamos outra versão do programa anterior. 252 void triplicar1(long *ponteiroN); int main(int argc, char *argv[]) { long numero = 20; long *p = № // p recebe o endereço de numero triplicar1(p); // envia p (endereço de numero) a função triplicar1 printf("%d", *p); // exibe o valor de numero getch(); return 0; } O importante é que o endereço da variável numero tenha sido passado para a função triplicar1. A diferença entre as duas versões é que na primeira foi passado o endereço de numero para a função triplicar e na segunda um ponteiro foi transmitido. Na verdade, as duas versões fazem a mesma coisa, transmitem um endereço. No início desta unidade dissemos que mesmo sem saber já utilizávamos o conceito de ponteiro. Agora podemos passar isso a limpo. Trata-se da função scanf. Ao utilizá-la, devemos informar um endereço de modo que o valor teclado seja armazenado nesse endereço. Só pra lembrarmos acompanhemos o texto abaixo. long numero = 0; scanf("%d", &numero); 3.3 Um problema estranho? No início desta unidade falamos sobre certo problema estranho. É hora de tentarmos resolvê-lo. Afinal de contas esse problema é mesmo estranho? Tratava-se de escrever uma função que retornasse 2 ou mais valores. Agora temos as ferramentas para solucioná-lo. O problema consistia em transformar segundos em horas, minutos e segundos. Mãos à obra. Apresentamos abaixo uma solução para esse problema. É bom que o leitor tente encontrar sua própria solução antes de verificar a que segue. void novotempo(long* pontHoras, long* pontMinutos, long* pontSegundos) { /* Não esquecer de colocar um * antes do nome do ponteiro para poder modificar o valor das variáveis e não os seus endereços. */ *pontHoras = *pontSegundos / 3600; *pontMinutos = (*pontSegundos % 3600)/60; *pontSegundos = (*pontSegundos % 3600) % 60; } 253 /* programa novotempo */ #include<stdio.h> void novotempo(long* pontHoras, long* pontMinutos, long* pontSegundos); int main(int argc, char *argv[]) { long horas = 0, minutos = 0, segundos=0, segtemp=0; // Entrar com o número de segundos printf("Digite o numero de segundos a transformar: "); scanf(("%", &segundos); segtemp=segundos novotempo(&horas, &minutos, &segundos); // Os valores horas, minutos e segundos foram modificados! printf("\n%d segundos corresponde a:\n", segtemp); printf("%d horas\n", horas); printf("%d minutos\n", minutos); printf("%d segundos\n", segundos); getch(); return 0; } Ao digitarmos o valor 27000 para a variável segundos na execução do programa acima, teremos a seguinte saída: Exercício. Procure explicar como os procedimentos foram realizados no programa novotempo. Explicandoos de forma coerente significa que o conceito de ponteiros foi bem assimilado. 3.4 Resumo Os ponteiros não são simples de trabalhar, reconheçamos. Não nos preocupemos muito com isto agora. É difícil pra todo mundo. O melhor que temos a fazer é repassar esta unidade tantas vezes quanto necessário a fim de consolidarmos este conceito. E para terminar, mais um pequeno resumo. Em C podemos criar duas coisas na memória: variáveis e ponteiros. • variáveis: é com elas que temos trabalhado até aqui. Criar uma variável é muito simples: basta indicar seu nome e seu tipo. long minhaVariavel = 0; // variável criada na memória (valor 0) Se escrevemos &minhaVariavel, obtemos o endereço da variável na memória • ponteiros: são variáveis um pouco particulares, pois elas tomam por valor o endereço de outras variáveis. Para criar um ponteiro vazio (que não contém endereço de nenhuma variável) escrevemos assim: 254 long *meuPonteiro = NULL; Um ponteiro torna-se útil quando lhe atribuímos por valor o endereço de uma variável. long *meuPonteiro = &minhaVariavel; • observemos que *meuPonteiro funciona exatamente com se tivéssemos escrito minhaVariavel. printf("%d", *meuPonteiro); é então equivalente a printf("%d", minhaVariavel); O resultado é exatamente o mesmo salvo que no primeiro caso passamos por um ponteiro para acessar a variável. 3.5 Teste 1. Qual dos tipos abaixo corresponde a um ponteiro? • ( ) int • ( ) double* • ( ) long 2. Se digitamos &bola, o que obtemos? • ( ) O endereço de bola • ( ) O valor de bola • ( ) O valor da variável apontada por bola 3. Se digitamos *total, o que obtemos? • ( ) O endereço de total • ( ) O valor de total • ( ) O valor da variável apontada por total 4. Por qual valor devemos inicializar um ponteiro? • ( ) NOTHING • ( ) NULL • ( ) MAIN • ( ) ADDRESS 5. Examine o código seguinte: long num = 25; long *ponteiro = # Supor que num se encontre no endereço 1400 da memória do computador e ponteiro se encontre no endereço 3800. Se na seqüência do programa for solicitada a impressão de *ponteiro, que valor será exibido na tela? • • • • • ( ( ( ( ( ) 3800 ) 25 ) 1400 )0 ) impressão impossível 4. Avaliando o que foi construído O conceito de ponteiro é considerado por muitos como aquele em que o programador leva mais tempo para assimilar. Devemos considerar sua grande importância dentro da programação, pois somente com a sua utilização podemos trabalhar com estruturas de dados dinâmicas, aquelas podem aumentar ou diminuir durante a execução de um programa dependendo unicamente da disponibilidade de memória. As principais 255 estruturas de dados dinâmicas utilizadas em computação são as filas, pilhas e árvores. O estudo dessas estruturas foge ao escopo deste texto, mas poderão ser encontradas nos livros sobre estruturas de dados. 5. Referências 1. Ascencio, Ana F G., Campos, Edilene A V., Fundamentos da Programação de Computadores: Algoritmos, Pascal, C/C++ e Java, 2ª edição, Pearson Education, 2007. 2. Hutchison, Robert C., Just, Steven B., Programming using the C language. MGraw-Hill Book Company, 1988. 3. Kernighan, Brian W., Ritche, Dennis M., Le langage C, Masson, 1986. 256 Unidade X - Arrays 1. Situando a temática Nesta unidade aprenderemos a criar variáveis do tipo array. Os arrays são muito utilizados em C, pois eles são muito práticos. Começaremos explicando o funcionamento dos arrays na memória de um computador. A compreensão desse funcionamento conduz o programador a construir programas mais confiáveis. Afinal de contas é sempre bom compreender o porquê das coisas. 2. Problematizando a temática Ao trabalharmos com um conjunto de valores, podemos fazê-lo de modo a utilizar cada valor uma única vez. Se for este o caso, à medida que esses valores forem sendo lidos o processamento necessário é executado e ficaremos satisfeitos. O que ocorre na prática porém é diferente. Imaginemos que se deseje calcular a distância entre cada elemento de um conjunto e a média aritmética desses valores. Obviamente, precisaremos de cada valor para calcular a média. Em seguida, precisaremos outra vez de cada valor para calcular as diferenças. E onde estão esses valores agora? Se o conjunto possuir 100 elementos, por exemplo, podemos dar um nome a cada elemento, mas isso seria um trabalho enorme. Os arrays permitem resolver este tipo de problema sem maiores preocupações. 3. Conhecendo a temática 3.1 Os arrays e a memória Arrays são seqüências de variáveis de mesmo tipo, situadas em posições contíguas na memória. Concretamente, trata-se de várias variáveis de mesmo tipo, portando o mesmo nome e diferenciadas apenas por um ou mais índices. O número de índices de uma variável determina a dimensão do array a que ela pertence. Assim, se para identificar uma determinada variável precisamos utilizar dois índices, então essa variável pertence a um array de duas dimensões. Geralmente chamamos de elementos as variáveis de um array. Exemplo. Seja um array de 4 elementos alocado na posição (endereço) 1800. Dizer que um array está armazenado no endereço 1800 é apenas uma forma de nos expressarmos. Na verdade, o array começa nessa posição. Vejamos o esquema abaixo. Endereço Valor ... 2293600 52 2293601 10 2293602 -65 2293603 94 Ao solicitarmos em um programa a criação de um array com 4 elementos, o programa solicita ao sistema operacional espaço suficiente na memória para a alocação desse array. Os quatro elementos devem ficar em endereços contíguos, um vizinho ao outro, e devem ser todos de um mesmo tipo. Em resumo, sobre os arrays devemos guardar que: • Quando um array é criado, ele ocupa um espaço contíguo na memória: os endereços ocupados estão em seqüência, não existe “buraco” entre eles. • Todos os elementos de um array são de um mesmo tipo. Um array de int conterá apenas variáveis de tipo int e nada mais. ... 257 3.2 Declarando um array Como sabemos todas as variáveis em C devem ser declaradas antes de serem usadas. Comecemos com um exemplo. Vamos declarar um array de 4 elementos de tipo long. long x[4]; É suficiente colocar entre colchetes o número de elementos do array. Neste caso, x é o nome do array. Não existe limite para o número de elementos de um array, salvo a memória disponível. 3.3 Como acessar um elemento de um array Para acessar um elemento de um array devemos escrever: nomeDoArray[n] onde n é a posição (índice) do elemento dentro do array. Atenção. Os elementos de um array são indexados a partir de 0 (zero). Assim, os elementos do array x declarado acima são referenciados por: x[0], x[1], x[2] e x[3]. Observe que não existe o elemento de índice 4 na declaração acima. Uma tentativa de fazê-lo resulta em um erro de execução. 3.4 Atribuindo valores a um array Para atribuir um valor a um elemento de um array é necessário fazer uma referência ao elemento desejado e atribuir-lhe um valor como fazemos com qualquer outra variável. Lembremo-nos que cada elemento é uma variável. Exemplo. long x[4]; x[0] = 52; x[1] = 10; x[2] = -65; x[3] = 94; 3.5 Arrays e ponteiros Existe alguma relação entre arrays e ponteiros? Se escrevermos apenas o nome do array, sem índice, teremos um ponteiro. Vejamos o programa ao lado. Resultado: temos o endereço 2293600 onde começa o array x. Ao solicitarmos a impressão de x obtivemos o endereço do primeiro elemento do array x, portanto o valor de x é o endereço de outra variável, no caso x[0]. Lembremonos do conceito de ponteiro. Se, no lugar de printf("%d", x); tivéssemos escrito printf("%d", x[0]); teríamos obtido como reposta 52, o valor de x[0]. /* Programa array1 #include<stdio.h> int main() { long x[4]; x[0]=52; x[1]=10; x[0]=-65; x[0]=94; printf("%d", x); getch(); return 0; } */ Atenção! Como o nome de um array é um ponteiro, podemos utilizar o símbolo * para conhecer o valor do primeiro elemento de um array. Assim, printf("%d", *x); e printf("%d", x[0]); são equivalentes. Experimente. Também é possível obter o valor do segundo elemento escrevendo *(x+1) (endereço do array +1). Para os demais elementos o raciocínio é o mesmo, *(x+2) referencia o valor do terceiro elemento e assim por diante. 258 3.6 Listando um array Para listar todos os elementos de um array devemos fazer uso de um comando de repetição. Seria muito incômodo escrever um printf para cada elemento. Imagine só se o array possuísse 800 elementos! Vejamos um exemplo com uso do comando for. /* programa array2 */ #include<stdio.h> int main(int argc, char *argv[]) { long x[4], i = 0; x[0] = 15; x[1] = -45; x[2] = 218; x[3] = 14; for (i = 0 ; i < 4 ; i++) { printf("%d\n", x[i]); } getch(); return 0; } A execução deste programa produzirá a seguinte saída: 15 -45 218 14 Nosso laço percorre todo o array com a ajuda de uma variável de controle i. Estude o programa array2 ao lado e procure entender o que se passa. Observe que a variável de controle i introduzida no código vale 0, 1, 2 e 3. Exercício. Modifique o código fonte do programa array2 de modo que a saída seja Listagem do array: 15 -45 218 14 Atenção! Não tente exibir o valor de x[4]. Um array com 4 elementos possui índices 0, 1, 2 e 3. Se tentarmos exibir x[4] ocorrerá um erro de execução e o sistema operacional encerrará o programa avisando que o este tentou acessar um endereço que não lhe pertence. 3.7 Inicializando um array Agora que sabemos percorrer um array somos capazes de inicializar todos os seus elementos com zero, por exemplo, com o uso de um laço. Tente fazê-lo antes de ver o programa ao lado. A saída deve ser algo como: x=0000000000 Outro modo de inicializar um array Podemos também inicializar um array de modo mais automático. Essa técnica consiste em escrever x[4]={valor1, valor2, valor3, valor4} quando da declaração do array. De modo geral, basta escrever os valores entre chaves e separados por vírgula. /* programa array3 – inicializa um array */ int main(int argc, char *argv[]) { long x[10], i = 0; // inicialização do array for (i = 0 ; i < 10 ; i++) { x[i] = 0; } // verificação – exibição dos valores de x for (i = 0 ; i < 10 ; i++) { printf("x = "); printf("%d ", x[i]); } getch(); return 0; } Melhor que isso, se fizermos x[4]={10, -32}, os dois primeiros elementos serão iniciados com 10 e -32, respectivamente e os demais serão inicializados com zero. Exercício. Como inicializar todos os valores a zero utilizando a técnica acima? 3.8 Passagem de arrays como parâmetros Nós certamente teremos necessidade de imprimir todos os valores de um array. Por que não escrevermos uma função para executar essa operação? Isto nos permitirá ver como passar um array para uma função, como parâmetro real, claro. 259 Será necessário passarmos duas informações para a função, o array (seu endereço) e o seu tamanho. De fato, nossa função deverá ser capaz de inicializar um array de qualquer tamanho, por isso a necessidade de conhecer o tamanho do array. Vejamos um código para isso. #include<stdio.h> // Protótipo da função de impressão void imprimir(long *v, long comp); int main(int argc, char *argv[]) { long vetor[4] = {10, 15, 3}, i = 0; // imprimir o conteúdo do vetor imprimir(vetor, 4); getch(); return 0; void imprimir(long *v, long comp) { long i; for (i = 0 ; i < comp ; i++) { printf("%d\n", v[i]); } } } Observemos que: 1. vetor em main é um ponteiro 2. v em imprimir é um ponteiro A função imprimir toma como parâmetro um ponteiro para o tipo long e o tamanho de um array para saber até onde estão os seus elementos. Importante: existe outro modo de indicar que uma função recebe array. Em vez de indicar que a função espera um long *v, podemos escrever assim: void imprimir(long v[], long comp) Este comando é equivalente ao usado no código apresentado acima, mas a presença dos colchetes lembra ao programador que a função está aguardando um array. Isto permite evitar confusões. Esta forma é bastante utilizada. Exercícios É importante escrever várias funções que trabalhem sobre arrays para que nos tornemos “práticos” sobre este assunto. 1. Criar uma função de nome somaVetor que retorne a soma de todos os elementos de um array. Utilize um return para retornar o valor calculado. Como incentivo aqui está o protótipo da função somaVetor. long somaVetor(long vetor[ ], long comp); 2. Criar uma função mediaVetor que calcule e retorne a média aritmética dos (Atenção. O valor de uma média é sempre um número decimal). elementos do vetor. 3. Criar uma função copiarVetor que tenha como argumentos 2 arrays. O conteúdo do primeiro array deverá ser copado para o segundo. Protótipo: void copiarVetor(long v1[ ], long v2[ ], long comp); 4. Criar uma função maxVetor1 que deverá atribuir o valor 0 a cada elemento do array que lhe é passado como parâmetro, desde que esse elemento tenha um valor que seja superior a um valor máximo também recebido por parâmetro. Protótipo: void maxVetor1(long v[ ], long comp, long max); 5. Criar uma função ordenaVetor que classifique os elementos de um array em ordem crescente. Se um array inicialmente possui os elementos {21, 30, 17, 8}, nesta ordem, ao fim da operação ele deverá se apresentar como {8, 17, 21, 30}. 260 3.9 Arrays de duas ou mais dimensões Para utilizarmos arrays de duas ou mais dimensões precisamos declará-los como fazemos para qualquer outra variável. A declaração de um array de duas dimensões é semelhante à declaração de arrays com uma única dimensão. Devemos indicar explicitamente quantos elementos existem em cada dimensão. Vejamos como declarar um array de inteiros de nome X, com 10 elementos em uma dimensão e 8 elementos em outra: int X[10][8] ;. Simples não? Os arrays de uma e duas dimensões recebem nomes especiais. Os arrays de uma dimensão são chamados de vetor e os de duas dimensões recebem o nome de matriz. Exemplo de programação usando matriz. Vamos escrever um programa para preencher uma matriz de nome Nota, que represente as notas de 10 alunos de uma turma em 3 disciplinas. Trata-se de distribuir essas notas em uma estrutura de duas dimensões. Acompanhe o nosso raciocínio. Consideraremos uma matriz de 3 linhas, cada linha representando uma disciplina, e 10 colunas, uma para cada nota de um aluno. Nossa estrutura tem a seguinte aparência. notas disciplinas Como imaginamos, a matriz deve ser declarada como int Nota[3][10]. Atribuindo valor aos elementos de um array A atribuição de valores aos elementos de uma matriz se dá do mesmo modo que para as outras variáveis já vistas, ou seja, podemos fazê-lo por atribuição direta ou por leitura via teclado (como uso da função scanf). Não podemos esquecer que as matrizes possuem dois índices. Assim, podemos escrever Nota[0,3]=8,5; para indicarmos que o aluno 3 obteve 8,5 como nota na disciplina 1. As disciplinas serão representadas por 0, 1, 2 (linha 0, linha 1, linha2). Não se desespere. Lembre-se que os índices dos arrays começam em 0(zero). Por isso, a primeira linha tem índice 0, a segunda índice 1, e assim por diante. O mesmo ocorre com relação às colunas. Então o elemento da terceira linha e sexta coluna é representado por Nota[2,5]. A atribuição de valores via teclado se dá como auxílio de um comando de repetição. Lembremos do que já foi dito antes: cada conceito estudado é importante e deve ser acumulado, para uso #include<stdio.h> futuro, à medida que os programas vão ficando mais complexos. int main(){ Vejamos um modelo para a leitura de dados via teclado e atribuição int i,j; aos elementos de uma matriz. int X[3][4]; for (i=0; i<3; i++) Leia o código ao lado e procure compreender cada passo. { Evidentemente este programa lê uma matriz 3x4, de inteiros de printf("Leitura da linha %d\n", i); nome X. Escreva outro programa para realizar a mesma tarefa de for (j=0; j<4; j++) modo que os dados de entrada apareçam na tela assim : { printf("elemento %d ", j); Primeira linha : 10 23 15 17 scanf("\n%f", X[i,j]) ; Segunda linha : 76 56 29 2 } Terceira linha : -5 43 -2 45 } getch(); Observe bem, após a leitura de um número, não há mudança de return 0; linha, a menos que esse número seja o último da sua linha. } Na plataforma Moodle, o aluno encontrará vários outros exemplos com matrizes assim como muitos exercícios. 261 3.10 Teste 1. Qual das linhas abaixo cria um array de double com 10 elementos? ( ) double* vetor[10]; ( ) double vetor{10}; ( ) double vetor[10]; ( ) double vetor[9]; 2. Qual o valor do primeiro índice de um array? ( )0 ( )1 ( ) -1 3. Qual dos protótipos abaixo não permite a passagem do array vetor como parâmetro? ( ) void f(long vetor[], long comp); ( ) void f(long vetor, long comp); ( ) void f(long *vetor, long comp); 4. Qual o outro modo de inicializar o array vetor com estes valores? long vetor[4]; vetor[0] = 10; vetor[1] = 23; vetor[2] = 505; vetor[3] = 8; ( ) long vetor[4] = 10, 23, 505, 8; ( ) long vetor[4] = [10, 23, 505, 8]; ( ) long vetor[4] = (10, 23, 505, 8); ( ) long vetor[4] = {10, 23, 505, 8}; Para não esquecer. • • Não esquecer JAMAIS que um array começa no índice 0 e não no índice 1. Quando passar um array para uma função, como parâmetro claro, passar TAMBEM o seu tamanho, senão a função não poderá reconhecer o tamanho do array. 4. Avaliando o que foi construído Concluímos este texto com uma apresentação sobre os arrays. Esse tipo de dados possui larga utilização na solução de problemas que necessitam a reutilização de variáveis dentro de um mesmo programa. Conforme visto, os arrays são utilizados na implementação de vetores e matrizes em C. Existem outros tipos de dados que deverão ser estudados por aqueles que pretendem aprofundar-se no estudo desta linguagem. O tratamento de caracteres, registros e arquivos (files) são assuntos fortemente recomendados para leitura. A base de programação adquirida até aqui é suficiente para que o aluno continue o seu desenvolvimento em programação, agora de forma autônoma. 262 5. Referências 1. Ascencio, Ana F G., Campos, Edilene A V., Fundamentos da Programação de Computadores: Algoritmos, Pascal, C/C++ e Java, 2ª edição, Pearson Education, 2007. 2. Hutchison, Robert C., Just, Steven B., Programming using the C language. MGraw-Hill Book Company, 1988. 3. Kernighan, Brian W., Ritche, Dennis M., Le langage C, Masson, 1986. 263