Baixe o app para aproveitar ainda mais
Esta é uma pré-visualização de arquivo. Entre para ver o arquivo original
computação básica/Apostila_ICC_Filomeno_Final.pdf Página 1 de 75 UNIVERSIDADE DE BRASILIA APOSTILA DE INTRODUÇÃO A CIÊNCIA DA COMPUTAÇÃO Prof: Luís Filomeno Brasília, 17 de Julho de 2009 Página 2 de 75 Agradecimentos Gostaria de aproveitar a oportunidade para agradecer às seguintes pessoas e instituições pelo apoio ao longo do desenvolvimento deste trabalho. Gostaria de exprimir minha gratidão ao Prof. Jair Barbosa, meu colega de trabalho, enquanto trabalhador da UCB, meu sincero reconhecimento por sua competência e suas sugestões pertinentes. Igualmente, agradeço a todos os meus estudantes durante os anos que lecionei na Universidade Católica de Brasília - UCB pelas sugestões e interrogações que tornaram possível a concretização da presente apostila. Agradeço a Faculdade do Gama – Universidade de Brasília, por ter me concedido à oportunidade de desenvolver essa apostila. Página 3 de 75 SUMÁRIO 1 - INTRODUÇÃO............................................................................................................................1 1.1 COMPILADORES E INTERPRETADORES ...........................................................................3 1.2 ESTRUTURA BÁSICA DE UM PROGRAMA EM C ..............................................................3 1.3 A LINGUAGEM C E A SENSIBILIDADE AOS CARACTERES............................................4 1.4 ENTRADA E SAÍDA DE DADOS..............................................................................................4 1.5 — CONSTANTES E VARIÁVEIS .............................................................................................6 1.6 — TIPOS DE DADOS.................................................................................................................7 1.7 — PALAVRAS-CHAVES (RESERVADAS) –...........................................................................8 1.8 — COMENTÁRIOS ...................................................................................................................9 2 - OPERADORES DA LINGUAGEM C......................................................................................10 2.1 – OS OPERADORES ARITMÉTICOS ...................................................................................10 2.2 — OPERADOR DE ATRIBUIÇÃO.........................................................................................10 2.3 — OPERADORES RELACIONAIS ........................................................................................11 2.4 — FUNÇÕES GETCH( ), GETCHE( ), GETCHAR( ) E PUTCHAR( ) ................................12 2.5 — EXPRESSÕES ARITMÉTICAS..........................................................................................13 2.6 — CÓDIGOS DE BARRA, TABELA DE TIPOS DE DADOS , CONVERSÃO DE TIPOS E CASTS. ........................................................................................................................................14 3 — COMANDOS (ESTRUTURAS) DE CONTROLE DO PROGRAMA ..................................17 3.1 — ESTRUTURAS DE SELEÇÃO............................................................................................17 3.2 — A ESCADA IF-ELSE-IF ......................................................................................................19 3.3 — OPERADOR CONDICIONAL OU ALTERNATIVO (?) OU TERNÁRIO ......................19 3.4 — SELEÇÃO DE MÚLTIPLA ESCOLHA (SWITCH)..........................................................20 4 — COMANDOS DE CONTROLE DO PROGRAMA ...............................................................21 4.1 — O LAÇO FOR ......................................................................................................................21 4.2 — O LAÇO WHILE .................................................................................................................23 4.3 — O LAÇO DO-WHILE..........................................................................................................24 4.4 — COMANDOS DE DESVIO ..................................................................................................24 5 — FUNÇÕES ...............................................................................................................................27 Página 4 de 75 5.1 - INTRODUÇÃO.......................................................................................................................27 5.2 — PASSAGEM DE PARÊMTROS ( POR VALOR E POR REFERÊNCIA) ......................31 5.3 FUNÇÕES RECURSIVAS (OU DE RECORRÊNCIA) ..........................................................32 5.4 — CLASSES DE ARMAZENAMENTO..................................................................................33 6 – MATRIZES E STRINGS..........................................................................................................35 6.1 — MATRIZES UNIDIMENSIONAIS......................................................................................35 6.2 — MATRIZES COM DUAS OU MAIS DIMENSÕES ...........................................................37 6.3 — MATRIZES COMO ARGUMENTOS DE FUNÇÕES.......................................................38 6.4 — STRINGS E FUNÇÕES DE MANIPULAÇÃO...................................................................40 6.5 — INICIALIZAÇÃO DE MATRIZES.....................................................................................43 7 - PONTEIROS..............................................................................................................................45 8 – ESTRUTURAS DE DADOS HETEROGÊNEAS ....................................................................49 8.1 FORMA DE UMA ESTRUTURA EM C .................................................................................49 8.2 – ACESSO AOS MEMBROS DE UMA ESTRUTURA E ATRIBUIÇÃO DE ESTRUTURAS49 8.3 – MATRIZ DE ESTRUTURAS, ESTRUTURAS E ELEMENTOS (MEMBROS) DA ESTRUTURA COMO ARGUMENTOS DE FUNÇÃO. ..............................................................51 8.4 – CRIAÇÃO DE TIPOS DE DADOS. ......................................................................................52 8.5 – MATRIZES E ESTRUTURAS DENTRO DE ESTRUTURAS ............................................53 9 - ARMAZENAMENTO SECUNDÁRIO DE DADOS – ARQUIVOS.......................................54 9.1 – OPERAÇÕES FUNDAMENTAIS COM ARQUIVOS.........................................................55 9.2 DADOS NO FORMATO DE BLOCO OU REGISTRO .........................................................57 9.3 LEITURA E ESCRITA DE CARACTERE A CARACTERE.................................................59 9.4 LEITURA E ESCRITA DE CADEIA DE CARACTERES .....................................................59 9.5 LEITURA E ESCRITA DE DADOS DE MODO FORMATADO ..........................................60 10 - LISTAS DE EXERCÍCIOS .....................................................................................................62 11- BIBLIOGRAFIA ......................................................................................................................71 Página 1 de 75 1 - Introdução Este curso visa os ensinamentos dos fundamentos da linguagem de programção C, que nas ultimas décadas tem ganho muito usuários principalmente devido a sua versatilidade em ser uma linguagem híbrida. A linguagem “C” ou apenas C tem sido caracterizado como uma linguagem assembler de alto nível (nível mais alto [Pascal, Cobol, Basic, Fortran]; nível médio [C e C++, Forth]; nível mais baixo [Assembler]. Essa caracterização é devida ao fato de que o programador pode acessar os elementos internos do computador: bits, bytes e registradores que controlam a CPU e dispositivos externos. Desta maneira a linguagem de programação C caracteriza-se com uma linguagem de “nível alto” bem como uma linguagem de “nível baixo”. Deduz-se pela explanação anterior de que sistemas grandes e complexos podem ser construídos utilizando a linguagem C. Atualmente, em termos de aplicações em tempo real, a linguagem de programação C é a mais utilizada, desde a indústria passando pelas áreas de educação e pesquisa. Por isso a motivação na elaboração desta apostila e fornecer ao usuário um modo direto e simples de aprender a utilizar essa poderosa linguagem de programação. A linguagem C criada por Dennis Ritchie, na Bell Company na década de 70 do século passado, deriva da linguagem B, e inicialmente foi concebida para o sistema operacional UNIX. Atualmente versões da linguagem C são compatíveis com os diversos sistemas operacionais. Como referido anteriormente, o objetivo deste curso é de apresentar os conceitos fundamentais dessa linguagem de programação, que é rápida, impetuosa e eficiente. Para tal, pressupõe-se apenas familiaridade com os sistemas de computadores e conhecimento elementar de raciocínio lógico para acompanhamento e compreensão de alguns algoritmos implementados na linguagem C, apresentados nessa apostila. Dar-se-ão destaque as funções mais comuns e usuais dessa linguagem usados na maioria dos compiladores disponíveis. O enfoque será dado às instruções do C para o padrão ANSI. Ao leitor, recomenda-se fortemente a compreensão do compilador em que se esteja trabalhando, pois desta maneira poder-se-a entender as mensagens de avisos, mensagens de erros e possíveis bugs inerentes ao compilador, e/ou compilador-programa escrito pelo leitor. Reafirma-se que o domínio dessa linguagem não se restringe apenas aos conhecimentos de sua estrutural funcional, mas o conhecimento e compreensão do compilador em uso. Recomenda-se igualmente, que os programas exemplos mostrados sejam implementados, compilados e executados pelo usuário, visando a comprovação dos fundamentos apresentados. Página 2 de 75 Página 3 de 75 1.1 Compiladores e Interpretadores O processador inclui arquivos externos com fonte em C, se ele contiver diretivas #include. O compilador traduz a saída do pré-processador, em estágios, para um arquivo objeto – um arquivo OBJ – se não forem encontrados erros semânticos ou sintáticos. Se houver erros no código fonte C, o compilador apresenta uma lista deles com os respectivos números de linha em que ocorrem. O programador passa por um ciclo de edição/compilação até que todos os erros sintáticos e semânticos sejam eliminados. O ligador une o arquivo-objeto produzido pelo compilador aos arquivos apropriados da biblioteca e eventualmente a outros a outros arquivos-objetos. O arquivo resultante é um .EXE que pode ser executado pela máquina. 1 – Criar o programa 2 – Compilar o programa Compilando um programa em C 3 – Linkeditar o programa com as funções necessárias de bibliotecas. 1.2 Estrutura Básica de um Programa em C A unidade fundamental de programas em C são as funções, ou seja, em C qualquer programa é estruturado com base em funções. Um programa pode ter várias funções quantas o leitor quiser. A seguir apresenta-se a explicitação de uma função em C. Função básica: tipo nome ( ) { variável; void main(void) instrução_1; { . variável; . instrução; . } instrução_2; } Em todo o programa em C deve existir uma única função chamada main, que indica a inicialização da execução do programa. Geralmente essa função é do tipo void, mas alguns compiladores requerem uma declaração do tipo int. Página 4 de 75 O programa termina quando esteja encerrada a execução da função main. A função é atrás descrita é do tipo void que significa que ela não tem valor de um return, isto é, não retorna nada. Por outro lado, se a função for do tipo int, deverá ter no final do mesmo a palavra return seguido de um valor inteiro, ex: return (0). 1.3 A linguagem C e a sensibilidade aos caracteres Uma das particularidades da linguagem C é que a mesma é “sensível” aos caracteres maiúsculos e minúsculos. Existe diferença entre escrever alguma variável com letras maiúsculas e minúsculas, pois elas são diferentes embora possuam os mesmos caracteres ou letras. Para fins exemplificativos, declaramos uma variável de nome multa. Os exemplos a seguir de MULTA, mUlTa, MuLtA etc, serão interpretados pelo compilador com três variáveis diferentes embora sejam composta pelos mesmos caracteres. Em C os comandos ou também ditas instruções são sempre escritas com caracteres minúsculos, tais com auto, break, case, if, for, while etc. Sempre que uma destas ou outras palavras reservadas forem utilizadas com caracteres maiúsculos serão interpretados pelo compilador com variáveis em vez de palavras ou comando do C. 1.4 Entrada e saída de dados Em qualquer sistema de computação existe a interação do usuário e o computador para leitura e escrita de dados, que poderá ser feita através do teclado, drivers de entrada e de saída, arquivo e impressoras. Na linguagem C existem algumas funções para entrada e saída de dados que apresentaremos a seguir. 1.4.1 Função printf: Saída no Terminal Em programação é fundamental gravar saídas em arquivos e em terminais de saída ou monitor. Uma das mais úteis e versáteis funções de saída é printf. Esta função é implementada na biblioteca externa <stdio.h>. A função printf pode ser usada para dar saída a qualquer combinação de caracteres, inteiros, reais, cadeias, inteiros sem sinal, inteiros longos e inteiros longos sem sinal. Uma chamada típica de printf é a seguinte: printf (“\nA idade de João é %d. Sua renda é R$ %5.2f”, idade, renda) Presume-se que à variável inteira idade e à variável real renda tenham sido atribuídos valores. A combinação barra invertida \n – leva o cursor para a linha seguinte. A cadeia literal A idade de João é exibida a partir da nova linha. O %d é um marcador de lugar para uma variável inteira. O Página 5 de 75 valor da variável “idade” é usado em lugar do %d. O literal Sua renda é vem a seguir. O %5.2f é um marcador de lugar (caracter de conversão de formato) para um valor real, bem como uma instrução de formato para incluir apenas duas casas decimais. Mais finalmente, os marcadores de lugar são definidos da seguinte forma: % [largura] [.precisão] [indicadores] [I,L] excluir I, L – são usados para tipos inteiros longos largura – tamanho mínimo, em caracteres, do campo a exibir Exemplo: Número real de dez posições, dos quais inclui 5 números correspondem a parte inteira e 4 números a parte real, ou seja ficam após a virgula. %10.4f Tipo de Conversão Significado da Precisão inteiro quantidade de dígitos real dígitos após o ponto decimal cadeia quantidade de caracteres Caracter de Conversão Tipo de Objeto %c char (caractere) %s matriz de char (ou matriz de caracteres) %d int %o int (octal) %u int sem sinal %x int (hexadecimal) %ld long (decimal) %lo long (octal) %lu unsigned long %lx long (octal) %f float/double (ponto fixo) %e float/double (exponencial) %g float/double (o menor entre os formatos f ou e) %lf long float (ponto fixo) %le long float (exponencial) %lg long float (o menor entre os formatos f ou e) 1.4.2 Função scanf: Entrada a partir do teclado Página 6 de 75 A instrução scanf é uma das muitas funções de entrada (pelo teclado) presente nas bibliotecas externas (stdio.h). Para cada variável de entrada é necessário colocar um marcador de lugar. O sinal epersete, &, deve ser usado na frente de cada variável de entrada. Este símbolo significa “o endereço de”. Mais adiante quando apresentarmos o conceito de ponteiros, retornaremos a esse operador. scanf(“%d”, &palavra); Não é recomendável a especificação de números de digitos a serem lidos, pois isso restringe a leitura da variável e pode ocasionar erros. Se por ex: scanf(“%2d”, &valor); e fosse digitado 1987, o endereço guardaria apenas o digito 87 e perderia 19. No item 2.4 nos referiremos a outras funções de leitura de caracteres. 1.5 — Constantes e Variáveis Tal como nas demais linguagens de computação, em C também manuseiam-se dois tipos de dados: constantes e variáveis. Constantes – são valores fixos ou predefinidos e uma vez atribuídos, permanecem inalteráveis durante a execução de qualquer programa. As constantes podem ser numéricas, lógicas e literais. Eles são da forma decimal, hexadecimal, octal e caractere. Ex.: Constante caracter 82, 0x41, 0754, ‘A’ Ex. const int a=9; float alfa= 3.15; char beta=’C’ Variáveis - é um espaço de memória reservado para armazenar certo tipo de dado e tendo um nome para referenciar o seu conteúdo. Este espaço de memória ocupado por uma variável pode ser compartilhado por diferentes valores segundo certas circunstâncias. Uma declaração de variável consiste no nome de um tipo, seguido do nome da variável, seguida de ponto-e-vírgula. Ex: float x, y; int a; char letra; As variáveis devem ser declaradas antes de serem usadas no programa. Uma variável pode ser declarada em qualquer ponto do programa. Tipo de variável refere-se sempre ao tamanho de memória e à forma de armazenamento. Os tipos de variáveis quanto ao tamanho: Tipo Bit Bytes char 8 1 Página 7 de 75 int 16 2 float 32 4 double 64 8 void 0 0 De acordo com o uso das variáveis no programa elas podem ser de dois tipos: locais e globais. As locais são declaradas no interior de uma função, as globais são declaradas fora de uma função. Essas são visíveis (isto é, podem ser acessadas) em qualquer lugar do arquivo (módulo) na qual são definidas. /* Arquivo primeiro.c */ void main (void) { extern int a, b; printf (“\n a=%d b=%d\n”, a, b); float y= 3,14; printf (“\n y=%f \n”, y); } /* Arquivo segundo.c */ int chama int a=2; b=5; Neste caso a=2 e b=5, foram declaradas fora do programa principal, logo são externas, e funcionam como variáveis globais. Ao contrario, a variável y foi declarada no escopo do programa principal, assim, ela é vista como uma variável local. No estudo de funções, abordaremos com maior profundidade os conceitos de variáveis locais e globais. 1.6 — Tipos de Dados Um tipo é constituído por um conjunto de valores e um conjunto de operações que podem ser executadas com as variáveis de tal tipo. O C tem muitos tipos de dados predefinidos, os escalares mais simples (char, int, long, float, double e outros que apresentaremos mais tarde como (Ponteiros, Enumeração, Matriz, Estrutura, Página 8 de 75 União – tipos agregados –, Função e Vazio – que não pode assumir valores nem participar de operações.) Tipos escalares de informação processada pelo computador: • caractere (char): sequência contendo letras, números e símbolos especiais (caracteres alfanuméricos) – essa sequência deve ser indicada entre aspas (“ “) – Exemplo: “Gama - DF”, “356-9025”, “CEP-72140-000” – também chamado de string ou cadeia de caracteres. – Para um caractere indica-se (’’) • inteiro (int): toda informação numérica inteira (não fracionária) negativa, nula ou positiva. Exemplo: -50, 130, 0, 73 • real (float): toda informação numérica pertencente ao conjunto dos números reais (inteiros ou fracionários), (negativa, nula ou positiva). Exemplo: 12, 0, -3, 1.7, 101.5, 4.0 = símbolo de ponto • double (double): informação na forma de número reais mas com número de dígitos maiores. • Logico: conjunto de valores - falso ou verdadeiro – Esse tipo só apresenta um desses valores (excludentes) – também chamado de booleano. 1.7 — Palavras-Chaves (Reservadas) – ANSI Existem várias entidades americanas que definem padrões para a indústria e informática, tais como ANSI, ANSII, etc. Pela padronização ANSI existem algumas palavras chamadas reservadas ou chaves que não podem ser usadas como identificadores e nem como funções. O compilador é sensível as diferenças entre letras maiúsculas e minúsculas. As palavras apresentadas na tabela abaixo jamais poderão ser utilizadas a não ser para o propósito especifico definido em C. asm default for pascal switch auto do goto register typedef break double huge return union case else if short unsigned cdecl enum int signed void char extern interrupt sizeof volatile Página 9 de 75 const for long static while continue float mean struct 1.8 — Comentários Os comentários geralmente destinam-se a documentar e depurar o código. São usados de modo a ajudar no esclarecimento e compreensão do código. Textos delimitados pelo símbolo /* e */ são ignorados pelo compilador e representam a maneira mais geral de escreverem-se comentários em C. A grande maioria dos compiladores atuais permite que se utilize o mesmo compilador para escrever, compilar e executar tanto códigos escritos em C como em C++. Assim é usual encontrar-se programas escritos em C, mas que usam comentários do C++. Resumindo, os comentários abaixo costumam ser aceites em C. Ex: /* Isso é um comentário em C*/ // Isso é um comentário em C++, mas alguns compiladores o reconhecem. Página 10 de 75 2 - Operadores da Linguagem C Na linguagem de programação C contempla os seguintes tipos de operadores: aritméticos, de atribuição, aritméticos de atribuição, relacionais e lógicos. Nas próximas seções apresentam-se esses operadores. 2.1 – Os Operadores Aritméticos São os sinais de adição (+), subtração (–), divisão ( / ), multiplicação (*) e resto (%). Exceto o operador resto, que não está definido para variáveis de tipo real, todos os outros estão definidos para os tipos inteiros, caracteres e reais. Alguns destes operadores são unários e outros binários. Os unários agem sobre uma variável apenas, modificando ou não o seu valor, e retornam o valor final da variável. Os binários (ex. soma, multiplicação, divisão, subtração e resto da divisão) usam duas variáveis e retornam um terceiro valor, sem alterar as variáveis originais, desde que o resultado seja colocado numa terceira variável. Por exemplo, o produto (ou multiplicação) é um operador binário, pois usa duas variáveis, multiplica os valores dos operandos, sem alterar as variáveis, e fornece o valor final do produto numa outra variável. 2.2 — Operador de Atribuição O operador de atribuição (=), e consiste na atribuição na variável a esquerda do valor ou endereço da variável da direita. Ex.: j=2; y=x; a=b+c; 2.2.1 — Operadores de Atribuição Aritméticos São os operadores derivados da junção dos operadores aritméticos e de atribuição, ou seja, (+=, – =, *= e /=). Todos atribuem o resultado de uma expressão a uma variável. Se o tipo do lado esquerdo da atribuição não for o mesmo que o do lado direito, o tipo do lado direito será convertido para o tipo do lado esquerdo. Exemplos: a+=b a=a+b a–=b a=a–b a*=(b+c) a=a*(b+c) Página 11 de 75 a/=(b–c) a=a/(b–c) Os operadores pós-fixados e prefixados, ++ e ––, são usados para incrementar e decrementar uma variável. O significado de cada um deles é o seguinte: ++x – incrementa de um a variável x, antes de usar seu valor em uma expressão. x++ – incrementa de um a variável x, após usar seu valor em uma expressão. --x – decrementa de um a variável x, antes de usar seu valor em uma expressão. x-- – decrementa de um a variável x, após usar seu valor em uma expressão. Exemplos.: a=4; b=++a; o resultado é b=5 a=4; b=a++; o resultado é b=4 a=7; c=--a; o resultado é c=6 a=7; c=a--; o resultado é c=7 2.3 — Operadores Relacionais Os operadores relacionais são o teste de igualdade (==), teste de desigualdade(!=) menor que(<), menor do que ou igual(<=), maior que(>), maior do que ou igual (>=). Todos os operadores relacionais retornam um inteiro. O valor do inteiro é um (1) se a relação entre os operandos é verdadeira, e zero (0) se a relação entre os operandos for falsa. 2.3.1 — Operadores Lógicos O conceito de operador lógico provém da área dos circuitos eletrônicos, e seu conceito é similar ao príncipio de funcionamento das portas lógicas. Assim têm-se três (3) operadores lógicos que são: interseção, união e negação. A tabela-verdade correspondente a esses operadores mostra-se a abaixo: A B A e B A ou B negação A 0 0 0 0 1 0 1 0 1 1 1 0 0 1 0 1 1 1 1 0 Esses operadores quando utilizados na linguagem C adotam a seguinte representação: && - operador e (interseção) | | - operador ou (união) ! – negação. Página 12 de 75 Os operadores lógicos podem receber qualquer tipo de argumento numérico, incluindo caracter, e retornar um para verdadeiro, ou zero para falso. A avaliação de uma expressão envolvendo operadores lógicos procede da esquerda para a direita tão logo o resultado possa ser determinado. Ex.: i=4; j=4; if ((i>5) && (j==4)) o resultado desta operação é 0 (falso) pois 4 é menor que 5 (falso) e 4 igual a 4 (verdadeiro). Para ser verdadeiro as duas operações relacionais deveriam ser verdadeiras, o que não é. 2.3.2 — Operadores de caracteres Em C existe o operador de concatenação (+) que é aplicável apenas as variáveis do tipo caractere. Permite a concatenação de caracteres, cuja interpretação seria aglutinar ou juntar caractere(s) a outro(s). 2.4 — Funções getch( ), getche( ), getchar( ) e putchar( ) Além da função de leitura de dados scanf() que permite ler todos os tipos de dados escalares, tais como char, int, float, double, existem também algumas funções especificas para leitura e escrita apenas de um (1) caractere, tais funções pertencem a arquivo cabeçalho ou biblioteca <conio.h>. Eis o mnemônico destas funções: getch () – lê um caractere a partir do teclado mas não o mostra na tela; getche () – lê um caractere a partir do teclado e mostra-o na tela; getchar () – lê um caractere a partir do teclado mas não o mostra na tela; putchar () – escreve um caractere mostrando-o na tela; Ex: option = getch(); -> o caractere lido é atribuído a variável option; option = getche(); -> o caractere lido é atribuído a variável option e mostrado na tela; Ex: do { printf(“1: Entrada dados\n”); printf(“2: Altera dados\n”); printf(“3: Sair\n”); escolha=getch( ); Página 13 de 75 ch=getche( ); } while ((ch!=1) &&(ch!=2)&&(ch!=3)); 2.5 — Expressões aritméticas Operadores, constantes e variáveis são os elementos que constituem as expressões. Uma expressão em C é qualquer combinação válida desses elementos. Uma vez que a maioria das expressões tende a seguir as regras gerais da álgebra, elas são freqüentemente tomadas como certas. Nas expressões devem ser usadas apenas variáveis numéricas, operadores aritméticos e funções matemáticas pré- definidas, tais como, por exemplo: sin(x), sqrt(t), abs(x) etc. Ex: x= 3*5+4; y = x+6%2+3.28; z= y*y; Deve-se prestar atenção a procedência dos operadores, pois alguns operadores têm ordem de procedência maior que outros. Para evitar possíveis erros faça uso sempre de parênteses. Ex: 9+4*5 (9+4)*5 3+9/6 (3+9)/6 Se tentarmos estabelecer uma ordem de precedência geral aplicável a linguagem de programação C esta deverá ser a seguinte: 1) Funções e parêntesis 2) Expressões aritméticas +, - (unitários) *, /, % +, - (binários) 3) Comparações <, <=, == , >= , >, != 4) Não, inversora, ou negação 5) Conjunção ou e 6) Disjunção ou também dita ou. Página 14 de 75 2.6 — Códigos de barra, tabela de tipos de dados, conversão de tipos e casts. C suporta diversos códigos de barra invertida (listados na tabela abaixo) e de forma que você pode facilmente entrar esses caracteres especiais como constantes, e usá-los em conjunto com diretivas de impressão. Código Significado \b Retrocesso \f Alimentação de formulário \n Nova linha \r Retorno de carro \t Tabulação horizontal \” Aspas duplas \’ Aspas simples \0 Nulo \\ Barra invertida \v Tabulação vertical \a Alerta (beep) \N Constante octal \xN Constante hexadecimal 2.6.1- Conversões de tipos Em programação é comum existirem expressões em que variáveis de um tipo são misturadas com variáveis de outro tipo. Para compreender o principio de conversão na atribuição e nas expressões apresenta-se a seguir a tabela dos tipos definidos pelo padrão ANSI. Constam dessa tabela o tipo de dados e a quantidade de dígitos necessários para representá-los. Com isso torna-se mais fácil compreender as operações de conversão entre diferentes tipos. Tabela de todos os tipos de dados definidos pelo padrão ANSI Tipo Tamanho em Bits Tamanho em Bytes char, unsigned char, signed char 8 1 int, unsigned int, signed int, short int 16 2 long int, signed long int, float 32 4 double 64 8 2.6.1.1 Conversão de tipos em atribuições Em um comando de atribuição, a regra de conversão de tipos é muito simples: o valor do lado direito (o lado da expressão) de uma atribuição é convertido no tipo do lado esquerdo (a variável destino). Deve-se prestar atenção para atribuições de tipos em que a variável destino requer menos bits que a variável a direita da expressão. Neste caso, embora o processo de atribuição seja possa efetuado, Página 15 de 75 nem sempre se tem a certeza de que dados da variável a direita, foram armazenados na variável destino. Ex: int a=5; double z= 3,14196; a=z; Esse processo de atribuição, certamente não gerará mensagem de erro, mas conceitualmente está errado, pois como a quantidade de bits de um número double são 64 e de um número int são 16 bits. O compilador não entenderá se atribui os 16 bits mais significativos ou os 16 bits menos significativos. È perrogativa de o programador instruir o compilador a fazer a atribuição correta. Conversão de Tipos em Expressões Quando constantes e variáveis de tipos deficientes são misturadas em uma expressão elas são convertidas a um mesmo tipo. O compilador C converte todos os operandos no tipo de maior operando, o que é denominado promoção de tipo, como descrito nas regras de conversão de tipo abaixo. Se um operando é long double, então o 2º é convertido em long double, senão Se um operando é double, então o 2º é convertido em double, senão Se um operando é float, então o 2º é convertido em float, senão Se um operando é unsigned long, então o 2º é convertido em unsigned long, senão Se um operando é long, então o 2º é convertido em long, senão Se um operando é unsigned int, então o 2º é convertido em unsigned int, senão. resultado = (ch / i) + (f * d) - (f + i) int floatdouble int double double Página 16 de 75 2.6.1.2 Cast (modelador) Cast (ou modelador) aplicam-se as expressões. Visam forçar uma expressão a ser de um determinado tipo. A forma genérica de um cast é: (tipo) expressão; onde tipo é qualquer tipo de dados em C. Ex: No programa abaixo força-se a um resultado do tipo inteiro a ser um resultado real. #include <stdio.h> Void main(void) { int a =60 ; float b1, b2; b1=(float )a/13; b2=(float )a*2,0; printf(“os resultados são %f e %f\n”,b1,b2); } No programa acima anterior, se o uso da cast o programa faria a divisão de números inteiros entre 60 e 13, que resulta em 4 e converteria posteriormente para real, e daria 4,0. Com o uso da cast, a divisão entre 60 e 3, resulta no valor correto de 4,615. Na multiplicação, fará o produto entre os inteiros 60 e 2, e posterioremente fará a conversão para o número real 120,0. Página 17 de 75 3 — Comandos (Estruturas) de controle do programa Dos conceitos apresentados até o momento percebe-se que a execução de um programa em C ocorre de cima para baixo, ou seja, em ordem seqüencial de execução de instrução após instrução. Ocorre que as linguagens de programação modernas possuem comandos que permitem a execução de condições, saltos e/ou repetições, chamados de comandos de controle de fluxo ou de programa. Na linguagem de programação C os comandos de controle dividem-se em seqüências, de selecção (ou condicional) e de repetição (looping). O uso destes comandos depende da natureza do problema a ser sistematizado através de um programa. Em algumas situações usam-se apenas alguns comando, em programas mais complexos e normal o uso de todos os comandos de controle. 3.1 — Estruturas de Seleção Do ponto de vista conceitual, estas estruturas aplicam-se quando dependendo de uma avaliação condicional, realizada mediante lógica convencional na forma de expressões lógicas, como conseqüência, uma instrução ou um conjunto de instruções selecionadas deve ser executado. A Linguagem de Computação C suporta dois tipos de comandos de seleção: if e switch. Além disso, o operador ? (chamado operador ternário) é uma alternativa ao if em certas circunstâncias. 3.1.1 — if Essa estrutura de seleção pode ser implementada na forma simples, composta ou incadeada. Linguagem de Computação C suporta dois tipos de comandos de seleção: if e switch. Além disso, o operador ? (chamado operador ternário) é uma alternativa ao if em certas circunstâncias. A forma da sentença if é no modo simples: if(expressão) comando; A forma geral da sentença if ou forma composta é: if(expressão) comando; else comando; onde o comando pode ser uma única instrução ou um bloco com várias instruções ou nada (no caso de comandos vazios). Página 18 de 75 Se a expressão é verdadeira (diferente de zero), o comando ou bloco que forma o corpo do if é executado; caso contrário; o comando ou bloco que é o corpo do else (se existir) é executado. Lembre-se que o código associado ao if ou código do else será executado, nunca ambos. O comando condicional controlando o if deve produzir um resultado escalar. Um escalar é um inteiro, um caractere, é raro usar um número ponto flutuante (real) para controlar uma expressão condicional. 3.1.2 — IF’s Aninhados Um if aninhado é um comando if que é o objeto de outro if ou else. If’s aninhados são muito comuns em programação. Em C, um comando else sempre se refere ao comando if mais próximo, que está dentro do mesmo bloco do else e não está associado a outro if. Ex.: int a=4, b=3, c=4; if (a<2) { if (b==5) comando 1; if (c>8) comando 2; /* este if */ else comando 3; /* está associado a este else */ } else comando 4; /* associado ao if (a<2) */ No exemplo anterior, quando efetua-se o primeiro teste, como a=4 e 4 não é menor que 2, então nesse caso executa-se o comando 4, que corresponde a condição falsa do teste efetuado com (a<2). O padrão C ANSI especifica que pelo menos 15 níveis de aninhamento devem ser suportados. Ex.: #include<stdio.h> void main(void) { int i=4, j=6, k = 8; if (i<k) { if (i>j) printf(“Instrução1\n”); else printf(“Instrução2\n”); } } O programa anterior está correto e imprime “Instrução 2”. Página 19 de 75 3.2 — A escada if-else-if A sua forma geral é dada por: if (expressão) comando; else if (expressão) comando; . . . else comando; As condições são avaliadas de cima para baixo. Assim que uma condição verdadeira é encontrada, o comando associado é executado e desvia do resto da escada. Se nenhuma das condições for verdadeira então o último else é executado. Se o último else não está presente, nenhuma ação ocorre se todas as condições são falsas. 3.3 — Operador Condicional ou Alternativo (?) ou Ternário O operador condicional (?) pode ser usado para substituir comandos if-else na forma geral. if (condição) expressão; else expressão; Os corpos do if e else devem ser uma expressão simples, nunca um outro comando de C. O operador ? é chamado de operador ternário, porque requer três operandos, e tem a seguinte forma geral: expressão1?expressão2:expressão3 A interpretção da forma anterior é a seguinte: o valor de uma expressão ? é determinada como segue: expressão1 é avaliada, se for verdadeira, expressão 2 será avaliada e se tornará o valor da expressão ? inteira. Se expressão1 é falsa, então expressão 3 é avaliada e se torna o valor da expressão. Ex.: #include <stdio.h> void main(void) { int i=6, j=4; int resultado=(i<j)? i : j; printf (“%dn”, resultado); x=15 z=(x<30)?40:60; similar a: x=15 if (x<30) z=40 else z=60 #include <stdio.h> void main(void) { int i=6, quadrado; quadrado=i>0?i*i:-(i*i) printf(“%d ao quadrado é %d”, i, quadrado); } Página 20 de 75 } 3.4 — Seleção de múltipla escolha (switch) Utiliza-se essa estrutura de seleção quando uma determinada variável pode ser igual a diferentes valores que se deseja avaliar. switch (<valor>) { case v1 comandos 1; break; case v2, v3 comandos 2; break; case v4 comandos 3; break; default: comandos 4; } Para muita gente os comandos break são opcionais, dentro do switch. Eles terminam a seqüência de comandos associados com cada constante. Se o comando break é omitido, a execução continua pelos próximos comandos case até que um break, ou fim do switch, seja encontrado. O teste condicional da instrução de múltipla escolha não usa os operadores relacionais, pois só realiza a operação de igualdade, como se mostra no exemplo abaixo: Ex.: void main(void) { char ch; printf (“1. Soma \n”); printf (“2. Multiplicação\n”); printf (“3. Divisão\n”); printf (“4. Subtracão\n”); printf (“Pressione Qualquer Outra Tecla para Abandonar\n”); printf (“ Entre com sua Escolha ”); ch=getchar( ); /* Lê do Teclado a Seleção */ switch(ch) { case ‘1’: Soma( ); break; case ‘2’: Multiplica( ); break; case ‘3’: Divisao( ); break; case ‘4’: Subtracao( ); Página 21 de 75 break; default: printf(“Nenhum Opção Selecionada”); } } 4 — Comandos de controle do programa Em todas as linguagens modernas de programação, comandos de iteração (também chamados de laços) permitem que um conjunto de instruções seja executado até que ocorra uma certa condição. Essa condição pode ser predefinida (como no laço for) ou com final em aberto (como nos laços while e do-while). 4.1 — O Laço FOR Este comando é encontrado em todas as linguagens de programação, contudo em C, ele fornece a flexibilidade e capacidades surpreendentes. A forma geral do comando for é: for(inicialização; condição; incremento) comando; O laço for permite várias variações. A inicialização é geralmente um comando de atribuição que é usado para colocar um valor na variável de controle do laço. A condição é uma expressão relacional que determina quando o laço acaba. O incremento define como a variável de controle do laço varia cada vez que o laço é repetido. Uma vez que a condição se torne falsa, a execução do programa continua no comando seguinte ao for. Ex.: Este programa imprime os números de 1 a 50 na tela: #include <stdio.h> void main(void) { int x; for(x=1;x<=50;x++) printf(“%d”,x); } No programa x é inicialmente 1, uma vez que x é menor do que 50, printf( ) é executado e x é incrementado em 1 e testado para ver se ainda é menor ou igual a 50. Este processo se repete até que x fique maior que 50, nesse caso, o laço (ciclo) termina. Página 22 de 75 Ex.: Laço for com múltiplos comandos: #include <stdio.h> void main(void) { int x; float z; for(x=100;x!=65;x-=5) { z=x*x; printf(“O quadrado de %d é %f”,x,z); } } Neste caso a multiplicação de x por si mesmo, como a função printf( ) são executados até que x seja igual a 65. Repare que o laço é executado de forma inversa: x é inicializado com 100 e será subtraído de 5 cada vez que o laço se repetir. Nos ciclos de repetição for o teste condicional sempre é executado no topo do laço. Isso significa que o código dentro do laço pode não ser executado se todas as condições forem falsas logo no início, atente ao exemplo a seguir. Ex.: x=10; for (y=10;y!=x;++y) printf(“%d”,y); printf(“%d”, y); /* Este é o único comando printf( ) que será executado */ Como x e y são iguais desde a sua entrada. o laço nunca executa. Já a expressão condicional é avaliada como falsa, nem o corpo do laço nem a porção do incremento são executados. 4.1.1 — Variações do laço FOR Uma das variações mais comum usa o operador vírgula para permitir que duas ou mais variáveis controlem o laço. Lembra que o operador vírgula é usado para encadear expressões numa configuração “faça isso e isso”. Ex.: #include <stdio.h> /* Este programa não está completo, logo não funciona */ void main(void) { int x, z, y, w; for(x=0,y=0;x+y<10;++x,y++) { z=y+x; w= y-x; . . Página 23 de 75 . } } 4.1.2 — O Laço infinito Já que nenhuma das três expressões que formam o laço for é obrigatória, você pode fazer um laço sem fim, deixando a expressão condicional vazia, como aqui: for(; ;) printf(“Esse comando será executado para sempre.\n”); 4.2 — O Laço WHILE O segundo laço disponível em C é o laço while. A sua forma geral é: while(condição) comando; Onde comando é um comando vazio, um comando simples ou um bloco de comandos. A condição pode ser qualquer expressão, é verdadeiro é qualquer valor diferente de zero. O laço se repete quando a condição for verdadeira. Quando a condição for falsa, o controle do programa passa para a linha após o código do laço. Ex.: O programa abaixo mostra um controle pelo teclado, que simplesmente se repete até que o usuário digite A ou a: void main(void) { char ch; ch=’\o’; /* Inicializa */ while((ch!=’A’)&&(ch!=’a’)) { ch=getchar( ); printf(“%c”, ch); } } Inicialmente, ch é inicializado com nulo. O laço while verifica se o ch não é igual a A ou a. Como ch foi inicializado como nulo, o teste é verdadeiro e o laço começa. Uma vez digitado A ou a, a condição se torna falsa, porque ch fica igual a A ou a e o laço termina. Os laços while verificam a condição de teste no início do laço, o que significa que o código do laço pode ser executado. Isso elimina a necessidade de se efetuar um teste condicional antes do laço. O controle pode ser feito pelo usuário ou por intermédio de uma variável contadora. Página 24 de 75 4.3 — O Laço DO-WHILE Este laço difere dos laços for e while, pois nestes a condição do laço é testada no inicio do laço, enquanto que no laço do-while o teste condicional de verificação é efetuado no final do laço. Isso significa que o laço sempre será executado ao menos uma vez. A forma geral do laço do-while é: do { comando; } while (condição); Embora as chaves não sejam necessárias quando apenas um comando está presente, elas são geralmente usadas para evitar confusão para usuário com o while. O laço do-while repete até que a condição se torne falsa. Este tipo de ciclo de repetição é geralmente utilizado para validação de variáveis, ou seja, usa-se repetitivamente até que as variáveis recebam apenas os valores aceitáveis. Ex: Código de validação de variáveis inteiras positivas. int x; do { printf(“Digite o valor de x: x >=0\n”); scanf(“%d”, &x); } while (x<0); Ex.: O seguinte programa (laço do-while) lerá números do teclado até que se encontre um número menor ou igual a 100. #include <stdio.h> void main(void) { int num; do { scanf(“%d”, &num); } while (num>100); } 4.4 — Comandos de Desvio Na linguagem C existem quatro comandos que realizam um desvio incondicional: return, goto, break e continue. Esses comandos são apresentados nas subseções abaixo. Página 25 de 75 4.4.1 — O Comando RETURN O comando return é usado para retornar de uma função. Ele é um comando de desvio porque faz com que a execução retorne (salte de volta) ao ponto em que a chamada da função foi feita. Se return tem um valor associado a ele, esse valor é o retorno da função. Se nenhum valor for especificado para retorno, assume-se apenas lixo é retornado (alguns compiladores em C irão automaticamente retornar se nenhum valor foi especificado, mas não conte com isso!). A forma geral do comando return é: return expressão; Lembre-se de que a expressão é opcional. Entretanto, se estiver presente, ela se tornará o valor da função. Você pode usar quantos comandos return quiser dentro de uma função, contudo a função cessará de executar tão logo ela encontre o primeiro return. A chave (}) que finaliza uma função faz com que a função retorne. É o mesmo que return sem nenhum valor especificado. 4.4.2 — O Comando GOTO Não existe nenhuma situação na programação que necessite do goto. Contudo goto é uma conveniência que, se usada prudentemente pode ser vantagem em certas situações na programação. O comando goto requer um rótulo para sua operação (um rótulo é um identificador válido em C seguido por dois pontos). O rótulo tem de estar na mesma função do goto que o usa – você não pode efetuar desvios entre funções. A forma geral do goto é: goto rótulo; . . . rótulo: Onde rótulo é qualquer rótulo válido existente antes ou depois do goto. Ex.: x=1; loop1: x++ if(x<100) goto loop1; Página 26 de 75 Contudo, não é comum o uso do comando goto para saltos. È conveniente usar técnicas de computação que substituam o comando goto. É recomendável o uso do goto sempre que se pretende escrever algo na tela, para o melhor posicionamento de textos na tela do computador. Ex: gotoxy (5,5) printf(“Inicio da escrita\n”). No exemplo acima se instrui o compilador a se posicionar na posição x=5 e y=5 da tela do monitor e nessa posição escrever “Início da escrita”. 4.4.3 — O Comando BREAK O comando break tem dois usos. Pode ser usado para terminar um case em um comando switch (já apresentado anteriormente). O leitor pode usá-lo, também, para interromper (frear) de modo imediato a execução de um laço, evitando o teste condicional normal do laço. Um comando break provoca uma saída apenas do laço mais interno, e o controle do programa retorna ao comando seguinte ao laço. Ex.: #include <stdio.h> void main(void) {int t; for(t=0;t<100;t++) { printf(“%d”, t); if(t==10) break; } } No exemplo anterior são escritos os números de 1 até 10 na tela. Então, o laço termina porque o break provoca uma saída imediata do laço, desrespeitando o teste condicional t<100. 4.4.4 — O Comando CONTINUE Este comando é utilizado para forçar que ocorra a próxima iteração do laço, pulando qualquer código. Para o laço for, continue faz com que o teste condicional e a porção de incremento do laço sejam executados. Para os laços while e do-while, o controle passa para o teste condicional. Ex: Neste caso o uso do comando continue é para apressar a saída do laço. void main (void) { char alfa, ch; alfa =0; while (!alfa) { ch = getchar( ); if(ch==’$’) { alfa =1; Página 27 de 75 continue; } else putchar(ch+1); /* Desloca o alfabeto uma posição */ } } O programa apresentado antes codifica uma mensagem desloca todos os caracteres uma letra acima ou na frente. Por exemplo, se for digitado um b se tornaria um c. A função termina quando um $ for digitado. 5 — FUNÇÕES 5.1 - Introdução Antes de iniciarmos a abordagem de funções na linguagem de programação C, faremos uma breve resenha dos objectivos do uso de funções nas linguagens de programação. Em geral se tem problemas complexos, cuja solução requer igualmente algoritmos complexos. No entanto, é prática elegante e eficiente em computação dividir algum problema complexo em conjuntos de problemas mais simples de se resolver. A isso se chama modularização, ou seja, repartição dos problemas grandes e complexos em módulos menores e simples, de tal modo, que a aglutinação destas soluções simples resultam na solução do problema complexo. O uso de funções aparece na literatura com vários outros nomes, tais como sub-programas, procedimentos etc. Independentemente da nomenclatura, toda a função tem um propósito bem definido, permite o reaproveitamento do seu código, pode ser executado em diversas situações no mesmo programa e/ou em programas diferentes; As funções são uma das características mais importantes da linguagem de programação C e o local onde ocorre toda atividade de um programa. Toda a linguagem de programação C é estrutura em termos de funções. Assim, nas próximas secções estudaremos declaração de funções, os argumentos das funções e tipos de funções. 5.1.1 — A forma geral de uma função Uma função é iniciada pela palavra reservada funcao, seguida de um especificador_de_tipo referente ao tipo de retorno da função, do nome da função e de uma lista de parâmetros. É da seguinte estrutura: especificador_de_tipo nome_da_função(lista de parâmetros) { Página 28 de 75 corpo da função } O especificador_de_tipo especifica o tipo de valor que o comando return da função devolve para o programa e pode ser de qualquer tipo válido (ex.: int, float, double, string, etc.). Se nada for especificado, o compilador assume que a função devolve um resultado inteiro. A lista de parâmetros é uma lista de nomes de variáveis seguidas por vírgulas e seus tipos associados que recebem os valores dos argumentos quando a função é chamada. A lista de parâmetros consiste nos valores que o programa principal possui, e que são necessários a função, para obtenção dos dados desejados no programa principal. Uma função pode não ter parâmetros, neste caso a lista de parâmetros é vazia, mas mesmo assim os parênteses são necessários. Para explicitar claramente, que a função não receberá jamais algum parâmetro ela deverá ter na lista de parâmetros a palavra void. A declaração das variáveis é similar ao visto no início do curso. Na especificação de parâmetros da função devem incluir o tipo e o nome da variável. Na sua forma geral é: função(tipo nomevar1, tipo nomevar2,...) Variáveis Locais – só podem ser referenciadas por comandos que estão dentro do bloco no qual as variáveis foram declaradas. Uma variável local é criada na entrada de seu bloco e destruída na saída. Elas são definidas internamente a uma função. As variáveis locais não podem manter seus valores entre chamadas a funções, exceto quando a variável é declarada de tipo de cruzamento static, isso faz com que o compilador a trate como uma variável global a fins de armazenamento, mas limita o seu escopo para dentro da função. 5.1.2 — Argumentos de Funções Se uma função usa argumentos, ela deve declarar variáveis que aceitem os valores dos argumentos, essas variáveis são chamadas de parâmetros formais da função. Ex: /* Calcula a média de um estudante */ float (float nota1, float nota2, float nota3) { float media; media=(nota1+nota2+nota3)/3; return media; } Página 29 de 75 Devemos assegurar sempre que os argumentos usados para chamar a função sejam compatíveis com o tipo de seus parâmetros. Se os tipos forem incompatíveis, alguns compiladores geram mensagem de erro, em outros ocorrem resultados inesperados. 5.1.3 — O comando Return Provoca uma saída imediata da função que o contém, isto é, faz com que a execução do programa retorne ao código chamador. A palavra reservada return identifica qual valor será retornado ao programa acionador da função. Se o comando return não fizer parte de uma função, nesse caso as funções usam o modo default para terminar a execução, exceto se a função for do tipo void. 5.1.4 — Funções que retornam valores inteiros e não-inteiros Todas as funções, exceto as do tipo void, devolvem um valor, e esse valor é especificado pelo comando return. Se nenhum comando return estiver presente, o retorno da função será indefinido. Sempre que o tipo da função não é especificado o compilador atribui a ela o tipo padrão int. Mas quando é necessário um tipo de dados diferentes o processo dois passos: 1º) Deve ser dada a função um especificador de tipo explícito; 2º) O tipo da função deve ser identificado antes da primeira chamada feita a ela. Por isso, usa-se a declaração da função antes do programa principal e depois a explicitação do que a função faz após o programa principal. Deste modo o C pode gerar um código correto para funções que não devolvem valores inteiros. Para evitar que o compilador não devolva um valor inteiro, devemos utilizar uma forma especial de declaração, perto do início do programa, informando o compilador que tipo de dado sua função realmente devolverá, ou seja, a declaração da função. Protótipo de Função: permite os tipos de argumentos das funções sejam declarados, e é um dos acréscimos mais importantes do ANSI ao C. A forma geral de uma definição de protótipo de função é: tipo nome_func(tipo nome_var1,..., tipo nome_varN); O uso dos nomes dos parâmetros é opcional, mas é bom incluí-los, pois eles habilitam o compilador a identificar qualquer incompatibilidade de tipos por meio do nome quando ocorre um erro. Sempre existe a questão: “Se deve atribuir esse valor a alguma variável já que um valor está sendo devolvido? Não! Se não houver nenhuma atribuição específica o retorno é descartado. Ex.: Considere o programa abaixo, que usa as funções soma( ) e divisão(). #include <stdio.h> int soma(int a, int b); /* Protótipo da função soma( ) */ Página 30 de 75 float divisao( int b, int a) /* Protótipo da função divisao( ) */ void main(void) { int a, b, c; a=2; b=3; c=soma(a,b); /* atribui o valor de soma( ) a c */ printf(“%f”, divisao(b,a)); /* o valor de retorno não é atribuído, mas usado por printf */ soma(a,b); /* o valor é perdido porque não é atribuído */ } /* ou utilizado como parte de uma expressão */ /* Explicitação da função soma */ int soma(int, a, int b) { return(a+b); } /* Explicitação da função divisão */ float divisao(int, b, int a) { return(b/a); } Todas as funções que não devolvem (retornam) valores devem ser declaradas como retornando o tipo void. Ao declarar uma função como void, você a protege de ser usada em uma expressão, evitando uma utilização acidental. Ex.: float calculo(void) Diz que a função “calculo ” não tem parâmetros, isto é, quando acionado pelo programa principal não recebe nada dele, apenas o seu acionamento, mas devolve (retorna) um float. Isso informa ao compilador que a função não tem parâmetros e qualquer chamada à função com parâmetros é um erro. Ex.: Uso de parâmetros de função de modo incompatível originando erro. /* Este programa usa um protótipo de função para forçar uma verificação de tipos */ void quadrado(float *i); /* protótipo */ void main(void) { int x; x=10; sqr_it(x); /* incompatibilidade de tipos */ } void quadrado(int *i) { *i= *i * *i; } NOTA: Quando um caractere é devolvido por uma função declarada como o tipo int o valor caractere é convertido em um inteiro. Visto que o C faz a conversão de caractere para inteiro, e Página 31 de 75 vice-versa, uma função que devolve um caractere geralmente é declarada como devolvendo caractere, mas pode ser declarada como do tipo int. 5.2 — Passagem de parâmetros (por valor e por referência) A criação de funções nem sempre significa que o desenvolvimento do programa principal requererá menos esforço do usuário programador. Por exemplo, se o pedissem para elabora um programa que calculasse a média da nota de física de 1000 alunos numa prova de vestibular. O que foi apresentado até ao momento a lista de parâmetros teria 1000 variáveis notas. O que torna essa enumeração exaustiva, assim deve existir uma maneira de reaproveitar a função com ligeiras modificações ou adaptações. Por esse motivo, criou-se o conceito de passagem de parâmetros, que representa uma maneira de como o programa principal deve fornecer as informações necessárias às funções. A passagem de argumentos para funções é feita de duas maneiras: por valor e por referência. Chamada ou passagem por valor – o programa principal fornece uma copia do valor de um argumento para a função, de modo que alterações feitas nas funções não têm nenhum efeito nas variáveis usadas para chamá-las. Com a finalização da execução da função o valor do parâmetro passado por valor é destruído. Chamada ou passagem por referência – o endereço de um argumento é copiado no parâmetro. Dentro da função, o endereço é usado para acessar o argumento real utilizado na chamada, significa que alterações feitas no parâmetro afetam a variável usada para chamar a rotina. Ex. #include<stdio.h> #include<stdio.h> int sqr(int x); int sqr(int *t) void main(void) void main(void) { { int t=5; int t=5; printf(“%d %d”, sqr(t), t); printf(“%d %d”,sqrt(t),t); } } int sqr(int x) int sqr(int *t) { { x=x*x; *t=(*t)*(*t); return(x); return(*t); } } Lembramos, que o argumento para sqr( ), 5 é copiado no parâmetro x. Quando a atribuição x=x*x ocorre, apenas a variável local x é modificada. A variável t, usada para chamar sqr( ), ainda tem o Página 32 de 75 valor 5. Assim, a saída é 25 5. Salienta-se que é uma cópia do valor do argumento que é passada para a função. No segundo exemplo, o argumento para sqr( ), 5 é passado como parâmetro. Quando a atribuição *t=(*t)*(*t), a variável t é modificada. A variável t, usada para chamar sqr( ), não vale mais 5. A saída nesse caso é 25 25. 5.3 Funções recursivas (ou de recorrência) É comum em C, funções chamarem a si mesmas. A função é recursiva se um comando no corpo da função a chama. Recursão é o processo de definir algo em termos de si mesmo, e algumas vezes é chamado de processo circular. O uso de uma função recursiva objetiva uma seqüência de chamadas até que um certo ponto seja atingido. Analisemos a título de exemplo a função recursiva factorial( ) que obviamente calcula o fatorial de um número inteiro, isto é, o produto de todos os números inteiros entre n e 1. Tanto factorial( ) como sua equivalente iterativas são apresentadas abaixo: Ex.: factorial(int n) /* recursiva */ { int resposta; if (n==1) return(1); resposta=factorial(n-1)*n; /* chamada recursiva */ return(resposta); } fact(int n) /* não-recursiva */ { int t, resposta; resposta=1; for(t=1; t<=n; t++) resposta=resposta*(t); return(resposta); } A versão não recursiva de fact( ) deve ser clara. Ela usa um laço que é executado de 1 a n e multiplica consecutivamente cada número pelo produto móvel. A operação da função recursiva factorial( ) é mais complexa. Quando factorial( ) é chamada como argumento de 1, a função devolve 1, senão ela devolve o produto de factorial (n-1)*n. Para avaliar essa expressão, factorial( ) é chamada com n-1. Acontecerá isso até que n iguale a 1 e as chamadas à função comecem a retornar. Página 33 de 75 Se colocássemos o fatorial de 2, a primeira chamada a factorial( ) provoca uma segunda chamada com argumento (n = 2-1 = 1). Essa chamada retorna 1 que é, então, multiplicado por 2 (o valor original de n). O resultado, isto é, a resposta é 2. Quando uma função chama a si mesma, novos parâmetros e variáveis locais são alocadas na pilha e o código da função é executado com essas novas variáveis. Uma chamada recursiva não faz uma nova cópia da função, apenas os argumentos são novos. A principal vantagem das funções recursivas é que você pode usá-las para criar versões mais claras e simples de vários algoritmos. 5.4 — Classes de armazenamento Na linguagem C existem quatro tipos de especificadores de classe de armazenamento, que são auto, extern, static e register. Os especificadores atrás mencionados informam ao compilador como a variável deve ser armazenada. a) Variável auto Especificador auto: serve para definir variáveis automáticas que é uma referência para as variáveis locais. Na linguagem de programação C as variáveis são, por definição, locais ao bloco em que ela é declarada o uso de auto não tem nenhum significado, sendo portanto raramente utilizado. b) Variável extern O especificador de armazenamento precede a declaração da variável: especificador_de_armazenamento tipo nome_da_variável O especificador extern diz ao compilador que os tipos e nomes de variável que o seguem foram declarados em outro lugar, isto é, elas são variáveis globais sem realmente criar armazenamento para elas novamente. As variáveis globais devem ser declaradas em apenas um arquivo (.c, .cpp, .h, .hpp) e usadas como extern nos outros. Ex. float x,y; /* declaração global de x e y */ void main(void) { extern float y; /* uso opcional da declaração extern */ . . . } Página 34 de 75 Embora as declarações de variáveis extern possam ocorrer dentro do mesmo arquivo da declaração global, elas não são necessárias. Sempre que o compilador C encontra uma variável que não foi declarada, ele verificar se ela tem o mesmo nome de alguma variável global. Se tiver, o compilador assumirá que a variável global está sendo referenciada. c) Variável static Dentro de sua própria função ou arquivo, variáveis static são variáveis permanentes (estáticas), elas são reconhecidas fora de sua função ou arquivo (.cpp, .c, .h ou .hpp) , mas mantêm seus valores entre chamadas. O especificador static possibilita a definição de variáveis deste tipo na forma: static tipo_da_variável nome_da_variável; As variáveis estáticas têm comportamentos diferentes para variáveis locais e globais. Variáveis Locais Static – uma variável local que retém o seu valor entre chamadas de função ou bloco de código em que elas são declaradas. Variáveis Globais Static – aplicar o especificador static a uma variável global informa ao compilador para criar uma variável global que é reconhecida apenas no arquivo no qual a mesma foi declarada. Embora variável global, rotina(s) em outro(s) arquivo(s) não pode(m) reconhecê-la nem alterar o seu conteúdo diretamente. Esse tipo de variáveis são muito usadas em bibliotecas de funções. Ex. series(void) /* Series( ) pode produzir um membro da serie, baseado no numero precedente */ { static int serie_num=100; /* serie_num é gerador de série de números */ serie_num=serie_num+23; /* Esse número é atribuído uma só vez,*/ /* na próxima série o valor começa com 123. */ return(serie_num); } d) Variável register (registrador) Esse tipo de variável quando representando variáveis caracteres ou interios são colocados nos registradores da CPU (UCP). Já variáveis de tamanho maior como é o caso de matrizes, essas não podem ser colocadas em registradores da CPU (UCP). Assim, Como uma variável do tipo register pode ser armazenada em um registrador da CPU (UCP), ela não tem endereço, por conseqüência nunca deve ser utilizado o operador & para estas variáveis. O número de variáveis do tipo register (registrador) num programa é determinado pelo Sistema Operacional e específicidade de cada compilador. Existe sempre um limite no uso de varáveis Página 35 de 75 register, assim que esse limite seja atingido, as restantes variáveis do tipo register são transformadas em variáveis comuns de modo automático pelo compilador. Ex.: int main(void){ register int i; for (i =0; i<1000;i++){ ........} } 6 – Matrizes e strings Neste capítulo se aborda as matrizes de dados homogêneas, ou seja, todos os seus elementos são do mesmo tipo. Mais adiante, veremos que em C existem as matrizes de dados heterogêneas que pressupões a representação de vários tiopos de dados numa única matriz. Uma matriz homogênea ou um vetor homogêneo é uma coleção de variáveis do mesmo tipo que é referenciada por um nome comum. Um elemento específico da matriz é acessado por intermédio de um índice. Cada posição de armazenamento é chamada de elemento da matriz (ou do vetor). O endereço de índice mais baixo corresponde ao primeiro elemento e o mais alto o último elemento. As matrizes podem ter uma a várias dimensões. A matriz mais comum em C é a de string, que é uma matriz de caracteres terminada por um nulo. 6.1 — Matrizes Unidimensionais As expressões matriz e vetor são sinônimos para alguns casos, mas é bom deixar claro que elas são diferentes em termos conceituais. Assim, por vetor sempre se referencia uma matriz unidimensional, enquanto que matriz referencia sempre a matriz com uma ou mais dimensões, sendo identificada como matriz multidimensional. A forma geral para declarar uma matriz unidimensional é: tipo nome_var [dimensão] tipo declara o tipo de base da matriz, que é o tipo de cada elemento da matriz dimensão define quantos elementos a matriz irá guardar. Ex.: Para declarar uma matriz de 20 elementos, de nome calculo e do tipo float, declare como: Página 36 de 75 float calculo [20]; Em C, toda matriz tem 0 (zero) como índice do seu primeiro elemento. Logo quando escrevemos: char a[20]; Matrizes unidimensionais são, essencialmente, listas de informações do mesmo tipo, que são armazenadas em posições contíguas de memória em uma ordem de índice. O esquema abaixo mostra como uma matriz “ch” apareceria na memória se ela começasse na posição 40 da memória e fosse declarada como mostramos abaixo: char ch[5]; Elemento ch[0] ch[1] ch[2] ch[3] ch[4] Endereço 40 41 42 43 44 No acesso a cada elemento de uma matriz deve-se percorrer todo matriz, ou seja, todos os seus elementos. Particular atenção deve ser prestada a primeira posição de um elemento é a posição zero (0), ou seja, uma matriz é iniciada no elemento zero. Que difere da álgebra linear em que o primeiro elemento da matriz encontra-se na posição um (1). Estamos declarando uma matriz de caracteres com 20 elementos, a[0] até a[19]. Por exemplo, o seguinte programa carrega uma matriz inteira com números de 0 a 49. #include<stdio.h> void main(void) { int z[50]; /* Isto reserva 50 elementos inteiros */ int x; for(x=0; x<50;x++) { z[x]=x; printf(“z[%d]=%d\n”, x, z[x]); } } C não tem verificação de limites em matrizes. Você poderia ultrapassar o fim de uma matriz e escrever os dados de alguma outra variável ou mesmo no código do programa. Cabe ao programador, verificar os limites até onde necessário. A título de exemplo temos o programa abaixo, onde o código compila sem erros, mas é incorreto, porque o laço for fará com que a matriz modulo, ultrapasse seus limites, e escreverá para dos dados sobre outros espaços de memória já alocados a outras variáveis. Página 37 de 75 int modulo{20], y; /* Isto faz com que modulo seja ultrapassada */ for (x=0;y<=100;y++) modulo[y]=y; 6.2 — Matrizes com Duas ou Mais Dimensões C suporta matrizes multidimensionais. A forma mais simples de uma matriz multidimensional é a matriz bidimensional – uma matriz de matrizes unidimensionais, ou seja, uma matriz composta por linhas e colunas. Para declarar uma matriz bidimensional de inteiros d composta por 5 linhas e 8 colunas , escrevemos: int d[5][8]; Em C cada dimensão da matriz é colocada no seu próprio colchete, em contraste com outras linguagens que usam a vírgula para separar as dimensões: [1,5]. Matrizes bidimensionais são armazenadas em uma matriz linha-coluna, onde o primeiro índice indica a linha e o segundo a coluna. Isso significa que o índice mais a direita varia mais rapidamente do que o mais à esquerda quando acessamos os elementos da matriz na ordem em que eles estão realmente armazenados na memória. Ex.: d[3][3] Direito determina a coluna d[0][0] d[0][1] d[0][2] d[1][0] d[1][1] d[1][2] Esquerdo determina a linha d[2][0] d[2][1] d[2][2] Figura 6.1 – Uma matriz bidimensional na memória. Ex.: O seguinte programa carrega uma matriz bidimensional com números de 1 a 9, e escreve-os linha por linha. #include<stdio.h> void main(void) { int x, y, a[3][3]; Página 38 de 75 for(x=o;x<3;x++) { for(y=0;y<3;y++) a[x][y]=(x*3)+y+1; } /* Escrita dos números */ for(x=o;x<3;x++) { for(y=0;y<3;y++) printf(“%d”, a[x][y]); printf(“\n”) } } A forma geral da declaração de uma matriz multidimensional é: tipo nome_var [tamanho1][tamanho2]...[tamanhoN]; Ex.: int m[4][3][5]; /* declaração de uma matriaz multidimensioanal*/ int resultado = m[2][1][4] ; /* valor da posição daa matriaz */ Matrizes de três ou mais dimensões não são freqüentemente usadas devido a quantidade de memória que elas necessitam. As grandes matrizes multidimensionais são geralmente alocadas dinamicamente, através das funções de alocação dinâmica de C e ponteiros. Essa abordagem é discutida nas chamadas matrizes esparsas, que está fora do escopo desta apostila. Ex: Inicialização de uma matriz multidimensional int matriz[3][4][2] = {{{1,5},{2,6},{3,7},{4,8}},{{3,7},{4,9}, {5,4},{6,5}},{{1,0},{4,6},{6,7},{7,8}}}; 1,2,3,4 5,6,7,8 3,4,5,6 7,9,4,5 1,4,6,7 0,6,7,8 Pagina 1 Pagina 2 6.3 — Matrizes como Argumentos de Funções Em C, não se pode passar uma matriz inteira como um argumento para uma função. Página 39 de 75 Para as matrizes unidimensionais, somente o nome da matriz, desacompanhado dos colchetes, equivale ao endereço da matriz. O endereço da matriz é igual ao endereço do primeiro elemento (posição[0]), enquanto que o endereço dos outros elementos são diferentes. Se uma função recebe uma matriz unidimensional, podemos declarar o parâmetro formal em uma das três formas: a) Passar um ponteiro para uma matriz para uma função, especificando o nome da matriz sem índice void teste(int *y) /* Ponteiro*/ { ... } b)Passar um ponteiro para uma matriz para uma função, especificando o nome da matriz sem um índice: Ex.: void teste (int y[20]) /* Matriz dimensionada */ { . . Porque o compilador gera um código que instrui teste( ) a . receber um ponteiro – ele não cria uma matriz de 20 } elementos. c) Como uma matriz não-dimensionada – declara que uma matriz do tipo declarada, de algum tamanho, será recebida. O comprimento da matriz não importa porque o C não verifica os limites. Ex.: void teste(int y[ ]) /* Matriz dimensionada */ { . . . } /* Uma matriz sem um índice, é um ponteiro para o 1º elemento da matriz */ Página 40 de 75 Obs.: Todos os três métodos de declaração produzem resultados idênticos, pois cada um diz ao compilador que é um ponteiro para inteiro vai ser recebido. Quando uma matriz bidimensional é usada como argumento para uma função, apenas um ponteiro para o primeiro elemento é realmente passado. Porém, uma função que recebe uma matriz bidimensional como um parâmetro deve pelo menos definir o comprimento da segunda dimensão. Isso ocorre porque o compilador C necessita saber o comprimento de cada linha para indexar a matriz corretamente. Ex.: int y[5][5]; Pode ser declarada como: void func1(int y[ ][5] { . . .} 6.4 — Strings e Funções de Manipulação O uso mais comum de matrizes unidimensionais é como string de caracteres. Em C, uma string é definida como uma matriz de caracteres que é terminada por um nulo (‘\o’). Por esse motivo, declaram-se sempre matrizes de caracteres como sendo um caractere mais longo que a maior string que elas devem guardar. Uma cadeia de caracteres (ou string de caracteres) consiste em uma matriz homogênea do tipo escalar char (caracter) que ocupa 1 byte (8 bits) de memória para cada caracter, e a sua declaração é da forma: char nome_da_variável [tamanho]; a) Leitura de strings A leitura de strings de caracteres pode ser feita com a função scanf(), ela sempre acrescenta o ‘\0’ no primeiro espaço em branco que encontrar na cadeia de caracteres, desprezando o restante da string. Por outro lado, não é necessário o uso do operador &, pois o nome da string é o seu endereço inicial. Ex: scanf(“%s”, cidade); Página 41 de 75 Outra maneira de fazer a
Compartilhar