Baixe o app para aproveitar ainda mais
Prévia do material em texto
DEPARTAMENTO DE INFORMÁTICA Programação Estruturada Notas de aula Profa. Beatriz Lux Prof. Rolf Fredi Molz Atualizada em fevereiro 2017 Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 1 Sumário 2.0 Bibliografia recomendada 2 3.0 Conceitos Gerais 3 4.0 Comandos de controle de fluxo 17 5.0 Tipo Ponteiro 24 6.0 Modularização 33 7.0 Tipo estruturado homogêneo - matrizes 47 8.0 Strings em C 57 9.0 Tipo estruturado heterogêneo - registros 62 10.0 Métodos internos de ordenação 70 11.0 Métodos internos de pesquisa 77 12.0 Recursividade 79 13.0 Operações em meio magnético - arquivos 82 14.0 Estruturas e alocação dinâmica de memória – exemplo com Lista Encadeada 94 15.0 Anexo – Como adicionar um arquivo Header a um projeto 16.0 Anexo – Tabela ascii 99 101 Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 2 2.0 Bibliografia Recomendada Recomenda-se ao aluno não basear seu estudo apenas no conteúdo destas notas de aula, sendo importante que busque mais informações através da leitura de livros dedicados ao estudo de programação estruturada e da linguagem C. Abaixo cita- mos alguns títulos que consideramos apropriados e que nortearam a produção des- tas notas de aula. Ambos podem ser encontrados na nossa biblioteca: SCHILDT, Herbert. C : completo e total. 3. ed., rev. e atual. São Paulo: Makron, c1997. MIZRAHI, Victorine Viviane. Treinamento em linguagem C. Volume 1. São Paulo: Makron, 1990. MIZRAHI, Victorine Viviane. Treinamento em linguagem C. Volume 2. São Paulo: Makron, 1990. ZIVIANI, Nivio. Projeto de algoritmos com implementações em Pascal e C. São Paulo: Pioneira, 1999. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 3 3.0 Conceitos Gerais 3.1 Objetivos e Caracterização da Linguagem A linguagem de programação C foi criada por Dennis Richtie,no início da década de 70, nos laboratórios Bell para ser usada na implementação de sistemas opera- cionais (UNIX) e outras tarefas de programação de baixo nível. A linguagem era fornecida junto com o UNIX e manteve sua sintaxe padrão inalte- rada por cerca de dez anos. A principal documentação deste padrão encontra-se na publicação "The C Programming Language", de Brian Kernighan e Dennis Ritchie (K&R), tida como a "bíblia da linguagem C". Em 1985, ANSI (American National Standards Institute) estabeleceu um padrão oficial de C, o chamado "C ANSI". Um dos objetivos do processo de padronização C ANSI foi o de produzir um sobreconjunto do C K&R, incorporando muitas das características não-oficiais subseqüentemente introduzidas. C caracteriza-se por ser uma linguagem estruturada, pois possibilita dividir a infor- mação em blocos estanques, o que é feito através de subprogramas (funções), que são os blocos fundamentais de um programa em C. Desta maneira, determinada tarefa pode ser definida e escrita em separado, numa função, utilizando suas pró- prias variáveis (chamadas locais) de cuja existência, não precisará tomar conheci- mento o resto do programa ou os demais subprogramas. Este conceito é um dos tópicos que estudaremos com profundidade em nossa disciplina. Outra característica de C é sua portabilidade, ou seja, um código escrito em linguagem C poderá ser executado em diferentes máquinas, independentemente da sua configuração física e do sistema operacional residente, sendo apenas necessário sua re-compilação para cada arquitetura diferente A Linguagem C tornou-se ao longo do tempo uma das linguagens de programação mais usadas, pois, afora as qualidades acima citadas, possibilita produzir códigos concisos e de rápido processamento por ser uma linguagem de nível médio, onde estão combinados elementos de linguagens de alto nível e as funcionalidades de assembly. Foi utilizada na criação e desenvolvimento de softwares e sistemas ope- racionais que se tornaram famosos em todo mundo, como por exemplo o Sistema Operacional Windows. 3.2 Elementos Básicos da Linguagem 3.1 Símbolos usados Letras de A a Z tanto maiúsculas como minúsculas. O símbolo _ (caracter de sublinha) é considerado letra. Digitos de 0 a 9. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 4 Especiais + - * / = ^ # & , : ; ‘ $ e outros Símbolos especiais usados em conjunto: = atribuição != diferente <= menor ou igual >= maior ou igual { } delimitadores de inicio e fim de um bloco de comandos /* inicio de comentário */ fim de comentário // inicio de comentário de aoenas 1 linha (não é necessário fechá-lo) O tamanho máximo de uma linha é de 127 caracteres. 3.2 Tipos Básicos de Dados Todas as vaiáveis em C possuem um identificador (nome) e um tipo, que especifica os valores que a variável poderá assumir. A tabela 1 apresenta o s cinco tipos bá- sicos, o número de bits que cada um utiliza e a faixa e valores que alcança. Tipo Bits Faixa de valores char 8 -128 à 127 int 16 -32768 à 32767 float 32 -3.4E-38 à 3.4E+38 double 64 -1.7E-308 à 1.7E+308 void 0 Sem valor Tabela 1 3.3 Identificadores Os identificadores, que servem para nomear programas, variáveis, constantes, pro- cedimentos, etc., seguem a seguinte regra: Devem iniciar por uma letra e a esta podem seguir letras, números ou sinal de sublinha. Não pode conter espaços. Tamanho: até 127 caracteres, sendo significativos os 32 primeiros. Obs.: atentar para as palavras reservadas da linguagem que não podem ser usadas. Em C os primeiros 32 caracteres de um nome de identificador são significan- tes. Isso quer dizer que duas variáveis com os 32 primeiros caracteres em comum, diferindo somente no 33º , são consideradas iguais. A linguagem C é case-sensitive , o que significa que letras maiúsculas e mi- núsculas são tratadas como diferentes umas das outras. Por isso, media, Me- dia e MEDia são distintos. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 5 3.4 Palavras reservadas da linguagem A tabela 2 apresenta as palavras reservadas da linguagem C, que não poderão ser utilizadas como identificadores. auto double if static break else int struct case entry long switch char extern register typedef continue float return union default for sizeof unsigned do goto short while Tabela 2 3.5 Estrutura básica de um Programa em C Os programas em C são formados por uma ou mais funções, porém sempre haverá uma (e só uma) função denominada main, onde a execução do programa neces- sariamente irá começar. Além da main poderá haver outras funções, dependendo apenas de como o programador dividir as tarefas que seu programa deverá realizar. Exemplo de um programa simples em C: #include<stdio.h> //inclusão de biblioteca #include<math.h> //inclusão de biblioteca #define v 3.0 // definição de contante int main() { float a,r,q; // declaração de variáveis printf("Informe um real\n"); //apresentação de dados na tela scanf("%f",&a); //entrada de dados via teclado printf("O no.lido foi: %.2f\n", a); r=sqrt(a); printf("raiz quadrada de %.2f: %.2f\n",a,r); q=pow(a,2); printf("%.2f ao quadrado: %.2f\n",a,q); printf("%.2f ao quadrado: %.2f",v,pow(v,2)); return 0; //retorno da função main } Tela de entrada e saída do programa quando executado (F9): 3.5.1 Diretiva #include Toda a diretiva, em C, começa com o símbolo # no início da linha. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 6 A diretiva #include inclui o conteúdo de um outro arquivo dentro do programa atual, ou seja, a linha que contêm a diretiva é substituída pelo conteúdo do ar- quivo especificado. No programa exemplo acima necessitamos incluir alguns arquivos que contêm a declaração de funções de bibliotecas padrão. As bibliotecas, normalmente, pos- suem a extensão .h e se encontram em algum diretório pré-definido pelo compila- dor. Sempre que o programa utilizar alguma função da biblioteca-padrão deve ser incluído o arquivo correspondente. Apresenta-se a seguir alguns dos principais .h da linguagem C: Descrição stdio.h - Funções de entrada e saída (I/O) string.h - Funções de tratamento de strings math.h - Funções matemáticas ctype.h - Funções de teste e tratamento de caracteres stdlib.h - Funções de uso genérico É importante saber que cada diretiva deve estar em sua própria linha. 3.5.2 – Definição de Constantes O conceito de constantes em linguagens de programação é atribuir um certo valor constante (que não pode ser alterado) a um nome, e quando este nome for refe- renciado dentro do código do programa, será utilizado nas operações o valor atri- buído a este nome. Uma das formas de definirmos constantes em C é utilizar a diretiva #define que é conhecida como diretiva de macro-substituição. Conforme o tipo da constante, será sua representação: Constantes de caractere são envolvidas por aspas simples (‘’); Ex; ‘a’ Constantes formadas por cadeia de caracteres são envolvidas por aspas du- plas; Ex: “Tecle algo para sair do programa” Constantes com valor inteiro são representadas por um número inteiro; Ex: 10, -100 Constantes com valor real (float) devem apresentar o ponto decimal seguido da parte fracionária do número; Ex: 19.55 # define nome “João” # define pi 3.1416 # define letra ´a´ O compilador substitui o identificador pelo valor cada vez que aquele for encontrado no programa fonte antes da compilação do programa. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 7 É recomendável o uso de identificadores como sinônimos de constantes pois au- menta a legibilidade do programa e auxilia sua documentação, além disto, o agru- pamento das constantes no início do programa torna mais fácil a sua modificação, caso necessário. 3.5.3 Comentários É bastante útil e indicado colocar comentários nos códigos de programas para fa- cilitar sua leitura e elucidar seu objetivo e funcionamento . Utiliza-se caracteres especiais para indicar comentários: comentários de uma linha podem ser iniciados com duas barras ( // ) comentários de mais de uma linha iniciam com uma sequencia dos carac- teres barra asterisco ( /* ) e encerrados com asterisco barra ( */ ) 3.5.4 Declaração e variáveis Todas as variáveis de um programa deverão ser declaradas. Para declarar uma variável devemos identificador inicialmente seu tipo, a seguir o nome da variável e encerrar a declaração com ponto e vírgula. float raio; int idade; Se tivermos mais de uma variável com o mesmo tipo, podem ser declaradas na mesma linha, separadas por vírgula. float raio, área; As variáveis são classificadas em variáveis locais e globais. Variáveis globais são aquelas declaradas fora do escopo das funções. Variáveis locais são aquelas declaradas no início de um bloco e seus escopos estão restritos aos blocos em que foram declaradas. A tabela 2 exemplifica as duas formas de declaração, local e global. #include<stdio.h> #include<math.h> #define v 3.0 float a,r,q; int main() { printf("Informe um real\n"); scanf("%f",&a); printf("O no.lido foi: %.2f\n ", a); r=sqrt(a); printf("raiz quadrada de %.2f: %.2f\n", a,r); return 0; } #include<stdio.h> #include<math.h> #define v 3.0 int main() {float a,r,q; printf("Informe um real\n"); scanf("%f",&a); printf("O no.lido foi: %.2f\n ", a); r=sqrt(a); printf("raiz quadrada de %.2f: %.2f\n", a,r); return 0; } Exemplo variável global Exemplo variável local Tabela 2 Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 8 Observações: É uma prática tradicional do C, usar letras minúsculas para nomes de variáveis e maiúsculas para nomes de constantes. Isto facilita na hora da leitura do código; Quando se escreve código usando nomes de variáveis em português, evita-se possíveis conflitos com nomes de rotinas encontrados nas di- versas bibliotecas, que são em sua maioria absoluta, palavras em in- glês. 3.5.4 A função principal A linha int main() indica que estamos definindo uma função de nome main. To- dos os programas em C devem ter uma função main, pois é esta função que será chamada quando o programa for executado. O conteúdo da função é delimitado por chaves { }, obrigatoriamente. Isso significa que { (abre chaves) substitui a palavra inicio que utilizamos em algo- ritmos e } (fecha chaves) substitui a palavra fim. O código que estiver dentro das chaves será executado seqüencialmente quando a função for chamada. Em C, sempre que necessitarmos delimitar um bloco de comandos que deve ser executado sequencialmente, utilizamos as chaves, isto se tornará novamente re- levante quando estudarmos o uso dos comando condicionais e de repetição. 3.5.5 O uso do ponto e vírgula É importante notar no exemplo que todas as linhas de comando são separadas por ponto e vírgula, exceto as declarações #include, #define, o cabeçalho da fun- ção e os demarcadores de blocos (chaves) de instruções. Durante a compilação do programa, o ponto-e-vírgula mostra ao compilador quando uma linha de comando termina e quando outra linha de comando se inicia. Assim, o compilador acusará um erro sempre que verificar a falta de um ponto-e- vírgula, pois ele não saberá quando termina ou começa um determinado comando dentro do código digitado. 3.5.6 Operador de atribuição O operador “=” atribui um valor ou resultado de uma expressão contida a sua direita para a variável (ou constante) especificada a sua esquerda. Exemplos: a=V*2; b=c*valor+sqrt(x); É possível fazer atribuição sucessiva de valores: a=b=c=1; Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 9 É possível atribuir um valor a uma variável ao mesmo tempo em que ela é decla- rada. int a=5,b=2; float r=8.6; 3.5.7 Expressões, Operadores e funções aritméticas Expressões são algoritmos especificados para a computação de valores, consis- tem em operações com variáveis, constantes e funções combinadas com opera- dores. a=v*2; c=a+b; c=a/d; 3.5.7.1 Operadores Aritméticos Veja na tabela 3 os operadores aritméticos Operador Ação + Soma (inteira e ponto flutuante) - Subtração ou Troca de sinal (inteira e ponto flutuante) * Multiplicação (inteira e ponto flutuante) / Divisão (inteira e ponto flutuante) % Resto de divisão (de inteiros) -- Decremento ++ Incremento Tabela 3O operador / (divisão) quando aplicado a variáveis inteiras, fornece o resultado da divisão inteira; quando aplicado a variáveis em ponto flutuante fornece o resultado da divi- são "real". O operador % fornece o resto da divisão inteira entre dois inteiros. Exemplo: int a ,b, x, y; float z , z1, z2; { a=17; b=3; z=17.0 x = a / b; y = a % b; z1 = z / b; z2 = a/b; } ao final da execução destas linhas, os valores calculados seriam x = 5 y = 2 z1 = 5.666666 z2 = 5.0 . Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 10 Note que, na linha correspondente a z2, primeiramente é feita uma divisão inteira (pois os dois operandos são inteiros). Somente após efetuada a divisão é que o resultado é atribuído a uma variável float. 3.5.7.2- Funções Aritméticas Pré-Definidas São funções implementadas na biblioteca math.h. Na tabela 4 são apresentadas algumas funções matemáticas em C resultado função tipo do pa- râmetro tipo do re- sultado Logaritmo neperiano log(x) real real Logaritmo base 10 log10(x) real real e elevado à potencia x exp(x) real real Valor absoluto de x fabs(x) real real abs(x) int int Decompõe um real x em duas partes: y recebe a parte inteira e a função devolve a parte fracionária, ambas como real (dou- ble) modf(x,y) real, real real Arredonda o valor de x para cima ceil (x) real real Arredonda o valor de x para baixo floor(x) real real Retorna x elevado a potência y pow(x,y) real real Raiz quadrada de x sqrt(x ) real real Retorna o resto da divisão de x por y Ex: fmod (25.55 , 2) = 1.55 fmod(x,y) real, real real Seno de x sin(x ) real real Cosseno de x cos( x) real real Arco-tangente de x atan( x) real real Tabela 4 A ativação da função no programa é feita através do identificador, nome da função e da lista de argumentos (parâmetros), entre parênteses e separados por vírgula. Obs. : x é o argumento da função, pode ser uma expressão aritmética. 3.5.7.3 Operadores Relacionais Uma relação é uma comparação realizada entre valores do mesmo tipo. Estes va- lores são representados na relação por constantes, variáveis ou expressões. O re- sultado será sempre True ou False. A natureza da relação é indicada por um ope- rador relacional, descrito na tabela 5. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 11 Operador Ação > Maior do que >= Maior ou igual a < Menor do que <= Menor ou igual a == Igual a != Diferente de Tabela 5 3.5.7.4 Operador ternário Em C podemos expressar relações de uma forma bastante compacta, através do uso do operador ternário, que serve para substitui o comando if-else. Como exemplo, na coluna 1 da tabela 6 é apresentada uma construção condicional com o comando if–else e na coluna 2 a sua correspondente com comando ternário. If (n1>n2) maior= n1 else maior=n2 maior=(n1>n2) ? n1 : n2 Tabela 6 Fica definido na construção que aparece na segunda coluna que haverá uma ava- liação da expressão lógica e que se resultar verdadeira deverá ser atribuido o valor de n1 à variavel maior, se resultar falsa, a variável maior receberá o valor de n2. Forma geral do operador ternário: condição ? expressão_1 : expressão_2 3.5.7.5 Operadores Lógicos Os operadores lógicos em C são descritos na tabela 7 Operador Ação && E || OU ! NÃO Tabela 7 Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 12 3.5.7.6 Operadores de Atribuição Composta Em C qualquer expressão da forma: variável = variável operador expressão pode ser escrita como: variável operador = expressão Exemplos: raiz = raiz * 4; raiz *= 4; ano = ano + 10; ano += 10; soma = soma / ( a + b); soma /= (a + b); i = i % 2; i %= 2; A tabela 8 mostra que diferentes posicionamentos dos operadores resultados origi- nam diferentes resultados. x=10; y=++x; y recebe 11 e x recebe 11 x=10; y=x++; y recebe 10 e x recebe 11 int a,b,c,i=3; a:? b:? c:? i:3 a=i++; a:3 b:? c:? i:4 b=++i; a:3 b:5 c:? i:5 c=--i; a:3 b:5 c:4 i:4 Tabela 8 3.5.7.7 Conversão entre tipos de variáveis - type casting O C permite a mudança de tipo das suas variáveis através do operador (cast), onde cast é o novo tipo pretendido. O casting pode ser usado com qualquer um dos tipos simples. Exemplo: int k; char ch = 'A'; k = (int) ch; Aqui o valor de k será 65 (o código ascii do carácter 'A'). Um uso freqüente do casting é assegurar que a divisão entre inteiros não seja uma divisão inteira. Basta para isso converter para real o numerador ou denominador. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 13 O outro operando é assim também automaticamente convertido, antes de se efe- tuar a operação. 3.5.7.8 Operador unário sizeof() O operador sizeof()retorna o tamanho em bytes da expressão ou tipo fornecido entre parênteses Por exemplo, suponha que o tipo float tenha quatro bytes então o operador sizeof(float) retorna o valor 4. 3.6 Funções de entrada e saída de dados 3.6.1 Função de saída de dados formatada Forma Geral: printf (“expressão de controle”, lista de argumentos); Esta função permite escrever na tela e é um comando da biblioteca stdio.h A expressão de controle pode conter caracteres que serão exibidos na tela e os códigos de formatação (ou controle) que indicam o formato em que os argumentos deverão ser impressos. Cada argumento deve ser separado por vírgula. Os códigos de controle usam a notação % para formatar variáveis e \ para formatar a disposição na tela (quebra de linha, tabulação, etc). Para cada formatação de variável na expressão de con- trole deve haver uma variável na lista de argumentos. Exemplo: printf("O MDC entre %d e %d eh: %d\n",m,n,x); A tabela 9 apresenta códigos de controle para formatar variáveis: Código Significado %d inteiro %f float %lf double %c 1 caractere %s String %p ponteiro %% Coloca na tela um % Tabela 9 Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 14 A tabela 10 apresenta os códigos de controle para disposição na tela Código Significado \b Retrocesso ("back") \f Alimentação de formulário ("form feed") \n Nova linha ("new line") \t Tabulação horizontal ("tab") \" Aspas \' Apóstrofo \0 Nulo (0 em decimal) \\ Barra invertida \v Tabulação vertical \a Sinal sonoro ("beep") \N Constante octal (N é o valor da constante) \xN Constante hexadecimal (N é o valor da constante) Tabela 10 Na lista de argumentos pode haver variáveis e/ou constantes, sempre separadas por vírgula, veja o exemplo do trecho de programa abaixo: int p p= 65487 * 236597 ; printf("O Produto entre %d e %d eh: %d\n",65487, 236597 ,p); A função printf(): Imprime a partir do último caracter impresso.• Não muda de linha até que a linha acabe ou que encontre um \n. Alguns exemplos de printf() e o que eles exibem: printf ("Teste %% %%") -> "Teste % %" printf ("%f",40.345) -> "40.345" printf ("Um caractere %c e um inteiro %d",'D',120) -> "Um carac- tere D e um inteiro 120" printf ("%s e um exemplo","Este")-> "Este e um exemplo" printf ("%s%d%%","Juros de ",10) -> "Juros de 10%" Ao apresentar na tela valores com ponto flutuante, estes serão colocados em nota- ção científica. Se quisermos apresentar os valores com casas decimais é necessá- rio acrescentar um formato, como no exemplo abaixo: #include <stdio.h> #define V 3 int a; float b; Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 15 int main() { a=V*2; b=sqrt(a); printf(" %d %5.2f ",a,b); return 0; } Através desta especificação de formato ( printf(" %d %5.2f ",a,b), o valor de b será escrito com 5 caracteres no total (incluindo o ponto da casa decimal) e duas ca- sas depois do ponto decimal. A tabela 11 apresenta mais exemplos: Código Descrição %6f Ponto flutuante com pelo menos seis caracteres %.2f Ponto flutuante com dois caracteres após o ponto decimal %6.2f Ponto flutuante com pelo menos seis caracteres e dois após o ponto decimal %6d Inteiro com pelo menos seis caracteres Tabela 11 3.6.2 Função de entrada de dados formatada Permitem ler dados pelo teclado. Sua sintaxe é semelhante ao printf: Forma Geral : scanf(“expressão de controle”, lista de argumentos); A expressão de controle pode conter códigos de formatação, precedidos por %. A lista de argumentos: Depende da String de Formato. Separados por ‘,’. Sempre variáveis. As variáveis devem ser precedidas por ‘&’ Exemplo: int mat; float media; int main () { printf(“Entre com a matrícula do aluno e sua media: "); scanf(“%d %f“, &mat, &media); -------; --------; } %f indica que será lido um valor do tipo float, atribuído a uma variável na sequencia indicada na lista de argumentos (&media). A lista de argumentos consiste então no endereço das variáveis. C oferece um operador para tipos básicos chamado operador de endereço &, que retorna o endereço do operando. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 16 Exemplo para o operador de endereços: int main() { int num; num = 2; printf (“Valor %d, endereço = %u”, num, &num); return 0; } O programa acima imprime o valor e o endereço de memória da variável num. %u é usado pois endereço é visto como inteiro sem sinal. Ex. de saída: Valor=2, endereço = 1370 3.6.3 Funções de Entrada e Saída de dados para caracteres 3.6.3.1 Funções de Entrada e Saída para caracteres (com enter) getchar() Biblioteca: stdio.h Declaração: int getchar(void); Propósito: A função getchar() (get character) lê um caracter individual da entrada padrão (em geral, o teclado). Esta função é dita line buffered, isto é, não retorna valores até que o caracter de controle line feed (\n) seja lido. Este caracter, normalmente, é enviado pelo teclado quando a tecla [enter] é pressionada. Se forem digitados vários caracteres, estes ficarão armazenados no buffer de entrada até que a tecla [enter] seja pressionada. Então, cada chamada da função getchar() lerá um caracter armazenado no buffer. putchar() Biblioteca: stdio.h Declaração: int putchar(int c); Propósito: Esta função putchar() (put character) imprime um caracter individual c na saída padrão (em geral o monitor de vídeo). getchar retorna inteiro mas é possível atribuir a uma variável char (o que geralmente é feito). putchar é declarada para entrada de inteiro mas geralmente é usada com um argumento caracter. 3.6.3.2 Funções de Entrada e Saída para caracteres (sem enter) getch(), getche() Alguns compiladores nào comportam estas funções Declaração: int getch(void); int getche(void); Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 17 Ao ser executada, a função getch() (get character) aguarda que uma tecla (ou com- binação de teclas) seja pressionada, recebe do teclado o código correspondente e retorna este valor. A função getche() (get character and echoe) tem o mesmo funcionamento porém escreve na tela, quando possível, o caracter correspondente. Exemplo // Uso das funcoes getchar() e putchar() #include <stdio.h> char c; void main(void) { printf("\nDigite uma frase:\n"); do { c = getchar(); // leitura do 'buffer' if(c >= 97 && c <= 122) // se c e' minúsculo... { c -= 32; // c=c-32 transforma em maiúsculo } putchar(c); // impressao dos caracteres maiúsculos }while (c != '\n'); // ...enquanto nao e' [enter] } 4.0 Comandos de controle de fluxo 4.1 Comandos Condicionais Um comando condicional é usado para selecionar um único comando de seus com- ponentes para a execução. 4.1.1 Comando if- else O comando IF especifica que um comando deve ser executado somente se o re- sultado de uma expressão lógica for verdadeira. Se for falsa, então outro comando deve ser executado, ou nenhum comando da sua estrutura deve ser executado. Formas do comando: A) if (condição) comando ; Nesta forma, o comando somente será executado se a condição for TRUE. Caso contrário, se a condição for FALSE, nenhuma ação será realizada. O comando a ser executado pode ser simples ou composto. Um comando será simples quando for apenas uma linha de instrução (seguida de ponto e vírgula) e será composto quando houver mais de uma linha de instrução, originando um bloco que deverá ser delimitado por chaves Ex: if (a = = b ) Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 18 { x = 1.5 ; y = 2.5 ; } B) if (condição) comando 1; (ou bloco) else comando 2; (ou bloco) Neste caso, se a condição for verdadeira será executado o comando 1 e caso for falsa será executado o comando 2. Tanto o comando 1 como o comando 2 po- dem ser simples ou compostos. Exemplos: If (a = = b) x = 1.5; else x = 2.5 ; if (a == b) { x = 1.5; x := 1.5 ; y = 2.5 ; else { x = -1.5 ; y = -2.5 ; } Exemplo: Faça um programa que leia um número e informe se é igual a 10, maior ou menor que 10. #include <stdio.h> int num; void main (void) { printf ("Digite um numero: "); scanf ("%d",&num); if (num==10) { printf ("\n\nVoce acertou!\n"); printf ("O numero e igual a 10.\n"); } else if (num>10) printf ("O numero e maior que 10."); else printf ("O numero e menor que 10."); } Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 19 4.1.2 Comando condicional switch O comando switch é próprio para testar uma variável em relação a diversos valo- res pré-estabelecidos. É básicamente usado na implementação de menus. Sua forma geral é: switch (variável) { case constante_1: declaração_1; break; case constante_2: declaração_2; break; case constante_n: declaração_n; break; default declaração_default; } A estrutura switch não aceitacondições, apenas constantes. O switch testa a variável e executa a declaração cujo case corresponda ao valor atual da variável. A declaração default é opcional e será executada apenas se a variável, que está sendo testada, não for igual a nenhuma das constantes. Não há necessidade de chaves envolvendo as instruções de cada case, pois estas instruções não são con- sideradas um bloco, mas deve haver um conjunto de chaves envolvendo todo o corpo de cases, incluindo default. O comando break, faz com que o switch seja interrompido assim que uma das declarações seja executada. Mas ele não é essencial ao comando switch. Se após a execução da declaração não houver um break, o programa continuará exe- cutando os cases do switch, na ordem seqüencial, até o fim do switch. Exemplo: Faça um programa que leia um número e informe se é igual ou diferente de 9, 10 e 11 . #include <stdio.h> int num; int main (void) { printf ("Digite um numero: "); scanf ("%d",&num); switch (num) { case 9: printf ("\n\nO numero e igual a 9.\n"); break; case 10: printf ("\n\nO numero e igual a 10.\n"); break; case 11: printf ("\n\nO numero e igual a 11.\n"); break; default: printf ("\n\nO numero não e nem 9 nem 10 nem 11.\n"); } } Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 20 4.2 Comandos de Repetição 4.2.1 Comando for Forma geral: for (inicialização;teste;incremento) comando ou bloco; Havendo um bloco de comandos, este deverá ser envolvido por chaves. O comando for é uma instrução de múltiplos usos, não precisando essas três partes separadas pelo operador ; (ponto-e-vírgula). O que deve ser sempre lembrado é a interpretação da condição de teste. Tal deve ser pensada com a seguinte frase: “faça o laço enquanto o teste for verdadeiro”. Exemplo 1: Faça um programa que imprima os 100 primeiros números naturais, exceto o zero #include <stdio.h> int n; int main () { for (n=1; n<=100; n++) printf ("%d ",n); return 0; } Exemplo 2: Faça um programa que imprima os primeiros 20 números pares. #include <stdio.h> int main () { int n=2; for (; n<=40; n+=2) printf ("%d ",n); } No exemplo acima, a parte de inicialização foi omitida para exemplificar que não é necessária, desde que essa parte seja realizada em alguma outra parte do código. Expandindo esse conceito, pode-se realizar um loop infinito com a instrução for da seguinte forma: #include <stdio.h> int main () { int n=2; for (;;) { printf ("%d ",n); n=n*2; } return 0; } Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 21 O exemplo exposto implementa um laço (loop) infinito. Observe que não há a parte de condição de saída da instrução for. Essa condição de saída, nesse caso, deve ser colocada dentro do bloco de instrução do loop, por meio de uma instrução con- dicional if, por exemplo. 4.2.2 Comando while Forma Geral : while (condição) comando ou bloco; Exemplo: Faça um programa que imprima os 100 primeiro números naturais, exceto o zero #include <stdio.h> int n; int main() { n= 1; while (n<=100) { printf(“%d”,n); n++; } return 0; } 4.2.3 Comando do - while Forma geral: do { instrução; } while (condição); Mesmo que a declaração seja apenas um comando é uma boa prática deixar as chaves. O ponto-e- vírgula final é obrigatório. A estrutura do-while executa a instrução, testa a condição e, se esta for verdadeira, volta para a instrução. Este comando, ao contrário do for e do while, garante que a instrução será executada pelo menos uma vez. 4.3 Listas de Exercícios dos Comandos Condicionais e de Repetição 1- Faça um programa que, dado um par de valores, que representa as coordena- das de um ponto no plano, determine o quadrante ao qual pertence o ponto, ou Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 22 então se está na origem ou sobre um dos eixos cartesianos. Permitir a repetição da leitura e do cálculo até que o usuário deseje encerrar o programa. 2- Uma empresa decidiu dar uma gratificação especial a seus funcionários, base- ada no número de horas extras e no número de horas que o empregado faltou ao trabalho. O valor do prêmio é obtido pela consulta à tabela abaixo, em que H é o número de horas extras subtraído do número de horas faltas. H( horas) Prêmio (R$) [0,10] 50,00 (10,20] 80,00 (20,30] 110,00 (30,40] 180,00 (40,100] 250,00 Considere que o prêmio deverá ser acrescentado ao salário bruto do funcionário e sobre este total deve ocorrer um desconto de 8.5% relativo a impostos. Dados o salário bruto do funcionário, o número de horas que ele faltou e o nú- mero de horas-extras que fez (considerar horas e minutos do tipo real ex: 8.30), imprimir o salário bruto, o prêmio obtido pelo funcionário e seu salário líquido. 3- Faça um programa que lendo idade em anos e sexo de um associado de um clube, conceda desconto na mensalidade a ser paga, observando: sexo feminino, até 30 anos desconto de 20% sexo “ 31 a 40 anos desconto de 30% sexo “ acima de 41 anos desconto de35% sexo masculino até 25 anos sem desconto sexo masculino acima de 25 anos desconto de 25%. Forneça idade e mensalidade a pagar. O programa deverá ser encerrando quando o usuário digitar ‘F’ respondendo a pergunta: Repetir o cálculo (s/n)? 4- Faça um programa que, tendo como dados de entrada o custo de 5 produtos e seus códigos, escreva os seus preços finais (com acréscimo de imposto) e suas origens, conforme tabela abaixo: Código Origem Imposto 1 Sul 10% 2 Sudeste 12% 3 Leste 11% 5- Faça um programa que escreva de quantas maneiras diferentes pode-se obter cada um dos diferentes totais de pontos resultantes do lançamento de dois da- dos. 6- Faça um programa que leia um valor n, inteiro e positivo, repetindo a leitura caso o valor esteja fora do estabelecido, calcula e escreve o valor de E, sendo 7- Faça um programa para imprimir na tela os 25 primeiros múltiplos de um número dado ! 1 ... !3 1 !2 1 !1 1 1 n E Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 23 8- Faça um programa que simula o lançamento de um dado por 10 vezes e fornece o número de vezes que saiu cada face do dado. Utilize para simular o lança- mento as funções rand( ) e srand(). 9- Faça um programa para calcular a soma: S = 1 -1/2 + ¼ - 1/6 + 1/8 + ... + 1/200 10- Faça um programa que lê uma seqüência indeterminada de dois valores inteiros positivos e forneça o máximo divisor comum pelo processo das divisões suces- sivas dos dois valores. A leitura encerra quando um dos valores (ou os dois) for igual a zero. Cálculo do M.D.C. pelo processo das divisões sucessivas: Nesse processo efetuamos várias divisões até chegar a uma divisão exata. O divisor desta divisão é o m.d.c. Acompanhe o cálculo do m.d.c.(48,30). Regra prática: 1º) dividimos o número maior pelo número menor; 48 / 30 = 1 (com resto 18) 2º) dividimos o divisor 30, que é divisor da divisão anterior, por 18, que é o resto da divisão anterior, e assim sucessivamente;30 / 18 = 1 (com resto 12) 18 / 12 = 1 (com resto 6) 12 / 6 = 2 (com resto zero - divisão exata) 3º) O divisor da divisão exata é 6. Então m.d.c.(48,30) = 6. 4.4 Exercícios Resolvidos // Exercicio 5 #include <stdio.h> int t,d1,d2,m; int main() { for(t=2;t<13;t++) { m=0; for(d1=1;d1<7;d1++) for(d2=1;d2<7;d2++) if (d1+d2==t) m++; printf("total %2d = %2d maneiras\n",t,m); } return 0; } // Exercício 8 #include <stdio.h> #include <stdlib.h> int r, x, s1,s2,s2,s3,s4,s5,s6; int main() Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 24 { s1=s2=s3=s4=s5=s6=0; srand( time(NULL) ); /* a função time() retorna a hora corrente, ou -1 se ocorrer um erro. Se for passado 'time'como argumento a hora corrente é armazenada em 'time'.*/ /* srand( ) estabelece um ponto de partida para a sequencia gerada por rand() e utiliza a hora do sistema como semente. */ for(x=1;x<11;x++) { r=1+rand()%6; /* A função rand() retorna um valor inteiro aleatório entre 0 e 32767, usamos o resto da divisão por seis mais 1 para garantir no. entre 1 e 6 */ printf("gerou %d\n",r); switch (r) { case 1: s1++; break; case 2: s2++; break; case 3: s3++; break; case 4: s4++; break; case 5: s5++; break; case 6: s6++; break; } } printf("1 saiu %d vezes\n",s1); printf("2 saiu %d vezes\n",s2); printf("3 saiu %d vezes\n",s3); printf("4 saiu %d vezes\n",s4); printf("5 saiu %d vezes\n",s5); printf("6 saiu %d vezes\n",s6); return 0; } 5.0 Tipo Ponteiro Um endereço de memória é como se fosse um número inteiro que serve para es- pecificar um byte específico de memória. Podemos imaginar a memória do compu- tador como sendo um enorme vetor onde cada elemento é um byte, acessível atra- vés de um número inteiro que é seu “endereço”. Uma diferença básica entre um inteiro qualquer e um inteiro que representa um endereço está nas operações que podem ser efetuadas com eles. Enquanto intei- ros comuns podem ser adicionados, multiplicados, etc., os endereços servem ape- nas para referenciar posições específicas da memória. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 25 Normalmente, usamos variáveis para armazenar dados (caracteres, reais, inteiros, etc.). Se um endereço é como se fosse um número inteiro, nada impede que pos- samos criar também variáveis para armazenar endereços de memória. Estas vari- áveis especiais são denominadas ponteiros. “Uma variável do tipo ponteiro serve para armazenar um endereço de memória”. 5.1 Declaração de variáveis do tipo ponteiro Variáveis Estáticas - O seu valor é referido através do nome da variável, ou seja, “o nome da variável é considerado uma expressão designatória do seu valor”. Sua vida útil é: Variáveis globais: durante toda execução do programa. Variáveis Locais: durante a execução do subprograma. Variáveis Dinâmicas - Uma variável dinâmica pode ser criada ou destruída dina- micamente em qualquer ponto durante a execução de um programa. O valor de uma variável dinâmica não é referido através de seu nome, mas sim através de uma ligação ou ponteiro para outra variável em que este valor está armazenado. Ao passo que com uma variável estática estamos interessados no valor da variável, com uma variável do tipo ponteiro estamos interessados no valor para o qual ela aponta. Variável Estática : num 5 Variável Ponteiro: p 27 Antes de criarmos uma variável ponteiro, é preciso definir que tipo de ponteiro va- mos usar. Note que, para acessar uma informação na memória, ter apenas o seu Variável Anônima - aquela cujo valor não é obtido diretamente através do seu nome, mas sim através de uma variável do tipo ponteiro. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 26 endereço não é suficiente pois é necessário conhecer também o seu tamanho (quantos bytes devemos acessar a partir do endereço). Sintaxe da declaração de um ponteiro: É preciso definir para que tipo de dado o ponteiro vai apontar: tipo * identificador_da variável; Exemplos: uma variável denominada p e que aponta para inteiro: int *p dois ponteiros para char: char *temp,*pt2 Para armazenar o endereço de uma variável ponteiro o compilador reservará 2 bytes (dependendo da máquina). O valor de uma variável ponteiro não é referido através de seu nome, mas sim através de uma ligação ou ponteiro para outra variável em que este valor está ar- mazenado. 5.2 Operadores de ponteiros 5. 2.1 Existem dois operadores especiais para ponteiros: & operador unário que devolve o endereço do seu operando * operador unário que devolve o valor da variável localizada no endereço apontado (é o complemento de &) Tanto & como * têm precedência maior que qualquer operador aritmético, ex- ceto o menos unário. Exemplos de declaração, atribuição e operadores em ponteiros: int *p, c, q, *px, *py; c=100; p=&c; q=*p; *px=5; *py=*py /* as variáveis p, px e py são do tipo pon- teiro para inteiros, as variáveis c e q são do tipo inteiro*/ //c recebe 100 /* p armazena o endereço que a variável c ocupa na memória, logo p aponta para c*/ /*q armazena o valor que esta armazenado no // ende- reço para o qual p aponta (100) endereço para onde p aponta*/ /* armazena o valor 5 na variável apontada pelo ponteiro px */ */armazena na variável apontada por py o mesmo valor que está na variável apontada por px (5) */ Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 27 Observação: Na declaração, o símbolo * indica o “tipo apontado” em outras instruções indica “a variável apontada por”. Um exemplo: #include <stdio.h> int main () { int a, *pa; double b, *pb; char c, *pc; // atribuições de endereços pa = &a; pb = &b; pc = &c; // atribuição de valores a = 1; b = 2.34; c = '@'; printf("\n valores:%5d %5.2lf %c", a, b, c); printf("\n ponteiros:%5d %5.2lf %c", *pa, *pb, *pc); printf("\n enderecos:%p %p %p", pa, pb, pc); /* mais atribuições de valores usando os pontei- ros*/ *pa = 77; *pb = 0.33; *pc = '#'; printf("\n valores :%5d %5.2lf %c", a, b, c); printf("\n ponteiros:%5d %5.2lf %c", *pa, *pb, *pc); printf("\n enderecos:%p %p %p\n", pa, pb, pc); return 0; } Ponteiros também são variáveis e, portanto, ocupam posições na memória: #include <stdio.h> int main() { int a, *pa; double b, *pb; char c, *pc; // atribuições de endereços pa = &a; pb = &b; pc = &c; // mostra o endereço das variáveis printf("\nendereço de a, b e c: %p %p %p", pa, pb, pc); // mostra o endereço das variáveis de outra forma printf("\nendereço de a, b e c (outra forma): %p %p %p", &a, &b, &c); // mostra o endereço dos ponteiros printf("\nendereço dos ponteiros: %p %p %p\n", &pa, &pb, &pc); return 0; } Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 28 5.2.2 Operações com Ponteiros 5.2.2.1 Atribuição entre ponteiros Se temos dois ponteiros, p1 e p2 podemos fazer p1=p2. Repareque estamos fazendo com que p1 aponte para o mesmo lugar que p2 aponta. Se quisermos que a variável apontada por p1 tenha o mesmo conteúdo da variável apontada por p2 devemos fazer *p1=*p2. 5.2.2.2 Operações aritméticas com Ponteiros: apenas duas Incremento de um ponteiro Quando incrementamos um ponteiro ele passa a apontar para o próximo valor do mesmo tipo para o qual o ponteiro aponta. Isto é, se temos um ponteiro para um inteiro e o incrementamos, ele passa a apontar para o próximo inteiro. Esta é mais uma razão pela qual o compilador precisa saber o tipo de um pon- teiro: se você incrementa um ponteiro char* ele anda 1 byte na memória e se você incrementa um ponteiro double* ele anda 8 bytes na memória. Considere ptr um ponteiro para inteiro que contém o valor atual 2000 e 2 bytes o tamanho necessário para um inteiro. Após a expressão: ptr++ ptr conterá 2002 (e não 2001) Cada vez que ptr for incrementado, ele apontará para a posição do próximo inteiro. Em outras palavras, os ponteiros são incrementados relativamente ao tamanho do tipo base. Decremento de um ponteiro O decremento funciona de forma semelhante ao incremento Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 29 Supondo que p, p1 e p2 sejam ponteiros para inteiros: p++; // p aponta para o próximo inteiro p--; // p aponta para o inteiro anterior p1=p2+3; // p1 aponta para o terceiro inteiro após o inteiro apontado por p2 5.2.2.3 Operadores relacionais Considerando ponteiros de mesmo tipo, é possível utiliza-los em expressões que contenha operadores relacionais: podemos saber se dois ponteiros são iguais ou diferentes (== e !=). no caso de operações do tipo >, <, >= e <= estamos comparando qual pon- teiro aponta para uma posição mais alta na memória. Então uma compara- ção entre ponteiros pode nos dizer qual dos dois está "mais adiante" na me- mória. A comparação entre dois ponteiros se escreve como a comparação entre ou- tras duas variáveis quaisquer: If (p1<p2) printf(“p1 aponta para uma memória mais baixa que p2\n”); 5.2.2.4 Operações não permitidas com ponteiros Não é possível: dividir ou multiplicar ponteiros, adicionar dois ponteiros, adicionar ou subtrair floats ou doubles de ponteiros. 5.3 Ponteiros para Ponteiros Um ponteiro para ponteiro pode ser imaginado com a situação de anotarmos em um papel o local onde está guardado o endereço da casa de um amigo. Declaração de um ponteiro para um ponteiro: tipo_da_variável **nome_da_variável; onde: **nome_da_variável é o conteúdo final da variável apontada; *nome_da_variável é o conteúdo do ponteiro intermediário. Um exemplo: #include <stdio.h> int main() { float fpi = 3.1416, *pf, **ppf; pf = &fpi; /* pf armazena o endereco de fpi */ ppf = &pf; /* ppf armazena o endereco de pf */ Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 30 /* imprime o valor armazenado no endereço apontado por pf */ printf("valor armazenado no endereço apontado por pf: %f\n", *pf); /* Imprime o valor armazenado no endereço apontado pelo ponteiro (pf) para o qual ppf aponta */ printf("Valor armazenado no endereço apontado por ppf: %f\n", **ppf); return 0; } 5.4 Inicialização de ponteiros – a constante “NULL” A biblioteca stdio.h define uma macro especialmente para que façamos a iniciali- zação de um ponteiro, informando que ele não tem um endereço associado É muito importante que saibamos para onde o ponteiro está apontando, ou seja: nunca use um ponteiro que não foi inicializado. O exemplo abaixo ilustra esta situação: int main () /* Nao Execute!!! */ { int n,*p; n=22; *p=n; Return 0; } Este programa compilará e rodará. Mas o ponteiro p pode estar apontando para qualquer lugar. Estamos gravando o número 22 em um lugar desconhecido. Com um número apenas, não vamos ver nenhum defeito. Porém se tivermos por ábito gravar números em posições aleatórias no computador, o micro poderá tra- var. !! Inicialize sempre os seus ponteiros com o valor NULL !! 5.5 Alocação dinâmica de memória Considerando a declaração abaixo: int *p identificamos que p é uma variável que aponta para valores inteiros (ou seja, arma- zena seu endereço). No início da execução de um bloco em que esta variável é declarada, a variável p é criada (tal como acontece com as varáveis estáticas). Depois de sua criação p existe, embora não exista ainda a variável do tipo int para onde ela aponta, situação que pode ser representa como no esquema abaixo: p ? Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 31 Para que p possua um endereço (aponte para um endereço), é preciso que faça- mos uma alocação dinâmica de memória (um pedido de memória do tamanho cor- reto para armzenar um valor inteiro). A alocação dinâmica permite ao programador alocar memória para variáveis quando o programa está sendo executado. Assim, poderemos definir, por exemplo, um vetor ou uma matriz cujo tamanho descobriremos em tempo de execução. A memória alocada pelas funções de alocação dinâmica é obtida do heap. O heap é a região de memória livre que se encontra entre o programa (com a área de armazenamento permanente) e a pilha (stack). O tamanho do heap é, a princípio, des- conhecido do programa. Existe uma função especial em C para alocarmos memória no heap, a função malloc(). 5.5.1 Função malloc A função malloc() serve para alocar memória de forma dinâmica. Sintaxe: void malloc (int size); Biblioteca: stdlib.h A função recebe o número de bytes que queremos alocar (size), aloca na memó- ria e retorna um ponteiro void * para o primeiro byte alocado. O ponteiro void * pode ser atribuído a qualquer tipo de ponteiro por isso, deve ser utilizado sempre um typecasting (conversão para o tipo apropriado). Se a memória for alocada no topo do heap, o heapPointer é atualizado (incremen- tado de size). Ex: sendo p um ponteiro para inteiro fazemos o typecasting para inteiro p= (int *) malloc( sizeof(int) ); como não sabemos necessariamente o comprimento de um inteiro (2 ou 4 bytes dependendo do compilador), usamos como parâmetro sizeof(int). Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 32 Se não houver memória suficiente para alocar a memória requisitada a função malloc() retorna NULL. Após realizar a alocação de um endereço, nosso ponteiro p já possuirá uma variá- vel anônima a ele associada: p ? Porém, a variável anônima associada a p não está armazenando ainda nenhum valor. Para isso fazemos uma atribuição (ou uma leitura): *p = 97; o que pode ser graficamente representado como: p 97 Exemplo e esquema representativo da memória durante a alocação dinâmica de memória: #include <stdlib.h> int *pi; (1) int main () { pi = (int *) malloc (sizeof(int)); (2) *pi=97; (3) return 0; } (1) (2) (3) Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 33 5.5.2 Função free() A função free(p) libera a região de memória apontada por p para uso. O tamanho liberado está implícito, isto é, é igual ao que foi alocado anterior- mente por malloc. Caso p seja um ponteironulo nenhuma ação é executada. Declaração: void free(void *p); Biblioteca: stdlib.h O trecho de código abaixo aloca dinâmicamente um inteiro e depois o libera: #include <stdlib.h> int main(int) { int *pi; pi = (int *) malloc (sizeof(int)); ... … free(pi); } 6.0 Modularização (subprogramas) 6.1 Introdução ao conceito de modularização Exemplos de modularização, i.e., sistemas que são compostos por módulos com funções bem definidas e tão independentes quanto possível, são bem conheci- dos. Um destes exemplos que pode ser citado, são os sistemas (domésticos ou não) de som, onde há um módulo responsável pela amplificação, outro para sinto- nizar rádio, outro para amplificar o som, ler CDs e assim por diante. A divisão de um sistema em módulos tem várias vantagens. Para o fabricante, a modularização tem a vantagem de reduzir a complexidade do problema, dividindo- o em subproblemas mais simples, que podem inclusive ser resolvidos por equipes independentes. Sob o ponto de vista da fabricação, é mais simples alterar a com- posição de um módulo, por exemplo, porque se desenvolveram melhores circuitos para o amplificador, do que alterar a composição de um sistema integrado. Por outro lado, é mais fácil detectar problemas e resolvê-los, pois os módulos são, em princípio, razoavelmente independentes. Claro que os módulos muitas vezes não são totalmente independentes. Por exemplo, o sistema de controle à distância de uma aparelhagem implica interação com todos os módulos simultaneamente. A arte da modularização está em identificar claramente que módulos devem existir no sistema, de modo a garantir que as ligações entre os módulos sejam minimiza- das e que a sua coesão interna seja máxima. Isto significa que, no caso de um bom sistema de alta fidelidade, os cabos entre os módulos são simplificados ao máximo e que os módulos contenham apenas os circuitos que garantem que o mó- dulo faça sua função. A coesão tem, portanto, a ver com as ligações internas a Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 34 um módulo, que idealmente devem ser maximizadas. Normalmente, um módulo é coeso se tiver uma única função, bem definida. Para quem utiliza, por outro lado, a modularização tem como vantagem principal permitir a alteração de um único módulo sem ter de comprar um sistema novo. Claro que para isso acontecer o novo módulo tem de (1) ter a mesma função do módulo substituido e (2) possuir uma interface idêntica (os mesmo tipo de cabos com o mesmo tipo de sinal eléctrico). Isto é, os módulos, do ponto de vista do utilizador, funcionam como "caixas pretas" com uma função bem definida e com interfaces bem conhecidas. Para o utilizador o interior de um módulo é irrelevante. Mas a modularização tem outras vantagens: o amplificador pode no futuro ser reu- tilizado num sistema de vídeo, por exemplo, evitando a duplicação de circuitos com a mesma função. As vantagens da modularização são muitas e foi introduzida na programação a partir das linguagens como o C e o Pascal. É um dos métodos usados em progra- mação para desenvolvimento de programas de grande escala mas é útil mesmo para pequenos programas, quanto mais não seja pelo treino que proporciona. Vantagens da modularização para a programação: 1. Facilita a detecção de erros, pois é em princípio simples verificar qual é o módulo responsável pelo erro. 2. É mais fácil testar os módulos individualmente do que o programa com- pleto. 3. É mais fácil fazer a manutenção (correcção de erros, melhoramentos, etc.) módulo por módulo do que no programa total. Além disso, a modulariza- ção aumenta a probabilidade dessa manutenção não ter consequências nefastas nos outros módulos do programa. 4. Permite o desenvolvimento independente dos módulos. Isto simplifica o trabalho em equipe, pois cada elemento, ou cada sub-equipe, tem a seu cargo apenas alguns módulos do programa. 5. A mais evidente vantagem da modularização em programas de pequena escala, mas também nos de grande escala, é a possibilidade de reutiliza- ção do código desenvolvido. Um programador assume, ao longo do desenvolvimento de um programa, os dois papeis descritos acima: por um lado é fabricante, pois é sua responsabilidade de- senvolver módulos; por outro é utilizador, pois fará com certeza uso de outros mó- dulos, desenvolvidos por outrem ou por ele próprio no passado. Esta é uma noção muito importante. É conveniente que um programador possa ser um mero utilizador dos módulos já desenvolvidos, sem se preocupar com o seu funcionamento interno: ele sabe qual a interface do módulo e qual a sua função, e usa-o. Isto permite reduzir substancialmente a complexidade da informação que o programador tem de ter presente na sua memória, conduzindo por isso a substanciais ganhos de produtividade e a uma menor taxa de erros. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 35 Em nosso estudo, até agora, já entramos em contato com o uso de módulos pron- tos. São as funções que utilizamos para limpar a tela, escrever na tela, fazer en- trada de dados e outras. Existem basicamente duas formas de criar subprogramas: utilizar procedimentos ou funções. A linguagem C só permite a utilização de funções , por isso enfocare- mos basicamente funções em nossas aulas. Um procedimento é um conjunto de instruções, com interface bem definida, que pode receber e enviar dados para quem o acionou, normalmente o programa prin- cipal. Uma função por sua vez, também segue a definição acima, com a diferença de que devem ter obrigatóriamente um tipo definido para o valor que a função retorna. Em outras palavras, funções só não retornam um valor se as definirmos como void. Uma vez definida ("fabricada"), uma função, ou um procedimento, podem ser utili- zados sem que se precise conhecer o seu funcionamento interno, da mesma forma que o usuário da aparelhagem de som não está muito interessado nos circuitos dentro do amplificador, mas simplesmente nas suas características. Claro, para que funcuione adequadamente, é preciso saber conectar o equipamento ao sistema. O mesmo acontece com as funções e os procedimentos. Tomemos como exemplo uma função que é utilizada para gerar automáticamente números aleatórios, a função rand. Para poder utilizá-la devemos conhecer primeiro seu protótipo (cabeçalho da função). O protótipo irá nos mostrar qual o tipo e os parâmetros de rand. Onde procurar esta informação? Tenha sempre a mão um bom livro de C ou utilize o help do turbo C. O protótipo é: int rand(void) int informa que a função retorna um valor inteiro rand é seu nome e (void) indica que ela não requer parâmetros. Outro exemplo é a função matemática sqrt. Esta função, como já estudamos, in- forma como resultado (retorna) a raiz quadrada de um número. Para fazer este cálculo devemos colocar entre parênteses (como parâmetro) o valor ou a variável que servirá para fazer o cálculo da raiz quadrada e devemos declarar uma variável apropiada para armazenar o resultado. Veja na tabela 4. Exemplo: #include math.h float r,n; int main () { r=sqrt(16); // r armazena o retorno da função cujo parâmetro é 16*/ ------; ------; return 0; } Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 36 Para usarmos funções prontas, portanto, não necessitamos conhecer o seu código, porém é importante conhecer seu protótipo (tipo que retorna, nome da função e parâmetros que requer). Mas nem sempre utilizaremos apenas funções prontas, muitas vezes “fabricare- mos” nós mesmos nossas funções. 6.2 Definiçãode funções em C Outra observação: Se não declaramos o tipo de uma função ela será considerada int. 6.3 Escopo de uma variável (região de validade) tipos de parâmetros e retorno de funções Variáveis Globais (Estáticas): são declaradas fora das funções e são reco- nhecidas em todo o programa (em main e inclusive nos demais subprogra- mas). Variáveis Locais: são declaradas no bloco da função e são válidas apenas na função. São alocadas através da requisição de espaço na pilha (stack) Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 37 e permanecem na memória somente durante a execução da função, deixando de existir quando a função é encerrada. Exemplo2: Faça um programa que leia três valores reais que representam a base maior, base menor e altura de um trapézio, calcule e informe a área do trapézio. #include <stdio.h> int main () { float bmai,bmen,h,area; printf("Digite a base maior\n"); printf("Digite a base me- nor\n"); printf("Digite altura do tra- pezio\n"); scanf("%f%f%f",&bmai,&bmen,&h); area=(bmai+bmen)/2*h; printf("area do trapezio: 5.2f\n",area); return 0; } #include <stdio.h> void calculo (float ma, float me, float altura); int main () { float bmai,bmen,h; printf("Digite a base maior\n"); printf("Digite a base menor\n"); printf("Digite altura do trape zio\n"); scanf("%f%f%f",&bmai,&bmen,&h); calculo(bmai,bmen,h); } void calculo (float ma, float me, float altura) { float area; area=(ma+me)/2*altura; printf("Area: %5.2f",area); } Ex2: sem função Ex2: com função e parâmetros, sem retorno É importante observar que, antes da função main, foi feita a declaração do protótipo da função, que consiste em colocar seu cabeçalho seguido de ponto e vírgula. Isto é feito para informar ao compilador a existência do código da função após main. Para evitar a declaração do protótipo, a função poderia ser implementada antes de Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 38 main, colocando exatamente as mesmas instruções que foram colocadas após main, sem o ponto e vírgula ao final da linha do cabeçalho. Portanto, sempre que implementarmos o código de funções após main, deveremos fazer a declaração de seu protótipo antes de main. Apenas as funções que tiverem como retorno o tipo int não precisarão seguir esta regra. A função calculo foi implementada com 4 variáveis locais: bma, bme, alt: são declaradas entre parênteses, pois são parâmetros da função. area: declarada no corpo da função porque não é parâmetro A função calculo não precisa retornar nada para quem a chamou, logo seu tipo é void. Ao ser acionada uma função com parâmetros, devemos informar os valores, entre parênteses e estes ficarão armazrnados nas variáveis locais declaradas dentro dos parênteses, na mesma ordem em que são colocados. No exemplo teremos a variável bma passando seu valor para o parâmetro ma, te- remos a variável bme passando seu valor para o parâmetro me e a variável h pas- sando seu valor para o parâmetro altura. Assim, existirá uma comunicação de informações entre main e a função calculo que é a passagem destes valores, lidos em main e sem os quais a função não poderia efetuar o calculo. Esta comunicação se dá somente no sentido de main para função (e não vice- versa) e é conhecida como passagem de parâmetros por valor. Neste exemplo a função nada retorna, visto que ela mesma informa o resultado do cálculo, mas poderíamos entender que seria melhor se a função retornasse a área calculada para quem a acionou. O exemplo abaixo apresenta uma forma de resolução onde a área calculada é o retorno da função . #include <stdio.h> float calculo (float ma, float me, float altura); int main () { float bmai,bmen,h,a; printf("Digite a base maior\n"); printf("Digite a base menor\n"); printf("Digite altura do trapezio\n"); scanf("%f%f%f",&bmai,&bmen,&h); a=calculo(bmai,bmen,h); printf("Area: %5.2f",area); return 0; } float calculo (float ma, float me, float altura) { float area; area=(ma+me)/2*altura; return(area) } Ex3: resolução com retorno da função Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 39 Observe autilização do comando return antes de encerrar a função. Através deste comando estamos informando qual o retorno desta função, no caso o valor arma- zenado na variável local área, que por isso deve ser do mesmo tipo da função. Outra forma de passarmos valores para “fora” de uma função é através dos parâ- metros. Podemos declarar parâmetros de forma que eles não só recebam um valor de quem aciona a função mas ao mesmo tempo retornem um valor para a variável que se corresponde com o parâmetro. Esta forma de passagem de parâmetros é chamada de passagem por referência e consiste em informarmos o endereço da variável no acionamento da função. Por sua vez, o parâmetro correspondente deverá ser declarado como tipo ponteiro, para poder armazenar este endereço. Exemplo: Faça um programa que leia 3 valores inteiros (a,b e c) e escreva-os or- denados, de forma que em a esteja o menor valor e em c o maior. #include <stdio.h> int main () { int a,b,c,aux; printf("Informe os tres inteiros\n"); scanf("%d %d %d",&a,&b,&c); if (a>b) {aux=a; a=b; b=aux; } if (a>c) {aux=a; a=c; c=aux; } if (b>c) {aux=b; b=c; c=aux; } printf("a=%d b=%d c=%d",a,b,c); return 0; } Percebe-se que a cada vez que o teste entre as variáveis for verdadeiro será reali- zado um mesmo bloco de instruções, que troca as variáveis envolvidas no teste. Este trecho de código pode ser implementado em uma função que receba os valo- res e devolva-os trocados, como é mostrado abaixo, realizando uma passagem de parâmetros por referência. Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 40 #include <stdio.h> void troca(int *x, int *y); int main () { int a,b,c; printf("Informe os tres inteiros\n"); scanf("%d %d %d",&a,&b,&c); if (a>b) troca(&a,&b); if (a>c) troca(&a,&c); if (b>c) troca(&b,&c); printf("a=%d b=%d c=%d\n",a,b,c); return 0; } void troca (int *x, int *y) { int aux; aux=*x; *x=*y; *y=aux; } Variáveis Locais Estáticas É possível, quando da declaração de variáveis prefixá-las com o qualificador static. As variáveis locais estáticas são inicializadas uma só vez e não desapare- cem quando a função a que pertencem termina, podendo no entanto ser acessadas apenas dentro da função. As variáveis locais estáticas também mantêm o seu valor de umas chama- das para as outras, como se vê nos exemplos seguinte: Exemplo: #include <stdio.h> void func (int x); int main () // função principal { func (10); func (20); func (30); return 0; } void func (int x) { /* declaração de 1variável local, porém estática, e por isso, permanece com o valor da chamada anterior*/ static int s = 0; printf("\n valor de s no inicio da funcao = %5d", s); s = s + x; printf("\n valor de s no final da funcao = %5d", s); } Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 41 6.4Inclusão de arquivos de cabeçalho com funções criadas pelo usuário Podemos criar um arquivo cabeçalho (*.h) com as funções que acharmos apropri- adas, geralmente aquelas que são muito utilizadas. Abaixo apresenta-se o mesmo exemplo utilizado para demonstrar passagem de parâmetros por referência, visto anteriormente. Agora, porém, a função que faz a troca entre os valores está imple- mentada em um arquivo externo, chamado uteis. h. Veja abaixo: #include <stdio.h> #include"d:\tc\bin\A09_01\ Ex_incl\uteis.h" int main () { int a,b,c,aux; printf("Informe os tres inteiros\n"); scanf("%d %d %d",&a,&b,&c); if (a>b) troca(&a,&b); if (a>c) troca(&a,&c); if (b>c) troca(&b,&c); printf("a=%d b=%d c=%d",a,b,c); return 0; } //arquivo .h que possui uma funcao para trocar dois inteiros //----------------------------------- //cabeçalho da função void troca(int *x, int *y); //----------------------------------- // desenvolvimento da função void troca(int *x, int *y) { int aux; aux=*x; *x=*y; *y=aux; } OBS: quando delimitamos o arquivo cabeçalh < > estamos direcionando sua busca no diretorio padrao de inclusao (a pasta include do diretorio tc), quando uti- lizamos " " significa o diretorio corrente, ou podemos especificar um caminho. Para criar um arquivo header acione o menu File - New e depois File outra vez. Escolha header: Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 42 6.5 Exercícios propostos 1. Escreva uma função que dadas as notas de prova p1 e p2 e as notas dos exer- cícios-programa ep1, ep2 e ep3 de um aluno, devolve o valor da média final deste aluno. A média final do aluno é dada por (3p+ep)/4, onde p = (p1+2p2)/3 e ep = (ep1+2ep2+3ep3)/6. 2. Idem, acrescentando se p<5 ou ep<6 a média final é o mínimo entre 4.5, p e ep. 3. Fazer um programa que possibilite várias opções de cálculos a partir de um menu. O programa chamará a função correspondente a cada cálculo. a- S = 1/1 + 3/2 + 5/3+........+ 99/50 b- S = 1/1 - 2/2 + 3/3 -..........- 10/10 c- S = 1000/1 - 997/2 + 994/3......... d- S = 480/10 - 475/11 + 470/12 - ....... e- Sair Obs.: Nas opções c e d fazer os cálculos para os 20 primeiros termos. O menu deverá ficar disponível até ser escolhida a opção e. 4. Faça uma função que receba como argumento os valores dos lados de um tri- ângulo, a função deverá retornar 0 se triângulo for equilátero, 1 se for isósce- les ou 2 se for escaleno. 5. Fazer um programa em C que solicita o total gasto pelo cliente de uma loja, imprime as opções de pagamento, solicita a opção desejada e imprime o valor total das prestações (se houverem). a - Opção a vista com 10% de desconto b- Opção em duas vezes (preço da etiqueta) c- Opção de 3 até 10 vezes com 3% de juros ao mês (somente para compras acima de R$ 100,00). OBS: fazer uma função que imprime as opções, solicita a opção desejada e retorna a opção escolhida. No programa principal, testar a opção escolhida e ativar a função correspon- dente (uma função para cada opção). 6. Faça um programa para ler valores inteiros até ser digitado o valor -1 (tarefa 1) e fornecer o fatorial de cada valor lido (tarefa2). 7. Faça um programa que verifica quais dentre os 1000 primeiros números natu- rais são número perfeito. Número perfeito é todo número que, somando seus divisores, esta soma resulta nele mesmo. Utilize uma fção para a verificação de cada um dos números. A função main deve informar o resultado. Ex: 6 é divisível por 1 6 é divisível por 2 6 é divisível por 3 1+2+3 = 6 é quadrado perfeito. 8. Faça um programa que lê os seguintes dados relativos a 30 alunos de uma escola de futebol: altura; data de nascimento (tarefa de main). O programa de- verá fornecer como saída: a idade média dos alunos (tarefa2); a altura média dos alunos (tarefa 3). Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 43 9. A prefeitura de uma cidade fez uma pesquisa entre seus habitantes, coletando dados sobre o salário e o número de filhos. A prefeitura deseja saber: média do salário da população (utilize 1 função); média do número de filhos ((utilize 1 função); maior salário (utilize 1 função); percentual de pessoas com salário até R$920,00 (utilize 1 função). Encerrar a leitura dos dados com salário negativo. 10. Faça um programa onde main imprima na tela os ‘n’ primeiros números primos, onde ‘n’ será fornecido pelo usuário. Utilize uma função para verificar se o nú- mero é primo. 11. Dado n e p inteiros, n,p >= 0, calcular as combinações de n elementos p a p, isto é: n! / (p! * (n-p)! ). Utilize funções. 12. A sequência de Fibonacci é a seguinte: 1, 1, 2, 3, 5, 8, 13, 21, ... os dois primeiros termos são iguais a 1. Cada termo seguinte é igual `a soma dos dois anteriores. Escreva um programa onde main solicita ao usuário o nú- mero do termo e calcule através de uma função o valor do termo. Main deverá escrever o valor do termo. Por exemplo: se o número fornecido pelo usuário for igual a 7, main deverá imprimir 13. 13. Escreva um programa em que main solicite ao usuário três números inteiros a, b, e c onde a é maior que 1. Uma outra função deve somar todos os inteiros entre b e c que sejam divisíveis por a. Main deve informar a soma. 6.6 Exercícios Resolvidos Exercício 1 #include <stdio.h> #include <stdlib.h> float media(float p1,float p2,float p3,float ep1,float ep2,float ep3); int main() { float p1,p2,p3,ep1,ep2,ep3; printf("Informe as notas das tres provas:\n"); scanf("%f%f%f",&p1,&p2,&p3); printf("Informe as notas dos tres exercícios:\n"); scanf("%f%f%f",&ep1,&ep2,&ep3); printf("A media eh: %.2f",media(p1,p2,p3,ep1,ep2,ep3)); return 0; } float media(float p1,float p2,float p3,float ep1,float ep2,float ep3) { float p,ep,m; p=(p1+2*p2)/3; ep=(ep1+2*ep2+3*ep3)/6; m=(3*p+ep)/4; return(m); } Exercício 4 #include <stdio.h> #include <stdlib.h> int triangulo (float a, float b, float c); int main() Notas de aula de Programação Estruturada Professoes Beatriz Lux e Rolf F. Molz 44 { float a,b,c; int tipo; printf("Informe os lados do triangulo\n"); scanf("%f%f%f",&a,&b,&c); tipo=triangulo(a,b,c); switch(tipo) { case 0: printf("\n Equilatero"); break; case 1: printf("\n Isosceles"); break; case 2: printf("\n Escaleno"); break; case 3: printf("\n Não formam um triangulo"); break; } return 0; } int triangulo (float a, float b, float c) { int r; if(a<=b+c && b<=a+c && c<= a+b) // teste para saber se podem formar triangulo if(a==b && b==c) r=0; else if(a!=b && b!=c && a!=c) r=2; else r=1; else r=3; return (r); } Exercício 8 #include <stdio.h> #include <time.h> int idade(int day, int month, int year,int d,int m,int a); float acum_altura(float a); int acum_idade(int i); int main() { const max= 3; char nome[15]; int x,dia,mes,ano,d,m,a,i; float alt,sh,si; struct tm *local; time_t t; t= time(NULL); local = localtime(&t);
Compartilhar