• Nenhum resultado encontrado

ApostilaC

N/A
N/A
Protected

Academic year: 2021

Share "ApostilaC"

Copied!
23
0
0

Texto

(1)

Revelado

101010010010101 10010010101001010100 10101010101010010010 10101101110000101110 10100110101010100010 01100011111100011010 10010110010100100100 11000010010101001010 10011000101010010101 01110010101001001100 1 01101001010101100 11001001000

(2)
(3)

Copyright © 2005 de Frederico Costa Guedes Pereira Este material foi elaborado para ser utilizado pelos alunos da disciplina Programação e Estruturas de Dados, do Curso Superior de Desenvolvimento de Softwares para a Internet do Centro Federal de Educação Tecnológica da Paraíba.

Nenhuma parte deste material pode ser reproduzida ou transmitida de qualquer modo ou por quaisquer meios, sem prévia autorização do autor e sem lhe ser dado o

Instituto Federal de

(4)
(5)

Índice

CAPÍTULO 1: FUNÇÕES ... 6 1.1 INTRODUÇÃO ... 6 1.2 DECLARANDO FUNÇÕES EM C ... 6 1.3 PASSAGEM DE VALORES ... 13 1.4 BIBLIOTECAS ... 19 BIBLIOGRAFIA ... 23

(6)

Capítulo 1: Funções

“A arte de programar consiste em organizar e dominar a complexidade.” E. W. Djikstra

1.1 Introdução

As funções estão para uma programa complexo assim como os tijolos estão para um edifício. A complexidade na construção da estrutura física de um edifício pode ser simplificada até o ponto de se colocar um tijolo sobre o outro. Na programação, podemos decompor um problema complexo em vários problemas menores e mais simples.

A forma de se fazer isso em C é usando funções. Uma função contém a solução de um subproblema do problema maior. Vamos exemplificar. Suponha que você tenha que implementar um programa em C para calcular a diferença entre duas horas informadas por um usuário. Seu problema então é criar um código C que receba as duas horas, faça a diferença entre elas e depois informe o resultado (em formato de horas, minutos e segundos). Você pode fazer toda a lógica da entrada de dados (leitura das duas horas), processamento (cálculo da diferença entre as horas recebidas) e apresentação dos resultados (exibição da diferença entre as horas no formato HH:MM:SS) numa única função main(), por exemplo. Isto pode funcionar perfeitamente se:

 Você só vai fazer este programa;

 Você não vai precisar ler e imprimir horas em outras partes deste ou de outros códigos;

 Você está com preguiça de aprender funções. Se este não é o seu caso, continue lendo!

Dividir um problema em subproblemas nem sempre é uma tarefa simples ou imediata. Uma das dificuldades para quem se inicia na programação estruturada é exatamente saber como é que se divide

um programa em funções. Isto se chama decomposição: dividir um problema em problemas menores. A

decomposição na programação estruturada é realizada com funções. Não existem regras definitivas para se chegar à melhor decomposição de um programa, mas há técnicas que podem ajudar você a encontrar o seu estilo de decomposição! Uma destas técnicas é a programação utilizando tipos de dados abstratos, que abordaremos noutro capítulo. Por enquanto fica a dica: leia muito código e tente escrever os seus próprios! No começo, seu código pode parecer meio feio, mas com a prática vai melhorando!

1.2 Declarando funções em C

Vamos começar esta seção mostrando o código para o problema que propusemos na introdução.

01 02 03 04 05 #define HORA 3 main(){

unsigned int hora1[HORA], hora2[HORA], dif[HORA]; unsigned int segundos1, segundos2, diferenca;

(7)

06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

printf("Forneca horas da hora1: "); scanf("%d", &hora1[0]);

printf("Forneca minutos da hora1: "); scanf("%d", &hora1[1]);

printf("Forneca segundos da hora1: "); scanf("%d", &hora1[2]);

printf("\nForneca horas da hora2: "); scanf("%d", &hora2[0]);

printf("Forneça minutos da hora2: "); scanf("%d", &hora2[1]);

printf("Forneca segundos da hora2: "); scanf("%d", &hora2[2]);

segundos1 = hora1[0]*3600 + hora1[1]*60 + hora1[2]; segundos2 = hora2[0]*3600 + hora2[1]*60 + hora2[2]; diferenca = segundos2 – segundos1;

dif[0] = diferenca / 3600;

dif[1] = (diferenca % 3600) / 60; dif[2] = (diferenca % 3600) % 60;

printf("A primeira hora foi: %dh%dm%ds\n",hora1[0], hora1[1], hora1[2]); printf("A segunda hora foi: %dh%dm%ds\n", hora2[0], hora2[1], hora2[2]); printf("\n\nA direfereça entre as horas fornecidas é de %dh%dm%ds",

dif[0], dif[1], dif[2]);

return 0; }

Código 1: Programa de diferença entre horas.

O Código 1 resolve o problema da diferença das horas. No entanto, ele está monolítico, não é fácil identificar partes neste código que sejam especializadas em algum aspecto do software (como por exemplo, a interface com o usuário ou a lógica do negócio). Outra implicação da falta de modularidade é a repetição desnecessária de código: para ler duas datas, repetimos os comandos de leitura de horas, minutos e segundos duas vezes, uma para cada hora fornecida. Se tivéssemos pensado no software de forma modular, esta parte seria uma função. Esta função poderia até melhorar a entrada de dados, fornecendo uma máscara para leitura adequada da data e crítica dos valores fornecidos. Note que isto também poderia ser feito no programa acima, mas o tornaria muito maior e mais complexo.

Nossa incursão na modularidade do Código 1 será na parte que imprime uma data. Decidimos escolher esta parte por usar passagem por valor, sendo mais simples de implementar. A leitura da data exigiria a apresentação do conceito de passagem por referência, que abordaremos mais tarde. Antes de apresentarmos o código da função que imprime uma data, vamos falar de como uma função é declarada em C.

Um programa em C consiste de uma coleção de (uma ou mais) funções. Elas podem estar todas no mesmo arquivo fonte ou distribuídas por vários arquivos, formando bibliotecas de funções. A distribuição em bibliotecas promove a reutilização das funções em vários sistemas e não apenas para aquele para a qual foi projetada inicialmente. Na parte final deste capítulo abordaremos a implementação de uma biblioteca de funções.

(8)

tipo nome_da_funcao([lista_de_parâmetros]) {

declaração_vars_locais;

comandos;

}

A primeira linha é chamada de cabeçalho da função. Toda função em C deve ter um tipo de retorno ou o tipo especial void. Se o tipo for omitido, assume-se que a função seja inteira (int). Note que a declaração de uma função é similar a de uma variável: o nome da função segue o seu tipo. A lista de parâmetros é opcional (indicado pelo uso de colchetes), mas os parêntesis devem sempre ser usados na declaração (e posteriormente nas chamadas à função). Ainda no que se refere à lista de parâmetros, cada parâmetro deve ser declarado separadamente (com vírgula), mesmo que todos sejam do mesmo tipo. No corpo da função podemos ter declaração de variáveis locais à função seguidas dos comandos que implementam a lógica da função. Alguns exemplos de cabeçalhos válidos e inválidos de funções C (os corpos das funções não serão mostrados neste momento):

Válidos

int dobro(int x) { ... }

void imprime_data(char data[]) { ... } getint(void) { ... }

complex getcomplex(char *file) { ... }

int uniao(char c1[], int q1, char c2[], int q2, char c3[],int q3) { ... }

Inválidos complex getComplex { ... } int return(int x) { ... } float toFloat(char) { ... } char 2upper(char c) { ... } int compare(int x, y) { ... }

Tabela 1: Cabeçalhos de funções

Você seria capaz de dizer por que os cabeçalhos acima são inválidos? Deixaremos como exercício. O corpo da função é formado pela declaração das variáveis locais (se houver) e pelos comandos da

função. Funções com tipo devem ter pelo menos um comando return exp em cada um de seus fluxos

internos. exp é uma expressão do mesmo tipo da função. O comando return pára a função e retorna o

valor da expressão para o ponto em que ela foi chamada. Um ponto de chamada é o local de outra função onde esta função foi usada (chamada). Por exemplo, na linha 06 do Código 1 temos uma chamada à função pré-definida printf(). Na linha 07 temos uma chamada à função scanf().

Quando uma função não retornar nada para o ponto de chamada, seu tipo deve ser void. A rigor, uma

função sempre deveria retornar um valor para seu ponto de chamada. O uso de void numa função C pode ser visto como uma declaração de um procedimento e não de uma função. Entretanto, em C, sempre chamamos os subprogramas (mesmo os void) de funções. Vejamos alguns exemplos de funções C:

(9)

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20

/* Arredonda um real para n casas decimais */ /* Parametros: número a ser arredondado (num), número de casas (n) */ /* Retorno: o número real arredondado */ double arredonda(double num, int n) {

double temp; temp = num*pow(10,n); temp = ceil(temp); return temp/pow(10,n); } main() { double x = 3.456543; x = arredonda(x, 3); printf("%lf", x); //imprime 3.457000 return 0; }

Código 2: Função arredonda().

O programa C acima é formado por duas funções: a função principal, main(), e a função arredonda(). Esta possui dois parâmetros, o número a ser arredondado e o número de casas a ser usada no arredondamento. No corpo da função arredonda() há a declaração de uma variável temporária temp, do tipo double, e os comandos que fazem o arredondamento. Como a função é double, ela deve possuir um comando return seguido de uma expressão que produza um valor double ou compatível (float ou int). Digite o código acima, compile-o e execute-o. Altere o número de casas decimais na chamada à arredonda() em main().

A programação estruturada prega que toda função deve ser capaz de executar sua lógica utilizando apenas os valores passados como parâmetros. O uso de variáveis globais é reconhecido como uma péssima prática de programação e seu uso deve ser limitado ao máximo. No caso da função arredonda(), tudo que ela precisa para executar lhe é passado como parâmetro pela função que a chamar. É esta função (a que chama) que deve providenciar um número real e um inteiro para arredonda().

Um erro comum para quem se inicia na programação estruturada é querer fazer os 3 passos que praticamente todo programa possui (entrada de dados, processamento e exibição de resultados) dentro de cada função. Por exemplo, a segunda versão da função arredonda() abaixo não esta sintaticamente incorreta, mas está pobre sob o ponto de vista da modularidade do programa.

01 02 03 04 05 06 07 08

/* (VERSAO BOZO) Arredonda um real para n casas decimais */ /* Parametros: número (num), número de casas (n) */

/* Retorno: o número real arredondado */ void arredonda(double num, int n) {

double temp, numero, casas;

printf("Informe um numero: "); scanf("%f", &numero);

(10)

14 15 16 17 18 19 20 } main() { double x; x = arredonda(x, 3); return 0; }

Código 3: Versão pobre para arredonda(). O que há de errado com o código de arredonda() acima? Vários problemas!

 Ele não apenas arredonda um número, mas também lê este número e ainda exibe o resultado do

arredondamento na saída padrão. Esta função deixou de ser coesa (fazer uma coisa só!). Ela não faz apenas o arredondamento, mas se preocupa também como os dados irão chegar até ela via interface com o usuário e como ela os apresentará ao mesmo usuário. Ela está sobrecarregada, coitada! E se um dia algum polonês quisesse usar essa sua função de arredondamento? Ao usar a sua função num programa, ele se depararia com uma frase em português pedindo para ele

"fornecer um número" e "fornecer o número de casas".

 Segundo: imagine que um programador estivesse usando a função para arredondar vários

doubles em um arranjo. O programa deveria pegar cada elemento deste arranjo, arredondá-lo e colocá-lo noutro arranjo. Não há qualquer tipo de interação com o usuário neste programa: for (i=0; i<MAX ;i++) {

b[i] = arredonda(a[i], 2); //não vai funcionar... }

Entretanto, quando o programa estivesse dentro for para pegar cada número de um arranjo e arredondá-lo, lá apareceria a mensagem pedindo um número e a quantidade de casas decimais. Ora, mas os números já estão no arranjo a. Eles já foram lidos! Ah, mas a função sempre lê o número e a quantidade de casas que ela vai arredondar. Ela foi escrita assim! Em outras palavras, ela está fazendo mais do que deveria. Ela não está coesa, está fazendo mais de uma coisa (arredondando e fazendo entrada/saída com o usuário).

 Por último, observe que os parâmetros da função estão lá só de enfeite. Eles não foram usados no código da função nenhuma vez. Então por que será que nós estamos passando x e 3 como parâmetro de arredonda() lá no main().

Resumo: ao escrever uma função, foque a função para o cumprimento de um único objetivo. Lembre-se que este objetivo pode ser alcançado através de vários passos, mas ele é único. Para funções que realizam processamento, escreva-as de forma que os dados a serem processados sejam recebidos como parâmetros e não solicitados diretamente ao usuário pelo teclado. Deixe esta tarefa de interface com o usuário para outras funções especializadas em entrada de dados e em exibição de resultados. Não se preocupe, em algum lugar do seu código elas irão se encontrar (entrada->processamento->saída). Faça seu código modulado desde o início.

Já percebi que alguns alunos preferem fazer tudo no main() para depois organizar em funções. Isto é desastroso, pois no final, depois que o código estiver funcionando, ninguém vai ter coragem de alterá-lo. Obrigue-se a escrever seu código de forma modular desde o início, com isso você não terá retrabalho e estará exercitando a prática de escrita de códigos modulares.

(11)

Vamos mostrar agora o código de outra função, para você se acostumar com a sintaxe de declaração de funções em C: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <stdio.h #include <conio.h>

/* Escreve uma string envolta por uma moldura em (x,y) */ /* Parametros: string (str), coordenadas (x, y) */ /* Retorno: não tem */ void moldura(int x, int y, char *str) {

int i, tam; if (x > 80 || x < 0 || y > 24 || y < 0) //coordenadas inválidas return; tam = strlen(str); gotoxy(x,y); putchar(218);

for (i=0; i < tam ;i++) {

putchar(196); } putchar(191); gotoxy(x,y+1); putchar(179); puts(str); putchar(179); gotoxy(x, y+2); putchar(192);

for (i=0; i < tam ;i++) {

putchar(196); } putchar(217); } main() { char nome[]="Frederico"; clrscr(); moldura(3, 3, nome); return 0; }

Código 4: Função moldula().

A função moldura() imprime um string emoldurado. O main() do Código 4 deverá imprimir o seguinte na coordenada (3,3) da tela modo texto:

(12)

não pode ser usada em nenhuma expressão, deverá ser chamada sozinha numa linha da função chamadora (veja linha 35 de main()).

Vamos voltar para o nosso código monolítico (Código 1). Podemos agora melhorá-lo escrevendo uma função que imprima uma hora no formato conhecido (00h00m00s).. Lembre-se, esta função não faz leitura da data da entrada padrão, ela receberá a data por parâmetro e imprimirá na saída padrão (é uma função de exibição de resultados). Vamos ao código:

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #define HORA 3

void printhora(unsigned int *); main(){

unsigned int hora1[HORA], hora2[HORA], dif[HORA]; unsigned int segundos1, segundos2, diferenca; printf("Forneca horas da hora1: ");

scanf("%d", &hora1[0]);

printf("Forneca minutos da hora1: "); scanf("%d", &hora1[1]);

printf("Forneca segundos da hora1: "); scanf("%d", &hora1[2]);

printf("\nForneca horas da hora2: "); scanf("%d", &hora2[0]);

printf("Forneça minutos da hora2: "); scanf("%d", &hora2[1]);

printf("Forneca segundos da hora2: "); scanf("%d", &hora2[2]);

segundos1 = hora1[0]*3600 + hora1[1]*60 + hora1[2]; segundos2 = hora2[0]*3600 + hora2[1]*60 + hora2[2]; diferenca = segundos2 – segundos1;

dif[0] = diferenca / 3600;

dif[1] = (diferenca % 3600) / 60; dif[2] = (diferenca % 3600) % 60; printf("A primeira hora foi:"); printhora(hora1);

printf("A segunda hora foi:"); printhora(hora2);

printf("\n\nA direfereça entre as horas fornecidas é de "); printhora(dif);

return 0; }

void printhora(char hora[]){

printf("%02dh%02dm%02ds", hora[0], hora[1], hora[2]); }

Código 5: Função printhora().

Criamos uma função apenas para imprimir uma hora. No Código 1 nós precisamos desta funcionalidade 3 vezes (linhas 24, 25, 26). Alguém pode dizer que não foi vantagem criar esta função, pois ela só tem uma linha de código. Poderíamos muito bem colocar esta linha onde a função é chamada nas linhas 28, 30 e 32 do Código 5, não é mesmo? Não! Há uma vantagem no uso da função. Imagine que nosso

(13)

programa fosse 30 vezes maior do que este e que usássemos o printf() com a máscara "%02dh%02dm%02ds" em 20 lugares deste código para imprimir horas. Agora queremos mudar para o formato de hora americano (00:00:00). Teríamos que modificar estes 20 pontos do programa, com o pontencial de um ou mais deles não serem alterados corretamente, gerando bugs no código! Com a função, alteramos o único printf() da função e pronto!

Uma outra novidade do Código 5 é o protótipo da função que aparece na linha 03. Protótipos de funções são os cabeçalhos das funções repetidos no início do código, antes da primeira função. Estes protótipos servem para declarar as funções no arquivo e assim não haver necessidade de uma predefinida para a implementação das funções no arquivo. Se uma função A é chamada por uma função B, então A tem que ser implementada antes de B. Com os protótipos de A e B declarados no início do código, A e B podem ser implementadas em qualquer ordem. Observe os códigos anteriores a este último, em que não declaramos nenhum protótipo. Eles eram necessários? Por quê?

Protótipo: declaração da função realizada no início do arquivo, antes das implementações. Um protótipo só deve possuir o cabeçalho da função seguido de um ponto-e-vírgula. Os identificadores dos parâmetros são opcionais no protótipo.

Uma última observação. Como o código acima é muito pequeno, protótipos, definição de constantes (#define) e a própria implementação das funções podem estar num único arquivo. Em códigos maiores o correto seria construir uma biblioteca de funções. Aguarde sensacionais revelações sobre este assunto.

1.3 Passagem de valores

A maioria das linguagens de programação estruturadas possuem duas formas de passagem de dados para uma função: a passagem por valor e a passagem por referência.

Na passagem por valor, uma cópia dos valores contidos nos parâmetros reais1 é passada para os parâmetros formais. A função arredonda() do Código 2 só possui parâmetros passados por valor. Na passagem por referência, o endereço do parâmetro real é copiado para o parâmetro formal. Com isso, tudo o que você fizer com o parâmetro formal irá refletir no parâmetro real. A função moldura() do Código 4 possui tanto parâmetros que são passados por valor (x e y) quanto por referência (str).

Vamos mostrar a diferença entre elas de uma forma mais diagramática. Veja o código do programa a seguir: 01 02 03 04 05 06 07 08

double delta(double, double, double); main(){

double x=1, y=2, d; d = delta(x, y, 3.0);

(14)

09 10 11 12 13 14 return 0; }

void delta(double a, double b, double c){ return b*b – 4*a*c;

}

Código 6: Funçao delta().

Na chamada da função delta() na linha 06, encontramos os seguintes parâmetros reais: x, y e 3 (duas variáveis e um literal do tipo double). Veja como esses valores são passados para os parâmetros formais declarados na linha 12:

06 delta( x , y , ) (formais)

Uma cópia dos parâmetros reais é passada para os formais! 12 delta( a , b , c ) (reais)

Como uma cópia dos valores é que é realizada, o que você fizer com as variáveis a, b e c, não alterará em nada o valor de x, y e 3 lá na função main(). Quando uma função é executada, antes de executar seu primeiro comando, todas as variáveis locais e os parâmetros formais são criados na memória do computador, numa região chamada pilha. Ao término da execução da função, antes de voltar para o ponto de chamada, estas mesmas variáveis locais e parâmetros são retirados da pilha e deixam de existir no programa. Dizemos que seu escopo (local de uso) é limitado ao corpo da função em cada chamada da função. Ou seja, estas variáveis são criadas tantas vezes quantas forem as chamadas para a função e terão um tempo de vida igual ao tempo em que a função estiver em execução.

Voltando ao tema da modificação dos valores dos parâmetros formais. Caso você modificasse a função delta() para o código a seguir, isso não afetaria em nada as variáveis x e y no main().

01 02 03 04 05 06 07 08

/* Versao BOZO de delta */

void delta(double a, double b, double c){ double temp = b*b – 4*a*c;

a = 100; b = 200; c = 300; return temp; }

Código 7: Função delta() modificada.

Na mudança que fizemos acima em delta(), nós modificamos propositadamente os valores dos parâmetros formais para valores fixos (100, 200 e 300). Isto não afetaria as variáveis x, y e o literal 3 do main() (linha 06). Se imprimíssemos os valores de x e y depois da chamada à delta() em main() (linha 06), eles continuariam com os valores 1 e 2, respectivamente.

Passagem por valor: uma cópia dos valores dos parâmetros reais é passada para os parâmetros formais. Use naqueles parâmetros que não precisam ser alterados pela

1 2 3

(15)

função (parâmetros só de entrada).

Lembre-se, você passa um parâmetro por valor! Não é a função que é passada por valor, como muitos pensam. Isso é uma características de seus parâmetros e não da função em si!

Agora vamos ver um exemplo de passagem por referência. Veja o código a seguir: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17

void troca(char *, char *); main(){ char c='A', d='F'; troca(&c,&d); printf("c:%c - d:%c", c, d); return 0; }

void troca(char *c1, char *c2){ char temp;

temp = *c1; *c1 = *c2; *c2 = temp; }

Código 8: Função troca().

Este programa contém uma função troca() que faz a troca de valores de duas variáveis. Suponha que essa operação fosse requisitada 200 vezes num programa seu. Ao invés de digitar 200 vezes as três linhas (200x3=600 linhas) que realizam a troca, você apenas chama 200 vezes a função troca(). Esta função possui dois parâmetros por referência. Parâmetros formais que são recebidos por referência devem ser marcados em C com um asterisco (*) para diferenciá-los de parâmetros por valor. Na chamada, você deve passar o endereço da variável e não a variável em si. Se você passar a variável, seu valor será copiado; passando o endereço, ela será referenciada pelo parâmetro formal! Esquematicamente temos:

06 troca( &c , &d ) (formais)

O endereço dos parâmetros reais é passados para os formais! 12 delta( *c1 , *c2 ) (reais)

As variáveis c1 e c2 possuem, cada uma, o endereço de um parâmetro real. Elas não receberão os valores destas variáveis, mas seus endereços de memória (operador &), ilustrados no esquema acima

'A'

0x100 'F'

(16)

efeito lá na área referenciada por estas variáveis, ou seja, c e d. Dizemos que c1 aponta para (ou referencia) c enquanto c2 aponta para (ou referencia) d.

E o que aconteceria se não usássemos os asteriscos antes de c1 e c2 dentro da função troca()? Neste caso, a linha 14 daria um erro, pois estaríamos tentando jogar o valor (0x100, um endereço) para tempo que é um char. Tipos incompatíveis! Já a linha15 (*c1 = *c2) ficaria c1 = c2, o que resultaria em c1 recebendo o valor 0x200, ou seja, c1 e c2 apontando para d (0x200 = endereço de d). Confusão total!

E se não colocássemos os & na chamada (linha 06)? Aí dependeria do seu compilador. Pelo menos um aviso (warning) seria emitido durante a compilação, indicando que na função você espera um endereço e você ta passando é o valor das variáveis.

O printf() da linha 07 imprimiria os valores 'F' para c e 'A' para d.

Passagem por referência: o endereço do parâmetro formal é passado para o parâmetro real, que deve ser declarado como um apontador (*). Use quando você quiser alterar, na função, o valor de um parâmetro real.

Veja agora uma função que troca a posição de dois elementos quaisquer de um arranjo. 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17

void trocaArray(char *, int, int); main(){

char letras[] = "abcde"; trocaArray(letras, 0, 4);

printf("%s", letras); //imprime ebcda na tela

return 0; }

void trocaArray(char *s, int x, int y){ char temp;

temp = s[x]; s[x] = s[y]; s[y] = temp; }

Código 9: Função trocaArray().

Este programa é similar ao Código 8, com algumas pequenas diferenças. Desta vez estamos passando um arranjo para uma função e arranjos, ao contrário dos outros tipos de variáveis, são passados por referência (por default) e não por valor. Não há como passar um arranjo por valor para uma função em C. Por isso o parâmetro formal s está declarado com um asterisco (linha 12). Mas observe a chamada da função (linha 06). Não utilizamos o operador & na variável letras! Por quê? Porque ela é um arranjo e portanto é passada por referência automaticamente, não precisamos utilizar &. Observe que os outros dois parâmetros de trocaArray() são passados por valor, uma vez que não os modificaremos na função. Eles indicam os índices dos elementos que serão permutados.

(17)

Observe a forma que lidamos com um parâmetro formal que recebe um arranjo na função trocaArray(). Ela é declarada char *s, e na hora de usá-la na função utilizamos a notação de arranjos habitual, usando colchetes. Não precisamos utilizar o * antes de s. Se você o colocar, provocará um erro de sintaxe no código. Lembre-se disso: trabalhar com arranjos em funções é bem mais fácil, pois a sintaxe de uso de um arranjo é sempre a mesma (usando [ ]). Só não se esqueça de colocar o asterisco no parâmetro formal!

Há uma outra sintaxe antiga e pouco utilizada para declarar parâmetros formais para arranjos. Consiste em utilizar um par de colchetes em vez do asterisco. A declaração de trocaArray() ficaria assim nesta notação:

void trocaArray(char s[], int x, int y)

Você é quem escolhe a notação mais conveniente (mas saiba que os programadores profissionais preferirão a notação com asteriscos). Por último, se você colocar algum número inteiro entre os colchetes na declaração acima não fará a menor diferença! Este valor não aloca memória e tanto faz não colocá -lo, colocar 1, 100 ou 1000000.

A tabela a seguir resume os dois tipos de passagens de dados em C:

Passagem por valor Passagem por referência

 Uma cópia dos valores dos

parâmetros reais é passada para os parâmetros reais.

 O endereço dos parâmetros reais é passado para os parâmetros formais (que devem ser declarados com *).

 Os parâmetros reais podem ser

variáveis, literais ou mesmo

expressões.

 Os parâmetros reais só podem ser variáveis (literais e expressões não possuem endereço de memória!)

 Arranjos não são passados por valor em C

 Arranjos são passados por referência sempre.

Tabela 2: Passagem por valor versus por referência.

Vamos agora voltar para o nosso programa de horas (Código 5). Vamos modularizá-lo um pouco mais, criando uma função para ler uma hora e outra que realize a diferença entre horas.

Primeiro a função que lê horas: 01 02 03 04 05 06 07 08 09 10 #define HORA 3

//Protótipos das funções void printhora(unsigned *);

void difhora(unsigned *, unsigned *, unsigned *); void gethora(unsigned *);

main(){

(18)

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 gethora(hora2);

//Processamento (calculo da diferenca de horas) difhora(hora2, hora1, dif);

//Exibição de resultados

printf("A primeira hora foi:"); printhora(hora1);

printf("A segunda hora foi:"); printhora(hora2);

printf("\n\nA direfereça entre as horas fornecidas é de "); printhora(dif);

return 0; }

/* Imprime uma hora na saída padrão */ void printhora(char hora[]){

printf("%02dh%02dm%02ds", hora[0], hora[1], hora[2]); }

/* Calcula a diferenca entre h2 e h1 (h1-h2) e pões em dif */ void difhora(unsigned *h1, unsigned *h2, unsigned *dif) { unsigned segundos1, segundos2, diferenca;

//Converte as duas horas para segundos segundos1 = h1[0]*3600 + h1[1]*60 + h1[2]; segundos2 = h2[0]*3600 + h2[1]*60 + h2[2]; //Subtrai os segundos

diferenca = segundos2 – segundos1;

//Converte a diferenca em horas novamente dif[0] = diferenca / 3600;

dif[1] = (diferenca % 3600) / 60; dif[2] = (diferenca % 3600) % 60; }

/* Le uma hora */

void gethora(unsigned *hora) { char c[3];

//Le horas c[0] = getch(); c[1] = getch(); c[2] = '\0';

hora[0] = (unsigned) atoi(c); putchar('h');

//Le minutos c[0] = getch(); c[1] = getch(); c[2] = '\0';

(19)

69 70 71 72 73 74 75 76 77

hora[1] = (unsigned) atoi(c); putchar('m');

//Le segundos c[0] = getch(); c[1] = getch(); c[2] = '\0';

hora[2] = (unsigned) atoi(c); }

Código 10: Funções gethora() e difhora().

Compare o código acima com o Código 1 e note como o programa principal ficou mais fácil de ler. Destaca-se nele as três partes de um sistema: entrada de dados, processamento e saída.

A função difhora() faz exatamente o que o nome diz, calcula a diferença entre duas horas e põe o resultado numa terceira variável (dif). Um detalhe importante sobre esta função é que não poderíamos ter calculado a diferença e devolvido como resultado da função (por return). Isto porque arranjos não podem ser devolvidos por return. Então só nos resta calcular a diferença e colocar no terceiro parâmetro (dif). Como arranjos são passados automaticamente por referência, tudo o que fizermos na variável dif da função difhora() afetará o parâmetro real da chamada.

A função gethora() lê uma hora completa (as três partes). Veja que no Código 1 utilizávamos três scanf() para ler cada parte da hora, e repetíamos novamente para cada hora que líamos. Agora só precisamos chamar uma função, gethora(). Que aliás, ficou muito grande, não é? Vamos dar um jeito nisso. Veja a nova versão de gethora():

01 02 03 04 05 06 07 08 09 10 11 12 /* Le uma hora */

void gethora(unsigned *hora) { char c[HORA];

int i, j;

for (i=0; i < HORA ;i++) { for (j=0; j < 2 ;j++)

c[j] = getch();

c[j] = '\0';

hora[i] = (unsigned) atoi(c);

} }

Código 11: Função gethora() melhorada.

Incomparavelmente melhor e mais compacta (reduzimos à metade do número de linhas da original!). E ainda aproveitamos para parametrizar o for com a macro HORA.

(20)

 Reaproveitamento de conjunto de funções e outras definições (como tipos, por exemplo) em outros sistemas;

 Divisão do trabalho em equipes, onde cada equipe pode ficar responsável por uma parte do có digo. Isto é vital em grandes projetos de software.

Praticamente toda linguagem de programação possui uma forma de modularizar o código fonte. Cada uma dá uma denominação própria a estes módulos:

Pascal chama-os de units;

Java chama-os de classes;

Modula2 chama-os de módulos;

Ada chama-os de; pacotes.

E em C? Como são chamados? C utiliza a denominação biblioteca (library, em inglês). Quando Kernighan e Ritchie definiram a linguagem, preocuparam-se em definir também um conjunto de bibliotecas padrões para ela. Essas bibliotecas pré-existentes possuem várias funções úteis aos programadores C. A biblioteca <stdio.h>, por exemplo, é uma delas. O C ANSI define várias bibliotecas padrões e todo livro de C apresenta estas bibliotecas, suas funções e outros recursos.

Uma vez definida uma biblioteca, você pode usá-la para compor o seu programa. Isto significa que você pode usar todas as funções e outras definições (tipos, macros, etc.) no seu código. Você já vem fazendo isso quando usa um printf(), por exemplo. Ao incluir a biblioteca <stdio.h> no seu código, você informa ao compilador que quer usar o printf() e todas as outras funções ali definidas. Isto promove a reutilização de código. Ao invés de se preocupar em escrever uma função de formatação e impressão de dados na saída padrão, simplesmente use uma que já existe em alguma biblioteca.

A boa notícia é que você não está restrito apenas às bibliotecas padrões do C. Você pode construir sua própria biblioteca e utilizá-la em vários sistemas. Ou então, você pode utilizar bibliotecas para melhorar a modularidade de um sistema com um código fonte enorme, sem necessariamente reutilizar estas bibliotecas em outros sistemas. Com o tempo, aprendemos a construir bibliotecas que são genéricas, isto é, que servem não apenas para o nosso propósito imediato, mas para muitos outros programas que ainda iremos escrever.

Uma biblioteca em C é formada geralmente por dois arquivos:

Um arquivo de cabeçalho, com extensão .h, que contém a interface ou definições da biblioteca e

Um arquivo de implementação, com extensão .c, que contém a implementação do que foi definido no

.h.

O uso do arquivo de cabeçalho não é obrigatório, mas é uma boa prática utilizá-lo. A pergunta que você deve estar se fazendo é: o que vai em cada um destes arquivos? Há um padrão que recomenda o seguinte:

Nos arquivos .h:

 Definições de macros (#define)

(21)

 Protótipos de funções públicas Nos arquivos .c:

 Definições de macros internas ao .c (#define)

 Definições de tipos internos ao .c (typedef)

 Protótipos das funções internas ao .c (static)

 Implementação de todas as funções da biblioteca

O arquivo de cabeçalho é usado para que a biblioteca informe "ao mundo" tudo que ela fornece! A forma como estes recursos estão implementados é uma tarefa do arquivo de implementação. Por exemplo: numa biblioteca de manipulação de números complexos, as funções de soma e subtração estariam publicadas no .h na forma de protótipos. A implementação da soma e da subtração ficariam confinadas ao arquivo .c. Exemplo: complex.h typedef struct { double real; double imag; } complex;

complex cxadd(complex, complex); complex cxsub(complex, complex);

O arquivo .h ao lado informa "ao mundo" que está disponiblizando uma definição de tipo complex e duas funções que manipulam este tipo. As funções cujos protótipos estão declarados aqui são chamadas de externas.

complex.c

#include "complex.h" //funcoes internas

static double is_zero(complex); //implementações

complex cxadd(complex a, complex b) {

//implementação da soma... }

complex cxsub(complex a, complex b) { //implementação da subtração... } double is_zero(complex c) { //implementação da função... }

O arquivo .c ao lado contém a implementação das funções externas e também de funções internas que não interessam a terceiros, mas apenas às próprias funções desta biblioteca.

A função is_zero() não é pública, é de uso interno das funções da biblioteca (p.e. alguma função deve chamar is_zero() na sua implementação).

(22)

Após implementar os arquivos .h e .c de uma biblioteca, o próximo passo é compilar a biblioteca. Ela pode ser compilada separadamente de todos os outros códigos fontes. As bibliotecas padrão do C já vêm pré-compiladas. O fornecedor do compilador C que você utiliza disponibiliza um arquivo .h e um arquivo compilado da biblioteca (.o ou .obj). Se você não quer dar acesso de seus códigos fontes a terceiros, disponibilize sua biblioteca pré-compilada para seus usuários.

Projetos no Turbo C

A forma como você trabalha com um sistema cujo código fonte é constituído por vários arquivos fontes de bibliotecas é dependente do seu ambiente de desenvolvimento. No Turbo C, você deve criar um projeto e adicionar os seus arquivos fontes .c e os .obj de bibliotecas de terceiros.

Quando você está desenvolvendo suas próprias bibliotecas em paralelo com uma aplicação, é comum adicionar só .c no projeto. A partir do momento que as bibliotecas ficam estáveis (isto é, não precisam mais serem corrigidas), você pode compilá-las em separado e adicionar os arquivo objetos (.obj) ao projeto.

Quando você está com o arquivo de uma biblioteca aberto e tecla F9, a biblioteca é compilada separadamente (só ela). Experimente fazer isso e depois ver o diretório onde ela está. Um arquivo .obj é criado nele.

Se você está com um projeto aberto e escolhe Compile|Build All, todos os arquivos .c do projeto são compilados e linkados para formar um executável (.exe).

(23)

Bibliografia

MIZHAHI, Victorine Viviane. Treinamento em Linguagem C – Treinamento Completo, Módulo 1;

McGrawHill; São Paulo; 1990.

MIZHAHI, Victorine Viviane. Treinamento em Linguagem C – Treinamento Completo, Módulo 2;

McGrawHill; São Paulo; 1990.

KERNIGHAM, Brian, RITCHIE, Dennis. C - A Linguagem de Programação; 2a Edição; Editora Campus;

Referências

Documentos relacionados

10 Enquadramento  Fiscal  Produto/Mercadoria  Código da TIPI e Operações  Legislação 

1 Salienta-se, contudo, que os arts. 1º ao 12 não são hierarquicamente superiores em relação aos demais artigos do código, tão-pouco são vinculantes, apenas

A norma penal não cria direitos subjetivos somente para o Estado, mas também para o cidadão, se o Estado tem o jus puniendi, o cidadão tem o direito subjetivo de

nº 50 UF AM Ponta Negra Município CEP Torre Avalon Apartamento 902 Manaus 69037-061 Bairro CPF Endereço Cliente. Alameda Zaire

No entan- to, na análise comparativa das respostas obtidas para cada questão do protocolo QVV entre os sujeitos do GP e do GNP que perceberam a mu- dança vocal, foi encontrada

Para isso, mais especificamente, buscamos identificar as expectativas sociais sentidas pelas e pelos jovens em relação às suas identidades sexuais e de gênero, bem como

Cada qual a seu modo, com caraterísticas organizacionais diferentes, CCIR, CONIC, REJU e Koinonia são quatro entidades ecumênicas que vêm tendo certo destaque no país em

Com o objetivo de obter uma farinha pré-cozida de trigo e soja (90:10) com boas características tecnológicas para uso em croquete de carne, foram estudados efeitos de parâmetros