Baixe o app para aproveitar ainda mais
Prévia do material em texto
Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 1 Apostila Linguagem C Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 2 Sumário INTRODUÇÃO ........................................................................................................................................ 3 EXPRESSÕES ........................................................................................................................................... 4 TIPOS BÁSICOS DE DADOS .............................................................................................................................. 4 NOMES DE IDENTIFICADORES .......................................................................................................................... 5 VARIÁVEIS ................................................................................................................................................... 5 OPERADORES ............................................................................................................................................... 9 ABREVIAÇÕES EM C ..................................................................................................................................... 12 COMANDOS DE CONTROLE .................................................................................................................. 13 COMANDOS DE SELEÇÃO .............................................................................................................................. 13 COMANDOS DE ITERAÇÃO ............................................................................................................................ 18 COMANDOS DE DESVIOS .............................................................................................................................. 22 MATRIZES E STRINGS ........................................................................................................................... 26 MATRIZES UNIDIMENSIONAIS........................................................................................................................ 26 PASSANDO VETORES PARA FUNÇÕES ............................................................................................................... 27 STRINGS .................................................................................................................................................... 28 MATRIZES BIDIMENSIONAIS .......................................................................................................................... 30 MATRIZES DE STRINGS ................................................................................................................................. 31 INICIALIZAÇÃO DE MATRIZES ......................................................................................................................... 32 PONTEIROS .......................................................................................................................................... 33 VARIÁVEIS PONTEIROS ................................................................................................................................. 33 OPERADORES DE PONTEIROS......................................................................................................................... 33 ATRIBUIÇÃO DE PONTEIROS .......................................................................................................................... 34 INCREMENTANDO E DECREMENTANDO PONTEIROS ........................................................................................... 35 COMPARAÇÃO DE PONTEIROS ....................................................................................................................... 36 PONTEIROS E VETORES ................................................................................................................................ 36 PONTEIROS E STRINGS ................................................................................................................................. 37 ALOCAÇÃO DINÂMICA DE MEMÓRIA .............................................................................................................. 38 PONTEIROS E MATRIZES ............................................................................................................................... 40 VETORES DE PONTEIROS .............................................................................................................................. 41 PONTEIROS PARA PONTEIROS ........................................................................................................................ 42 FUNÇÕES ............................................................................................................................................. 44 FUNÇÕES RECURSIVAS ................................................................................................................................. 45 FUNÇÕES QUE RETORNAM PONTEIROS ............................................................................................................ 46 ESTRUTURAS ........................................................................................................................................ 47 REFERECIANDO ELEMENTOS DE ESTRUTURAS ................................................................................................... 48 MATRIZES DE ESTRUTURAS ........................................................................................................................... 49 PASSANDO ESTRUTURAS PARA FUNÇÕES .......................................................................................................... 49 PONTEIROS PARA ESTRUTURAS ...................................................................................................................... 51 ESTRUTURAS ANINHADAS ............................................................................................................................. 52 ENTRADA/SAÍDA PELO CONSOLE ......................................................................................................... 53 FUNÇÃO: PRINTF() ...................................................................................................................................... 54 FUNÇÃO: SCANF() ....................................................................................................................................... 56 ENTRADA/SAÍDA COM ARQUIVO ......................................................................................................... 58 STREAMS E ARQUIVOS ................................................................................................................................. 58 SISTEMA DE ARQUIVOS ................................................................................................................................ 59 FUNÇÕES PARA ARQUIVOS ........................................................................................................................... 61 Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 3 Capítulo 1 – Introdução A linguagem C foi inventada na década de 70. Ela é considerada uma linguagem de médio nível por combinar elementos de linguagens de alto nível com a simplicidade e funcionalidade de linguagens de baixo nível. Os códigos feitos pela linguagem C são bastante portáveis porque podem se adaptar a um software escrito de um tipo de computador a outro. Em geral, o C é considerado uma linguagem estruturada, o que permite a compartimentalização do código e dos dados. Sua principal componente estrutural é a função. Funções são blocos de construção em que toda a atividade do programa ocorre, e elas admitem que você defina e codifique separadamente as diferentes tarefas de um programa,permitindo, então, que seu programa seja modular. Após uma função ter sido criada, você pode contar com que ela trabalhe adequadamente em várias situações, sem criar efeitos inesperados em outras partes do programa. O fato de você poder criar funções isoladas é extremamente importante em projetos maiores onde um código de um programador não deve afetar acidentalmente o de outro. Outra maneira de estruturar e compartimentalizar o código em C é através do uso de blocos de código. Um bloco de código é um grupo de comandos de programa conectado logicamente que é tratado como uma unidade. Em C, um bloco de código é criado colocando-se uma seqüência de comandos entre chaves. Nesse exemplo os dois comandos após o if e entre chaves são executados se x for igual a 4. Esses dois comandos, junto com as chaves, representam um bloco de código, o que permite que muitos algoritmos sejam implementados com clareza, elegância e eficiência. Todo compilador C vem com uma biblioteca (que é um arquivo contendo as funções padrão que seu programa pode usar) C padrão de funções que realizam as tarefas necessárias mais comuns. if (x == 4) { printf(“Parabens, voce acertou, x é 4”); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 4 Capítulo 2 - Expressões Tipos Básicos de Dados Todas as linguagens de programação de alto nível suportam o conceito de tipos de dados, onde um tipo de dado define um conjunto de valores que uma variável pode armazenar e o conjunto de operações que pode ser executado com essa variável. No C, existem 5 tipos básicos de dados, que são: caractere (char), inteiro (int), ponto flutuante (float), ponto flutuante de precisão dupla (double) e sem valor (void). O tipo char é utilizado para especificar valores definidos pelo conjunto de caracteres ASCII, este conjunto contém letras, símbolos e até mesmo algarismos, porém os algarismos não poderão ser manipulados matematicamente. O tipo int é utilizado para especificar algarismos inteiros, sendo que estes sim poderão ser manipulados matematicamente. Os tipos float e double são utilizados para especificar algarismos contínuos (fracionários), sendo que o double aborda uma faixa de números maior do que o float e com maior precisão. O padrão ANSI especifica que a faixa mínima de um valor em ponto flutuante é de 1e-37 a 1e+37. O tipo void declara explicitamente uma função que não retorna valor algum ou cria ponteiros genéricos, ou seja, utiliza-se void sempre em que não há necessidade de retornar algum valor. Tipo Tamanho Intervalo unsigned char 8 bits 0 até 255 char 8 bits -128 até 127 short int 16 bits -32,768 até 32,767 unsigned int 32 bits 0 até 4,294,967,295 int 32 bits -2,147,483,648 até 2,147,483,647 unsigned long 32 bits 0 até 4,294,967,295 enum 16 bits -2,147,483,648 até 2,147,483,647 long 32 bits -2,147,483,648 até 2,147,483,647 float 32 bits 3.4 x 10-38 até 3.4 x 10+38 double 64 bits 1.7 x 10-308 até 1.7 x 10+308 long double 80 bits 3.4 x 10-4932 até 1.1 x 10+4932 Obs.: O tipo float possui 6 dígitos de precisão, enquanto que os tipos double e long double possuem 10 dígitos de precisão. Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 5 Nomes de Identificadores São considerados identificadores: os nomes de variáveis, funções, rótulos e vários outros objetos definidos pelo usuário. Esses identificadores podem variar de um a diversos caracteres. O primeiro caractere deve ser uma letra ou um sublinhado e os caracteres subseqüentes devem ser letras, números ou sublinhados. Abaixo se encontram alguns exemplos: Correto Incorreto valor 1valor Teste1 Ola&todos calculo_fatorial calculo...fatorial A linguagem C diferencia as letras maiúsculas e minúsculas, portanto: valor, Valor e VALOR são três identificadores distintos. Um identificador não pode ser igual a uma palavra-chave de C e não deve ter o mesmo nome que as funções que você escrever ou as que estão na biblioteca C. Variáveis Uma variável é uma posição de memória com um nome, que é usada para guardar um valor que pode ser modificado pelo programa. Todas as variáveis em C devem ser declaradas antes de serem usadas. A forma geral de uma declaração é: tipo nome_da_variável; Onde, tipo deve ser um ser um tipo válido em C, como aqueles vistos anteriormente; e nome_da_variável deve obedecer às regras vistas na seção nome de identificadores, podendo consistir em um ou mais nomes de identificadores separados por vírgulas. Abaixo estão alguns exemplos: int i, j, l; char letra; double valor; float peso, altura; Todas as variáveis são constituídas de dois valores, o primeiro é a posição de memória, este é um valor hexadecimal. O segundo é o valor contido nele, este pode ser manipulado. Exemplo: #include <conio.h> #include <stdio.h> void main() { int y = 25; /*y assume o valor 25*/ /* imprime o valor da posição de memoria em hexadecimal */ printf("A posicao de memoria (hexadecimal) e: %X", &y); /* imprime o valor da posição de memoria em decimal */ printf("\n\nA posicao de memoria (decimal) e: %d", &y); /* imprime o valor de definido pelo programador */ printf("\n\nO valor contido em x e: %d", y); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 6 As variáveis serão declaradas em três lugares básicos: dentro de funções (chamadas de variáveis locais), na definição de parâmetros das funções (chamadas de parâmetros formais) e fora de todas as funções (chamadas de variáveis globais). • Variáveis locais são aquelas declaradas dentro de um determinado bloco, portanto elas não são reconhecidas fora de seu próprio bloco de código. Lembrando que um bloco de código começa com um abre-chave e termina com uma fecha-chave. Estas variáveis existem apenas dentro deste bloco onde foram declaradas, portanto ela é criada na entrada do seu bloco e destruída na saída. Exemplo: A variável x foi declarada duas vezes, uma vez em funcao1( ) e outra em funcao2( ). O x na primeira função não tem nenhuma relação com a segunda, isso ocorre porque ela é uma variável local, e funciona somente dentro do bloco em que foi declarada. Quando sai da primeira função esta variável é destruída, acontecendo o mesmo na saída da segunda. A maioria dos programadores declara todas as variáveis usadas por uma função imediatamente após o abre-chaves da função e antes de qualquer outro comando. Porém, as variáveis locais podem ser declaradas dentro de qualquer bloco de código. Exemplo: void funcao1() { int x; x = 15; } void funcao2() { int x; x = -33; } void funcao() { int x; scanf(“%d”, &x); if( x = = 1) /*não há espaço entre os sinais de igual*/ { int y = 5; /*esta variável só é criada na entrada deste bloco*/ printf(“O resultado é: %d”, (x+y)); } } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 7 Nesta função, a variável y é criada na entrada do bloco de código if e destruída na saída. Além disso, y é reconhecida apenas dentro do bloco if e não pode ser diferenciada em qualquer outro lugar. • Parâmetros formais: Se uma função usa argumentos, ela deve declarar variáveis que receberão os valores dos argumentos, estas variáveis são chamadas de parâmetros formais. Elas se comportam como qualquer outra variável local dentro da função. Exemplo: Nos próximos capítulosserá ensinado como realizar chamada de funções, passando variáveis como parâmetros. Neste exemplo, prestemos atenção na função soma(), ela tem como parâmetros de entrada as variáveis a e b (ambos inteiros), estas variáveis funcionam apenas neste bloco, sendo destruídas após a saída da função, se comportando assim como variáveis locais. Importante: Você deve ter certeza de que os parâmetros formais que estão declarados são do mesmo tipo dos argumentos que você utiliza para chamar a função. Se há uma discordância de tipos, resultados inesperados podem ocorrer. • Variáveis globais são, ao contrário das variáveis locais, reconhecidas pelo programa inteiro e podem ser usadas por qualquer pedaço do código. Elas guardam seus valores durante toda a execução do programa. Elas são criadas declarando-as fora de qualquer função e podem ser acessadas por qualquer expressão independente de qual bloco de código contém a expressão. Vide no programa seguinte, valor foi declarada fora de todas as funções, portanto é uma variável global. Exemplo: /* declaração da função soma, declarando também a e b como parâmetros formais da função*/ int soma(int a, int b) return (a+b); void main() { int x = 1, y = 5, z; z = soma(x,y); /* aqui ocorre uma chamada de função, passando x e y como argumentos*/ printf(“%d”, z); /* imprime 6 na tela */ } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 8 A imagem abaixo é o resultado do programa apresentado acima: Observe este programa. As funções main( ) e funcao1( ) usaram a variável valor mesmo sem tê-la declarado. Já a funcao2( ) declarou uma variável local chamada valor, ou seja, com o mesmo nome com da variável global. Quando isso acontece, todas as referências ao nome da variável dentro do bloco onde a variável local foi declarada dizem respeito somente a ela mesma e não tem qualquer efeito sobre a variável global. Notamos isso no programa acima, quando a funcao1( ) faz a chamada da funcao2( )¸ valor recebe a atribuição 40, que é imprimido na tela, mas como é uma variável local, essa é destruída assim que sai do bloco, voltando assim para a funcao1( ), bloco onde valor é igual a 50. Variáveis globais são úteis quando o mesmo dado é usado em muitas funções em seu programa. No entanto, você deve evitar usar variáveis globais desnecessárias. Elas ocupam memória durante todo o tempo em que seu programa está executando, não apenas quando são necessárias. int valor; void funcao1(); void funcao2(); void main() { valor = 100; printf(“%d : na funcao main”, valor); funcao1(); getch(); } void funcao1() { valor = 50; printf(“\n%d : na funcao 1 depois de valor = 50”, valor); funcao2(); printf(“\n%d: na funcao 1 depois de ter passado pela funcao 2”, valor); } void funcao2() { int valor; valor = 40; printf(“\n%d : na funcao 2 depois de declara valor = 40 como variavel local”, valor); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 9 Operadores A linguagem C possui quatro classes de operadores: aritméticos, relacionais, lógicos e bit a bit. Além de ter alguns operadores especiais para tarefas particulares. Operador de Atribuição Você pode usar o operador de atribuição dentro de qualquer expressão válida de C. A forma geral do operador de atribuição é: Nome_da_variável = expressão; Onde expressão pode ser uma simples constante ou uma expressão tão complexa quanto você necessite. Você verá dois termos: lvalue e rvalue, onde lvalue se refere ao termo do lado esquerdo do igual, e rvalue do lado direito. O lvalue é o destino da atribuição, ele sempre deve ser uma variável ou um ponteiro, enquanto que o rvalue deve ser uma constante ou função. Exemplos: X = 40; // atribuição de uma constante inteira. Y = 3.1234; // atribuição de uma constante tipo float. Z = x + 2y; // atribuição de uma função. Conversão de Tipos em Atribuições Conversão de tipos refere-se à situação em que variáveis de um tipo são misturadas com variáveis de outro tipo. Em um comando de atribuição, o valor do lado direito (rvalue) de uma atribuição é convertido no tipo do lado esquerdo (lvalue), como segue no exemplo abaixo: #include <conio.h> #include <stdio.h> void main() { int x = 2; char ch; float f = 30.6954; ch = x; /* linha 1 */ printf("ch = %c", ch); x = f; /* linha 2 */ printf("\nx = %d", x); f = ch; /* linha 3 */ printf("\nf = %f", f); f = x; /* linha 4 */ printf("\nf = %f", f); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 10 Se executarmos o programa, teremos os seguintes resultados: Na linha 1, x está entre 0 e 156, portando ch e x possuem valores idênticos, que nesse caso é 2. De outra forma, o valor de ch reflete apenas os bits menos significativos de x. Na linha 2, x recebe a parte inteira de f. Na linha 3, f converte o valor inteiro de 8 bits armazenado em ch no mesmo valor em formato de ponto flutuante. Isso também ocorre na linha 4, exceto por f converter um valor inteiro de 16 bits no formato de ponto flutuante. Operadores Aritméticos A tabela abaixo lista os operadores aritméticos de C, e suas ações. Operadores Ações - Subtração, também menos unário + Adição * Multiplicação / Divisão % Módulo da divisão (resto) -- (dois -) Decremento ++ (dois +) Incremento O menos unário multiplica seu único operando por -1. Isto é, qualquer número precedido por um sinal de menos troca de sinal. Incremento e Decremento C inclui dois operadores úteis geralmente não encontrados em outras linguagens. São os operadores de incremento e decremento, ++ e --. O operador ++ soma 1 ao seu operando, e -- subtrai 1. Em outras palavras: Ambos os operadores de incremento e decremento podem ser utilizados como prefixo ou sufixo do operando. Prefixo Sufixo ++x; x++; Expressão É o mesmo que Ou ainda x = x + 1; ++x; x++; Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 11 Porém, há uma diferença quando esses operadores são usados em uma expressão. Quando um operador de incremento ou decremento precede seu operando, C executa a operação de incremento ou decremento antes de usar o valor do operando. Se o operador estiver após seu operando, C usa o valor do operando antes de incrementá-lo ou decrementá-lo. Considere o seguinte: X = 10; Y = ++x; Coloca 11 em y. Porém se o código fosse escrito como X = 10; Y = x++; y receberia 10. Em ambos os casos, x recebe 11, a diferença está em quando isso acontece. Operadores Relacionais e Lógicos No termo operador relacional, relacional refere-se às relações que os valores podem ter uns com os outros. No termo operador lógico, lógico refere-se às maneiras que essas relações podem ser conectadas. Estes freqüentemente trabalham juntos. Operadores Relacionais Operador Ação > Maior que >= Maior ou igual que < Menor que <= Menor ou igual que = = Igual != Diferente Operadores Lógicos Operador Ação && AND || OR ! NOT A idéia de verdadeiro e falso está por trás dos conceitos dos operadores lógicos e relacionais. Em C, falso é zero, enquanto que verdadeiro é qualquer valor diferente de zero. Abaixo temos a tabela verdade dos operadores lógicos, usando 1s e 0s. x y x&&y x||y !x 0 0 0 0 1 0 1 0 1 1 1 0 1 1 0 1 1 0 1 0 Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 12 Ambos os operadores são menores em precedência do que os operadores aritméticos. Isto é, uma expressão como 12 > 1 + 12 é avaliada como se fosse escrita 12 > (1 + 12). O resultado é, obviamente, falso. É permitido combinar diversasoperações em uma expressão como mostrado aqui: 10 > 5 && !(10 < 9) || 3 <= 4 Neste caso, o resultado é verdadeiro. Casts É possível forçar uma expressão a ser de um tipo usando uma construção chamada cast. A forma geral de um cast é (tipo) expressão onde tipo é um tipo de dado padrão de C. Por exemplo, para ter certeza de que a expressão x/2 será do tipo float, escreva (float) x / 2; Exemplo: Abreviações em C C oferece uma abreviação especial que simplifica a codificação de certos tipos de comandos de atribuição. A forma geral de uma abreviação C é variavel = variavel operador expressão; é o mesmo que variavel operador = expressão; Exemplos: Expressão É o mesmo que x = x + 8; x += 8; y = y / 6; y /= 6; z = z * y; z *= y; #include <conio.h> #include <stdio.h> void main() { int x = 10; printf("\nX/3 e igual a: %f", (float) x/3); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 13 Capítulo 3 – Comandos de Controle No C existe vários comando de controle do programa. O padrão ANSI divide os comandos nestes grupos: • Seleção • Iteração • Desvio • Rótulo • Expressão • Bloco Comandos de Seleção if ... else A forma geral da sentença if é if ( expressão) comando; else comando; onde comando pode ser um único comando, um bloco de comandos ou nada. Enquanto que else é opcional, seu uso não é obrigatório. Se a expressão é verdadeira (algo diferente de 0), 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 de que apenas o código associado ao if ou código associado ao else será executado, nunca ambos. Abaixo vemos o fluxograma correspondente a esta estrutura de decisão. Condição? Bloco1 Bloco2 V F Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 14 O comando condicional controlando o if deve produzir um resultado escalar. Um escalar é um inteiro (int), um caractere (char) ou tipo de ponto flutuante (float). No entanto, é raro usar um número de ponto flutuante para controlar um comando condicional, porque isso diminui consideravelmente a velocidade de execução. (A CPU executa diversas instruções para efetuar uma operação em ponto flutuante. Ela usa relativamente poucas instruções para efetuar uma operação com caractere ou inteiro). Veja um exemplo do uso do if: ifs Aninhados Um if aninhado é um comando if que é o objeto de outro if ou else. ifs 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. Por exemplo Como observado, o último else não está associado a if(j) porque não pertence ao mesmo bloco. Em vez disso, último else está associado ao if(i). O else interno está associado ao if(k), que é o if mais próximo. O padrão ANSI especifica que pelo menos 15 níveis de aninhamento devem ser suportados. Na prática, a maioria dos compiladores permite substancialmente mais. if(i){ if(j) comando 1; if(k) comando 2; //este if... else comando 3; //..está associado a este else } else comando 4; // associado a if(i) #include <conio.h> #include <stdio.h> void main() { int valor; printf("Entre com um valor inteiro: "); scanf("%d", &valor); if(valor > 0) printf("\nO valor que voce digitou e positivo"); else printf("\nO valor que voce digitou e negativo"); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 15 if – else – if Uma construção comum em programação é a forma if–else–if, algumas vezes chamada de escada if-else-if devido a sua aparência. A sua forma geral é if (expressão1) comando 1; else if (expressão2) comando 2; else if (expressão3) comando 3; . . . else comando N; As condições são avaliadas de cima para baixo. Assim que uma condição verdadeira é encontrada, o comando associado a ela é executado e o resto da escada é contornado. Se nenhuma das condições for verdadeira, então o último else é executado. Isto é, se todos os outros testes condicionais falham, o último comando else é efetuado. Se o último else não está presente, nenhuma ação ocorre se todas as condições são falsas. ? C contém um operador muito poderoso e conveniente que substitui certas sentenças da forma if-else. O ? é um operador ternário (que requer três operandos) que tem a forma geral Exp1 ? Exp2 : Exp3; Onde Exp1, Exp2 e Exp3 são expressões. Note o uso e o posicionamento dos dois pontos. O operador ? funciona desta forma: Exp1 é avaliada. Se ela for verdadeira, então Exp2 é avaliada e se torna o valor da expressão. Se Exp1 é falso, então Exp3 é avaliada e se torna o valor da expressão. Por exemplo, X = 10; Y = X > 9 ? 100 : 200; a Y é atribuído um valor 100. Se X fosse menor que 9, Y teria recebido o valor 200. O mesmo código, usando o comando if-else é X = 10; if ( X > 9) Y = 100; else Y = 200; Exemplo: Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 16 Este programa é análogo ao da seção if-else, incorporando agora os conceitos do operador ?. Observe os dois e faça as comparações. Switch C tem um comando interno de seleção múltipla, switch, que testa sucessivamente o valor de uma expressão contra uma lista de constantes inteiras ou de caractere. Quando o valor coincide, os comandos associados àquela constante são executados. A forma geral do comando switch é switch (expressão) { case constante1: comandos break; case constante2: comandos break; . . . default: seqüência de comandos } Abaixo vemos o fluxograma correspondente a esta estrutura de decisão. #include <conio.h> #include <stdio.h> void main() { int valor; printf("Entre com um valor inteiro: "); scanf("%d", &valor); valor > 0 ? printf("\nO valor que voce digitou e positivo") : printf("\nO valor que voce digitou e negativo"); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 17 O valor da expressão é testado, na ordem, contra os valores das constantes especificadas nos comandos case. Quando uma coincidência for encontrada, a seqüência de comandos associada àquele case será executada até que o comando break ou o fim do comando swtich seja alcançado. O comando default é executado se nenhuma coincidência for detectada. O default é opcional e, se não estiver presente, nenhuma ação será realizada se todos os testes falharem. O comando switch é freqüentemente utilizado na construção de menus, veja o exemplo abaixo: Expressão Conjunto 1 Conjunto 2 ... Conjunto N Conjunto D Rótulo 1 Rótulo 2 Rótulo N Rótulo D Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 18 Comandos de Iteração Os comandos de iteração, conhecidos também como 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 ocorre no laço for) ou com o final em aberto (como ocorre nos laços while e do-while). Laço for O laço for é uma estrutura de repetição, chamada de estrutura de repetição com contador. Ele executa um bloco de instruções em uma quantidade de vezes predefinida. A forma geral do comando for é for(inicialização; condição; incremento) { bloco } onde inicialização é uma expressão de inicializaçãodo contador, geralmente é um comando de atribuição que é usado para colocar um valor inicial na variável de controle do laço. A condição é uma expressão relacional que determina quando o laço termina. O incremento define como a variável de controle do laço varia cada vez que o laço é repetido. Estas três seções devem ser separadas por ponto-e-vírgula (;). O bloco é executado enquanto a condição for verdadeira, ou seja, uma vez que a condição se torne falsa, o laço é encerrado. #include <conio.h> #include <stdio.h> #include <stdlib.h> void funcao1(); // função para conversão de temperatura void funcao2(); // função para calculo de fatorial void main() { int opcao; printf("1: Conversao de Temperatura"); printf("\n2: Calcular Fatorial"); printf("\nEscolha uma opcao e tecle ENTER: "); scanf("%d",&opcao); // entrada de variavel switch(opcao) // analisa a opcao do usuario { case 1: // caso seja 1... funcao1(); // ... executa função 1 break; case 2: // caso seja 2... funcao2(); // ... executa função 2 break; default: // caso não seja nenhuma... exit(0); // ...o programa é encerrado break; } } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 19 Por exemplo, o programa abaixo imprime, na tela, os números de 1 a 100: Podem existir mais de uma expressão de inicialização e de incremento na estrutura for. Estas expressões devem ser separadas por vírgulas (,). Mas não pode haver mais de uma expressão de condição. Por exemplo: for(i = 0, j = 10; i < 10; i++, j--) { ... } Laço while Outra estrutura de repetição disponível no C é o laço while. Sua forma geral é: While(condição){ bloco } A condição pode ser qualquer expressão. Este laço se repete quando a condição for verdadeira, ou seja, qualquer valor diferente de zero. Quando a condição for falsa, o programa pula este laço e o bloco não é executado. O fluxograma desta estrutura é mostrado abaixo: Exemplo, o programa abaixo imprime, na tela, os números de 1 a 100: Condição? bloco V F #include <conio.h> #include <stdio.h> void main() { for(int i = 1; i<=100; i++) printf("%d ",i); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 20 Laço do-while Ao contrário dos laços for e while, que testam a condição do laço no começo, o laço do-while sempre será executado ao menos uma vez. Sua forma geral é: do{ bloco }while(condição); A condição é qualquer expressão relacional e/ou lógica. O bloco será executado uma vez, se a condição for falsa, ou seja, igual a zero, o programa encerra o laço. Porém, se a condição for verdadeira, ou seja, qualquer número diferente de zero, o bloco é executado novamente. O fluxograma desta estrutura é mostrado abaixo: bloco Condição? V F #include <conio.h> #include <stdio.h> void main() { int i = 1; while(i<=100) { printf("%d ", i); i++; } _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 21 Exemplo, o programa abaixo imprime, na tela, os números de 1 a 100: O comando do-while é uma boa escolha na construção de menus, porque sempre se deseja que as opções do menu execute ao menos uma vez. Depois que as opções forem mostradas, o programa será executado até que uma opção válida seja selecionada. Abaixo se encontra o exemplo utilizado na seção do switch, agora utilizando os conceitos de do-while: #include <conio.h> #include <stdio.h> #include <stdlib.h> void funcao1(); //função para conversão de temperatura void funcao2(); //função para calculo de fatorial void main() { int opcao; do{ printf("1: Conversao de Temperatura"); printf("\n2: Calcular Fatorial"); printf("\n3: sair"); printf("\nEscolha uma opcao e tecle ENTER: "); scanf("%d",&opcao); // entrada de variavel }while(opcao!=1 && opcao!=2); /*este bloco será executado sempre que opcao for diferente de 1 e de 2*/ switch(opcao) // analisa a opcao do usuario { case 1: // caso seja 1... funcao1(); // ... executa função 1 break; case 2: // caso seja 2... funcao2(); // ... executa função 2 break; case 3: // caso seja 3... exit(0); // ...o programa é encerrado break; } } #include <conio.h> #include <stdio.h> void main() { int i = 1; do{ printf("%d ", i); i++; }while(i<=100); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 22 Comandos de Desvios A linguagem C possui quatro comandos que realizam um desvio incondicional: return, goto, break e continue. Comando return O comando return é utilizado para retornar de uma função. Ele é um comando de desvio, pois faz com que a execução retorne ao ponto em que a função foi chamada. A sua forma geral é return expressão; A expressão é opcional, se houver alguma expressão contendo algum valor associado ao return, este é retornado da função para onde ela foi chamada. Se nenhum valor de retorno for especificado, assume-se que apenas lixo é retornado. Você pode usar quantos comandos return quiser dentro de uma função. Entretanto, a função deixará de executar tão logo ela encontre o primeiro return. Uma função do tipo void não pode ter um comando return. Comando goto No C, devido ao seu rico conjunto de estruturas de controle, há pouca necessidade da utilização do goto. A grande preocupação da maioria dos programadores sobre o goto é sua tendência de tornar os programas ilegíveis, mas se este for utilizado prudentemente, pode ser uma vantagem em certas situações na programação. O comando goto requer um rótulo para sua operação, o qual rótulo é um identificador válido em C seguido de dois pontos. O rótulo deve estar na mesma função do goto que o utiliza, senão você não poderá efetuar o desvio. A forma geral do goto é goto rótulo; ... rótulo: ... Por exemplo, o programa abaixo imprime, na tela, os números de 1 a 100: #include <conio.h> #include <stdio.h> void main() { int i = 1; repetir: // rótulo printf("%d ",i); i++; // incremento if(i <= 100) goto repetir; // chamada do rótulo _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 23 Comando break O comando break pode ser usado de duas formas. Ele pode ser usado em conjunto com switch...case como visto anteriormente, ou pode também ser usado em conjunto com um laço de repetição (for, do-while, while) que força a interrupção deste laço independentemente da condição de controle. Por exemplo, o programa abaixo imprime, na tela, os números de 1 a 10: Porém, o comando break força a saída apenas do laço mais interno de onde ele se encontra. Por exemplo, o programa abaixo imprime, na tela, os números de 1 a 10, 10 vezes. #include <conio.h> #include <stdio.h> void main() { for(int i = 1; i <= 100; i++) { printf("%d ",i); if(i == 10) break; // sai do laço quando i = 10 } _getch(); } #include <conio.h> #include <stdio.h> void main() { for(int i = 1; i <= 10; i++) { for(int j = 1; j <= 100; j++) { printf("%d ",j); if(j == 10) break; // sai do laço mais interno quando i=10 } printf("\n"); } _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 24 Função exit() A função exit() provoca uma terminaçãoimediata do programa inteiro, forçando um retorno ao sistema operacional, ela age como se estivesse finalizando o programa. A forma geral é void exit(int código_de_saída); O valor código_de_saída é um inteiro que será passado para o Sistema Operacional. O zero é geralmente usado como um código de retorno que indica uma terminação normal do programa, enquanto que outros argumentos são usados para indicar algum tipo de erro. O exemplo abaixo é idêntico ao utilizado na seção do-while: #include <conio.h> #include <stdio.h> #include <stdlib.h> void funcao1(); //função para conversão de temperatura void funcao2(); //função para calculo de fatorial void main() { int opcao; do{ printf("1: Conversao de Temperatura"); printf("\n2: Calcular Fatorial"); printf("\n3: sair"); printf("\nEscolha uma opcao e tecle ENTER: "); scanf("%d",&opcao); // entrada de variavel }while(opcao!=1 && opcao!=2); /*este bloco será executado sempre que opcao for diferente de 1 e de 2*/ switch(opcao) // analisa a opcao do usuario { case 1: // caso seja 1... funcao1(); // ... executa função 1 break; case 2: // caso seja 2... funcao2(); // ... executa função 2 break; case 3: // caso seja 3... exit(0); // ...o programa é encerrado break; } } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 25 Neste exemplo, se o usuário escolher sair do programa, caso 3, o comando exit(0) encerrará o programa. Note que o argumento utilizado é o inteiro zero, que indica saída normal do programa. As funções funcao1() e funcao2() devem ser definidas pelo programador. Comando continue O comando continue funciona de uma forma um tanto quanto similar ao comando break. Só que, ao invés de forçar a terminação do laço, continue força que ocorra a próxima iteração deste, pulando qualquer código intermediário. Para o laço for, este comando 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 de programa passa para o teste condicional. Veja o exemplo a seguir, o programa imprime, na tela, os números ímpares de 1 a 100: #include <conio.h> #include <stdio.h> void main() { int resultado; for(int i = 1; i <= 100; i++) { resultado = i%2; // resto da divisão i/2 if(resultado == 0) // se o resto for 0 continue; // continue força a proxima iteração do laço printf("%d ",i); } _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 26 Capítulo 4 – Matrizes e Strings Uma matriz é um conjunto de variáveis de um mesmo tipo. Em C, todas as matrizes consistem em posições contíguas na memória. O endereço mais baixo corresponde ao primeiro elemento e o mais alto, ao último elemento. Matrizes podem ter de uma a várias dimensões. A matriz mais comum em C é a de string, que é simplesmente uma matriz de caracteres terminada por um nulo. Matrizes Unidimensionais A forma geral para se declarar uma matriz unidimensional é tipo nome[tamanho]; onde tamanho deve ser um inteiro que define quantos elementos a matriz irá armazenar e tipo é o tipo de cada elemento da matriz. Matrizes devem ser explicitamente declaradas, juntamente com seu tamanho, para que o compilador possa alocar espaço para elas na memória. Por exemplo, se você quiser declarar um vetor (matriz unidimensional) do tipo float, chamada notas, e com 50 elementos, este vetor deve ser declarado da seguinte forma: float notas[50]; Para vetores, cada elemento possui um índice, sendo que o primeiro elemento possui índice zero e o último possui em seu índice o tamanho do menos um (tamanho-1). Por exemplo, considere o vetor int x[10]; aqui você está declarando um vetor com dez elementos, x[0] até x[9]. No seguinte código, o programa faz o carregamento de um vetor com os números de 0 a 99: Para um vetor, o tamanho total em bytes é calculado da seguinte forma: Total em bytes = sizeof(tipo) * tamanho do vetor #include <conio.h> #include <stdio.h> void main() { int x[100]; for(int i = 0; i < 100; i++) x[i] = i; // x[0]=0... x[1]=1... x[2]=2... etc. } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 27 A linguagem C não possui verificação de limites em matrizes. Você poderia ultrapassar o fim de uma matriz e escrever nos dados de alguma outra variável. Como programador, você deve prover verificação dos limites onde for necessário. A tabela abaixo exemplifica como um vetor apareceria na memória começando na posição 100 e fosse declarado na seguinte forma: double y[7]; Elemento y[0] y[1] y[2] y[3] y[4] y[5] y[6] Endereço 100 101 102 103 104 105 106 Passando vetores para funções Considere o seguinte fragmento de programa que passa o endereço de x para função(): void main() { int x[15]; funcao(x); . . . } Se uma função recebe um vetor, você pode declarar o parâmetro formal em uma entre três formas: como um ponteiro, como uma matriz dimensionada ou como uma matriz adimensional. Exemplos de como funcao pode receber o vetor x: funcao(int *x) /* ponteiro */ { . . . } Ou funcao(int x[10]) /* matriz dimensionada */ { . . . } Ou ainda funcao(int x[]) /* matriz adimensional */ { . . . } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 28 Todos os três métodos possuem resultados idênticos, pois cada um diz ao compilador q um ponteiro inteiro vai ser recebido. A primeira declaração usa, de fato, um ponteiro. A segunda emprega a declaração de matriz padrão. A última declaração simplesmente especifica que uma matriz do tipo int, de algum tamanho, será recebida. Strings Uma string é definida como um vetor de caracteres que é terminada por um nulo. Um nulo é especificado como ‘\0’ e geralmente é zero. Por essa razão, você precisa declarar matrizes de caracteres como sendo um caractere mais longo que a maior string que elas devem guardar. Por exemplo, para declarar um vetor string que guarda uma string de 10 caracteres, deve-se declarar desta forma char string[11]; Com isso reserva espaço para o nulo no final da string. Embora a linguagem C não tenha o tipo de dado string, ela permite constantes string. Uma constante string é uma lista de caracteres entre aspas. Por exemplo, “ola mundo” Você não precisa adicionar o nulo no final das constantes string manualmente, o compilador C faz isso automaticamente. C apresenta uma gama de funções de manipulação de strings. Abaixo lista as mais comuns com seus respectivos efeitos, considere s1 e s2 duas strings, e ch um caractere. Função Efeito strcpy(s1,s2) Copia s2 em s1 strcat(s1,s2) Concatena s2 ao final de s1 strlen(s1,s2) Retorna o tamanho de s1 strcmp(s1,s2) Retorna 0 se s1 e s2 são iguais; menor que 0 se s1 < s2; maior que 0 se s1 > s2 strchr(s1,ch) Retorna um ponteiro para a primeira ocorrência de ch em s1 strstr(s1,s2) Retorna um ponteiro para a primeira ocorrência de s2 em s1 Essas funções utilizam o cabeçalho STRING.H. O programa a seguir ilustra o uso dessas funções: Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 29 Deve-se lembrar que strcmp() retorna zero em caso das strings serem iguais, e zero é falso por isso deve-se usar o operador ! para reverter a condição, assim se tornará verdadeiro e o bloco dentro do if será executado. Se executarmos o programa teremos o seguinte resultado:#include <conio.h> #include <stdio.h> #include <string.h> void main() { char string1[80]; // declaração da string1 char string2[80]; // declaração da string2 printf("Entre com a primeira string: "); gets(string1); // recebe a string1 printf("Entre com a segunda string: "); gets(string2); // recebe a string2 printf("\nO tamanho da primeira string e: %d", strlen(string1)); // imprime o tamanho da string1 printf("\nO tamanho da segunda string e: %d", strlen(string2)); // imprime o tamanho da string2 if(!strcmp(string1,string2)) // faz a comparação printf("\nAs strings sao idênticas"); else printf("\nAs strings sao diferentes"); printf("\n%s",strcat(string1,string2)); // concatenação printf("\n%s",string1); // imprime a string1 concatenada strcpy(string1,"teste"); // copia “teste” para string1 printf("\n%s",string1); if(strchr(string2,'a')) // procura pela letra a na string2 printf("\nA segunda string contem a letra a"); if(strstr("ola mundo","ola")) printf("\nA expressao \"ola mundo\" contem a palavra \"ola\""); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 30 Matrizes Bidimensionais A declaração de uma matriz bidimensional é semelhante ao de unidimensional. Por exemplo, se você quiser declarar uma matriz bidimensional chamada x, do tipo inteiro e de tamanho 5,10, você escreveria int x[5][10]; Similarmente, para acessar o elemento 0,1 você usaria X[0][1]; O exemplo abaixo carrega uma matriz bidimensional com os números de 1 a 9 e o imprime em forma matricial: Construindo uma tabela dessa matriz com seus valores, temos a seguinte disposição: Coluna Linha 0 1 2 0 1 2 3 1 4 5 6 2 7 8 9 Neste exemplo, o elemento matriz[0][0] possui o valor 1, o elemento matriz[0][1] possui o valor 2, e assim por diante. O valor do último elemento matriz[2][2] será 9. #include <conio.h> #include <stdio.h> void main() { int matriz[3][3]; for(int i=0; i<3; i++) for(int j=0; j<3; j++) matriz[i][j] = i*3+j+1; for(int i=0; i<3; i++) { for(int j=0; j<3; j++) printf("%d",matriz[i][j]); printf("\n"); } _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 31 Matrizes bidimensionais são armazenadas em uma matriz linha-coluna, onde o primeiro índice indica a linha e o segundo, a coluna. No caso da matriz bidimensional, a seguinte fórmula fornece o número de bytes de memória necessários para armazená-la: Total em bytes = tamanho do 1º índice * tamanho do 2º índice * sizeof(tipo) Portando, a matriz exemplificada acima, que possui as dimensões 3, 3 teria 3 * 3 * 2 ou 18 bytes alocados, já que cada inteiro ocupa 2 bytes. Quando passamos uma matriz bidimensional é passada como um argumento para uma função, apenas um ponteiro para o primeiro elemento é realmente passado. No entanto, uma função que recebe uma matriz bidimensional como um parâmetro formal deve definir ao menos o comprimento da segunda dimensão. Você pode especificar a primeira dimensão, se quiser, mas não é necessário. Por exemplo, uma função que recebe uma matriz bidimensional de inteiros com dimensões 10, 10 é declarada dessa forma: funcao(int matriz[][10]) { . . . } Matrizes de Strings É muito comum no C a utilização das matrizes de strings. Para criar uma, use uma matriz bidimensional de caracteres. O tamanho do índice esquerdo indica a quantidade de strings que deseja e o tamanho do índice do lado direito indica o comprimento máximo de cada string. Por exemplo, se você quiser criar uma matriz de strings com a capacidade para 50 strings, sendo que, cada uma suporte no máximo 80 caracteres, você escreverá char nome[50][80]; Para acessar uma string individualmente, você simplesmente especifica apenas o índice esquerdo. Por exemplo, se você possuir uma lista com os nomes de cinqüenta alunos, e quiser imprimir o nome do 4º aluno, você deverá escrever printf(“%s”, nome[3]); Com isso, toda string da 4ª linha será impressa na tela. Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 32 Inicialização de Matrizes C permite a inicialização de matrizes no momento da declaração. A forma geral de uma inicialização de matriz é semelhante à de outras variáveis, como mostrado aqui: especificador_de_tipo nome_da_matriz[tamanho1 ...[tamanhoN] = {lista_de_valores}; A lista_de_valores é uma lista separada por vírgulas de constantes cujo tipo é compatível com especificador_de_tipo. A primeira constante é colocada na primeira posição da matriz, a segunda, na segunda posição e assim por diante. No exemplo abaixo uma matriz inteira de dez elementos é inicializada com os números de 1 a 10: int i[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; Isso significa que i[0] terá valor 1 e i[9] terá valor 10; Matrizes de caracteres que contêm strings permitem uma inicialização abreviada que toma a forma: char nome_da_matriz[tamanho] = “string”; No código abaixo, a matriz de string chamada string é inicializada com a frase “ola mundo”. char string[] = “ola mundo” Note que não foi definido o tamanho da matriz, quando se realiza uma inicialização de matriz isso pode ser feito, pois o compilador conta os números de elementos e aloca o espaço automaticamente. Este código é o mesmo que char string[10] = “ola mundo” Não esqueça de contar um espaço extra para o nulo (‘\0’). Inicialização de matrizes multidimensionais é equivalente ao de matrizes unidimensionais. Por exemplo, a matriz abaixo é inicializada com os números de 1 a 9: int i[3][3] = {1,2,3,4,5,6,7,8,9}; Que também pode ser escrita desta forma: int i[][3] = {1,2,3,4,5,6,7,8,9}; Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 33 Capítulo 5 – Ponteiros Um ponteiro é uma variável que contém um endereço de memória. Esse endereço é normalmente a posição de outra variável na memória. Se uma variável contém o endereço de outra, então a primeira variável é dita para apontar para a segunda. Variáveis Ponteiros Se uma variável irá conter um ponteiro, ela deve ser declarada da seguinte forma: tipo *nome; onde tipo é qualquer tipo válido em C e nome é o nome da variável ponteiro. Operadores de Ponteiros Existem dois operadores para ponteiros: * e &. O & é um operador unário (operador unário é aquele quer requer apenas um operando) que devolve o endereço na memória de seu operando. Por exemplo, m = &var; coloca o endereço da memória da variável var em m, daí dizemos que m está apontando para var. O endereço não tem relação alguma com o valor de var. O operador & pode ser imaginado como retornando “o endereço de”. O comando de atribuição anterior significa “m recebe o endereço de var”. O segundo operador de ponteiro, *, é o complemento de &. É um operador unário que devolve o valor da variável localizada no endereço que o segue. Neste caso, se m contém o endereço da variável var, q = *m; coloca o valor de var em q. Vamos supor que var usa o endereço 100 na posição de memória e que esta variável tenha o valor 5. Portanto com a primeira atribuição m terá o valor 100, e com a segunda atribuição q terá o valor 5, porque 5 estava armazenado na posição 100, que é o endereço que estava armazenado em m. O operador * pode ser imaginado como “no endereço”. Nesse caso, o comando anterior significa “q recebe o valor que está no endereço m”. Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 34 Atribuição de Ponteiros Do mesmo modo que umavariável comum o conteúdo de um ponteiro pode ser passado para outro ponteiro do mesmo tipo. As variáveis ponteiro devem sempre apontar para os tipos de dados corretos. Uma variável ponteiro declarada como apontador de dados inteiros deve sempre apontar para dados deste tipo. Observar que em C é possível atribuir qualquer endereço a uma variável ponteiro. Deste modo é possível atribuir o endereço de uma variável do tipo float a um ponteiro inteiro. No entanto, o programa não irá funcionar da maneira correta. Veja o exemplo abaixo, o endereço do terceiro elemento do vetor v é carregado em p1 e o endereço da variável i é carregado em p2. Além disso, no final o endereço apontado por p1 é carregado em p2. Os comandos printf() imprimem os valores e os endereços apontados pelos ponteiros respectivos. %p imprime o valor em hexadecimal assim como é usado pelo computador. Com a execução deste programa, temos o seguinte resultado na tela: #include <conio.h> #include <stdio.h> void main(void) { int vetor[] = { 10, 20, 30, 40, 50 }; int *p1, *p2; int i = 100; p1 = &vetor[2]; printf("Endereco de p1 e %p e seu valor e %d\n", p1, *p1); p2 = &i; printf("\nEndereco de p2 e %p e seu valor e %d\n", p2, *p2); p2 = p1; printf("\nEndereco de p2 e %p e seu valor e %d\n", p2, *p2); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 35 Incrementando e Decrementando Ponteiros O exemplo abaixo mostra que operações de incremento e decremento podem ser aplicadas em operandos. O primeiro printf imprime 30 o segundo 40 e o terceiro 50. Pode parecer estranho que um endereço que aponte para um número inteiro que é armazenado em dois bytes seja incrementado por um e passe para apontar para o próximo número inteiro. A resposta para isto é que sempre que um ponteiro é incrementado (ou decrementado) ele passa a apontar para a posição do elemento seguinte (ou anterior). Do mesmo modo somar três a um ponteiro faz com que ele passe apontar para o terceiro elemento após o atual. Portanto, um incremento em um ponteiro que aponta para um valor que é armazenado em n bytes faz que n seja somado ao endereço. É possível usar o seguinte comando: *(p+1)=10; Este comando armazena o valor 10 na posição de memória seguinte àquela apontada por p. É possível somarem-se e subtraírem-se inteiros de ponteiros. A operação abaixo faz com que o ponteiro p passe a apontar para o terceiro elemento após o atual. p = p + 3; A diferença entre ponteiros fornece quantos elementos do tipo do ponteiro existem entre os dois ponteiros. No exemplo abaixo é impresso o valor 3. #include <conio.h> #include <stdio.h> void main(void) { int vetor[] = { 10, 20, 30, 40, 50 }; int *p1; p1 = &vetor[2]; printf("%d\n", *p1); p1++; printf("%d\n", *p1); p1 = p1 + 1; printf("%d\n", *p1); _getch(); } #include <conio.h> #include <stdio.h> void main(void){ float vetor[] = { 1.0, 2.0, 3.0, 4.0, 5.0 }; float *p1, *p2; p1 = &vetor[2];/* endereco do terceiro elemento */ p2 = &vetor[0];/* endereco do primeiro elemento */ printf("Diferenca entre ponteiros %d\n", p1-p2); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 36 Não é possível multiplicar ou dividir ponteiros. Comparação de Ponteiros É possível comparar ponteiros em uma expressão relacional. Só é possível comparar ponteiros de mesmo tipo. O trecho de programa abaixo ilustra um exemplo deste tipo de operações. if (c == v) printf("As variáveis estao na mesma posicao.\n"); else printf("As variaveis nao estao na mesma posicao.\n"); Sendo c e v dois ponteiros declarados e inicializados anteriormente. Ponteiros e Vetores Ponteiros e Vetores estão fortemente relacionados na linguagem C. O nome de um vetor é um ponteiro que aponta para a primeira posição do vetor e todas as operações já mencionadas para ponteiros podem ser executadas com um nome de vetor. Por exemplo, a declaração int v[100]; declara um vetor de inteiros de 100 posições, e a partir dela temos que v é um ponteiro equivalente ao da declaração abaixo int *v; Por esta razão as seguintes declarações são idênticas e podem ser intercambiadas independentemente do modo como v foi declarado: v[i] = *(v+i); &v[i] = v+i; O exemplo ilustrado abaixo mostra as duas notações sendo usadas para imprimir o mesmo vetor. #include <conio.h> #include <stdio.h> void main() { float v[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; int i; for (i=0; i<9; i++) printf("%.1f ", v[i]); printf("\n"); for (i=0; i<9; i++) printf("%.1f ", *(v+i)); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 37 Existe uma diferença fundamental entre declarar um conjunto de dados como um vetor ou através de um ponteiro. Na declaração de vetor, o compilador automaticamente reserva um bloco de memória para que o vetor seja armazenado. Quando apenas um ponteiro é declarado, a única coisa que o compilador faz é alocar um ponteiro para apontar para a memória, sem que espaço seja reservado. O nome de um vetor é chamado de ponteiro constante e, portanto, não pode ter o seu valor alterado. Assim, os comandos abaixo não são válidos: Ponteiros e Strings Na linguagem C, este tipo de inicialização de ponteiro é perfeitamente válido: char *str = “Esta e uma string” Como você pode observar, o ponteiro str não é um vetor. Todo compilador C cria o que é chamada de tabela de string, que é usada internamente pelo compilador para armazenar as constranges strings usadas pelo programa. Assim, o comando de declaração anterior coloca o endereço de “Esta e uma string”, armazenado na tabela de strings no ponteiro str. Veja o exemplo abaixo, este programa imprime na tela o conteúdo da string e de trás para frente: #include <conio.h> #include <stdio.h> void main() { int list[5], i; /* O ponteiro list nao pode ser modificado recebendo o endereco de i */ list = &i /* O ponteiro list nao pode ser incrementado */ list++; _getch(); } #include <conio.h> #include <stdio.h> #include <string.h> void main() { char *str = "Esta e uma string"; printf("%s\n\n", str); for(int i = strlen(str)-1; i>=0; i--) printf("%c", str[i]); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 38 Alocação Dinâmica de Memória A alocação dinâmica é o meio pelo qual um programa pode obter memória enquanto está em execução. Como você sabe, as variáveis globais e locais devem ser declaradas de executar um programa, elas não podem ser acrescentadas durante o tempo de execução, portanto elas são constantes. Porém, haverá momentos em que um programa precisa usar quantidades de armazenamento variáveis, não constantes As funções básicas de alocação de memória são malloc(), calloc() e free(). Estas funções são encontradas na biblioteca stdlib.h. As funções malloc() e calloc() alocam memória (neste volume utilizaremos malloc()) e free() libera. Toda vez que você alocar espaço na memória deverá liberá-la. A função malloc() tem o seguinte protótipo: void *malloc(size_t numero_de_bytes); Aqui, numero_de_bytes é o número de bytes de memória que você quer alocar. A função malloc() devolve um ponteiro do tipo void, o que significa que você pode atribuí-lo a qualquer tipo de ponteiro, utilizando um cast.O fragmento de código seguinte aloca 1000 bytes na memória: char *str; str = (char *) malloc(1000); Ou ainda: char *str; str = (char *) malloc(1000*sizeof(char)); Como cada elemento do tipo char corresponde a 1 byte, então teremos 1000 bytes alocados. Se quisermos 150 elementos do tipo int: int *ptr; ptr = (int *) malloc(150*sizeof(int)); Mas infelizmente a memória disponível para o desenvolvimento de nosso software não é infinita, portanto sempre que for feita a alocação dinâmica de memória devemos testar o valor devolvido por malloc(). Se houver um erro de alocação ele devolverá zero (0). Exemplo: int *ptr; ptr = (int *) malloc(150*sizeof(int)); if(!ptr) { printf(“Erro! Memoria Insuficiente”); _getch(); exit(1); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 39 Como já foi dito, a função free() libera a memória alocada. Ela possui o seguinte protótipo: void free(void *p); É muito importante que você nunca usar free() com um argumento inválido; isso destruiria a lista de memória livre. Veja o programa abaixo utilizando os conceitos de alocação dinâmica de memória: Note que foi utilizado um do-while para o recebimento da variável tam, isso porque esta variável não pode assumir um valor menor ou igual a zero, já que não se pode realizar uma alocação de memória negativa ou igual a zero. #include <conio.h> #include <stdio.h> #include <stdlib.h> void main(void) { float *ptr; int tam; do{ printf("Entre com a quantidade de elementos que deseja para seu vetor: "); scanf("%d", &tam); }while(tam<=0); /* tam não pode ser menor ou igual a 0 */ ptr = (float *) malloc(tam*sizeof(float)); /* aloca memoria */ if(!ptr) /* verificação de erro na alocação */ { printf("Erro! Memoria Insuficiente"); _getch(); exit(1); } for(int i = 0; i < tam; i++) { printf("Entre com o %d elemento: ", i+1); scanf("%f", ptr+i); /* recebe o elemento do usuario */ } system("cls"); printf("Seu vetor e: "); for(int i = 0; i < tam; i++) printf("%.2f ", *(ptr+i)); /* imprime o elemento */ free(ptr); /* libera memoria */ _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 40 Ponteiros e Matrizes Nesta seção abordaremos a relação entre ponteiros e matrizes bidimensionais, já que matrizes unidimensionais (vetores) já foram tratadas anteriormente. Não há muita diferença destes, apenas na forma como o ponteiro é tratado para acessar um local específico da memória. Sabemos que um ponteiro aponta para uma área de memória que é endereçada de maneira linear. Deste modo, é necessário mapear o endereço de cada elemento na matriz, que é dado por linha coluna, em um endereço linear. Considere uma matriz chamada matriz de tamanho LIN, COL que poderia ser declarada e ter um de seus elementos lidos da seguinte maneira: Caso o programa utilizasse ponteiros ao invés de notação de matrizes o trecho de programa ficaria da seguinte maneira, observe que o endereço de cada elemento da matriz teve de ser calculado explicitamente: #include <conio.h> #include <stdio.h> #define LIN 3 #define COL 4 void main() { int matriz[LIN][COL]; for(i=0; i<LIN; i++) { for (j=0; j<COL; j++) { printf("Elemento %d %d = ", i, j); scanf("%d", matriz[i][j]); } } } #include <conio.h> #include <stdio.h> #define LIN 3 #define COL 4 void main(void){ int *matriz; int i, j; matriz = (int *) malloc(LIN*COL*sizeof(int)); if (!matriz){ printf("Erro! memoria suficiente.\n"); _getch(); exit(1); } for(i=0; i<LIN; i++){ for (j=0; j<COL; j++){ printf("Elemento %d %d = ", i, j); scanf("%d", matriz+(i*COL+j)); } } } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 41 Vetores de Ponteiros Como ponteiros também são variáveis é possível então criar vetores de ponteiros e utilizá-los. O programa abaixo mostra um programa onde é utilizado um vetor de ponteiros para linhas de caracteres. Observe esta declaração: char *linha[LINHAS]; Ela define um vetor, cujos elementos são ponteiros do tipo char que apontam para posições de memória. Até este momento temos apenas posições reservadas para armazenar os ponteiros. O espaço na memória só é efetivamente alocado após a chamada da função malloc(). Neste programa fizemos a alocação linha por linha, onde cada linha possui capacidade para 60 elementos do tipo char, se comportando portando como uma matriz de strings (visto no capítulo 4), só que utilizando o conceito de ponteiros. #include <conio.h> #include <stdio.h> #include <stdlib.h> #define LINHAS 10 #define COLUNAS 60 void main(void) { char *linha[LINHAS]; for(int i = 0; i < LINHAS; i++) { /* aloca memoria, linha por linha */ if(!(linha[i] = (char *)malloc(COLUNAS*sizeof(int)))) { printf("Nao consegui alocar o vetor %d.\n", i); exit(i); } } for(int i = 0; i < LINHAS; i++) { printf("Entre com a linha %d.\n", i); gets(linha[i]); /* recebe as strings */ } for(int i = 0; i < LINHAS; i++) /* imprime as strings */ printf("Linha %d: %s\n", i, linha[i]); for(int i = 0; i < LINHAS; i++) free(linha[i]); /* libera a memoria, linha por linha */ _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 42 Ponteiros para Ponteiros No exemplo da seção anterior, observamos que o número de linhas da matriz é fixo, e, portanto, há uma mistura de notação de ponteiros com matrizes. Vamos considerar um exemplo onde tanto o número de linhas como o de colunas é desconhecido. Neste exemplo iremos criar um vetor de ponteiros que irá armazenar o endereço inicial de cada linha. Portanto, para obter um elemento da matriz primeiro devemos descobrir onde está a linha no vetor que armazena os endereços das linhas, em seguida procuramos na linha o elemento. O programa abaixo pede ao usuário que digite o número de linhas e colunas da matriz. Em seguida lerá todos os elementos da matriz e a imprimirá na tela. Observe que agora foi criado um ponteiro para ponteiro chamado de **matriz. O programa primeiro pergunta o número de linhas da matriz para poder alocar espaço para armazenar os ponteiros para cada uma das linhas. Em seguida é alocado espaço para armazenar cada uma das linhas. Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 43 #include <stdio.h> #include <conio.h> #include <stdlib.h> void main () { int **matriz; /* matriz de ponteiros */ int lin, col; /* número de linhas e colunas */ do { printf("Entre com o numero de linhas: "); scanf("%d", &lin); /* recebe o numero de linhas */ } while (lin<=0); /* aloca as linhas da matriz */ matriz = (int **) malloc (lin * sizeof(int *)); if (!matriz) { printf("Erro! Memoria Insuficiente!"); _getch(); exit(1); } do{ printf("Entre com o numero de colunas: "); scanf("%d", &col); /* recebe o numero de colunas */ }while (col<=0); for (int i = 0; i < lin; i++) { /* alocaas colunas de cada linha da matriz */ *(matriz+i) = (int *) malloc(col * sizeof (int)); if(! *(matriz+i) ){ printf("Erro! Memoria Insuficiente!"); exit(1); } } printf("Entre com os elementos da matriz\n"); for(int i = 0; i < lin; i++) { for(int j = 0; j < col; j++) { printf("\nElemento %d %d: ", i, j); scanf("%d", *(matriz +i) +j); } /* recebe os elementos */ } system("cls"); printf("Os elementos da sua matriz sao\n\n"); for(int i = 0; i < lin; i++) { for(int j = 0; j < col; j++) printf("%d ", *(*(matriz +i) +j)); printf("\n"); /* imprime os elementos na tela */ } free(matriz); _getch(); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 44 Capítulo 6 – Funções Funções são os blocos de construção de C, e é o local onde toda atividade ocorre. Elas são umas das características mais importantes de C. A forma geral de uma função é especificador_de_tipo nome_da_funções(lista_de_parâmetros) { corpo da função } especificador_de_tipo se refere ao tipo de valor que a função retorna. Este pode ser qualquer tipo válido em C. lista_de_parâmetros é uma lista de variáveis separadas por vírgulas e seus tipos associados, se caso a sua função não precisar de parâmetros, a lista será vazia. No entanto, os parênteses ainda são necessários. Exemplos: Também pode ocorrer, como já foi discutido no capítulo 2, a necessidade da declaração de variáveis internamente, esse tipo de variável é chamada de variável local, essas variáveis vem a existir na entrada da função e são destruídas ao sair, ou seja, não podem ser acessadas após o fim da função. Em geral os argumentos podem ser passados de duas maneiras, chamada por valor ou chamada por referência. No primeiro caso, é copiado o valor de um argumento para o parâmetro de uma função, assim alterações feitas nos parâmetros formais não possuem efeito nas variáveis utilizadas. No segundo caso, chamada por referência, é repassada para a função o endereço da variável como argumento, assim as operações ocorrem diretamente no argumento, ou variável global. void funcao1(char y, int h) { bloco } float funcao2(float x, double *z) { bloco } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 45 Funções Recursivas Um tipo especial de função é a função recursiva, ou recursão, que ocorre quando um comando no corpo da função a chama. Um exemplo clássico de função recursiva é o calculo de fatorial. Veja o exemplo abaixo: O mesmo exemplo pode ser feito de forma não-recursiva, veja o trecho do programa: int fatorial(int x) { int resultado = 1; for(int t = 1; t <= x; t++) resultado = resultado*(t); return resultado; } #include <stdio.h> #include <conio.h> int fatorial(int x); void main () { int num; do{ printf("Entre com um numero natural para o calculo do fatorial: "); scanf("%d", &num); }while(num < 0); printf("\n\nO fatorial de %d e %d", num, fatorial(num)); _getch(); } int fatorial(int x) { int resultado; if(x == 1) return 1; else if(x == 0) return 1; else return resultado = x*fatorial(x-1); } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 46 Funções que retornam ponteiros As funções que devolvem ponteiros são manipuladas da mesma forma, embora precisem de atenção especial. Ponteiros para variáveis não são variáveis, eles são o endereço na memória de um certo tipo de dado. Para se retornar um ponteiro, a função deve ter o tipo de retorno igual a um ponteiro. Veja um exemplo de uma função que devolve um ponteiro para a primeira ocorrência do caractere c na string s Lembrando que quando se incrementa um ponteiro, ele aponta para o próximo elemento da memória. #include <stdio.h> #include <conio.h> char *match(char c, char *s); void main () { char ch, *s = "ola mundo", *string; printf("Entre com um caractere: "); scanf("%c", &ch); string = match(ch, s); printf(string); _getch(); } char *match(char c, char *s) { while(c != *s) s++; return s; } Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 47 Capítulo 7 – Estruturas Em C, uma estrutura é uma coleção de variáveis referenciadas por um nome, fornecendo uma maneira conveniente de se ter informações relacionadas agrupadas. Uma definição de estrutura forma um modelo que pode ser usado para criar variáveis de estruturas. As variáveis que compreendem a estrutura são chamadas elementos da estrutura. Por exemplo, se quisermos uma estrutura com dados de um aluno, com informação deste como seu nome, número de matrícula, entre outros, devemos usar o seguinte fragmento de código que mostra como criar um modelo de estrutura. A palavra-chave struct informa ao compilador que um modelo de estrutura está sendo definido. struct alunos { char nome[80]; char numero_matricula[15]; int numero_de_faltas; float notas[4]; }; Note que a definição termina com um ponto-e-vírgula. Isso ocorre porque uma definição de estrutura é um comando. Além disso, o nome (ou rótulo) da estrutura alunos identifica essa estrutura de dados em particular e é o seu especificador de tipo. Com este código, nenhuma variável foi de fato declarada. Foi feito apenas uma definição do formato da estrutura. Para declarar uma variável com essa estrutura escreva struct alunos informacao; Isso declara uma variável do tipo estrutura alunos chamada informacao. O compilador C aloca automaticamente memória suficiente para acomodar todas as variáveis que formam a variável estrutura. Neste caso, esta estrutura ocupa na memória o espaço de 113 bytes. Você também pode declarar uma ou mais variáveis enquanto a estrutura é definida. Por exemplo, struct alunos { char nome[80]; char numero_matricula[15]; int numero_de_faltas; float notas[4]; } informacao, dados, variavel; Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 48 O nome da estrutura pode ser omitido se você precisar apenas de uma variável estrutura. Isso significa que struct alunos { char nome[80]; char numero_matricula[15]; int numero_de_faltas; float notas[4]; } informacao; Declara uma variável chamada informacao como definido pela estrutura que a precede. Portanto, a forma geral de uma definição de estrutura é struct nome { tipo nome_da_variável1; tipo nome_da_variável2; . . . tipo nome_da_variávelN; }variáveis_estrutura; Onde nome ou variáveis_estrutura podem ser omitidos, mas não ambos. Refereciando Elementos de Estruturas Os elementos individuais de estruturas são referenciados através do operador ponto (.). Por exemplo, digamos que você deseja acessar o número de faltas de um aluno, atribuindo a esse um valor, escreva informacao.numero_de_faltas = 2; O nome da variável estrutura seguido por um ponto e pelo nome do elemento referencia esse elemento individual da estrutura. A forma geral para acessar um elemento de estrutura é nome_da_variável_estrutura.nome_do_elemento Portanto, para escrever o numero de faltas na tela escreva printf("%d",infomrmacao.numero_de_faltas); Analogamente, pode ser usada a função gets() para receber o nome do aluno gets(informacao.nome); Universidade Federal De Uberlândia
Compartilhar