Escola Superior de Tecnologia TRANSIÇÃO PARA Para programadores de José Cordeiro e Joaquim Filipe 1998
ESCOLA SUPERIOR DE TECNOLOGIA / INSTITUTO POLITÉCNICO DE SETÚBAL Transição para Para programadores de ª Dezembro 1998 José Cordeiro e Joaquim Filipe Escola Superior de Tecnologia / Instituto Politécnico de Setúbal R do Vale de Chaves, Estefanilha 2910 Setúbal Telefone (065) 790000 Fax (065) 721869 j.cordeiro@computer.org jfilipe@est.ips.pt
Índice ÍNDICE 3 INTRODUÇÃO 4 ALGUMAS DIFERENÇAS FUNDAMENTAIS ENTRE O PASCAL E O 4 ESTRUTURA DO PROGRAMA 5 Blocos 5 Comentários 5 Funções 5 TIPOS DE DADOS ELEMENTARES 6 VARIÁVEIS 7 Definição 7 Inicialização 7 Variáveis globais 7 Existência e visibilidade 7 CONSTANTES 8 OPERADORES E EXPRESSÕES 9 Operadores aritméticos 9 Operadores relacionais 9 Operadores Lógicos 9 Atribuição 9 Precedências 10 Funções associadas aos tipos de dados elementares 11 Expressões 11 Conversão entre tipos de dados elementares 11 Operador de coerção (cast) 12 ENTRADA E SAÍDA DE DADOS 13 Entrada de Dados 13 Saída de Dados Simples 13 Saída de Dados Formatada 14 ESTRUTURAS DE CONTROLO - SELECÇÃO 14 Instrução If 14 Instrução Switch 15 ESTRUTURAS DE CONTROLO - REPETIÇÃO 16 Instrução for 16 Instrução while 16 Instrução do-while 17 SUBPROGRAMAS 17 Funções e Procedimentos 17 Transmissão de Parâmetros 17 Retorno de Valores (funções) 18 Protótipos de Funções 19 TIPOS DE DADOS AVANÇADOS ADOS 19 Tipo enumerativo 19 Matrizes 19 Cadeias de caracteres 20 Registos 21 Ficheiros 22 Tipos de dados definidos pelo utilizador 22 ESTRUTURAS DE DADOS DINÂMICAS 23 Listas 23
Introdução A linguagem surge como uma extensão à linguagem C trazendo consigo dois importantes conjuntos de novidades: 1. Melhorias e acrescentos à linguagem C 2. Possibilidade de programação orientada por objectos Este segundo conjunto de novidades não será tido em conta neste manual. Este manual pretende ilustrar, de uma forma prática e simples, a passagem da linguagem para a linguagem. Neste sentido apenas se referem as características existentes na linguagem que têm correspondência na linguagem. Para uma transição completa seria ainda necessário analisar as características do que funcionam como uma extensão ao e as novas possibilidades de programação orientada por objectos. A transição para a partir do é feita directamente não se fazendo nenhuma referência à linguagem C nem sendo necessário o conhecimento desta. Isto deve-se, principalmente, a esta forma de transição ser mais simples e preferível. Algumas Diferenças Fundamentais entre o e o Do ponto de vista da transição do para o estas linguagens podem ser consideradas muito semelhantes sendo as suas diferenças principalmente de sintaxe. Para uma melhor compreensão convém, no entanto, referir algumas diferenças básicas de sintaxe e estrutura que se consideram fundamentais: EM A a Maiúsculas diferenciadas ; O Sinal ; termina instruções f() Subprogramas só funções f() Funções ao mesmo nível Enquanto em é indiferente a utilização de maiúsculas e minúsculas, em existe essa distinção, sendo todas as palavras reservadas desta linguagem escritas em letras minúsculas. O ponto-e-vírgula em é utilizado para separar instruções. Em esse caracter é utilizado para terminar instruções. Enquanto em os subprogramas podem ser de dois tipos: funções e procedimentos, em apenas existem funções. Contudo, existe em o tipo void (vazio), pelo que é possível obter, em, o mesmo efeito dos procedimentos do, utilizando funções que retornam void. Os subprogramas em (as funções) estão todos ao mesmo nível, não se podendo colocar uns dentro de outros como acontece em. 4
Estrutura do Programa Blocos Em, o conceito de bloco é essencial. Sintacticamente, o programa é constituído por um cabeçalho, um bloco e um ponto final. O bloco contém, por sua vez, diversas zonas incluindo, em particular, todas as funções e procedimentos utilizados pelo programa, bem como o corpo (zona de instruções) do programa principal. Em não existe o conceito de bloco. O programa principal é apenas mais uma função, ainda que com um nome especial: main. Esta função é a primeira a ser executada. Todas as outras funções definem-se ao mesmo nível desta função principal. Blocos de instruções definidos entre os símbolos e Em todas as instruções encontram-se agrupadas dentro de uma outra forma de bloco: o bloco de instruções. Este bloco é delimitado pelas palavras begin e end.. Tal como acontece em em esta forma de bloco existe utilizando-se neste caso como caracteres delimitadores as chavetas. Este tipo de bloco pode também ser utilizado sempre que se precise de colocar mais que uma instrução num local onde a sintaxe da linguagem obrigue à utilização de apenas uma instrução (Ex.: nos ciclos for) e denomina-se neste caso de instrução composta. Begin <instrução>; <instruções> end <instrução>; <instruções>; FIGURA 1 Bloco de instruções ou instrução composta Comentários começados pela sequência de símbolos // e válidos apenas na linha corrente Funções com declaração obrigatória. Comentários Os comentários colocados dentro de um programa em são introduzidos de forma diferente: são começados pela sequência // e levam o compilador a ignorar tudo o que apareça a seguir dentro da mesma linha. Existe também a possibilidade de utilizar comentários muito semelhantes aos usados em em que as chavetas que delimitam os comentários (Ex.: comentário...) são substituídas em pela sequência de símbolos /* e */ (Ex.: /*comentário...*/). No entanto, esta segunda forma de comentar é desaconselhada por ser mais difícil de gerir e poder originar erros de programação. Funções Todas as funções em necessitam de ser declaradas antes de serem utilizadas para que o compilador reconheça o seu formato, algo semelhante à utilização da declaração forward em. 5
Enquanto em as funções standard da linguagem são automaticamente reconhecidas, em é necessário fornecer a sua declaração. Isto é feito habitualmente através de uma linha de código colocada no início do programa. Esta linha leva o programa de compilação a incluir no programa o texto de um ficheiro contendo a declaração das funções utilizadas (e.g. #include <iostream.h> para incluir o ficheiro iostream.h com as principais funções de entrada e saída do ). Na figura seguinte dá-se um exemplo de um programa completo escrito em e em para permitir a comparação da estrutura do programa nas duas linguagens. PROGRAM exemplo (input, output); VAR N : integer; função factorial: FUNCTION fact(i : integer) : integer; BEGIN if i<=1 then fact:=1 else fact:=n*fact(i-1) END; corpo do programa principal BEGIN Write('Escreva um número'); Read(n) Write('Factorial de n = ', fact(n)) END. LISTAGEM 1 programa para calcular o factorial de um número. #include <iostream.h> // função factorial: int fact(int i) if (i <= 1) return 1; else return i*fact(i-1); // corpo da função principal main() int n; cout << "Escreva um número "; cin >> n; cout << "Factorial de n = " << fact(n); Tipos de Dados Elementares Entre os tipos de dados básicos do iremos considerar os tipos caracter, inteiro, real e lógico. Tipos de dados caracter e inteiro idênticos. Tipo lógico implementado através de inteiros. Tipo real dividido em dois tipos: float e double. Os tipos de dados caracter e inteiro são semelhantes nas linguagens e. O tipo real aparece em sub-divido em dois tipos: float e double. Estes tipos são idênticos permitindo guardar um valor real, a sua única diferença é a precisão, tendo o tipo double uma precisão dupla em relação ao tipo float. Deve-se acrescentar que é preferível usar o tipo double por questões de compatibilidade com as funções matemáticas do. O tipo lógico não é suportado directamente pelo, em vez disso utiliza-se o valor inteiro 0 para representar o valor falso e um valor inteiro diferente de 0 (normalmente o 1 ou o 1) para representar o valor lógico verdadeiro. 6
& Na tabela 2 apresenta-se uma correspondência entre os tipos de dados elementares de ambas as linguagens: Char Integer Real Boolean char int float/double int TABELA 1 Correspondência entre tipos de dados elementares Variáveis Variáveis definidas em qualquer sítio do código com possíbilidade de inicialização simultânea Definição As variáveis em não possuem uma zona própria de definição como em podendo ser criadas em qualquer sítio do código. Como princípio devem-se definir as variáveis o mais perto possível do local em que são utilizadas. A definição das variáveis faz-se colocando primeiro o tipo da variável e a seguir o seu nome, terminando-se com o caracter ponto-e-vírgula: <variável> <tipo> <nome>; Tal como em é possível definir simultaneamente mais que uma variável do mesmo tipo desde que se separem os seus nomes por vírgulas. Inicialização Em é possível ainda inicializar uma variável na altura da sua definição. Isto é feito colocando após o nome de cada variável o sinal de igual seguido do seu valor inicial (Ex.: int valor = 10; ). Variáveis globais definidas fora do corpo das funções. Variáveis globais As variáveis que pertencem ao programa principal em consideram-se globais e podem ser reconhecidas em qualquer parte do programa. Uma vez que em as funções estão ao mesmo nível, as variáveis da função principal (main) são locais a esta não podendo ser referidas a partir de outras funções. Para que uma variável seja global em esta deve ser declarada fora do corpo das funções. Deve-se, contudo, evitar o uso das variáveis globais. Existência e visibilidade As variáveis locais em, tal como em, só existem e são visíveis dentro do bloco (conjunto de código delimitado por chavetas) em que estão declaradas. De notar ainda que as variáveis em só são reconhecidas dentro dum bloco a partir do ponto em que foram declaradas. 7
PROGRAM conversão (input, output); CONST Polegadas = 2.54; VAR Comprimento : real; variável global Resultado : real; variável global BEGIN Write('Valor em polegadas: '); Read( comprimento ); Resultado := comprimento*polegadas; Write('Em centímetros = ',resultado); END. #include <iostream.h> const double polegadas = 2.54; double comprimento; // variável global main() cout << "Valor em polegadas: "; cin >> comprimento; double resultado; // variável local resultado = comprimento*polegadas; cout << "Em centímetros " << resultado; LISTAGEM 2 Definição de variáveis e constantes Constantes É possível em e em utilizar dois tipos de constantes: As constantes definidas pelo utilizador associadas a um identificador e os valores constantes. Na tabela 3 apresentam-se exemplos de alguns valores constantes nas duas linguagens. -3.02-3.02 0.15E-2 0.15E-2 23 23 $FF40 0xFF40 False 0 a algum texto a algum texto TABELA 2 Correspondência entre valores constantes Constantes definidas como variáveis utilizando o modificador const Para definir constantes através de um identificador em utiliza-se a zona de definição de constantes referenciada pela palavra CONST. Em as constantes definem-se nos mesmos locais e da mesma forma que as variáveis acrescentando-se antes do tipo a palavra reservada const. Habitualmente as constantes definem-se no inicio do programa. CONST nome = José ; PI = 3.14159; Diasdasemana = 7; Escudo = $ ; const char *nome = José ; const double PI = 3.14159; const int Diasdasemana = 7; const char Escudo = $ ; LISTAGEM 3 Definição de constantes 8
Operadores e Expressões Operadores semelhantes aos do utilizando por vezes símbolos diferentes Operadores aritméticos Os operadores aritméticos aplicados aos valores reais são os mesmos em ambas as linguagens. No que diz respeito à sua aplicação a valores inteiros enquanto em se usam as palavras reservadas DIV e MOD para as operações de divisão e resto em usam-se os símbolos / e %. As operações realizadas são equivalentes. Operadores relacionais Apenas as operações de verificação de igualdade e diferença adoptam símbolos diferentes nas duas linguagens, sendo utilizado em o conjunto de símbolos == para a igualdade e o conjunto!= para a diferença. Operadores Lógicos Embora as operações lógicas em ambas as linguagens sejam semelhantes os símbolos adoptados são bastante diferentes. Em usam-se os símbolos && para a operação lógica e, para a operação ou e! para a negação. Não existe equivalente em para a operação lógica ou exclusivo do. Atribuição é uma operação em que devolve o valor que foi atribuído Atribuição Atribuir um valor a uma variável é uma operação em que devolve como resultado o valor atribuído. O símbolo adoptado é o sinal de igual =. Em a atribuição é por seu lado uma instrução única que usa o símbolo :=. É necessário tomar cuidado com o facto de que o símbolo de comparação entre dois valores em ser o mesmo que o de atribuição em o que pode levar a que este seja usado erradamente numa operação de comparação como por exemplo: if(x=5) em vez de if(x==5) ). Este caso é perfeitamente válido em, significa que o valor 5 é atribuído à variável x devolvendo 5 como resultado da operação de atribuição. Uma vez que este valor é diferente de 0 a condição do if é verdadeira e o código do if é sempre executado. VAR N:Integer; BEGIN Atribuição de um valor a uma variável N := 10; Comparação de dois valores IF N = 10 THEN Write( Os valores são iguais ); END. int n; // Atribuição de um valor a uma variável n = 10; // Comparação de dois valores if ( n == 10 ) cout << Os valores são iguais ; LISTAGEM 4 Comparação versus atribuição 9
A tabela seguinte sumariza as equivalência de operadores: + + - - * * / e DIV / MOD % < < <= <= > > >= >= = == <>!= not! and && or TABELA 3 Correspondência de operadores. Precedências com regras diferentes Precedências As regras de precedência entre operadores são diferentes em e. Sempre que surjam dúvidas devem-se utilizar os parênteses. A tabela 4 apresenta um resumo das regras de precedência numa e noutra linguagem. not! * / div mod and * / % + - or + - < <= > >= <> = < <= > >= TABELA 4 Precedência de operadores. ==!= && = 10
Funções básicas associadas aos tipos de dados elementares com equivalentes em Funções associadas aos tipos de dados elementares Existe um conjunto de funções pré-definidas no associadas aos tipos básicos. Na tabela 5 apresentam-se as funções do que se aplicam aos tipos básicos e produzem resultados do mesmo tipo. Note-se que em é necessário fornecer a declaração das funções antes de serem usadas. Isto é conseguido incluindo o ficheiro de extensão.h onde se encontra a declaração (Ex.: para a função fabs deve-se colocar no ficheiro uma linha com #include <math.h> para fornecer a declaração da função). Tipo base Função Função Declaração em : Abs abs stdlib.h ou math.h inteiro Sqr Abs fabs stdlib.h ou math.h Sqr Real Sqrt sqrt math.h Sin sin math.h Cos cos math.h Arctan atan math.h Ln log math.h Exp exp math.h Caracter Pred Succ TABELA 5 Correspondência de funções aplicadas aos tipos básicos. Expressões definidas com base apenas nos tipos reais e inteiro Expressões Enquanto em as operações e as expressões podem ser de qualquer um dos tipos elementares mencionados, em as expressões só podem ser do tipo inteiro ou do tipo real (float ou double). Isto significa na prática que todas as expressões lógicas e do tipo caracter são efectuadas a partir do tipo inteiro. Os caracteres em expressões envolvendo este tipo são convertidos para o seu valor em código ASCII, sendo este o valor usado nas expressões. O valor lógico também é utilizado como um inteiro, sendo 0 para o valor falso e qualquer outro valor (diferente de zero) para o valor lógico verdadeiro. Conversão entre tipos de dados elementares Existem alguns casos em que é possível misturar diferentes tipos de dados numa expressão. Por exemplo quando se utilizam operadores relacionais, embora o tipo de dados dos operandos tenha de ser idêntico o resultado é 11
sempre do tipo lógico. Em é também possível misturar o tipo inteiro com o tipo real numa expressão através da conversão automática do inteiro num real obtendo-se como resultado um valor real. Outra forma de se misturar tipos de dados diferentes é utilizar funções que operam sobre um dado tipo de dados e produzem como resultado um tipo diferente. Conversão de tipos utiliza o operador de coerção (cast) Na tabela seguinte apresentam-se as funções que, em, operam sobre um tipo de dados e produzem um tipo diferente como resultado. Estas funções permitem obter a conversão entre tipos de dados. Em existe correspondência para apenas uma destas funções. Nesta linguagem a conversão normal de tipos é realizada duma forma automática pelo compilador ou utilizando um operador especial o operador de coerção. Função Conversão Função Declaração em : Trunc Real Inteiro Floor (*) math.h Round Ord Chr Odd Real Inteiro Caracter Inteiro Inteiro Caracter Inteiro Lógico TABELA 6 Correspondência de funções aplicadas aos tipos básicos. usadas na conversão de tipos. * Esta função devolve um valor do tipo double que possui parte decimal nula Operador de coerção (cast) É possível, de uma maneira geral, utilizar tipos diferentes em operações em. Se for necessário o compilador converte automaticamente um dos tipos (por exemplo uma operação envolvendo inteiros e reais (float ou double) os inteiros são convertidos em reais antes da operação). Quando a conversão resultar em perca de informação ou for duvidosa o compilador gera uma mensagem de aviso. Para eliminar a mensagem, e de uma certa forma informar o compilador que a conversão é válida e pretendida, o programador deve realizar uma operação de coerção sobre o tipo que pretende converter. A operação de coerção permite a conversão explicita entre tipos de dados diferentes. Esta operação obtem-se colocando o tipo, para o qual se quer converter um valor ou expressão, seguido entre parênteses do valor ou expressão a converter. (Ex.: 2 + 4 * int(2.3) produz o resultado inteiro 10). Esta forma de coerção possui uma notação funcional, ou seja como se fosse uma função: o nome da função é dado pelo tipo de dados e o valor entre parênteses é o argumento. Existe, no entanto uma outra notação para a coerção, herdada da linguagem C, que consiste em colocar o tipo para o qual se quer converter entre parênteses antes do valor. (Ex.: 2 + 4 * (int) 2.3 ). 12
VAR Ch:Character; I:Integer; Val:Real; char ch; int i; BEGIN Ch := a ; I := ord(ch); Writeln( Ascii de:, ch, é, I ); Writeln( Ascii:, i, é, chr(i) ); Val := 3.124; Write( Real:, val ); Writeln( para inteiro:, trunc(val) ); END. LISTAGEM 5 Conversão de tipos e operação de coerção ch = a ; i = int( ch ); // i=ch; era suficiente por // conversão automática cout << Ascii de: << ch << é: << i; cout << endl; cout << Ascii: << i << é: << char(i); cout << endl; val = 3.124; cout << Real: << val; cout << para inteiro: << int(val); // ou então int(floor(val)) cout << endl; Entrada e Saída de Dados A entrada e saída de dados mais comum utiliza respectivamente o teclado e o monitor. Os ficheiros serão abordados mais à frente. Entrada de dados feita através do operador de extracção e da variável cin Entrada de Dados As instruções read e readln do são substituídas, em, por uma única instrução: cin, que se comporta como o read. Enquanto a lista de parâmetros de read é apresentada entre parênteses encontrando-se os parâmetros separados por vírgulas, a instrução cin encontra-se separada dos parâmetros pelo operador de extracção >> utilizando-se o mesmo operador para separar os parâmetros entre si. Saída de dados feita através do operador de inserção e da variável cout Saída de Dados Simples As instruções write e writeln, do, são substituídas, em, por uma única instrução: cout, que é equivalente ao write. Tal como acontece com a instrução de entrada de dados, cin, também o cout utiliza um operador, neste caso o operador de inserção << para separar os parâmetros do comando e para separar os parâmetros entre si. Podem ainda ser utilizados caracteres especiais como o tab(\t) e o newline(\n). program exemplo(input,output); var a : integer; b : real; c : char; main() int a; float b; char c; begin read(a, b, c); writeln('valores lidos: ', a, b, c); end. LISTAGEM 6 Instruções de entrada e saída de dados cin >> a >> b >> c; cout << "Valores lidos: " << a << b << c; 13
Formatação da saída de dados utiliza manipuladores Saída de Dados Formatada Em é possível definir na instrução write o número mínimo de caracteres com que um parâmetro deve aparecer escrito. Isto é conseguido colocando a seguir ao parâmetro um sinal de dois pontos seguido de um valor inteiro com o número de caracteres de escrita. Para o caso de números reais é ainda possível definir o número de casas decimais juntando mais um sinal de dois pontos e o total de casa decimais (Ex.: escrita de um real com 6 caracteres e duas casas decimais Write( valor:6:2 );). Em o mesmo efeito é conseguido através de manipuladores que se incluem antes dos parâmetros a serem escritos. O manipulador setw permite definir em o número mínimo de caracteres de escrita e o manipulador setprecision o número de casas decimais. Estes manipuladores possuem uma sintaxe semelhante às funções levando um argumento com o valor numérico (ver exemplo na listagem 7). Existe ainda o manipulador endl que se utiliza para mudar de linha. VAR N:Integer; R: Real; BEGIN N := -10; R := 23.126 Write( Valor inteiro, N:5 ); Writeln; Writeln ( Valor real, R:8:2 ); Writeln( FIM :5, ------ ) END. OUTPUT: Valor inteiro - -10 Valor real - 23.12 Fim------ LISTAGEM 7 Saída de dados formatada int n; double r; n = -10; r = 23.126; cout << Valor inteiro - << setw(5) << n; cout << endl; cout << Valor real << setw(8) << setprecision(2) << r << endl; cout << setw(5) << Fim << ------ << endl; // OUTPUT: // Valor inteiro - -10 // Valor real - 23.12 // Fim------ NOTA Quando se utilizam manipuladores em deve-se incluir o ficheiro iomanip.h no início do programa, ou seja acrescentar a linha: #include <iomanip.h>. Estruturas de Controlo - Selecção Em existem duas instruções de selecção: if e case. As instruções correspondentes em são: if e switch. Instrução if com condição lógica entre parênteses e sem then Instrução If A instrução if do requer que a condição lógica esteja entre parênteses, isto deve-se ao facto de não ser utilizada a palavra then que funciona como marcador do fim da condição em. 14
A sintaxe é a seguinte: <instrução if> if ( <condição> ) <instrução1>; [ else <instrução2>; ] if x=y then a:=1 else a:=2; if (x==y) a=1; else a=2; LISTAGEM 8 Instrução if Instrução switch substitui case do Instrução Switch A instrução case do possui como equivalente em a instrução switch. O argumento desta instrução em deve ser fornecido entre parênteses, e deve ser de um tipo inteiro ou compatível (tipos caracter ou enumerativo). Também as diferentes possibilidades de selecção são precedidas em pela palavra reservada case podendo apenas existir um valor de cada hipótese. Ainda outra diferença é que se for necessário fornecer mais que uma instrução em não é necessário colocar as chavetas para as agrupar. A sintaxe é então a seguinte: <Inst. switch> switch (<expressão>) case <const> : <instrução>; LISTAGEM 9 Instrução switch case mes of 1,3,5,7,8,10,12: ultimodia := 31; 4,6,9,11: ultimodia := 30; 2: if ano mod 4 = 0 then ultimodia := 29 else ultimodia := 28; otherwise: ultimodia := 0 end; switch( mes ) case 1: case 3: case 5: case 7: case 8: case 10: case 12: ultimodia = 31; break; case 4: case 6: case 9: case 11: ultimodia = 30; break; case 2: if ( ano%4 == 0 ) ultimodia = 29; else ultimodia = 28; break; default: ultimodia = 0; break; 15
NOTA Para a correspondência directa entre a instrução case do e a instrução switch do deve-se acrescentar uma instrução break no final de cada case do. Estruturas de Controlo - Repetição As três instruções de repetição do são: for, while e repeat. Em as instruções correspondentes são: for, while e do. Instrução for com formato e funcionalidade diferentes em Instrução for A instrução for do é muito mais poderosa e versátil que a do.. Esta instrução possui entre parênteses uma lista de outras três instruções fornecidas pelo programador. A primeira destas instruções é corrida apenas uma vez quando se entra no for e é utilizada normalmente para iniciar uma variável de controlo. A segunda instrução fornece um valor lógico e é corrida sempre que se inicia a passagem no ciclo, se o valor lógico for falso o ciclo já não é executado passando-se à instrução seguinte. A terceira e última instrução é corrida sempre que se termina uma passagem pelo ciclo e utilizase normalmente para alterar o valor da variável de controlo. A sintaxe da instrução for é a seguinte: <Inst. for> for(<instr1>;<instr2>;<instr3>) <instrução>; for i:=0 to 10 do begin j:=2*i+1; writeln('impar:, j) end; for (i=0; i<=10; i=i+1) j=2*i+1 cout << "impar: " << j << endl; LISTAGEM 10 Instrução for Instrução while com a condição entre parênteses e sem do Instrução while A instrução while é idêntica em e, necessitando apenas em que a condição esteja entre parênteses não sendo então utilizada a palavra do. A sintaxe é então a seguinte: <Inst. while> while (<condição>) <instrução>; 16
Fact := 1; N := 5; Termo := N; While termo > 1 do Begin Fact := fact * termo; Termo := termo 1; End Writeln( Factorial de, N, =, fact); fact = 1; n = 5; termo = n; while (termo > 1) fact = fact * termo; termo = termo 1; cout << Factorial de << n << = << fact << endl; LISTAGEM 11 Instrução while Instrução do-while sustitui repeat-until do Instrução do-while A instrução correspondente ao repeat-until do é a instrução do-while do. Esta instrução funciona de uma forma ligeiramente diferente, enquanto em o ciclo termina quando uma determinada condição é verdadeira em o ciclo termina quando a condição é falsa, continuando enquanto essa condição for verdadeira. A condição aparece em entre parênteses. A sintaxe desta instrução é: <Inst.do> do <instrução>; while (<expressão>); repeat write(em que numero pensei?'); read(n); if n = numero then writeln('acertou!!') else if n < numero then writeln('baixo...') else writeln('alto...') until n=numero; LISTAGEM 12 Instrução do-while do cout << "Em que numero pense?"; cin >> n; if (n==numero) cout << "Acertou!!" << endl; else if (n < numero) cout << "Baixo..." << endl; else cout << "Alto..." << endl; while (n!=numero); Subprogramas Funções e Procedimentos Conforme foi referido na secção inicial, o difere do num aspecto fundamental: enquanto o dispõe de dois tipos de subprogramas (funções e procedimentos) o apenas tem um tipo: as funções. Para emular os procedimentos, em, pode recorrer-se a funções que retornam o tipo void, uma vez que este tipo significa vazio é o mesmo que retornar vazio ou seja não retornar nada. Transmissão de Parâmetros Tanto em como em é possível passar parâmetros por valor e por referência. 17
Parâmetros com passagem por referência são obtidos colocando & antes do nome da variável Para o a diferenciação do método de transmissão de cada parâmetro é efectuada na definição da função (indicação ou não da palavra reservada var) e não na sua invocação. Em passa-se o mesmo utilizando-se na lista de parâmetros da função o operador & antes do nome de cada variável a passar por referência. De notar ainda que em é obrigatório indicar nos parâmetros de uma função o tipo antes de todas as variáveis, não se podendo referir um tipo para um grupo de variáveis separadas por vírgula. PROGRAAM transmiteref (input, output); VAR a, b : real; PROCEDURE troca (var x, y : real); VAR aux : real; BEGIN Aux := x; x := y y := aux END; BEGIN Write( Escreva dois reais: ); Readln(a, b); Writeln(a, b, -- Após troca: ); Troca(a, b); Write(a, b) END. LISTAGEM 13 Passagem de parâmetros para funções. #include <iostream.h> void troca(double &x, double &y) double aux; aux = x; x = y; y = aux; main() double a, b; cout << "Escreva dois reais: "; cin >> a >> b; cout << a << b << " -- Após troca:\n"; troca(a, b); cout << a << b; Retorno de Valores (funções) O tipo do valor que é retornado por uma função em aparece como primeiro elemento antes do nome da função, ao contrário do em que este aparece a seguir ao símbolo : após a lista de parâmetros da função. Retorno de funções feita a partir da instrução return que termina também a função O retorno de um valor, em, efectua-se por atribuição do mesmo a uma pseudo variável com o mesmo nome que a função. Em a devolução de um valor efectua-se através da instrução return <expressão>, a qual também termina imediatamente a execução da função devolvendo o resultado de <expressão>. function fact (i : integer) : integer; begin if i<=1 then fact:=1 else fact:=i*fact(i-1) end; int fact (int i) if (i<=1) return 1; else return i*fact(i-1); LISTAGEM 14 Retorno de funções 18
Protótipos de Funções Um protótipo de uma função é uma declaração que, para além do nome da função, especifica os tipos de dados dos parâmetros e do resultado devolvido. É terminado pelo ponto-e-vírgula. O compilador de utiliza o protótipo para verificar a validade sintáctica das invocações da função. Esta declaração é análoga à declaração forward, do. O protótipo das função deve aparecer antes da chamada da função. Normalmente, é boa prática colocar os protótipos das funções num ficheiro separado com o mesmo nome do ficheiro fonte mas com a extensão.h, o qual é inserido no início do ficheiro fonte utilizando a directiva #include. Por exemplo, se se definirem 10 funções, para além da função main, no ficheiro prog.c, deve construir-se um ficheiro designado prog.h, cujo conteúdo deverá ser uma lista dos 10 cabeçalhos (headers) das funções definidas em prog.c, terminados por ponto-e-vírgula (;). Tipos de Dados Avançados O é uma linguagem com uma tipificação muito rica possuindo uma grande quantidade de tipos de dados. Embora o apresente grande parte dos tipos do alguns deles não têm correspondência em como é o caso dos tipos sub-domínio e conjunto (set). Tipo enumerativo O tipo enumerativo encontra-se entre os tipos que existem igualmente em, possuindo no entanto uma sintaxe diferente nesta linguagem: <tipo enum> enum <nome> <ident1>, <ident2>, ; Matrizes apenas com índices inteiros que começam obrigatoriamente no valor zero Matrizes As matrizes (arrays) permitem armazenar sequências de elementos, todos do mesmo tipo, identificados por um ou mais números de ordem (índices). Uma importante diferença na definição e utilização de matrizes é que em o índice é sempre um inteiro cujo valor inicial é obrigatoriamente zero. Quando se declara uma matriz em fornece-se o número total de elementos existentes, digamos n. De notar que este número nunca aparece como índice da matriz uma vez que começando no valor 0 os seus índices variam entre 0 e n-1, sendo n o total de elementos da matriz. Uma característica muito importante do é que a escrita e leitura de matrizes não é testada pela linguagem, pelo que é possível escrever ou ler de um índice que não exista. Quando isto acontece na operação de escrita pode ser desastroso uma vez que se estará a escrever na memória numa zona onde poderá estar o código ou dados do programa corrompendo-os. Nas listagens 15 e 16 apresentam-se exemplos com matrizes unidimensionais e bidimensionais. 19
program media10valores(input,output); var mat:array[1..10] of real; x : real; I : integer; Begin x := 0.0; writeln( Introduza 10 valores reais: ); for i:=1 to 10 do begin write( I, º valor: ); read( mat[i] ); x := x + mat[i]; end; main() double mat[10]; double x x = = 0.0; cout << << "Introduza Introduza 10 10 valores reais:\n"); reais:\n ); for( i=0; i<9; i=i+1 i=i+1 )) cout << << i+1 i+1 << << "º º valor: "; ; cin cin >> >> mat[i]; x x = = x x + + mat[i]; cout << << "Média = = "" << << x x // 10.0 << << endl; writeln("média = ", x / 10.0 ) end. LISTAGEM 15 Média de uma matriz de valores reais program exemplo(input,output); var mat:array[0..9, 0..4] of real; x : real; begin mat[2, 3]:=2.4325; x:= mat[2, 3]*3.14; mat[4, 0]:=x*mat[2, 3]; write("elem.4,0 = ", mat[4, 0]) end. ; main() double mat[10][5]; double x; mat[2][3] =2.4325; x= mat[2][3]*3.14; mat[4][0] =x*mat[2][3]; cout << "Elem.4,0 = " << mat[4][0]; LISTAGEM 16 Utilização de matrizes bidimensionais Cadeias de caracteres Tal como acontece em as matrizes de caracteres ou cadeias de caracteres (strings) possuem um tratamento especial. Este tipo de matrizes unidimensionais serve para guardar um pedaço de texto. Strings definidas como matrizes de caracteres e terminadas com o caracter ASCII de valor zero Em pode-se armazenar numa matriz de caracteres um pedaço de texto de qualquer dimensão que não exceda o tamanho da matriz. Para que seja possível determinar o fim do texto por convenção acrescenta-se um caracter especial - o caracter \0 (com o código ASCII 0), no fim do texto. Todas as funções que trabalham com cadeias de caracteres em adoptam esta convenção. Assim sendo, uma matriz de caracteres deverá ter espaço para o texto incluindo o caracter de terminação. As funções de leitura e escrita de dados tratam as cadeias de caracteres como se fossem compostas por um único valor de um tipo especial. Assim é possível escrever ou ler uma cadeia de caracteres fornecendo apenas o seu identificador. Outro aspecto importante das cadeias de caracteres em é que sobre elas não existem operações definidas. Assim todas as operações com cadeias de caracteres passam pelo uso de funções. Na tabela seguinte fornece-se uma pequena lista de funções que permitem efectuar as operações existentes em sobre cadeias de caracteres do. 20
operação função Declaração em : Comparação strcmp String.h Junção strcat String.h Cópia strcpy String.h Dimensão strlen String.h TABELA 7 Operações com cadeias de caracteres em. main() char string[100]; char *string1 = "Inicio do texto"; char string2[20]; cout << "Dimensão da string1: " << strlen(string1); // Obter o numero de caracteres strcpy( string2, "e fim do texto"); // Copiar para string2 o texto do segundo parâmetro strcpy( string, string1 ); strcat( string, string2 ); // Copiar para string o texto de string1 // Juntar a string o texto de string2 cout << "Texto final: " << string; LISTAGEM 17 Exemplo de operações com cadeias de caracteres Estruturas equivalentes ao tipo registo do Registos Os registos são estruturas de dados que permitem agrupar elementos de diversos tipos, referenciados através de identificadores. O Equivalente aos registos em são as estruturas de cuja sintaxe é a seguinte: <tipo estrutura> struct <nome> <variável1>; <variável2>;... ; program exemplo(input,output); var pt1 : record x : real; y : real end; begin pt1.x:=2.4325; pt1.y:=3.14; pt1.x:=pt1.x*4.5; write("ponto= ", pt1.x, pt1.y) end. LISTAGEM 18 Utilização de registos ou estruturas main() struct ( float x; float y; ) pt1; pt1.x=2.4325; pt1.y=3.14; pt1.x=pt1.x*4.5; cout << "Ponto= " << pt1.x << pt1.y; 21
Ficheiros implementados pelos tipos ifstream e ofstream Ficheiros Os ficheiros permitem armazenar informação sobre a forma de uma sequência de elementos todos do mesmo tipo. Os dados são guardados de forma permanente, em dispositivos de memória não volátil, como por exemplo os discos rígidos. Ainda que seja possível manipular ficheiros de elementos de qualquer tipo, o tipo de ficheiros mais comum é o de caracteres (texto em formato ASCII). Em não existe o tipo ficheiro, sendo utilizado um conjunto de tipos especiais definidos pelo utilizador que são pré-definidos na linguagem: Em particular os tipos ifstream para leitura de dados e ofstream para escrita de dados. program exemplo(input,output); var fich1, fich2 : file of char; a : integer; b : real; c : char; begin assign(fich1, "out.txt"); assign(fich2, "in.txt"); rewrite(fich1); reset(fich2); read(fich2, a, b, c); write(fich1, a, b, c); close(fich2); close(fich1) end. main() ofstream fich1; // para escrita ifstream fich2; // para leitura int a; float b; char c; fich1.open("out.txt"); fich2.open("in.txt"); fich2 >> a >> b >> c; fich1 << a << b << c; fich2.close(); fich1.close(); LISTAGEM 19 Utilização de ficheiros Nota: A manipulação de ficheiros não está abrangida pelo standard, pelo que as instruções acima referidas podem ter ligeiras alterações sintácticas em diferentes sistemas operativos. Tipos de dados definidos pelo utilizador Também em é possível definir novos tipos de dados com base nos existentes. Definição de tipos efectuada em qualquer lugar do código utilizando a palavra chave typedef Os novos tipos de dados em não possuem uma zona de definição própria como em, sendo declarados nos mesmos locais que as variáveis. Normalmente os novos tipos são declarados num ficheiro separado conjuntamente com a declaração de funções e variáveis externas. Este ficheiro é depois incluído no programa usando a directiva #include. Alternativamente os novos tipos são declarados no início do programa. A sintaxe da definição de tipos em é a seguinte: <Def de tipo> typedef <tipo existente> <nome>; 22
array[ ] of <tipo> <tipo> [ ] Record File struct ofstream ou ifstream ( val1, val2,...) enum val1, val2,...) p^ *p TABELA 8 Resumo dos tipos de dados avançados. Estruturas de Dados Dinâmicas Tanto o como o proporcionam a possibilidade de definir e utilizar estruturas de dados cuja topologia e número de elementos varia ao longo da execução do programa. Por esse motivo, estas estruturas de dados são designadas como estruturas de dados dinâmicas. O elemento essencial para a construção de estruturas de dados deste género é um tipo de dados elementar, designado ponteiro (pointer). As variáveis deste tipo contêm valores especiais: endereços de posições de memória. Desta forma, a informação não se encontra no valor da variável mas sim no valor da posição de memória guardada na variável, conforme se ilustra na figura 1. P1 (pointer) 1214843 *P1 () / P1^ () (nº real, na posição de mem. 1214843) 3.14159 FIGURA 1: Exemplo de uma referência indirecta por meio de um ponteiro Dada a variável do tipo pointer, é possível obter o valor por ela indicado de forma indirecta, através do acesso à chamada variável referenciada pelo ponteiro. A identificação da variável referenciada é obtida, em, acrescentando no fim do nome do ponteiro um acento circunflexo (^), enquanto em é necessário acrescentar um asterisco (*) no início. É preciso muito cuidado ao trabalhar com este tipo de dados uma vez que através deles é possível escrever em qualquer sitio da memória incluindo sobre o próprio programa ou sobre os seus dados. Listas Uma lista é uma estrutura de dados dinâmica, semelhante a um vector (array unidimensional) com a diferença de o número dos seus elementos poder aumentar ou diminuir durante a execução do programa, consoante as necessidades. 23
Esta possibilidade oferece duas vantagens importantes: 1. Se o número de elementos previsto inicialmente for ultrapassado o programa não terminará abruptamente com uma mensagem de erro. 2. O sobredimensionamento dos arrays é evitado, com a consequente optimização da gestão da memória disponível. Tem a desvantagem de o processamento de listas ser geralmente mais lento do que o de arrays. Dados dinâmicos utilizam os operadores new e delete para reservar e libertar espaço na memória Os procedimentos de manipulação de ponteiros, utilizados em, têm correspondentes em conforme é indicado na tabela 9. new dispose nil new delete NULL TABELA 9 Entidades utilizadas na manipulação de ponteiros. Seguidamente apresentam-se um programa em e outro em que preenchem uma lista de 5 elementos com números inteiros, lidos do teclado, e ecoam os valores lidos no ecrã (admitindo que eram lidos os valores 4, 1, 8, 4 e 7). A estrutura de dados criada é a que se mostra na figura 2: 4 1 8 4 7 FIGURA2: Estrutura de dados criada pelos programas abaixo descritos Na página seguinte estão escritos dois programas, um em e o outro em, que constroem a estrutura de dados acima referida. NULL () NIL () 24
program lista (input,output); type lista = ^elem; elem = record num : integer; prox : lista end; var lis, lis1 : lista; j : integer; begin new(lis); lis1:=lis; for j:=1 to 4 do begin readln(lis^.num); new(lis^.prox); lis:=lis^.prox end readln(lis^.num); lis^.prox:=nil; eco... while lis1<>nil do begin writeln(lis1^.num); lis1:=lis1^.prox end end. #include<iostream.h> #include<stdlib.h> typedef struct elem *lista; struct elem int num; lista prox; ; main() lista lis, lis1; int j; lis = new elem; lis1 = lis; for(j=1;j<=4;j++) cin >> lis->num; lis->prox = new elem; lis = lis->prox; cin >> lis->num; lis->prox = NULL; // eco... while (lis1!= NULL) cout << lis1->num << endl; lis1 = lis1->prox; LISTAGEM 20 Implementação de listas ligadas 25