Baixe o app para aproveitar ainda mais
Prévia do material em texto
1 INTRODUÇÃO À CIÊNCIA DA COMPUTAÇÃO André Christóvão Pio Martins - DEE OBJETIVOS Ao término da disciplina o aluno deverá ser capaz de construir algoritmos para solução de problemas de média complexidade e implementá-los em linguagem C CONTEÚDO 1. Características básicas dos computadores 2. Algoritmos e programas 3. Linguagem de baixo e de alto nível 4. Linguagens compiladas e interpretadas 5. Programação procedural e orientada a objetos 6. Linguagem C: a. Estrutura da linguagem b. Tipos de dados e declarações c. Operadores, comandos de atribuição, entrada e saída d. Funções pré-definidas e. Estruturas de seleção e repetição f. Vetores, Matrizes e Strings g. Arquivos h. Funções e subrotinas BIBLIOGRAFIA BÁSICA 1. ASCENCIO & CAMPOS: Fundamentos da Programação de Computadores - Algoritmos, Pascal, C/C++ e Java. Pearson. 2. JOYANES: Fundamentos de Programação: Algoritimos, Estruturas de Dados e Objetos. McGraw-Hill. 3. SCHILDT: C Completo e Total. Pearson. 4. ALVES. C++ Builder 6. Érica. 5. FORBELLONE & EBERSPACHER: Lógica de Programação - A Construção de Algoritmos e Estruturas de Dados. Makron Books. 6. SALIBA: Técnicas de Programação – Uma Abordagem Estruturada. Makron Books. 7. Apostilas da Internet (UFMG, UNICAMP, etc) CRITÉRIO DE AVALIAÇÃO 0,5 2 21 ≥+= PPMP Obs 1. Presença obrigatória (70%) 2. O material das aulas será enviado por e-mail 3. O material referente a cada aula deve ser lido antes da aula 4. Compilador CodeBlocks 5. Compilador DevC++ 2 CARACTERÍSTICAS BÁSICAS DOS COMPUTADORES DIGITAIS 1- Monitor 2- Placa-mãe 3- Processador 4- Memória RAM 5- Placas de rede, som,... 6- Fonte de energia 7- Leitor de CD/DVD 8- Disco rígido(HD) 9- Mouse 10- Teclado HARDWARE: parte física do computador, o principal componente é a CPU (CPU≠gabinete), os demais são dispositivos de entrada e saída. SOFTWARE: parte lógica do computador, são os programas: • Sistema Operacional: gerencia o hardware, os demais programas e o uso do hardware pelos programas, é uma plataforma para os aplicativos. • Compilador: permite a construção de novos aplicativos (DevC++, TurboPascal, etc.). • Aplicativo: programa que executa tarefas específicas e pré-definidas (Word: edição de texto, PowerPoint: apresentação, MatLab: cálculos matemáticos, etc). Obs: os detalhes do uso do hardware pelos programas que serão implementados não serão abordados. No hardware você bate, o software você xinga. 3 UC – Unidade de Controle ULA – Unidade Lógica e Aritmética DMA – Acesso Direto à Memória UCP – Unidade Central de Processamento (Microprocessador/Microcontrolador ≠ CPU/Gabinete) MEMÓRIA Voláteis: registradores, cache, RAM Não-voláteis: ROM, HD, CD, DVD, memória flash DISPOSITIVOS DE E/S Teclado (E) Monitor de vídeo (S) Impressoras (S) Pen Drive (E/S) Disco Rígido (E/S) Discos Flexíveis (E/S) PROCESSAMENTO DA INFORMAÇÂO Os microprocessadores são formados por milhões de chaves (transistor) que podem estar abertas ou fechadas, ou seja, são capazes de distinguir apenas dois níveis de tensão (~3 V, valor binário 1, e ~0 V, valor binário 0). A lógica que permite aos computadores digitais processar informação baseada nestes dois valores é chamada Álgebra de Boole. Portanto, toda informação (dados ou instruções) tratada pelos computadores digitais tem que ser convertida em BIT (BInary DigiT, 0 ou 1) ou BYTE (grupo de 8 bits). Como 28=256, em um byte podem-se representar 256 diferentes valores, desde 0000.0000 até 1111.1111 ⇒ Linguagem de Máquina. Para que o computador seja funcional, as instruções e os dados devem ser arranjados de forma a que se atinjam objetivos específicos ⇒ Programas. A linguagem de máquina resulta em programas ininteligíveis e mesmo a Linguagem Assembly gera programas extensos, difíceis de serem entendidos, com baixa portabilidade e caros, embora eficientes e rápidos (linguagens de baixo nível). Por exemplo, para o microprocessador 8085 temos: 4 Linguagem de máquina: 0011.1110 0011.0010 Linguagem Assembly: MVI A,32H Linguagem humana: Armazene o número 50 no Registrador A Alguns programas (compiladores) permitem que se escrevam os programas com instruções mais próximas da linguagem humana e fazem a tradução de tais programas para a linguagem de máquina. Linguagem de alto nível: ADA, ALGOL, BASIC, CLIPPER, COBOL, FORTRAN, LISP, LOGO, PASCAL, C, C++, Java, etc. Os programas podem ser executados à medida que as instruções são digitadas pelo programador (linguagens interpretadas), ou de uma só vez após o programa todo ter sido digitado, compilado e criado o arquivo executável (linguagens compiladas). No primeiro caso, o programa interpretador deve estar instalado na máquina. No segundo caso, basta que o executável criado esteja na máquina. Programação Estruturada: programas que usam estruturas de controle para definir a seqüência de execução das instruções (comandos IF, FOR, WHILE, REPEAT, etc). Evita a necessidade de LABEL e o comando GO TO. Programação Procedural: os programas são seqüências de chamadas de procedimentos, que podem alterar dados ou apenas executar procedimentos específicos. Caracterizam-se pela passagem de parâmetros e pelas variáveis locais/globais. Facilita a construção de programas complexos e a busca por erros. Programação Orientada a Objeto: consiste em tratar os dados (atributos) e os procedimentos (métodos) que atuam sobre os dados como um único objeto. Caracterizada pelo Encapsulamento, Herança e Polimorfismo, facilita a construção e manutenção de programas muito complexos, implementados por equipes de programadores. UM PROGRAMA ESCRITO EM C++ OU JAVA PODE NÃO SER UM OOP 5 ALGORITMOS “Processo de cálculo, ou de resolução de um grupo de problemas semelhantes, em que se estipulam, com generalidade e sem restrições, as regras formais para a obtenção do resultado ou da solução do problema” [AURÉLIO] ⇒ Especificar uma seqüência de passos lógicos para que o computador possa executar uma tarefa determinada (independe da linguagem) MANIPULAÇÃO DA INFORMAÇÃO PELOS ALGORITMOS • Os algoritmos são formados por INSTRUÇÕES que determinam a maneira como os DADOS devem ser processados • Os dados podem ser numéricos (INTEIROS, REAIS, COMPLEXOS), literais (CARACTERES, STRINGS) ou lógicos (BOOLEANOS) • As variáveis armazenam os dados e podem ser de qualquer dos tipos acima, e ainda multidimensionais (VETORES, MATRIZES) CARACTERÍSTICAS • Ter um começo e um fim • Não ser ambíguo • Receber dados de entrada e gerar informações de saída • Todas as etapas do algoritmo devem ser alcançáveis em um tempo finito FORMAS DE REPRESENTAÇÃO 1) FLUXOGRAMA Utiliza símbolos gráficos padronizados para representar os algoritmos VANTAGENS • Visualmente muito informativo • Padronização mundial 6 DESVANTAGENS • Aumenta de complexidade e pode perder a clareza à medida que o algoritmo cresce • Difícil de ser traduzido para linguagem de programação 2) LINGUAGEM ALGORÍTMICA Pseudolinguagem de programação (Portugol, Pseudocódigo, etc), cujos comandos, em português ou inglês, descrevem as etapas do algoritmo. Nos algoritmos não estruturados as instruções eram numeradas (LABEL) e as repetições eram forçadas por meio de uma instrução VÁ PARA (GO TO). VANTAGENS • Usa o português ou inglês como base • Facilidade na passagem para as linguagens de programação • Forma de apresentação condensada DESVANTAGENS • Ainda não está padronizado • Visualmente pouco informativo CONCLUSÃO As duasformas de apresentação podem ser complementares Fluxograma → mais adequado para dar uma visão geral do algoritmo ex.: programa principal que chama várias subrotinas Pseudocódigo → mais adequado para especificar cada etapa do algoritmo ex.:detalhar cada subrotina do programa principal INSTRUÇÕES BÁSICAS DOS ALGORITMOS • Declaração de variáveis: var a int[10] ou inteiro a[10] • Expressões aritméticas: a+b-c*d/f**g ou a+b-c*d/f^g • Expressões lógicas: a.E.b ou (a)E(b) ou a AND b ou a & b • Comando de atribuição: a←b-c*d/f**g ou a:=b-c*d/f**g • Comando de saída: escreva a ou escreva (a) • Comando de entrada: leia a ou leia (a) 7 ESTRUTURAS CHAVES PARA A CONSTRUÇÃO DE ALGORITMOS 1) SEQUENCIAÇÃO Os comandos do algoritmo fazem parte de uma seqüência, onde a ordem na qual ocorrem é relevante para o resultado final, pois serão executados um de cada vez, estritamente, de acordo com essa ordem b←0 b←b+1 a←1/b 931: b←0 932: b←b+1 933: a←1/b 2) DECISÃO Também é conhecida por estrutura condicional, a execução de um ou mais comandos depende da veracidade de uma condição. se a>b então aux←b b←a a←aux senão c←a+b fim_se 622: se a>b vá para 625 623: c←a+b 624: vá para 629 625: continue 626: aux←b 627: b←a 628: a←aux 629: continue 3) REPETIÇÃO (“looping” ou laço) Para (FOR): normalmente usado para executar uma seqüência de comandos um número pré-definido de vezes, permite definir um contador que pode ser incrementado ou decrementado. O contador é uma variável que pode ser utilizada em operações internas ao FOR ou pode ser modificada dentro do FOR. No segundo caso é preciso tomar cuidado para que não ocorra um looping infinito. 8 para i=1:2:9 faça leia a[i] c[i]←a[i]+b fim_para para i=1 até 9 incr 2 faça leia a[i] c[i]←a[i]+b fim_para para i=1 enquanto i<10 incr 2 faça leia a[i] c[i]←a[i]+b fim_para 527: i←1 528: continue 529: leia a[i] 530: c[i]←a[i]+b 531: i←i+2 532: se i<10 vá para 528 Enquanto (WHILE): executa uma seqüência de comandos caso uma determinada condição seja verdadeira, os comandos podem não ser executados nenhuma vez se a condição for inicialmente falsa, caso a condição seja verdadeira a seqüência de comandos deve eventualmente torná-la falsa para evitar o looping infinito. cont←0 enquanto a≥b faça a←a-b cont←cont+1 fim_enquanto 874: cont←0 875: continue 876: se a<b vá para 880 877: a←a-b 878: cont←cont+1 879: vá para 875 880: continue Repita (REPEAT-UNTIL/DO-WHILE): executa uma seqüência de comandos até que uma determinada condição se torne verdadeira (ou enquanto permaneça verdadeira, no caso do DO-WHILE), os comandos são executados pelo menos uma vez, caso a condição seja falsa a seqüência de comandos deve eventualmente torná-la verdadeira para evitar o looping infinito. 9 i←0 repita i←i+1 leia a[i] c[i]←a[i]+b até a[i]<0 fim_repita i←0 faça i←i+1 leia a[i] c[i]←a[i]+b enquanto a[i]≥0 fim_faça_enquanto 1054: i←0 1055: continue 1056: i←i+1 1057: leia a[i] 1058: c[i]←a[i]+b 1059: se a[i]≥0 vá para 1055 REFINAMENTOS SUCESSIVOS Um algoritmo estará pronto quando todas as suas instruções forem passíveis de serem traduzidas para a linguagem de programação e sua execução alcançar o resultado pretendido. Caso contrário, as instruções devem ser desdobradas em novas instruções, que constituirão um refinamento do algoritmo inicial. Isso é feito sucessivamente para todos os comandos, até que o algoritmo esteja pronto. O algoritmo para um problema único pode não ser único ⇒ programadores diferentes geram algoritmos diferentes, uns mais eficientes e outros menos, embora todos efetivos ⇒ mesmo os algoritmos prontos são passíveis de melhorias. ⇒⇒⇒⇒ A partir de um primeiro esboço (estrutura base do algoritmo) faz-se sucessivos refinamentos. ⇒⇒⇒⇒ Primeiro se constrói um algoritmo efetivo depois se busca o “ótimo”. ⇒⇒⇒⇒ Partes do algoritmo que aparecem mais de uma vez podem ser agrupadas como uma única sub-rotina que será chamada repetidas vezes. ⇒⇒⇒⇒ É fundamental documentar o algoritmo com nome, autor, data e objetivo do algoritmo, descrição das variáveis e comandos, seqüência de comandos ou sub- rotinas. 10 LINGUAGEM C Desenvolvida por Dennis Ritchie, baseada na linguagem B de Ken Thompson que se originou da linguagem BCPL de Martin Richards. É considerada uma linguagem de médio nível por permitir a manipulação de bits, bytes e endereços de memória. É uma linguagem estruturada e permite a criação de funções, embora não permita a criação de funções dentro de funções. Essa capacidade de construção de sub-rotinas isoladas torna a linguagem C muito conveniente para a implementação de programas complexos. Outra característica interessante da linguagem C são os blocos de códigos, ie, grupos de comando colocados dentro de chaves, que permitem uma implementação mais eficiente e clara dos algoritmos. A linguagem C tem apenas 32 palavras-chave reservadas: auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while A linguagem C é case sensitive. Por ser muito enxuta, a linguagem C não possui palavras- chave para várias funções que são intrínsecas em outras linguagens, (operações de entrada/saída, etc). Por isso, os programas em linguagem C normalmente incluêm chamadas a várias funções contidas na biblioteca C padrão, cujo código-objeto é adicionado, durante a linkedição, ao código do programa para formar o programa executável. 11 A estrutura geral de um programa em C é: #include <stdio.h> //inclusões de bibliotecas float x; //declaração de variáveis globais int main() //função principal { //início da função int x; //declaração de variáveis locais printf(“%i”,x); //comandos } //fim da função principal float f(float y) //tipo devolvido, função e argumentos { //início da função return x+y; //comandos } //fim da função A linguagem C apresenta cinco tipos básicos de dados (char, int, float, double e void) que podem ser modificados (signed, unsigned, long e short): char (8 bits): 256 caracteres (letras, números, símbolos) int (16 bits): números inteiros (-32.767 a 32.767) unsigned int (16 bits): inteiros (0 a 65.535) long int (32 bits): inteiros (-2.147.483.647 a 2.147.483.647) unsigned long int (32 bits): inteiros (0 a 4.294.967.295) float (32 bits): números reais (16 dígitos e expoentes ±38) double (64 bits): reais (16 dígitos e expoentes ±308) Os nomes de identificadores (variáveis, funções, rótulos, etc) devem começar com uma letra ou um sublinhado, seguidos de letras, números ou sublinhados. É conveniente que os nomes sejam significativos, mas não excessivamente longos: auxiliar vetor1 produto_34 Todas as variáveis devem ser declaradas antes de serem usadas. A declaração pode ser feita fora de todas as funções (variáveis globais), dentro dos blocos (variáveis locais) e na definição dos parâmetros das funções (parâmetros formais). As variáveis podem ser unidimensionais ou multidimensionais (vetores, matrizes, etc). 12 int i, j; float a[10][10], x[10], c;int main() { int x; char c; { int c; ⋮ As variáveis locais são alocadas ao iniciar a execução do bloco e são destruídas ao término desta. Os parâmetros formais das funções se comportam como variáveis locais. Quando houver variáveis locais e globais com mesmo nome, o bloco em questão irá processar apenas a variável local. Na linguagem C o comando de atribuição é feito utilizando o sinal =, podendo ser feita atribuição múltipla (x=y=10;), ou dentro de qualquer expressão (havendo sempre a conversão para o tipo da variável). As variáveis podem ser modificadas por const (impede sua alteração ao longo do programa=>inicialização explícita ou alteração via hardware) e volatile (informa ao compilador que a variável pode ser alterada independentemente do programa) const int a=10; A linguagem C permite que um programa seja compilado a partir de arquivos separados, para facilitar o gerenciamento das variáveis pode-se utilizar o comando extern que indica em cada arquivo as variáveis globais: Arquivo 1 Arquivo 2 int x, y; extern int x,y; int main() void funcao1(void) { { ⋮ ⋮ } } As variáveis locais podem manter seu valor entre chamadas da função com o uso de static: static int n; 13 Esse mesmo modificador aplicado a uma variável global faz com que a mesma seja reconhecida apenas no arquivo em que foi declarada. Se for usado o modificador register o compilador irá fazer com que a variável em questão seja armazenada no dispositivo de acesso mais rápido disponível. Portanto, ela poderá não ter endereço, caso seja armazenada em registradores. As variáveis podem ser inicializadas na declaração fazendo a atribuição de alguma constante (do tipo int, char, float, etc.). Variáveis locais e register não inicializadas possuem valores desconhecidos, variáveis globais e locais static não inicializadas possuem valor zero. Constantes iniciadas em 0 são octais e em 0x são hexadecimais. Constantes caractere devem ser escritas entre aspas simples ‘a’, e string entre aspas duplas “abc”. int i=1, hex=0x80, oct=012; float a=3.4F, b=-4.35e-3; Caracteres não imprimíveis são especificados por meio de barras invertidas: \b retrocesso \0 nulo \” \’ aspas \\ barra invertida \n nova linha \v tabulação vertical \r retorno \a beep \t tabulação horizontal 14 OPERADORES A linguagem C possui diversos tipos de operadores: Aritméticos: - + * / % -- ++ Obs: 1) na divisão inteira ou caractere o resto é truncado. 2) o sinal -- ou ++ pode ser colocado antes ou depois da variável. 3) x++ é mais eficiente do que x=x+1. Lógicos: && || ! Obs: 1) na linguagem C falso é zero e verdadeiro é qualquer não zero. Relacionais: > >= < <= == != Bit a bit: & | ^(xor) ~(comp. de 1) >> <<(deslocamentos) Ternário: y=x>9?100:200; (se x>9 então y recebe 100 cc. recebe 200) Ponteiro: &(endereço do ponteiro) *(conteúdo do ponteiro) Obs: 1) um ponteiro é o endereço na memória de uma variável. 2) variáveis ponteiro devem ser declaradas: char eu, *tu, eles; 3) Nesse caso, no endereço apontado por tu tem um caractere. Tamanho em bytes: sizeof x; sizeof (int); Vírgula: x=(y=3,y+1); � y=3; x=y+1; Ponto: cadastro.peso=74.5; (indica um elemento de uma estrutura) Seta: p->peso=74.5 (mesmo efeito do ponto se p é um ponteiro para peso) Casts: (float) 5/2; (força um determinado tipo em uma expressão) C reduzido: -= += *= /= (x=x+10 � x+=10) Obs: 1) ordem de precedência: + → - () [] -> ! ~ ++ -- - (tipo) * & sizeof * / % + - << >> < <= > >= == != & ^ | && || ?: = += -= *= /= %= , 2) Os parênteses podem ser usados para alterar a ordem de precedência. 3) Durante a avaliação de uma expressão é feita a promoção de tipo. 15 I/O PELO CONSOLE A linguagem C não define nenhuma palavra-chave para operações de entrada/saída, que são efetuadas por funções de biblioteca (stdio.h). As funções a seguir realizam as operações de entrada/saída via console (teclado, monitor). Entrada/saída não formatada: letra=getchar(); Lê o caractere (mostra na tela, não é iterativa). putchar(letra); Mostra a letra na tela (considera stdin e stdout). letra=getc(fp); Lê o caractere (mostra na tela, não é iterativa). putc(letra,fp); Mostra a letra na tela (considera a stream fp). gets(frase); Lê a linha até o return; acrescenta nulo no fim. puts(frase); Mostra a frase (considera stdin e stdout). cin>>variavel; Leitura do C++ para qualquer tipo de variável. cout<<variavel; Saída do C++ para qualquer tipo de variável. Entrada/saída formatada: printf(“caractere %c.\n”,letra); printf(“numero %f real.\n”,x); printf(“numero %5d com cinco digitos.\n ”,i); printf(“completa %05d com zeros.\n ”,i); printf(“frase %5.7s de 5 a 7 caracteres.\n ”,tu); printf(“numero %.3f com 3 casas decimais.\n ”,x); printf(“%5.3f com 5 dig. e 3 casas dec..\n ”,x); printf(“%-5.3f justificado a esquerda.\n ”,x); scanf(“%d”, &i); Lê um decimal e armazena em i. scanf(“%c”, &a); Lê um caractere e armazena em a. scanf(“%s”, palavra); Lê uma string até o 1o espaço em branco. Obs: 1) a leitura com scanf() não precisa ser necessariamente formatada. 2) espaços em branco ou caracteres em scanf() fazem a leitura ignorar a mesma quantidade de espaços ou os mesmos caracteres. 3) comentários podem ser colocado entre /* e */ ou após //. 4) a chamada para a biblioteca é feita por: #include <stdio.h> 16 7) a entrada não iterativa requer a digitação do enter após o dado a ser lido, esse enter permanece na stream de entrada e pode ser lido posteriormente Especificadores de formato: %c %s caractere e string %d %i inteiro decimal %u inteiro decimal sem sinal %p ponteiro %e %E notação científica com e ou E %f notação em ponto flutuante decimal %g %G usa o formato mais curto entre, %e ou %f e %E ou %f %#e %#E %#f %#g %#G força a colocação do ponto decimal %o octal %#o força a colocação de 0 %x hexadecimal sem sinal (abcdef) %#x força a colocação de 0x %X hexadecimal sem sinal (ABCDEF) %#X força a colocação de 0x printf(“pont aponta %n um inteiro com valor 12”, &pont); printf(“%% escreve o simbolo de porcentagem”); printf(“%*.*f deixa a precisão como argumento”,4,2,x); scanf(“%d%*c%d”,&x,&y); lê o caractere mas não o armazena scanf(“%[abc]s”,s1,s2); digitando abcdabc s1=”abc” e s2=”dabc” idem p/ [a-b], mas o inverso p/ [^abc] 17 FUNÇÕES MATEMÁTICAS Todas a funções a seguir estão definidas na biblioteca math.h. Todos os ângulos são considerados como radianos, os argumentos das funções devem ser escalares ou elementos de vetor e matriz, mas nunca o vetor e a matriz. cos(x) sin(x) tan(x) cosseno, seno e tangente de x acos(x) asin(x) atan(x) arco cosseno, arco seno e arco tangente de x atan2(x,y) arco tangente de x/y cosh(x) sinh(x) tanh(x) cosseno, seno e tangente hiperbólicos de x ceil(x) floor(x) arredondamento inteiro de x, para cima e para baixo exp(x) log(x) log10(x) exponencial, logaritmo natural e na base 10 de x fabs(x) fmod(x,y) valor absoluto de x e resto da divisão inteira de x por y ldexp(x,y) retorna x*2^y frexp(x,y) retorna a mantissa de x (0,5 a 1) e armazena a potência de 2 em y modf(x,y) retorna a parte inteira de x e armazena a parte decimal de x em y pow(x,y) sqrt(x) x elevado a y e raiz quadrada de x 18 ESTRUTURAS DE CONTROLE DE FLUXO DO PROGRAMA O fato da linguagem C considerar como resultado falso de um teste condicionalo valor 0 e como valor verdadeiro qualquer valor diferente de 0 permite a construção de códigos extremamente eficientes. Estruturas de seleção: if (a>b) printf(“\n%i”,a); else printf(“\n%i”,b); Se a expressão entre parênteses resulta em um valor diferente de zero (verdadeiro), o 1o comando é executado, caso contrário o 2o comando é executado. Obs: 1) a expressão não precisa ser necessariamente um teste lógico. 2) o 1o comando pode ser um comando único, um bloco ou estar ausente. 3) o else é opcional e se refere sempre ao if anterior do mesmo bloco. 4) o 2o comando pode ser outro if formando uma escada if-else-if. 5) o operador ternário ? pode substituir o if em algumas situações. switch (a+b) { case 1: printf(“a+b=1”); break; case 2: printf(“a+b=2”); break; default: printf(“a+b=%i”,a+b); } Se o valor da expressão entre parênteses for igual à constante de algum case, o comando subseqüente será executado, caso contrário o comando subseqüente ao default será executado. O padrão ANSI C estabelece um mínimo de 257 cases. As mesmas observações do if são válidas para o switch. Sendo que default e break são opcionais. 19 Estruturas de repetição: for (int i=0;i<=100;i=i+1) printf(“\n%i”,i); A inicialização (1o campo) geralmente atribui um valor inicial à variável de controle do laço, contador i, que é alterada conforme o comando de incremento (3o campo). Nesse caso, o comando subseqüente ao for é repetido para cada valor do contador enquanto o comando de condição (2o campo) estiver satisfeito, ie, resulte em um valor não zero. Obs: 1) a condição é verificada antes do comandof, e o incremento depois. 2) inicialização/condição/incremento podem ser qualquer expressão de C. 3) o comandof também pode alterar o contador (cuidado com laço infinito). 4) pode-se ter mais de um contador, nesse caso a inicialização e o incremento são separados por vírgulas. while (i<=100) printf(“\n%i”,i++); O comando subseqüente ao while é executado repetidamente enquanto a condição entre parênteses tiver valor não-nulo, verdadeiro. A condição é avaliada no início do laço e pode ser qualquer expressão de C. Comando pode estar ausente ou ser um bloco de comando. A variável de controle, contador i, deve ser declarada e inicializada previamente. do printf(“\n%i”,i++); while (i<=100); O comando subseqüente ao do é executado repetidamente enquanto condição entre parênteses tiver valor não-nulo, verdadeiro. A condição é avaliada no fim do laço. O comando pode ser único ou ser um bloco de comandos. Convém sempre usar um bloco de comandos com tabulação, mesmo quando o comando for único, para aumentar a clareza do código. Comandos de desvio incondicional: return x; Retorna de uma função, ie, volta ao ponto onde foi feita a chamada da função. Se tiver um valor ou variável associada, esse será o valor da função. É similar à última } da função. 20 goto ponto1; ⋮ ponto1: O comando goto desvia o fluxo de execução do programa para o rótulo correspondente, ponto1. O desvio pode ser feito para frente ou para trás. Rótulo é um identificador que segue as regras para os nomes das variáveis. É muito pouco utilizado por ser um resquício da programação não estruturada. break; Termina um case em um comando switch ou força a fim de um laço no qual esteja inserido, retornando para o comando seguinte ao laço. Nesse último caso, normalmente vem associado a uma estrutura de seleção if. exit(); Força o fim de um programa retornando ao sistema operacional. continue; Força a ocorrência da próxima iteração do laço no qual está inserida, pulando os códigos restantes do comando. Para o laço for, força a verificação da condição e a execução do incremento, para os laços while e do-while, força a verificação da condição. 21 VETORES E MATRIZES Uma matriz é uma coleção de variáveis do mesmo tipo que é referenciada por um nome comum. Cada elemento da coleção é acessado por um índice. Em linguagem C os elementos de uma matriz ocupam posições adjacentes na memória e os índices se iniciam com 0, e não com 1. float x[10][5]...[12] A quantidade de memória utilizada para armazenar a matriz é: sizeof(tipo)*10*5*...*12 Em linguagem C cabe ao programador se certificar que os limites da matriz não serão excedidos ao longo do programa. O nome da matriz sem índices especifica o endereço do primeiro elemento que pode ser usado como ponteiro para a matriz: int *p; int sample[10]; p=sample; //é idêntico a p=&sample[0]; sample[5]=10; //é idêntico a *(p+5)=10; O ponteiro para a matriz é muito útil para passar a matriz como argumento de uma função, já que a matriz propriamente dita não pode ser passada. Vetores são matrizes unidimensionais, sendo que, em linguagem C, as strings são vetores de caracteres que terminam em nulo “\0”. O uso de matrizes de caracteres não é raro, sendo que cada linha da matriz pode ser tratada como uma string. char palavra[15]; char texto[20][80]; gets(texto[2]); A inicialização das matrizes pode ser feita durante a declaração (inclusive sem dimensioná-las): int i[2][2]={1,34,43,25}; int i[][2]={1,34,43,25}; char palavra[6]=”UNESP”; char palavra[6]={‘U’,’N’,’E’,’S’,’P’,’\0’}; char palavra[]=”UNESP”; Os comandos cin e scanf buscam todos os caracteres até a ocorrência de um espaço em branco. O comando gets busca todos os caracteres até a ocorrência do enter. As constantes caractere de barra invertida permitem inserir caracteres não imprimíveis, p.ex. ‘\n’ indica o começo de uma nova linha. Convém lembrar que os elementos de uma matriz ocupam posições adjacentes na memória, mas duas variáveis declaradas em seqüência não. 22 FUNÇÕES DE CADEIA DE CARACTERES: <string.h> strcpy(s1,s2) copia s2 para s1 e retorna s1; strncpy(s1,s2,n) copia no máximo n caracteres da cadeia s2 para s1, retorna s1 e preenche as posições restantes com ‘\0’; strcat(s1,s2) concatena s2 com s1, retorna s1 e finaliza com ‘\0’, s1 dever ter tamanho suficiente para “receber” s2; strncat(s1,s2) concatena no máximo n caracteres de s2 com s1, retorna s1 e finaliza com ‘\0’, strcmp(s1,s2) compara s1 com s2, retorna negativo se s1<s2, zero se s1=s2 e positivo se s1>s2; strncmp(s1,s2,n) compara no máximo n caracteres de s1 com s2, retorna negativo se s1<s2, zero se s1=s2 e positivo se s1>s2; strchr(s1,s2) retorna um ponteiro à primeira ocorrência de s2 em s1, ou NULL caso não ocorra; strrchr(s1,s2) retorna um ponteiro à última ocorrência de s2 em s1, ou NULL caso não ocorra; strspn(s1,s2) retorna o comprimento da substring inicial de s1 consistindo de caracteres em s2; strcspn(s1,s2) retorna o comprimento da substring inicial de s1 consistindo de caracteres que não estão em s2; strspbrk(s1,s2) retorna apontador para a primeria ocorrência em s1 de qualquer caractere de s2, ou NULL se não houver; strstr(s1,s2) retorna apontador para a primeira ocorrência de s2 em s1, ou NULL se não houver; strlen(s1) retorna o comprimento de s1; strspn(s1,s2) retorna o comprimento da substring inicial de s1 consistindo de caracteres em s2; strcoll(s1,s2) compara s1 com s2 segundo a localidade especificada por setlocale(), retorna negativo se s1<s2, zero se s1=s2 e positivo se s1>s2; 23 PONTEIROS Um ponteiro é uma variável que contém um endereço de memória. Se esse endereço é o de uma outra variável, a primeira variável aponta para a segunda. char *p; Na declaração de um ponteiro, o tipo especificado é o da variável apontada. Assim, a variável p armazena um inteiro queé o endereço de uma variável caractere. O operador unário & devolve o endereço de seu operando. Enquanto o operador unário * devolve o valor armazenado no endereço de seu operando: p=&palavra; palavra=*p; Uma variável ponteiro pode receber o valor de outro ponteiro, ser incrementada, decrementada ou deslocada: p1=p2; p1++; p2--; p1=p1+12; Obs: os incrementos, decrementos e deslocamentos são feitos segundo o tipo da variável apontada. Assim, se o tipo ocupa mais de uma posição de memória um incremento pode não levar para a posição subseqüente da memória. Um ponteiro pode apontar para outro ponteiro (indireção múltipla): float **p1, *p2, x; p2=&x; p1=&p2; Durante a compilação de um programa são criadas quatro áreas de memória: código do programa, variáveis globais, pilha e heapy. Variáveis globais são alocadas durante a compilação e variáveis locais usam a pilha, mas nenhuma delas pode ser acrescentada durante a execução. A menos que se use alocação dinâmica da memória heapy. Da biblioteca <stdlib.h> podemos utilizar a função malloc() para alocar memória dinamicamente e free() para liberá-la: char *p; p=(char*)malloc(1000); //aloca 1000 bytes p=(char*)malloc(500*sizeof(int)); //melhor portabilidade Como a memória é finita convém sempre verificar se o ponteiro não é nulo: if (!(p=(char*)malloc(20*sizeof(float)))) { printf(“SEM MEMÓRIA.”); exit(1); } As operações utilizando ponteiros para as variáveis são, em geral, mais rápidas que as operações utilizando as próprias variáveis. Além disso, a alocação dinâmica de memória, 24 permite a implementação de programas bastante eficientes, inclusive utilizando vetores e matrizes de dimensão genérica: #include stdlib.h> float *vetor,**matriz; int main(void) { int i,n=10; vetor=(float*)malloc(n*sizeof(float)); matriz=(float**)malloc(n*sizeof(float)); for (i=0;i<n;i++) matriz[i]=(float*)malloc(n*sizeof(float)); } Erros cometidos com ponteiros são, em geral, difíceis de serem descobertos. Podendo ser bastante graves quando se armazena um dado em uma posição errada da memória (substituindo outras variáveis ou mesmo partes do código). Os erros mais comuns são: int *p, x=10; *p=x; //ponteiro (endereço) não inicializado p=x; //erro de sintaxe, o correto seria p=&x; 25 FUNÇÕES Funções são estruturas que facilitam a implementação e verificação dos programas por dividi-los em blocos independentes (C não permite funções dentro de funções, assim não é “estruturada em blocos”). Em C tem a seguinte forma geral: tipo nome(parâmetros) { comandos; } O tipo default de retorno é int. Os parâmetros são os argumentos da função, eles se comportam como variáveis locais e sua declaração tem a seguinte forma geral: tipo nome1, tipo nome2, ... , tipo nomeN A linguagem C permite especificar uma função com quantidade de parâmetros variável, desde que seja maior ou igual a 1: int func(int a, int b, ...) Os comandos no corpo da função determinam a atividade exercida por ela e não são acessíveis aos demais comandos do programa, pois tem escopos diferentes. A chamada da função é feita simplesmente pelo seu nome: t=4.0; x=sqrt(t); Nesse caso, os parâmetros formais da função copiam os valores dos parâmetros que são passados para a função. Assim, os valores que os parâmetros têm fora da função não são alterados, mesmo que os parâmetros formais sejam alterados no corpo da função (“chamada por valor”). Se as alterações nos parâmetros formais, dentro da função, alteram os valores dos argumentos que foram passados temos a "chamada por referência". A linguagem C só faz chamadas por valor. Quando se deseja alterar as variáveis que são passadas para uma função, seus parâmetros formais devem ser declarados como sendo ponteiros. Nesse caso, na chamada da função os argumentos devem ser precedidos por um &. 26 A passagem de matrizes como argumento de funções é uma exceção, pois é feita sempre por referência. Podendo ser declarada de maneiras diferentes, evitando sempre a ambigüidade em matrizes multidimensionais: Protótipo argumentos chamada da função void f(int vet[50]); int x[50] f(x); void f(int vet[]); int x[50] f(x); void f(int *vet); int x[50] f(x); void g(float mat[50][10]); float A[50][10] g(A); void g(float mat[][10]); float A[50][10] g(A); A função main() pode ter parâmetros formais (argumentos de linha de comando) que seguem o nome do programa na linha de comando do sistema operacional: int main (int argc,char *argv[]); O argc (argument count) é um inteiro e possui o número de argumentos com os quais a função main() foi chamada na linha de comando (vale no mínimo 1). O argv (argument values) é um ponteiro para uma matriz de strings. Cada string desta matriz é um dos parâmetros da linha de comando (argv[0] sempre aponta para o nome do programa), portanto deve-se fazer as conversões adequadas: i=atoi(argv[1]); converte o segundo argumento em inteiro x=atof(argv[2]); converte o segundo argumento em real O comando return provoca o encerramento imediato da função e, se o valor de retorno é informado, a função retorna este valor, que tem que ser compatível com o tipo declarado para a função: int mul(int a, int b) { return a*b; } Como o default para tipos de funções é o int e o C pode não fazer a verificação de tipo, o uso incorreto das funções podem gerar resultados bizarros. Para evitar esse tipo de erro foi 27 criado o protótipo de função, que consiste em declarar no início do programa todas a funções (com seus tipos e parâmetros) que serão escritas: tipo func(tipo var1, tipo var2, ..., tipoN varN); Obs: 1) uma alternativa para os protótipos seria escrever as funções antes da função main(), de modo que o compilador já saiba durante a compilação da chamada das funções quais são os parâmetros e os tipos retornados (pode não funcionar quando se tem o programa espalhado por vários arquivos). 2) uma função que retorna um ponteiro deve ser declarada como sendo um ponteiro para o tipo de variável apontada pelo ponteiro de retorno. 3) uma função que não retorna valor pode ser declarada com void para evitar a atribuição incorreta do valor retornado. Em C, uma função pode chamar a si própria, se tornando recursiva (p/ex. cálculo do fatorial). A maioria das funções recursivas não torna o programa mais eficiente (em termos de linhas de códigos, uso de memória ou tempo de execução), mas alguns algoritmos são mais facilmente implementados recursivamente. Obs: é essencial que funções recursivas tenham critérios de parada para que não entrem em loop infinito. 28 ARQUIVOS A linguagem C permite que o programa manipule um arquivo armazenado em um dispositivo de memória secundária utilizando funções de biblioteca. C++ suporta todo o sistema de arquivo do C, a recíproca não é verdadeira. Lembrando que a linguagem C++ é orientada, e implementar um programa orientado a objeto é mais do que simplesmente utilizar comandos do C++. Para a linguagem C qualquer dispositivo de E/S é um arquivo: HD, disquete, impressora, teclado, vídeo, etc . O sistema de E/S da linguagem C fornece um nível de abstração entre o programador e o dispositivo utilizado. Essa abstração é chamada de stream e o dispositivo real é chamado de arquivo. Existem dois tipos de streams: texto (seqüência de caracteres) e binária (seqüência de bytes). Uma stream é associada a um arquivo específico realizando um processo de abertura, com recursos diferentes para cada arquivo, p.ex. o acesso aleatório existe quando o arquivo é um disco mas não parao teclado. No fechamento ocorre a dissociação da stream e o conteúdo desta é descarregado no arquivo, flushing. Todos os arquivos são fechados automaticamente ao fim do programa main ou com uma chamada a exit(). O fechamento não se dá quando ocorre uma quebra do programa ou quando se chama abort(). A biblioteca com as funções para manipulação de arquivos é <stdio.h>. Um ponteiro de arquivo é um ponteiro para informações que definem o arquivo: nome, status, posição atual do arquivo. As operações das funções de E/S são feitas a partir desse ponteiro. A declaração é feita como: FILE *arq; As funções mais comuns da biblioteca são: arq=fopen(“dados.dat”,”modo”); abre um arquivo, devolve o ponteiro fclose(arq); fecha um arquivo Obs: 1) dados.dat: nome do arquivo (ou path) 2) modo: caractere que indica o modo de abertura do arquivo r: abre arquivo de texto, somente leitura w: cria arquivo de texto, somente escrita a: anexa novos dados, arquivo de texto 29 3) b indica que o arquivo é binário: rb, wb e ab 4) + indica que o arquivo é de leitura e escrita: r+, w+, a+, rb+, wb+ e ab+ 5) w e wb criam um arquivo novo ou sobrepõe o antigo com um vazio ⇒ ao manipular um arquivo existente deve-se usar leitura ou leitura e escrita 6) é sempre conveniente testar a abertura do arquivo fputc(ch,arq); escreve um caractere num arquivo ch=fgetc(arq); lê um caractere do arquivo fputs(s1,arq); escreve uma string num arquivo fgets(s1,n,arq); lê uma string de até n-1 caracteres do arquivo Obs: 1) char: variável do tipo caractere que será lida/escrita no arquivo 2) s1: variável string que será escrita no arquivo fwrite(&var,nbytes,cont,arq); escreve qualquer tipo de dado fread(&var,nbytes,cont,arq); lê qualquer tipo de dado Obs: 1) var: variável cujo conteúdo será escrito ou lido no arquivo 2) nbytes: número de bytes do conteúdo 3) cont: quantos itens, cada um de nbytes, serão escritos ou lidos 4) para cada tipo de variável (int, float, double, etc.) nbytes pode ser determinado com o comando: sizeof(tipo) fprintf(arq,“texto+spf”,&var1,...); escreve qualquer tipo de dado fscanf(arq,“texto+spf”,&var1,...); lê qualquer tipo de dado Obs: 1) spf: especificadores de formato, caractere iniciado com % 2) var1,...: variáveis cujos conteúdo serão escritos ou lidos Embora esta seja a forma mais prática de manipular arquivos devido ao fato de os dados estarem em ASCII, o tempo de conversão do binário para ASCII faz com que essa não seja a forma mais rápida rewind(arq); reposiciona o indicador no início do arquivo 30 fseek(arq,nbytes,pos); posiciona o indicador num endereço específico Obs: 1) nbytes: quantidade de bytes deslocados a partir da posição pos 2) pos: macro que define o ponto de origem para o posicionamento SEEK_SET: posição inicial SEEK_CUR: posição atual SEEK_END: posição final feof(arq); verifica se o final do arquivo foi atingido Obs: 1) Valor retornado zero ⇒ fim do arquivo não atingido 2) Valor retornado não zero ⇒ fim do arquivo atingido remove(arq); apaga um arquivo 31 TIPOS DE DADOS DEFINIDOS PELO USUÁRIO A ESTRUTURA é uma coleção de variáveis de tipos diferentes, referenciadas por um nome, conveniente para agrupar informações relacionadas. A forma geral é: struct ident { tipo campo1; ... tipo campoN; } var1,..., varM; struct ident { tipo campo1; ... tipo campoN;}; struct ident var1,..., varM; Um exemplo seria a identificação dos assinantes numa lista telefônica: struct assinante { char nome[30]; char rua[20]; long int cep; char tel[9]; } residencial[1000], comercial[200]; Os campos da estrutura são acessados usando o operador ponto: residencial[10].cep=17030; printf(“%d”,comercial[0].cep); scanf(“%d”,&comercial[0].cep); scanf(“%s”,comercial[0].tel); scanf(“%c”,&comercial[0].tel[2]); gets(residencial[98].rua); O campo de uma estrutura1 pode ser uma estrutura2, para acessar um campoX desta última a forma geral é: estrutura1.estrutura2.campoX O padrão C ANSI permite a atribuição de estruturas, eliminando a necessidade de atribuição campo a campo: residencial[23]=comercial[11]; Uma estrutura inteira pode ser passada para uma função. A passagem da estrutura é feita por valor: a estrutura, que está sendo passada, é copiada, campo por campo, em uma variável local da função, não alterando a variável fora da função. Assim, os parâmetros da função devem ser declarados como estruturas (as mesmas dos argumentos), e estas devem ser declaradas como estruturas globais (fora do main). Na linguagem C pode-se declarar um ponteiro para uma estrutura: struct assinante *p; Isso permite a passagem por referência de estruturas como argumentos de funções, o que torna o programa mais rápido. Nesse caso, o acesso aos campos da estrutura é feita por meio do operador seta (->): p=&assinante[77]; p->cep=37140; Para economizar memória, se adequar a dispositivos de hardware ou para atender rotinas de criptografia pode ser necessário acessar individualmente os bits dentro dos bytes. Isso pode ser feito utilizando os campos de bits. No exemplo abaixo, status guarda informações booleanas distintas em cada um de seus bits, embora se possa usar mais de um bit para o mesmo campo: struct status_type { unsigned delta_cts: 1; unsigned delta_dsr: 1; ... unsigned dsr: 1; unsigned ring: 1; unsigned rec_line: 1; } status; Uma declaração union determina uma única localização de memória onde podem estar armazenadas várias variáveis diferentes em momentos diferentes. Portanto, o compilador reserva espaço suficiente para acomodar o maior tipo dentre os campos (isso permite tornar o código independente da máquina). A declaração e acesso de uma união são semelhantes as de uma estrutura: union identificador { tipo nome1; ... tipo nomeN; } var1, ... , varM; Uma enumeração é um conjunto de variáveis inteiras que especifica todos os valores legais que uma variável desse tipo pode ter. A forma geral é: enum identificador {lista_de_valores} variáveis; Cada valor da lista corresponde a um inteiro começando em 0, que pode ser inicializado, no exemplo abaixo mao=0, balaio=10 e carro=11: enum medida {mao, balaio=10, carro} volume; O operador sizeof (não é função, pois a substituição é feita na compilação) retorna o tamanho em bytes de variáveis ou de tipos, e pode ser usado para garantir portabilidade ou fazer alocação dinâmica de memória de tipos definidos pelo usuário. O sizeof admite duas formas: sizeof variável sizeof (tipo) O comando typedef permite ao programador definir um novo nome para um determinado tipo. Não se está criando um novo tipo de dado apenas renomeando um já existente, isso pode ser útil para aumentar a portabilidade. Sua forma geral é: typedef tipo novo_nome; Em linguagem C toda linha de comando começando com # é uma diretiva do pré- processador. #define nome_macro string substitui toda nome_macro que aparecer no código pela string, o nome da macro pode ter argumentos (macro semelhante a função): #define ABS(a) (a)<0? -(a) : (a) printf(“valor absoluto de –1 e %d”,ABS(-1)); #error mensagem pára a compilação e mostra a mensagem. #include <arquivo.h> instrui o compilador a ler o código no arquivo. #if #ifdef #ifndef #else #elif #endif permitem a compilação condicional de uma parte do código de acordo com uma condição/definição. #if condição comandos; #endif #undef nome_macro remove a definição do nome da macro. defined nome_macro verifica se o nome damacro está definido. #line numero “nome_arquivo” estabelece novos __LINE__ e __FILE__. #pragma permite dar instruções ao compilador (consultar manual). O padrão C ANSI especifica cinco nomes intrínsecos de macros: __LINE__ número da linha atualmente sendo compilada. __FILE__ string com o nome do arquivo sendo compilado. __DATE__ data da tradução do arqivo fonte em código objeto, mm/dd/aa. __TIME__ hora da tradução do arqivo fonte em código objeto, h:m:s. __STDC__ booleana que indica se a implementação segue o padrão C ANSI.
Compartilhar