Apostila
Introdução à Programação
Núcleo de Cidadania Digital
INTRODUÇÃO À PROGRAMAÇÃO
Vitória 2017
Presidente da República Michel Temer Ministro da Educação José Mendonça Bezerra Filho UNIVERSIDADE FEDERAL DO ESPÍRITO SANTO Reitor Reinaldo Centoducatte Realização Núcleo de Cidadania Digital Produção de Texto Káio Simonassi, Jonas Mendes Fiorini, Marinês de Jesus Rocha Barros, Mariana Marchiore Birro, Iagoh Ribeiro Lima, Fernando Diniz, Vinícius Risso, Willian Abreu Ferreira Capa e Projeto Gráfico Barbara Coutinho da Silva
Vice-reitora Ethel Leonor Noia Maciel Impressão Gráfica Universitária Revisão Raquel Dutra, Ester Barreto Bento, Paolla Ferreira, Leonam Gonçalves Pereira, Sérgio Rossi Júnior, Valdemar Traspadini Júnior, Marini Ignácio Favero Editoração e Ilustração Paulo Henrique Lagass Barbosa da Silva
NÚCLEO DE CIDADANIA DIGITAL Av. Fernando Ferrarri, 514, Centro de Vivências, Campus Goiabeiras, Vitória - ES Telefone: (27) 4009-2048 www.ncd.ufes.br
Dados Internacionais de Catalogação-na-publicação (CIP) (Biblioteca Central da Universidade Federal do Espírito Santo, ES, Brasil) I61
Introdução à programação / Núcleo de Cidadania Digital, UFES. Vitória, ES : Universidade Federal do Espírito Santo, Núcleo de Cidadania Digital, 2017. 124 p. : il. ; 30 cm 1. Programação (Computadores). 2. Tecnologia da informação. I. Universidade Federal do Espírito Santo. Núcleo de Cidadania Digital. CDU: 004.42
Programação
SUMÁRIO Apresentação
9
Guia da Apostila de Programação
10
Capítulo 1 - Conceitos Fundamentais
11 11 11 12 12 12 13 14 16 17 17 19
Capítulo 2 - Computador e Programação
20 20 21 21 22 23 23 23 23 24 24 26 26 27 27 28
Capítulo 3 - Linguagem C
29 29 31 32 33 34
1.1 - Por que estudar programação? 1.2 - O que é um Algoritmo? 1.3 - Fluxogramas 1.4 - Entradas e Saídas (Input / Output) 1.5 - Operadores Numéricos 1.6 - Operadores Relacionais 1.7 - Operadores Lógicos 1.8 - Variáveis 1.9 - Controle de Fluxo 1.9.1 - Estruturas Condicionais 1.10 - Exercícios
2.1 - Números Binários 2.2 - Bits e Bytes 2.3 - Tabela ASCII 2.4 - Arquitetura de Computadores 2.4.1 - O processador 2.4.2 - Memória Principal 2.4.3 - Memória Secundária 2.4.4 - Dispositivos de Entrada e Saída 2.5 - Linguagens de Programação 2.5.1 - Níveis de Linguagens 2.5.2 - O Compilador 2.6 - Linux 2.6.1 - Executando seu código no Linux 2.6.2 - Comandos básicos usados no terminal 2.7 - O Bloco de Notas
3.1 - Compilar no Linux - GCC 3.2 - Estrutura Básica do Programa 3.2.1 Declaração/Importação de bibliotecas: 3.2.2 - Função main() (Função Principal) 3.2.3 - Chaves { } e Ponto e Vírgula
Programação 3.3 - Variáveis em C 3.3.1 - Nomes de Variáveis 3.3.2 - Palavras Reservadas de C 3.3.3 - Tipos de Variáveis 3.3.3.1 - Modificadores de Tipos de Variáveis 3.3.4 - Declarar uma variável 3.4 - Exercício 3.5 - Atribuição de Valores às Variáveis 3.6 - Expressões Aritméticas 3.6.1 - Precedência de Operadores Matemáticos 3.6.2 - Operador resto (%) 3.6.3 - Exercício 3.6.4 - Biblioteca math.h 3.6.5 - Exercício 3.7 - Funções de Entrada e Saída 3.7.1 - Printf() 3.7.2 - Exercício 3.7.3 - Scanf() 3.7.8 - Exercício 3.8 - Boas Práticas de Programação 3.9 - Exercícios
34 34 35 36 37 39 40 41 43 44 45 45 46 48 48 48 51 51 52 52 53
Capítulo 4 - Estruturas Condicionais
54 54 57 57 59 60 62 64 65
Capítulo 5 - Estruturas de Repetição
66 67 68 69 71 71
Capítulo 6 - Funções
73 73 74
4.1 - Operadores de Comparação 4.2 - Comando de decisão If 4.2.1 - Sintaxe If 4.2.2 - Estrutura condicional if else 4.2.3 - Estrutura condicional if aninhado 4.3 - Comando de decisão switch 4.4 - Exercícios 4.5 - Boas Práticas de Programação
5.1 - While 5.1.1 - Exercícios 5.2 - For 5.2.1 - Exercícios 5.3 - Do while
6.1 - Modularização 6.2 - Características de Funções
Programação
6.3 - Como fazer uma função em C? 6.4 - Casos de Funções 6.5 - Exemplo de código em C 6.6 - Escopo de variáveis 6.7 - Boas práticas de programação 6.8 - Exercícios
76 77 81 82 83 83
Capítulo 7 - Vetores
85 86 87 88 88 89 89 90
Capítulo 8 - Vetores II
92 92 94 95 99 99
7.1 - Acesso e Atribuição 7.2 - Definição de tamanho em tempo de execução 7.3 - Vetores e funções 7.3.1 - Passando vetores como parâmetros de uma função 7.3.2 - Vetores como “retorno” de uma função 7.4 - Cuidados ao se utilizar vetores 7.5 - Exercícios
8.1 - Strings 8.1.1 - Exercícios 8.2 - Matrizes 8.2.1 - Exercícios 8.3 - Boas Práticas de Programação
Capítulo 9 - TAD’s
100 101 103 105 105 106
Capítulo 10 - Ponteiros
109 109 110 111 111 112 113 114 114
9.1 - Struct 9.2 - Struct’s e funções 9.2.1 - Exercícios 9.3 - Structs mais complicadas 9.4 - Makefile
10.1 - Definição 10.2 - Atribuição de variáveis 10.3 - Aritmética de ponteiros 10.3.1 - Utilizando o operador * 10.3.2 - Utilizando o operador & 10.3.3 - Exercícios 10.4 - Operações com Ponteiros 10.4.1 - O que acontece quando incrementamos e/ou decrementamos um ponteiro? 10.4.2 - O que acontece quando imprimimos um ponteiro não inicializado?
115
Programação 10.4.3 - Como atribuo o valor de um ponteiro a outro? 10.4.4 - O que acontece se eu tiver dois ponteiros apontando para o mesmo lugar e alterar o conteúdo de um deles? 10.4.5 - Posso ter um mesmo endereço de memória com mais de um valor? 10.5 - Vetores e ponteiros 10.6 - Ponteiros e Funções 10.6.1 - Passagem por Valor 10.6.2 - Passagem por Referência 10.7 - Ponteiros e Struct’s 10.8 - Exercícios
116 118
118 119 120 121 121 122 123
Programação
Apresentação O Núcleo de Cidadania Digital é um Programa de Extensão da Universidade Federal do Espírito Santo. Foi inaugurado em 26 de agosto de 2005, graças a uma parceria firmada entre a Petrobras e a UFES, tendo como intuito principal promover inclusão sociodigital de maneira inovadora no estado do Espírito Santo. Em abril de 2007, o NCD fez uma parceria com a Prefeitura de Vitória, por meio da qual entrou para a rede de Telecentros do Programa Casa Vitória, fazendo com que os serviços prestados fossem melhorados aos nossos usuários. Essas parcerias foram finalizadas, mas o NCD se manteve com o apoio da PROEX-UFES. Devido ao alto custo dos sistemas proprietários e, também, visando entre outros aspectos a alta confiabilidade dos sistemas livres em geral, trabalhamos na certeza de que a verdadeira inclusão sociodigital deve ser baseada em conceitos universais de forma que os incluídos possam utilizar o computador indiferentemente do sistema instalado. O NCD adota a filosofia da utilização de Softwares Livres. Uma contribuição importante do NCD para com a comunidade é a disponibilização dos materiais bibliográficos e dos sistemas computacionais, como o sistema de cadastramento, que é desenvolvido pela própria equipe do NCD. Esta apostila de Programação foi desenvolvida em parceria com o projeto Introcomp, que é mantido e gerenciado pelo PET - Engenharia de Computação. E tem como objetivo introduzir os conhecimentos de programação.
Núcleo de Cidadania Digital | NCD
9
Programação
Guia da Apostila de Programação Seja bem-vindo à Apostila de Programação do Núcleo de Cidadania Digital. Nesta apostila iremos apresentar a você o incrível mundo da programação. É impossível pensar em avanços tecnológicos sem relacioná-los com linhas de códigos que descrevem centenas de operações, como você entenderá no decorrer desta apostila. Desde aparelhos eletrônicos até aplicativos de smartphones, a ciência da programação está aí para possibilitar a interação entre o homem e a máquina da melhor maneira possível. Esta apostila tem o propósito de ensinar a lógica geral da programação e a utilização da linguagem C, uma das mais utilizadas atualmente. Este guia tem o propósito de sugerir a diferentes tipos de estudantes uma linha de estudo a ser seguida. Se você nunca estudou programação e tem vontade de aprender desde o início os conceitos fundamentais desta ciência, nada mais óbvio do que começar a leitura pelo primeiro capítulo. Seguindo esta orientação, você não perderá informações contidas nos primeiros capítulos e que serão essenciais no decorrer do curso. Se você já teve a oportunidade de aprender alguma linguagem de programação e os conceitos básicos de computação já estão mais que fixados, nossa sugestão é que você dê um salto de alguns capítulos e já comece o seu estudo pelo Capítulo 3. Este capítulo é o início do estudo da Linguagem C. Se você já possui domínio da Linguagem C e está aqui somente para relembrar algum assunto, sugerimos a busca do tópico em si no nosso sumário, que divide e explicita os assuntos abordados nesta apostila. A equipe do NCD deseja-lhe sucesso nos seus estudos!
10
Núcleo de Cidadania Digital | NCD
Programação
Capítulo 1 - Conceitos Fundamentais Neste capítulo iremos apresentar os conceitos fundamentais da programação: desde o significado de um algoritmo até o uso de operadores matemáticos. Este capítulo é fundamental para que sua base seja bem edificada e seu estudo mais fluido ao longo do curso. Os conceitos aqui aprendidos serão usados ao longo de toda a apostila.
1.1 - Por que estudar programação? Você deve estar se perguntando: qual a importância de se estudar programação? Programação é uma forma de comunicar ao computador o que você deseja que ele faça, desde fórmulas a estruturas de condição, através de códigos. Com isso, é possível gerar tecnologia, seja criando softwares específicos para determinadas rotinas ou até mesmo programando hardware e, quem sabe, até robôs. Além disso, a prática da programação é uma excelente forma de se aprimorar o raciocínio lógico.
1.2 - O que é um Algoritmo? Um algoritmo nada mais é do que uma receita que mostra passo a passo os procedimentos necessários para a resolução de uma tarefa. Ele não responde a pergunta “o que fazer?”, mas sim “como fazer?”. Em termos mais técnicos, um algoritmo é uma sequência lógica, finita e definida de instruções que devem ser seguidas para resolver um problema ou executar uma tarefa. Embora você não perceba, utiliza algoritmos de forma intuitiva e automática diariamente quando executa tarefas comuns. Como estas atividades são simples,dispensam que se fique pensando nas instruções necessárias para fazê-las, sendo assim, o algoritmo presente nelas acaba passando despercebido. Por exemplo, quando vamos fazer um café o algoritmo utilizado é algo como: 12345
Pegar uma panela com água; Levar ao fogo até ferver; Acrescentar o açúcar na água; Pegar o bule e o coador; Colocar o pó na medida desejada no coador;
67 89-
Despejar a água fervente; Após coar todo o café, retirar o coador; Pegar a garrafa de café; Despejar o café na garrafa.
Os algoritmos são muito utilizados na área de programação, descrevendo as etapas que precisam ser efetuadas para que um programa execute as tarefas que lhe são designadas. Existem diversas formas de escrever um algoritmo, podendo ser: em forma de descrição narrativa, como feito com a receita de bolo (não é muito comum na computação), ou na forma de fluxograma.
Núcleo de Cidadania Digital | NCD
11
Programação
1.3 - Fluxogramas Fluxograma é uma forma simples e diagramada do que se espera do algoritmo completo. O objetivo de um fluxograma é mostrar de antemão a lógica usada na solução de um problema.
Figura 1 - Fluxograma preparo de um café
1.4 - Entradas e Saídas (Input / Output) No mundo da programação, entrada significa o valor ou informação que está entrando no programa. Já a saída é o resultado das operações, ou seja, é o que obtemos ao final da execução do programa. No nosso algoritmo do preparo do café, os ingredientes, como o pó de café, água e o açúcar, são as entradas; a saída é o próprio café. É importante ressaltar que em um algoritmo, o conhecimento das entradas é de suma importância para a obtenção da saída.
Figura 2 - Bloco de entrada e saída
1.5 - Operadores Numéricos Na maioria das vezes, quando estivermos fazendo um algoritmo, vamos precisar trabalhar com algum tipo de dado. Dados são informações que normalmente são apresentados em forma de números, mas também podem ser apresentados na forma de letras, palavras, imagens, dentre outros.
12
Núcleo de Cidadania Digital | NCD
Programação Como é bastante comum usarmos dados que tenham valores numéricos, quando estivermos trabalhando com esses valores iremos usar as expressões aritméticas já conhecidas da matemática. Para acostumar-se com os operadores usados em linguagens de programação, vamos usar os operadores mais comuns:
Nome Operador Soma
+
Subtração
-
Multiplicação
*
Divisão real
/
Divisão inteira
/.
Resto da divisão
%
Tabela 1 - Operadores Numéricos A ordem de utilização dos operadores funciona exatamente como na Matemática:
A multiplicação e a divisão precedem a soma e a subtração; conteúdo entre parênteses tem precedência (é resolvido primeiro);
Vale ressaltar que na programação não utilizamos colchetes e chaves nas operações, e sim, múltiplos parênteses, sendo que o parêntese mais interno possui maior precedência. Exemplos: ❏ ❏ ❏ ❏ ❏ ❏
9 + 9 = 182 + 5*3 = 17 (2 + 5)*3 = 21 14 /. 3 = 4 14 / 3 = 4,6666… 14 % 3 = 2 ((14 % (2 + 1))*2) + 5 = 9
1.6 - Operadores Relacionais São os operadores que relacionam duas expressões e têm como resultado valores lógicos, ou seja, o resultado de uma operação relacional irá resultar em verdadeiro ou falso. São eles:
Núcleo de Cidadania Digital | NCD
13
Programação Nome
Operador
Igual
==
Diferente
!=
Maior
>
Maior ou igual Menor Menor ou igual
>= < <=
Tabela 2 - Operadores Relacionais Exemplos: ❏ 12 < 20 ❏ 5 + 3 <=8 ❏ (7+5)*3 < 7+5*3
VERDADEIRO VERDADEIRO FALSO
1.7 - Operadores Lógicos O tipo de dado primitivo mais simples é o chamado tipo lógico. Um dado lógico só pode assumir dois valores (VERDADEIRO ou FALSO). Isso seria equivalente a uma lâmpada, que pode estar acesa (verdadeiro) ou apagada (falso). Os operadores que relacionam esse tipo de dado são chamados de “operadores lógicos”. São eles: Nome
Operador
Conjunção (E / AND)
&&
Disjunção (OU / OR)
||
Negação (NÃO/ NOT)
!
Tabela 3 - Operadores Lógicos
O operador “E” ou “AND” resulta em um valor VERDADEIRO se os dois valores de entrada da operação forem VERDADEIROS, caso contrário o resultado é FALSO.
14
Núcleo de Cidadania Digital | NCD
Programação O operador “OU” ou “OR” resulta em um valor VERDADEIRO se ao menos um dos dois valores de entrada da operação for VERDADEIRO, caso contrário o resultado é FALSO. O operador “NÃO” ou “NOT” é o único operador que recebe como entrada apenas um valor, e sua função é simplesmente inverter os valores, ou seja, se o valor de entrada for VERDADEIRO, o resultado será FALSO e se o valor de entrada for FALSO, o resultado será VERDADEIRO. Abaixo, está montada uma tabela-verdade, essa tabela explicita o resultado de operações lógicas, relacionando os valores A e B de cada linha através do operador especificado na coluna. Ou seja, com A sendo V (verdadeiro) e B também sendo V (verdadeiro) na primeira linha, temos que A&&B (A e B) é verdadeiro, pois ambos são verdadeiros, já na segunda linha, observamos que A sendo F (falso) e B sendo V (verdadeiro) A&&B representado na terceira linha se torna falso.
Tabela 4 - Tabela-verdade para duas entradas A e B Obs.: como muitos termos no mundo da programação aparecem com seus nomes originais em inglês, é bem comum encontrarmos false e true em vez de falso e verdadeiro, respectivamente. Em operações lógicas, devemos lembrar que, no caso do uso de parênteses, a prioridade permanece de dentro para fora. Em casos gerais, temos a seguinte prioridade de operadores: 1. Negação 2. Conjunção (E) 3. Disjunção (OU) Exemplos: ❏ (3 > 5) || (5*3 > 5 + 3) ❏ (3 > 5)&&(5*3 > 5 + 3) ❏ !true && false
VERDADEIRO FALSO FALSO
Núcleo de Cidadania Digital | NCD
15
Programação
❏ !(true&&false) VERDADEIRO ❏ true && false || true VERDADEIRO ❏ true && (false||true) VERDADEIRO
❏ true || false && true VERDADEIRO ❏ (8*5 < 15) || !(8+7 <= 15) FALSO
1.8 - Variáveis Sempre que estivermos fazendo um algoritmo, precisamos armazenar e manipular valores (dados). Esses valores são armazenados e acessados da memória do computador e esse acesso à memória é abstraído no conceito de variáveis. As variáveis podem ser entendidas como “caixas” que podem guardar uma informação (um valor/dado). Para guardarmos um novo valor em uma variável usamos o comando de atribuição. Na notação de um algoritmo, utilizamos uma seta ( ← ) para atribuirmos um valor a uma variável. Abaixo, temos alguns exemplos de como realizar essa atribuição: x ← 5 y ← 6
z ← x+y t ← !false && true
Quando encontramos a seta ( ←) temos que ter em mente a expressão “recebe” o valor seguinte, no caso do nosso exemplo acima, devemos ler as expressões assim:
x ← 5 - “A variável x recebe o valor 5” y ← 6 - “A variável y recebe o valor 6” z ← x+y - “A variável z recebe o valor da soma das variáveis x e y” t ← !false && true - “A variável t recebe o valor da expressão lógica”
A variável sempre estará à esquerda e a expressão à direita. Como visto acima, as variáveis são capazes de armazenar numéricas, relacionais e lógicas, porém muitas vezes anteriormente dado a uma variável e isso além de ser Abaixo segue um exemplo de uma variável tendo seu x ← 4
16
Núcleo de Cidadania Digital | NCD
x←5
o resultado de expressões queremos trocar o valor possível é muito comum. valor anterior sobreposto:
Programação No exemplo acima, o valor inicial da variável era 4 e o valor final da variável passou a ser 5, pois o valor da mesma foi sobreposto na segunda linha. Há a possibilidade também de se utilizar o valor antigo para gerar um novo valor a ser armazenado na variável, como no exemplo abaixo: a ← 3 a ← 7*a
Agora, o novo valor da variável “a” é “21”, pois primeiro é resolvida a expressão da direita para que se faça a atribuição em seguida, ou seja, as operações precedem a atribuição.
1.9 - Controle de Fluxo Para a construção de algoritmos mais elaborados, precisamos ter acesso a mecanismos que permitam controlar o fluxo de execução dos comandos. Por exemplo, é fundamental ter meios para tomar decisões que são baseadas em condições avaliadas ao longo da execução do algoritmo: Quando vamos preparar um café, devemos verificar se a água já está fervendo antes de passar para a próxima etapa. Também precisamos de mecanismos para a construção de procedimentos iterativos, isto é, procedimentos que repetem a execução de uma sequência de comandos por um determinado número de vezes: Para saber se a água já está fervendo, verificamos se aparecem bolhas na superfície da água, caso essa condição não seja cumprida, esperamos mais um tempo e verificamos novamente o aparecimento das bolhas e assim continuamos até que a água esteja visivelmente borbulhando para continuar nosso algoritmo.
1.9.1 - Estruturas Condicionais Estruturas condicionais são utilizadas no algoritmo para estabelecer se o conjunto de instruções que procede a condição será executado. A estrutura a seguir ilustrará de forma mais clara a utilização desta ferramenta. se (condição) então [LISTA DE COMANDOS SE A CONDIÇÃO FOR ATENDIDA] senão [LISTA DE COMANDOS SE A CONDIÇÃO NÃO FOR ATENDIDA] fim-se
Núcleo de Cidadania Digital | NCD
17
Programação Exemplificando: Algoritmo para imprimir na tela “Acesso Liberado” para o usuário com idade igual ou superior a 12 anos: idade_jogador ← leiaUmNumeroDoTeclado( ) se (idade_jogador >=12) então imprima “Acesso Liberado” senão imprima “Acesso Restrito” fim-se Algoritmo que verifica se um número é múltiplo de 17: num ← leiaUmNumeroDoTeclado( ) resto ← num%17 se (resto == 0) então imprima “sim” senão imprima “não” fim-se
18
Núcleo de Cidadania Digital | NCD
Programação
1.10 - Exercícios 1. Resolva as seguintes operações: ❏ (3 + 2*3) + 3*2
❏ !true && false || true
❏ !(false&&true)
❏ (8*5 < 15) || !(8+7 <= 15)
2. Faça um algoritmo que implemente o processo de preparo de um arroz. Quais são as entradas e saídas deste algoritmo? 3. Represente o algoritmo do exercício anterior em um fluxograma da maneira que preferir. 4. Retorne o resultado das variáveis c e d.
a ← 8; b ← 4; c ← ((a*5)+a)/b; d ← a < b && a < 1
5. Crie um algoritmo que verifique se um número é múltiplo de 17 e de 23. 6. Uma escola está querendo montar times para participar de um campeonato de futebol, os alunos serão divididos em 3 categorias: Mirim – até 10 anos, Infantil de 11 a 15 anos e Júnior – de 16 até 19 anos. Crie um algoritmo que dada a idade do aluno, diga a qual categoria ele pertence.
Núcleo de Cidadania Digital | NCD
19
Programação
Capítulo 2 - Computador e Programação Neste capítulo iremos apresentar a você a estrutura básica de um computador, introduzindo conceitos pertinentes ao universo da programação. É de suma importância que esses conceitos sejam fixados ao longo do nosso estudo, pois assim será possível compreender como um código de programação é executado pelo computador e como podemos otimizar o mesmo.
2.1 - Números Binários O conhecido sistema de numeração decimal é composto por dez algarismos para designar quantidades: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Já o sistema binário possui apenas dois: 0 e 1. Os computadores utilizam o sistema binário para realizar cálculos durante o processamento de dados, enquanto os seres humanos usufruem do sistema decimal para fazer contas. Apesar de terem relação, esses sistemas funcionam de forma bem diferenciada. O sistema decimal é muito intuitivo para o ser humano, pois podemos representar nos dedos das mãos (10 números), para o computador utilizar 0 e 1 é muito mais intuitivo, é como se o computador tivesse apenas dois dedos para fazer as contas. Usando a operação de apenas dois dígitos ou estados (sim ou não, verdadeiro ou falso, ligado ou desligado e 0 ou 1, por exemplo), o sistema binário permite que os computadores processem dados com maior efetividade. Qualquer valor diferente desses dois algarismos será desprezado pela máquina, fato que promove maior confiabilidade aos cálculos. O sistema binário permite representar números, caracteres ou símbolos, e realizar operações lógicas ou aritméticas por meio de circuitos eletrônicos digitais (também chamados de portas lógicas). O sistema operacional do computador identifica as combinações numéricas através do valor aplicado pelo programador aos zeros e uns do programa em execução. Assim, a leitura dos códigos binários funciona como um interruptor: quando o computador identifica o 1, a luz acende; ao se deparar com o 0, a luz é apagada (são feitas milhares de leituras por segundo!). Por meio desses sinais, a máquina pode realizar os cálculos e processamentos necessários para transformar o conteúdo codificado em um formato que possamos compreender – seja texto, imagem ou som. Todos os softwares são codificados e armazenados com base no sistema binário. Isso significa que, se pudéssemos abrir o disco rígido do computador e ler o que está escrito nele, veríamos uma lista aparentemente interminável de zeros e uns.
20
Núcleo de Cidadania Digital | NCD
Programação
2.2 - Bits e Bytes Binary digit, ou simplesmente bit, é a menor unidade de informação que pode ser armazenada ou transmitida, usada na computação. Um bit pode assumir somente 2 valores: 0 ou 1. Binary term, ou simplesmente byte, é usado com frequência para especificar o tamanho da memória ou a capacidade de armazenamento de um certo dispositivo, independentemente do tipo de dados. A codificação padronizada de byte foi definida como sendo de 8 bits. Para os computadores, representar 256 números binários é suficiente. Por isso, os bytes possuem 8 bits. Basta fazer os cálculos. Como um bit representa dois valores (1 ou 0) e um byte representa 8 bits, basta fazer 2 (do bit) elevado a 8 (do byte) que é igual a 256, ou seja, 28 =256. É importante ressaltar que além do byte, são utilizados também seus múltiplos que serão apresentados na tabela a seguir. Nome
Símbolo
Múltiplo (bytes) Outras equivalências
Kilo
K
210
-
Mega
M
220
1024 kB
Giga
G
230
1024 MB
Tera
T
240
1024 GB
Peta
P
250
1024 TB
Tabela 5 - Múltiplos em bytes
2.3 - Tabela ASCII A armazenagem e processamento de dados de um computador está totalmente baseado no sistema binário. É necessário codificar os caracteres em binário para que seja possível processá-los. A tabela ASCII (American Standard Code for Information Interchange) é a codificação de caracteres de sete bits baseada no alfabeto inglês. A codificação define 128 caracteres, preenchendo completamente os sete bits disponíveis em 27 =128 sequências possíveis. Desses 128, temos 33 que não são imprimíveis, e são chamados de caracteres de controle. A seguir, apresentamos como os caracteres são organizados na tabela ASCII.
Núcleo de Cidadania Digital | NCD
21
Programação
Figura 3 - Tabela ASCII
2.4 - Arquitetura de Computadores Para melhor entendermos o funcionamento de um computador, iremos apresentar a imagem a seguir.
Figura 4 - Arquitetura de um computador
22
Núcleo de Cidadania Digital | NCD
Programação Um computador é formado pela Unidade Central de Processamento (CPU), memórias, dispositivos de entrada e saída, além de barramentos (“caminhos”) que permitem a comunicação desses componentes.
2.4.1 - O processador Um processador é uma espécie de microchip especializado. A sua função é acelerar, acessar, resolver ou preparar dados, dependendo da aplicação. Basicamente, um processador é uma poderosa máquina de calcular. Ele recebe um determinado volume de dados, orientados em padrão binário 0 e 1 e tem a função de responder a esse volume, processando a informação com base em instruções armazenadas em sua memória interna.
2.4.2 - Memória Principal Memória que o processador pode acessar diretamente, ou seja, o resultado das instruções processadas pelo processador são armazenadas neste tipo de memória, pois seu acesso é rápido. Este tipo de memória é volátil, isto é, os dados aqui armazenados são perdidos assim que o computador é desligado.
2.4.3 - Memória Secundária Este tipo de memória é não-volátil, ou seja, os dados aqui permanecem após o computador ser desligado. Além disso, possui maior capacidade de armazenamento e é mais barata, porém seu acesso é mais lento. Não podem ser acessadas diretamente, isto é, a informação precisa ser carregada na memória principal antes de ser manipulada pelo processador. Exemplos muito comuns de memórias secundárias são: discos rígidos (HD), discos ópticos, fitas magnéticas, etc.
2.4.4 - Dispositivos de Entrada e Saída São dispositivos que permitem a entrada e saída de dados do computador, ou seja, são o meio de interação, que chamamos no âmbito da computação de interface, entre usuário e máquina. São exemplos de dispositivos de E/S o teclado, o mouse, a impressora,o scanner e o monitor.
Núcleo de Cidadania Digital | NCD
23
Programação
2.5 - Linguagens de Programação Agora que você já tem conhecimento de como o computador funciona, podemos apresentar como esse mesmo computador interpreta as instruções que você ordena em um código de programação. Pense que um computador, apesar de ser uma ferramenta extremamente “inteligente”, jamais conseguirá entender suas instruções caso você não as faça com um certo padrão. E quem define este padrão? As linguagens de programação! As linguagens de programação são como idiomas, obedecem uma sintaxe, ou seja, um padrão de escrita. Você não pode escrever seu código de qualquer forma, pois o seu interlocutor, a máquina, não irá entender o que foi escrito. Devido a isso, é necessário que estudemos os principais comandos de programação que uma linguagem possa oferecer. Um computador é capaz de aceitar diversas linguagens de computação devido a uma ferramenta chamada compilador. O compilador funciona como um tradutor. Você escreve o seu código na linguagem que você achar melhor e esse compilador tem a função de traduzir as suas instruções em linguagem de máquina, ou seja, o “idioma” do computador. Em resumo, um computador estritamente recebe “ordens”, processa essas “ordens” e gera uma informação. Tais “ordens” são fornecidas como conjuntos de instruções logicamente ordenadas, de forma padronizada. Uma linguagem de programação é justamente uma forma padronizada de comunicação com um computador. A maioria dos sistemas computacionais atuais operam utilizando a lógica digital binária. É essa a “linguagem” pela qual computadores interpretam instruções e se comunicam. Embora os primeiros computadores fossem programados em linguagem de máquina, a complexidade crescente dos programas obrigou a encontrar-se outras formas de programação.
2.5.1 - Níveis de Linguagens O conceito de máquina multinível surgiu para resolver o problema da disparidade entre o que é conveniente ao computador e o que é conveniente ao ser humano. Entender o computador em níveis, permite abstrair do programador a complexidade eletrônica/ digital envolvida no tratamento de instruções pela máquina. As linguagens de baixo nível são linguagens conhecidas por manter uma forte relação com o hardware e são restritas a linguagem de máquina, além disso, são linguagens complexas de se trabalhar.
24
Núcleo de Cidadania Digital | NCD
Programação As linguagens de alto nível são conhecidas por se aproximarem da linguagem utilizada por serem humanos e, por isso, são mais fáceis de se trabalhar. Cada declaração numa linguagem de alto nível equivale a várias declarações numa linguagem de baixo nível. Abaixo, um comparativo entre as linguagens de baixo nível e alto nível. Alto Nível
Baixo Nível
Facilidade de manipulação, por tratar comandos com linguagens mais próximas do programador.
Dificuldade de programação devido a forte proximidade com a sistemática de hardware.
Facilidade em encontrar erros.
Dificuldade em encontrar erros devido ao nível de detalhes presente no código.
Vantagem de portabilidade.
Totalmente dependente da máquina utilizada.
Lenta se comparada a linguagem de baixo nível.
Vantagem de ser rápida, por operar em um nível próximo aos circuitos eletrônicos de um computador.
Rotinas mais complexas, e por esse motivo, ocupam mais memória.
Vantagem de utilizar menos espaço de memória.
Tabela 6 - Comparativo entre as linguagens de baixo nível e alto nível
É preciso lembrar que embora existam inúmeras linguagens de programação de alto nível, o computador continua a entender e ser capaz de executar somente programas em linguagens de baixo nível, a “linguagem de máquina”. Existem basicamente duas alternativas para esta implementação: interpretação e tradução. Interpretação: as ações indicadas pelos comandos da linguagem são diretamente executadas. Um interpretador é um programa que executa repetidamente a seguinte sequência: 1. Obter o próximo comando do programa. 2. Converter o comando em instruções a nível de linguagem de máquina. 3. Executar estas instruções.
Núcleo de Cidadania Digital | NCD
25
Programação Tradução: um tradutor é uma ferramenta de programação que possibilita traduzir um programa de uma determinada linguagem para uma outra linguagem. Os programas escritos em linguagens de alto nível são traduzidos para versões equivalentes em linguagem de máquina antes de serem executados.
2.5.2 - O Compilador Compilador é uma ferramenta que traduz uma linguagem de alto nível (código fonte)em uma linguagem de baixo nível (código de máquina) na forma de um arquivo executável. Além disso, o compilador também funciona como um verificador de erros, ou seja, ao compilar o seu programa, o compilador aponta onde se encontram os erros de sintaxe do seu código. São exemplos de compiladores: Free Pascal, Turbo Pascal, Turbo C, Visual C, Delphi, Visual Basic entre outros.
Figura 5 - Exemplo da função do compilador
2.6 - Linux Linux é um núcleo de sistemas operacionais desenvolvido por Linus Torvalds, baseado no núcleo Unix. Unix é o núcleo base de muitos sistemas operacionais criado por Ken Thompson, Dennis Ritchie, Douglas McIlroy e Peter Weiner em conjunto com os Laboratórios Bell e MIT. O Linux é um software livre, isto é, seu código fonte está disponível para que qualquer pessoa o possa utilizar, estudar, modificar e distribuir livremente.
Figura 6 - Logotipo do Linux
26
Núcleo de Cidadania Digital | NCD
Programação 2.6.1 - Executando seu código no Linux Muito embora o Linux possua diversas e ótimas interfaces gráficas (GUI’s - Graphical User Interfaces) bastante amigáveis, ainda requerem, por vezes, que façamos uso da linha de comando. O ambiente tradicional do Unix é o CLI (Command Line Interface), o terminal, onde você digita os comandos para dizer ao computador o que ele deve fazer. Esse modo é extremamente poderoso e rápido, porém, implica que você saiba para que serve cada comando e seus diversos parâmetros. É importante frisar que o terminal não é o local onde você irá escrever seu código, ele serve apenas para executar o seu código já compilado ou compilá-lo com o uso de algum programa instalado. Para abrir o terminal pressione Ctrl+ Alt + t no sistema operacional Linux.
Figura 7 - Terminal do Linux
2.6.2 - Comandos básicos usados no terminal pwd - este comando lhe permite saber em qual diretório você está no momento, “print working directory”. ls - lista o conteúdo de um diretório, ou seja, arquivos e subdiretórios. cd - Permite se deslocar entre os vários diretórios do sistema. cd / - ir ao diretório raiz cd - ir ao seu diretório pessoal.
Núcleo de Cidadania Digital | NCD
27
Programação cd .. - acessar um diretório de um nível. cd – - voltar ao diretório que se encontrava. cd /home/desktop - navegar múltiplos níveis de diretórios em um só comando, nesse caso, se vai direto para a pasta “/home/Desktop”, independente de onde se encontrava anteriormente.
2.7 - O Bloco de Notas Para escrever o código de um programa, podemos utilizar a ferramenta de Bloco de Notas presente no Windows com esse mesmo nome e no Linux com o nome Pluma. Porém essas ferramentas não são customizadas para a programação. Recomendamos o software livre Notepad++ e o Geany para o uso como editor. Essa ferramenta possibilita ao programador uma interface amigável, onde é possível organizar melhor seu código, destacando em outras cores palavras chaves do seu código, por exemplo. Além disso, o Notepad++ e o Geany abrangem inúmeras linguagens de programação.
Figura 8 - Área de trabalho do Geany
28
Núcleo de Cidadania Digital | NCD
Programação
Capítulo 3 - Linguagem C Como mencionamos anteriormente, existem inúmeras linguagens de programação, porém, em nosso curso iremos aprender a utilizar a linguagem C para fazermos nossos programas. A linguagem C foi criada e implementada no início dos anos 70 por Dennis Ritchie em um DEC PDP-11 (minicomputador), usando o sistema operacional Unix. C é considerada uma linguagem de nível alto/médio (características que vimos no capítulo anterior). Por ser flexível e ter ferramentas importantes, se tornou uma das linguagens de programação mais utilizadas, servindo para criação de alguns softwares famosos como Microsoft Windows , o Mac OS X e o GNU/Linux e como base para vários jogos.
3.1 - Compilar no Linux - GCC Quando você escreve um programa, ele não faz nada até ser compilado. O compilador que vamos utilizar, ou seja, a ferramenta que irá transformar o código que escrevemos em linguagem de máquina para o computador executar, é o GCC (GNU Compiler C). O GCC é um compilador de linguagem C, C++, Java, entre outras. É distribuído como software livre e usado em computadores Unix e Linux. Já os usuários de Windows que queiram compilar programas em linguagem C podem utilizar o compilador Dev-C++. Agora, iremos aprender passo-a-passo como vamos executar nosso programa no Linux utilizando o GCC. Com o GCC já instalado no computador, podemos usá-lo diretamente do terminal do Linux, então o primeiro passo é abrir o terminal e localizarmos a pasta em que nosso programa foi salvo, fazemos isso com os seguintes comandos:
Figura 9 - Terminal do Linux com os comandos para localizar o programa
Núcleo de Cidadania Digital | NCD
29
Programação Neste caso a pasta que queremos é a “~/Principal”. Caso deseje visualizar todo o conteúdo desta pasta, use o comando dir ou ls .
Figura 10 - Terminal do Linux com o comando para exibir o conteúdo da pasta O próximo passo é compilar nosso programa, para isso digitamos os seguintes comandos e apertamos a tecla [Enter]:
Figura 11 - Terminal do Linux com os comandos para compilar Você não verá mensagem na tela se a compilação ocorrer sem problemas; o compilador só retorna algo quando ocorrem erros.
30
Núcleo de Cidadania Digital | NCD
Programação Por fim, digitamos os seguintes comandos para executarmos o programa que escrevemos e ao final apertamos a tecla [Enter]:
Figura 12 - Terminal do Linux com os comandos para executar
3.2 - Estrutura Básica do Programa Finalmente, vamos aprender como escrever nosso algoritmo na linguagem C. Para começar, precisamos entender que C é uma linguagem estruturada, ou seja, o compilado segue um fluxo linear de compilação, em outras palavras, ele lê nosso código de cima para baixo, uma linha de cada fez, por isso, devemos respeitar uma estrutura de código. Vejamos a seguir:
Figura 13 - exemplo de código básico
Nesse primeiro exemplo de estrutura básica, o código está declarando uma biblioteca e a função principal (main). A seguir, vamos entender o que é isso:
Núcleo de Cidadania Digital | NCD
31
Programação 3.2.1 Declaração/Importação de bibliotecas: A primeira coisa que devemos adicionar ao nosso código são as bibliotecas que iremos usar. As bibliotecas são conjuntos de funções já pré-escritas por outros programadores e nos ajudam a poupar muita programação. Veremos com detalhes nos próximos capítulos sobre o que é uma função. Para podermos entender como funciona a biblioteca, imagine que nosso programa tem a finalidade de exibir uma mensagem na tela, para isso, teríamos que escrever um código que tivesse essa finalidade, mas ao invés disso, basta importar uma biblioteca de I/O (entrada e saída). Quando o programa for compilado, o compilador vai buscar nas bibliotecas a função citada no código e utilizá-la.
Biblioteca 1 - Ex.: stdio.h
Biblioteca 3 - Ex.: stdlib.h
Biblioteca 2 - Ex.: math.h
Figuras 14, 15 e 16 Tipos de bibliotecas
A importação/declaração de uma biblioteca é dada pelo seguinte comando: #include <nomedabiblioteca.h>
O caracter # juntamente com a palavra include, indica que estamos incluindo algo ao programa, e entre os sinais de maior ( > ) e menor ( < ) escrevemos o nome da biblioteca a ser incluida seguido dos caracteres .h que é a extensão do arquivo da biblioteca. Agora sim podemos entender a primeira linha do código do nosso exemplo, onde está escrito #include <stdio.h> estamos adicionando ao nosso programa a biblioteca stdio.h,
32
Núcleo de Cidadania Digital | NCD
Programação que traduzida para o português significa “cabeçalho padrão de entrada/saída”, e como o nome diz, essa biblioteca tem funções de entrada e saída do programa, como imprimir dados na tela, capturar dados do usuários, entre várias outras funções. C também possui outras bibliotecas importantes como a string.h e math.h . Com a biblioteca string.h conseguimos manipular palavras que recebemos como entrada no nosso programa, como por exemplo, copiar a palavra, comparar com outra, etc. Já a biblioteca math.h é muito utilizada em programas com operações matemáticas, pois nela existem várias funções que nos auxiliam, como arredondamento de números, funções trigonométricas, entre outras. Geralmente, vamos precisar utilizar mais de uma biblioteca, então devemos adicioná-las individualmente uma embaixo da outra. Exemplo: #include <stdio.h> #include <math.h> #include <string.h>
Lembrando que se caso não for adicionada a biblioteca que contem a função que iremos utilizar, o programa não será compilado.
3.2.2 - Função main() (Função Principal) No nosso exemplo, vimos que após ser adicionada a biblioteca, adicionamos a função main(), e como o nome mesmo sugere, main traduzida do inglês significa a principal. Todos os programas deverão conter a função main(), ela é o ponto de partida do nosso programa e é por ela que o compilador irá começar a ler o código. Sendo assim, todos os comandos que nosso programa irá executar deverá estar escrito dentro do bloco da função main(), e a partir dela também chamaremos outras funções. Mais a frente, veremos com mais detalhes o que são funções, mas já se pode adiantar que cada função irá retornar um valor, por isso, a declaração da main é dessa forma:
int main()
A palavra int significa inteiro. Escrever isso antes de main() indica que ao final da execução dessa função vamos retornar um valor inteiro, por esse motivo, ao final do bloco da main() escrevemos return 0. Zero neste caso é o número inteiro que estamos retornando. Você deve está pensando para que finalidade a função retorna um valor, mas isso ainda veremos com mais detalhes nos próximos capítulos.
Núcleo de Cidadania Digital | NCD
33
Programação 3.2.3 - Chaves { } e Ponto e Vírgula As chaves, em C, servem para indicar ao compilador O INÍCIO ( { ) e O FINAL ( } ) de um determinado bloco de instruções. O que é um bloco de instruções? É um agrupamento de linhas que deve ter um sentido único. Se voltarmos ao exemplo da estrutura básica, iremos observar que a função main() é iniciada e encerrada com as chaves, assim como todas as funções deverão ser. Já o Ponto e Vírgula é utilizado para indicar o final de uma instrução, ou seja, deverá ser utilizado sempre que uma instrução for finalizada. Sendo assim, sempre que houver um ABRIR CHAVES na linha posterior, NÃO se usa um ponto e vírgula para não quebrar a ligação entre o comando e o bloco de instruções delimitado por estas chaves.
CUIDADO: Nem sempre um “ponto e vírgula” esquecido ou adicionado erroneamente pode acusar erro no momento da compilação. Então devemos ser bem cuidadosos ao inseri-los.
3.3 - Variáveis em C Retornaremos agora a falar de variáveis, porém aplicando seu conceito na programação em C. Variáveis são elementos básicos que um programa manipula. Uma variável é um espaço reservado na memória do computador para armazenar um tipo de dado determinado. Para utilizar uma variável, ela deve ser primeiramente declarada, ou seja, requisitar um espaço necessário na memória para ela. Se não declararmos uma variável e tentarmos utilizá-la, o compilador irá nos avisar e não continuará a compilação.
3.3.1 - Nomes de Variáveis Após reservar o espaço requisitado, o computador irá associar a ele o nome da variável. Variáveis devem receber nomes para que possam ser referenciadas e modificadas quando necessário. Esses nomes somos nós, programadores, que escolhemos, mas para isso devemos seguir algumas regras. Sendo elas:
34
Núcleo de Cidadania Digital | NCD
Programação 1. 2. 3. 4.
5.
6. 7. 8.
Deverá ser iniciada por uma letra ou pelos caracteres "_" e “$”; A partir do segundo caracter pode conter letras, números e o caracter “_”; Deverá conter no máximo 32 caracteres; C é uma linguagem case-sensitive, ou seja, há diferença entre nomes com letras maiúsculas e nomes com letras minúsculas, ou seja, ao declarar as variáveis Idade e idade estaremos declarando duas variáveis diferentes; Os nomes também não podem conter espaços. Costuma-se usar maiúsculas e minúsculas para separar palavras, como por exemplo: “IdadeUsuario” e também podemos separar as palavras pelo caracter “_”, como por exemplo: “Idade_usuario”; Não podem conter acentuação; Uma vez declarada a variável com um nome, não poderá existir outra com este mesmo nome; Não podem ser palavras reservadas.
Para melhor entendimento, costuma-se declarar variáveis associando o nome com o dado a ser armazenado nesta variável. Por exemplo, ao escrever um programa que irá pedir que o usuário digite a idade dele, seria interessante que a variável que irá guardar esse dado tenha um nome associado a essa finalidade, como por exemplo: idade, idadeUsuario ou algum outro nome sugestivo.
3.3.2 - Palavras Reservadas de C As palavras reservadas são comandos de uso específico dentro da linguagem C. Essas palavras não podem ser usadas com outro propósito além do original, ou seja, não podemos usar palavras reservadas para dar nome a variáveis ou funções. Por exemplo, a palavra return, que já conhecemos, é uma palavra reservada que tem a finalidade de retornar algo, se tentarmos usar essa palavra para declarar uma variável ou uma função, o nosso programa não irá funcionar. Existem 32 palavras reservadas na linguagem C, por enquanto vamos apenas conhecêlas e no decorrer do curso iremos aprender a finalidade da maioria delas.
Núcleo de Cidadania Digital | NCD
35
Programação Lista de palavras reservadas da linguagem C:
asm break case char const continue default do
double else enum extern float for goto if
int long register return short signed sizeof static
struct switch typedef union unsigned void volatile while
Note que as palavras reservadas da linguagem C são sempre escritas em letras minúsculas.
3.3.3 - Tipos de Variáveis Sabemos que declarar uma variável significa reservar um espaço na memória do computador para armazenar um dado. O tipo de uma variável determina exatamente o quanto de memória o computador irá reservar. Existem diversos tipos de dados que podem ser usados nas variáveis, por isso cada tipo armazena um determinado espaço de memória. Vamos voltar ao exemplo em que temos que escrever um programa que armazena a idade do usuário. A idade nesse caso é um número inteiro, então é interessante que o computador reserve apenas memória para este dado, nem mais e nem menos. Para isso temos que declarar a variável como tipo inteiro, e nesse caso usamos a palavra reservada int. Em C, temos os seguintes tipos básicos de variáveis: char: Guarda um caractere (letras, números e símbolos); int: Guarda um número inteiro; float: Guarda um número real com certa precisão; double: Guarda um número real com precisão maior que float; void: Esse tipo serve para indicar que um resultado não tem um tipo definido. Uma das aplicações desse tipo em C é criar um tipo vazio que pode posteriormente ser modificado para um dos tipos anteriores.
36
Núcleo de Cidadania Digital | NCD
Programação
Figura 17 - Tamanho de tipos de variáveis
Tipo
Nº de bytes
Valores Válidos
char
1
Letras, números e símbolos. Ex: ( ‘b’, ‘H’, ‘^’, ‘*’,’1’,’0’)
int
4
de -32767 até 32767 (apenas números inteiros)
float
4
de -3.4 x 1038 até +3.4 x 10+38 com até 6 dígitos de precisão
double
8
de -1.7 x 10308 até +1.7 x 10+308 com até 10 dígitos de precisão
Tabela 7 - Número de memória de cada variável
Devemos ter muito cuidado ao declarar o tipo de uma variável, pois, ao fazermos isso, estamos a restringindo guardar apenas aquele tipo de dado. Por exemplo, se declaramos uma variável como inteiro e a mesma recebe ao invés disso um número racional (com vírgula), ocorrerá o que chamamos de range overflow ou estouro de faixa, no qual todos os números depois da vírgula serão ignorados e o programa não ira funcionar precisamente. 3.3.3.1 - Modificadores de Tipos de Variáveis Podemos ter variações dos tipos básicos de variáveis, para isso usamos os modificadores. Modificadores são palavras que alteram o tamanho do conjunto de valores que o tipo pode
Núcleo de Cidadania Digital | NCD
37
Programação representar. Por exemplo, um modificador permite que possam ser armazenados números inteiros maiores. Um outro modificador obriga que só números sem sinal possam ser armazenados pela variável, pois, não é necessário guardar o bit de sinal do número e somente números “positivos” são armazenados. Os tipos de modificadores são: long: Aumenta a faixa de valores que poderão ser armazenados. Na verdade, quando você declara um int, ele já é um long int. Ao declarar um long int, estamos apenas assegurando essa faixa de valores para int. Long também pode ser utilizado com float. Nesse caso, a faixa de valores de float será a mesma de um double; short: Reduz a faixa de valores que poderão ser armazenados a uma variável inteira. Apesar de parecer que utilizar esse tipo de modificador só nos trará restrições, pensando em termos de otimização é como se estivéssemos optando por transportar uma única pessoa em um carro (short int) em vez de em um ônibus (int) unsigned e signed: Estes modificadores alteram a faixa dos números inteiros int e caracteres char para valores somente “positivos”, ou seja, sem sinal (unsigned) ou fixa a faixa deles em valores com sinal (signed). Por padrão, se você não declarar esses modificadores, as variáveis tipo inteiro e caracter serão signed. Quando definimos que um inteiro terá valores sem sinal (unsigned), na verdade estamos dizendo que só estamos admitindo valores “positivos”. Isso altera a faixa de um inteiro para uma capacidade maior de números “positivos”. Se você declarar um valor negativo, ocorrerá um estouro de faixa.
Figura 18 - Diferenças no tamanho dos tipos de int
38
Núcleo de Cidadania Digital | NCD
Programação Na declaração da variável, o modificador deverá ser escrito antes do tipo. Exemplo: unsigned int - Número inteiro sem sinal. Podemos utilizar também uma combinação de dois modificadores, como por exemplo: unsigned long int - Admite um número inteiro, “positivo” e grande.
3.3.4 - Declarar uma variável Como já vimos, todas as variáveis deverão ter um nome e um tipo, e ao serem declaradas, devemos especificar essas características, sendo feito da seguinte forma: primeiramente declaramos o tipo, com ou sem modificadores, em seguida declaramos o nome da variável. Assim:
<tipo> <nome_da_variável>;
No exemplo abaixo, declaramos uma variável do tipo inteira (int) e uma variável do tipo float, que permite números racionais com os nomes IdadeDoUsuario e Peso, respectivamente.
int IdadeDoUsuario; float Peso;
Caso desejemos declarar várias variáveis, todas do mesmo tipo, podemos listá-las em uma mesma linha, separando os nomes por vírgulas. Exemplo:
int IdadeDoUsuario, resultado1;
Assim, para o programa ser escrito de uma forma mais clara e organizada, costuma-se reservar um espaço no início do programa no qual declaramos todas as variáveis que iremos utilizar durante a execução. Como mostra o exemplo abaixo:
Figura 19 - Exemplo de código com declaração de variáveis
Núcleo de Cidadania Digital | NCD
39
Programação Lembrando que devemos tomar muito cuidado ao nomear uma variável, pois ela deverá obedecer todas as regras que já mencionamos, sendo assim, observe o exemplo a seguir em que declaramos corretamente algumas variáveis e outras com alguns erros comuns:
float total2; - está correto. float 2total; - está errado, pois se iniciou com um número. int num; float salário;
está errado, pois o caracter “.” não é permitido. - está errado, pois não é permitido acento.
char nome_completo;
-
está correto.
3.4 - Exercício Marque (V) para verdadeiro e (F) para falso para as seguintes afirmações a seguir: ( ) int inteiro1; - Declaração correta. ( ) char 1nome; - Declaração correta. ( ) long int variável; - Declaração incorreta. ( ) Shot float resultado; - Declaração correta. ( ) int main; -
Declaração correta.
( ) float Resultado_1, resultado2; - Declaração correta. ( ) double NUMERO2 - Declaração incorreta. ( ) void nome1, nome2; - Declaração correta. ( ) int 1; - Declaração correta. ( ) float a; -
Declaração incorreta.
( ) char variável2; - Declaração incorreta.
40
Núcleo de Cidadania Digital | NCD
Programação
3.5 - Atribuição de Valores às Variáveis Vimos que as variáveis guardam dados de acordo com o tipo declarado. Ao guardar um dado em uma variável, dizemos que estamos fazendo uma atribuição de valores a essa variável. O comando de atribuição em C é o sinal “=” e entendemos da seguinte forma: variável = valor a ser atribuído;
A atribuição é indicada sempre da direita para esquerda, ou seja, a variável à esquerda do sinal de igualdade recebe o valor à direita. Exemplo: int valor = 10;
Figura 20 - Ilustração de alocação de valor em uma variável
Ao fazer esse comando, estamos guardando, ou seja, atribuindo à variável valor o número inteiro 10. Lembre-se que quando estiver fazendo a atribuição de um caracter, a variável deve ser do tipo char e o caracter deve estar entre aspas simples, como mostrado no exemplo abaixo: char letra = ‘a’;
Existem duas formas de atribuir dados a uma variável. Podemos fazer isso na declaração/ inicialização da variável ou durante a execução do programa. Atribuir valor a uma variável na inicialização do programa: é basicamente fazer o que fizemos no exemplo acima. Ao declarar a variável, além de declararmos o tipo e o nome, atribuímos à variável um valor inicial, fazendo isso ao iniciar o programa. Atribuir valor a uma variável na execução do programa: Suponha que iremos criar um programa que realiza uma soma de dois números. Precisamos, então, declarar duas variáveis para guardar esses valores, certo?! Após fazer essa
Núcleo de Cidadania Digital | NCD
41
Programação soma, nosso programa irá gerar um valor, esse valor, por final, precisa ser guardado em uma terceira variável para darmos continuação ao nosso programa, seja para mostrar esse resultado ao usuário ou usar esse valor para uma outra função. Assim, ao invés de atribuir um valor fixo à variável, atribuímos a operação que irá gerá-lo. Exemplo:
Assumindo que as variáveis já estão declaradas:
a=b+c
Figura 21 - Ilustração de atribuição Ao escrever esse código estamos atribuindo à variável a, o valor da soma de b e c. Um erro comum dos programadores iniciantes é pensar que ao fazer isso, estamos afirmando que a variável a é igual a soma de b e c, mas agora sabemos que ao invés disso, estamos guardando, ou melhor, atribuindo o valor da soma de b e c na variável a. Podemos também perguntar se uma variável é igual a outra, para isso usamos o caracter “==” e não devemos confundir isso com o comando de atribuição. Desse modo:
v == 5;
Assim, estamos perguntando se a variável v é igual a 5. As operações de comparação lógica retornam valores True e False, de acordo com o resultado da comparação. Vamos ver agora um exemplo completo de atribuição de valores a uma variável.
42
Núcleo de Cidadania Digital | NCD
Programação Exemplo: Figura 22 - Exemplo de código com atribuição
Ao final da execução desse programa, teremos o seguinte valor atribuído: a = 5
3.6 - Expressões Aritméticas No último exemplo, realizamos uma soma entre dois números. Em C, fazer operações matemáticas simples, como soma, subtração, multiplicação, entre outras, é bastante fácil e intuitivo. Para essas operações, usamos os seguintes símbolos: Soma: + Subtração: Multiplicação: * Divisão: / Resto da divisão: % Assim, podemos usá-las como mostram os exemplos abaixo, assumindo que as variáveis necessárias já estão declaradas: i = i + 1; x = i - 1;
forca = massa * aceleracao; d = 256/13; p = 27%2; Núcleo de Cidadania Digital | NCD
43
Programação Observe que alguns símbolos matemáticos que usamos em computação são diferentes daqueles que usamos no dia-a-dia. E sobre o resto da divisão, falaremos melhor desse operador matemático ainda nesta seção.
3.6.1 - Precedência de Operadores Matemáticos Podemos usar mais de um operador na mesma expressão, e assim como na álgebra, em C, há uma ordem de precedência de operadores. Essa ordem que o computador segue para resolver equações matemáticas é a mesma ordem que nós usamos na matemática real. Assim sendo, segue a ordem: 1. Expressões em parênteses; 2. Exponenciação, multiplicação, divisão e resto da divisão; 3. Adição e subtração. Diferente da matemática real, em C, não usamos chaves e colchetes para as operações matemáticas. Esses caracteres são reservados para outros finalidades, logo, o sistema seguirá a ordem de cálculo de dentro para fora em relação aos parênteses. Quando operações adjacentes têm a mesma precedência, elas são associadas da esquerda para a direita. Assim, a * b / c * d % e é o mesmo que ((((a * b) / c) * d) % e).
Se atribuirmos às variáveis a e b os valores abaixo, teremos os seguintes resultados: a = 2 + 4 * 10; - fazendo isso, estamos atribuindo o número 42 à variável a, pois é o mesmo que (2 + (4 * 10)). Visto que a multiplicação tem precedência sobre a soma. b = 2 + 40 / 2 + 5; - retornará 27, o mesmo que (2 + (40 / 2) + 5). Visto que a divisão tem precedência sobre a soma. Outro exemplo seria atribuirmos a uma terceira variável c o valor de a + b, assim: c = a + b;
44
Núcleo de Cidadania Digital | NCD
Programação Seria o mesmo que fazermos : c = 42 + 27;
3.6.2 - Operador resto (%) A operação de resto (%) é usada quando queremos encontrar o resto da divisão entre dois inteiros. Por exemplo, 22 dividido por 5 é 4, com resto 2. Então se fizermos a expressão 22 % 5 teremos como resposta o valor 2. Note que % só pode ser utilizado entre dois inteiros. Usando ele com um operando do tipo float causa um erro de compilação (como em 22.3 % 5).
3.6.3 - Exercício Calcule qual o valor a ser atribuído para as variáveis a seguir, de acordo com a operações matemáticas representadas: 1. a = 5 - 3 * 4 ; 2. temperatura = (5 - 3) * 4 ; 3. e = 4 + 3 * 5 / 5 ; O exemplo abaixo mostra como podemos utilizar a atribuição de valores simples e com operações matemáticas. Exemplo: O programa abaixo calcula o comprimento da circunferência de um círculo de acordo com o valor atribuído ao raio:
Figura 23 - Exemplo de código que calcula comprimento de circunferência
Núcleo de Cidadania Digital | NCD
45
Programação 3.6.4 - Biblioteca math.h Vimos que existem vários processos em um algoritmo que podemos determinar com operações matemáticas ou cálculos sendo executados pelo nosso programa. Além das operações aritméticas básicas, temos também como resolver cálculos mais complexos. Para isso, usamos as funções da biblioteca math.h. Com esta biblioteca, podemos encontrar facilmente funções para calcular potências, raízes quadradas e funções trigonométricas para cálculos que envolvem seno, cosseno e tangente, além de constantes para números irracionais como, por exemplo, PI ( π). Funções matemáticas Trigonométricas: sin (): Retorna o valor do seno. Recebe como argumento o valor do ângulo em double. cos (): Retorna o valor do cosseno. Recebe como argumento o valor do ângulo em double. tan (): Retorna o valor da tangente. Recebe como argumento o valor do ângulo em double. É importante lembrar que as funções que envolvem ângulos devem receber valores em radianos e não em graus. Logarítmicas: log (): Retorna o valor do logaritmo na base e (ln). Exige um argumento do tipo double. log10 (): Retorna o valor do logaritmo na base 10. Exige um argumento do tipo double. Potências: pow (): Retorna o valor da base elevada ao expoente. Recebe dois argumentos do tipo double, o primeiro é a base e o segundo o expoente. Por exemplo: se quisermos saber o resultado da operação 210, faríamos pow (2, 10).
46
Núcleo de Cidadania Digital | NCD
Programação sqrt (): Retorna o valor da raiz quadrada. Recebe como argumento um double do qual ele deve extrair a raiz. Arredondamento: ceil(): Retorna o primeiro float sem casas decimais acima. Recebe um float como argumento. Exemplo: ceil (45.98561) resultaria em 46. floor(): Retorna o primeiro float sem casas decimais abaixo. Recebe um float como argumento. Exemplo: floor (45.98561) resultaria em 45. Para ilustrar todas essas funções e constantes, abaixo está um código demonstrando o resultado de algumas funções da biblioteca math.h. Exemplo: Se repetirmos o exemplo do cálculo da circunferência que fizemos acima e inserir a biblioteca math.h, podemos colocar no lugar de 3,14, o caracter pi ( π), ficando assim:
Figura 24 - Exemplo do uso da math.h O comando #define define o valor de uma constante qualquer. Assim teremos aproximadamente a mesma resposta para os dois exemplos, porém com o valor pi da máquina teremos uma precisão muito maior do que com 3,14, ou seja, a resposta estará mais próxima da correta.
Núcleo de Cidadania Digital | NCD
47
Programação 3.6.5 - Exercício 1. Faça um programa que calcule o volume de uma esfera. (Considere o raio = 3, e Volume = (4 * pi * raio³) / 3 ). 2. Faça um programa que calcule o seno do ângulo π/6 e arredonde a resposta.
3.7 - Funções de Entrada e Saída Se você pensar bem, perceberá que um computador é praticamente inútil se não interagir com o usuário. Até aqui, ao compilar nossos programas, vimos que não apareceu nada na tela do terminal, assim, ficamos sem uma resposta do nosso programa. Então agora vamos ver funções que além de mostrar informações na tela, permitem a interação com o programa em execução. As trocas de informação entre o computador e o usuário são chamadas entrada e saída (input e output, em inglês). Entrada é a informação fornecida a um programa e saída é a informação fornecida pelo programa. É comum referir-se aos dois termos simultaneamente: entrada/saída ou E/S (I/O, em inglês). Essa comunicação é realizada através do teclado, terminal, arquivo, monitor, impressora, dentre outros. Em C, as funções padrão de entrada e saída estão declaradas na biblioteca stdio.h.
3.7.1 - Printf() A função printf() é uma função de saída utilizada quando queremos mostrar alguma informação do programa para o usuário. Por exemplo, se quisermos mostrar a mensagem “Olá mundo !!”, digitamos o seguinte código:
printf(“Olá mundo !!” );
Tudo que estiver entre as apas irá aparecer na tela do terminal. Vamos então treinar. Faça o exemplo a seguir:
48
Núcleo de Cidadania Digital | NCD
Programação
Figura 25 - Exemplo do uso da função printf() Mais interessante que mostrar mensagens na tela é mostrar os valores das variáveis. Por exemplo, quando fizemos o cálculo da circunferência anteriormente, não foi possível visualizar o resultado. Agora que aprenderemos a utilizar a função printf() podemos tratar desses casos. Para imprimir um valor de uma variável, teremos que especificar qual o seu tipo dentro da função printf(), para isso, usamos os operadores de conversão que são caracteres especiais reservados. Sendo eles: Operador Finalidade %d %f
Imprimir um inteiro Imprimir um float
%c
Imprimir um caracter
%s
Imprimir uma string
%%
Imprimir o resto da divisão
Tabela 8 - Relação de operador e sua finalidade
Além de usarmos os operadores para especificar o tipo da variável a ser impressa, temos que especificar qual é a variável. Para isso, a função printf() tem o seguinte formato: printf(“operador”, variável a ser impressa);
Núcleo de Cidadania Digital | NCD
49
Programação Vamos então analisar o seguinte exemplo: Exemplo:
Figura 26 - Exemplo de código para apresentação de dados com printf() Podemos também imprimir várias variáveis ao mesmo tempo. Do seguinte modo: printf(“%d %c”, variavel1, variavel2);
O computador associa a variável ao operador declarado na mesma ordem, então no exemplo acima variavel1 é do tipo inteiro e a variavel2 do tipo char. É importante ressaltar que podemos escrever como quisermos entre as aspas na função printf(), utilizar palavras reservadas, com acentos, maiúsculas, etc. Exemplo:
Figura 27 - Exemplo de código para apresentação de dados com printf()
50
Núcleo de Cidadania Digital | NCD
Programação 3.7.2 - Exercício Faça um programa que calcule e mostre para o usuário o volume da esfera. Sabendo que a mesma tem um raio que mede 6 metros e que a relação de volume e raio da esfera é dada pela seguinte expressão: Volume = (4 * PI * raio³) / 3
3.7.3 - Scanf() Scanf() é a nossa função de entrada, com ela podemos pegar dados em tempo de execução, ou seja, podemos perguntar ao usuário qual o dado a ser utilizado e utilizá-los. Por exemplo, podemos pedir ao usuário que digite dois números para serem somados pelo programa, sendo assim, ao escrevermos nosso código, devemos declarar duas variáveis que serão usadas para guardar estes dois valores a serem recebidos. Para armazenar a variável, a função scanf() tem o formato bem parecido com a função printf(), os operadores são os mesmos e especificamos o tipo e, logo em seguida, a variável juntamente com o caracter “&”, assim: scanf(“operador “, &variavel);
Observe o exemplo abaixo: Exemplo:
Figura 28 - Exemplo do uso da função scanf()
Núcleo de Cidadania Digital | NCD
51
Programação Nesse exemplo, atribuímos às variáveis a e b os valores que o usuário digitar no teclado, realizamos então a soma e atribuímos à variável c. Em seguida mostramos ao usuário a resposta. Assim que compilarmos o programa, o mesmo permanecerá parado até digitarmos os dados, isso sempre ocorrerá quando utilizarmos a função scanf(). Vale ressaltar que não podemos esquecer do caracter “&” na frente da variável, pois sem ele o código não irá funcionar. Mais detalhes sobre o operador & serão passados nos capítulos seguintes.
3.7.8 - Exercício 1. Escreva um programa que solicite três números, faça a média e mostre ao usuário a resposta.
3.8 - Boas Práticas de Programação Ao realizar as operações matemáticas, é interessante separá-las por parênteses, mesmo que o compilador execute as operações na ordem que vimos. Isso ajuda bastante o programador a não se confundir. Se vamos atribuir a uma variável uma operação de divisão, é importante declararmos a mesma como tipo float, para não perdermos informação caso essa divisão não resulte em um número inteiro. Para encerrar este capítulo, vamos retomar ao programa do cálculo da circunferência e acrescentar as funções de entrada e saída. Exemplo:
Figura 29 Exemplo do código completo de cálculo da circunferência
52
Núcleo de Cidadania Digital | NCD
Programação
3.9 - Exercícios 1. Faça um programa que declare uma variável de tipo inteira, uma variável do tipo real e uma variável do tipo caractere, e as inicialize com valores válidos. Em seguida, imprima as variáveis. 2. Escreva um programa que solicite ao usuário um número inteiro “a”. Seu programa deve inverter o sinal de “a” e exibir o resultado. 3. Uma sorveteria vende três tipos de picolés. Sabendo que o picolé tipo 1 é vendido por R$ 1.50, o do tipo 2 por R$ 2.00 e o do tipo 3 por R$ 0.75, faça um programa que, para cada tipo de picolé, mostre a quantidade vendida e o total arrecadado. 4. Considerando que, para um consórcio, sabe-se o número total de prestações, a quantidade de prestações pagas e o valor atual da prestação, faça um programa que determine o total pago pelo consorciado e o saldo devedor. 5. Uma empresa contrata um encanador a R$20,00 por dia. Crie um programa que solicite o número de dias trabalhados pelo encanador e imprima o valor líquido a ser pago, sabendo que são descontados 8% de imposto de renda.
Núcleo de Cidadania Digital | NCD
53
Programação
Capítulo 4 - Estruturas Condicionais Imagine que teremos que construir um programa para uma votação, nele perguntamos ao usuário se ele quer ou não votar em algum candidato, se usuário digitar “sim”, teremos de dar as opções de candidatos para ele, caso ele digite “não”, o programa deverá se encerrar. Até aqui, todos os nossos programas executaram o código do início ao fim. Mas, na maioria dos casos, temos que construir programas em que seu “rumo” será definido a partir da interação com o usuário enquanto utiliza o programa, ou seja, estabelecemos condições que controlam se um determinado trecho de código é executado ou não. Um exemplo é a nossa votação, se escolhermos não votar definimos que o programa irá encerrar, logo, a parte do programa condicionada ao usuário votar não será executada. Esse tipo de código é o que chamamos de estruturas condicionais. Com elas conseguimos pré-programar atitudes que o programa terá de acordo com a ação de cada usuário no momento da execução. Neste capítulo vamos aprender os principais comandos oferecidos pela linguagem C para esse controle de fluxo, sendo eles divididos em estruturas condicionais, estruturas de repetição e desvios incondicionais, assim veremos como e quando vamos usá-los. Antes de iniciar o estudo das estruturas condicionais, vamos entender como iremos descrever condições e como elas serão avaliadas durante a execução do programa. Na linguagem C, uma condição é nada mais que qualquer expressão, cujo valor resultante precisa ser necessariamente um número inteiro. Se esse valor for 0 (zero), então a expressão é interpretada como sendo falsa. Caso contrário, se a expressão resultar em qualquer outro valor não nulo, então a condição será tratada como verdadeira. Veremos mais adiante um exemplo que ilustrará esse caso.
4.1 - Operadores de Comparação A maioria das decisões de um programa é baseada na comparação entre dois valores. Para escrever tais condições, existem os operadores de comparação, também chamados de operadores relacionais. Os operadores de comparação são tratados da mesma forma que os demais operadores aritméticos, tais como +, -, / e * (respectivamente, soma, subtração, divisão e multiplicação). Sendo eles:
54
Núcleo de Cidadania Digital | NCD
Programação Operador Condição > >=
Maior que
Maior ou igual que
<
Menor que
<=
Manor or igual que
== !=
Igual Diferente
Tabela 9 - Operadores de comparação Assim, para escrever comparações, valem as mesmas regras usadas para expressões aritméticas. O resultado de um operador de comparação será 1 se a comparação for verdadeira ou 0 caso ela seja falsa. Então:
Expressão
Resultado
valor 1 < valor
Resulta 1 se valor1 é menor que valor2, caso contrário, resulta em 0
valor 2 >= valor 1
Resulta 1 se valor1 é maior ou igual que valor2, caso contrário, resulta em valor2
valor1 == valor2
Resulta 1 se valor1 é exatamente igual que valor2, caso contrário, resulta em 0.
Tabela 10 - Resultados de expressões de comparação
Núcleo de Cidadania Digital | NCD
55
Programação É comum atribuirmos a uma variável o resultado de uma condição. Exemplo:
Figura 30 - Exemplo de atribuição de uma expressão lógica à um int Às variáveis a e b são atribuídos os resultados das expressões de comparação entre os parênteses. Eles são necessários para indicar que a comparação será realizada antes da atribuição. Após a execução, a recebe 1 (pois 1 < 2 é verdadeiro) e b recebe 0 (pois 3 ≤ 2 é falso). Para escrever algumas condições em C, podemos usar os operadores lógicos, que unem duas expressões em uma condição maior e mais complexa. Nos casos abaixo, cond1 e cond2 representam expressões cujos resultados são números inteiros.
Ideia
Expressão
Resultado
&& E (and)
(cond1) && (cond2)
Resulta 1 se cond1 e cond2 forem verdadeiras (não nulas). Se uma das duas for falsa (zero), então resulta em 0.
|| OU (or)
(cond1) || (cond2)
Resulta 1 se cond1 ou cond2 forem verdadeiras (não nulas). Se as duas forem falsas (zero), então resulta em 0.
Tabela 11 - Resultado de operadores condicionais
56
Núcleo de Cidadania Digital | NCD
Programação
4.2 - Comando de decisão If No início deste capítulo, usamos como exemplo um programa para uma votação em uma urna eletrônica. Nele determinamos que caso o usuário escolhesse votar, o próximo passo seria escolher o candidato, mas se o usuário decidisse não votar, o programa deveria se encerrar. Assim como mostra a figura a seguir:
Figura 31 - Esquema de uma urna eletrônica
Para realizar este controle, utilizaremos a estrutura de seleção if. Ela nos é útil quando existem instruções dentro do programa que somente devem executar se satisfizerem a uma determinada condição. A palavra if, traduzida do inglês, significa se, então é comum nos referirmos a estrutura if como estrutura se. Nesse exemplo seria como se falássemos para o programa: “Se o usuário disser sim, peça para ele votar, senão, encerre o programa.
4.2.1 - Sintaxe If A sintaxe da estrutura condicional if é dada da seguinte forma:
Figura 32 - Esquema da estrutura condicional if
Núcleo de Cidadania Digital | NCD
57
Programação Primeiro, avalia-se a expressão, que representa a condição a ser verificada. Note que esta expressão é obrigatoriamente envolvida por parênteses. Muito possivelmente, essa expressão utilizará um dos operadores de comparação. Se a expressão resultar em um valor diferente de zero (verdadeiro), então o programa executa o bloco de sentenças e depois continua normalmente com a próxima sentença logo após o bloco do if. Mas se a expressão resultar em zero (falso), então todo o bloco de sentenças é ignorado e a execução segue diretamente para o primeiro comando após o bloco de sentenças do if. Exemplo: Neste exemplo, vemos como pode ser feito o programa para a votação na urna eletrônica. Vamos analisar agora, passo a passo, as linhas de código desse exemplo:
Figura 33 Exemplo do uso da estrutura condicional if int decisao; Primeiro, declara-se a variável do tipo inteiro com o nome decisao. printf(“Deseja votar? Sim: Digite 1.
Não: Digite 2”);
scanf(“%d”, &decisao); Em seguida, a função printf imprime uma mensagem para solicitar a vontade do usuário, e scanf realiza a leitura de um número inteiro na variável decisao. If (decisao == 1) { printf(“Pode votar. “); } Por fim, o valor da variável “decisao” é comparado com o valor 1. Se o resultado da expressão (decisao == 1) for verdadeiro (diferente de zero), então o próximo bloco é
58
Núcleo de Cidadania Digital | NCD
Programação executado. Este bloco contém apenas a função printf que imprime a frase “Pode votar”. Caso contrário, a execução salta este bloco e passa para o comando return 0, que finaliza o programa. Resultados de execução: Deseja votar? Sim: Digite 1. Não: Digite 2: 2 Neste caso a autorização não é impressa, pois o valor da decisão é diferente de 1. Deseja votar? Sim: Digite 1. Pode votar.
Não: Digite 2: 1
4.2.2 - Estrutura condicional if else Seguindo com o exemplo anterior, ao invés de encerrar o programa quando o usuário decidir não votar, desejamos agora imprimir uma mensagem dizendo: “Você decidiu não votar”. Para isso, basta adicionar ao if, a estrutura condicional else que normalmente chamamos de senão. Utilizamos a estrutura else, ao desejarmos que o programa execute uma outra sentença quando a estrutura if for avaliada como falsa.
Figura 34 - Fluxograma da urna eletrônica Sintaxe da estrutura if-else if (expressão) { sentença1; }else { sentença2; }
Núcleo de Cidadania Digital | NCD
59
Programação Exemplo:
Figura 35 - Exemplo da utilização da estrutura if-else Pela lógica do programa, se a condição da expressão do if for avaliada como falsa, então, obrigatoriamente, a expressão do else é verdadeira e vice-versa. Logo, uma das duas será necessariamente executada. Por coincidência, nos exemplos vistos até o momento, usamos apenas as funções printf e scanf dentro da estrutura if e da estrutura else, mas não necessariamente são permitidas somente estas funções, podemos realizar qualquer outra, como fazer operações matemáticas, atribuição de valores, etc.
4.2.3 - Estrutura condicional if aninhado Vamos evoluir ainda mais com o nosso exemplo. Agora, ao invés de só perguntar ao usuário se o mesmo deseja votar, vamos dar as opções de candidatos para caso ele aceite votar. Com isso, além do programa realizar a primeira sentença, vamos ter que acrescentar uma outra com as opções dos candidatos. Para isso, a linguagem C oferece a estrutura condicional if{...}else if{...}else{...}, ou if aninhado, com capacidade de realizar decisões múltiplas. A estrutura oferece vários blocos como alternativas para serem executados. Vejamos o fluxograma:
Figura 36 - Fluxograma completo de uma urna eletrônica
60
Núcleo de Cidadania Digital | NCD
Programação Sintaxe da estrutura if aninhado: if (expressão) { sentença1; }else if (expressão) { sentença2; } else { sentença3; }
Com esta estrutura as expressões são avaliadas em ordem, de cima para baixo. O programa executa somente o primeiro bloco se a primeira expressão é satisfeita, ignorando os demais blocos. Se uma expressão resultar em zero (falso), então a próxima expressão é avaliada. Quando a expressão for diferente de zero (verdadeiro), apenas o bloco correspondente a ela é executado, sendo as demais expressões ignoradas. Ao término da execução da sentença, o programa encerra a estrutura e volta a executar os códigos seguintes do programa. Observação 1: A estrutura pode apresentar tantos blocos if else quantos forem necessários para descrever a lógica de um algoritmo. Nosso exemplo, no entanto tem apenas 2 condições. Observação 2: O último bloco, associado com o else, é executado quando nenhuma das outras condições for verdadeira. Esse bloco é opcional e pode ser omitido. Exemplo: Veremos a seguir nosso código da urna eletrônica:
Núcleo de Cidadania Digital | NCD
61
Programação
Figura 37 - Código completo da urna eletrônica Observe que para o programa mostrar ao usuário a mensagem “Obrigado por votar!”, ele precisou satisfazer as duas expressões impostas. Assim, caso uma delas não fosse satisfeita o programa executaria imediatamente a sentença da função else.
4.3 - Comando de decisão switch O mesmo exemplo dado no início deste capítulo, a simulação da votação em uma urna eletrônica, pode ser resolvido de outra maneira, utilizando-se um outro comando de decisão: o switch.
Figura 38 - Fluxograma da urna com switch-case
62
Núcleo de Cidadania Digital | NCD
Programação
Sintaxe do switch-case:
switch(expressão){
case (condição):
comandos;
...
break;
case (condição):
comandos;
...
break; default: comandos; }
A expressão ou variável a ser considerada deve estar entre parênteses, seguida por um bloco, que já aprendemos a separar por chaves { }. Dentro deste bloco, deve constar os casos (case) nos quais a sua expressão ou variável se encaixam. No exemplo da urna eletrônica, caso o candidato escolhesse votar, digitaria 1, e, caso não, digitaria 2. Sendo assim, usando-se o comando switch, o código seria:
Núcleo de Cidadania Digital | NCD
63
Programação
Figura 39 - Exemplo de uso da estrutura switch-case Além disso, após determinar a sequência de comandos a ser seguida dentro de cada caso, eles devem ser encerrados pelo comando break, que é responsável por parar imediatamente a execução de comandos dentro de todo o bloco do switch, saindo do mesmo e continuando a execução do programa fora dele. O comando break evita que cálculos desnecessários sejam feitos, otimizando assim o seu programa. Se a sua expressão não se encaixar em nenhum dos casos, ao final dos comandos há ainda a opção do default. Nele, o seu programa irá executar a sequência de comandos indicada dentro dessa opção caso nenhuma das opções anteriores tenha sido executada.
4.4 - Exercícios 1. Faça um programa que decide se a idade do usuário lhe permite tirar carteira de habilitação. O programa deve imprimir uma mensagem negando a autorização, caso a pessoa seja menor de idade ou autorizando caso tenha idade maior de 18 anos. 2. Acrescente ao programa acima a média do exame médico do usuário, assim, ele só terá autorização para tirar carteira de habilitação caso ele seja maior de 18 anos e obter média acima de 7 no exame médico.
64
Núcleo de Cidadania Digital | NCD
Programação 3. Por fim, caso o usuário tenha idade e média desejáveis para tirar carteira, o programa deverá informar o período de renovação dos exames médicos: até 65 anos, os prazos são de 5 em 5 anos; depois, o exame precisa de renovação a cada 3 anos. 4. Faça um programa que retorne o nome do mês correspondente ao seu número. Por exemplo, se a entrada for 2, deve imprimir “Fevereiro”.
4.5 - Boas Práticas de Programação O operador que checa se duas expressões são iguais é formado por dois símbolos de igualdade consecutivos (“==”). O símbolo “=” simples significa atribuição. A troca entre operadores “=” e “==” não necessariamente será detectada pelo compilador. Essa é uma fonte de erros corriqueira para os iniciantes nas linguagens C; Deslocamos as sentenças dentro do bloco da construção if para a direita, usando tabulações. Isso é uma boa prática de programação, pois a disposição do código refletirá a estrutura lógica do programa; Caso o bloco de código que será executado na estrutura condicional for de apenas uma instrução, não é necessário o uso de chaves ({}), pois o compilador entende que ele deverá executar pelo menos uma instrução, logo ele irá executá-la estando ou não entre chaves.
Núcleo de Cidadania Digital | NCD
65
Programação
Capítulo 5 - Estruturas de Repetição Como em uma linha de montagem em que um operário monta um tipo de peça várias vezes, repetindo o mesmo processo, no meio da programação nos deparamos com situações em que é necessário repetir um mesmo processo várias vezes. Para lidar com esse tipo de situação da maneira mais inteligente possível, ao invés de escrevermos um mesmo comando repetidas vezes e termos o número dessas repetições estáticos, por serem definidos pelo código, nós utilizamos as estruturas de repetições, que nos permitem definir uma quantidade exata de repetições ou repetir o processo até que algum evento específico aconteça. Quando fazemos o uso desse tipo de estrutura, dizemos que estamos criando um loop ou laço.
Figura 40 Montanha-Russa, ilustra uma estrutura de repetição Antes de nos aprofundarmos nas estruturas de repetição propriamente ditas, devemos entender o conceito de contadores. Esse é um recurso que utilizamos para definirmos o número de repetições que serão feitas pelo sistema em algumas situações específicas. Os contadores, também conhecidos como variáveis de iteração, são variáveis do tipo int que são incrementadas ou decrementadas a cada vez que o sistema executa o bloco de código que está dentro da estrutura de repetição, então, dependendo do valor desse contador, o sistema executará o código novamente ou não. Existem duas formas de manipular (incrementar ou decrementar) os contadores: a forma intuitiva (a = a +1 ou a = a - 1) e uma forma um pouco diferente (a++ ou a--). As duas formas funcionam da mesma maneira, mas damos preferência para o uso da segunda para diferenciarmos a manipulação do contador das outra variáveis e por ser mais prática.
66
Núcleo de Cidadania Digital | NCD
Programação Agora que já entendemos como funciona e para que servem os contadores, vamos começar nossos estudos sobre as estruturas de repetição, dentre elas a mais utilizada é o while.
5.1 - While A tradução de “while” é enquanto, ou seja, ele terá uma condição e enquanto esta não for satisfeita a repetição irá continuar. A sintaxe do while é a seguinte: while(condição){ Comando 1; … Comando n; }
Ou seja, o while testa a condição e caso ela seja verdadeira, os comandos são executados, caso seja falsa, o programa pula o bloco do while e continua a execução do código a partir do fechamento da chave ( } ). Podemos ilustrar o funcionamento dessa estrutura com o fluxograma mostrado abaixo:
Figura 41 - Fluxograma de condição
Para podermos entender melhor, segue abaixo dois exemplos do uso do while: Exemplo 1: Faça um programa que receba 10 números inteiros e imprima a metade de cada número recebido.
Núcleo de Cidadania Digital | NCD
67
Programação Resolução:
Figura 42 - Exemplo de código que imprime a metade do número recebido com while() Exemplo 2: Dada uma sequência de números positivos, representando os valores dos itens de uma compra, informe o total a ser pago. Considere que a sequência termina quando aparece o primeiro número não positivo. Resolução:
Figura 43 - Exemplo de código que imprime o total de uma compra
5.1.1 - Exercícios 1. Faça um programa usando while que faz uma contagem até um número n digitado pelo usuário. 2. Faça um programa que receba um número e imprima seu dobro. A leitura de números deve parar quando o número recebido for 0
68
Núcleo de Cidadania Digital | NCD
Programação 3. Num banco, as contas são identificadas por um número de conta com dígito verificador. Esse dígito verificador é calculado do seguinte modo: primeiramente somamos todos os dígitos do número da conta, depois dividimos a soma por 10 e tomamos o resto. Dado um número de conta, informe o dígito verificador correspondente.
5.2 - For Além do while, temos outras estruturas de repetição, como o for. Essa estrutura é mais indicada para situações em que se sabe o número de repetições a serem efetuadas, pois o número de repetições é definido pelo valor do iterador, o que normalmente não ocorre com o while, pois este costuma depender de uma variável qualquer que é alterada dentro do laço e é comparada na condição. A sintaxe do for, mostrada abaixo, exige que seja informada a condição inicial, condição de parada e o incremento ou decremento do iterador. for(<início>; <quando parar>; <iterador>){ Comando 1; … Comando n; }
Portanto, podemos ilustrar o funcionamento dessa estrutura através do seguinte fluxograma:
Figura 44 - Fluxograma de condição
Núcleo de Cidadania Digital | NCD
69
Programação Para podermos entender melhor, segue abaixo dois exemplos do uso do for: Exemplo 1: Faça um programa que receba 10 números inteiros e imprima a metade de cada número recebido. Resolução:
Figura 45 - Exemplo de código que imprime a metade do número recebido com for()
Exemplo 2: Faça um programa que receba 10 notas e informa a maior e a menor delas. Resolução:
Figura 46 - Exemplo de código que imprime o menor e o maior de 10 números dados
70
Núcleo de Cidadania Digital | NCD
Programação 5.2.1 - Exercícios 1. Faça um programa usando for que faça uma contagem até um número n digitado pelo usuário. 2. Faça um programa que receba 10 números e retorne a média aritmética entre eles. Obs.: A fórmula da média aritmética é (somatória dos elementos)/(número de elementos). 3. Faça um programa que imprima a sequência de Fibonacci até seu n-ésimo elemento (n é recebido como parâmetro). Obs.: A sequência de Fibonacci é composta de 0, 1 e do terceiro elemento em diante é a soma dos dois anteriores.
5.3 - Do while Outra estrutura de repetição que temos é o “do-while”, que é muito semelhante ao while, porém executa os comandos de dentro do laço ao menos uma vez, pois a condição é testada ao fim de cada repetição, como mostra a sintaxe abaixo: do{ Comando 1; … Comando n; }while(condição);
Podemos ilustrar o funcionamento dessa estrutura através do seguinte fluxograma:
Figura 47 - Fluxograma de condição
Núcleo de Cidadania Digital | NCD
71
Programação Exemplo: Faça um programa que, de acordo com a opção escolhida, exiba uma mensagem diferente. Resolução:
Figura 48 - Exemplo de código com o uso de do-while
72
Núcleo de Cidadania Digital | NCD
Programação
Capítulo 6 - Funções Quando começamos a programar melhor, vemos que existem algumas práticas que devemos ter para facilitar o “manuseio” do código. Essas práticas não são essenciais, ou seja, se o código for escrito de outra maneira ele pode funcionar também, porém elas deixam o código mais legível e de melhor entendimento. Um recurso muito importante para conseguirmos esse tipo de resultado é o uso de funções, mas antes de falarmos especificamente sobre elas, vamos tentar entender as vantagens e razões para seu uso.
6.1 - Modularização
Figura 49 - Linha de montagem No nosso dia a dia, nos deparamos com a modularização quando nos especializamos em uma determinada área de atuação no mercado de trabalho, por exemplo, temos os responsáveis pela construção de casas, os responsáveis por cuidar da nossa saúde, os responsáveis pela educação, entre outros. Logo, vivemos em uma sociedade “modularizada”. No mundo da programação, a modularização consiste em dividir o código em blocos menores, o que deixa o programa mais legível, ou seja, fica mais fácil de entender a função de cada parte de código, como mostrado no exemplo abaixo:
Figura 50 - Exemplo de código com uso de função (módulo) Núcleo de Cidadania Digital | NCD
73
Programação Consequentemente, isso acrescenta uma facilidade na manutenção do código, pois será mais fácil saber onde está o erro uma vez que seu programa está dividido em blocos e cada um desses blocos tem uma função diferente. Além disso, ainda há o fato de que seu programa ficará mais confiável, uma vez que serão usadas muitas variáveis locais e isso deixará a informação que está sendo tratada mais “escondida”. Outra funcionalidade muito importante dessa prática é o fato de você poder usar um mesmo bloco de código várias vezes durante a execução do programa, o que deixa ele mais rápido e mais “leve”, além de ser mais prático para o programador. Para realizarmos a modularização de um código fazemos uso das funções, que serão tratadas mais especificamente no decorrer deste capítulo, e, com elas, vem também o uso das variáveis locais.
6.2 - Características de Funções Quando falamos de funções, logo lembramos daquelas usadas na matemática. Elas são bons exemplos de se ter em mente, pois assim como as funções da programação, elas têm uma forma predeterminada e executam algum tipo de processamento quando utilizadas. E para obter uma determinada saída (retorno), só é necessário que se tenha uma entrada (valores fornecidos quando a função é chamada, conhecidos como parâmetros) específica.
Figura 51 - Exemplo de função matemática (Função de Bhaskara)
Existem vários tipos de funções, que serão apresentadas no decorrer do capítulo, mas, de forma geral, podemos dizer que cada função recebe dados de entrada e, após realizar algumas operações, exibem uma saída, mesmo que essa não seja exibida para o usuário, com exceção da função do tipo void que será tratada em mais detalhes no decorrer da apostila.
74
Núcleo de Cidadania Digital | NCD
Programação
Figura 52 - Linha de produção A função nada mais é do que um bloco de código criado para executar algum tipo específico de operação com os dados recebidos no momento em que ela é chamada (requisitada pelo programa) e, como qualquer outra parte de código, as funções podem, inclusive, chamar outras funções ou até elas mesmas com algum tipo de modificação na entrada e uma condição de parada. Mas se a função é um bloco de código como outro qualquer, como é feita a identificação de cada função? Para fazer essa diferenciação usamos o cabeçalho (ou assinatura) da função. Nele estão contidos o tipo de retorno, nome e parâmetros da função, como mostra a figura a seguir:
Figura 53 - Exemplo de função matemática (Função de Bhaskara)
Retorno: indica o tipo de dado que a função estará retornando ao fim de sua execução. No caso de não retornar nenhum dado, o tipo é o void (do inglês “vazio”). Nome: é como a função será chamada pelas outras parte do código. Não se tem um padrão estabelecido para todos os programas, mas é aconselhável que seja adotado um padrão na forma de escrita do nome em todo o código e que o nome represente o que ela fará ao ser requisitada. Parâmetros: são as variáveis que serão passadas para a função no momento que esta for chamada (entrada). O tipo de cada variável deve ser explicitado no cabeçalho, porém não é informado na chamada da função.
Núcleo de Cidadania Digital | NCD
75
Programação Exemplos de erros comuns em cabeçalhos: int funcao(a, b): Não foi explicitado o tipo dos parâmetros. void calcula media(float a, float b): O nome da função não pode ter espaços. soma(int a, int b): Não foi especificado o retorno da função.
6.3 - Como fazer uma função em C? Em todo o programa em C, temos uma função especialmente importante para seu funcionamento chamada main. Essa função é implementada como mostrado abaixo: int main(argc, argv){ ... “Corpo da função” return 0; }
Essa função apresenta a mesma estrutura das funções comuns, porém ela é usada para ligar todas as outras. O tipo dela é int, pois ao fim de sua execução ela retorna 0 para o sistema operacional indicando o fim do programa sem erros. A função tem o nome padrão de main e tem como parâmetros argc e argv, que representam o número e os argumentos (organizados em um vetor), propriamente ditos, a serem recebidos por ela, respectivamente. Normalmente nós não utilizamos esses parâmetros e acabamos por declarar essa função sem esse argumentos [int main()]. A abertura e fechamento de chaves ({ }) define o bloco de código da função, ou seja, define os limites da função. E, ao fim do código, temos o “return 0;” que é opcional (já está implícito, mas não há problema em utilizar) e indica o retorno ao sistema operacional. Seguem abaixo três exemplos de funções simples:
76
Núcleo de Cidadania Digital | NCD
Programação
Figura 54 - Exemplos de funções simples
Podemos entender as funções como ferramentas para executar aquilo que desejamos, portanto, como qualquer ferramenta, não adianta tê-las e não saber usá-las.
Figura 55 Caixa de ferramentas Quando precisamos usar uma função, nós fazemos uma “chamada” da mesma, por exemplo, para usarmos as funções mostradas acima, escreveremos:
int s = soma(a, b); int m = maior(a, b);
Do lado esquerdo temos as variáveis, que devem ser devidamente declaradas anteriormente ou como mostrado, e receberão o resultado das respectivas funções chamadas no lado direito. Para chamar a função, escrevemos o nome e, entre parênteses, as variáveis passadas como parâmetro para a função. No caso de não serem necessários parâmetros, devemos deixar os parênteses vazios, mas presentes.
Núcleo de Cidadania Digital | NCD
77
Programação Logo, juntando os exemplos em um mesmo código, temos:
Figura 56 - Exemplo completo do uso de funções simples Após a execução deste código, será apresentado o seguinte resultado para nós:
Resultado da soma: 25 O maior eh: 15 To sabendo tudo!!
A leitura do código pelo sistema é feita de cima para baixo, porém ele só registra a existência das funções comuns e não as lê no primeiro momento. A única função que será lida imediatamente será a main() e as demais só serão executadas quando devidamente chamadas pela função principal.
78
Núcleo de Cidadania Digital | NCD
Programação
6.4 - Casos de Funções No uso de funções nos deparamos com algumas situações particulares que vale a pena ressaltar, como feito a seguir: Funções com vários pontos de retorno:
Figura 57 - Sinalização de viaduto o processamento de uma função é paralisado quando há o retorno (return), portanto é comum quando trabalhamos com estruturas condicionais utilizarmos mais de um ponto de retorno, para que o resultado seja de acordo com as condições implementadas, como mostrado a seguir:
Figura 48 - Exemplo de função com mais de um ponto de retorno
Essa função, como o nome já sugere, retorna a se este for menor do que b ou b se a for maior. Funções sem retorno (tipo void):
Figura 58 - Sinalização de rua sem saída
Núcleo de Cidadania Digital | NCD
79
Programação Este tipo de função não retorna nenhum valor para a função principal, ou seja, quando chamada, não é necessário atribuir a função à nenhuma variável. Esse recurso é utilizado quando todo o processamento desejado é feito dentro da função, como no seguinte exemplo:
Figura 59 - Exemplo de função com mais de um ponto de retorno Funções sem parâmetros: são funções que não recebem nenhum tipo de valor da função principal, mas podem ou não retornar algo, por exemplo:
Figura 60 - Exemplo de função sem parâmetros Funções sem parâmetros nem retorno: são funções que, normalmente, só apresentam dados através de apresentação de dados na tela, como no exemplo a seguir:
Figura 61 - Exemplo de função sem parâmetro e nem retorno
80
Núcleo de Cidadania Digital | NCD
Programação
6.5 - Exemplo de código em C Para ilustrar um pouco o uso de funções e como uni-las no código, segue abaixo o exemplo de uma calculadora simples implementada em C:
Figura 62 - Exemplo de código em C para uma calculadora simples
Núcleo de Cidadania Digital | NCD
81
Programação
6.6 - Escopo de variáveis Em programação temos vários tipos de variáveis, como int, float, char, porém o modo como as utilizamos define o escopo delas, ou seja, indica qual ou quais funções podem ver a variável. O escopo é definido de acordo com o local onde a variável foi declarada. Quando uma variável é declarada dentro de uma função, ela é considerada variável local e só poderá ser utilizada por aquela função específica. Caso seja necessário o uso de seu valor em uma outra função, ela deve ser passada como parâmetro no momento da chamada da função. Já a variável global é declarada fora de qualquer função, normalmente logo após aos “#includes” e pode ser utilizada por qualquer função sem precisar ser passada como parâmetro. À primeira vista o uso de variáveis globais pode ser tentador, porém essa é uma prática condenável em códigos mais robustos, pois esse tipo de recurso é muito custoso em termos de memória pelo fato do sistema ter de reservar um espaço para essa variável durante toda a execução do programa, o que não acontece com as variáveis locais, que são liberadas no momento do fim da execução da função que a utiliza.
Figura 63 - Exemplo de código com uso incorreto de variáveis locais
O printf() de dentro da função, nesse exemplo mostrado, apresentará a soma dos números a e b, porém o printf() que está dentro da main() não permitirá que o programa seja executado, pois o compilador não irá reconhecer a variável apresentada no printf(), uma vez que ela só existe dentro da função soma.
82
Núcleo de Cidadania Digital | NCD
Programação
6.7 - Boas práticas de programação Quando trabalhamos com funções o nosso código fica mais organizado e modularizado, porém devemos ter alguns cuidados para utilizar esse recurso da melhor maneira possível. Uma boa prática é dar nomes sugestivos às funções, ou seja, colocar um nome que se relacione com o que a função faz, e tentar seguir um mesmo padrão de escrita, por exemplo separar palavras no nome da função com “underline”(_) ou letra maiúscula, da mesma forma que devemos fazer com as variáveis. Outra boa prática, e que deve ser implementada em todo o código, é o uso de comentários. O uso de comentários sobre o funcionamento de cada parte do código permite que qualquer pessoa entenda e seja capaz de fazer manutenção ou continuar a implementação do seu código, inclusive você mesmo. Esse recurso pode ser utilizado de duas formas: por linha, usando “//” no início da linha a ser comentada, ou por bloco, onde é definido o início e o fim do comentário com “/*” e “*/”, respectivamente, o que permite o comentário de múltiplas linhas. Um pequeno exemplo de comentário pode ser visto abaixo:
Figura 64 - Exemplo do uso de comentário no código
O texto escrito após “//” é completamente ignorado pelo compilador, pois serve apenas para o programador ou programadores que trabalham naquele código saberem mais diretamente qual é a utilidade daquela linha de código, ou, no nosso caso, o que faz a função menor().
6.8 - Exercícios 1. Faça uma função que receba dois números e tenha como retorno a média aritmética deles. 2. Faça uma função sem retorno que receba dois números e imprima a soma e a diferença entre eles.
Núcleo de Cidadania Digital | NCD
83
Programação 3. Faça uma função sem parâmetros nem retorno que imprima na tela os números de 0 a 100. 4. Faça uma função com vários pontos de retorno que receba dois números e os compare, retornando 0 se eles forem iguais, 1 se o segundo for maior que o primeiro e -1 se o primeiro for maior que o segundo. 5. Faça uma função que retorne o resultado da operação feita entre dois números, passados como parâmetro. A operação a ser feita também deverá ser recebida como parâmetro na forma do seu símbolo característico.
84
Núcleo de Cidadania Digital | NCD
Programação
Capítulo 7 - Vetores Um dos recursos muito utilizados na Linguagem C são os vetores. Eles são capazes de armazenar vários dados de um mesmo tipo de modo sequencial, podendo ser utilizados em algoritmos de ordenação, pesquisa linear e busca binária, por exemplo. Por definição, vetor é uma estrutura de dados linear, composta por um número fixo (finito) de elementos de mesmo tipo, ou seja, armazenam vários dados de mesmo tipo como se fossem uma “fila” de dados, ou um trem onde cada vagão é um dado, como podemos ver na seguinte ilustração:
Figura 65 - Exemplo do uso de comentário no código Declaramos os vetores da seguinte maneira: <tipo> <nome_do_vetor>[<tamanho_do_vetor>];
Exemplificando: int vet[32]; Este vetor chamado vet possui 32 posições de variáveis do tipo inteiro. float notas[40]; Este vetor chamados notas possui 40 posições de variáveis float. Abaixo, observa-se como seria armazenado um vetor para conter 5 notas de alunos:
Posição 0 1 2 3 4 Notas 9.5 7.9 9.8 10 3.3 Tabela 12 - Exemplo de vetor de notas
Núcleo de Cidadania Digital | NCD
85
Programação É muito importante ter sempre em mente que um vetor começa na posição zero, portanto um vetor como o vet[32] tem posições que vão de 0 a 31.
7.1 - Acesso e Atribuição O acesso e a atribuição de valores a cada elemento de um vetor é realizado pelos índices, que indicam a posição do elemento no vetor. Temos o vetor abaixo como exemplo. Ele foi declarado com o nome de placar.
Figura 66 - Exemplo de vetor com índices Para acessar o valor de uma posição do vetor e armazená-lo em outra variável qualquer, fazemos: float numero = placar[0]; /* a variável numero recebeu o conteúdo da posição 0 do vetor placar, ou seja, numero=23.48.*/ float valor = placar [5]; /* a variável valor recebeu o conteúdo da posição cinco do vetor placar, ou seja, valor=23.60.*/ Para atribuir ou sobrescrever um elemento, fazemos: placar[6]= 31.60; /* o elemento que ocupa a posição 6 passa a armazenar o número 31.60*/
Figura 67 - Representação do vetor placar após alteração
86
Núcleo de Cidadania Digital | NCD
Programação Veremos a seguir, uma maneira errada de realizar uma atribuição entre vetores:
Figura 68 - Exemplo de erro na atribuição entre vetores Para realizar uma atribuição entre vetores, é necessário que façamos posição a posição, através de uma estrutura de repetição (while ou for). Agora veremos como poderíamos realizar a atribuição solicitada no exemplo anterior:
Figura 69 - Uso correto na atribuição entre vetores
Note que foi realizado um processo iterativo, atribuindo valor elemento a elemento.
7.2 - Definição de tamanho em tempo de execução É possível realizar a definição do tamanho de um vetor durante o tempo de execução. E como podemos realizar isso? Basta solicitar ao usuário durante a execução do programa, o tamanho desejado do vetor. Para total esclarecimento, utilizaremos o exemplo a seguir:
Núcleo de Cidadania Digital | NCD
87
Programação
Figura 70 - Exemplo de vetor com tamanho definido pelo usuário Neste exemplo, durante a execução do programa, será solicitado ao usuário quantificar o tamanho do vetor n, responsável por armazenar as notas.
7.3 - Vetores e funções 7.3.1 - Passando vetores como parâmetros de uma função Diferente das variáveis já aprendidas, quando um vetor é passado como parâmetro de uma função, seu valor alterado dentro da função também é alterado fora dela. Para ilustrarmos, como passamos vetores como parâmetros de uma função, utilizaremos o código abaixo, que é uma função que imprime valores de um vetor:
Figura 71 - Exemplo de função com vetor passado como parâmetro
88
Núcleo de Cidadania Digital | NCD
Programação A função ImprimeVetor() recebe como parâmetros o vetor vet[] e seu respectivo tamanho, os valores destes parâmetros são declarados na função principal.
7.3.2 - Vetores como “retorno” de uma função Aprenderemos através do código abaixo como fazer uma função que imprime cada posição de um vetor. Para isso é necessário que passemos o vetor e seu tamanho como parâmetro e façamos uma estrutura de repetição que imprima cada posição até que o vetor acabe. Existem formas mais eficientes de se trabalhar com vetores e funções através do uso de ponteiros, que será ensinado no capítulo 10.
#include <stdio.h>
void ImprimeVetor(int vetor[ ], int tam){
for(int i=0; i<tam; i++){ printf(“%d”, vetor[i]); } } int main(){ int vet[5]; vet[] = {1,2,3,4,5} ImprimeVetor(vet, 5); return 0; } Figura 72 - Exemplo de função que imprime vetor
7.4 - Cuidados ao se utilizar vetores É necessário tomar algumas precauções ao se utilizar vetores em programação. A seguir algumas dicas na hora de se implementar vetores: Cuidado na implementação para não extrapolar o tamanho fixo do vetor (tentar acessar a posição 5 de um vetor de tamanho igual a 4, por exemplo),
Núcleo de Cidadania Digital | NCD
89
Programação índices inexistentes, ocasionando um erro chamado “Segmentation Fault” ou “Falha de Segmentação”; Evitar o uso de vetores com tamanho maior que o necessário, pois haverá um grande consumo de memória ociosa, caso isso seja feito. Exemplos: Dada uma sequência de N números, exibi-los em ordem invertida.
Figura 73 - Exemplo de código que inverte a ordem de sequência numérica Dada uma sequência de N números, utilizando vetor, exiba apenas os pares.
Figura 74 - Exemplo de código que exibe os números pares de um vetor
90
Núcleo de Cidadania Digital | NCD
Programação
7.5 - Exercícios 1. Crie um programa para que o usuário possa armazenar a idade de 7 pessoas. Lembre-se de informar ao usuário que ele possui somente espaço para armazenar essas 7 idades. 2. Crie um programa para que um professor possa armazenar as notas de todos os seus alunos. Lembre-se que o número de alunos deve ser pedido para o usuário e que você deve criar em tempo de execução um vetor contendo o número de posições igual ao número de alunos. 3. Retorne ao exercício 2 e faça com que o programa retorne a média da sala. 4. Crie um programa para armazenar a pontuação de um dado time ao longo de um campeonato que possui 15 rodadas. Ao final, retorne ao usuário a somatória da pontuação deste time. 5. Retorne ao exercício 4 e coloque a seguinte condição no programa: o time será desclassificado caso não pontue em alguma rodada.
Núcleo de Cidadania Digital | NCD
91
Programação
Capítulo 8 - Vetores II 8.1 - Strings Até agora vimos vetores que guardavam números, mas e se quisermos guardar, por exemplo, o nome de uma pessoa? Para isso usamos o conceito de string. Uma string nada mais é do que um vetor de caracteres. No contexto de C, para identificar o final de uma string, é atribuído ao último espaço do vetor um byte nulo, representado por ‘\0’. Esse conceito é utilizado toda vez que precisamos guardar uma sequência específica de caracteres, como quando precisamos guardar um nome, uma frase ou até mesmo uma senha, por exemplo.
Figura 75 - Ilustração de uma string Na realidade, se formos analisar o que realmente acontece no nível mais baixo do computador, veremos que a string armazena o código numérico da tabela ASCII referente a cada caractere do vetor. O que diferencia uma string de um caractere comum na hora da declaração é que para a string deve ser informado o tamanho, assim como fazemos com os vetores comuns, como mostrado abaixo: char str[<tamanho do vetor>]; Exemplos: char nome[100]; char vogais[6];
Assim como as variáveis e vetores comuns, as strings podem ser inicializadas com um valor pré-determinado e podemos fazer isso de três maneiras, como mostrado a seguir: char nome[6] = {‘J’,’o’,’n’,’a’,’s’,’\0’}; char nome1[6] = “Jonas”; char nome2[11] = “Jonas\0teste”;
92
Núcleo de Cidadania Digital | NCD
Programação Vale a pena ressaltar que não é necessário colocar o ‘\0’, pois ele será inserido automaticamente, porém deve-se ter o cuidado de sempre deixar uma posição do vetor reservada para ele. Além disso, vale lembrar que a atribuição irá acabar no momento em que o sistema ler o ‘\0’, ou seja, se este for colocado na atribuição antes do fim da string desejada, como no vetor nome2[11], o que vier após a ele será ignorado. Na maioria das vezes em que programamos, não é interessante que tenhamos as strings inicializadas e imutáveis, mas sim que as recebamos do usuário. Para isso, a forma mais simples é através do scanf(), como mostrado abaixo: char nome[10], texto[100]; scanf(“%s”, nome); scanf(“%s”, texto);
Podemos perceber duas diferenças entre a leitura de um caractere e de uma string: o uso no “%s” ao invés do “%c”, o que caracteriza o tipo de dado recebido; e a ausência do “&” na frente da variável, pois esse caractere especial indica que, ao invés do valor, está sendo passado o endereço da variável, mas, quando estamos utilizando vetores, isso já é “automático”, uma vez que cada posição do vetor tem um endereço para a próxima. Para imprimir uma string o processo é basicamente o mesmo utilizado para uma variável comum, como mostrado a seguir: printf(“Nome: %s\n”, nome); //O ‘\n’ é apenas para quebra de linha. printf(“%s”, texto);
Para usar algumas funções matemáticas específicas, por exemplo, nós podemos utilizar, como já mostrado, a biblioteca “math.h”. No caso de operações com strings não é diferente. Para facilitar nosso trabalho com as strings usamos a biblioteca “string.h”. Essa biblioteca traz, entre outras, as funções strcat, strcmp, strlen e strcpy, que são as mais utilizadas. Para entendermos a função de cada uma delas, vamos detalhá-las abaixo: strcat: concatena strings, ou seja, copia a segunda string e a cola no fim da primeira (temos que tomar o cuidado em ter certeza de que o primeiro vetor seja grande o suficiente para comportar as duas strings), como mostrado na sintaxe: strcat(stringDestino, stringFonte);
Núcleo de Cidadania Digital | NCD
93
Programação strcmp: compara as duas strings passadas como parâmetro, retornando -1 caso a primeira seja menor que a segunda, 0 caso sejam iguais e 1 caso a segunda seja menor que a primeira. A sintaxe dessa função é a seguinte: strcmp(string1, string2); strlen: retorna o número de caracteres da string passada como parâmetro (caso uma string contenha números, estes serão interpretados como caracteres também), como mostra a sintaxe: strlen(string); strcpy: copia a segunda string e cola na primeira, sobrescrevendo caso esta não seja vazia (caso a primeira string seja maior que a segunda, provavelmente haverão valores indesejados ao fim dela, pois serão os que sobraram da antiga). A sintaxe é a seguinte: strcpy(stringDestino, stringFonte);
8.1.1 - Exercícios 1. Faça uma função que receba um nome digitado pelo usuário e o imprima na tela. 2. Faça uma função que receba o nome e o sobrenome do usuário em duas strings diferentes, recebidas como parâmetro, faça a concatenação delas e imprima na tela. 3. Faça uma função que receba em uma string o nome completo de uma pessoa e imprima somente o primeiro nome. Dica: utilize seus conhecimentos de vetores. 4. Faça uma função que ao ser recebida uma palavra todas as vogais sejam substituídas por “x”. Ela deve ser impressa antes e depois da substituição. Dica: utilize seus conhecimentos de vetores. 5. Faça uma função que receba o nome completo (nome e 2 sobrenomes) do usuário e os separa em strings diferentes. Os nomes devem ser impressos em ordem inversa (imprime o último nome, depois o segundo e depois o primeiro). Dica: utilize seus conhecimentos de vetores.
94
Núcleo de Cidadania Digital | NCD
Programação
8.2 - Matrizes
Figura 76 - Exemplo de matriz como junção de vetores O uso de vetores nos facilitou a implementação de muitos programas, mas ele não resolve todos os problemas de alocação de dados. Por exemplo, se formos guardar 4 notas de cada aluno em uma sala com 50 pessoas, como faremos para guardar mais de uma nota para cada elemento do vetor alunos? Para resolver essa questão de maneira mais simples, nós fazemos o uso das matrizes. Uma matriz é, basicamente, um vetor de vetores, ou seja, em cada instância do vetor guardamos um outro vetor, formando uma espécie de “tabela”, como ilustrado a seguir: 1
9,0
7,0
7,5
8,3
2
4,5
7,3
5,5
9,8
3
7,6
10,0
8,7
9,3
. . .
. . .
. . .
50
5,6
7,8
. . . 9,3
. . . 8,8
Tabela 13 - Matriz de notas (a primeira coluna é o ID e as demais são as notas do aluno
Núcleo de Cidadania Digital | NCD
95
Programação A declaração, bem como a manipulação de matrizes, é bem semelhante a de vetores, porém é necessário um par extra de colchetes (“[]”) para o tamanho, na declaração, ou para a posição, na manipulação, das colunas da matriz. Portanto, a sintaxe da declaração fica como nos exemplos abaixo: Exemplos:
int produtos[100][800]; int alunos[20][100]; int boletim[20][5];
Se uma matriz é um vetor de vetores e a única diferença na declaração é um par extra de colchetes, podemos entender um vetor como uma matriz “1xn” ou “nx1” ([1][n] ou [n] [1]), sendo n um número inteiro qualquer. Assim como com os vetores, podemos atribuir valores a cada posição da matriz no momento da declaração e, para isso, atribuímos um conjunto de conjuntos, em que cada um desses conjuntos internos serão as linhas da matriz, como mostrado no exemplo: int matPreenchida[4][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Tabela 14 - Ilustração do preenchimento de uma matriz Caso essa atribuição não aconteça, a matriz será criada e preenchida com “lixo”. Uma outra forma de evitar que isso aconteça é inicializar a matriz com um mesmo valor para todas as posições e, para isso, fazemos como mostrado a seguir (matriz inicializada com zeros): int matInicializada[4][4] = {0};
96
Núcleo de Cidadania Digital | NCD
Programação Agora que sabemos como atribuir valores à nossa matriz, devemos entender como podemos acessar um item específico dela. Para isso, assim como fazíamos com os vetores, devemos apenas especificar qual é o elemento. A única diferença é que além da coluna, devemos informar em qual linha está o valor desejado, como no exemplo a seguir:
int elemento=0, matriz[4][4]={{7,3,5,6},{2,7,5,9},{1,6,4,10},{14,3,17,13}}; elemento = matriz[0][1]; // Linha 0 e coluna 1. printf(“%d”, elemento); //O número impresso será 3. matriz[0][3] = 0; //{7,3,5,0} - primeiro conjunto matriz[0][1] = 100; //{7,100,5,0} - primeiro conjunto matriz[3][2] = 1000; //{14,3,1000,13} - quarto conjunto elemento = matriz[0][1]; //elemento = 100; printf(“%d”, elemento); //O número impresso agora será 100.
Mas, e se eu quiser que o usuário preencha a matriz? E se eu quiser que meu código a preencha sem que eu tenha que atribuir na declaração? Para fazermos algo do tipo ou alguma operação que seja necessário percorrer a matriz elemento por elemento, devemos fazer uma implementação de uma estrutura de repetição de modo que percorra tanto as linhas como as colunas. Esse procedimento é semelhante ao utilizado com vetores, porém é utilizado um for dentro de outro for, de modo que um seja para percorrer as linhas e o outro para as colunas, como mostrado a seguir:
Figura 77 - Exemplo de preenchimento de matriz usando for
Exemplos: Implemente uma função que receba uma matriz 10x2 de produtos (a primeira coluna de ID’s e a outra de preços) como parâmetro e imprima o código e o preço do produto mais caro.
Núcleo de Cidadania Digital | NCD
97
Programação Resolução:
Figura 78 - Exemplo de código onde é impresso o maior valor na matriz e ID OBS.: A notação de “%.1f” ou similar serão explicadas no final deste capítulo.
Faça uma função que receba uma tabela com a altura e o peso de cada um dos 30 alunos de uma turma da academia. A matriz é 30x3, sendo que a primeira coluna é de ID’s, a segunda de alturas e a terceira de pesos. Você deve imprimir essa matriz. Resolução:
Figura 79 - Exemplo de código que imprime matriz
98
Núcleo de Cidadania Digital | NCD
Programação 8.2.1 - Exercícios 1. Faça um código que declare uma matriz 4x4 e faça com que o usuário a preencha. Após preenchida, imprima elemento por elemento. 2. Faça uma função que receba uma matriz de inteiros 10x10 e imprima a média dos valores presentes nela. 3. Implemente uma função que receba uma matriz com o código de 20 alunos e 5 notas de cada. Deve ser calculada a média de cada aluno e impressa uma nova matriz com o código de cada aluno e sua respectiva média.
8.3 - Boas Práticas de Programação Quando utilizamos dados do tipo float, uma boa prática que aconselhamos é formatar a saída da maneira mais conveniente, limitando quantos números após a vírgula desejamos que seja mostrado. Esse tipo de formatação é feita colocando um algarismo que represente quantos dígitos desejamos após a vírgula logo após o símbolo de porcentagem, precedido de um ponto, como a seguir:
printf(“%.2f”, altura); //ex: 1,80; 1,55 e 1,22.
Núcleo de Cidadania Digital | NCD
99
Programação
Capítulo 9 - TAD’s
Figura 79 - TAD
Os TAD’s são os tipos abstratos de dados, e sua implementação foi uma forma encontrada para que o código fique mais organizado e mais seguro, pois ele separa as operações possíveis de serem efetuadas (funções implementadas) das estruturas de dados propriamente ditas (essas estruturas serão abordadas em detalhes mais à frente). Além disso, ainda há a vantagem de facilitar o reuso de código pelo fato de um mesmo TAD poder ser acessado por mais de uma programa e também facilita a manutenção, uma vez que alterações no TAD não afetam os programas que o utilizam. Os tipos fundamentais de dados, como int e float, funcionam como TAD’s, pois não é de competência do programador entender como uma soma, por exemplo, é feita, ele somente utiliza seu operador e obtém o resultado desejado. Isso porque as operações com esses tipos de dados (soma, subtração, divisão e etc.) estão escondidas em uma espécie de TAD. Uma boa prática de programação é a utilização desse tipo de dados sempre que pudermos, normalmente quando estão envolvidas estruturas de dados mais elaboradas que serão tratadas no decorrer do capítulo.
100
Núcleo de Cidadania Digital | NCD
Programação Um TAD é, especificamente, uma estrutura que armazena dados de interesse das funções a serem executadas pelo(s) programa(s) que o acessa(m) e oferece ao resto do código o acesso a essas funções através apenas de seus protótipos, escondendo a verdadeira implementação das mesmas.
9.1 - Struct Muitas vezes os tipos de dados existentes não suprem nossas necessidades, portanto podemos criar nossos próprios tipos de dados através da utilização de struct. E com o uso das nossas próprias estruturas de dados, fazemos com que nosso programa fique mais adequado a nossa realidade, facilitando a implementação. Essa estrutura pode ser usada conforme os exemplos a seguir:
Figura 80 - Exemplo de duas structs
Uma curiosidade é que essa estrutura de dados foi criada para funcionar como algo parecido com as classes e templates presentes nas linguagens orientadas a objetos, como Java e C++. Após declarada a struct, podemos utilizá-la declarando instâncias da mesma, como mostrado a seguir: struct aluno aluno1;
Núcleo de Cidadania Digital | NCD
101
Programação Porém, essa sintaxe em que é necessária uma declaração, como a mostrada, não é muito prática, portanto, podemos utilizar o artifício do typedef. Ele pode ser usado no momento em que é criada a struct ou a qualquer momento do código, mas devemos nos atentar para as diferenças de sintaxe, como mostrado a seguir: typedef struct disciplina Disciplina; typedef struct ponto{ double x; double y; } Ponto; Após esse tipo de implementação, podemos declarar instâncias das estruturas apenas colocando o nome da estrutura (Ponto ou Disciplina) seguido do nome da variável, como declarado abaixo: Disciplina d; Ponto p;
Agora que já vimos o que são e como criá-las, chegou a hora de entendermos melhor como usarmos esse recurso. Podemos utilizar as struct’s da mesma forma que utilizamos os outros tipos de dados, com a única diferença de que temos que implementar struct antes de declarar uma instância dela. Porém, utilizando nossos conhecimentos de TAD’s, a forma mais adequada de utilizá-las é implementando estas em um arquivo “.h” à parte e depois importando esse arquivo através do comando “#include “<nome_arquivo>.h”” (esse comando é colocado nas primeiras linhas do código para evitar erros de referência e melhorar a organização). Depois de importado o arquivo, somente precisamos declarar a instância da struct que desejamos e utilizá-la. Agora conseguimos criar e declarar instâncias de struct’s, mas como podemos usar cada atributo da struct individualmente? Para isso nós fazemos uso de uma notação diferente, na qual colocamos o nome da instância que estamos utilizando seguido do atributo desejado, separados por um ponto, como mostrado a seguir na struct Ponto, onde são atribuídos valores aos atributos x e y: Ponto p1; p1.x = 10; p1.y = 2;
102
Núcleo de Cidadania Digital | NCD
Programação Exemplo:
Figura 81 - Exemplo do uso de struct
9.2 - Struct’s e funções Como dito anteriormente, uma struct é um novo tipo de dado criado pelo programador e, por isso, faz sentido usarmos esse recurso como utilizamos os outros tipos de dados, como int e float, por exemplo. Portanto, para podermos passar uma struct como parâmetro de uma função, fazemos exatamente como foi mostrado no capítulo 6 (Funções), especificando o tipo e colocando o nome da variável, como mostrado a seguir:
Núcleo de Cidadania Digital | NCD
103
Programação
Figura 82 - Passando struct como parâmetro
Assim como os tipos primitivos de dados, podemos também fazer uma função que retorne uma struct. Para isso devemos especificá-la na assinatura da função e termos uma variável adequada para recebê-la na main, como feito a seguir:
Figura 83 - Exemplo de função que retorna uma struct
104
Núcleo de Cidadania Digital | NCD
Programação 9.2.1 - Exercícios 1. Faça um código que receba o nome, média e idade de 5 alunos e retorne o nome e a idade do aluno com a maior média. 2. Implemente um código que declare uma struct de filme usando typedef e guardando 5 de suas principais características (com seus devidos tipos). Depois de declarada, receba valores para preenchê-la e a imprima. 3. Faça uma função que receba 3 instâncias de uma struct (também a ser implementada) de eletrodomésticos, que guarda o nome, preço e consumo de energia do respectivo produto. A função deve imprimir os dados do eletrodoméstico mais econômico.
9.3 - Structs mais complicadas Frequentemente as struct’s não se apresentam de forma tão amigável para nós, pois em alguns casos precisamos guardar matrizes, ou ainda guardar a própria struct em outra estrutura, como vetor. Além de podermos ter uma struct dentro de outra struct, como mostrado no exemplo abaixo: Exemplo: Arquivo “TAD.h”:
Figura 84 - Exemplo de TAD.h
Núcleo de Cidadania Digital | NCD
105
Programação Arquivo “main.c”:
Figura 85 - Exemplo de main.c com o uso de TAD.h
9.4 - Makefile Quando compilamos nossos códigos pelo terminal, digitamos os mesmos comandos várias vezes para executar essa tarefa. Para programas simples, isso pode parecer uma tarefa fácil, mas mesmo assim monótona. Porém, conforme aumentamos a complexidade e o número de arquivos interligados do nosso código, percebemos que esses comandos do terminal crescem cada vez mais.
106
Núcleo de Cidadania Digital | NCD
Programação
Figura 86 - Exemplo de comando para compilar programa com vários arquivos
Para simplificar o processo de compilação, que normalmente é feito várias vezes até que o código fique pronto, foi criado o makefile, que nada mais é que um arquivo que guarda, com uma linguagem específica (shell script), todos os comandos necessários para compilarmos nosso código, reduzindo todo esse processo ao simples comando “make”. Um exemplo de makefile simples está sendo mostrado na figura abaixo:
Figura 87 - Exemplo de makefile
Vamos analisar cada comando utilizado no exemplo: CC: informa qual será o compilador utilizado; CFLAGS: é onde podemos colocar opções específicas do compilador, nesse caso está sendo utilizada a ferramenta “Wallgrind”, que detalha mais possíveis erros de compilação; all: informa o nome das regras a serem executadas; program (pode mudar de acordo com o que for escrito em all): diz qual será o arquivo destino da execução dessas regras;
Núcleo de Cidadania Digital | NCD
107
Programação program.o (também pode ser alterado): descreve o(s) arquivo(s) onde está o código, seguido das bibliotecas utilizadas (.h); clean: descreve o comando utilizado para excluir os arquivos intermediários criados no processo de compilação; run: onde são descritos os comandos, propriamente ditos, para a execução. Nesse caso, como o comando program já havia sido declarado, ele é somente repetido aqui para que o run recompile o programa e garanta que todas as alterações que tenham sido realizadas no código antes desse passo, sejam consideradas. Na linha seguinte, é colocado o comando para execução do programa após ser compilado.
Para executar o makefile, precisamos somente colocá-lo na mesma pasta onde estão o(s) código(s) e executar o comando “make” no terminal, como mostrado na figura:
Figura 88 - Comando para a execução do makefile no terminal
108
Núcleo de Cidadania Digital | NCD
Programação
Capítulo 10 - Ponteiros Quando trabalhamos com programas que envolvem muitos dados, podemos ter um processamento mais lento ao movermos esses dados de uma função para outra. Para evitar esse tipo de sobrecarga, o recurso encontrado na linguagem C é o uso de ponteiros. Para que toda uma estrutura (uma matriz, por exemplo) não precise ser carregada para a função toda vez que esta é chamada, fazemos o uso dos ponteiros, que são variáveis de armazenamento de endereços de memória. Ou seja, ao invés de passarmos a estrutura como parâmetro de uma função, passamos apenas o local onde ela se encontra. Os ponteiros se mostram ainda mais úteis quando pensamos que não precisamos mais passar uma matriz inteira, por exemplo, para uma função, mas somente o endereço do campo que desejamos alterar. Você pode não ter percebido, mas já trabalhou com ponteiros anteriormente nessa apostila. No capítulo 8, ao descrevermos como “automático” o fato de, na leitura de uma string (scanf), somente ser necessário que passemos o nome do vetor de caracteres, na verdade se deve ao fato de que cada campo do vetor ter um ponteiro interno para o próximo. E isso ocorre com todo e qualquer tipo de vetor. Logo, todos os programas que utilizam vetores, estão, intrinsecamente, utilizando ponteiros. Assim como ponteiros podem fazer seu programa ficar mais eficiente, podem também fazê-lo, simplesmente, não compilar. Há situações em que uma falha no código pode fornecer um endereço fora do escopo do programa, resultando em um erro conhecido como “Segmentation Fault”.
10.1 - Definição Para ir até a casa de alguém, é preciso que se tenha o endereço dessa pessoa, sendo desnecessário tê-la ao seu lado durante todo o percurso até lá. Da mesma forma, não é necessário carregar uma variável até a memória durante toda a execução do programa, mas apenas ter um ponteiro com seu endereço para que o programa acesse a memória apenas quando necessário. Assim, podemos dizer que o ponteiro aponta para a variável. Para entendermos de forma prática como funciona a utilização dos ponteiros, precisamos saber que quando declaramos uma variável do tipo int, por exemplo, reserva-se 4 bytes da memória, ou seja, a quantidade de bytes depende do tipo de dado a ser armazenado. Logo, ao declararmos um ponteiro do tipo int, estamos atribuindo a ele o endereço do primeiro byte (da sequência de 4 bytes), sendo que a operação nos 3 bytes seguintes será automática, isto é, passando o endereço do primeiro byte já estamos passando o do bloco de memória inteiro ocupado pela variável.
Núcleo de Cidadania Digital | NCD
109
Programação
10.2 - Atribuição de variáveis Como dito anteriormente, é necessário que se entenda como é feita a atribuição de variáveis “por debaixo dos panos” para se entender o funcionamento dos ponteiros. Quando fazemos a atribuição de um inteiro, por exemplo, temos uma linha de código como “int x = 10;”, porém o que realmente acontece é que o sistema operacional separa um espaço de memória equivalente ao tamanho de um int e com um endereço em hexadecimal 0xA15D5487, por exemplo, atribuindo a ele o valor 10. Para facilitar o idealização da memória, podemos pensar nela como sendo uma grande tabela, onde suas células guardam valores de diversos tipos e têm seus respectivos endereços em hexadecimal. Para ilustrar esses conceitos, façamos um pequeno exemplo:
int x = 10;
0xA15D5486 32145 0xA15D5487 10 0xA15D5488 55674
Tabela 15 Tabela ilustrando que cada variável tem um endereço (1)
x = 25;
. . . . . .
0xA15D5486 32145 0xA15D5487 25 0xA15D5488 55674
Tabela 16 Tabela ilustrando que cada variável tem um endereço (2)
110
. . . . . .
Núcleo de Cidadania Digital | NCD
Programação
10.3 - Aritmética de ponteiros Um ponteiro, ao mesmo tempo que aponta para um espaço de memória, também é uma variável e tem seu próprio espaço na memória, portanto é necessário que tenhamos uma forma de diferenciar quando nos referimos ao espaço de memória apontado ou ao ocupado pelo ponteiro. Essa diferenciação é feita através do uso de * ou &.
10.3.1 - Utilizando o operador * O * é usado desde o momento da criação do ponteiro e é ele que o diferencia das variáveis comuns. Observe que, nesse caso, o símbolo * não tem ligação com a operação de multiplicação. Ele pode ser interpretado por “o conteúdo apontado por”, uma vez que quando usamos *p, por exemplo, estamos nos referindo ao espaço de memória apontado pelo ponteiro p. Como vimos então, somente podemos diferenciar a declaração de um ponteiro para a de uma variável qualquer com o uso do *. Logo, ao declararmos int x=4 estamos declarando a variável inteira x e atribuindo a ela o valor 4, porém, ao declararmos int *p estamos declarando um ponteiro p que, por enquanto, não aponta para lugar algum, então dizemos que a ele é atribuído um “lixo”, como mostrado na figura abaixo:
Figura 89 - Ilustração da declaração de um ponteiro
Para evitar problemas de esquecer um ponteiro apontando para um lixo, é interessante que façamos a declaração e a atribuição na mesma linha. Caso não seja possível atribuir um valor “útil”, atribuímos NULL. Dessa forma não vamos correr o risco de poluir nosso resultado com um lixo.
Núcleo de Cidadania Digital | NCD
111
Programação Ponteiros podem ainda existir para vários tipos de dados. Se queremos declarar um ponteiro p para apontar para um inteiro, usaremos então, como no exemplo acima, int *p, ou seja, aponta-se aqui para um endereço de memória que contém valor do tipo int. O mesmo vale para qualquer tipo de variável, seja ela double ou char, por exemplo. Note ainda que, na declaração de um ponteiro, o símbolo * deve estar entre o tipo e o nome da variável, podendo-se assim escrever char *p ou char* p, por exemplo, como preferir.
10.3.2 - Utilizando o operador & O símbolo &, ao contrário do que acontece com o *, representa “endereço de”, ou seja, iremos utilizá-lo sempre que quisermos definir para onde será apontado o ponteiro. Logo, quando temos &x isso indica o endereço hexadecimal da memória no qual está alocada a variável x. Portanto a utilização do & está muito atrelada ao uso também do *, pois só podemos apontar para uma variável usando o * se antes tivermos seu endereço, que é obtido através do &. Por exemplo, tem-se: int num = 10;
E deseja-se armazenar o endereço da variável num no ponteiro p: int *p = &num;
ou int* p = NULL; p = &num;
Logo, *p indica o valor atual de num, 10. Caso deseje alterar o valor de num para 50, por exemplo, pode-se então fazer: num = 50; ou *p = 50;
Das duas formas, o valor de *p será atualizado para o novo valor, 50.
112
Núcleo de Cidadania Digital | NCD
Programação 10.3.3 - Exercícios 1. Com base no resultado esperado, conserte o código abaixo para que o programa funcione corretamente: a. Resultado esperado: “Valor: 10” #include<stdio.h> int main(){ int x = 2; int *p = NULL; *p = x; x=10; printf(“Valor: %d”, *p); } b. Resultado esperado: “Valor: 55” #include<stdio.h> int main(){ int n = 30; int *p = NULL; *p = &n; int soma= p + 25; printf(“Valor: %d”, soma); } 2. Descreva com suas palavras o que está acontecendo nos códigos abaixo: a. #include<stdio.h> int main(){ int nota = 7; int *nAluno = &nota; printf(“Nota: %d”, *nAluno); nota = 9; printf(“Nota revisada: %d”, *nAluno); }
return 0;
Núcleo de Cidadania Digital | NCD
113
Programação c. #include <stdio.h> int main() { int n1 = 3, n2 = 10, *p = NULL; p = &n1; n1 = 5; n2 = (*p) + 1; n1 = 10; printf(“Valores finais: n2=%d\n *p=%d”, n2, *p);
}
return 0;
10.4 - Operações com Ponteiros Nesta sessão iremos abordar algumas operações usuais que são feitas com ponteiros. Entender essas situações típicas lhe ajudarão a entender como funcionam as mecânicas da utilização de ponteiros. Iremos adotar uma metodologia que visa responder a essas situações típicas.
10.4.1 - O que acontece quando incrementamos e/ou decrementamos um ponteiro? Como um ponteiro nada mais é que um espaço de memória que aponta para um endereço na memória, quando incrementamos ou decrementamos um ponteiro, na realidade não estamos alterando o valor apontado pelo ponteiro e sim o endereço de memória para o qual o ponteiro aponta. Conforme podemos ver no programa abaixo, criamos uma variável inteira a e atribuímos a ela o valor 1, depois disso, criamos um ponteiro p que aponta para o endereço de a. Quando imprimimos o valor de p obtemos 1, que é o que colocamos na variável a. Ao incrementar a variável p, diferente do que se pode imaginar a princípio, não estamos passando o valor de a para 2 e sim passando o endereço de memória que é apontado por p para a próxima posição de memória.
114
Núcleo de Cidadania Digital | NCD
Programação
Figura 90 - Exemplo de incrementação de ponteiros
Em um exemplo feito no laboratório, obtivemos a saída do programa como: ncd@prod01:~/Área de Trabalho$ gcc -o teste teste.c ncd@prod01:~/Área de Trabalho$ ./teste Numero inicial de p : 1 //Valor de a inicializado Numero após a incrementação : -330016424 //Valor presente na próxima posição de //memória, note que o valor -330016424 //é lixo.
10.4.2 - O que acontece quando imprimimos um ponteiro não inicializado? Quando tentamos imprimir um ponteiro que não aponta para nada, não inicializado, podemos nos deparar com um erro bem comum quando trabalhamos com C, o “Segmentation Fault”, ou o programa utilizará um valor incompatível com o desejado (lixo). Isso ocorre pois ao não inicializar o ponteiro quando o criamos, o sistema o preenche com um endereço qualquer, que pode ou não corresponder a um endereço ao qual o compilador tem permissão de acesso, logo, se não tiver permissão, ele acusará Segmentation Fault, senão ele irá retornar ao programa o conteúdo desconhecido de um espaço de memória qualquer, o que resultará em um valor inesperado e aleatório.
Núcleo de Cidadania Digital | NCD
115
Programação
Figura 91 - Exemplo de Segmentation Fault utilizando ponteiros
ncd@prod01:~/Área de Trabalho$ gcc -o teste teste.c ncd@prod01:~/Área de Trabalho$ ./teste Falha de segmentação (imagem do núcleo gravada)
10.4.3 - Como atribuo o valor de um ponteiro a outro? Conforme já citamos nos tópicos acima, um ponteiro aponta para um endereço de memória. Diferente do que se costuma pensar normalmente, não é incomum que vários ponteiros apontem para o mesmo endereço de memória, poderíamos ter, por exemplo, 3 ponteiros que apontam para o endereço 0x15da4571 (hex) sem nenhum problema.
Figura 92 - três ponteiros apontando para o mesmo espaço de memória
116
Núcleo de Cidadania Digital | NCD
Programação Então, se temos um ponteiro p que aponta para uma variável a, conforme vemos no exemplo abaixo, não existe nenhum problema em ter um outro ponteiro z que também aponta para a variável a. Note que inicializamos o ponteiro z com o endereço de b para evitar o problema descrito no tópico anterior.
Figura 93 - Exemplo de código com atribuição de um ponteiro a outro ponteiro
Em um exemplo feito no laboratório, obtivemos a saída do programa como:
ncd@prod01:~/Área de Trabalho$ gcc -o teste teste.c ncd@prod01:~/Área de Trabalho$ ./teste Valor inicial de p : 1 Valor inicial de z : 10 Apos a atribuicao de z, temos : 1
Núcleo de Cidadania Digital | NCD
117
Programação 10.4.4 - O que acontece se eu tiver dois ponteiros apontando para o mesmo lugar e alterar o conteúdo de um deles? Conforme tratamos no tópico anterior, podemos ter dois ponteiros ou mais que apontam para o mesmo endereço de memória. É importante ressaltar que esses ponteiros apontam para exatamente o mesmo local na memória e o valor deles é o mesmo, portanto, se eu alterar o valor apontado por um desses ponteiros, automaticamente o valor apontado pelo outro ponteiro também será alterado. Podemos observar melhor isso no exemplo a seguir:
Figura 94 - Exemplo de código onde dois ponteiro apontam para um mesmo endereço
ncd@prod01:~/Área de Trabalho$ gcc -o teste teste.c ncd@prod01:~/Área de Trabalho$ ./teste Valor inicial de p : 1 Após o incremento de a, temos que o valor final de p : 2
10.4.5 - Posso ter um mesmo endereço de memória com mais de um valor? Ter um mesmo endereço de memória com mais de um valor é impossível! Os endereços de memória são únicos, portando o valor contido em um determinado endereço é exclusivo. Para ficar mais claro, podemos tratar os espaços de memória como se fossem espaços no mundo real, portanto como no mundo real, não podemos ter 2 ou mais corpos ocupando o mesmo espaço.
118
Núcleo de Cidadania Digital | NCD
Programação
10.5 - Vetores e ponteiros Definimos vetores como uma estrutura de dados linear, composta por um número fixo(finito) de elementos de mesmo tipo. Porém quando falamos de vetores, por facilidade, omitimos que eles nada mais são do que espaços de memória em série, os quais obtemos na declaração, um ponteiro que aponta para a primeira posição dessa sequência e cada elemento aponta para o próximo. Ou seja, ao criar um vetor (int a[10];), estamos reservando 10 espaços de memória em sequência e retornando um ponteiro que aponta para a primeira posição do vetor, a[0]. Se atribuirmos esse ponteiro a um outro ponteiro conforme o exemplo abaixo, podemos observar que um incremento no ponteiro (p++) resulta em um avanço para a próxima posição de memória, conforme vimos anteriormente, portanto irá apontar para a posição a[1] do vetor.
Figura 95 - Exemplo de código que imprime posição seguinte do vetor incrementando ponteiro
ncd@prod01:~/Área de Trabalho$ gcc -o teste teste.c ncd@prod01:~/Área de Trabalho$ ./teste Valor inicial de p : 1 Apos o incremento de a, temos que o valor final de p : 2
Agora se ao invés de incrementar o ponteiro(p++) fizermos uma soma, p=p+3, receberemos a terceira posição a partir do ponto em que p aponta. Portanto se p apontar para a segunda posição do vetor, a[1], e fizermos p=p+3, iremos obter o valor de a[4], conforme o exemplo a seguir:
Núcleo de Cidadania Digital | NCD
119
Programação
Figura 96 - Complemento do código anterior que imprime posição do vetor “saltando” 3 endereço
ncd@prod01:~/Área de Trabalho$ gcc -o teste teste.c ncd@prod01:~/Área de Trabalho$ ./teste Valor inicial de p : 1 Apos o incremento de a, temos que o valor final de p : 2 Apos o incremento de a, temos que o valor final de p : 5
10.6 - Ponteiros e Funções Quando trabalhamos anteriormente com funções estávamos em geral preocupados com os valores que passávamos como parâmetros e com os valores que ela nos retornava. Porém nem sempre queremos funções desse estilo, em algumas ocasiões precisamos que a função apenas modifique o que estamos passando para ela em vez de apenas utilizarem seu valor para gerar um retorno.
120
Núcleo de Cidadania Digital | NCD
Programação 10.6.1 - Passagem por Valor Conforme citamos, quando estamos apenas interessados no valor retornado pela função, fazemos uma passagem por valor, como exemplificado no programa abaixo:
Figura 97 - Exemplo de função com passagem de parâmetro por valor
10.6.2 - Passagem por Referência Esse tipo de passagem recebe esse nome, pois estamos passando a referência da variável para a função, ou seja, estamos passando o endereço. Esse tipo de passagem tem tudo a ver com os ponteiros, pois como estamos passando o endereço, as variáveis envolvidas na função devem ser ponteiros. Esse tipo de passagem é parecida com a anterior, porém iremos agora utilizar o conhecido ‘&’. Supondo o mesmo exemplo acima, do cálculo da circunferência, iremos agora criar a variável que irá receber a circunferência na própria main e iremos passar a referência (endereço) para que a função faça o cálculo.
Figura 98 - Exemplo de função com passagem de parâmetro por referência
Núcleo de Cidadania Digital | NCD
121
Programação É importante notar as diferenças entre as duas passagens, note que não precisamos criar dentro da função a variável “Circunferência” e nem retornar o seu valor ao final, por isso a função que era int, se torna void.
10.7 - Ponteiros e Struct’s Quando trabalhamos com structs anteriormente, vimos que as mesmas podem abrigar diversas variáveis, dentre elas os ponteiros. Também podemos ter ponteiros que apontam para structs.
Figura 99 - Exemplo de ponteiro que aponta para struct
Note que precisamos usar (*ponteiroP).x para acessar a variável, esse tipo de escrita é um pouco confusa, portanto o mais usual costuma ser utilizarmos o operador “->” que é equivalente ao utilizado anteriormente.
122
(*ponteiroP).x ⇔ ponteiroP->x
Núcleo de Cidadania Digital | NCD
Programação
10.8 - Exercícios 1. Descreva o que acontece nos códigos abaixo e diga qual será a saída do código:
a. #include <stdio.h> int main( ){ int num=10; int *p = &num; printf(“%d “, *p); p++; printf(“%d\n”, *p);
} b.
return 0;
#include <stdio.h> int main( ){ int a; int vetor[6] = { 11, 4, 21, 5, 9, 57 }; int *ponteiro = vetor;
}
for (a=0; a<6; a++) { printf(“%d\n”, *ponteiro); ponteiro++; }
2. Crie uma matriz de inteiros 50x50 na função “main()”. Utilizando ponteiros, faça uma função que preencha essa matriz com números de 1 a 2500 em ordem decrescente.
Núcleo de Cidadania Digital | NCD
123
Programação 3. Dado o código abaixo, comente o que está acontecendo linha a linha, e após testar o código, conserte o que está incorreto:
#include <stdio.h> int main( ){ int aux = 0, num, *p, **p; if( aux = scanf(“%d”, &num)) *p = &num; else **p=0;
}
printf(“%d, %d, %d”, num, *p, **p); return 0;
4. Faça uma função que receba um ponteiro para uma string e troque toda primeira letra de cada palavra pela sua correspondente maiúscula. 5. Crie uma função calcula_hexagono() que calcule a área e o perímetro de um hexágono regular de lado L. A função deve ter a seguinte assinatura:
void calcula_hexagono (float l, float *area, float *perimetro)
E deve ser utilizada na função main(). Observação: para compilar no “terminal” utilizando a biblioteca math.h, usa-se -lm. Exemplo: gcc -o teste teste.c -lm
124
Núcleo de Cidadania Digital | NCD
Folha para rascunho
Folha para rascunho
O curso Introdução à Programação tem como objetivo ensinar conceitos básicos de lógica e programação na linguagem “C”. Espera-se que ao final do curso, o aluno tenha obtido conhecimento necessário para programar na linguagem “C” e seja capaz de continuar o seu desenvolvimento por conta própria, pois terá sua base fortalecida para o aprendizado de outras linguagens de programação. O intuito é fomentar o interesse do aluno sobre a programação e ter melhor compreensão dos programas utilizados no cotidiano, bem como torná-lo um produtor de tecnologia em potencial.
Parceiros
@NCD fb.com/curtaNCD www.ncd.ufes.br