ivro - estruturas de dados e algoritmos em c

Download IVRO - Estruturas de Dados e Algoritmos Em C

If you can't read please download the document

Upload: cesar-augusto-farias

Post on 19-Oct-2015

18 views

Category:

Documents


13 download

TRANSCRIPT

  • PROGRAMAO

    ESTRUTURAS DE DADOS

    E ALGORITMOS

    EM C

    Professor Doutor Antnio Manuel Adrego da Rocha Professor Doutor Antnio R ui Oliveira e Silva Borges

    Departamento de Electrnica e Telecomunicaes

    Universidade de Aveiro

  • Prefcio

    Este texto serve de suporte disciplina de Programao II, cujo objectivo o de fornecer uma familiarizao com o ambiente de programao fornecido pelo Unix, na sua variante mais popularizada Linux e o domnio da linguagem C, na sua norma ANSI, para o desenvolvimento de programas de mdia e elevada complexidade. Comeamos por apresentar os aspectos essenciais da linguagem C em dois captulos. Depois introduzirmos as construes mais complexas da linguagem de forma gradual, medida que so necessrias construo de estruturas de dados mais complexas, bem como para a optimizao e generalizao de algoritmos. Os aspectos fundamentais apresentados no texto so os seguintes: x A familiarizao progressiva com a linguagem de programao C e com as suas bibliotecas. x A apresentao de algoritmos recursivos e sua compara o com os algoritmos iterativos

    equivalentes. x A introduo da metodologia de decomposio modular das solues, ou seja, o paradigma da

    programao modular. x O estudo da organizao da Memr ia de Acesso Aleatri o (RAM), nas suas implementaes

    esttica e semiesttica, e, de um conjunto significativo de algoritmos de pesquisa e de ordenao. x O estudo da organizao de memrias mais complexas que a Memri a de Acesso Aleat rio, como

    por exemplo, a Mem ria Fila de Espera (FIFO), a Memoria Pilha (Stack) e a Memria Associativa (CAM), nas suas implementaes esttica, semiesttica e dinmica, e, dos algoritmos associados para pesquisa, introduo e retirada de informao.

    Assume-se que os alunos frequentaram a disciplina de Programao I, e portanto, j esto familiarizados com a metodologia de decomposio hierrquica das solues, estabelecendo dependncias de informao e no encapsulamento da informao com a criao de novas instrues no mbito da linguagem Pascal, ou seja, com o paradigma da programao procedimental. Bem como, com a cria o de estruturas de dados estticas com alguma complexidade que modelam correctamente a resoluo dos problemas. Pelo que, a apresentao da linguagem C feita por comparao com a linguagem Pascal. Pretende-se ainda, que os alunos se familiarizem com a terminologia informtica apresentada nos textos de referncia da rea das Cincias da Computa o, pelo que, se tenha optado pela apresentao sistemtica, em itlico e entre parntesis, dos nomes dos algoritmos e das estruturas de dados em ingls.

  • Captulo 1 INTRODUO AO C

    Sumrio

    Este captulo dedicado introduo das primeiras noes sobre a gramtica da linguagem C. Comeamos por apresentar a estrutura de um programa e os seus elementos bsicos. Explicamos os tipos de dados bsicos existentes, a definio de constantes e de variveis. Apresentamos os vrios tipos de expresses e operadores existentes e a instruo de atribuio, que a instruo bsica de uma linguagem imperativa. Apresentamos de seguida as estruturas de controlo, que permitem alterar o fluxo da sequncia das instrues. Apresentamos ainda as instrues de leitura de dados do teclado scanf e de escrita de dados no monitor printf . Finalmente, apresentamos as bibliotecas que contm as funes mais usuais e que estendem a operacionalidade da linguagem.

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 2

    1.1 Introduo

    Em 1972, Dennis M. Ritchie desenvolveu a linguagem C, nos Laboratrios Bell da companhia AT & T, que a principal empresa de telecomunicaes dos Estados Unidos da Amrica, como uma linguagem de programao concebida para a escrita de sistemas operativos, aquilo que se designa por Programao de Sistemas. Como a linguagem C era to flexvel e permitia que os compiladores produzissem cdigo em linguagem mquina muito eficiente, em 1973, Dennis M. Ritchie e Ken Thompson reescreveram quase totalmente o sistema operativo Unix em C. Devido a esta liga o ntima, medida que o Unix se tornou popular no meio acadmico, tambm a linguagem C se tornou a linguagem preferida para o desenvolvimento de aplicaes cientficas. Pelo que, apesar de ter sido concebida para a escrita de sistemas operativos, a linguagem C hoje encarada como uma linguagem de uso geral. A principal caracterstica da linguagem C que combina as vantagens de uma linguagem de alto nvel descendente do AlGOL 68, com a eficincia da linguagem assembly, uma vez que permite a execuo de operaes aritmticas sobre ponteiros e operaes sobre palavras binrias. A linguagem C tambm tem uma sintaxe muito compacta e permite que operadores de tipos diferentes possam ser combinados livremente. Esta liberdade e poder da linguagem C, permite aos programadores experientes escreverem cdigo compacto e eficiente que dificilmente poderiam ser escritos noutras linguagens de programao. Mas, como fracamente estruturada em termos semnticos, tambm permite que construes sem sentido aparente, escritas por programadores inexperientes, sejam aceites pelo compilador como vlidas. O facto da linguagem C ser muito poderosa, exige portanto, do programador muita disciplina e rigor na utilizao das construes da linguagem, para que o cdigo escrito seja legvel e facilmente altervel. Apesar da linguagem C ter sido desenvolvida no princpio da dcada de 1970, a norma ANSI (American National Standards Institute) foi apenas aprovada em 1989 (norma ISO/IEC 9899-1990).

    1.2 A estrutura de um programa em C

    Ao contrrio do que se passa em Pascal, em que um programa apresenta uma organizao hierrquica que reflecte directamente o algoritmo que lhe deu origem, na linguagem C, um programa organizado horizontalmente como um agrupamento de variveis e funes colocadas todas ao mesmo nvel, uma estrutura conhecida pelo nome de mar de fune s. Neste contexto, a diferenciao entre o programa principal e os diversos subprogramas associados feita pelo facto de existir uma funo particular, de nome main, que sempre invocada em primeiro lugar aquando da execuo do programa. Assim, a funo main desempenha na prtica o papel do programa principal do Pascal. Segundo a norma ANSI, a funo main uma funo de tipo inteiro, em que o valor devolvido serve para informar sobre o estado de execuo do programa.

  • 3 CAPTULO 1 : INTRODUO AO C

    De facto, a noo de devolver um valor associado ao estado de execuo de um programa corresponde filosofia subjacente arquitectura de comandos do Unix, em que a linguagem de comandos (shell) , no fundo, uma verdadeira linguagem de programao, que permite construir comandos mais complexos por combinao de comandos mais simples (shell scripts), usando um conjunto de estruturas de controlo muito semelhantes aos encontrados na linguagem C ou Pascal. Neste contexto, os comandos mais simples so programas, de cujo sucesso de execuo vai eventualmente depender a continuao das operaes. Ora, como esta uma rea que no ser explorada no mbito desta disciplina, em muitos programas, sobretudo naqueles que sero desenvolvidos nesta disciplina, no se coloca a questo de devolver um valor. Pelo que, para evitar a mensagem de aviso do compilador, recomenda-se terminar o main com a instruo return 0. Um programa em C tem a estrutura apresentada na Figura 1.1.

    Figura 1.1 - Estrutura de um programa em C.

    Vamos analisar as diversas partes de um programa em C atravs do exemplo do programa de converso de distncias de milhas para quil metros apresentado na Figura 1.2. O ficheiro fonte que contm o programa comea por mencionar as funes e estruturas de dados externas necessrias execuo do programa, que esto implementadas noutros ficheiros providenciados pela linguagem C, as chamadas bibliotecas da linguagem, ou que em alternativa so desenvolvidos pelo utilizador. A linguagem C foi concebida tendo em mente facilitar a construo descentralizada de aplicaes, atravs do fraccionamento do cdigo de um programa por diferentes ficheiros fonte. Por isso, necessrio e inevitvel, mesmo em programas muito simples, recorrer aluso a funes e definies feitas externamente, ou seja, noutros ficheiros. Para tornar mais rigorosa esta referncia, foi criado o conceito de ficheiro de interface, onde todas as aluses e definies associadas a um dado tipo de funcionalidade so colocadas. Estes ficheiros de interface distinguem-se dos ficheiros fonte, propriamente ditos, por terem a terminao .h em vez de .c. A norma ANSI fornece um conjunto muito variado de ficheiros de interface que descrevem as diferentes funcionalidades fornecidas pelas bibliotecas de execuo ANSI. Em Unix, por defeito, todos eles esto armazenados no directrio /usr/include. Da, no ser necessrio referenciar este caminho de um modo explcito. Quando o ficheiro em causa est neste directrio, basta colocar o seu nome entre os smbolos < e >. Em todos os outros casos, a especificao do caminho deve ser includa no nome do ficheiro e o conjunto ser colocado entre aspas duplas.

    aluso a funes e definies externas aluso a funes e definies locais int main ( void ) { declarao de variveis aluso a funes sequncia de instrues return 0; } definio de funes locais

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 4

    A incluso de ficheiros de interface num dado ficheiro fonte feita usando a directiva do pr-processador # include numa das suas duas variantes:

    x No caso do ficheiro de interface pertencer linguagem C, ento ele est armazenado no directrio por defeito e usa-se a directiva #include .

    x No caso do ficheiro de interface ter sido criado pelo utilizador e no estar armazenado no directrio por defeito, usa-se a directiva #incl ude " nome do ficheiro de interface" . No mnimo, todos os ficheiros que contenham cdigo que faa acesso aos dispositivos convencionais de entrada e de sada tm que incluir o ficheiro de interface stdio.h, que descreve as funes e que contm as defini es associadas com o acesso aos dispositivos de entrada e de sada e aos ficheiros. Normalmente, o dispositivo de entrada o teclado e o dispositivo de sada o monitor. Portanto, qualquer programa interactivo tem pelo menos a aluso a este ficheiro de interface, tal como se apresenta na Figura 1.2. A seguir s definies de objectos externos segue-se a aluso s funes locais que vo ser usadas na funo main, bem como a definio de estruturas de dados e constantes locais. Locais para a aplicao, mas que para o ficheiro, se comportam como definies globais. Repare que a estruturao do programa, muito diferente do Pascal, sendo que as funes so primeiramente aludidas ou referidas, para se tornarem visveis em todo o ficheiro e s depois da funo main que so definidas. Neste exemplo, define-se apenas, atravs da directiva #define, um identificador constante MIL_QUI, que representa o factor de converso de milhas para quilmetros. Ele visvel para todo o cdigo do ficheiro, ou seja, uma constante global. A funo main implementada com instrues simples, e com recurso apenas s funes de entrada e de sada de dados da biblioteca stdio.

    Figura 1.2 - Programa da converso de distncias.

    Vamos agora analisar com detalhe na Figura 1.3 a definio da funo main, que tal como qualquer outra funo na linguagem C, supe a especificao do seu cabealho e do seu corpo. No cabealho, indica-se o tipo do valor devolvido, que como j foi referido anteriormente sempre do tipo inteiro, o nome, e entre parnteses curvos, a lista de

    /* Programa de converso de distncias de milhas para quilmetros */

    /* Instrues para o pr-processador */

    #include /* interface com a biblioteca de entrada/sada */

    #define MIL_QUI 1.609 /* factor de converso */

    /* Instrues em linguagem C propriamente ditas */

    int main ( void ) { double MILHAS, /* distncia expressa em milhas */ QUILOMETROS; /* distncia expressa em quilmetros */

    do /* Leitura com validao de uma distncia expressa em milhas */ { printf ("Distncia em milhas? "); scanf ("%lf", &MILHAS); } while (MILHAS < 0.0);

    QUILOMETROS = MIL_QUI * MILHAS; /* Converso da distncia */

    /* Impresso da distncia expressa em quilmetros */ printf ("Distncia em quilmetros %8.3f\n", QUILOMETROS); return 0; }

  • 5 CAPTULO 1 : INTRODUO AO C

    parmetros de comunicao. Neste caso a fun o no comunica directamente com o exterior, pelo que, no existe lista de parmetros de comunicao. Quando tal acontece, utiliza-se o identificador void. O corpo da funo delimitado pelos separadores { e }, correspondentes, respectivamente, aos separadores begin e end do Pascal, e contm a declarao das variveis locais, a aluso a funes usadas na sequncia de instrues e a sequncia de instrues propriamente dita. Constitui aquilo que em linguagem C se designa por um bloco.

    int main ( v oid )

    {double MILHAS,

    QUILOMETROS;

    do{

    printf ( "Distnci a em mil has? ");scanf (" %lf", &MI LHAS);

    } while (MILHAS < 0 .0);QUILOMETROS = MIL_Q UI * MIL HAS;pr intf ("D istncia em quil metros %8.3f\n" , QUILOM ETROS);re turn 0;

    }

    cabealho

    corpo

    se quncia de instr ues

    declarao de vari veis loc ais

    Figura 1.3 - A funo main.

    1.3 Elementos bsicos da linguagem C

    Os identifi cadores so nomes que so usados para designar os diferentes objectos existentes no programa, como por exemplo, o nome do programa, os nomes das funes, os nomes das constantes, tipos de dados e variveis. Os identificadores obedecem regra de produo apresentada na Figura 1.4.

    Figura 1.4 - Definio f ormal de um identificador.

    Ou seja, so formados por uma sequncia de caracteres alfanumricos e o carcter underscore, em que o primeiro carcter obrigatoriamente uma letra do alfabeto ou o carcter underscore. Embora seja possvel comear um identificador pelo carcter underscore, tal deve ser evitado. Este tipo de notao , em princpio, reservado para o compilador. No h limite para o comprimento de um identificador. Na prtica, esse limite imposto pelo compilador. A norma ANSI exige um comprimento mnimo de 31 e 6 caracteres, respectivamente, para os identificadores internos e externos.

    identificador ::= letra do alfabeto | carcter underscore | identificador letra do alfabeto | identificador carcter underscore | identificador algarismo decimal

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 6

    Na linguagem C, os alfabetos maisculo e minsculo so distintos, ou seja, a linguagem sensvel ao tipo de letra (case sensitive). Assim sendo, o identificador conv_dist diferente do identificador CONV_DIST. Embora no seja obrigatrio, costume designar todos os identificadores da responsabilidade do programador com caracteres maisculos de maneira a distingui-los dos identificadores da linguagem C, que tm de ser escritos obrigatoriamente com caracteres minsculos. Em alternativa, h programadores que gostam de usar um carcter maisculo no primeiro carcter de cada palavra que compe o identificador e os restantes caracteres minsculos. Nesse caso, devem designar pelo menos os nomes das constantes em caracteres maisculos. Mas, o importante que cada programador defina o seu prprio estilo, e que exista uma certa coerncia nas regras que adopte. As palavras reservadas da linguagem C so: auto; break; case; char; const; continue; default; do; double; else; enum; extern; float; for; goto; if; int ; long; register; return; short; signed; sizeof; static; struct; switch; typedef; union; unsigned; void; volatile; e while. As palavras reservadas aparecem a cheio ao longo do texto e no cdigo apresentado. Para melhorar a legibilidade do programa, devem ser introduzidos comentrios relevantes, que expliquem o significado dos diferentes objectos, ou que operao efectuada por grupos bem definidos de instrues. Um comentrio uma qualquer sequncia de smbolos inserida entre /* e */ e que no contenha */. Isto significa que no se pode nunca encapsular comentrios. A Figura 1.5 apresenta a definio formal do comentrio.

    Figura 1.5 - Definio f ormal do comentrio.

    O uso adequado de comentrios melhora extraordinariamente a legibilidade e a compreenso de um segmento de c digo. Assim, devem introduzir-se comentrios, pelo menos, nas situaes seguintes:

    x Em ttulo, para explicar o que faz o segmento de cdigo e descrever, caso exista, o mecanismo de comunicao associado.

    x Sempre que se declarem constantes ou variveis, para explicar o seu significado, a menos que este seja trivial.

    x A encabear as pores de cdigo correspondentes decomposio algortmica que lhe deu origem.

    1.4 Representao da informao

    Em Pascal, os tipos de dados predefinidos esto directamente relacionados com o tipo de informao neles armazenado. Assim, para informao numrica, existem os tipos inteiro (integer) para a representao exacta e real (real) para a representao aproximada; para quantidades lgicas existe o tipo booleano (boolean); e para a representao de smbolos grficos existe o tipo carcter (char). Em linguagem C, pelo contrrio, os tipos de dados predefinidos reflectem apenas o formato de armazenamento. So sempre tipos numricos, embora em alguns casos possibilitem uma interpretao alternativa, funo do contexto em que so usados.

    comentrio ::= /* qualquer sequncia de smbolos que nocontenha */ */

  • 7 CAPTULO 1 : INTRODUO AO C

    A Figura 1.6 apresenta os tipos de dados simples existentes na linguagem C. Estes tipos de dados tambm se designam por escalares, uma vez que, todos os seus valores esto distribudos ao longo de uma escala linear. Dentro dos tipos de dados simples, temos o tipo ponteiro (pointer), o tipo enumerado (enum) e os tipos aritmticos, que se dividem em tipos inteiros e tipos reais. Os tipos aritmticos e o tipo enumerado designam-se por tipos bsicos. Os tipos aritmticos inteiros podem armazenar valores negativos e positivos, que o estado por defeito ou usando o qualificativo signed, ou em alternativa, podem armazenar apenas valores positivos, usando para o efeito o qualificativo unsigned que lhe duplica a gama dinmica positiva. O tipo aritmtico int pode ainda ser qualificado como short, reduzindo-lhe a capacidade de armazenamento. O qualificativo long pode ser usado para aumentar a capacidade de armazenamento do tipo inteiro int e do tipo real double.

    Tipos de Dados Simples

    Enumeradoenum

    Aritmticos

    float

    Ponteiropointer

    Tipos Inteiros Tipos Reais

    doublecharint

    Tipos Bsicos

    Qualificat ivos

    short

    long

    signed

    unsigned

    Figura 1.6 - Tipos de dados simples existentes na linguagem C.

    1.4.1 Tipos de dados inteiros

    Na linguagem C existem os seguintes tipos de dados inteiros:

    x O tipo char permite a representao de quantidades com sinal num byte e portanto, permite armazenar valores entre -128 a 127.

    x O tipo unsigned char permite a representao de quantidades sem sinal num byte e portanto, permite armazenar valores entre 0 e 255.

    x O tipo short [int ] permite a representao de nmeros negativos e positivos em 2 bytes e portanto, permite armazenar valores entre -32768 e 32767.

    x O tipo unsigned short [int ] permite a representao de nmeros positivos em 2 bytes e portanto, permite armazenar valores entre 0 e 65535.

    x O tipo int permite a representao de nmeros negativos e positivos em 2 bytes ou 4 bytes, consoante o computador.

    x O tipo unsigned int permite a representao de nmeros positivos em 2 bytes ou 4 bytes, consoante o computador.

    x O tipo long [int ] permite a representao de nmeros negativos e positivos em 4 bytes ou 8 bytes, consoante o computador.

    x O tipo unsigned long [int] permite a representao de nmeros positivos em 4 bytes ou 8 bytes, consoante o computador.

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 8

    O tamanho dos tipos inteiros int e long e a sua gama dinmica dependem do compilador e do hardware utilizados. Esta informao indicada no ficheiro limits.h localizado no directrio include do ambiente de desenvolvimento. No caso do computador utilizado nesta disciplina, cujo processador de 32 bits, os tipos int e unsigned int so representados em 4 bytes, pelo que, permitem armazenar respectivamente valores entre -2147483648 e 2147483647 e valores entre 0 e 4294967295. Os tipos long e unsigned long so tambm representados em 4 bytes.

    1.4.2 Tipos de dados reais

    Na linguagem C existem os seguintes tipos de dados reais: float; double; e long double. O tamanho, a preciso e a gama dinmica dos tipos reais dependem do compilador e do hardware utilizados. Esta informao indicada no ficheiro float.h localizado no directrio include do ambiente de desenvolvimento. Para o caso do computador utilizado nesta disciplina, o tamanho, a preciso e a gama dinmica dos tipos reais so os seguintes:

    x O tipo float utiliza 4 bytes, o que permite armazenar valores entre 1.2x10-38 e 3.4x1038, com uma mantissa de 6-7 algarismos significativos.

    x O tipo double utiliza 8 bytes, o que permite armazenar valores entre 2.2x10-308 e 1.8x10308, com uma mantissa de 15-16 algarismos significativos.

    x O tipo long double utiliza 12 bytes, o que permite armazenar valores entre 3.4x10-4932 e 1.2x104932, com uma mantissa de 18-19 algarismos significativos.

    1.4.3 Representao de caracteres e inteiros

    A maioria das linguagens de programao, entre as quais se inclui o Pascal, faz a distino entre o tipo inteiro e o tipo carcter. Mesmo, apesar de nessas linguagens, os caracteres serem armazenados na memria em numrico, usando para o efeito o cdigo ASCII. Na linguagem C no existe tal distino. O tipo char que utiliza um byte permite armazenar quer um carcter quer um valor inteiro. A Figura 1.7 apresenta dois exemplos que exemplificam esta polivalncia. No primeiro caso a atribuio do valor 65, que o cdigo ASCII do carcter A, equivalente atribuio do prprio carcter A varivel CAR. No segundo caso a atribuio do valor 3 varivel NUM diferente da atribuio varivel CAR do carcter 3, cujo cdigo ASCII 51. Devido a esta polivalncia de interpretao do valor inteiro armazenado na varivel, o valor escrito no monitor depende do especificador de formato que seja empregue na instruo de sada de dados. O formato %d representa o valor decimal, enquanto que o formato %c representa o carcter.

    Figura 1.7 - Utilizao do tipo char.

    char CAR; /* declarao da varivel CAR de tipo cha r */ ... CAR = A; /* ambas as atribuies armazenam */ CAR = 65; /* na posio de memria CAR o valor 65 */FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF char NUM, CAR; /* declarao das variveis NUM e CAR de tipo char */ ... NUM = 3; /* armazena na posio de memria NUM o valor 3 */ CAR = 3; /* armazena na posio de memria CAR o valor 51 */

  • 9 CAPTULO 1 : INTRODUO AO C

    1.5 Constantes e variveis

    Uma constante um objecto, cujo valor se mantm invariante durante a execuo do programa. A utilizao de um valor constante num programa, no fornece qualquer indicao sobre o seu significado ou finalidade. Pelo que, a utilizao de uma mnemnica, ou seja, um nome associado a um valor constante, permite aumentar a legibilidade de um programa. Por outro lado, se um valor constante aparecer mais do que uma vez ao longo do programa, pode acontecer que o programador cometa algum lapso na repetio do valor e ter um erro algortmico que no detectvel pelo compilador e que pode ser muito difcil de detectar pelo prprio programador. A utilizao de uma constante permite assim parametrizar um programa, melhorar a legibilidade, j que os valores so substitudos por nomes com significado explcito e, a robustez, porque a alterao do valor realizada de um modo centralizado. Ao contrrio do que se passa em Pascal, a linguagem C no contempla a definio explcita de identificadores associados com constantes. Esta restrio pode ser ultrapassada, tal como se apresenta na Figura 1.2, usando a seguinte directiva do pr-processador.

    #define identificador de constante expresso

    O que o pr-processador faz, ao encontrar esta directiva no ficheiro fonte, efectuar a partir desse ponto a substituio de todas as ocorrncias do identificador de constante pela expresso, o que se designa por uma macro de substituio. Como se trata de um processo de substituio puro, e no de clculo do valor associado, torna-se necessrio, sempre que a expresso no for um literal, coloc-la entre parnteses curvos para garantir o clculo correcto do seu valor, independentemente do contexto em que est localizada.

    #define identificador de constante ( expresso )

    Um dos erros mais frequentemente cometido, por programadores que se esto a iniciar na utilizao da linguagem C, quando utilizam esta directiva na definio de um identificador constante a sua terminao com o ;. Nesse caso o ; torna-se parte da substituio podendo gerar situaes de erro. As constantes numricas inteiras podem ser representadas no sistema decimal, no sistema octal, em que a constante precedida pelo dgito 0 ou no sistema hexadecimal, em que a constante precedida pelo dgito 0 e pelo carcter x. Quando as constantes esto representadas nos sistemas octal ou hexadecimal, o sinal normalmente expresso de uma maneira implcita, usando a representao em complemento verdadeiro. A Figura 1.8 apresenta alguns exemplos, considerando uma representao do tipo int em 32 bits.

    Figura 1.8 - Exemplos de constantes inteiras.

    O compilador atribui por defeito o tipo int a uma constante numrica inteira. Quando este tipo no tem capacidade de armazenamento suficiente, os tipos seguintes so sucessivamente atribudos. No caso de uma constante decimal, usa-se o tipo long, ou se ainda for insuficiente o tipo unsigned long. No caso de uma constante octal ou hexadecimal, usa-se pela seguinte ordem o tipo unsigned int, ou o tipo long, ou o tipo unsigned long.

    Sistema decimal Sistema octal Sistema hexadecimal 54 066 0x36 -135 -0207 -0x87 em complemento verdadeiro 037777777571 0xFFFFFF79 0 00 0x0

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 10

    A atribuio do tipo pode ser forada pelos sufixos U para unsigned e L para long. Uma constante seguida do sufixo U do tipo unsigned int ou do tipo unsigned long. Uma constante seguida do sufixo L do tipo long ou do tipo unsigned long. Uma constante seguida do sufixo UL do tipo unsigned long. As constantes numricas reais so sempre expressas no sistema decimal, usando quer a representao em parte inteira e parte fraccionria, quer a chamada notao cientfica. O compilador atribui por defeito o tipo double a uma constante numrica real. A atribuio do tipo pode ser forada pelos sufixos F e L. Uma constante seguida do sufixo F do tipo float. Uma constante seguida do sufixo L do tipo long double. So exemplos de constantes reais os valores 0.0148, 1.48e-2 e 0.0. As constantes de tipo carcter podem ser expressas indiferentemente pelo respectivo smbolo grfico, colocado entre aspas simples, ou atravs do valor do seu cdigo de representao nos sistemas octal e hexadecimal, precedidas do carcter \, tal como se mostra na Figura 1.9.

    Figura 1.9 - Exemplo de constante de tipo carcter.

    Para alguns caracteres de controlo, pode ainda ser usada uma representao alternativa que consiste numa letra do alfabeto minsculo precedida do carcter \. Por exemplo o carcter de fim de linha o \n, o carcter de backspace o \b, o carcter de tabulao o \t, o carcter aspas duplas o \ e o carcter ponto de interrogao o \?. As constantes de tipo cadeia de caracteres so expressas como uma sequncia de caracteres, representados por qualquer dos mtodos anteriores, colocados entre aspas duplas. Por exemplo Ola malta!\n. Uma varivel um objecto, cujo valor se altera em princpio durante a execuo do programa, excepo eventualmente feita s variveis de entrada, cujo valor depois de lido do teclado , em princpio, mantido inalterado at ao fim da execuo do programa. Todas as variveis usadas num programa tm que ser previamente definidas ou declaradas. O objectivo da declarao simultaneamente a reserva de espao em memria para o armazenamento dos valores que as variveis vo sucessivamente tomar, e a associao de cada identificador com a rea de memria correspondente. A Figura 1.10 apresenta a definio formal da declarao de variveis na linguagem C. Para declarar variveis comea-se por identificar o tipo de dados seguido da varivel, ou lista de variveis que se pretendem declarar desse tipo, separadas por vrgulas, terminando a declarao com o separador ;. conveniente agrupar a declarao de variveis do mesmo tipo na mesma linha para aumentar a legibilidade do programa. A reserva de espao em memria no pressupe, em princpio, a atribuio de um valor inicial varivel. Em consequncia, nada deve ser presumido sobre o seu valor antes que uma primeira atribuio tenha sido efectivamente realizada. No entanto, a linguagem C permite combinar a definio de uma varivel com a atribuio de um valor inicial, usando para o efeito o operador de atribuio seguido da expresso de inicializao.

    Sistema decimal Sistema octal Sistema hexadecimal B \ 102 \x42

  • 11 CAPTULO 1 : INTRODUO AO C

    Figura 1.10 - Definio formal da declarao de variveis.

    Ao contrrio de outras linguagens de programao a linguagem C usa apenas um nico smbolo, o smbolo =, como operador de atribuio. A diferena do operador de atribuio em relao ao Pascal um dos erros mais frequentemente cometido por programadores que se esto a iniciar na utilizao da linguagem C. A regio de reserva de espao em memria principal no necessariamente contgua, pelo que, frequente surgirem buracos resultantes do alinhamento das variveis, em reas de endereos mltiplos de 4, para maximizar a taxa de transferncia de informao entre o processador e a memria principal. A Figura 1.11 apresenta alguns exemplos de declarao de variveis e sua colocao na memria.

    -2.5

    1324

    G

    A

    B

    C

    D

    E

    4 bytes

    4 bytes

    8 bytes

    8 bytes

    rea re serv ada mas no ini ci ali zada

    rea n o re serva da

    char A, B = G;unsigned int C = 1324;double D = -2 .5, E;

    rea re serv ada e inici aliz ada

    Figura 1.11 - Alguns exemplos de declarao de variveis e sua colocao na mem ria.

    declarao de variveis ::= declarao de variveis de um tipo | declarao de variveis | declarao de variveis de um tipo

    declarao de variveis de um tipo ::= tipo de dados lista de variveis ;

    tipo de dados ::= qualquer tipo de dados vlido na linguagem C

    lista de variveis ::= identificador de varivel genrico | lista de variveis , identificador de varivel genrico

    identificador de varivel genrico ::= identificador de varivel | identificador de varivel = expresso de inicializao

    identificador de varivel ::= identificador vlido na linguagem C

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 12

    1.6 Sequenciao

    Tal como foi referido anteriormente, o corpo de uma funo delimitado pelos separadores { e }, correspondentes, respectivamente, aos separadores begin e end do Pascal, e contm a declarao das variveis locais, a aluso a funes usadas na sequncia de instrues e a sequncia de instrues propriamente dita. A Figura 1.12 apresenta a definio formal de uma sequncia de instrues. Ao contrrio do que se passa em Pascal, na linguagem C cada instruo simples obrigatoriamente terminada com o separador ;, a menos que o ltimo smbolo da instruo seja o separador }.

    Figura 1.12 - Definio formal da sequncia de instrues.

    Em Pascal, a invocao de uma funo, sendo uma expresso, no pode ser considerada uma instruo. Na linguagem C, contudo, o conceito de procedimento no tem uma existncia separada. Define-se como sendo uma funo de um tipo especial, o tipo void. Pelo que, a invocao de um procedimento , por isso, em tudo semelhante invocao de uma funo de qualquer outro tipo, quando o valor devolvido no tido em considerao. Logo, nestas circunstncias, na linguagem C, a pura e simples invocao de uma funo de qualquer tipo considerada uma instruo. A sequenciao de instrues a forma mais simples de controlo de fluxo num programa, em que as instrues so executadas pela ordem em que aparecem no programa. Dentro das instrues simples, apenas as instrues de atribuio, de entrada-sada e de invocao de uma funo, so verdadeiramente instrues de sequenciao, j que, as instrues decisrias e repetitivas permitem alterar a ordem do fluxo do programa. Tal como no Pascal, na linguagem C tambm existe o conceito de instruo composta, cuja definio formal se apresenta na Figura 1.13, e que composta por uma sequncia de instrues simples encapsuladas entre os separadores { e }. Uma instruo composta um bloco de instrues simples que se comporta como uma instruo nica e usada em instrues decisrias e repetitivas.

    Figura 1.13 - Defini o formal da instruo composta.

    1.6.1 Expresses

    A Figura 1.14 apresenta a definio formal de uma expresso. Uma expresso uma frmula que produz um valor. Pode assumir as seguintes formas: ser uma constante; ser uma varivel; ser o resultado da invocao de uma funo; ser uma expresso composta por operandos e operadores, sendo que existem operadores unrios e binrios; e ser uma expresso entre parnteses curvos.

    instruo composta ::= { sequncia de instrues simples}

    sequncia de instrues simples ::= instruo simples | sequncia de instrues simples instruo simples

    instruo simples ::= instruo de atribuio | instruo decisria | instruo repetitiva | instruo de entrada-sada | invocao de umafuno

  • 13 CAPTULO 1 : INTRODUO AO C

    Figura 1.14 - Defini o formal de uma expresso.

    Os primeiros trs tipos de expresses so a frm ula mais simples de produzir um valor e resumem-se atribuio a uma varivel, do valor de uma constante, ou do valor de uma varivel ou do valor devolvido por uma funo. As expresses mais complexas envolvem operadores unrios ou binrios. Uma expresso pode ser composta por uma expresso colocada entre parnteses curvos. Este tipo de expresso usa-se para compor expresses mais complexas, ou para modificar a prioridade do clculo de expresses parcelares. Vamos agora analisar as expresses aritmticas. O clculo de uma expresso complexa supe um processo de decomposi o prvio em expresses mais simples, que determinado pela precedncia e pela associatividade dos operadores presentes. Precedncia significa importncia relativa entre os operadores. Operadores de maior precedncia foram a ligao a si dos operandos, antes dos operadores de menor precedncia Por exemplo, como a multiplicao tem precedncia sobre a adio, ento a expresso a + b * c tem subjacente o agrupamento a + (b * c). Quando numa expresso todos os operadores tm a mesma precedncia, a associatividade permite determinar a ordem de ligao dos operandos aos operadores, da direita para a esquerda, ou da esquerda para a direita. Por exemplo, como a associatividade da adio da direita para a esquerda, ento a expresso a + b + c tem subjacente o agrupamento (a + b) + c. No entanto, qualquer que seja o agrupamento imposto pela precedncia e pela associatividade dos operadores presentes, ele pode ser sempre alterado pela introduo de parnteses curvos. No caso de aritmtica inteira, sempre que numa expresso surgem constantes, variveis, ou se invocam funes de tipo char ou short, os seus valores so automaticamente convertidos pelo compilador em quantidades de tipo int . Do mesmo modo, constantes, variveis, ou funes de tipo unsigned char ou unsigned short, so automaticamente convertidas pelo compilador em quantidades de tipo int , ou de tipo unsigned int, se o primeiro tipo no tiver capacidade de armazenamento suficiente. No caso de aritmtica real, sempre que numa expresso surgem constantes, variveis, ou se invocam funes de tipo float, os seus valores so automaticamente convertidos pelo compilador em quantidades de tipo double. Os operadores binrios supem normalmente operandos do mesmo tipo. Quando so de tipo diferente, a expresso do tipo com menor capacidade de armazenamento, automaticamente convertida no tipo da outra expresso. A hierarquia dos diferentes tipos de dados, expressa por ordem decrescente da sua capacidade de armazenamento, a que se apresenta a seguir.

    long double o double o float o unsigned long o long o unsigned int o int

    Alm das converses automticas que foram referidas, a converso de uma expresso num tipo especfico qualquer pode ser sempre forada atravs do operador cast.

    ( qualquer tipo de dados escalar vlido em C ) ( expresso numrica )

    expresso ::= constante | varivel | invocao de uma funo | operador unrio expresso | expresso operador unrio expresso operador binrio expresso | ( expresso )

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 14

    A Figura 1.15 apresenta um exemplo da utilizao do operador cast. A diviso de duas variveis inteiras d um resultado inteiro. Para forar a diviso real preciso forar um dos operandos a double, fazendo um cast de um dos operandos, neste caso da varivel A.

    Figura 1.15 - Exemplo da utilizao do operador cast.

    Se a converso se efectua no sentido crescente da hierarquia, no h risco de overflow ou de perda de preciso. Caso contrrio, estes problemas podem ocorrer. Concretamente, quando a converso se efectua no sentido decrescente da hierarquia, podemos ter as situaes apresentadas na Figura 1.16.

    Figura 1.16 - Situaes possveis se a converso se efectuar no sentido decrescente da hierarquia.

    Vamos agora apresentar alguns exemplos representativos destes problemas. A Figura 1.17 apresenta um exemplo da situao em que se atribui um valor negativo de uma varivel int a uma varivel unsigned int. O valor armazenado na memria em binrio vai ser interpretado como sendo positivo, pelo que, h uma mudana na interpretao do valor.

    Figura 1.17 - Mudana na interpreta o do valor.

    A Figura 1.18 apresenta um exemplo da situa o em que se atribui uma varivel unsigned int a uma varivel unsigned char, que tem uma menor capacidade de armazenamento. O valor que vai ser armazenado em B constitudo pelos ltimos 8 bits de A, ou seja, o resto da diviso de A por 256, que o mximo valor que se pode armazenar num byte.

    int A = -1024; unsigned int B; ... B = A;

    /* A = -1024 10 = 1111 1111 1111 1111 1111 1100 0000 0000 2 */ /* B = 1111 1111 1111 1111 1111 1100 0000 0000 2 = 4294966272 10*/

    Tipos de dados de partida Tipos de dados de chegada long double double ou float

    existe arredondamento e portanto pode ocorrer overflow

    double float existe arredondamento e portanto pode ocorrer overflow

    qualquer tipo real qualquer tipo inteiro existe truncatura e portanto pode ocorrer overflow

    tipo inteiro signed ( unsigned ) mesmo tipo inteiro unsigned ( signed )mudana na interpretao do valor

    tipo inteiro unsigned tipo hierarquicamente inferior unsignedresto do mdulo do registo de chegada

    tipo inteiro signed tipo hierarquicamente inferior signedpode ocorrer overflow

    int A = 5, B = 2; double DIVISAO; ... DIVISAO = A / B; /* DIVISAO = 2.0 */FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DIVISAO = ( double ) A / B; /* DIVISAO = 2.5 */

  • 15 CAPTULO 1 : INTRODUO AO C

    Figura 1.18 - Resto do mdulo do registo de chegada.

    A Figura 1.19 apresenta um exemplo da situao em que se atribui uma varivel int a uma varivel char, que tem uma menor capacidade de armazenamento. Como o valor de A excede a capacidade de armazenamento de B, ento temos uma situao de overflow. Esta situao facilmente detectada, uma vez que, o valor de chegada negativo, quando o valor de partida era positivo.

    Figura 1.19 - Ocorrncia de overflow.

    No clculo de uma expresso complexa, uma vez fixado o agrupamento, segundo as regras da prioridade e da associatividade dos operadores envolvidos, modificadas ou no pela introduo de parnteses curvos, a ordem pela qual o compilador calcula as diferentes subexpresses em larga medida arbitrria. O compilador pode mesmo reorganizar a expresso, se isso no afectar o resultado final. Em geral, esta questo no acarreta consequncias graves. Contudo, sempre que o clculo de uma expresso envolva operadores com efeitos colaterais, ou seja, uma expresso em que o valor de uma ou mais variveis afectado pelo processo de clculo, normalmente devido utiliza o dos operadores unrios incremento e decremento, o cdigo resultante pode deixar de ser portvel. Assim, de extrema importncia organizar cuidadosamente a formao das expresses para se evitar que tais situaes ocorram. Quando no clculo de uma expresso existe o risco de ocorrncia de overflow em resultado de uma possvel transformao da expresso numa equivalente, tal como se mostra na Figura 1.20, isso deve ser impedido por decomposi o do clculo da expresso em duas ou mais expresses parcelares, seno o cdigo resultante pode deixar de ser portvel.

    Figura 1.20 - Exemplo de uma expresso onde existe o risco de ocorrncia de overflow.

    A linguagem C permite ainda a construo de um tipo especial de expresso, cuja finalidade fornecer o tamanho em bytes do formato de um tipo de dados particular. Esse tipo pode ser representado, explicitamente, pelo seu identificador, ou, implicitamente, por qualquer expresso desse tipo.

    int X, K1 = 1024, K2 = 4096, K3 = 4094; ... X = K1 * K1 * (K2 K3);

    /* se o compilador transformar a expresso na expresso */ /* aparentemente equivalente X = K1 * K1 * K2 K1 * K1 * K3 */

    /* vai ocorrer overflow para uma representao int em 32 bits */

    int A = 1152; char B; ... B = A;

    /* A = 1025 10 = 0000 0000 0000 0000 0000 0100 1000 0000 2 */ /* B = 1000 0000 2 = -128 10*/

    unsigned int A = 1025; unsigned char B; ... B = A;

    /* A = 1025 10 = 0000 0000 0000 0000 0000 0100 0000 0001 2 */ /* B = 0000 0001 2 = 110*/

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 16

    sizeof ( qualquer tipo de dados vlido em C ) ou sizeof ( expresso )

    No segundo caso, o valor da expresso nunca calculado, sendo unicamente determinado o seu tipo. A norma ANSI exige que o tipo do resultado do operador sizeof seja do tipo inteiro e unsigned, ou seja, do tipo unsigned int ou do tipo unsigned long. Este tipo o tipo size_t que est definido na biblioteca stdlib.

    1.6.2 Operadores

    A Figura 1.21 apresenta os operadores aritmticos disponveis na linguagem C.

    Figura 1.21 - Operadores aritmticos.

    Os operadores unrios incremento e decremento, s podem ser usados com variveis e, incrementam e decrementam o valor da varivel de uma unidade. Podem ser colocados antes da varivel, o que se designa por pr-incremento e pr-decremento. Nesse caso o valor da varivel alterado antes da sua utilizao na expresso em que a varivel est inserida. Ou, podem ser colocados depois da varivel, o que se designa por ps-incremento e ps-decremento. Nesse caso o valor da varivel s alterado depois da sua utilizao na expresso em que a varivel est inserida. A Figura 1.22 apresenta um exemplo da utilizao do operador unrio incremento. Na primeira expresso, uma vez que estamos perante o ps-incremento de X, ento no clculo de Y utilizado o valor de X antes deste ser incrementado, pelo que, Y fica com o valor 10 e s depois que o valor de X incrementado. Na segunda expresso, uma vez que estamos perante o pr-incremento de X, ento em primeiro lugar X incrementado e s depois calculado o valor de Y, pelo que, Y fica com o valor 15. Em ambas as expresses o valor final de X igual a 3.

    Figura 1.22 - Exemplo da utilizao do operador unrio incremento.

    int X = 2, Y; ... Y = 5 * X++; /* aps o clculo da expresso X = 3 e Y = 10 */FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF Y = 5 * ++X; /* aps o clculo da expresso X = 3 e Y = 15 */

    Operadores Unrios

    Operador Smbolo Sintaxe Observaes + +x Simtrico - -x Incremento de 1 ++ ++x x tem que ser x++ uma varivel Decremento de 1 -- --x x tem que ser x-- uma varivel

    Operadores Binrios

    Operador Smbolo Sintaxe Observaes Adio + x + y Subtraco - x - y Multiplicao * x * y Diviso / x / y y 0 Resto da diviso inteira % x % y y 0 e x e y tm de ser ex presses inteiras

  • 17 CAPTULO 1 : INTRODUO AO C

    A Figura 1.23 apresenta uma expresso que utiliza o operador ps-incremento incorrectamente, uma vez que, o cdigo resultante pode deixar de ser portvel. Neste tipo de expresso impossvel prever o valor final da expresso, uma vez que tal depende da ordem de clculo dos operandos. Pelo que, quando numa expresso existe um operador com efeito colateral, a varivel afectada s pode ser usada uma e uma nica vez.

    Figura 1.23 - Exemplo de uma expresso com utilizao incorrecta do operador ps-incremento.

    O operador diviso usado simultaneamente para representar o quociente da diviso inteira e da diviso real. Tal como se mostra na Figura 1.15, tratar-se- de uma diviso inteira, sempre que os operandos forem inteiros e tratar-se- de uma diviso real, quando pelo menos um dos operandos for real. Quando as expresses do quociente e do resto da diviso inteira so quantidades negativas, o resultado no univocamente determinado e depende do compilador utilizado. No caso do quociente da diviso inteira, a norma ANSI possibilita dois tipos de aproximao, a truncatura ou a aproximao ao maior inteiro, menor ou igual ao correspondente quociente real. Se os operandos so ambos positivos ou negativos, o resultado obtido pelos dois mtodos idntico. Se um dos operandos negativo, tal no se passa. Por exemplo, 7/(-3) ou -7/3, tanto pode produzir um quociente de -2, como de -3. Para evitar esta ambiguidade e garantir-se a portabilidade, sempre que haja a possibilidade do quociente ser negativo, a operao x/y deve ser substituda, por exemplo, pela expresso seguinte, de maneira a forar a aproximao por truncatura.

    (tipo inteiro) ((double) x/y)

    Para o resto da diviso inteira a norma ANSI impe que se verifique a seguinte condio.

    x = x % y + (x / y) * y

    Isto significa que, conforme o tipo de aproximao usado em x/y e a localizao do operando negativo, diferentes restos so possveis quando um dos operandos negativo.

    7 % (-3) = 1 -7 % 3 = -1 (truncatura) 7 % (-3) = -2 -7 % 3 = 2 (aproximao ao maior inteiro menor ou igual)

    Na prtica, o problema no to grave, porque a expresso x%y no faz matematicamente sentido para y negativo e, por isso, no deve nunca ser usada neste contexto. Alm disso, quando x negativo, fcil verificar que os dois resultados possveis so congruentes. Contudo, para se obter uma portabilidade completa, conveniente substituir a expresso x%y, pela construo condicional seguinte.

    if (x >= 0) r = x % y ; else r = y - (-x) % y ;

    A Figura 1.24 apresenta os operadores relacionais disponveis na linguagem C. Em relao ao Pascal existe apenas diferena nos operadores de igualdade e de desigualdade.

    int X, K = 3; ... X = K * K++;

    /* se a ordem de clculo for da esquerda para a direita, X = 9 */ /* se a ordem de clculo for da direita para a esquerda, X = 12 */

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 18

    Figura 1.24 - Operadores relacionais.

    A expresso resultante de tipo int e assume o valor zero, se o resultado da comparao for falso, e o valor um, se o resultado for verdadeiro. Ao contrrio do que se passa geralmente, a precedncia dos diversos operadores no a mesma. Os operadores maior ou igual, menor ou igual, maior e menor tm maior precedncia do que os operadores igual e diferente. Dado que os tipos reais fornecem apenas uma representao aproximada das quantidades numricas, nunca deve ser usado o operador igualdade com operandos desse tipo. Mesmo os operadores maior ou igual e menor ou igual devem ser usados com extremo cuidado. A expresso booleana de terminao de um processo de contagem, nomeadamente, nunca deve ser formada com expresses de tipo real. A Figura 1.25 apresenta os operadores lgicos que se aplicam tanto a expresses numricas inteiras como a reais. Os operandos so interpretados como representando o valor falso, se forem iguais a zero, e como representando o valor verdadeiro, em todos os restantes casos. A expresso resultante de tipo int e assume o valor zero, se o resultado da comparao for falso, e o valor um, se o resultado for verdadeiro. Ao contrrio do Pascal, uma expresso formada por operadores lgicos nem sempre calculada at ao fim. O clculo procede da esquerda para a direita e o processo interrompido logo que o resultado esteja definido.

    Figura 1.25 - Operadores l gicos.

    A Figura 1.26 apresenta os operadores para manipulao de bits que se aplicam apenas a expresses numricas inteiras.

    Figura 1.26 - Operadores de manipula o de bits.

    Operador Unrio

    Operador Smbolo Sintaxe Complemento (not) ~ ~x

    Operadores Binrios

    Operador Smbolo Sintaxe Conjuno (and) & x & y Disjuno inclusiva (or) | x | y Disjuno exclusiva (xor) ^ x ^ y Deslocamento direita >> x >> y Deslocamento esquerda y Menor < x < y Maior ou igual >= x >= y Menor ou igual

  • 19 CAPTULO 1 : INTRODUO AO C

    Os operadores lgicos actuam isoladamente sobre cada um dos bits dos operandos. Para os operadores lgicos binrios, as operaes so efectuadas em paralelo sobre os bits localizados em posies correspondentes de cada um dos operandos. O resultado da operao uma quantidade inteira do tipo do operando com maior capacidade de armazenamento, no caso dos operadores lgicos binrios, ou do tipo do operando x, no caso do operador complemento booleano ou dos operadores de deslocamento. Para os operadores de deslocamento, o operando y representa o nmero de posies a deslocar no sentido pretendido. O resultado da operao no est definido, quando y maior ou igual do que o comprimento em bits do operando x, ou negativo. A norma ANSI impe a realizao de um deslocamento lgico, quando x for de um tipo qualquer unsigned. Contudo, nada garantido quando x for de um tipo signed, embora normalmente o deslocamento seja ento aritmtico. Assim, para se obter uma portabilidade completa, deve fazer-se sempre um cast para tipos unsigned.

    (unsigned int ou unsigned long) x >> y (unsigned int ou unsigned long) x > = < esquerda o direita =! == esquerda o direita & esquerda o direita ^ esquerda o direita | esquerda o direita && esquerda o direita || esquerda o direita menor

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 20

    Figura 1.28 - Defini o formal da instruo de atribuio.

    A primeira variante da instruo de atribuio a instruo de atribuio tpica das linguagens imperativas, em que se atribui o valor de uma expresso a uma varivel. semelhante encontrada em Pascal. A nica diferena que o operador de atribuio , neste caso o =, em vez de :=. A Figura 1.29 apresenta trs exemplos.

    Figura 1.29 - Exemplos da primeira variante da instruo de atribuio. A segunda variante uma construo tpica da sintaxe compacta da linguagem C, onde o operador de atribuio precedido por um qualquer operador binrio. Esta variante definida pela expresso geral operador binrio=, em que o operador binrio representa qualquer operador binrio aritmtico (*, /, %, +, ), ou de manipulao de bits (&, |, ^, >>,

  • 21 CAPTULO 1 : INTRODUO AO C

    A importncia desta notao tem a ver de novo com a compactao resultante e com a possibilidade fornecida ao compilador de proceder mais facilmente a uma optimizao do cdigo gerado. A Figura 1.31 apresenta dois exemplos.

    Figura 1.31 - Exemplos da terceira variante da instruo de atribuio.

    A quarta variante uma instruo de atribuio condicional, onde o valor da primeira expresso avaliada e caso seja verdadeira, ento atribudo o valor resultante do clculo da segunda expresso varivel, seno atribudo o valor resultante do clculo da terceira expresso varivel. Comporta-se assim como a instruo condicional binria if then else e equivalente ao cdigo em linguagem C que se apresenta a seguir.

    if (expresso_1) identificador de varivel = expresso_2 ; else identificador de varivel = expresso_3 ;

    Em termos formais, trata-se de um caso particular da primeira variante em que a expresso a indicada do tipo expresso_1 ? expresso_2 : expresso_3, ou seja, o agrupamento das trs expresses pelo operador ? :. Este operador constitui o nico exemplo de operador ternrio existente na linguagem C. Enquanto que a segunda e a terceira expresses podem ser de qualquer tipo vlido na linguagem C, desde que compatveis, segundo as regras de converso, com o tipo da varivel, a primeira expresso de um tipo escalar bsico. A precedncia deste operador surge imediatamente abaixo dos operadores descritos at ao momento e a sua associatividade da direita para a esquerda. A Figura 1.32 apresenta um exemplo.

    Figura 1.32 - Exemplo da quarta variante da instruo de atribui o.

    Uma caracterstica notvel da Linguagem C que a instruo de atribuio tambm uma expresso. O seu valor o valor atribudo ao operando da esquerda, ou seja, ao identificador de varivel. O que faz com que os operadores = e operador binrio= apresentem uma precedncia imediatamente abaixo do operador condicional e uma associatividade igualmente da direita para a esquerda. Tornam-se por isso possveis instrues de atribuio mltipla, cuja sintaxe a que se apresenta a seguir.

    identificador de varivel_1 = ... = identificador de varivel_N = expresso ;

    Note-se que, devido associatividade e regra geral de converso automtica, relevante a ordem pela qual surgem na instruo as variveis, quando pertencentes a tipos escalares diferentes. Qualquer alterao a esta ordem pode conduzir a valores distintos atribudos a algumas delas. tambm perfeitamente possvel substituir, em qualquer ponto da cadeia de atribuies, o operador = pelo operador operador binrio=, numa das suas mltiplas formas. Isto, contudo, no deve nunca ser feito, porque a interpretao da instruo resultante torna-se muito difcil e, portanto, muito sujeita a erros. A Figura 1.33 apresenta dois exemplos de instrues de atribuio mltipla.

    int X, ABSX; ... ABSX = X < 0 ? X : X;

    /* equivalente a if (X < 0) then ABSX = -X; else ABSX = X; */

    int X, Y; ... Y++; /* equivalente a Y = Y + 1 */ --X; /* equivalente a X = X - 1 */

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 22

    Figura 1.33 - Exemplos de instrues de atribuio mltipla.

    A Figura 1.34 apresenta a precedncia e a associatividade entre os operadores de atribuio.

    Figura 1.34 - Precedncia e associatividade entre os operadores de atribuio.

    1.7 Estruturas de controlo

    1.7.1 Instrues decisrias

    Na linguagem C existem dois tipos de instrues de tomada de deciso. A instruo decisria binria if e a instruo decisria mltipla switch.

    1.7.1.1 A instruo decisri a binria if

    A instruo decisria binria if (se), cuja definio formal se apresenta na Figura 1.35, tem duas variantes que so fundamentalmente semelhantes s encontradas em Pascal. A nica diferena reside no facto do separador then no surgir aqui. Em consequncia, a expresso decisria obrigatoriamente colocada entre parnteses curvos para estabelecer a separao da instruo a executar. A expresso decisria deve ser de um tipo escalar bsico. A expresso falsa se for igual a zero e verdadeira se assumir qualquer outro valor. Nestas condies, o compilador aceita qualquer expresso numrica como expresso decisria vlida. Porm, por questes de clareza, isto deve ser evitado. de bom estilo que a expresso decisria represente sempre uma expresso booleana.

    Figura 1.35 - Defini o formal da instruo if.

    Uma questo muito importante para editar programas legveis o alinhamento das instrues. A Figura 1.36 apresenta como se deve alinhar a instruo if. No caso da variante mais simples e se existir apenas uma instruo simples curta, ento a instruo if pode ser toda escrita na mesma linha, mas se a instruo simples for longa deve ser escrita na linha seguinte mais alinhada para a direita. Caso a instruo seja composta, ento os separadores { e } devem ser alinhados com o if. No caso da variante completa, devemos alinhar o separador else com o if.

    instruo decisria binria ::= if ( expresso ) instruo simples ou composta |

    if ( expresso ) instruo simples ou composta else instruo simples ou composta

    expresso ::= expresso decisria de um tipo escalar bsico

    Operadores na classe Associatividade Precedncia operador condicional ? : direita o esquerda maior

    = += -= *= /= %= direita o esquerda menor >>=

  • 23 CAPTULO 1 : INTRODUO AO C

    Figura 1.36 - Alinhamento da instruo if .

    A Figura 1.37 apresenta um encadeamento de instrues if, o que se designa por instrues if encadeadas (nested if structures). Neste tipo de agrupamento, a regra de agrupamento semelhante de Pascal. O compilador associa sempre o separador else instruo if que ocorreu imediatamente antes.

    Figura 1.37 - Construo de instrues if encadeadas.

    Vamos agora considerar a situao apresentada na Figura 1.38, em que queremos ter o separador else para o primeiro if, mas em que o segundo if no o tem. Neste tipo de situao, que se designa por else desligado (dangling else), o separador else vai ser atribudo pelo compilador ao segundo if, independentemente de ter sido alinhado com o primeiro if.

    Figura 1.38 - Situao do else desligado.

    Para resolver este problema existem as duas solues apresentadas na Figura 1.39. A primeira, consiste em usar uma instruo composta a abraar o segundo if, de maneira a informar o compilador onde acaba o segundo if. A segunda, consiste em usar um separador else com uma instruo nula, para emparelhar com o segundo if, e assim forar o emparelhamento do segundo separador else com o primeiro if. A instruo nula o ;.

    if ((CAR >= A) && (CAR = a) && (CAR = 0) && (CAR 0) if (V2 > 0) V2++; else V1++; /* situao do else desligado */

    if ( expresso ) instruo simples; FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF if ( expresso ) { instruo simples; ... instruo simples; } FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF if ( expresso ) { instruo simples; ... instruo simples; } else { instruo simples; ... instruo simples; }

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 24

    Figura 1.39 - Solues para a situao do else desligado.

    Devido ao facto das instrues de atribuio constiturem tambm expresses, um erro muito frequentemente cometido por programadores que se esto a iniciar na utilizao da linguagem C e que no sinalizado pelo compilador, consiste em trocar o operador identidade pelo operador de atribuio na escrita da condi o decisria, tal como se apresenta na Figura 1.40. O valor da expresso A = 5 5 e como um valor diferente de zero, ento a expresso verdadeira e vai ser executada a instruo B = 2. Pelo que, aps a execuo da instruo, as variveis A e B vo assumir respectivamente, os valores 5 e 2.

    Figura 1.40 - Erro devido troca do operador identidade pelo operador de atribuio.

    1.7.1.2 A instruo decisri a mltipla switch

    Muitas das situaes de estruturas if encadeadas podem ser resolvidas atravs da instruo decisria mltipla switch (comutador), cuja definio formal se apresenta na Figura 1.41.

    Figura 1.41 - Defini o formal da instruo switch.

    Embora as palavras reservadas na linguagem C sejam distintas das do Pascal, a estrutura sintctica da instruo de deciso mltipla essencialmente a mesma nas duas linguagens. Na norma ANSI, a expresso de deciso, bem como as constantes que formam a lista de constantes, so de qualquer tipo escalar bsico inteiro. A principal diferena decorre do modo como os vrios ramos de seleco esto organizados. Em Pascal, eles so mutuamente exclusivos, enquanto que, na linguagem C, a execuo sequencial a partir do

    instruo decisria mltipla ::= switch ( expresso ) { bloco de execuo }

    bloco de execuo ::= bloco de execuo selectivo | bloco de execuo bloco de execuo terminal

    bloco de execuo selectivo ::= lista de constantes sequncia de instrues simples | bloco de execuo selectivo lista de constantes sequncia de instrues simples

    lista de constantes ::= case constante inteira : | lista de constantes case constante inteira :

    bloco de execuo terminal ::= default : sequncia de instrues simples

    if (A = 5) B = 2; /* o que se pretendia era if (A == 5) B = 2; */ else B = 4;

    if (V1 > 0) { if (V2 > 0) V2++; } else V1++; FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF if (V1 > 0) if (V2 > 0) V2++; else ; else V1++;

  • 25 CAPTULO 1 : INTRODUO AO C

    ponto de entrada. Para se conseguir o mesmo tipo de execuo encontrado em Pascal, a ltima instruo de cada sequncia de instrues ter que ser obrigatoriamente a instruo break, tal como se mostra na Figura 1.43. A Figura 1.42 apresenta como se deve alinhar a instruo. Para aumentar a legibilidade, o bloco de execuo selectivo e o bloco de execu o terminal devem ser alinhados mais direita. As sequncias de instrues simples devem ser todas alinhadas, permitindo uma melhor anlise das instrues que vo ser executadas.

    Figura 1.42 - Exemplo da utilizao e alinhamento da instruo switch.

    A Figura 1.43 faz a comparao da instruo switch com a instruo case.

    Figura 1.43 - Comparao da instruo switch com a instruo case.

    preciso ter em considerao que a instruo switch no to poderosa como a instruo case do Turbo Pascal, uma vez que a ltima permite que a lista de valores enumerada possa ser constituda por um literal, ou por um conjunto de literais separados pela vrgula, ou por um intervalo de valores, ou por combinaes de todas estas situaes.

    1.7.2 Instrues repetitivas

    Tal como na linguagem Pascal, na linguagem C existem dois tipos de instrues de repetio. As instrues while e do while, cujo nmero de iteraes previamente desconhecido, tm uma estrutura de controlo condicional. A instruo for, cujo nmero de iteraes previamente conhecido, tem normalmente uma estrutura de controlo contadora.

    case CAR of /* na linguagem Pascal */ a, e, o : writeln (Vogais speras); i, u : writeln (Vogais doces); else writeln (Outros smbolos grficos) end ; FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF switch (CAR) /* na linguagem C */ { case a : case e : case o : printf ("Vogais speras\n"); break ; case i : case u : printf ("Vogais doces\n"); break ; default : printf ("Outros smbolos grficos\n"); }

    switch ( expresso ) { case V1 : instruo simples; case V2 : instruo simples; break ; case V3 : instruo simples; instruo simples; break ; case V4 : instruo simples; break ; default : instruo simples; }

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 26

    1.7.2.1 As instrues repetitivas while e do while

    A Figura 1.44 apresenta a definio formal das instrues while (enquanto fazer), e do while (fazer enquanto).

    Figura 1.44 - Definio formal das instrues while e do while.

    As instrues while e do while correspondem, respectivamente, s sintaxes while do e repeat until encontradas no Pascal. A expresso decisria deve ser de um tipo escalar bsico. A expresso falsa se for igual a zero e verdadeira se assumir qualquer outro valor. Nestas condies, o compilador aceita qualquer expresso numrica como expresso decisria vlida. Porm, por questes de clareza, isto deve ser evitado. de bom estilo que a expresso decisria represente sempre uma expresso booleana. Ao contrrio da instruo repeat until do Pascal, a sequncia de instrues simples da instruo do while colocada entre os separadores { e }. No entanto, existe uma diferena semntica importante entre a instruo repeat until do Pascal e a instruo do while da linguagem C. Em Pascal, o ciclo repetitivo executado at a expresso decisria de terminao ser verdadeira. Mas, na linguagem C, o ciclo repetitivo executado enquanto a expresso decisria de terminao for verdadeira. Ou seja, em situaes equivalentes, uma necessariamente a negao da outra. Tal como em Pascal, a utilizao correcta de qualquer das instrues supe que a expresso decisria de terminao possa ser modificada, durante a execuo do ciclo repetitivo, para que o seu valor passe eventualmente de verdadeiro a falso. Caso contrrio, o ciclo repetitivo seria infinito. preciso igualmente garantir que todas as variveis que constituem a expresso decisria so previamente inicializadas. A Figura 1.45 apresenta como se deve alinhar a instruo while. No caso da variante mais simples e se existir apenas uma instruo simples curta, ento a instruo while pode ser toda escrita na mesma linha, mas se a instruo simples for longa deve ser escrita na linha seguinte mais alinhada para a direita. No caso da variante, em que, o corpo do ciclo repetitivo constitudo por uma instruo composta, os separadores { e } que definem a instruo composta devem ser alinhadas pela palavra reservada while, e a sequncia de instrues simples so escritas, uma por linha, todas alinhadas mais direita de maneira a que seja legvel onde comea e acaba o ciclo repetitivo.

    Figura 1.45 - Alinhamento da instruo while.

    instruo while ::= while ( expresso ) instruo simples ou composta FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF instruo do while ::= do { sequncia de instrues simples } while ( expresso ) ;

    while ( expresso ) instruo simples; FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF while ( expresso ) { instruo simples; ... instruo simples; }

  • 27 CAPTULO 1 : INTRODUO AO C

    A Figura 1.46 apresenta como se deve alinhar a instruo do while. As palavras reservadas do e while devem ser alinhadas e a condio booleana de terminao deve ser escrita frente do terminador while. As instrues que constituem o corpo do ciclo repetitivo so escritas uma por linha e todas alinhadas mais d ireita de maneira a que seja legvel onde comea e acaba o ciclo repetitivo.

    Figura 1.46 - Alinhamento da instruo do while.

    A Figura 1.47 faz a comparao dos ciclos repetitivos do while e while, para calcular a mdia de um nmero indeterminado de nmeros lidos do teclado, sendo que, a leitura termina quando lido o valor zero. No caso do ciclo repetitivo do while, as instrues constituintes do corpo do ciclo repetitivo, contagem do nmero til de nmeros lidos e sua soma, necessitam de ser protegidas quando lido o valor de terminao, para no provocar um clculo errneo.

    Figura 1.47 - Exemplo comparativo da utilizao dos ciclos repetitivos do while e while.

    1.7.2.2 A instruo rep etitiva for

    A Figura 1.48 apresenta a definio formal da instruo repetitiva for (para fazer enquanto), cujo nmero de iteraes previamente conhecido. A instruo for da linguagem C constitui uma superinstruo que no tem correspondncia em mais nenhuma linguagem. A parte da inicializao executada em primeiro lugar e uma s vez. Em geral, a sua funo atribuir valores iniciais a uma ou mais variveis usadas no ciclo repetitivo. A parte da terminao uma expresso que calculada antes do incio de cada nova iterao e que determina a continuao, se for verdadeira, ou no, se for falsa, do processo repetitivo.

    SOMA = 0.0; /* clculo da mdia com o ciclo do while */ do { printf ("Introduza um numero? "); scanf ("%lf", &NUMERO); if (NUMERO != 0.0) { SOMA += NUMERO; N++; } } while (NUMERO != 0.0); FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF SOMA = 0.0; /* clculo da mdia com o ciclo while */ printf ("Introduza um numero? "); scanf ("%lf", &NUMERO); while (NUMERO != 0.0) { SOMA += NUMERO; N++; printf ("Introduza um numero? "); scanf ("%lf", &NUMERO); }

    do { instruo simples; ... instruo simples; } while ( expresso ) ;

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 28

    A expresso deve ser de um tipo escalar bsico. A expresso falsa se for igual a zero e verdadeira se assumir qualquer outro valor. Nestas condies, o compilador aceita qualquer expresso numrica como expresso decisria vlida. Porm, por questes de clareza, isto deve ser evitado. de bom estilo que a expresso decisria represente sempre uma expresso booleana. Finalmente, a parte de actualizao executada no fim de cada iterao. Em geral, a sua funo actualizar os valores de uma ou mais variveis usadas no ciclo repetitivo, entre as quais, sempre a varivel contadora. Normalmente, as expresses de actualiza o das variveis recorrem aos operadores unrios incremento e decremento ou ao operador de atribuio precedido por um qualquer operador binrio.

    Figura 1.48 - Defini o formal da instruo for.

    A instruo for deve ser alinhada da mesma maneira que a instruo while. A Figura 1.49 faz a comparao da instruo for do Pascal e do C usando como exemplo, o clculo das primeiras N potncias de 2.

    Figura 1.49 - Exemplo do clculo das potncias de 2 com um ciclo repetitivo for.

    Qualquer uma das trs partes componentes da instruo for, a inicializao, a terminao ou a actualizao pode ser omitida. Situaes especiais correspondem aos seguintes casos:

    x Quando todos os elementos so omitidos, ou seja, for ( ; ; ), temos um ciclo repetitivo infinito.

    x Quando a inicializao e a actualizao so omitidas, ou seja, for ( ; terminao ; ), temos um ciclo repetitivo funcionalmente equivalente ao ciclo repetitivo while. Ao contrrio do que se passa em Pascal, a varivel contadora tem um valor bem definido aps o esgotamento do ciclo repetitivo, que ser aquele que conduziu a um valor falso da expresso de terminao.

    POT := 1; /* na linguagem Pascal */ for I := 1 to N do begin writeln (Potencia de 2 = , POT:10); POT := POT * 2 end ; FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF for (POT = 1, I = 1 ; I

  • 29 CAPTULO 1 : INTRODUO AO C

    Devido sua grande versatilidade, a instruo for deve ser utilizada com extremo cuidado. de bom estilo us-la apenas como uma generalizao da instruo for de Pascal. O que significa, nomeadamente, que o valor da varivel contadora nunca deve ser modificado dentro do ciclo repetitivo. Um dos erros mais frequentemente cometido por programadores que se esto a iniciar na utilizao da linguagem C, consiste em esquecer que o ciclo repetitivo for actua enquanto a expresso de terminao for verdadeira. Se no exemplo anterior fosse utilizada a condio I == N, o ciclo repetitivo no seria executado uma nica vez, a no ser que N fosse 1, quando se pretende que o ciclo repetitivo seja executado N vezes. Os ciclos repetitivos podem ser encadeados, tal como a instruo condicional binria, dando origem a estruturas repetitivas em que, uma instruo do corpo do ciclo repetitivo ela mesmo um ciclo repetitivo. A este tipo de construo d-se o nome de ciclos imbricados (nested loops).

    1.7.2.3 Instrues nula, break e continue

    H ainda trs instrues que so muitas vezes usadas em conjunto com as instrues repetitivas. So elas a instruo nula ou muda, a instruo break e a instruo continue. A instruo nula, expressa pela colocao do separador ;, surge muitas vezes associada com as instrues while e for quando se pretende que o corpo do ciclo repetitivo no tenha qualquer instruo. A Figura 1.50 apresenta um exemplo que determina o comprimento de uma cadeia de caracteres. Uma vez que uma cadeia de caracteres um agregado de caracteres terminado obrigatoriamente com o carcter \0, ento preciso detectar o ndice do agregado onde ele est armazenado. O que possvel fazer atravs de uma instruo repetitiva em que a expresso de teste, recorrendo ao operador unrio incremento, provoca o deslocamento dentro do agregado e, portanto, o ciclo repetitivo no carece de qualquer instruo.

    Figura 1.50 - Clculo do comprimento de uma cadeia de caracteres.

    A instruo break uma instruo que, genericamente, permite a sada intempestiva do bloco que est a ser executado. A sua utilizao no caso da instruo de deciso mltipla switch possibilitou, como se viu, transformar operacionalmente esta instruo e adequ-la ao tipo de funcionalidade encontrado na instruo case do Pascal. A sua aplicao em conjuno com as instrues while e do while tambm muito interessante, j que possibilita em muitas situaes prticas reduzir a complexidade da expresso de terminao, aumentando por isso a clareza e a legibilidade do cdigo correspondente. A Figura 1.51 apresenta um exemplo em que se pretende obter a soma de um mximo de 10 valores no negativos lidos do dispositivo de entrada, sendo que a leitura parar mais cedo se for lido um valor negativo. Compare as solues e repare na simplificao da condio de terminao da verso em linguagem C, devido utilizao da instruo break.

    COMP = -1; while (s[++comp] != \0) ;

    /* ou em alternativa com o ciclo for */

    for (comp = 0; s[comp] != \0; comp++) ;

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 30

    Figura 1.51 - Exemplo da utilizao da instruo break.

    A instruo continue uma instruo que permite interromper a execuo do ciclo repetitivo, forando o fim da iterao presente. A sua aplicao em conjuno com a instruo for possibilita em muitas situaes prticas reduzir a complexidade do ciclo repetitivo, aumentando por isso tambm a clareza e a legibilidade do cdigo correspondente. A Figura 1.52 apresenta um exemplo em que se pretende contar o nmero de caracteres alfabticos de uma linha de texto e converter as vogais a existentes de minsculas para maisculas e vice-versa. O exemplo apresentado a seguir socorre-se das rotinas de manipulao de caracteres e de cadeias de caracteres das bibliotecas de execuo ANSI e supe, por isso, a incluso dos ficheiros de interface ctype.h e string.h.

    Figura 1.52 - Exemplo da utilizao da instruo continue.

    N := 0; /* na linguagem Pascal */ for I := 1 to length (LINHA) do if LINHA[I] in [A..Z,a..z] then begin N := N + 1; if LINHA[I] in [A,E,I,O,U,a,e,i,o,u] then if LINHA[I] in [A,E,I,O,U] then LINHA[I] := chr(ord(LINHA[I])-ord(A)+ord(a)) else LINHA[I] := chr(ord(LINHA[I])-ord(a)+ord(A)) end ; FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF for (I = 0, N = 0; I < strlen (LINHA); I++) /* na linguagem C */ { if (!isalpha (LINHA[I])) continue ; N++; if ((toupper(LINHA[I]) != A) && (toupper(LINHA[I]) != E) && (toupper(LINHA[I]) != I) && (toupper(LINHA[I]) != O) && (toupper(LINHA[I]) != U)) continue ; if (isupper (LINHA[I])) LINHA[I] = LINHA[I]-A+a; else LINHA[I] = LINHA[I]-a+A; }

    N := 0; /* na linguagem Pascal */ SOMA := 0.0; readln (NUMERO); while (N < 10) and (NUMERO >= 0.0) do begin N := N + 1; SOMA := SOMA + NUMERO; readln (NUMERO) end ; FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF N = 0; /* na linguagem C */ SOMA = 0.0; scanf ("%lf", &NUMERO); while (N < 10) { if (NUMERO < 0.0) break ; N++; SOMA += NUMERO; scanf ("%lf", &NUMERO); }

  • 31 CAPTULO 1 : INTRODUO AO C

    Compare as solues e repare que as instru es condicionais na verso em linguagem C esto simplificadas, uma vez que no necessitam de ser encadeadas, para evitar as situa es em que os caracteres no precisam de ser convertidos. Se o carcter no um carcter alfabtico, ou se um carcter alfabtico, mas no uma vogal, ento fora-se a prxima iterao do ciclo repetitivo, usando para esse efeito a instruo continue.

    1.7.2.4 Ciclos repetitivos infinitos

    Um ciclo repetitivo infinito um ciclo repetitivo que no tem condio de terminao, ou cuja condio de termina o est mal implementada. A maior parte desses ciclos repetitivos so criados por engano. Mas, existem situaes em que se pretende de facto implementar um ciclo repetitivo infinito. Por exemplo, quando se pretende uma aplica o que repete sistematicamente uma determinada operao at que uma condio particular se verifica. Nessa situao mais fcil construir um ciclo repetitivo, que terminado atravs do recurso instruo break. A Figura 1.53 apresenta as duas formas mais simples de implementar ciclos repetitivos infinitos.

    Figura 1.53 - Ciclos repetitivos infinitos.

    1.8 Entrada e sada de dados

    Tal como em qualquer outra linguagem, as instrues de entrada e de sada da linguagem C so instrues especiais que estabelecem uma interface com o sistema operativo na comunicao com os diferentes dispositivos do sistema computacional, em particular com os dispositivos convencionais de entrada e de sada. Normalmente, o dispositivo de entrada o teclado e mencionado pelo identificador stdin, e o dispositivo de sada o monitor e mencionado pelo identificador stdout. Os dispositivos de entrada e de sada funcionam da seguinte forma. Sempre que se prime uma tecla do teclado, gerada uma palavra binria, representando o valor atribudo ao carcter correspondente, em cdigo ASCII por exemplo, que armazenada num registo do controlador do teclado. O sistema operativo alertado para esse facto e, eventualmente, vai ler o contedo do registo e armazenar sequencialmente o valor lido numa regio da memria principal que se designa por armazenamento tampo de entrada (input buffer). O valor tambm quase sempre colocado no armazenamento tampo de sada do monitor, para posterior envio para o monitor, produzindo-se assim a interaco visual com o utilizador. Em termos da instruo de entrada, o armazenamento tampo de entrada visto como uma sequncia ordenada de caracteres, organizada em linhas que consistem em zero ou mais caracteres, com representao grfica, ou com funes de controlo, seguidos do carcter de fim de linha que o \n. A esta sequncia de caracteres d-se o nome de fluxo de caracteres (text stream). De um modo geral, s linhas completas presentes no fluxo de caracteres de entrada esto disponveis para leitura. medida que os caracteres vo sendo processados pela instruo de entrada, vo sendo retirados do fluxo de caracteres de entrada.

    while ( 1 ) /* com o ciclo while */ instruo simples ou composta FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF for ( ; ; ) /* com o ciclo for */ instruo simples ou composta

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 32

    Por outro lado, o fluxo de texto de sada implementado numa regio da mem ria principal, directamente acessvel pelo sistema operativo, que se designa por armazenamento tampo de sada (output buffer). Aps a escrita de uma sequncia de caracteres pela instruo de sada, o sistema operativo alertado para esse facto e, eventualmente, vai transferir essa sequncia, carcter a carcter, para um registo do controlador do monitor. Em resultado disso, a mensagem associada reproduzida no monitor a partir da posio actual do cursor e da esquerda para a direita. Alm de caracteres com representao grfica, as sequncias escritas podem conter caracteres de controlo diversos que possibilitam, entre outras aces mais ou menos especficas, o deslocamento do cursor para uma outra posio do monitor. Na linguagem C, as instrues de entrada e de sada so funes de tipo int que pertencem biblioteca de execuo ANSI e cuja descrio est contida no ficheiro de interface stdio.h.

    1.8.1 A funo scanf

    Na linguagem C, a entrada de dados implementada pela funo scanf cuja sintaxe se apresenta na Figura 1.54. A funo scanf l sequncias de caracteres do fluxo de caracteres de entrada (stdin) e processa-as segundo as regras impostas pelo formato de leitura, armazenando sucessivamente os valores convertidos nas variveis, cuja localizao indicada na lista de ponteiros de variveis. Salvo em duas situaes especiais adiante referidas, deve existir uma relao de um para um entre cada especificador de converso e cada varivel da lista de ponteiros de variveis. Se o nmero de variveis da lista de ponteiros de variveis for insuficiente, o resultado da operao no est definido. Se, ao contrrio, o nmero de variveis for demasiado grande, as variveis em excesso no so afectadas. O tipo da varivel e o especificador de converso devem ser compatveis, j que a finalidade deste ltimo indicar, em cada caso, que tipos de sequncias de caracteres so admissveis e como devem ser tratadas. Quando o especificador de converso no vlido, o resultado da operao no est definido. O processo de leitura s termina quando o formato de leitura se esgota, lido o carcter de fim de ficheiro, ou existe um conflito de tipo entre o que est indicado no formato de leitura e a correspondente quantidade a ser lida. Neste ltimo caso, o carcter que causou o conflito mantido no fluxo de caracteres de entrada. A funo devolve o nmero de valores lidos e armazenados, ou o valor fim de ficheiro (EOF), se o carcter de fim de ficheiro lido antes que qualquer converso tenha lugar. Se, entretanto, ocorreu um conflito, o valor devolvido corresponde ao nmero de valores lidos e armazenados at ocorrncia do conflito. Pondo de parte o facto de se tratar de uma instruo de leitura formatada, scanf semanticamente equivalente instruo read de Pascal. No existe, contudo, na linguagem C um equivalente directo para a instruo readln. O papel de um carcter separador no formato de leitura forar a eliminao do fluxo de caracteres de entrada de todos os caracteres deste tipo at primeira ocorrncia de um carcter distinto. O papel de um literal no formato de leitura estabelecer a correspondncia com sequncias idnticas de caracteres existentes no fluxo de caracteres de entrada, e, por consequncia, elimin-las do processo de leitura. A correspondncia com o carcter %, que no um literal, obtida atravs do especificador de converso %% e no h lugar a qualquer armazenamento. O supressor de atribuio, que o carcter * , possibilita que uma sequncia de caracteres seja lida e convertida, mas o seu valor no seja armazenado em qualquer varivel.

  • 33 CAPTULO 1 : INTRODUO AO C

    Figura 1.54 - Defini o formal da funo scanf.

    int scanf ( formato de leitura , lista de ponteiros de variveis )

    formato de leitura ::= " cadeia de caracteres de definio" cadeia de caracteres de definio ::= especificador de converso | carcter separador | literal | cadeia de caracteres de definio especificador de converso | cadeia de caracteres de definio carcter separador | cadeia de caracteres de definio literal

    especificador de converso ::= %carcter de converso | %modificador de converso carcter de converso

    carcter de converso ::= i R l uma quantidade inteira genrica d R l uma quantidade inteira em decimal (signed) u R l uma quantidade inteira em decimal (unsigned) o R l uma quantidade inteira em octal (unsigned) x R l uma quantidade inteira em hexadecimal (unsigned) f R e R g R l uma quantidade real em decimal c R l caracteres s R l uma cadeia de caracteres [ lista de caracteres] R l uma cadeia de caracteres imposta pela lista de caracteres. Se o primeiro carcter for o ^ ento l uma cadeia de caracteres at encontrar um carcter da lista de caracteres p R l o valor de um ponteiro para void n R permite contar o nmero de caracteres lidos at ao seu aparecimento na cadeia de caracteres de definio

    modificador de converso ::= largura mxima de campo | especificador de dimenso | largura mxima de campo especificador de dimenso | supressor de atribuio | supressor de atribuio largura mxima de campo | supressor de atribuio especificador de dimenso | supressor de atribuio largura mxima de campo especificador de dimenso

    largura mxima de campo ::= valor decimal positivo que indica o nmero mximo de caracteres a serem lidos

    especificador de dimenso ::= h R com i, d, u, o, x, indica tipo short l R com i, d, u, o, x, indica tipo long R com f, e, g, indica tipo double L R com f, e, g, indica tipo long double

    supressor de atribuio ::= *

    carcter separador ::= carcter espao, carcter tabulador \t e carcter fim de linha \n

    literal ::= um ou mais caracteres diferentes do carcter separador ou do carcter %

    lista de ponteiros de variveis ::= ponteiro de varivel de tipo aritmtico | ponteiro de varivel de tipo void | lista de ponteiros de variveis , ponteiro de varivel de tipo aritmtico | lista de ponteiros de variveis , ponteiro de varivel de tipo void

    ponteiro de varivel de tipo aritmtico ::= & varivel de tipo aritmtico

    ponteiro de varivel de tipo void ::= & varivel de tipo void

  • PROGRAMAO ESTRUTURAS DE DADOS E ALGORITMOS EM C 34

    Na funo scanf as variveis so parmetros de entrada-sada, pelo que, tm de ser passadas funo por referncia, ou seja, por endereo. Para passar o endereo de uma varivel de tipo simples, temos que preceder a varivel pelo operador endereo que o carcter & . No caso das variveis de tipo agregado, o prprio nome da varivel representa o endereo do elemento inicial da varivel, e portanto, so usados no scanf normalmente.

    1.8.1.1 Leitura de caracteres

    Para a leitura de um carcter, o especificador de converso deve ser %c ou %1c. Para a leitura de mais do que um carcter, o nmero de caracteres a ser lido ser igual largura mxima de campo e ser necessrio garantir que o ponteiro da varivel referencia uma regio de memria com capacidade para o armazenamento dos caracteres lidos, tipicamente, um agregado de caracteres. A Figura 1.55 apresenta um exemplo de leitura de um carcter para a varivel CAR e de oito caracteres para um agregado de caracteres. A declarao char ARRAYCAR[8] define um agregado de 8 caracteres, sendo que o ndice do primeiro elemento do agregado o ndice zero. A invocao da funo, para uma linha de dados introduzida pelo teclado e constituda pelos caracteres a1234567890\n, resulta no armazenamento do carcter a na varivel CAR e dos caracteres 1 a 8 nos elementos sucessivos do agregado ARRAYCAR. Para passar o endereo da varivel CAR usa-se o operador &. Para indicar o incio do agregado de caracteres ARRAYCAR, ou se menciona apenas o nome do agregado, ou se indica o endereo do primeiro elemento do agregado &ARRAYCAR[0].

    Figura 1.55 - Exemplo da leitura de um carcter.

    A Figura 1.56 apresenta um excerto de cdigo em que se utiliza o scanf para ler uma varivel de tipo carcter, mas, de forma equivalente ao readln do Pascal.

    Figura 1.56 - Leitura de um carcter equivalente instruo readln do Pascal.

    #include int main (void) { char CAR, /* escolha da opo do menu */ CARX; /* varivel auxiliar para teste de fim de linha */ ... do { printf ("1 - Opo_1\n"); printf ("2 - Opo_2\n"); printf ("3 - Opo_3\n"); printf ("Qual a sua escolha? "); scanf ("%c", &CAR); /* ler o carcter que representa a opo */ if (CAR != \n) do /* descartar todos os eventuais restantes caracteres */ { /* da linha , incluindo o carcter fim de linha */ scanf ("%c", &CARX); } while (CARX != \n); } while ((CAR < 1) || (CAR > 3)); ... }

    char CAR, ARRAYCAR[8]; ... scanf ("%c%8c", &CAR, ARRAYCAR); / * ou em alternativa scanf ( "%c%8c" , &CAR, &ARRAYCAR[ 0] ); */

  • 35 CAPTULO 1 : INTRODUO AO C

    Depois da leitura da varivel de tipo carcter, caso tenha sido lido um carcter diferente de fim de linha ento vo ser lidos todos os caracteres que eventualmente existam no armazenamento tampo de entrada at leitura do carcter fim de linha inclusive, usando para o efeito uma varivel auxiliar de tipo carcter.

    1.8.1.2 Leitura de uma cadeia de caracteres

    Uma cadeia de caracteres (string) tipicamente um agregado de caracteres, terminado com o carcter especial \0. Este carcter especial serve de indicador de fim da cadeia de caracteres, e obviamente, ocupa uma posio de armazenamento do agregado. Pelo que, se necessitarmos de armazenar uma cadeia de caracteres com MAX_CAR caracteres, devemos definir um agregado de caracteres com o tamanho MAX_CAR+1. O subdimensionamento de uma cadeia de caracteres um dos erros mais frequentemente cometido por programadores que se esto a iniciar na utilizao da linguagem C. A leitura de mais do que um carcter usando o especificador de converso %c no muito prtica, pelo que, prefervel efectu-la como a leitura de uma cadeia de caracteres. Para a leitura de uma sequncia de caracteres que no contenha um carcter separador como elemento constituinte, no fundo, para a leitura de uma palavra, deve-se usar o seguinte especificador de converso.

    %nmero mximo