versão antiga em breve, versão revista - uffotton/produtos/apostila_c.pdf · linguagens como...

96
Versão antiga Em breve, versão revista L I N G U A G E M C Otton Teixeira da Silveira Filho Registro N° 70.630 Biblioteca Nacional - Escritório de Direitos Autorais 1997

Upload: dangbao

Post on 08-Nov-2018

224 views

Category:

Documents


0 download

TRANSCRIPT

Versão antiga 

Em breve, versão revista

L I N G U A G E M

C

Otton Teixeira da Silveira Filho

Registro N° 70.630 Biblioteca Nacional - Escritório de Direitos Autorais

1997

Números de página

SUMÁRIO

1)Introdução

2)C, A linguagem

2.1) Identificadores 12.1) Palavras-chave 32.2) Tipos de variáveis 42.3) Modificadores 4

3)Operadores 4

de atribuição 6vírgula 6aritméticos 6de incremento e decremento 7de endereço 8de bit 8lógicos 8relacionais 9

Precedência de operadores 9Abreviação de operações 10Conversão de tipos 11

4) Constantes 12

5)Controle de fluxo 13

if-else 13? : 13switch-case-default 13goto 14

6)Laços 15

while 15do-while 15for 15

Palavras auxiliares 16

break 16continue 16

7) Fatos e mitos - Velocidade de processamento17

8)Programas em C 18

7.1) Denteação 24

9)Funções 25Forma clássica

Variáveis Globais, Locais e Automáticas28Um Comando do Preprocessador, cabeçalhos e arquivos

Departamento de Ciência da Computação-UFF

Números de página

de inclusão 29Algumas funções úteis 31

10) Ponteiros 36Ponteiros e funções 36Ponteiros e vetores 39Aritmética de Ponteiros 42Inicializando vetores 43Ponteiros para funções 44Mais Sobre Funções 46

11) O Preprocessador

O comando #define (do preprocessador)51Mais comandos do preprocessador 54

12) Vetores Multidimensionais 56

13) Vetores de Ponteiros e Ponteiros de Vetores 58Ponteiros de Ponteiros & Ponteiros de Ponteiros de Ponteiros e etc...59

10) Funções 60Em notação moderna

Funções em Forma Moderna 60Mais Algumas Funções 61

11) main() com parâmetros 63

12) Modificadores 65

Variáveis estáticas 65Variáveis externas 67Variáveis registrador 67

13) Reservando memória para uso temporário 69

Fragmentação 70

14) Estruturas e uniões 71

Estruturas 71Estruturas e Manipulação de Bits 73Estruturas e funções 74Uniões 76Inicializando estruturas e unimos 81

15) Determinando o tamanho de Estruturas, Uniões e etc82

15) Enumeração 83

16) Definição de Tipos 85

16) Trabalhando com arquivos em disco

Funções de Alto Nível 86Funções de Baixo Nível 91

Departamento de Ciência da Computação-UFF

Números de página

17)Arquivos padrões e como usar a impressora 97

18) Respostas de problemas 99

19) Apêndices

Tabelas dos operadores lógicos101

Departamento de Ciência da Computação-UFF

Números de página

INTRODUÇÃO

C é uma linguagem de programação que tem como características principais grande flexibilidade,escrita compacta, padronização bem feita e alta velocidade de processamento. Por estas características, ela échamada, muitas vezes, de uma Linguagem de Programador pois envolve aspectos, como os já citados, queagradam a estes profissionais.

Foi desenvolvida no início da década de 70 por Dennis Ritchie influenciada pela linguagem B, estadesenvolvida por Ken Thompson. A linguagem B teve como fonte inspiradora uma outra linguagem, o BCPL. Bé uma linguagem que só tem um tipo de dado que corresponde ao tamanho da palavra do computador no qualtrabalha. A linguagem C, por sua vez, tem uma gama relativamente grande de tipos de variáveis mas não é tãofortemente tipada como Pascal. Permite ainda a criação de estruturas de dados não homogêneas. Apesar destasdiferenças, BCPL, B e C são filosoficamente semelhantes. A ideia é ter uma linguagem de escrita compacta,com recursos de manipulação de dados a baixo nível mas com características de linguagem de alto nível. Criarum compilador de C é uma tarefa consideravelmente mais fácil que criar um compilador Fortran, por exemplo,além de poder ser bem pequeno existindo versões que completas e funcionais que ocupam menos de 100KB deespaço em disco, exigem memória da ordem de 256KB, restricões que nos dias de hoje (1997) nos parecempaleolíticas. As primeiras versões para micro (Apple II, por exemplo) funcionavam bem melhor quecompiladores de outras linguagens.

As idéias por trás de C são tão simplificadoras e "naturais" que ela serve de base para outraslinguagens. Dando apenas um exemplo, existe uma linguagem que se baseia simultaneamente em C e noconceito de objetos[]. Esta linguagem, o C++ (pronuncia-se cê mais mais), é bem definida existindo o padrãoANSI.

Atualmente existem compiladores dos mais diversos fabricantes e ainda versões de domínio públicolargamente usados em ensino e pesquisa no mundo inteiro. O caso mais notório é o GNU C (embora o ProjetoGNU não se limite à este compilador) e existe versões do mesmo para os mais variados ambientes(UNIX/Linux, McIntosh, MS-DOS/Windows).

C originalmente foi uma linguagem construída para desenvolver programas de sistemas, ou seja :

Sistemas operacionaisAssemblersEditoresInterpretadoresCompiladoresGerenciadores de rede, etc.

Por exemplo, o sistema operacional UNIX é escrito em C, estando esta linguagem e este sistemaoperacional intimamente ligados inclusive sob o ponto de vista de filosofia de trabalho e construção deprogramas.

O que faz esta linguagem aplicável a estas funções é que C permite um nível de manipulação de dados,só conseguido anteriormente pelo uso de programas em Assembler. Estes últimos são de escrita difícil e tediososde escrever além de ter depuração geralmente problemática. Um fator que pode levar a adoção do Assembler é avelocidade mas, nem sempre este é o fator principal e mesmo neste ponto C não faz feio. C, permite o uso derecursos de "baixo nível" numa linguagem de "alto nível" e boa velocidade de execução. Por isso, muitas vezesC é chamada de linguagem de "médio nível". Como se pode perceber, não há nenhuma conotação ruim nostermos baixo ou médio. Para dar exemplos, Linguagens deBaixo Nível são os Assembly de cada máquina.Linguagens de Alto Nível são FORTRAN, ALGOL, BASIC, PASCAL, etc. As Linguagens de Médio Nívelmais conhecidas são C e FORTH, esta última tendo uma estrutura mais flexível e poderosa do que C masgeralmente é de execução mais lenta, por ser (nas suas versões mais comuns) parcialmente interpretada, além deser de programação confusa.

É claro que não demorou muito tempo para que uma linguagem com este poder fosse se difundindofora da área de programas de sistemas. Hoje, boa parte das aplicações de alto desempenho são cada vez maisescritas em C, sejam elas comerciais ou científicas.

Você poderá escrever em C qualquer programa que execute qualquer tarefa escrita originalmente emlinguagens como FORTRAN, Pascal, COBOL ou BASIC e coisas que estas não fazem ou que, para serem feitas,exigem conhecimento de truques ou macetes confusos ou específicos do produto com o qual você estivertrabalhando.

Mas nem tudo se resume a maravilhas. Em troca, C exige do programador muita responsabilidade eatenção. Outras linguagens criam barreiras que protegem certas estruturas na memória do computador, mas Cnão. Portanto se acostume com o lema de C :

"O programador é inteiramente responsável pelo que faz."

A filosofia é de que o programador não precisa ser tutelado.

Departamento de Ciência da Computação-UFF

Números de página

C não menospreza a sua inteligência, mas também não se responsabiliza por descuidos. Não se esqueçaque um algoritmo mal feito vai funcionar mal em qualquer linguagem. Mas não se assuste. Há uma brincadeiraentre os não usuários de C e mesmo os que iniciaram o seu estudo de maneira descuidada. Estes dizem: "Vocêgosta de viver perigosamente ? Programe em C!" Se fosse tão "perigosa" assim, C não seria tão usada.

Iniciaremos a discusão da linguagem C seguindo como referência o que é chamado "C padrão", ou seja,a definição mínima contida no livro "A Linguagem de Programação C" escrito por Brian Kernigham e DennisRitchie em sua primeira edição. Haverá ainda informações sobre uma complementação definida pelo AmericanNational Standards Institute (ANSI). A esta chamaremos de "Padrão ANSI".

Sobre o parágrafo anterior é bom chamar a atenção que cada fabricante de software que produz parauma máquina específica, tende a incluir “extenções da linguagem” fora dos padrões acima. Nem sempre épossível ignorar estas modificações do padrão. É o caso dos famigerados “modos de memória” em máquinasmicrosoft/Intel. No entanto, por uma questão de produtividade, flexibilidade e transportabilidade devemos nosater da melhor maneira possível aos padrões não dependentes de fabricante. Esta preocupação em seguir padrõesfacilita a construção de produtos muito mais do que limita a produção. Seguir uma padronização é consideradauma atitude louvável sendo que no caso do Unix todo produto que carrega os dizeres “POSIX COMPLIANT” éconsiderado de maneira mais “respeitosa”.

Como ponto ainda, temos versões “Integradas” e “não-integradas”, ou seja, versões onde temos umsistema que integra os passos de edição, compilação e depuração e sistemas em que cada uma destas fases sãoexecutadas separadamente. Os sistemas integrados são mais “confortáveis” mais o sistema de integração ocupaespaço em memória e ainda é um elemento estranho ao processamento do programa (podemos ter reaçõesdiferentes do programa se ele roda dentro ou fora do integrador). Muitas vezes (principalmente em programascríticos) é mais seguro botar a preguiça de lado de sofrer o “imenso desconforto” de apertar uns botões a mais.

O texto, no essencial, não se prende a um compilador ou a uma máquina específica. Quanto areferências a C++, damos o livro de definição da linguagem de Stroutrop e ainda chamamos a atenção que asversões mais recentes da versão de C do Projeto GNU já são efetivamente compiladores de C++. Produtorestradicionais de compiladores C também estão criando produtos já admitindo C++.

Quero lembrar que este texto, pressupõe um conhecimento prévio de técnicas de programação em umalinguagem como FORTRAN, Pascal, Assemblers, etc. Não é recomendável a adoção de C como primeiralinguagem de programação. Os programadores que tiverem alguma experiência com assemblers,provavelmente, absolverão com mais facilidade alguns conceitos incomuns contidos em C e não em outraslinguagens. Como este curso presupõe tão variadas origens, alguns pontos podem aparentar ser redundantes,óbvios ou (para alguns) um tanto quanto obscuros. Antes de mais nada cuidado com as aparências. O óbviopode não ser tão óbvio e o obscuro pode ter origem no “sotaque” que você carrega da linguagem deprogramação que você está mais acostumado.

Quanto da forma do texto, fazemos inicialmente uma descrição da linguagem de uma maneira geral eapresentamos exemplos simples que (ao se apresentar novas estruturas) vamos modificando sistematicamentefazendo uma apresentação informal da linguagem. A medida que evoluimos, introduzimos mais conceitos novose sofisticamos os exemplos até englobar a linguagem como um todo. De certa forma, esta estratégia tem porinspiração a origem de C. UNIX foi criado com a filosofia de construção que devemos ter ferramentas desoftware simples, dedicadas a determinada operação e confiável, algo como:

Menos é mais

. Devemos então adotar esta como filosofia de programação.

Departamento de Ciência da Computação-UFF

Números de página

C, A LINGUAGEM

IDENTIFICADORES

Um ponto importante em C são as especificações dos identificadores e algumas convenções na criaçãodos mesmos. Os identificadores em C, podem começar por qualquer letra maiúscula ou minúscula ou o sinal desublinhar (underscore _). No meio de um nome, poderam haver letras, números ou o sinal _ (sublinhado ouunderscore) e nada mais. O número de caracteres que difere um identificador de outro, pode variar decompilador para compilador sendo que na definição de C padrão K&R são levadas em consideração 8 caracterese em C ANSI 32 caracteres.

Exemplos corretos e distintos (nem por isto todos são recomendáveis!) :

Aaexemplo_de_identificadorOUTRO_exemplo_DE_identificadorMAIS_1_ExEmPlO_e_outro

Exemplos incorretos :

1_exemplo (começa por número)1a (idem)um-exemplo-errado (hífem entre palavras)+um_exemplo_errado (carater inválido "+")esta_errado_tambem! (caráter inválido "!")

Chamamos a atenção para convenções adotadas para os identificadores :

"Variáveis tem seus identificadores em letras minúsculas""Constantes tem seus identificadores em maiúsculas""Tipos criados pelo usuário devem ter os seus identificadores começando por uma maiúscula".

Recomenda-se, por questões de padronização, que você adote esta convenção, além do que ela facilita avisualização de "quem é quem" dentro do programa. Votaremos a falar dentro em pouco de constantes e maistarde de declarações de tipo. Variações podem ser feitas sempre que facilite o entendimento. Mas lembre-se:estas são apenas recomendações. Caso violá-las facilite a compreensão de um programa, é um caso a se pensar.

Lembre-se novamente que C distingue entre letras maiúsculas e minúsculas, portanto, osidentificadores abaixo são diferentes entre si.

SOMAsomaSoma

Fazemos ainda a recomendação que você ao definir os seus identificadores o faça da maneira maisóbvia possível. Se uma variável acumula a soma de um orçamento, denomine-a soma_do_orcamento e não só,xpto ou algo que o valha. Fica muito mais fácil de entender e é uma maior garantia de que, meses depois você(ou outra pessoa) se localize com mais facilidade (o próprio nome da variável passa a fazer parte dadocumentação).

Como veremos, C é uma linguagem de escrita compacta, o que facilita a sua digitação mas pode levar aescrevermos programas incompreensíveis se não formos óbvios sempre que pudermos. Hoje o que mais valenão é o programador cheio de truques e malandragens de programação mas aquele que prefere construirprogramas claros e de fácil visualização. Isto facilita a manutenção do programa e permite uma produção maisconstante, ou seja, se você programa por hobby ou em desenvolvimento de projetos científicos, a atitude de sersimples e claro lhe trará menos trabalho nos seus projetos. Caso você seja um profissional de produção desoftware, isto significará menor dispêndio de tempo e, consequentemente, maior eficiência.

Departamento de Ciência da Computação-UFF

Números de página

Palavras-chave

C será discutido em duas partes: as palavras-chave e as funções. As primeiras são palavras reservadas,ou seja, identificadores que não podem ser usados pelo usuário. São identificadores que são ou constituem partesde comandos ou declarações da linguagem. Quanto às funções, nada podemos fazer de prático em C sem elas.Apesar disto, inicialmente só falaremos das palavras-chave.

As palavras-chave são bem poucas, se compararmos com o número de comandos de linguagens comoPASCAL e abaixo temos a relação de todas elas.

PALAVRAS-CHAVE

auto double int structbreak else long switchcase enum@ register typedefchar extern return unionconst@ float short unsignedcontinue for signed@ void@default goto sizeof volatile@do if static while

@ extensões ANSI

As palavras marcadas com @ são conhecidas como extensões ANSI e as demais são as palavras padrão K&R.Variando de compilador para compilador, podemos ter algumas palavras extras.

Atenção: O fato de usar palavras-chave fora do padrão, poderá criar problemas caso você queira que umdeterminado programa construido por você possa ser levado de um compilador para outro ou mesmo paraoutra máquina. A esta característica de podermos migrar um programa com um mínimo de modificações échamada de Portabilidade. Se você pretende fazer com que seu programa seja o mais portável possível, evite ouso de palavras-chave diferentes das acima. Além disto, alguns compiladores mais antigos não aceitam asextensões ANSI. Um bom conhecimento da documentação de seu compilador é vital.

Toda palavra-chave em C se escreve em minúsculas, como se pode notar, mas o importante é chamarnovamente a atenção que C distingue duas palavras iguais mas escritas uma em maiúsculas e outra emminúsculas, o que a maioria das linguagens não faz. Isto será fonte de erros no início dos seus trabalhos com C.

Outra fonte de erros surgirá principalmente para os de imaginação mais fértil e que gostam de fazerparalelos entre linguagens, confiando em aparentes semelhanças. A maior parte das vezes, as dificuldades deentender C tem origem exatamente devido a suposições do tipo:

"Este comando é "igual" a daquela outra linguagem e, portanto, deve funcionar de maneira parecida."

Toda vez que você pensar coisas deste tipo, REPRIMA! Muitas semelhanças são apenas aparentes.Portanto, tenha em mente o lema:

C se parece apenas com C.

Departamento de Ciência da Computação-UFF

Números de página

Tipos de Variáveis

Passemos a mais um ponto fundamental: os tipos de variáveis. Os tipos disponíveis ao programadorcom a gama de valores correspondentes estão relacionadas abaixo :

Variável Tamanho(Bytes) Gamachar 1 [0,255]int 2 [-32768,32767]short int 1 [-128,127]unsigned int 2 [0,65535]long int 4 [-2147483648,2147483647]

Variável Tamanho(Bytes) Gama Algarismos sig.float 4 10 6 Double 8 12

OBS: Os valores dados para os tipos float e double, poderão tomar valores negativos nos respectivos domínios.É comum que se dispense a palavra int depois de short, unsigned e long. Observe que aqui temos váriaspalavras-chave. Temos além destas algumas que atuam sobre o tipo da variável e elas são listadas abaixo e quechamaremos aqui de modificadores :

Modificadores

Aqui chamamos de modificadores à palavras reservadas que modificam certas características de tiposde variável definidas anteriormente

auto signed@extern static

register volatile@

@ extensões ANSI

Apesar de termos vários tipos de variáveis, C permite muitas liberdades no trabalho destas. Se aoperação tem significado ou não, isto é deixado a você, o programador. Chamamos a atenção que o tipo inteirobásico em C é geralmente o de dois bytes, embora isto possa variar (Não se esqueça sempre verifique adocumentação de seu compilador). Ainda temos a dizer que na definição padrão de C, todas as operações deponto flutuante são executadas em double. É bom frizar isto, já que em programas que usem pesadamente destetipo de operação, o processamento do mesmo será mais lento do que se fosse feito em linguagens que trabalhemcom variáveis de ponto flutuante de quatro bytes, por razões óbvias. No entanto, muitos compiladores permitemque você use menor precisão como uma opção o que acelerará a execução embora com detrimento da precisão.

Departamento de Ciência da Computação-UFF

Números de página

OPERADORES

Temos, além das palavras-chave, a definição de operadores de C. E eles são abundantes:

Operador de atribuição '=' Ex.: a = b

À variável a é atribuido o valor de b. No caso de atribuição de caracteres, o caracter deverá estar entreaspas simples. Ex.: a = 'b'.

Operador vírgula ,

Este operador serve para separar várias operações a serem executadas dentro de parênteses. Se asequência de operações é atribuida a uma variável, o valor atribuido a variável será a da última expressão entreparênteses.

Ex : Seja a = 5 e b = 7, então a expressão

c = (a-b, b * 2)

fará que c tome o valor 14. É comum que encontremos este operador colocado separando operações dentro da declaração for, que

veremos abaixo.

Operadores aritméticos

Suporemos aqui que b e c são inteiros e respectivamente iguais a 7 e 5.

* multiplicação Ex. : a = b * c (a = 35) / divisão Ex. : a = b / c (a = 1) % modulo Ex. : a = b % c (a = 2) + adição Ex. : a = b + c (a = 12) - subtração Ex. : a = b - c (a = 2)

Como a maioria das linguagens de programação, C tem vários operadores sobrecarregados, ou seja,um mesmo operador atua sobre vários tipos. Os operadores de multiplicação, divisão, soma e subtração atuamindiscriminadamente sobre double, float, int e suas modificações. Note que o operador de módulo também ésobrecarregado pois vale para qualquer inteiro.

Observe ainda que não existe em C um operador de potenciação. Isto não é um fato exclusivo destalinguagem. Por exemplo, Pascal tem esta mesma ausência. Também é bom observar (embora seja óbvio) que ooperador % só tem sentido para operandos inteiros.

Operadores de incremento e de decremento

++ incremento Ex. : a = b++ ou a = ++b (resultados diferentes!) -- decremento Ex. : a = b-- ou a = --b (resultados diferentes!)

Para clarear o que ocorre, trabalhemos inicialmente somente com o operador de incremento. Aocolocarmos o operador antes do identificador da variável, primeiro a variável será incrementada e então estevalor será usado em outras operações na linha onde se encontra. Caso o operador estiver depois do identificador,então este valor será usado e só depois haverá o incremento. Vamos exemplificar o funcionamento destesoperadores com o caso abaixo onde temos uma variável com um determinado valor e esta, com o uso dosoperadores acima, é atribuida a outra variável. O valor da variável é dado ao lado de cada caso.

Para b = 5

a = b++ (a = 5)a = ++b (a = 6)a = b-- (a = 5)a = --b (a = 4)

Obs: Chamemos a atenção sobre uma questão de nomenclatura. Vamos tomar como exemplo o operador deincremento. Se o operador é colocado antes da variável (por exemplo, ++b) dizemos estar fazendo um pré-incremento. Se o operador está depois da variável (b++) dizemos então que fazemos um pós-incremento. Vale oanálogo para o operador de decremento.

Departamento de Ciência da Computação-UFF

Números de página

Operadores de endereço

Estes aqui serão meramente apresentados. A maneira de utilizar cada um será vista em detalhe nodecorrer do texto.

& "o endereço de" . Ex. : a = &b. a recebe o endereço da variável b. Vide ponteiros.

* "no endereço de". Ex. : c = *d. c recebe o valor da variável de endereço dado por d. Vide ponteiros.

[ ] "no endereço de ... mais um índice" . Ex. : c = a[1]. c recebe o elemento de a de índice 1. Videvetores.

."elemento da estrutura" (operador ponto). Vide estruturas.

-> "elemento da estrutura apontada por" (operador seta). Vide estruturas.

Operadores de Bit

Estes operadores trabalham a nível de bits. Tais operadores são necessários a uma linguagem quepretende substituir o assembler em projetos de software.

Para ilustrar, suporemos aqui que a e b são inteiros e respectivamente iguais a 5 e 6, ou seja, 00000101e 00000110 em base binária).

<< desloca para a esquerda. Ex. : c = a<<2 ( 20) (00010100)>> desloca para a direita. Ex. : c = a>>1 ( 2) (00000010)& E lógico Ex. : c = a & b ( 4) (00000100)| OU lógico Ex. : c = a | b (7) (00000111)^ OU Exclusivo Ex. : c = a ^ b (c = 3) (00000011)~ NAO Ex.: c = ~b (c = -32761) (11111001)

Os operadores de deslocamento à direita e a esquerda, deslocam os bits dentro das variáveis nestas direções.Estas operações, no caso de atuar sobre inteiros, é equivalente a multiplicar (deslocamento à esquerda) ou adividir (deslocamento à direita) por uma potência de dois. OBS.: Observe que o operador & aparentemente faz dois serviços: um como operador de endereços e outrocomo E lógico. A diferença está que no caso do operador de endereço, ele atua somente sobre um elemento(operador unário), enquanto no caso de ser o operador E lógico, ele precisa de dois elementos para obter umresultado (operador binário).

Operadores lógicos

Em C não temos variáveis do tipo lógico e a definição do verdadeiro lógico é dada por qualquer valordiferente de zero. Obviamente o falso lógico é o valor zero. Portanto estes operadores trabalharão sobrequaisquer variáveis dando uma resposta correspondente a condição examinada.

&& E lógico Ex. : condição1 && condição2.|| OU lógico Ex. : condição1 || condição2.! NAO lógico Ex. : !condição1.

Departamento de Ciência da Computação-UFF

Números de página

Operadores relacionais

Os operadores abaixo trabalham fazendo comparações entre dois valores devolvendo como resultadoum falso ou um verdadeiro.

> "maior que". Ex. : a > b < "menor que" Ex. : a < b == "igual a". Ex. : a == b >= "maior ou igual a". Ex. : a >= b <= "menor ou igual a". Ex. : a <= b != "não igual a". Ex. : a != b

OBS.: I) Não confunda o operador == (igual a) com o operador = (de atribuição).II) Lembre-se novamente que o verdadeiro lógico é qualquer valor diferente de zero e o falso é o zero.

Não confunda os operadores acima com os operadores de bit.

Departamento de Ciência da Computação-UFF

Números de página

ABREVIAÇÕES

Outro ponto interessante de C, é que podemos abreviar algumas expressões. Por exemplo, se temosalgo do formato

<variável1> = <variável1> <operador> <expressão>

podemos sempre escrever

<variável1> < operador> = <expressão>

Exemplos:

x += u é equivalente a x = x + ux += delta * b é equivalente a x = x + delta * ba >>= b é equivalente a a = a >> ba &= b é equivalente a a = a & b

É óbvio que estas abreviações podem confundir quem se inicia na linguagem ou tem poucoconhecimento da mesma. Tome os devidos cuidados, então.

PRECEDÊNCIA DOS OPERADORES

Abaixo temos a precedência de cada operador dentro de cada grupo e no caso geral.

OPERADORES ARITMÉTICOS

1) ++, --2) - (UNÁRIO)3) * , /, %4) +, -

OPERADORES RELACIONAIS E LÓGICOS

1) !2) > , >=, <, <=3) ==, !=4) &&5) ||

OPERADORES DE ENDEREÇO

1) ., ->2) *, &

Departamento de Ciência da Computação-UFF

Números de página

PRECEDÊNCIA GERAL

1)( ) Chamada de função[ ] Elemento de matriz-> Ponteiro para membro de estrutura. Membro de estrutura2) ! ~++--- Unário(tipo) Moldagem* "no endereço de"& "o endereço de"sizeof3) * Multiplicação/%4) +-5) <<>>6) <<=>=>7) ==!=8) & E de bit9) ^ OU Exclusivo de bit10) | OU de bit11) &&12) ||13) ?:14) =*= /= %= += -= <<= >>= &= ^= |=15) ,

Mudança de Precedência

A precedência poderá ser mudada pelo uso de expressões entre parênteses, tendo os parênteses maisinternos maior prioridade que os demais.

OBS. : Lembre-se: é preferível colocar parênteses sobrando para clarear o que ocorre do que permitirinterpretações dúbias.

Departamento de Ciência da Computação-UFF

Números de página

CONVERSÕES DE TIPO E CONVERSÕES FORÇADAS (cast, ou moldagem)

Agora que já falamos de tipos e operações, devemos nos preocupar com o uso de operações com tiposmistos, por exemplo, calcular o produto de uma variável inteira com uma de ponto flutuante e este resultado sercolocado numa variável de precisão dupla.

Há algumas conversões que são feitas automaticamente. Em operações mistas odos os char e short intsão convertidos em int. Todos os float são convertidos em double. Sempre a conversão entre pares deoperandos é feita para o tipo do maior operando, ou seja, operando-se um double e um int, temos comoresultado um double e se operamos um long e um int, temos um long como resultado. O que queremos dizercomo maior é o tipo que puder representar o maior número. Portanto, operando um inteiro longo e um númerode ponto flutuante, ambos de 4 bytes de comprimento, teremos a conversão do resultado em um número deponto flutuante. No entanto, nem sempre o resultado que temos é o esperado.

Algumas vezes pode ser interessante forçar uma conversão de tipo. Podemos fazer isto escrevendo,entre parênteses, o tipo ao qual queremos converter a variável a esquerda. Este construtor é denominado Cast ouainda Moldagem. Vamos a um exemplo. Suponha que i e j sejam duas variáveis inteiras de dois bytes (tipo int)e x uma variável de ponto flutuante (tipo float). Poderíamos escrever o que se segue

i = (int)x /j

Teríamos a divisão de x por j, mas não o ponto flutuante x mas o valor convertido para inteiro. A moldagem pode ser também parte da documentação. Mesmo que saibamos que determinadas

conversões serão feitas automaticamente, é sempre bom indicar explicitamente através da moldagem as nossas“intenções”. Isto muitas vezes facilita a compreenção do programa.

Ainda devemos tomar cuidado ao forçar certas conversões. O resultado pode não só ser inútil comogerador de dores de cabeça, caso seja feita de forma pouco cuidadosa.

Departamento de Ciência da Computação-UFF

Números de página

CONSTANTES

C tem uma notação especial para constantes de forma a ficar claro o seu tipo. Caso seja atribuído a umavariável um número de ponto flutuante, este será convertido primeiramente para double, seja a variável tipofloat ou não. Outras vezes é mais prático, por motivo de documentação, escrever uma constante em basehexadecimal do que em base decimal. Para evitar conversões automáticas desnecessárias (algumas vezesprejudiciais) além de facilitar a documentação, os criadores desta linguagem adotaram o padrão de colocar apóso último algarismo uma letra indicando o tipo de constante. Usa-se F para indicar que a constante é tipo float, Lpara indicar que é do tipo long e U para unsigned. No caso dos números hexadecimais, usa-se 0x como prefixo.Pode-se usar tanto letras maiúsculas quanto minúsculas. Podemos ainda fazer combinações com estesmodificadores. Portanto, teríamos que

100F é o número 100 de ponto flutuante120000L é o número 120000 inteiro longo65000U é um número inteiro não assinalado13500UL é um número inteiro longo não assinalado0X130 é o número 130 na base hexadecimal

Não custa relembrar que se não houver nenhuma declaração que o negue, as constantes são do tipo int.Além destas há uma série de constantes pré-definidas (usualmente chamadas Sequências Escape ou de

Fuga) que são caracteres de controle de dispositivos como impressoras, monitores, etc. Abaixo temos estasconstantes.

\a Alerta(bell)\b Retrocesso\f Alimentação de formulário\t Tabulação horizontal\v Tabulação vertical\\ Barra invertida\? Interrogação\' Apóstrofo\o Número octal\x Número Hexadecimal

Departamento de Ciência da Computação-UFF

Números de página

COMANDOS

Um comando é uma expressão válida seguida de um ponto-e-vírgula. Uma expressão válida por suavez é um conjunto de variáveis e operadores colocados de forma coerente. Para dar sentido mais claro a estaspalavras vamos a alguns exemplos de comandos válidos :

a = b; (É atribuido à variável a o valor contido em b) a = a + c * d; (a recebe o produto de c por d e mais o próprio valor corrente) ; (Comando nulo)

Para comandos não válidos teremos :

a = c (falta o ponto-e-vírgula) a = a ^& b; (^& não é um operador) a = 1z3 * b; (variável inválida)

BLOCOS

Um bloco é definido como um conjunto de comandos logicamente conectados. Ou seja, eles sãotrabalhados como se fossem um único comando. A indicação de um bloco é determinada por um abre chave noseu início e um fecha chave ao seu final, ou seja,

{ COMANDO 1 COMANDO 2 COMANDO 3 . . . COMANDO n }

Pela definição, um bloco pode conter blocos.

Departamento de Ciência da Computação-UFF

Números de página

CONTROLE DE FLUXO

Temos aqui as formas de controle de fluxo em C.

a) if (CONDIÇÃO) COMANDO1 else COMANDO2

Se CONDIÇÃO for verdadeira (diferente de “zero”), COMANDO1 será executada. Caso seja falsa (ouseja, igual a “zero”), então COMANDO2 será executada.

Atenção: O else é opcional.

b) ? :

É uma simplificação da declaração if. Exemplifiquemos seu uso :

var = expr1 ? expr2 : expr3

Se a expressão 1 for verdadeira, então é avaliada a expressão 2, senão é avaliada a expressão 3 e oresultado, num caso ou no outro, será atribuído à variável var.

c) switch , default e case

Estas declarações trabalham juntas, sendo que default é opcional.

switch (EXPRESSÃO) { case constante1 : COMANDO case constante2 : COMANDO . . . case constante n : COMANDO default : COMANDO }

Quando uma opção é tomada, a declaração correspondente é executada e TODAS as instruções abaixotambém o serão. Para que tal fato não ocorra, é necessário terminar cada bloco com um break, palavra queveremos mais abaixo. Caso o valor que EXPRESSÃO tomar não seja igual a nenhuma das constantes após apalavra case, então a declaração que estiver associada a palavra default será executada. Caso ela não for usada,haverá interrupção do laço.

Atenção: Observe o fato que os blocos de instruções na declaração switch, depois do case, dispensarem o abree fecha colchetes. Pela própria natureza da execução, não há a necessidade desta explicitação. Muitosprogramadores, por questão de padronização, colocam abaixo de cada case um par de chaves definindo umbloco mesmo este não sendo necessário. Como sempre, faça o que você achar mais claro.

Departamento de Ciência da Computação-UFF

Números de página

d) goto

É uma declaração comum em outras linguagens, mas que deve ser evitada sempre que possível, poisgeralmente dificulta o entendimento do programa. Dificilmente você encontrará um bom programador usando-aa não ser em último caso. Há situações nas quais esta declaração, ao contrário do habitual, facilita acompreensão do programa, embora sejam muito raras.

Para a sua utilização, é necessário a indicação de um rótulo que é um identificador seguido de : (doispontos).

.

.goto ok;..ok : ....;.Aqui não veremos exemplos com goto. Possivelmente você verá seu emprego muito raramente se é que

algum dia você verá.

OBS.: Embora, a estrutura básica do if e do switch-case se assemelhe a de outras linguagens, observebem as diferenças.

Departamento de Ciência da Computação-UFF

Números de página

LAÇOS

Como toda linguagem, C tem uma série de instruções que executam laços controlados. Novamente,tome cuidado com semelhanças.

a) while (EXPRESSÃO) COMANDO

Enquanto a expressão entre parênteses for verdadeira (diferente de zero) COMANDO é executado.

b) do COMANDO while (EXPRESSÃO);

Funciona de maneira semelhante ao ítem "a". A diferença é que aqui a decisão de parada é após aexecução da EXPRESSÃO.

c) for (EXPRESSÃO; CONDIÇÃO; EXPRESSÃO) COMANDO

Esta declaração é provavelmente conhecida por todos, por aparecer em várias linguagens. É comumque a EXPRESSÃO1 seja uma inicialização de uma variável, CONDIÇÃO a condição de parada eEXPRESSÃO2 o modificador do valor da variável mas poderemos ter coisas mais complicadas e interessantes.

Este laço é equivalente a um laço while com a seguinte estrutura :

expressao1;while(condição){comandoexpressão2;}

Alguns programadores mais radicais recomendam o "esquecimento" para o for. Não chegamos a tanto.

Uma ressalva importante é que CONDIÇÃO é avaliada no início do laço, como podemos observar daequivalência mostrada acima. O esquecimento deste fato pode levar a ações inesperadas.

Para os que acham que “todo for é igual”, daremos um exemplo das particularidades deste laço. Aexpressão abaixo é válida em C

for (; ; ) COMANDO

e define um laço sem fim.

Palavras Auxiliares

a) break;

É um comando auxiliar no controle de laços. Se você fizer uma construção com vários laços embutidos,ao encontrar esta palavra chave o programa interrompe o laço no qual se encontra e salta para o imediatamenteexterno.

b) continue;

Ao encontrar este comando, o programa saltará diretamente para a condição do laço, desconsiderandoos comandos posteriores a ela. No caso de usado num for, ao encontrar um continue, o controle passará para adeclaração 2 dentro dos parênteses do for. Só pode ser usado dentro de laços.

Departamento de Ciência da Computação-UFF

Números de página

Fatos e mitos - Velocidade de processamento

Como já foi dito acima, C é uma linguagem que gera programas de alta velocidade de processamento.Algumas pessoas já usaram C e acham que isto é uma balela já que "Na linguagem xxx um programa meu foiexecutado mais rapidamente que em C."

Um fato que devemos desde já chamar a atenção com ênfase é que C faz todas as suas operações deponto flutuante com o tipo double se você não obrigar que estas sejam feitas com outra precisão. Obviamenteisto gera duas consequências: Maior precisão no cálculo e maior tempo de execução.

C é uma linguagem que gera programas rápidos, continuo insistindo, mas a única maneira de se fazerum programa o mais rápido possível é o conhecimento não só da linguagem na qual programamos comotambém da implementação particular do compilador que usamos e o computador no qual estamos trabalhando.Haverá situações em que um programa em C será mais lento que o mesmo programa em alguma outralinguagem. Isto obviamente não é nenhum demérito. Se C ou outra linguagem qualquer solucionasse todos osproblemas de programação, é claro que não haveria a profusão de linguagens que existe, cada uma maisadaptada a determinadas tarefas. Devemos ter em mente as vantagens e limitações de cada linguagem e aadequação dela ao problema que queremos resolver. Para evitar problemas e tomar uma atitude mais realista emadura quanto à programar lembre-se da Lei de Murphy[ ] aplicada à computação:

Se você quer fazer uma besteira é fácil, mas se quiser fazer uma grande besteira, você vai precisar de um computador.

Departamento de Ciência da Computação-UFF

Números de página

ESTRUTURAS DE PROGRAMAS EM C: A FUNÇÃO main()

Um programa em C é uma chamada de função, no caso uma função especial de nome main(), a qualchamaremos de função principal. Ela é a única função obrigatória de existir num programa em C e inicialmentetrabalharemos apenas com ela. O mais simples programa em C, contendo apenas a função main() (mas que nãofuncionará!), tem a forma que se segue

main(){DECLARAÇÃO DAS VARIÁVEIS

COMANDO1COMANDO2..COMANDOn}

Todas as variáveis contidas no programa deverão ser declaradas.Como toda linguagem de programação, em C você poderá também inserir comentários. Isto é feito

colocando em qualquer coluna uma barra seguida de um asterisco depois escrevendo seu comentário e então, aoacaba-lo, teclando outro asterisco seguido de uma barra, ou seja,

/* comentario */

Obs: Não é permitido o aninhamento de comentários no ANSI C mas outras versões poderão permitir. Emalguns momentos o aninhamento de comentários é cômoda mas muitas vezes (dependendo da forma deimplementação) pode ser fonte de erros bobos.

Escrevamos o nosso primeiro programa que: a)Some dois números inteiros e coloque o valor da soma num outro número inteiro.b)Se a soma for maior que 9, faça a variável que contém a soma, ser igual a 9.

main(){ /* Primeiro programa */

int a, b, c;

a = 5;b = 3;

c = a + b;

if ( c > 9 ) { c = 9; }}

Temos acima a declaração das variáveis a, b e c como inteiras seguida da atribuição de valores e então sua somasendo atribuida a variável c. Logo após temos o teste do valor da soma. Como a = 5 e b = 3, temos que oresultado final será c = 8. Portanto, depois do teste, c continuará com o valor 8.

Para marcar bem a diferença entre o operador de atribuição = em C e em outras linguagens, vamosreescrever este programa.

Departamento de Ciência da Computação-UFF

Números de página

main() { /* Primeiro programa, segunda versão */

int a = 5, b = 3, c;

if ( (c = a + b) > 9 ) c = 9; }

O operador de atribuição é usado para inicializar as variáveis no momento de sua definição. Dentro dosparênteses do if, há a atribuição do valor da soma de a com b à variável c. Só após feito isto é que haverá acomparação e avaliação da condição. Esta maneira de usar o operador de atribuição pode parecer estranhainicialmente mas, indiscutivelmente, tem o seu charme. Observe ainda que como só temos um comando para oif , não foi criado um bloco.

Uma nova versão pode ser feita, agora com o uso do operador ? :

main() { /* primeiro programa, terceira versão */

int a = 5, b = 3, c;

c = (c = a + b) > 9 ? 9 : c; }

O resultado é idêntico. Aqui usamos o operador ? : no lugar do if. Este operador trabalha, no programaacima, da seguinte maneira:

Inicialmente as variáveis a e b são somadas e depois o resultado é atribuido à variável c. Verifica-se seo resultado é maior que 9. Se for, a c é atribuido o valor 9. Se não, a c é atribuido o próprio c, que já contém asoma de a e b. Observe os parênteses obrigando que a atribuição da soma seja feita antes do teste.

Façamos mais um programa. Queremos somar um número de ponto flutuante de precisão simples a elemesmo, 10 vezes enquanto ele for menor que 2000. Ao final devemos ter uma variável inteira que contenhaquantas vezes o processo de soma foi feito.

main(){ /* Segundo programa */

int n;float x;

x = 235.;

for (n = 1; n <= 10; n++) { x = x + x; if ( x >= 2000.0) break; }}Observe o uso do operador de incremento na sua versão de pós-incremento dentro do for. Lembre-se: queremosdizer com esta expressão que o valor da variável será usada e só então haverá o incremento. Note ainda o uso dobreak para interromper o laço do for. Como já foi descrito, no momento que a palavra-chave break forencontrada, o laço, dentro do qual está o break, será interrompido.

Vamos reescrever o programa.

Departamento de Ciência da Computação-UFF

Números de página

main(){ /* Segundo programa, segunda versão */

int n;float x;

x = 235.;n = 0;

do { x += x; n++; }while ( (x < 2000.) && (n < 10));}

Aqui temos o mesmo programa só que agora a saida é feita através de uma condição composta. Sãotestadas as condições de parada de tal forma que se pelo menos uma das duas for falsa haverá a interrupção daexecução. Lembre-se que o while continua sendo executado enquanto a expressão dentro dos parênteses forverdadeira. Temos ainda o uso do operador de pós-incremento usado isoladamente e também a utilização danotação abreviada para o cálculo de x. Observe bem as diferenças entre as duas versões não esquecendo que sãototalmente equivalentes, embora o for ter uma equivalência com o while e não com o do-while.

Vamos a mais um programa. Temos uma variável do tipo caracter (char) e vamos provocar asseguintes reações :a) Se for um 'a' ou um 'b', uma variável inteira tomará o valor 1; b) Se for um 'c', valor 2;c) Se for um 'd', 3;d) Se for um 'e', o valor 4.

main(){ /* Terceiro programa */int numero;char letra;

if ((letra == 'a') || (letra == 'b')) { numero = 1; }else { if (letra == 'c') { numero = 2; } else { if (letra == 'd') { numero = 3; } else { if (letra == 'e') { numero = 4; } } } }}

Departamento de Ciência da Computação-UFF

Números de página

Observe mais uma condição composta no primeiro if. Neste caso basta uma das duas expressões seremverdadeiras para que a variável numero tome o valor 1. Embora interessante como exemplo de uso de if, oprograma não é um bom exemplo de programação.

Reescrevendo este programa (como está se tornando hábito) temos:

main(){int numero;char letra;

switch (letra) { case 'a' : numero = 1; break; case 'b' : numero = 1; break; case 'c' : numero = 2; break; case 'd' : numero = 3; break; case 'e' : numero = 4; }}

Aqui substituimos uma sequência de if's por uma declaração do tipo switch-case. A função do break,chamamos novamente a atenção, é interromper a execução do bloco do switch.

Nada nos impede de reescrevermos o programa como o que se segue:

main(){ /* Terceiro programa, terceira vercao */

int numero;char letra;

switch (letra) { case 'a' : case 'b' : numero = 1; break; case 'c' : numero = 2; break; case 'd' : numero = 3; break; case 'e' : numero = 4; }}

Como o valor tomado pela variável numero é igual para os casos do caracter ser a ou b, deixamos semnenhuma declaração após os :. No fluxo do programa, caso o caracter seja um a, automaticamente teremoscomo execução a atribuição do valor 1 a variável numero seguida da execução do break, que interromperá oswitch-case.

A maneira prolixa da primeira versão (com if) foi para dramatizar as vantagens do uso do swith-case.

Departamento de Ciência da Computação-UFF

Números de página

Vamos a mais um exemplo. Agora descreveremos um problema a partir de sequência de instruções, ouseja, de forma algorítmica. No caso será o algoritmo de Euclides para calcular o MDC, ou seja, o MáximoDivisor Comum (quem diria, voltamos à escolinha!). O algoritmo é dado a seguir :

i) Sejam dois números inteiros e positivos m e n dos quais queremos achar o mdc. Seja ainda umavariável r.

ii) Faça r tomar o valor do resto da divisão de m por n, fazendo então m tomar o valor de n e esteúltimo o valor de r.

iii) Se r não for nulo, volte a ii). Caso r for nulo, m terá o valor do mdc.

O programa que faz tais operações afim de calcularmos o MDC é dado abaixo :

main(){/* programa que calcula o mdc de dois numeros m e n */

int m = 120, n = 9, r;

do { r = m % n; m = n; n = r; }while(r != 0);}

Usamos aqui mais um operador aritmético, o %, que dá o resto da divisão de dois números. No whilepoderiamos usar no lugar da expressão r != 0 algo como !r. Funcionaria do mesmo jeito com uma aparentevantagem de ser mais compacto sendo, no entanto, mais enigmático. Aqui se apostou em ser claro e, sempre quepossível, esta deve ser uma tática de programação.

Departamento de Ciência da Computação-UFF

Números de página

Indentação

Repare que em todos os programas já exibidos, cada palavra reservada, cada variável, não é escrita aesmo mas obedece uma regra de posicionamento em cada linha. Esta técnica geralmente é chamada deindentação e é usada em todas linguagens ditas estruturadas. A idéia por trás de tal comportamento não é sófazer programas bonitinhos mas sim, fazer programas mais legíveis. C já é uma linguagem muito concisa e secomecamos a teclar cada linha de qualquer jeito, teremos problemas de legibilidade que poderão dificultar adepuração e manutenção de programas e sistemas. Notem ainda que são colocados espaços entre variáveis eoperadores e espaço entre as linhas em muitas situações.

O estilo usado aqui é simplesmente um estilo, existindo maneiras diferentes de organizar as linhas deum programa. A linguagem C permite esta liberdade no estilo da escrita de tal forma que podemos modificarligeiramente a forma de escrever um programa da maneira que mais nos agradar. Os programas estão escritosneste texto de forma relativamente comum não sendo, obviamente, A Maneira Correta mas apenas (repito) umamaneira de se escrever programas em C. É comum se usar de 2 à 5 espaços para marcar a indentação mas istopode variar de acordo com seu próprio estilo.

Por exemplo, podemos escrever uma declaração if como

if ( a > b) { c = a + b; d = a * c;.}else { c = a - b; d = a + c * b;}

if (a > b) { c = a + b; d = a * c;{else{ c = a + b; d = a * c;}

if (a > b) { c = a + b; d = a * c; }else { c = a + b; d = a * c; }

ou outra maneira qualquer.No entanto, seja lá qual estilo você adotar, tenha em mente melhorar a legibilidade do programa. Para

perceber o que queremos dizer, vamos a um caso extremo: o programa do MDC poderia ser escrito como abaixo

main(){int m=120,n=9,r;do{r=m%n;m=n;n=r;}while(r!=0);}

As vantagens de escrever assim são ainda desconhecidas mas alguns insistem.....

Departamento de Ciência da Computação-UFF

Números de página

FUNÇÕES

Forma Clássica

Até o momento, sobre funções só foi dito que existiam e que main() era uma função. Agora falaremosde funções de forma mais genérica. Em C as funções podem ser escritas da seguinte forma, (chamada formaclássica)

TIPO nome_da_função(parâmetro 1,parâmetro 2,...,parâmetro n)

DECLARAÇÃO DE PARÂMETROS

{DECLARAÇÃO DE VARIÁVEIS

COMANDO1COMANDO2

.

.COMANDOn

return(parâmetro de saida);}

onde TIPO diz respeito ao tipo de variável que será devolvida pela função. Observe mais uma palavra chave,return. Podemos encontrar esta palavra chave sendo usada de duas maneiras: com o parâmetro de saida entreparênteses e com o mesmo parâmetro logo após o return. Esta palavra é optativa, não havendo necessidade deaparecer em nenhum ponto do bloco principal da função. Também podemos ter vários return dentro de umafunção embora isto não seja recomendável.

A semelhança da definição de uma função com um programa em C é por uma razão óbvia: o programaprincipal de C é uma função, como já foi dito. Dependendo da forma de definição de função, você pode colocara declaração de função antes ou depois da função main(). Aqui colocaremos as funções antes, de forma que umprograma em C pode ter a seguinte estrutura (mas ainda não funcionará!):

TIPO nome_da_função(parâmetro 1,parâmetro 2,...,parâmetro n)

DECLARAÇÃO DE PARÂMETROS { DECLARAÇÃO DE VARIÁVEIS COMANDOS }

main() { DECLARAÇÃO DAS VARIÁVEIS COMANDOS }

É bom chamar a atenção para que as variáveis declaradas dentro das funções são "invisíveis" para oprograma principal ou para as outras funções (daqui a pouco falaremos mais sobre esta "invisibilidade").

As funções sempre devolvem algum valor, mesmo que não haja um return. Se este valor temsignificado ou não, quem decide é o programador. Além disto, não há obrigatoriedade de usarmos o valorretornado. Quanto aos argumentos, eles são passados por valor e não por referência. Isto significa que osvalores dados através de variáveis do programa principal não serão modificados no programa principal mesmoque você as manipule dentro da função. Se tiver como parâmetros, por exemplo, x e y que valemrespectivamente 2 e 3, se houver uma multiplicação entre um e outro atribuindo a x o resultado dentro dafunção, veremos que o valor de x continuará igual a 2 no programa principal.

Isto aparentemente limita o uso mas, como veremos, este fato não se trata de uma limitação mas apenasuma maneira de termos, explicitamente, conhecimento do alcance das operações que serão feitas através dasfunções.

Departamento de Ciência da Computação-UFF

Números de página

Como exemplo faremos do primeiro programa uma função a qual chamaremos tetof().

int tetof(a,b,teto)

int a,b,teto; { int c;

if ( (c = a + b) > teto) c = teto; return(c); }

main (){int a = 5, b = 3, c, tetof();

/* Primeiro programa, quarta vercao */

c = tetof(a, b, 9);}

Novamente o funcionamento é igual.

Outro detalhe a saber é que se nada for indicado, ficará pressuposto que a função devolverá um inteiro.A razão disto já sabemos: C trabalha preferencialmente com variáveis do tipo inteiro. Portanto se nada forespecificado quanto ao tipo de valor que uma função devolverá, se considerará que o resultado será um inteiro.Se a função devolver um valor não compatível com inteiro, você precisa avisar a função que a chama, o tipo deresultado que ela irá devolver. Isto é feito junto ao definir a função e com a declaração de variáveis. Vamosdizer que uma função é do tipo char e seu nome é funcao1(). Então, na função que a chama deve haver junto, adeclaração de variáveis, o que se segue:

char funcao1();

Observe que não é necessário colocar os parâmetros da função mas é necessário colocar os parênteses para queo compilador saiba que é uma definição do tipo de valor devolvido pela função e não a declaração de umavariável.

Como mais um exemplo, daremos uma função que calcula o fatorial de um número. Lembre-se que o fatorial de um número inteiro positivo não nulo é o produto dele por todos os númerospositivos não nulos menores que ele.

int fatorial(n) int n; { int i, fat; for (i = 1; i <= n; i++) { fat = fat * i; }

return(fat); }

Departamento de Ciência da Computação-UFF

Números de página

main() { int fatorial(), n = 5, f;

f = fatorial(n); }

Repare que na lista de declaração de variáveis encontramos fatorial() colocado como outra variávelqualquer. Mas lembre-se, o que queremos dizer é que o valor retornado é inteiro.

Chamemos a atenção para alguns aspectos sobre funções em C. Ao contrário de outras linguagens(Pascal, por exemplo) C não permite aninhamento de funções, ou seja, não admite que tenhamos funçõesdeclaradas dentro de funções. Ainda temos que todas as funções em C são “visíveis” umas pelas outras. Talpropriedade, no jargão habitual, se traduz como:

As funções em C são globais.

EXERCÍCIO

I) O programa acima só calcula corretamente fatoriais até o de 7 (sete). Porque ?

Departamento de Ciência da Computação-UFF

Números de página

VARIÁVEIS GLOBAIS, LOCAIS E AUTOMÁTICAS

Já foi dito que as variáveis declaradas dentro de uma função são "invisíveis" para outras funções.Algumas vezes queremos que uma determinada variável "exista" não só para uma função ou para o programaprincipal, mas para todas as funções constituintes do programa, ou seja, seja de conhecimento GLOBAL.Partindo da nossa definição imperfeita de programa em C (que ainda não funcionará...), apresentamos comodefinir tais variáveis:

DECLARAÇÃO DE VARIÁVEIS GLOBAIS

TIPO nome_da_função(PARÂMETRO 1,PARÂMETRO 2,...,PARÂMETRO n)

DECLARAÇÃO DE PARÂMETROS

{ DECLARAÇÃO DE VARIÁVEIS

DECLARAÇÕES }

main() { DECLARAÇÃO DAS VARIÁVEIS

DECLARAÇÕES }

Aqui devemos dar uma parada para dar nomes aos bois. As variáveis declaradas fora das funções(incluindo a main()) são chamadas GLOBAIS e as declaradas dentro das chaves são chamadas de LOCAIS,DINÂMICAS ou AUTOMÁTICAS. Usaremos aqui o termo LOCAIS. Não há uma palavra reservada paradeclarar variáveis como globais mas existe uma para declarar como automática: ela é auto. Como todas asvariáveis em C são criadas como automáticas, raramente (para não dizer nunca) você verá esta palavra (auto)em uso. Outra coisa a ser dita é que as variáveis globais ocupam espaço na memória o tempo todo enquanto asvariáveis locais só ocupam espaço enquanto a execução do programa estiver dentro dos blocos nos quais estasvariáveis estão declaradas. Quando a execução sai destes blocos, as variáveis locais "evaporam", poupandomemória.

Além disto, é sempre boa política usar o mínimo de variáveis globais, já que estas são ativas para todafunção dentro de um programa podendo, assim, gerar reações inesperadas por ter um trecho de programaalterando uma variável global que em outro trecho era esperada com outro valor. Uso de variáveis globais deforma indiscriminada leva à dificuldades no reaproveitamento de código e dificuldades no acompanhamento doprocessamento de um programa já construido. Os usuários de FORTRAN são particularmente tentados à usarvariáveis globais a todo isntante. Se for o seu caso lembre-se que aqui você está programando em C, pensandoem C e que FORTRAN é outro departamento.

Departamento de Ciência da Computação-UFF

Números de página

PREPROCESSADOR: CABEÇALHOS E ARQUIVOS DE INCLUSÃO

Imagine que você criasse um conjunto de funções que trabalhasse com a tela do computador, outroconjunto de funções que contivesse todas as funções de entrada e saída de dados e outro conjunto e outro e maisoutro. Se você é um programador organizado, talvez achasse interessante que pudesse separar estas funções emcoleções de funções e invocar apenas as coleções que fossem necessárias. Em C podemos fazer isto de maneirasimples: Basta criar arquivos contendo as funções, constantes e definições relativas às mesmas. Assim, umprograma em C poderá ser constituído de uma coleção de vários arquivos.

No entanto, tais arquivos podem depender de definições em comum. Como então fazer com que todosos arquivos compartilhem estas definições? O que temos é um tipo especial de arquivo denominado Arquivo deCabeçalho e é justamente nele que guardamos tais definições. Quanto ao nome deste arquivo, existe aconvenção de usar a extensão .h para marcar o tipo deste arquivo. Se você tem uma vasta biblioteca de funçõespara os mais variados fins (teclado, gráficos, gerenciar arquivos em disco, etc.) poderá invocar apenas as quenecessitar.

Como é que eu invoco tais coleções é uma coisa ainda não dita. Para fazer esta invocação, usaremosuma declaração do chamado preprocessador, que é mais uma parte integrante da linguagem C. Toda declaraçãodo preprocessador é indicada por começar pelo caracter # (“cardinal” ou “velha”). A que veremos aqui é adeclaração #include que se responsabilizará por carregar o cabeçalho invocado, também chamado arquivo deinclusão.

A forma geral deste comando é o que se segue

#include "nome_do_cabecalho.h"

mas poderá ser encontrada também nesta forma

#include <nome_do_cabecalho.h>

A diferença entre um e outro é que o primeiro (entre aspas duplas) o arquivo de inclusão se encontra nodiretório de trabalho corrente, enquanto o segundo (entre < e >) se encontra no diretório padrão onde selocalizam os arquivos de inclusão.

Obs.:I) Um aspecto que devemos chamar a atenção é que dentro de um arquivo de cabeçalho podemos ter

outros #include. O número de aninhamentos destas declarações é dependente de cada compilador. II) Alguns compiladores automaticamente incluem alguns cabeçalhos automaticamente. Antes de ser

uma comodidade isto é um problema. Corremos o risco de ter um programa compilando e funcionando numamáquina e não funcionando em outra. Para evitar vícios e (com isto) problemas futuros, seja explícito sempre econfigure seu compilador para não aceitar “bibliotecas default”.

Com a inclusão do preprocessador, um programa em C pode ter a seguinte forma (que, finalmente,funcionará!):

Departamento de Ciência da Computação-UFF

Números de página

COMANDOS DO PREPROCESSADOR

DECLARAÇÃO DAS VARIÁVEIS GLOBAIS

FUNÇÃO1(PARÂMETROS)

DECLARAÇÃO DE PARÂMETROS { DECLARAÇÃO DAS VARIÁVEIS LOCAIS CORPO DA FUNÇÃO } FUNÇÃO2(PARÂMETROS) ..main() { DECLARAÇÃO DE VARIÁVEIS LOCAIS COMANDOS}

A estrutura acima é muito comum de se encontrar, embora possamos colocar comandos dopreprocessador no meio de um programa.

Falaremos detalhadamente sobre o preprocessador mais adiante.

OBS.: Uma pergunta que deve estar surgindo na cabeça do leitor mais atento é: Se toda função retorna umvalor a função main() devolve o que? E para quem? Um pouco mais a frente teremos estas respostas.

Departamento de Ciência da Computação-UFF

Números de página

ALGUMAS FUNÇÕES ÚTEIS (Da stdio.h)

A linguagem C não tem comandos para impressão. C deixa isto a cargo de funções e aqui vamosaprender como funcionam algumas para podermos trabalhar um pouco mais satisfeitos. Abaixo temos algumasque se encontram em stdio.h, ou seja, entrada e saída padrão (stardard in/out). Primeiro uma que escrevecaracteres na tela :

printf("CONTROLE", PARÂMETRO1,PARÂMETRO2,....,PARÂMETROn);

onde CONTROLE, contém declarações de formato e caracteres. O formato tem as opções:

%c caracter simples %d decimal inteiro %f ponto flutuante %e ponto flutuante em notação científica) %o octal %s cadeia de caracteres %u decimal inteiro sem sinal %x hexadecimal

Há ainda caracteres especiais (alguns já apresentados quando falamos de constantes), alguns dos quais estãolistados abaixo:

\b retrocesso \f saltar página (ou limpar tela) \n saltar para próxima linha \r retorno de carro \t tabulação horizontal \0 nulo\\ Barra reversa

Um exemplo.

printf(" i = %d \n x = %f -- y = %f", i,x,y);

Se i for igual a 1, x igual a 2.345679 e y igual a 3.141582, a saída será da forma :

i = 1x = 2.345679 -- y = 3.141582

Observe: o que não for especificação de formato ou caracter especial é escrito como foi colocado.Os valores impressos são ajustados para a esquerda. Além disto, se passamos um número de ponto

flutuante para esta função, ele sairá com todos os algarismos que o seu tipo permitir. Podemos formatar a saídalimitando o número de casas decimais num ponto flutuante ou posicionando as variáveis de maneira maisconveniente. O formato para ponto flutuante é o que se segue

[-]m.d

onde o sinal - indicaria ajuste a esquerda, m o espaço total e d o número de casa decimais que devem parecer. Osimples fato de querermos formatar a saída, faz com que o ajuste se faça pela direita, daí a necessidade do sinalnegativo para indicarmos que queremos que o ajuste continue pela esquerda.

Isto é feito colocando antes do símbolo de porcentagem um número que corresponderá ao espaço queserá reservado para a variável em questão. No caso de um número de ponto flutuante, podemos dar não só oespaço para o número a ser impresso como também o número de casas decimais depois do ponto decimaladicionando após o número de espaços reservados para o número a ser impresso um ponto seguido do númerode casas decimais. Abaixo temos várias opções de formato escritas a partir do exemplo acima não formatado ecom suas correspondentes saídas.

a) printf("\n i = %10d x = %10f -- y = %10f", i,x,y);

i = 1 x = 2.345679 -- y = 3.141582

Departamento de Ciência da Computação-UFF

Números de página

b) printf("\n i = %-10d x = %-10f -- y = %-10f", i,x,y);

i = 1 x = 2.345679 -- y = 3.141582

c) printf("\n i = %-10.4d x = %-10.4f -- y = %-10.4f", i,x,y);

i = 0001 x = 2.3456 -- y = 3.1415

Outras funções úteis são especificadas abaixo:

a) ch = getch() - Devolve em ch um inteiro sem sinal correspondente ao caracter lido no teclado. (Estafunção se encontra definida em conio.h (complementar in/out))

b) putchar(ch) - Manda um caracter para a tela. c) exit(i) - Interrompe a execução do programa. Se i = 0, indica uma finalização normal.

Façamos mais um programa. Este deverá, inicialmente, escrever na tela um menu com o seguinteconteúdo :

<1> : imprime em formato ASCII<2> : imprime em octal<3> : imprime em decimal<4> : imprime em hexadecimal<Outras> : sai do programa

e após escrever uma mensagem para entrar com um caracter. Feito isto, leia o caracter, imprima no formatopedido e volte ao menu.

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h> #include <conio.h>

main() { do { int ch, opcao;

printf(" <1> : imprime em formato ASCII \n"); /* Impressao do cabecalho */ printf(" <2> : imprime em octal \n"); printf(" <3> : imprime em decimal \n"); printf(" <4> : imprime em hexadecimal \n"); printf("<Outras> : sai \n");

printf(" Entre com uma opção "); opcao = getch(); printf("\n");

printf(" Entre com um caracter "); ch = getch();

switch (opcao) { case '1' : printf("\n ASCII - %c\n",ch); break; case '2' : printf("\n octal - %o\n",ch); break; case '3' : printf("\n decimal - %d\n",ch); break; case '4' : printf("\n hexadecimal - %x\n",ch); break; default : exit(0); } }while(1); }

Observe o uso do while com a expressão de controle permanentemente verdadeira (diferente de zero) e,portanto, criando um laço sem fim. Assim, a única forma de saída do programa é através do apertar de qualquertecla, menos as válidas, pois isto forcaria a execução da função exit() que, como já vimos, interrompe oprograma.

Faça alguns testes e você verá que se você entrar com, por exemplo, uma letra teremos um númerocomo resposta, se pedirmos uma opção diferente da de número 1. Isto não deve causar estranheza pois osformatos de saída do printf(), nas outras opções, são do tipo numérico. Lembramos que ao apertarmos a teclacorrespondente a um caracter ou outra qualquer, não estamos na realidade "mandando" uma letra para ocomputador mas simplesmente um código. Como os caracteres tem nos computadores uma representaçãointerna numérica dada por um determinado padrão, fica fácil de entender os números que vão surgindo duranteo uso deste programa. Você encontrará tabelas de algumas destas representações num dos apêndices deste texto.

Se examinarmos o programa acima, encontraremos um pequeno defeito. Se você quiser sair doprograma o mesmo pedirá para você entrar com um caracter, o que é totalmente desnecessário. Vamos criar umaversão deste programa no qual evitaremos este problema.

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h> #include <conio.h>

int le_char() { printf(" Entre com um caracter "); return (getch()); }

main() { do { char ch, opcao, le_char();

/* Impressao do cabecalho */ printf(" <1> : imprime em formato ASCII \n"); printf(" <2> : imprime em octal \n"); printf(" <3> : imprime em decimal \n"); printf(" <4> : imprime em hexadecimal \n"); printf("<Outras> : sai \n"); /* --------------------- */

printf(" Entre com uma opcao "); opcao = getch(); printf("\n");

switch (opção) { case '1' : ch = le_char(); printf(" ASCII - %c\n",ch); break;

case '2' : ch = le_char(); printf(" octal - %o\n",ch); break; case '3' : ch = le_char(); printf(" decimal - %d\n",ch); break; case '4' : ch = le_char(); printf("hexadecimal - %x\n",ch); break; default : exit(0); } }while(1); }

Criamos uma função com o objetivo de mandar uma mensagem para a tela e ler o caracter. Daqui a poucofaremos uma outra versão um pouco mais interessante.

OBS.: A forma que usamos o while (colocando uma constante dentro dos parênteses) NÃO DEVE SERIMITADA. Observe que 1 não tem significado nenhum em si. Devemos sempre usar uma variável ou umadefinição (isto veremos abaixo) no lugar de colocar constantes “mágicas”. Comentários ao lado podem atéesclarecer o funcionamento mas o ideal é que deixemos a variável "falar por si", ou seja, que ela tenha um"nome" que já seja o suficiente para mostrar a sua função.

Departamento de Ciência da Computação-UFF

Números de página

Mais um exemplo será útil para marcar as diferenças entre o for de C e laços semelhantes de outraslinguagens. Aqui teremos duas variáveis sendo “contadas” pelo mesmo for mas em “ritmos” diferentes.

main(){int impr, i, nimpr, nt;

nt = 100;nimpr = 10;

for (impr = 1, i = 1; impr < nt; impr += nimpr, i++) { printf("\n impr = %d ### i = %d \n", impr, i); }}

Neste caso, serão impressos os valores de impr que irão de 1 até 91 de 10 em 10, ou seja, serão geradosos números 1, 11, 21,...,91 enquanto i irá de 1 até 10 estando o laço sob controle da condição sobre impr.

EXERCÍCIOS

I)Experimente não usar a palavra reservada break no programa três, terceira versão.II) Use a função printf() para observar as saídas de todos os programas dados anteriormente como

exercício. (Não se esqueça de colocar #include <stdio.h>).

III) Desenvolva uma função que imprima um inteiro ou um caracter em forma binária. Feito isto,utilize-a no programa acima, criando outra opção no menu.

Departamento de Ciência da Computação-UFF

Números de página

PONTEIROS E FUNÇÕES

Vamos agora falar de um tipo de variável extremamente poderosa: o ponteiro, também chamadoapontador. Recordemos que existem dois operadores chamados operadores de endereço e estes sãorepresentados por:

& "o endereço de" . Ex. : a = &b. a recebe o endereço da variável "b". * "no endereço de". Ex. : c = *d. c recebe o valor da variável "apontada" por d.

Observemos que num computador, quando escrevemos algo como :

a = b;

O computador faz as seguintes operações: pega o conteúdo da posição da memória correspondente a variável b,coloca este valor na posição da memória correspondente a variável a. Ou seja, é uma transferência do conteúdode endereço para conteúdo de endereço. Os operadores de endereço nos permite obtermos endereços devariáveis ou tendo endereço de uma variável, saber seu conteúdo.

As variáveis que contém os endereços de outras variáveis denominamos PONTEIROS ouAPONTADORES. É claro que aqui surge uma suspeita: Como as variáveis tem estruturas diferentes,provavelmente teremos tipos diferentes de ponteiros para cada tipo de variável. Para usarmos o ponteiro certocom a variável certa, teremos que definir os ponteiros tal como fazemos com as outras variáveis. A notação é aque se segue

TIPO *nome_d_ponteiro_1, *nome_do_ponteiro_2, ...;

Podemos fazer, se quisermos, a definição dos ponteiros junto com as das variáveis.Lembre-se sempre que um ponteiro não tem "nenhuma" informação, o que ele tem é o endereço de

uma informação.Mas, neste ponto, alguém pode perguntar: Porque o título desta parte é PONTEIROS E FUNÇÕES? É

justamente aqui que veremos uma dos usos dos operadores de endereço. Para demonstrar esta utilidade, daremosum exemplo clássico de uma função que NÃO FUNCIONA. Abaixo temos escrita uma função que faria apermuta entre duas variáveis.

#include <stdio.h>

permutar(a,b) int a,b;

{ int auxiliar;

auxiliar = a; a = b; b = auxiliar; }

main() { int a = 5, b = 3, c = 7;

permutar(a,b); permutar(a,c);

printf(" %d, %d, %d .",a,b,c); }

O que aconteceria se executássemos este programa? As variáveis a e b não teriam os seus valorestrocados. Lembrem-se que as variáveis, como estão declaradas são locais e que funções em C só trabalhampassando valores. O que fizemos dentro da função permutar() não afetará o conteúdo das variáveis a, b e c doprograma principal.

Departamento de Ciência da Computação-UFF

Números de página

A saída desta situação não esta em declarar a, b e c como variáveis globais, já que assim você sópoderia intercambiar estas variáveis. A função ficaria restrita ao programa que você esta fazendo e a certasvariáveis de entrada.

A saída esta no fato de que a diferença entre as variáveis locais de cada função (a permutar() e a main()) é que elas ocupam posições diferentes na memória. Se passarmos o endereço da variável em vez do valor davariável, temos uma maneira de afetar este valor. Vejamos uma versão modificada do programa anterior:

#include <stdio.h>

permutar(a, b)int *a,*b; { int auxiliar;

auxiliar = *a; *a = *b; *b = auxiliar; }

main(){int a = 5, b = 3, c = 7;

permutar(&a, &b);permutar(&a, &c);printf(" %d, %d, %d .", a, b, c);}

Através do operador & ("o endereço de"), passamos para a função permutar() os endereços de a e bdo programa principal. Ao entrar na função, observemos que os parâmetros foram declarados com o uso dooperador * ("no endereço de"). Portanto *a e *b são os conteúdos das variáveis a e b do programa principal. Nomomento que eu estou fazendo as trocas dentro da função, estamos na verdade trocando os conteúdos dasvariáveis a e b do programa principal!

Uma pergunta a ser feita é:Já que toda função em C retorna algo, como fica permutar() que não precisa devolver nada? A

intenção em não colocar nada na declaração do tipo de saída é tentar passar a idéia de que qualquer coisaretornada pela função não tem significado. No entanto, esta não é uma boa maneira. O ANSI C apresenta umasolução e a veremos mais a frente.

Repare ainda o formato de saída das variáveis.

Departamento de Ciência da Computação-UFF

Números de página

PONTEIROS E VETORES

Vetores são encontradas em muitas linguagens e C não escapa disto. Se declara um vetor em C daseguinte maneira:

TIPO nome_do_vetor [TAMANHO]

com uma ressalva: vetores em C começam do índice 0 (zero) e acabam em TAMANHO menos um. Por exemplo

int vetor[10];

float evento[4];

Acima estamos declarando um vetor inteiro de dez posições e um vetor de ponto flutuante de quatro posições. Oelemento vetor[0] é o primeiro e vetor[9] é o último. Analogamente para o vetor evento, o primeiro elementoserá evento[0] e o último evento[3]. No caso de cadeia de caracteres temos uma situação especial. Se declararmos o vetor

char titulo[10]

teremos uma cadeia de caracteres de no máximo 9 (NOVE) caracteres e não dez. Uma das características de C éque toda cadeia de caracteres tem seu fim marcado por um caracter nulo. (Como atribuir à um vetor uma cadeiade caracteres? Um momentinho só! Já veremos isto.) Então lembre-se sempre :

"Para trabalhar com cadeias de caracteres, devemos deixar espaço para o caracter terminador dacadeia (o nulo). "

Outra particularidade :

"Não há verificação dos limites de um vetor. "

O esquecimento deste fato é uma grande fonte de erros. Mas o que os vetores tem com os ponteiros? Para explicar isto, de novo vamos falar de como o

computador trabalha só que agora com vetores. Um vetor é geralmente armazenado na memória do computadorem posições contíguas. Tendo a primeira posição de memória (que chamaremos de posição zero), acrescentandoo índice do vetor a esta posição multiplicado pelo tamanho do tipo de variável, teremos a posição do elementodo vetor correspondente ao do índice.

Em C um vetor, na verdade, é um ponteiro para o início da seqüência de elementos do vetor. Podemos,então, referenciarmos elementos do vetor tanto pelos índices como através de ponteiros.

Devemos aqui chamar a atenção que, o trabalho com ponteiros é mais rápido que o trabalho com osíndices. Esta opção dupla existe já que certos algoritmos (como os de ordenação) são mais simples de seremimplementados se a referência é feita por índice enquanto outros ficam mais simples com o uso de ponteiros.

Como exemplo, criaremos uma função que escreve na tela uma cadeia de caracteres usando a funçãoputchar(), já descrita.

#include <stdio.h>

putstr(s,n)/* Setimo programa */

char *s;int n;

{ int i;

for (i = 0; i < n; ++i) { putchar(s[i]); } }

main()

Departamento de Ciência da Computação-UFF

Números de página

{char s[10];..putstr(s,10);}

Você terá que passar como parâmetros não só o vetor como também o seu tamanho. Na verdade, nãohá a necessidade de passar o número de elementos. Poderíamos usar uma palavra chave que no informa otamanho de uma variável, mas não iremos nesta direção. Mas, e se lembramos que uma cadeia de caracterestermina por nulo? Vejamos uma versão do programa acima que se utiliza disto.

#include <stdio.h>

putstr(s)char *s; { int i;

for (i = 0; s[i]; ++i) { putchar(s[i]); } }

main(){char s[10];..putstr(s);}

Observe a condição de parada do for feita usando o fato que em C, uma cadeia de caracteres termina num nulo.Ficou bem elegante, embora um tanto quanto desagradável. Ainda podemos melhora-la.

#include <stdio.h>

putstr(s)

char *s;

{ while (*s) putchar(*s++); }

main(){char s[10];..putstr(s);

}

Observe que, com ponteiros, a função fica mais simples e ainda mais elegante.Esta função, ou melhor, sua equivalente, está na biblioteca padrão de C com o nome de puts().

Departamento de Ciência da Computação-UFF

Números de página

ARITMÉTICA DE PONTEIROS

O número de operações aritméticas que podem ser feitas com os ponteiros é bem restrita, pela próprianatureza deles. Lembre-se que o ponteiro em si não tem nenhum significado. Ele apenas nos diz onde seencontra a informação que queremos acessar. É bom sempre ter na cabeça que :

" Ponteiros não são inteiros."

Como a estrutura de endereçamento pode variar de computador para computador, não necessariamenteo endereço pode ser representado por um número inteiro. Além disto, mesmo que pudéssemos representar osponteiros como inteiros, obviamente os conceitos envolvidos são totalmente diferentes e, lembro mais uma vez,não devem ser misturados. Abaixo tabelamos os operadores que são válidos em operações com ponteiros.Suponhamos dois ponteiros p e q e um inteiro i:

++p pré-incrementa o valor do ponteiro--p pré-decrementa o valor do ponteirop++ pós-incrementa o valor do ponteirop-- pós-decrementa o valor do ponteiro*p acessa o conteúdo do endereço apontado por pp+i Soma de um ponteiro com um inteirop-i Subtração de um inteiro sobre o ponteirop-q Subtração entre ponteiros desde que apontem para o mesmo vetor

Os operadores de incremento e de decremento com ponteiros, trabalham de tal forma que levam emconsideração o tipo de ponteiro que está sendo usado. Portanto se estamos usando um destes operadores com umponteiro de inteiro, automaticamente o incremento (ou decremento) será de dois bytes. Se temos um ponteiropara ponto flutuante do tipo float, o incremento (ou decremento) será de quatro bytes.

Algumas vezes é interessante compararmos ponteiros. Imagine que tenhamos dois ponteiros p1 e p2(p1 > p2), que referenciam um determinado vetor. Logo p1 - p2 nos dará o número de elementos entre estesponteiros. Observe, no entanto, que não há sentido em compararmos ponteiros que não estejam referenciando amesma estrutura de dados.

Departamento de Ciência da Computação-UFF

Números de página

INICIALIZANDO VETORES

Podemos inicializar os valores dos vetores no momento da declaração desde que eles tenham sidodeclarados globais. A forma geral é a seguinte

TIPO nome[TAMANHO] = {ELEMENTO0, ELEMENTO1,..,ELEMENTO(n-1));

e para o caso especial de vetores do tipo char

char nome[TAMANHO + 1] = "cadeia de caracteres";ou

char nome[] = "cadeia de caracteres";

com o fato interessante de não determinarmos o tamanho da cadeia. Mais uma gentileza do compilador quedimensiona por você. Se a entrada da cadeia de caracteres for pelo formato geral, você não deve esquecer deincluir o caracter nulo, procedimento dispensável no formato especial para caracteres.

Exemplos:

float a[3] = {0, 3.1415926, 2.718281828};char titulo1[7] = {'T','i','t','u','l','o','\0'};char titulo2[7] = "Titulo";

Para exemplificarmos o uso de vetores e a sua inicialização, abaixo temos um pequeno programa quedado um vetor contendo números de ponto flutuante, temos como resultado final o maior e o menor elementos.

#include <stdio.h>

float a[10] = {0.0, 3.1415926, 2.718281828, 2.2360679, 6.0, 0.33333333, -1.4142135, 0.57721566, 0.6931471, 17.0};

main(){float max = a[0], min = a[0];int i;

for (i = 0; i < 10; i++) { if (max < a[i]) { max = a[i]; continue; } if (min > a[i]) min = a[i]; }}

Temos aqui o uso do continue para saltar o outro if, já que se um número for maior que o máximo, não temsentido em testa-lo.

Um exemplo um pouco mais complexo é o do programa abaixo que ordena um vetor de inteiros usandoordenação por seleção. A idéia é fazer comparações entre os valores do vetor de tal forma que inicialmentecoloquemos o menor valor do vetor na primeira posição do mesmo. Feito isto, examinamos os elementosrestantes e colocamos o menor deles na segunda posição. Continuando o processo, teremos o vetor ordenado.

#include <stdio.h>

int x[10] = {2, 4, 1, 8, 6, 7, 3, 9, 2, 5};

permutar(a,b)int *a,*b;

{ int auxiliar;

Departamento de Ciência da Computação-UFF

Números de página

auxiliar = *a; *a = *b; *b = auxiliar; }

main(){int i, j, aux, m, nm1, n = 10;

nm1 = n - 1;

for (i = 0; i < nm1; i++) { m = i;

for (j = i + 1; j < n; j++) if (x[j] < x[m]) m = j;

permutar(&x[i], &x[m]); }

for (i = 0; i < n; i++) printf(" x[%d] = %d", i, x[i]);}

Aqui usamos a função permutar() já definida anteriormente.

Departamento de Ciência da Computação-UFF

Números de página

VETORES MULTI-DIMENSIONAIS

Mas não existem só vetores unidimensionais em C. Assim como em outras linguagens, aqui tambémtemos vetores de mais de uma dimensão. A forma de declaração destes vetores é a que se segue :

TIPO nome_do_vetor [TAMANHO1] [TAMANHO2] ...[TAMANHOn]

Diferente da maioria das linguagens, como podemos ver. Aqui cada dimensão varia de 0 (zero) aTAMANHO menos um, como no caso unidimensional. Um exemplo de declaração de um vetor multi-dimensional é dado abaixo

int matriz [10][10];

declarando uma matriz de inteiros com dez linhas por dez colunas.Quando se deseja passar vetores multidimensionais como parâmetros de uma função, você não precisa

declarar a primeira dimensão, ou seja, se é declarada a matriz de caracteres

char a[80][24];

ao passa-la como parâmetro, podemos fazer

tela(a)char *a[][24];...

O processo de inicialização de vetores multidimensionais é análogo a inicialização de vetoresunidimesionais e tem a mesma restrição, ou seja, só podem ser inicializados os vetores declarados comovariáveis globais. O formato de inicialização, para o caso bidimensional, é o que se segue:

TIPO nome_do_vetor [TAMANHO1] [TAMANHOn] = { {elemento(0,0),...,elemento(0,TAMANHO1-1)},{elemento(1,0),...,elemento(1,TAMANHO2-1)},...{elemento(TAMANHOn-1,0),..,elemento(TAMANHOn-1,TAMANHOn-1)}};

Podemos interpretar que o que temos é a inicialização de um vetor de dimensão um onde cada elemento foidefinido também como um vetor de dimensão um. Para o caso de mais dimensões, basta proceder de maneiraanáloga.

Escreveremos um programa exemplo que contém uma função que faz o produto de uma matriz por umvetor.

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>

int a[4][4] = { {1, 2, 3, 4}, {4, 1, 2, 1}, {1, 2, 1, 4}, {4, 3, 2, 1} };

int b[4] = {1, 1, 1, 1};

matvet(a, b, c, n)int a[][4], b[4], c[4], n;

{ int i, j, aux;

for (i = 0; i < n; i++) { aux = 0; for ( j = 0; j < n; j++) { aux = aux + a[i][j] * b[j]; }

c[i] = aux; }}

main(){

int c[4], i;

matvet(a, b, c, 4);

for (i = 0; i < 4; i++) { printf(" c[%d] = %d \n", i, c[i]); }}

A função matvet() tem como entrada a matriz a ser multiplicada, o vetor b e um vetor de saída c alémda dimensão n da matriz e do vetor não “devolvendo nenhum valor”. As aspas colocadas tem uma razão jáconhecida: Toda função de C devolve um valor, este valor tenha significado ou não ou se nós mandarmosdevolver um valor ou não.

Para deixar claro o relacionamento entre ponteiros, vetores e matrizes e como são suas as relações,abaixo temos mais um programa que gera uma matriz e imprime a mesma de várias formas.

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>

main(){int a[3][3], i, j, k = 0, N = 3, *p;

for (i = 0; i < N; i++) for (j = 0; j < N; j++) a[i][j] = k++;

puts("Imprimindo o conteudo da matriz\n");for (i = 0; i < N; i++) for (j = 0; j < N; j++) printf("\n a[%d][%d] = %d", i, j, a[i][j]);

puts("\n\n\ Agora os N elementos apontados\n");

p = a[1];for (i = 0; i < N; i++) printf("\n p[%d] = %d", i, p[i]);

puts("\n\n Finalmente pegando o ponteiro do inicio da matriz\n");

p = a[0];for (i = 0; i < N * N; i++) printf("\n p[%d] = %d", i, p[i]);}

Como está descrito no próprio programa, estamos imprimindo a matriz usando os índices como éconvencional, depois usando um ponteiro dado pelo uso do identificador da matriz como se este tivesse só umíndice e variando este índice e finalmente imprimindo o apontado por um ponteiro que referencia o endereço doprimeiro elemento da matriz. Ao executá-lo você verá que a segunda impressão mostrará a segunda linha damatriz e na terceira impressão teremos todos os elementos. Deste último resultado temos que uma matriz temsuas linhas colocadas contiguamente.

Vemos então que podemos manipular uma matriz de diversas formas: Como “matriz tradicional”, comoponteiros para vetores ou como ponteiro para ponteiro. A forma que deve ser usada é justamente a que for maisconveniente para sua tarefa e não a que for mais conveniente para sua preguiça. Se bem que algumas vezes umaacompanha a outra....

EXERCÍCIO

I) Faça um programa que compare os elementos de dois vetores inteiros de mesmo tamanho,imprimindo as diferenças entre eles.

Departamento de Ciência da Computação-UFF

Números de página

VETORES DE PONTEIROS E PONTEIROS DE VETORES

Vimos que podemos acessar um elemento de um vetor via índice ou via referência por ponteiro, usandoas duas formas de acordo com nossas necessidades. O que falaremos aqui são os casos que realmentemisturamos os conceitos. Veremos inicialmente os vetores de ponteiros. A declaração deste objeto é feita daseguinte forma

Tipo *nome[Tamanho]

Se quisermos definir um vetor de ponteiros do tipo int de nome vetor e de 15 componentes, escreveríamos

int *vetor[15]

Qual seria o significado desta definição? O que está definido acima é um vetor de 15 posições cada umacontendo um ponteiro para inteiro.

Quanto ao ponteiro de vetores, sua declaração seria

Tipo (*nome)[Tamanho]

Aqui temos um ponteiro que aponta para um determinado vetor. Observe que neste caso teremos umasituação curiosa se lembrarmos que a definição de um vetor em si é a definição de um ponteiro para um pontona memória. Ou seja, temos uma variável na qual poderemos guardar o endereço de qualquer vetor do tipodefinido. Mais abaixo falaremos sobre isto..

Estes dois objetos tem a sua utilidade, obviamente. Mas um deles, o vetor de ponteiros, veremos embreve e provavelmente será um velho amigo em pouco tempo.

PONTEIROS PARA PONTEIROS & PONTEIROS PARA PONTEIROS PARA PONTEIROS & ETC.

Falaremos de alguns tipos de acesso a informação que podem gerar bastante confusão. Como já foidito, um vetor pode ser trabalhado através de um ponteiro. Isto sugere que os vetores de ponteiros ou osponteiros de vetores, podem ser usados (e pensados) como ponteiros para ponteiros, ou seja, um endereço quecontém outro endereço que por sua vez aponta para uma variável. Os mais criativos já devem estar pensandoque podemos descrever ponteiros para ponteiros para ponteiros ou coisa ainda mais complicada. A notação detais entidades é exemplificada abaixo :

int **aint ***a

são respectivamente um ponteiro de ponteiro para um inteiro e um ponteiro de ponteiro de ponteiro para inteiro. Raras vezes são necessárias indireções maiores que as dadas acima. A maioria dos casos nos quais o

programador é tentado a fazer muitas indireções é com a idéia de melhorar a performance do programa que estádesenvolvendo, já que as operações com ponteiros são geralmente mais rápidas que as operações com índice. Amaioria das vezes o que se ganha em velocidade ganha-se várias vezes o equivalente em dores de cabeça.Sempre que possível evite tais procedimentos.

Departamento de Ciência da Computação-UFF

Números de página

PONTEIROS PARA FUNÇÕES

Quando invocamos uma função, o que ocorre é que o fluxo do programa se interrompemomentaneamente e continua numa outra posição de memória (ou seja, num outro endereço). Isto nos permitereferenciar as funções através de ponteiros. Estes serão Ponteiros para Funções. Como os vetores, a simplesreferência ao nome da função nos dá o endereço da mesma.

Observe que o que disse acima é de seu uso e conhecimento seja lá que linguagem de programaçãovocê tenha estudado anteriormente. Da mesma forma que já dissemos que não existe passagem por referênciapara variáveis (o que passamos são os endereços destas variáveis) também não existe passagem por referênciade funções. Novamente o que a linguagem C faz é dizer para que programa o que está acontecendo de fato. Pelomenos para o nível de programação. Isto dá uma riqueza de possibilidades muito maior que o “mascaramentoprotetor” comum em outras linguagens. Abordaremos alguns aspectos e possibilidades mais típicas destesponteiros, sem explorar todas as possibilidades chamando a atenção que o que já falamos sobre ponteiros (efalaremos) poderá ser aplicado em muitas circunstâncias à este tipo de ponteiros. Uma aplicação típica é quando fazemos uma função de tratamento geral para funções arbitrárias. Neste caso, afunção entra como parâmetro de outra função. Então teremos que passar o ponteiro para a função geral para quepossamos executar nosso programa de maneira adequada.

A descrição de um ponteiro para função tem a seguinte forma

tipo da função (* Nome da função)(Lista de parâmetros)

Como primeiro exemplo, vamos usar uma função da biblioteca padrão que faz ordenação usando oalgoritmo Quick Sort desenvolvido por Hoare. Esta função tem o seguinte formato

qsort(v, N, tamanho, compar);

onde v é um ponteiro para a estrutura de dados a ser ordenada, N é o tamanho desta estrutura, tamanho é otamanho em bytes de cada elemento da estrutura e compar() é um ponteiro para a função que fará a comparaçãoentre dois elementos e qsort() devolve “algo sem significado”. compar() deverá devolver -1, 0 e 1 caso oselementos sejam respectivamente menor, igual ou maior que o outro.

Abaixo temos um programa no qual é dado inicialmente um vetor de inteiros que deve ser ordenado emordem crescente.

#include <stdio.h>#include <stdlib.h>

int v[8] = {6, 0, 5, 4, 1, 2, 3, 1};

int compar(a, b)int *a, *b; { if (*a < *b) return -1; else { if(*a == *b) return 0; else return 1; } }

Departamento de Ciência da Computação-UFF

Números de página

main(){int i, tamanho = 2, /* Tamanho de um inteiro em bytes */ n = 8, /* Numero de elementos */ compar();

qsort(v, n, tamanho, compar);for (i = 0; i < n; i++) printf(" v[%d] = %d", i, v[i]);}

Observe no cabeçalho mais uma biblioteca: stdlib.h. Ela contém a definição de qsort() além de outras funções. Como um exemplo um pouco mais complexo, apresentaremos um programa que contém duas funções.

Uma é função arbitrária e outra calcula, pelo método da bisseção, uma raiz da primeira função. A idéia básicadeste método de achar raízes é simples e fácil de entender observando a figura abaixo :

A

B

X

R

Temos acima uma função que se anula no ponto indicado R e que está entre A e B. Mesmo sem o desenhopodemos saber isto já que a função tem valor negativo no ponto A e positivo no B. Se dividimos o intervalo emdois e calculamos o valor da função no ponto médio X podemos através do seu sinal saber se R se encontra entreA e X ou entre X e B. Se subdividimos novamente o intervalo onde se encontra a raiz, teremos uma melhoraproximação de R. Continuamos subdividir até atingirmos a precisão que acharmos conveniente ou impomosum limite para o número de passos.

Algoritmo:Seja a função f(x) que tem uma raiz num intervalo [a, b]. Escolha o número máximo n_max de passos e

a tolerância. Então faça : i) Ache o valor de f no ponto a; ii) Ache o ponto médio do intervalo (x = (a + b)/2); iii) Ache o valor de f no ponto x; iv) Se f(a) * f(x) for negativo, a raiz se encontra entre a e x, caso não, esta entre x e b. No primeiro caso faça b= f(x ) e no segundo a= f(x); v) Se a distância entre a e b for menor que a tolerância ou o número de passos for maior que n_max, pare,senão volte a i;

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>#include <math.h>

int ACHOU = 1;

double f(x)double x; { return(exp(x) - 3 * cos(x)); }

int bissecao(f, a, b, tol, imax, *raiz) double (*f)(), *raiz;double a, b, tol;int imax; { int i; double x, fa, fx;

for (i = 0; i < imax; i++) { fa = f(a); x = (a + b)/2; fx = f(x);

if (fa * fx < 0) b = x; else a = x;

if (fabs(a - b) < tol) { *raiz = a; return ACHOU; } } return (! ACHOU); }

main(){int n_max = 10;double a = 0.0, b = 1.0, tol = 0.001, raiz, f(), bissecao();

if (bissecao(f, a, b, tol, n_max, &raiz) != ACHOU) printf("Nao atingiu a precisao") else printf(" Raiz = %f", raiz);}

Na definição da função bissecao() observe como colocamos a declaração do ponteiro de função. Osparênteses são necessários para que o compilador não interprete o nosso ponteiro para função como um ponteiropara variável. Repare ainda a utilização da variável ACHOU. A maneira como ela é usada colabora parafacilitar o entendimento do programa. No entanto, esta não é a melhor maneira de defini-la. Mais a frenteveremos algo mais elegante.

Como sempre, visamos a clareza e não a eficiência. Por exemplo, não existe a necessidade de calcular afunção nos pontos a e x em cada passo do algoritmo. e não há necessidade de termos dois critérios de parada, ouseja, por tolerância e por número de passos já que há uma relação entre estes parâmetros neste método. Maisdetalhes veja um livro de Cálculo Numérico[].

Departamento de Ciência da Computação-UFF

Números de página

MAIS SOBRE FUNÇÕES

a) Recursividade

Outro ponto sobre funções em C, é que elas podem ser usadas de maneira recursiva, ou seja, podeminvocar a elas mesmas. Talvez isto possa parecer estranho para alguns mas é em muitos casos extremamente útil.Como exemplo, daremos duas funções que calculam o fatorial de um número, uma iterativamente e outrarecursivamente.

#include <stdio.h>

fatorial(n) int n; { int i, fat;

for (i = 1; i <= n; i++) fat = fat * i; return(fat); }

main() { int fatorial(), n = 5;

printf(" o fatorial de %d e igual a %d ",n,fatorial(n)); }

Versão recursiva

#include <stdio.h>

fatorial(n) int n; { int i = n;

if (n == 1) return(i); else return( i * fatorial(--n)); }

main() { int fatorial(), n = 5;

printf(" fatorial de %d e igual a %d ",n,fatorial(n)); }

Departamento de Ciência da Computação-UFF

Números de página

O próximo programa será uma implementação do algorítmo de Euclides para o cálculo do MDC. Faremos amesma estratégia apresentando as versões não recursiva e recursiva.

#include <stdio.h>

int mdc(m, n)int m, n; { int r;

do { r = m % n; m = n; n = r; } while(r != 0);

return(m); }

main(){int m = 120, n = 9, mdc();

printf("\n o MDC de %d e %d e ", m, m, mdc(m, n));}

Versão recursiva

#include <stdio.h>

int mdc(a, b)int a, b; { if (b == 0) return(a); else return(mdc(b, a % b)); }

main(){ int m = 120, n = 9, mdc();

printf(", m = %d, n = %d, mdc = %d", m, n, mdc(m, n));}

Observe que a maneira como está escrita se assemelha mais com o algoritmo original que a versão nãorecursiva.

Mais um exemplo de problema resolvido de maneira recursiva é o de determinar a seqüência deFibonacci. Esta é definida como abaixo :

Departamento de Ciência da Computação-UFF

Números de página

Seja F0 = 0 e F1 = 1 os dois primeiros termos desta seqüência. Então qualquer termo pode ser

determinado a partir dos dois anteriores, ou seja,

Fi+2 = Fi+1 + Fi

Assim ficaríamos com 0 1 1 2 3 5 8 13 e etc. Abaixo temos um programa que exibe o n-ésimo elementodesta seqüência por meio de um processo recursivo.

#include <stdio.h>

int fibo(int n) { if (n <2) return (n); else return(fibo(n-1) + fibo(n-2)); }

main(){ int fibo(); int n = 8;

printf(" %d", fibo(n));}

Esta não é a maneira mais eficiente de calcular esta seqüência mas é indiscutivelmente fácil dereconhecer nesta implementação a definição do problema. Uma das origens da ineficiência desta implementaçãoestá no fato de fibo(n-2) ser chamada 2 vezes, fibo(n-3) ser chamada 3 vezes e assim por diante.

A esta altura deve ter gente perguntando o porque de uso recursivo de funções, já que estasaparentemente não são mais que curiosidades. Obviamente isto não é verdade. Existem alguns algoritmos quesão de definição simples de maneira recursiva e extremamente complicados de outra forma. Muitos algoritmosextremamente eficientes usam o paradigma chamado Dividir para Conquistar e são definidos de maneirarecursiva. No caso de ordenação, por exemplo, temos os algoritmos Quick Sort e Merge Sort comimplementações recursivas de fácil compreensão. O mesmo não se pode dizer de versões não recursivas dealgoritmos conceitualmente recursivos.

Vamos dar mais um exemplo de utilização de técnicas recursivas, construindo um programa que dá asolução para o problema da Torre de Hanoi. Para os que não conhecem, contemos uma historinha:

Dizem que em algum ponto perto de Hanói existe um mosteiro onde os monges se entregam a umtrabalho de manipular uma pilha de discos perfurados entre três postes. Os discos são todos de tamanhosdiferentes e colocados num poste (digamos, o da esquerda) de tal maneira que os menores estão postos sobre osmaiores. Ao lado desta pilha, temos dois outros postes que chamaremos de poste do meio e da direita. Amanipulação dos monges consiste em levar todos os discos do poste da esquerda para o poste da direita semnunca colocar um disco maior sobre um menor usando o poste central para auxiliar. Dizem que quandoacabarem de fazer a transferência de todos os discos, o mundo chegará ao seu fim.

Apesar do clima apocalíptico, este problema fica bem simples de ser implementado se usarmos derecursão. Uma recomendação porém: não tente colocar um valor muito grande de discos. O processamentopoderá ser muito longo. Não porque o programa é recursivo mas porque é demorado mesmo! Pode ser

demonstrado que o número de operações (manipulações de discos) é igual a 2n -1, onde n é o número de discos.No caso dos monges, podem ficar descansados quanto ao fim do mundo. O número de discos no primeiro posteera de 64 e levaria várias vezes a idade estimada do universo para fazer a transferência total, se cada trocalevasse apenas alguns segundos.

Antes de tentar executar este programa, leia os comentários após o mesmo.

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>

char posicao[][11] = {"a esquerda","o meio","a direita"};

int ESQUERDA = 0, MEIO = 1, DIREITA = 2;

mova(n, daonde, auxiliar, praonde)

int n, daonde, auxiliar, praonde; { if (n != 1) mova(n - 1, daonde, praonde, auxiliar); printf("Mova o disco d%-s para %-s", posicao[daonde], posicao[praonde]);

if (n != 1) mova(n - 1, auxiliar, daonde, praonde); }

main(){char *titulo = "\nTorre de Hanoi.\nDigite o nro. de discos : ";char *cadeia[20];int discos;while((puts(titulo), discos=atoi(gets(cadeia))) != 0) { puts("Coloque os discos a esquerda e faca os movimentos:"); mova(discos, ESQUERDA, MEIO, DIREITA); }puts("Ok! Pilha da esquerda totalmente transferida para direita");}

Observe alguns aspectos interessantes desta implementação: I) Veja a utilização do operador vírgula dentro da expressão a ser avaliada pelo while. ii) Digno de nota é a inicialização do vetor posicao[] de uma maneira "estranha". Aqui temos uma

liberdade que C nos dá. Especificamos o tamanho que deve ser reservado para cada cadeia de caracteres edeixamos quantas cadeias teremos em aberto.

iii) Observem ainda uma monstruosidade. Foi atribuído ao ponteiro titulo uma cadeia de caracteres.Qual a garantia de que temos espaço para colocar a cadeia de caracteres a partir do endereço dado peloponteiro? Não temos garantia nenhuma! Este programa, como está escrito, pode funcionar ou não! Devido à istopoderemos ter esta cadeia de caracteres sendo escrita "por cima" de outras variáveis. Não é bom arriscar.Solução? Esperem a próxima versão...

iv) Não colocamos o tipo devolvido pela função mova(). Sabemos que qualquer função em C devolvealgo. Aqui suprimimos o tipo como uma maneira de indicar que de fato esta função nada devolve. Não é umamaneira maravilhosa de dizer isto, já sabemos, e o C ANSI tem maneiras mais óbvias e precisas.

Departamento de Ciência da Computação-UFF

Números de página

OBSERVE AINDA QUE :

i)Aqui estamos manipulando vetores de maneiras extremamente variada. A pergunta que pode surgir éo porque desta variedade de opções. Certamente não é para usarmos de qualquer maneira de acordo com o nossohumor no dia. A linguagem C permite que uma mesma informação seja acessada de uma gama de maneiras deforma que exista uma que faça que fique mais fácil (ou mais claro) um determinado procedimento a serimplementado. Lembre-se que clareza é extremamente importante se o nosso objetivo é a produtividade.

ii) Um bom desafio (dos grandes) é fazer uma versão não recursiva que resolva este problema.

EXERCÍCIOS

I) Faça um programa não recursivo que dado um inteiro positivo n, imprima todos os n termos daseqüência de Fibonacci.

Departamento de Ciência da Computação-UFF

Números de página

FUNÇÕES EM NOTAÇÃO MODERNA

Aqui daremos mais algumas características das funções e descreveremos a notação moderna para adefinição dos seus parâmetros.

Toda função em C devolve algum valor. Mesmo que a função não tenha nenhum return, ela devolverá,de uma maneira geral, zero. Como em certas ocasiões não há a necessidade de um valor retornado (ounecessariamente não tem um tipo específico), foi criada uma palavra reservada, pertencente as extensões ANSI,que indica exatamente isto. Esta palavra é void e veremos uma aplicação dela logo abaixo.

Na notação moderna os parâmetros das funções e o valor devolvido, terão os seus tipos indicados pelasdefinições de tipos. Teríamos para a função fatorial, definida num dos exemplos deste texto, a seguinte notação

int fatorial(int n)

indicando que você passa para ela um inteiro e tem como resultado também um inteiro. No caso da funçãopermutar(), também definida num exercício, teríamos a definição

void permutar(int *a,int *b).

Aqui surge a palavra nova, void, indicando aqui que o valor devolvido (lembre-se: funções em Csempre devolvem algum valor) não tem significado. Temos ainda a indicação que passaremos dois ponteirospara inteiro como parâmetros, ou seja, você ao chamar a função deverá dar o endereço das variáveis e não seusvalores. Este jeito de definir uma função é denominado de Forma Moderna.

As vantagens estão não só na questão de documentação como também provoca uma verificação detipos gerando um alerta caso o tipo de dado de entrada numa função não for compatível com a definição dadadesta forma.

Atenção : Alguns compiladores antigos não admitem a escrita em notação moderna.

MAIS ALGUMAS FUNÇÕES

Daremos, abaixo, uma relação de algumas funções que podem ser úteis em seus trabalhos e exercícios,já escritas em notação moderna.

stdio.h

char *gets(void) - entrada de uma cadeia de caracteres pelo teclado; puts(char *s) - escreve a cadeia de caracteres s na tela, saltando automaticamente para a outra linha; scanf(*char, *var1,...,*varn) - entrada formatada. O seu funcionamento é análogo ao printf(), o

mesmo valendo para os caracteres de controle. Observe que o que temos como parâmetros são endereços.

ctype.h

char tolower(char ch) - Dá como saída o caracter ch em minúscula. char toupper(char ch) - Dá como saída o caracter ch em maiúscula.

stdlib.h

int atoi(char *s) - Converte a cadeia de caracteres s num número inteiro. long int atol(char *s) - Converte a cadeia de caracteres s num número inteiro longo.

Departamento de Ciência da Computação-UFF

Números de página

SOBRE ESTAS FUNÇÕES

Façamos alguns comentários sobre algumas destas funções. As funções puts() e printf() com asseguintes estruturas são "equivalentes"

puts(str) printf("%c\n", str)

O que as diferencia é que a o arquivo executável de um programa que utilize a função puts() será menor que oque usar um printf() equivalente. Algumas vezes este fato poderá ser de importância.

Observe ainda que a sentença de controle do printf() é uma cadeia de caracteres. Portanto, podemoscriar programas nos quais o formato de saída seja dado em tempo de execução.

EXERCÍCIO

I)Faça um programa em C que inverta as palavras de uma frase. A frase entrará via teclado. Exemplo :

Entrada : Um exemplo simples. Saída : mU olpmexe selpmis.

II) Na idade média e na renascença era muito comum os pesquisadores, nas mais variadas áreas,codificarem suas descobertas. Geralmente estas descobertas eram condensadas em pequenas frases. A frase a sercodificada sofria uma modificação tendo todas as suas letras colocadas em ordem alfabética e sem espaço.Exemplo:

Entrada : um exemplo simples. Saída : eeeillmmmoppssux.

Crie um programa que faça este procedimento com uma frase que seja dada via teclado. Dica: consultea tabela ASCII num dos apêndices. O texto de saída só deverá conter letras maiúsculas ou só minúsculas.

Departamento de Ciência da Computação-UFF

Números de página

FUNÇÃO main() COM PARÂMETROS

Até o momento, trabalhamos com a função principal main() como se ela não tivesse parâmetros. Naverdade, os parâmetros desta função são "opcionais". Eles são usados para passar dados para a execução doprograma no momento de sua invocação. Expliquemos melhor com um exemplo :

prog par1 par2 ... parn

onde prog é o nome de seu programa e par1, par2,..., parn são parâmetros de entrada. (Uma pergunta surge:porque as aspas no opcionais acima? O motivo é que main() pode ser interpretada como uma função comnúmero variável de parâmetros.)

Muito provavelmente, você já está acostumado a usar programas desta maneira. Agora você poderáfazer programas em C que aceitem dados assim. Um formato é o que se segue:

main(int argi, char *argv[])

onde argi é um inteiro que dá o número de parâmetros mais um e argv é um vetor de ponteiros para caracter dedimensão indefinida. Quero observar que argv é um ponteiro para cadeias de caracteres onde o primeiroelemento contém o ponteiro para a cadeia de caracteres correspondente ao nome do programa executável, osegundo contém outro ponteiro para uma cadeia de caracteres seguinte ao nome do programa, e assim pordiante. Chamemos a atenção que a primeira cadeia de caracteres contem o nome do programa invocado.

Vamos dar um exemplo de utilização destes parâmetros, reescrevendo o sexto programa :

#include <stdio.h>#define N_PARM 3

void permutar(int *a, int *b) { int auxiliar;

auxiliar = *a; *a = *b; *b = auxiliar; }

main(int argc,char *argv[]){int a,b,c; if (argc < N_PARM) { puts("Entre com dois numeros inteiros separados por espaco "); scanf("%d %d",&a,&b); }

else { a = atoi(argv[1]); b = atoi(argv[2]); } permutar(&a,&b); printf(" %d %d",a,b);}

Departamento de Ciência da Computação-UFF

Números de página

Este programa esperará que os dados entrem logo após a declaração do nome do programa executável.Se o número de parâmetros (lembre-se, esses são cadeias de caracteres) for menor que 3 (número de parâmetros"úteis" mais um), o programa solicitará os dados de entrada.

Já vimos que quando referenciamos um vetor, na verdade estamos falando de ponteiros. Logo oargumento *argv da função main() pode ser trabalhado também como um ponteiro para ponteiro, ou seja algoque guarda um endereço que com tem outro endereço onde (finalmente) se encontra uma informação. Ou seja,podíamos definir main() como

main(argc, **argv)

Algumas vezes trabalhar assim é mais interessante.Outro ponto a chamar a atenção é que main() apesar de ser uma função especial ela é uma função com

todas as atribuições e "deveres". Portanto, ela devolve um valor. A pergunta é a quem?

PARA QUEM main() RETORNA UM VALOR

Não é difícil de saber: a quem disparou a execução do programa, mais comumente o sistemaoperacional mas não sempre. Você pode escrever programas que disparam (ou são disparados por) outrosprogramas uma necessidade óbvia para uma linguagem que pretende ser a base de um sistema operacional.

Para indicar que o parâmetro de retorno de main() não tem significado relevante para as finalidades doprograma podemos escrever então

void main(argc, **argv)

ou a outra forma equivalente.

Departamento de Ciência da Computação-UFF

Números de página

O COMANDO #define (do preprocessador)

Falando de inicialização, vamos apresentar outro comando do preprocessador que, muitas vezes, éusado na inicialização de constantes. Mas veremos que ele é mais que isto. Através dele, poderemos criardefinições que serão substituídas em tempo de compilação, ou seja, fazer macro substituições. O seu formato é

#define nome definicao

Por exemplo,

#define TAMANHO 15 #define frase "Esta e uma frase"

No programa que contiver tais definições, o compilador fará substituições nos pontos em que surgiremcada nome acima.

Vamos fazer mais um programa no qual acharemos o maior elemento, não negativo, num vetor dado.

#include <stdio.h>

#define PI 3.1415926#define E 2.7182818

float vetor[5] = {1.4142135, PI, E, 1.7320508, 0.5772156};

main(){float maior = vetor[0];int contador;

for (contador = 4; contador; contador--) { if (maior < vetor[contador]) maior = vetor[contador]; }

printf(" O maior elemento do vetor dado e %f\n",maior);}

Observe a maneira pouco ortodoxa do uso do critério de parada do for, levando em consideração que o zerosignifica uma condição falsa e estando o for com contagem regressiva. Note ainda que foi adotada a convençãode notar o valor de constantes em letras maiúsculas.

Façamos uma variação do programa de determinar os elementos da seqüência de Fibonacci, jáconhecida nossa. Aqui usaremos mais uma forma de achar esta seqüência. Podemos achar o n-ézimo termo dasérie de Fibonacci usando a fórmula

Fa b

a bn

n n

+ = + = + = −1 5

1 52

1 52

; ;

Temos abaixo a nova implementação.

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>#include <math.h>

#define raiz_quadrada_de_cinco sqrt(5)

main(){double alfa = (1 + raiz_quadrada_de_cinco)/2, beta = (1 - raiz_quadrada_de_cinco)/2, f;int n = 15;

f = (pow(alfa, n) + pow(beta, n))/raiz_quadrada_de_cinco;

printf(" o elemento %d da serie de Fibonacci e %f", n, f);}

Observe aqui mais uma biblioteca <math.h>, que contém as funções matemáticas mais usadas (veja relação noapêndice). As funções aqui usadas são pow() que acha a potência de um número por outro e sqrt() que calcula araiz quadrada de um número. Observe ainda mais uma maneira de usarmos o #define. Não estamos atribuindo àraiz_quadrada_de_cinco o valor de sqrt(5). O que o #define faz é substituir, em tempo de compilação, osidentificadores que encontrar pelo que se segue na sua definição. Podemos então fazer coisas bem maiscomplexas com este comando: MACROS. Uma macro é um conjunto de caracteres que pode ser entendido comouma pequena função. Por exemplo, a função getchar() é geralmente definida como uma macro a partir dafunção getc(). Como exemplo de utilização, rescrevamos um programa.

#include <stdio.h>

#define TETO 9#define tetof(a,b,teto) a + b > TETO ? TETO : a + b

main(){int a = 5, b = 3, c;

c = tetof(a, b, TETO); }

Observe que devemos tomar cuidado com as macros. Se usamos parâmetros que são manipulados empontos distintos da mesma, podemos ter surpresas desagradáveis. Ao usarmos teto() da forma

teto(i++, j++, teto)

teremos uma reação estranha pois teremos a equivalência desta expressão à abaixo

i++ + j++ > TETO ? TETO : i++ + j++

Assim os valores de i e j terão seus valores incrementados duas vezes e o resultado estará incorreto.

Departamento de Ciência da Computação-UFF

Números de página

Para que fique claro o alcance das macros, abaixo temos mais um programa onde definiremos a partirda função printf() uma macro de impressão.

#include <stdio.h>

#define imprime(x) printf(" %d \n", x)

main(){int a = 1, b = 2, c = 3;

imprime(a);imprime(b);imprime(c);}

Apesar de termos de tomar cuidado é extremamente útil o uso das macros. Para dar exemplos, várias“funções” são definidas como macros como, por exemplo, getcha() e putchar() além de muitas outras.

Naturalmente deve ter surgido a dúvida :

Quando usar uma macro em substituição de uma função?

Não é difícil de ver que ao definirmos algo como uma macro, ganharemos tempo durante oprocessamento. Isto fica claro se lembrarmos que ao se chamar uma função, são copiadas as variáveis dosparâmetros, criada uma área de trabalho e ao final da execução, mais informações podem ter que ser salvas. Namacro substituição tais fatos não acontecem. No entanto, por serem geradas várias cópias de uma mesmafunção, teremos um arquivo executável maior. Devemos pesar os prós e os contras nesta situação.

Departamento de Ciência da Computação-UFF

Números de página

MAIS COMANDOS DO PREPROCESSADOR

O preprocessador tem por finalidade permitir que variáveis chave ou expressões sejam facilmentemodificadas e compartilhadas entre vários arquivos que constituem seu programa fazendo com que o programaseja minimamente perturbado na sua escrita caso estas variáveis ou expressões sejam modificadas. Permitisseassim a compilação condicional, ou seja, definindo o ambiente onde o programa será compilado o própriocódigo (na figura do preprocessador) se arrumará convenientemente. Serve, portanto, para facilitar ainda mais atransportabilidade dos programas escritos em C.

No total, os comandos do preprocessador são os seguintes

#define #include undef#if #ifdef #ifndef#else #endif #line#error @ #pragma @ #elif @# (comando nulo) @

@ Extensão ANSI

Os comandos #define e #include já nos são conhecidos e vamos aqui descrever os principais, primeirode maneira compacta e após isto faremos um exemplo para esclarecer o uso de alguns destes comandos.

#undef nome - remove a definição de nome.Este é útil, por exemplo, para forçar que uma função seja exatamente isto e não uma macro.

#if expressão - testa a expressão, necessariamente inteira.

#ifdef nome - testa se nome já foi definido. Se verdadeiro, o que se segue a este comando será levado emconsideração.

#ifndef nome - testa se nome não foi ainda definido. Se verdadeiro, o que se segue a este comando será levadoem consideração.

#else - se o teste feito em #if, #ifdef ou #ifndef for falso, o que vier depois deste comando será levado emconsideração.

#endif - finaliza um comando #if

Como exemplo, vamos supor que você esteja desenvolvendo um sistema que trabalhará em várioscomputadores, compiladores ou sistemas operacionais. É claro que haverá diferenças, mas seu programa seráessencialmente o mesmo a não ser por diferenças contidas em bibliotecas, cada uma adaptada ao conjuntosistema operacional/compilador sob o qual você estiver trabalhando. Você poderia então criar umpreprocessador do seguinte tipo, caso as opções fossem feitas entre sistemas operacionais:

Departamento de Ciência da Computação-UFF

Números de página

#define DOSXX 0#define DOSYY 1#define DOSZZ 2

#define SISTEMA_OPERACIONAL DOSXX

#if SISTEMA_OPERACIONAL == DOSXX#include <xxgraph.h>#include <xxkb.h>#else#if SISTEMA_OPERACIONAL == DOSYY#include <yygraph.h>#include <yykb.h>#else#include <zzgraph.h>#include <zzkb.h>#endif#endif

Observe que basta modificarmos a definição do sistema operacional para que o preprocessador se encarregue detodas as operações necessárias.

Nos exemplos deste livro, sempre temos os comandos do preprocessador antes de qualquer função. Istoé muito comum mas não é limitado a apenas esta forma. Podemos colocar os comandos do preprocessador emqualquer ponto. Portanto, poderemos ter um programa camaleônico, que assume várias formas dependendo deparâmetros dados inicialmente. Claramente esta possibilidade colabora com a portabilidade de um produto.

Departamento de Ciência da Computação-UFF

Números de página

VARIÁVEIS ESTÁTICAS (static)

Sabemos que os valores das variáveis locais simplesmente deixam de existir quando saímos dos blocosnas quais estão definidas. Com isso se economiza memória pois evita que variáveis que, muitas vezes, sãoutilizadas de maneira marginal, ocupem uma área de memória inutilmente. No entanto, algumas vezes, éinteressante que alguma variável local mantenha o seu valor entre chamadas. Para que isto seja feito, énecessário que você de a ela a classificação especial que chamaremos de tipo estática. Para isto, o formato é oque se segue:

static TIPO nome_da_variável;

Com isto, o valor da variável irá "sobreviver" entre as chamadas. Façamos um exemplo simples no qual criamos uma função que gera números aleatórios, ou melhor,

pseudo-aleatórios entre 0 e 1. (Para maiores informações sobre números aleatórios e algoritmos para suageração, consultar as referências [].)

#include <stdio.h>

float aleatorio(void) { static long semente = 1; float auxiliar;

semente = (semente * 32719 + 3) % 32749; auxiliar = semente/32749; return (auxiliar); }

main(){int i;float aleatorio();

for (i = 10; i; i--) printf(" numero aleatorio %f\n",aleatorio()); }

Na verdade, este exemplo não funcionará! A saída terá apenas valores nulos. Isto não se deve aofuncionamento do static ou do algoritmo. O que esta acontecendo é que no momento que esta havendo a divisãosemente/32749 dentro da função, não estão sendo feitas as conversões numéricas que esperávamos. Asconversões correm são as que se seguem: o número 32749 é identificado como do tipo int e teremos então umaoperação mista, já que semente é long int. Há então a conversão de tipo da constante 32749 para long int eentão é feita a divisão. Neste momento, no momento de atribuir o resultado da operação à variável auxiliar,haverá outra conversão, desta vez para o tipo float. Observe que se na divisão o dividendo for menor que odivisor teremos zero como o valor de auxiliar. Para termos o resultado correto deveremos modificar estaconversão automática.

Para que nosso programa funcione convenientemente, basta colocar (float) (ver Conversão de Tipos)depois da barra de divisão e antes do número, ou seja :

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>

float aleatorio(void) { static long semente = 1; float auxiliar;

semente = (semente * 32719 + 3) % 32749; auxiliar = semente/(float)32749; /* linha modificada */ return (auxiliar); }

main{int i;float aleatorio();

for (i = 10; i; i--) printf(" numero aleatorio %f\n",aleatorio());}

O funcionamento agora será correto já que, o (float) forçará que a constante 32749 seja convertida para float.No momento da divisão, teremos a conversão automática de semente para float e então será atribuído a auxiliarum valor do mesmo tipo.

A saída deste programa será um conjunto de números os quais, aparentemente, não tem nenhumarelação um com os outros. É bom chamar a atenção que o nome aleatório só tem sentido se considerarmos oconjunto de todos os pontos gerados.

Exercício

Experimente executar este programa sem o uso de static.

Departamento de Ciência da Computação-UFF

Números de página

VARIÁVEIS EXTERNAS (extern)

Um aspecto da linguagem C é que ela permite, e até incentiva, que você crie programas em móduloscompilados separadamente. A vantagem disto está no fato que o tempo de compilação toma boa parte do tempode desenvolvimento de um determinado projeto. Muitas vezes, o defeito ocorre num determinado ponto oufunção e seria uma grande perda de tempo compilar todo um programa por causa de um erro destes.Compilando módulos do programa separadamente, poupasse tempo mas cria um problema.

Já falamos das variáveis globais. Imagine que eu precisa dentro de um destes módulos de uma variávelglobal que esteja em outro módulo. Neste caso, ao compilar todo o programa, teremos uma mensagem de erronos dizendo que há variáveis duplicadas se declararmos as mesmas variáveis globais em todos os módulos. Secompilarmos um módulo separadamente e este não contiver as variáveis globais que referencia, haverá aemissão de outro erro nos dizendo que existem variáveis não declaradas.

A maneira de sair desta sinuca é justamente o uso da declaração extern. Declarando num módulo certasvariáveis como extern, você avisa que aquelas variáveis estão declaradas num outro módulo do programa. Aforma da declaração das variáveis extern é a que se segue :

extern TIPO variável1, variável2,..., variáveln

Esta declaração deve sempre estar contida dentro dos módulos que necessitem usar as variáveis globaisdefinidas em outro módulo.

Departamento de Ciência da Computação-UFF

Números de página

VARIÁVEIS REGISTRADOR (register)

Uma linguagem de programação comum, permite que você tenha variáveis só na memória docomputador o que obriga ao compilador, quando trabalha com tais variáveis, a ler um valor na memória, leva-lopara uma memória interna do processador central chamada de registrador, pegar outra variável colocando-a emoutro registrador e só então opera-las. Mas ainda não acabou! Agora o compilador terá que remeter o valorcalculado a posição de memória correspondente a variável que recebe as duas variáveis operadas. Ufa! Aconteceque C permite que parte deste trabalho não precise ser feito. Você poderá declarar uma determinada variávelpara que esta esteja guardada não em memória mas diretamente num registrador. Dá para perceber quepouparemos um bocado de tempo.

A declaração de variáveis do tipo register permite esta facilidade. A forma geral é a que se segue :

register TIPO var1, var2, ..., varn

Vamos a um exemplo de utilização, modificando (mais uma vez) programa feito anteriormente.

#include <stdio.h>

main() { register int n; float x; x = 235.;

for ( n = 1; n <= 10; n++) { x = x + x; if ( x >= 2000.) break; } }

O resultado da declaração da variável n com o do tipo register, é que o tempo de execução do laço for serámenor pois não haverá a necessidade de "se ir" à memória pegar o valor desta variável para fazer comparações eoperações de incremento.

Vemos, portanto, que vale mais a pena definirmos uma variável como register quanto mais usada elafor.

O número de variáveis do tipo register permitidas e seu tipo, são dependentes do computador e docompilador usados. Consulte os manuais de seu compilador. Geralmente se você define mais variáveis do tiporegistrador que o conjunto computador-compilador pode trabalhar, o próprio compilador reconverte a variávelpara uma do tipo comum.

Mais um aviso: Não use variáveis register quando a variável for referenciada por endereço. Afinal decontas estas variáveis não estão na memória e sim num registrador.

Departamento de Ciência da Computação-UFF

Números de página

RESERVANDO MEMÓRIA PARA USO TEMPORÁRIO (ou ALOCANDO MEMÓRIA )

Muitas vezes estamos trabalhando com dados que não precisam estar declarados o tempo todo. Ouainda, estamos com pouca memória disponível. A linguagem C permite que reservemos memória para usotemporário e que, logo que não haja mais a necessidade dela, possamos libera-la.

Isto é feito pelo uso de algumas funções da biblioteca padrão, que são listadas abaixo junto com suasdescrições :

a) void *malloc(unsigned int número_de_bytes) - Reserva um determinado número de bytes para usotemporário. Se esta reserva não for possível, será devolvido um nulo.

b) void *calloc(int número_de_elementos, int tam_dos_elementos) - Reserva memória determinadapelo produto do número de elementos pelo tamanho de cada elemento (em bytes). A memória reservada serápreenchida com zeros. Devolve um ponteiro não nulo em caso de operação correta.

c) void free(void *pt) - Disponibiliza para outros usos a memória reservada pelo uso de malloc() oucalloc(). A referência é feita pelo ponteiro devolvido pelas funções.

A área de memória reservada se encontra em posições contíguas na memória livre do computador.Damos abaixo um exemplo de uso destas funções.

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <conio.h>#include <dos.h>

#define NLINHAS 24#define NCOLUNAS 80#define SLEEPTIME 3

void main(void){char **tela,

*s,ch;

int linha, nchar;

s = (char *)malloc(NCOLUNAS + 1);

tela = (char **)malloc(NLINHAS);

puts("\nInicie o texto\n\n");

linha = 0;

do { gets(s); nchar = strlen(s);

tela[linha] = (char *)malloc(nchar);

strcpy(tela[linha], s); linha++; }while (linha < NLINHAS);

Departamento de Ciência da Computação-UFF

Números de página

puts("\n\n\n Aperte C(ontinue) para ver a tela");

do{ch = toupper(getch());}

while (ch != 'C');

linha = 0;

do { strcpy(s, tela[linha]);

puts(s); linha++; }

while (linha < NLINHAS);

sleep(SLEEPTIME);

puts("\n\n\n Liberando a memoria");

for (linha = 0; linha < NLINHAS; linha++) free(tela[linha]);

sleep(SLEEPTIME);}

Neste programa guardamos os dados de uma tela que é fornecida pelo usuário. Após isto, escrevemosesta tela. Para isto, criamos um ponteiro para ponteiro do tipo char com a finalidade de criarmos um vetor deponteiros para caracteres (no caso a variável tela) além de um ponteiro para caracteres (s) que servirá paraguardar temporariamente cada linha fornecida. Repare na moldagem feita no ponteiro devolvido por malloc()para os tipos que necessitamos.

Se no lugar de um vetor de ponteiros tivessemos colocado uma matriz com o tamanho da tela (80x24),estaríamos ocupando tanto para uma tela cheia quanto para uma tela vazia, o mesmo espaço em memória. Com aestratégia aqui adotada, se tivermos uma tela vazia, para cada linha só será ocupada por um único caracterer, nocaso o caracter \0 terminador de cadeia de caracteres. Assim o espaço reservado será dinamicamente adaptado àsnecessidades da tarefa que tiver sendo processada.

Observe ainda o uso da função sleep() que faz o processamento se interromper por um determinadonúmero de segundos. Esta função está referenciada no arquivo de inclusão dos.h sendo, portanto, dependentenão só de implementação como também do sistema operacional.

FRAGMENTAÇÃO

Um fenômeno que surge se temos um programa que faz uso de alocação de memória de formasistemática, é a chamada fragmentação. Este fenômeno pode nos levar a acreditar que não temos mais memóriadisponível. Para melhor compreensão, vamos exemplificar.

Suponhamos que você inicialmente disponha de 100KB de memória e faça uma reserva de 30KB,seguida de outra de 50KB. Teremos 20KB de memória livre para outros usos. Se você libera os primeiros 30KB,teremos, é claro, o total de 50KB. Caso você tente reservar neste momento 40KB, receberá a indicação dasfunções de alocação de memória que não há espaço disponível. Observe que isto ocorre porque o espaçoalocado poderá estar em posições contíguas na memória. Portanto se você fizer uso sistemático de alocaçãodinâmica de memória, faça um planejamento de seu uso durante o projeto do sistema.

Departamento de Ciência da Computação-UFF

Números de página

ESTRUTURAS

Estruturas são coleções de variáveis que podem ser referenciadas pelo mesmo nome. Estas variáveissão ligadas logicamente. A forma geral é a que se segue

struct nome_da_estrutura { TIPO variável1; TIPO variável2; . . TIPO variáveln; } estrutura1, estrutura2,...,estruturan;

A referência a cada elemento é feita através do operador .(ponto), da seguinte forma :

estrutura.elemento

Chamemos a atenção que se for definida só uma estrutura, não há necessidade de nome_da_estrutura. Também,se você quiser, poderá declarar as estruturas através de seu nome, como se fosse um tipo mas antes escrevendo apalavra reservada struct.

Exemplos:

a) struct { char nome_do_ocupante[20]; int apartamento; char bloco; } conjunto_j_k;

conjunto_j_k.nome_do_ocupante = "Edeunilde Braga"; conjunto_j_k.apartamento = 301;

b) struct agenda { char nome[20]; char telefone[8]; char endereço[30]; char assunto[40]; char dia[9]; };

struct agenda comercial, pessoal;

pessoal.nome = "Marieta Lanolina"; pessoal.telefone = "266-5672"; pessoal.endereço = " Anfilofio Benevides, 30"; pessoal.assunto = "Teatro as 21.00" pessoal.dia = "22/04/89"

Departamento de Ciência da Computação-UFF

Números de página

c) struct pais { char municipio[20]; long int população; char unidade_federativa[3]; long int população_por_u_f; } argentina, brasil, paraguai, uruguai;

brasil.municipio = "Niteroi"; brasil.população = 1350000; brasil.unidade_federativa = "RJ"; brasil.população_por_u_f = 13250000;

Com estes exemplos, principalmente no caso de agenda, dá a impressão de certa limitação já queaparentemente não se conseguiria cadastrar mais de uma pessoa. O que se pode fazer é declarar um vetor deestruturas. Por exemplo,

struct agenda pessoal_todo_ano[365];

A referência aos elementos é feita de maneira análoga, por exemplo

pessoal_todo_ano[120].nome = "Briareu Titanita";

Você poderá, ainda, criar estruturas que contenham estruturas. Por exemplo,

struct endr { char rua[20]; int número; char complemento[10]; };

struct dados { char nome[30]; struct endr endereço; }dados_pessoais;

Você referenciará os elementos da seguinte maneira

dados_pessoais.endereço.rua = "Temostenes Guaranhaes";

Com a definição de uma estrutura de dados, fica, por exemplo, mais fácil e clara a definição de camposnum determinado conjunto de informações.

Departamento de Ciência da Computação-UFF

Números de página

Estruturas e Manipulação de Bits

Você já viu que C pode manipular a nível de bits e também pode usar as estruturas para facilitar o seutrabalho. Vamos supor que você está comandando um dispositivo (uma impressora, um traçador de gráficos,etc) cujo controle seja feito pela manipulação dos bits 0, 1, 6 e 7, ficando os bits 2, 3, 4 e 5 sem função.Podemos criar a seguinte estrutura:

struct acionador { unsigned up: 1; unsigned down: 1; int : 4; int side: 2; };

As variáveis declaradas como inteiras, são armazenadas em complemento de dois e o seu bit de maisalta ordem trabalha como bit de sinal. Portanto tenha cuidado quanto trabalhar com estruturas deste tipo; podeocorrer fenômenos estranhos caso haja algum engano.

Observemos que acionador é uma struct de um byte. Alguns compiladores criam restrições quanto otamanho das variáveis struct. Também aqui consideramos que a ordem de preenchimento da variável é a do bitde mais baixa ordem para o de mais alta. Podem haver variações.

Departamento de Ciência da Computação-UFF

Números de página

ESTRUTURAS E FUNÇÕES

Pode surgir a pergunta: Como passar uma estrutura ou seus elementos para uma função? Se você que passar os elementos, deve proceder de maneira habitual já que cada elemento da estrutura

é uma variável comum. Por exemplo, seja a estrutura

struct teste { int i; float x; char ch; } teste1;

Se você que passar o elemento i para a função fatorial(), deve então escrever

fatorial(teste1.i);

A coisa se complica um pouco se queremos passar toda a estrutura. No caso do C padrão K & R não hácomo fazer isto. O que se faz é passar um ponteiro para a estrutura e seus elementos. Na versão ANSI C, taloperação é permitida. Observe que estruturas não são vetores, apesar de trabalhados de forma semelhante pelasfunções, já que o identificador de uma estrutura não é por si um ponteiro para um objeto homogêneo comovetores.

Vamos supor que temos a mesma estrutura acima mas você queira "passar toda a estrutura" para umafunção. A referencia dos elementos seria feita por ponteiros, da seguinte forma

(*nome_da_estrutura).elemento_da_estrutura

Os parênteses são necessários devido a precedência dos operadores. Vejamos um exemplo.

#include <stdio.h>

struct teste { int i; float x; char ch; } teste1;

funcao(struct teste *e) { (*e).x = (*e).x * (*e).i;

return((*e).x); }

main() { puts(" Entre com um numero inteiro e um flutuante separados por branco"); scanf("%d %f",teste1.i,teste1.x);

printf("saída da funcao %f",funcao(&teste1)); }

Poderíamos simplificar a escrita usando o operador seta que, na verdade, é equivalente ao conjunto daatuação do operador * e o . em estruturas. Com este operador, a referência a um elemento via ponteiro fica

nome_da_estrutura->elemento_da_estrutura

Reescrevendo o exemplo temos

#include <stdio.h>

Departamento de Ciência da Computação-UFF

Números de página

struct teste { int i; float x; char ch; } teste1;

funcao(struct teste *e) { e->x = e->x * e->i;

return(e->x); }

main(){/* Decimo programa, segunda versao */

puts(" Entre com um numero inteiro e um flutuante separados por branco"); scanf("%d %f",teste1.i,teste1.x);

printf("saída da funcao %f",funcao(&teste1));}

Assim fica menos tedioso de escrevermos as referências entre os elementos da estrutura.

Departamento de Ciência da Computação-UFF

Números de página

UNIÕES

Uma uniões funciona de uma maneira semelhante a struct porém, as variáveis selecionadascompartilham o mesmo espaço em memória. Sempre é reservado espaço suficiente para a mais longa dasvariáveis definidas. A idéia, claro, é poupar memória e organizar certas estruturas de dados. A forma dedeclaração é a que se segue

union NOME_DA UNIÃO { TIPO VARIÁVEL1; TIPO VARIÁVEL2; . . TIPO VARIÁVELn; } UNIÃO1,UNIÃO2,...,UNIÃOn;

Novamente, NOME_DA_UNIÃO e UNIÃO1...UNIÃOn são opcionais. A referência a um dos elementos é feitade maneira análoga a das estruturas. Referenciando um elemento, fazemos uso do operador ponto, como abaixo

UNIÃO1.ELEMENTO

e também podemos manipula-las pela obtenção do seu endereço via operador &. Podemos, ainda, ter comoelementos da união não só variáveis como também estruturas.

Vamos a um exemplo. Lembre-se que C tem a intenção de substituir o assembler, sempre que possível.Vamos, então, supor que você vá trabalhar num computador no qual a CPU trabalhe com bytes, palavras de doisbytes e palavras de quatro bytes e seus registradores sejam de quatro bytes. Observe que o conteúdo dosregistadores pode então ser trabalhados em bytes, palavras ou palavras duplas mas, o conteúdo do registradorserá um só. Podemos, então, criar uma união da seguinte forma

union registrador { long int dword; int word; short int byte; };

A variável union terá o tamanho do maior dos elementos contidos na sua declaração, que é o long int de quatrobytes. A referência ao byte de mais baixa ordem será feita da seguinte forma

registrador.byte = 10;

Esta declaração irá atribuir ao byte de mais baixa ordem do registrador o valor 10. Da mesma forma, sequeremos saber o valor do conteúdo total do registrador poderíamos escrever

printf(" Conteudo do registrador = %ld ",registrador.dword);

Departamento de Ciência da Computação-UFF

Números de página

Um exemplo não tão sofisticado daremos abaixo.

#include <stdio.h>

#define CHAR 0#define INT 1#define FLOAT 2#define DOUBLE 3

union Var { char c; int i; float f; double d; };

main(){union Var v;int tipo;

printf(" Entre com o tipo de variavel "); tipo = getch(); printf("\n");

switch (tipo) { case CHAR: scanf(“%c”, &(v.c)); break;case INT : scanf(“ %d\n", &(v.i)); break;case FLOAT: scanf(“ %f\n", &(v.f)); break;case DOUBLE: scanf(“ %lf\n", &(v.d)); break:default : exit(0); } while(1); }

Observe que temos uma variável definida (tipo) que apesar de estar correta, está colocada de umaforma “feia” pois os valores que ela deve tomar são int mas não todos os inteiros representáveis por int. Afunção desta variável é servir para especificar um dos tipos de entrada e sua definição como está não documentao que ocorre. Além disto, não é feita uma filtragem para impedir que entremos com valores inválidos. No finaldas contas, este é mais um daqueles programas que não fazem nada a não ser esclarecer alguns aspectos dalinguagem C.

Departamento de Ciência da Computação-UFF

Números de página

INICIALIZANDO ESTRUTURAS E UNIÕES

Se tivermos estruturas ou uniões globais, poderemos inicializa-las de forma similar a que usamos paravetores. Seja, por exemplo, uma estrutura chamada coordenada inicializada com as coodenadas de Niterói struct coordenada { float latitude, float longitude } = { 23.1, 43.14 }

DETERMINANDO TAMANHOS DE UNIÕES, ESTRUTURAS E VARIÁVEIS

Aqui veremos mais uma palavra reservada que, na verdade, é uma função que trabalha em tempo decompilação sendo esta a razão desta não estar numa biblioteca. Você a utiliza da seguinte maneira

sizeof(void)

O valor devolvido por sizeof() será o comprimento em bytes da variável que esta entre parênteses declaradavoid por não ter a priori um tipo específico.

Você poderá colocar qualquer coisa entre estes parênteses, incluído uniões e estruturas. Um exemplo:

include <stdio.h>

int a,b;char letras[10] = "abcdef"

main(){a = sizeof(letras);b = sizeof(a);printf(" %d %d ", a, b);}

Em a teremos o número de elementos a cadeia de caracteres e em b teremos o tamanho da variável a, ou seja,respectivamente 10 e 2. Alguns devem estranhar o resultado, já que letras só tem seis elementos (sete contando onulo). Observem que sizeof() não dá o espaço ocupado de forma útil mas sim o espaço que foi reservado para avariável.

O uso desta função em variáveis é conveniente quando queremos fazer programas que sejamindependentes das máquinas. Por exemplo, em certos computadores o número inteiro "básico" pode não ser o dedois bytes mas sim um inteiro de quatro bytes. Isto pode surgir em equipamentos nos quais o processador centralseja de 32 bits, ou seja, 4 bytes. Portanto usando a função sizeof(), podemos criar programas que se adaptam àsmáquinas onde estiverem sendo executados.

Departamento de Ciência da Computação-UFF

Números de página

ENUMERAÇÃO

Uma situação comum é quando você cria uma série de dados que deve ser compreendidos como de umdeterminado tipo e ao mesmo tempo dar a eles valores que os distingam. Suponhamos que você pretenda definiruma tabela de cores para trabalhar com uma interface gráfica. Uma forma de fazer isto é da seguinte forma

int preto = 0, marrom = 1, vermelho = 2, laranja = 3, amarelo = 4, verde = 5, azul = 6, branco = 7;

Uma maneira elegante e poderosa de fazer o mesmo é utilizando a palavra reservada enum. A forma de uso é aque se segue

enum nome { enum1, enum2, enum3,...};

onde nome designa o novo tipo com elementos enum1, enum2, enum3, etc, que são internamente representadospor variáveis do tipo int iniciando em zero e indo até n-1, onde n é o número de elementos.

Nosso exemplo acima ficaria escrito, através do enum como

enum cores { preto, marrom, vermelho, laranja, amarelo, verde, azul, branco};

Observe que a nossa situação agora é ainda mais favorável já que podemos definir variáveis do tipo cores. Porexemplo, se uma variável deve ter as cor de fundo da tela e outra as cores das letras, poderíamos escrever

enum cores cor_de_fundo, cor_das_letras;

Isto indiscutivelmente facilita nosso trabalho.Mas na verdade temos mais flexibilidade ainda. Em certas situações pode não nos interessar que a

numeração dos elementos do enum seja seqüencial à partir de 0. Digamos que criemos uma outra enumeraçãoque represente os dias da semana desta forma que segunda-feira receba o valor 2, terça-feira valor 3 e assim pordiante e o sábado valor 7 e domingo 8. Neste caso poderíamos fazer o que se segue

enum dias {segunda = 2, terca = 3, quarta = 4, quinta = 5, sexta = 6, sabado = 7, domingo = 8};

Mas podemos ser mais concisos escrevendo da seguinte maneira

enum dias {segunda = 2, terca, quarta, quinta, sexta, sabado, domingo};

A variável terca terá o valor de segunda mais um, quarta o valor de terca mais um e assim por diante.

Resumindo: Você tem então três opções de uso para o enum :

a) enumeração automática a partir de zero;b) enumeração forçada através do operador =;c) enumeração forçada inicialmente.Vamos pegar um exemplo dado anteriormente e (novamente!) modifica-lo:

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>

enum Tipo { CHAR, INT, FLOAT, DOUBLE};

union Var { char c; int i; float f; double d; };

main(){union Var v;Tipo tipo;

printf(" Entre com o tipo de variavel "); tipo = getch(); printf("\n");

switch (tipo) { case CHAR: scanf(“%c”, &(v.c)); break;case INT : scanf(“ %d\n", &(v.i)); break;case FLOAT: scanf(“ %f\n", &(v.f)); break;case DOUBLE: scanf(“ %lf\n", &(v.d)); break:default : exit(0); } while(1); }

Em comparação com a outra versão do programa, temos uma escrita mais compacta, clara e o uso daenumeração para especificar melhor o que fazemos. A declaração de tipo não deixa dúvidas para que estavariável foi criada e quais os valores válidos.

Departamento de Ciência da Computação-UFF

Números de página

DEFINIÇÃO DE TIPOS

Aqui veremos mais uma palavra chave que nos permitirá definir novos tipos de variáveis. Definiremosum novo tipo de variável da seguinte forma:

typedef TIPO NOME;

Você poderia redefinir tipos já existentes como, por exemplo

typedef float real;

Com isso, você poderia, a partir daqui, escrever

real a,b;

Mas typedef não se limita ao que foi mostrado. Você poderá criar estruturas de dados mais complexase defini-las como um novo tipo. Por exemplo :

typedef struct endr { char rua[20]; int numero; char bloco; };

Assim, podemos definir o seguinte tipo

endr endereço;

exatamente como usaríamos para definir uma variável qualquer. O efeito seria o mesmo que se tivéssemosescrito

struct endr endereço;

As idéias básicas por traz do uso do typedef são facilitar a documentação do programa e podermosparametrizar o mesmo com finalidade de se usarmos uma variável dependente de máquina, o que será necessáriomudarmos no programa ao migrarmos de máquina para máquina, será apenas os typedef. Ou ainda, criar tiposque se "adaptam" a determinadas particularidades do programa. Como exemplo disto, aqui temos um programaque calcula a distância entre dois pontos mas que os dados de entrada podem ser pontos no plano ou no espaço,isto sendo definido por um #define.

Departamento de Ciência da Computação-UFF

Números de página

#include <math.h>

#define _3D

typedef struct ponto Ponto;

struct ponto { float x, y;#ifdef _3D float z;#endif };

float distancia(Ponto p0, Ponto p1) { float d;

d = (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p0.y) * (p1.y - p0.y);

#ifdef _3D d = d + (p1.z - p0.z) * (p1.z - p0.z);#endif

return (sqrt(d)); }

main(){Ponto pa, pb;

pa.x = 1;pa.y = 2;pa.z = 1;

pb.x = 0;pb.y = 1;pb.z = 5;

printf(" d = %f", distancia(pa, pb));}

Acima, além do exemplo de utilização do typedef, temos o uso do preprocessador de uma maneira até agora nãoapresentada. Aqui _3D está definido e, como conseqüência, o cálculo será com três dimensões. Caso queiradefinir as operações apenas no plano, basta deixar _3D sem definição.

Departamento de Ciência da Computação-UFF

Números de página

TRABALHANDO COM ARQUIVOS EM DISCO

Funções de Alto Nível

Obviamente, C também pode trabalhar com arquivos em disco. Isto também, é claro, é deixado aresponsabilidade de funções. Existem duas maneiras de acessarmos arquivos em disco. Uma é através defunções de alto nível e a outra através das funções de baixo nível, estas últimas chamadas as vezes de "tipoUnix". Aqui falaremos das de alto nível. Mas, antes disto, falaremos de um tipo de variável especial e duasconstantes predefinidas úteis e necessárias no trabalho de arquivos. O tipo é denominado FILE, e é através deleque identificamos os arquivos. Sua forma de uso é dada abaixo

FILE *ponteiro_arquivo1, *ponteiro_para_arquivo2,.....;

Ainda temos as constantes : EOF - marcador de fim de arquivo. NULL - usada sob variados aspectos.

As funções básicas são as que se seguem:

a)FILE *fopen(char *nome_arquivo, char *modo)

Esta função devolve um ponteiro para arquivo. O modo pode ser um dos abaixo:

r - ler w- escrever a - adicionar ao final de arquivo já existente b - formato binário

Dependendo do compilador que você esteja usando, poderá haver outros modos. Você poderá agrupar mais deum modo simplesmente juntando as letras acima. Por exemplo, se o arquivo for do tipo que vai ser lido emodificado, use como modo rw (leitura e escrita).

Caso o arquivo não exista, ele será automaticamente criado. Se não for possível abrir o arquivo, oponteiro devolvido será o NULL.

b)int fclose(FILE *ponteiro_arquivo)

Esta função fecha o arquivo aberto por fopen(). A referencia a qual arquivo será fechado é dado peloponteiro que é devolvido por fopen(). Devolve EOF se alguma anormalidade ocorrer.

c)int getc(FILE *ponteiro_arquivo)

Devolve um caracter do arquivo referenciado pelo ponteiro dado como parâmetro. Se chegar ao finaldo arquivo, devolve o EOF.

d)int putc(int caracter, FILE *ponteiro_caracter)

Escreve o caracter dado como parâmetro no arquivo referenciado pelo ponteiro dado como segundoparâmetro. Devolve o caracter escrito se não houver problemas e EOF caso hajam.

e)int fseek(FILE *ponteiro_arquivo, long int deslocamento, int origem)

Posiciona, para leitura ou escrita, o ponteiro que marcará o caracter a ser trabalhado pelas funções getc() e putc(). Temos como parâmetros um ponteiro para arquivo, o deslocamento a partir

Departamento de Ciência da Computação-UFF

Números de página

do ponto origem e um parâmetro que dá a posição identificada como a origem Inicialmente a origem é no iníciodo arquivo e existem alguns valores definidos para esta variável :

0 - deslocamento a partir do início do arquivo 1 - deslocamento a partir do último deslocamento2 - deslocamento a partir do fim do arquivo

Como é de suspeitar pela tabela acima, a variável deslocamento poderá ser negativa. O acesso à informação feitadesta maneira é chamada de acesso aleatório ou acesso direto ou ainda acesso randômico (embora esta palavranão signifique nada tanto em português como em inglês) . Devolve zero se o deslocamento foi feito e diferentede zero caso haja algum problema.

Damos agora um exemplo de como usar as funções acima. Simplesmente criaremos um arquivodenominado arquivo1 e escreveremos nele 100 letras a.

#include <stdio.h>

FILE *fp;

main() { int i; char ch = 'a';

if ((fp = fopen(arquivo1,"w")) == NULL) { puts("Não foi possivel abrir este arquivo\n"); exit(); }

for (i = 0; i < 100; i++) putc(ch, fp); fclose(fp); }

Observe que testamos se o ponteiro devolvido foi nulo. Caso não seja possível criar o arquivo (falta de espaçoem disco, um disquete não se encontra no drive, etc.), a reação será a impressão do aviso. Se insistirmos emgravar nestes casos, podemos ter reações imprevisíveis.

Um programa que leria os dados escritos pelo programa anterior, é dado abaixo.

#include <stdio.h>

FILE *fp; main() { int i;

if ((fp = fopen(arquivo1,"r")) == NULL) { puts("Nao foi possivel abrir este arquivo\n"); exit(); }

for (i = 0; i < 100; i++) putchar(getc(fp)); fclose(fp); }

Departamento de Ciência da Computação-UFF

Números de página

Podemos ainda fazer um programa que leia um campo arbitrário em vez de ler seqüencialmente. Istopode ser feito usando a função fseek(). O programa abaixo, escreve num arquivo em disco uma frase que vocêteclar.

#include <stdio.h>

FILE *fp;

main() { char vetor[80];

if ((fp = fopen(arquivo1,"w")) == NULL) { puts("Nao foi possivel abrir este arquivo\n"); exit(); }

gets(vetor); while (*vetor) putc(*vetor++, fp);

fclose(fp); }

A recuperação destes dados, pode ser feita de forma aleatória (ou direta) usando o programa que sesegue

#include <stdio.h>

FILE *fp;

main() { if ((fp = fopen(arquivo1,"r")) == NULL) { puts("Nao foi possivel abrir este arquivo\n"); exit(); }

fseek(fp,20L,0); putchar(getc(fp));

fclose(fp); }

Este programa lerá o vigésimo caracter a partir do início do arquivo. Até o momento, sempre sabíamos a quantidade de elementos que estávamos lendo. Muitas vezes, esta

informação é desconhecida e, sem querer, poderíamos sair lendo informações falsas. Há uma maneira de lerdeterminadas informações sem o perigo de atropelarmos outras. Basta verificar se chegamos ao final do arquivocomparando cada caracter lido com a constante pré-definida EOF (End Of File) que, como o nome diz, marca ofim do arquivo. A seguir um exemplo de uso.

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>

FILE *fp;

main() { char ch;

if ((fp = fopen(arquivo1,"r")) == NULL) { puts("Nao foi possivel abrir este arquivo\n"); exit(); }

while (ch = getc(fp)) != EOF) putchar(ch); }

Observemos mais uma vez que as entradas e saídas em C são voltadas para trabalho com caracteres e,nem sempre queremos nos preocupar com ler ou escrever valores em arquivos, fazendo a conversão deles paracaracteres. Temos, para estes casos, duas funções que facilitam o nosso trabalho:

int fprintf( *FILE , *char, par1,..., parn);

int fscanf( *FILE, *char, *par1, .., *parn);

O funcionamento é idêntico à printf() e scanf(). Obviamente, usar estas funções não trazem sóvantagens. Elas são lentas e relativamente grandes. Se você necessitar de velocidade ou economia de espaço,pode ser que a comodidade saia caro. Caso velocidade seja a palavra chave, você deverá criar suas própriasfunções específicas.

Vamos a um exemplo de uso destas funções

#include <stdio.h>

FILE *fp;

main() { int i = 1; char x = 'a';

if ((fp = fopen(arquivo1,"rw")) == NULL) { puts("Nao foi possivel abrir este arquivo\n"); exit(); }

fprintf(fp, "%d%c", i, x); fscanf(fp, "%d%c", i, x);

printf("\n inteiro %d, caracter %c \n", i, x);

fclose(fp); }

EXERCÍCIO

I) Faça um programa que leia um arquivo e crie outro com extensão .sai, onde do arquivo original todoe qualquer caracter que não seja uma letra seja substituído por um espaço em branco. Dica : veja as funções doarquivo de inclusão ctype.h.

Departamento de Ciência da Computação-UFF

Números de página

ARQUIVOS PADRÕES E COMO USAR A IMPRESSORA

Mesmo que você não trabalhe com nenhum arquivo de maneira explícita, sempre que um programa emC inicia a sua execução, são abertos três arquivos :

stdin - entrada padrão stdout - saída padrão stderr - saída padrão para erros

Estes arquivos são, geralmente, associados a console mas podem ser "desviados" quando houvernecessidade. O que permitirá ou proibirá este desvio, são o sistema operacional que você estiver usando comotambém do compilador com o qual você estiver trabalhando. Consulte os manuais técnicos para saber de suaslimitações. Estes arquivos, da mesma forma que são abertos ao iniciar a execução do programa, são fechados aotérmino do mesmo.

C enxerga qualquer dispositivo como um arquivo sem se preocupar exatamente com "o que ele é". Istofacilita muito a vida do programador quando é necessário desviar a entrada ou a saída de um programa como jáfoi dito acima.

Com as idéias apresentadas, poderíamos escrever putchar() a partir da função putc():

putchar(ch)char ch; { putc(ch,stdout); }

Existem outros arquivos padrão. Um é o arquivo correspondente a impressora normalmentedenominado stdprn. Através dele, você poderá desviar a saída do seu programa para a impressora usando afunção fprintf() bastando especificar como arquivo o já citado stdprn. Assim, poderíamos pensar na printf()como um caso especial da fprintf() quando o arquivo referenciado é o stdout.

Exemplificaremos este uso com o seguinte programa, uma variação da Torre de Hanói com saída paraimpressora :

#include <stdio.h>

#define TITULO "\nTorre de Hanoi. Digite o nro. de discos."

char posicao[][11] = {"a esquerda","o meio","a direita"};

void mova(n, daonde, auxiliar, praonde)short n, daonde, auxiliar, praonde; { if(n != 1) mova(n - 1, daonde, praonde, auxiliar);

fprintf(stdprn, "Mova o disco d%-s para %-s", posicao[daonde], posicao[praonde]);

if(n != 1) mova(n - 1, auxiliar, daonde, praonde); }

Departamento de Ciência da Computação-UFF

Números de página

main(){enum {ESQUERDA, MEIO, DIREITA};char *cadeia[20];short discos;

while ((puts(titulo), discos = atoi(gets(cadeia))) != 0) { puts("Coloque os discos a esquerda e faca os movimentos");

fprintf(stdprn, "Execute os seguintes movimentos");

mova(discos, ESQUERDA, MEIO, DIREITA);

puts("Ok! Pilha da esquerda totalmente transferida para direita"); }}

Note as diferenças desta versão e da anterior. Além do uso do fprintf() temos o uso do enum paradefinir ESQUERDA, MEIO e DIREITA. Além disto, veja a solução para a "monstruosidade" da atribuição deuma cadeia de caracteres à um ponteiro sem reservar memória para tal: Um simples define. Resultado: umprograma mais limpo e mais claro.

Departamento de Ciência da Computação-UFF

Números de página

FUNÇÕES DE BAIXO NÍVEL

Temos aqui um outro conjunto de funções de entrada e saída para trabalho com arquivos. Adenominação "Baixo Nível", obviamente, não tem nenhum carretar pejorativo. A razão desta denominação estáno fato que no conjunto anterior de funções (as de "Alto Nível"), você não se preocupava de especificar eseparar uma determinada região de memória para servir de área intermediária entre seu programa e o arquivoem disco (esta área de memória muitas vezes é chamada de Buffer). No conjunto de funções que descreveremosabaixo (as de "Baixo Nível"), deveremos fazer a especificação destas áreas intermediárias. Em troca de umpouco mais de trabalho, teremos maior velocidade, flexibilidade e uma atuação otimizada.

Devemos chamar a atenção para o fato que estas funções não trabalham com ponteiros associados aosarquivos mas com inteiros denominados "descritores". Vamos às funções:

a) int creat(char *nome_arquivo, int modo_arquivo)

Esta função permite criar um arquivo que terá o nome nome_arquivo e será aberto num modoespecificado por modo_do_arquivo. O padrão ANSI tem definidos vários modos entre os quais os abaixo :

O_RDONLY - somente para leituraO_WRONLY - somente para gravaçãoO_RDWR - para leitura e gravação

As definições destes modos estão nos arquivos <fcntl.h> (UNIX V) ou <sys/file.h> (BSD).Devolve um inteiro (o descritor) associado ao arquivo criado. Se o arquivo não puder ser criado, é

devolvido -1.

b) int open(char *nome_arquivo, int modo)

Abre um arquivo já existente num determinado modo especificado como os acima mostrados em creat().

Devolve o descritor do arquivo aberto. Se o arquivo não puder ser aberto, é devolvido -1.OBS: Dependendo do compilador que você estiver usando, a função open() poderá fazer as vezes o papel dafunção creat().

c) int close(int descritor)

Fecha o arquivo associado ao descritor dado como argumento. Se não for possível fechar o arquivosolicitado, será devolvido -1.

d) int read(int descritor, void *buffer, int tamanho_do_buffer)

Le informações do arquivo em disco para a área de memória especificada por buffer. O tamanho destaárea é especificada por tamanho_do_buffer. Caso a leitura seja feita, a função devolverá o número de caractereslidos. Caso haja algum problema, será devolvido -1. Caso o valor devolvido seja 0 (zero), isto indicará que oarquivo chegou ao seu final.

e) int write(int descritor, void *buffer, int tamanho_do_buffer)

Escreve informações do arquivo em disco identificado pelo descritor para a área de memóriaespecificada por buffer. O tamanho desta área dado por tamanho_do_buffer. Se não houver nenhum problemaao se escrever no arquivo o pretendido, a função devolverá o número de caracteres escritos. Caso haja algumproblema, será devolvido -1.

*OBS : Poderá haver restrições quanto ao tamanho do buffer nas funções read() e write(). Pode ser que otamanho do espaço reservado seja um múltiplo de uma determinada potência de dois ou algo análogo. Em casode dúvida, consulte seus manuais.

Departamento de Ciência da Computação-UFF

Números de página

f) long lseek(int descritor, long deslocamento, int origem)

Faz o acesso aleatório ao arquivo associado ao descritor fornecido. O deslocamento será contado apartir dos valores tomados pelo referenciado em origem com os seguintes efeitos:

origem Efeito

0 deslocamento em relação ao início do arquivo 1 deslocamento em relação a posição atual 2 deslocamento em relação ao final do arquivo Caso não seja possível o acesso ao arquivo, será devolvido um -1.

g) int unlink(char *nome_arquivo)

Apaga o arquivo referenciado por nome_arquivo. Caso não seja possível apagar o arquivo, serádevolvido -1.

Vamos a um exemplo de utilização destas funções para compreendermos melhor o seu funcionamento.

#include <stdio.h>#include <fcntl.h>

#define BUFFERSIZE 64

main(){int desc;char buffer[64];

int i;

if(( desc = open("arquivo1", O_WRONLY)) == -1) { puts(" Nao foi possivel abrir este arquivo "); exit(); }

puts(" Entre com um texto ");gets(buffer);

puts(" Gravando em disco ");write(desc, buffer, BUFFERSIZE);

close(desc);}

Este programa escreve em disco uma mensagem dada pelo usuário. O tamanho do buffer foi escolhidoaqui de maneira arbitrária.

Agora vamos a outro pequeno programa que recupera a informação gravada pelo programa anterior.

Departamento de Ciência da Computação-UFF

Números de página

#include <stdio.h>#include <fcntl.h>

#define BUFFERSIZE 64

main(){int desc;char buffer[64];

int i;

if(( desc = open("arquivo1", O_RDONLY)) == -1) { puts(" Nao foi possivel abrir este arquivo "); exit(); }

puts(" Lendo a frase no disco");read(desc, buffer, BUFFERSIZE);

puts(" frase dada inicialmente: ");puts(buffer);

close(desc);}

Um exemplo mais complexo é dado abaixo, onde temos um programa que codifica um arquivo dado.Observe que este programa envolve vários conceitos já vistos.

#include <stdio.h>#include <string.h>#include <fcntl.h>

#define BUFSIZE 16384#define KEYSIZE 16#define MENSAGEM1 Nao pode ser aberto o arquivo#define MENSAGEM2 Nao pode ser criado o arquivo

int fdin, fdout; /* descritores */

int count;

char *inbuf, *key;

encrip(char *buffer, int num, char *code) { /* funcao codificadora */

int i, keylength;

keylength = strlen(code);

for (i = 0; i <= num; i++) buffer[i] = buffer[i] ^ code[i % keylength]; }

Departamento de Ciência da Computação-UFF

Números de página

main(argc, argv)

int argc;

char *argv[];{int i, n;

inbuf = (char *) malloc(BUFSIZE);key = (char *) malloc(KEYSIZE);

if (argc < 4) { puts(" encrip <arquivo original> <novo arquivo> <chave1>"); exit();

/* emite a forma de uso correto do programa, caso o usuario o tenha *//* feito de maneira incorreta */ }

if ((fdin = open(argv[1],O_RDONLY)) == -1) { printf(" MENSAGEM1 %s", nome1); exit(1); }

if ((fdout = creat(argv[2],0_WRONLY)) == -1) { printf(" MENSAGEM2 %s", nome2); exit(1); }

do { printf(" Lendo o arquivo a ser processado ");

count = read(fdin, inbuf, BUFSIZE); printf(" Gerando o arquivo processado.");

write(fdout, inbuf, count); }while(count == BUFSIZE);

printf(" OK ");

free(key); /* liberando area de memoria */free(inbuf);

close(fdin);close(fdout);}

Departamento de Ciência da Computação-UFF

Números de página

O processo de codificação é feito através de manipulação de bits dos caracteres do arquivo de entrada.Isto é feito usando o operador lógico ou exclusivo (ver o capítulo sobre operadores e o apêndice I). Fazemosuma operação lógica entre cada caracter do texto a ser codificado e uma ou mais cadeias de caracteres chamadaschaves. Após esta operação, gravamos o arquivo codificado. A única maneira de conseguirmos recuperar ainformação codificada é executando novamente o programa e entrando com as chaves de codificação. Portanto,o arquivo codificado só se torna acessível a quem souber das chaves.

Observo ainda que este programa, no processo de codificação, pode gerar o caracter EOF. Logo, noprocesso de decodificação poderemos ter uma perda de informação.

EXERCÍCIO

I) Suponha que os caracteres do seu arquivo a ser codificado se restrinjam ao código ASCII. Modifiqueo programa de modo que não se perca informação pela possível geração do EOF durante o processo decodificação. Dica: lembre-se que a tabela ASCII só usa os sete bits menos significativos para os seus códigos.

Departamento de Ciência da Computação-UFF

Números de página

APÊNDICE

I) Tabelas de funções lógicas

a) ~ (Negação ou Não Lógico)

0 ~ 1 1 ~ 0

b) & (E Lógico)

0 & 0 0 0 & 1 0 1 & 0 0 1 & 1 1

c) | (Ou Lógico)

0 | 0 0 0 | 1 1 1 | 0 1 1 | 1 1

d) ^ (Ou Exclusivo)

0 ^ 0 0 0 ^ 1 1 1 ^ 0 1 1 ^ 1 0

Departamento de Ciência da Computação-UFF