85
Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica 1 Apostila Linguagem C

Apostila C UFU.pdf

Embed Size (px)

Citation preview

Page 1: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

1

Apostila Linguagem C

Page 2: Apostila C UFU.pdf

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

Page 3: Apostila C UFU.pdf

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();

}

Page 4: Apostila C UFU.pdf

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.

Page 5: Apostila C UFU.pdf

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();

}

Page 6: Apostila C UFU.pdf

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));

}

}

Page 7: Apostila C UFU.pdf

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ítulos será 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 */ }

Page 8: Apostila C UFU.pdf

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);

}

Page 9: Apostila C UFU.pdf

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();

}

Page 10: Apostila C UFU.pdf

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++;

Page 11: Apostila C UFU.pdf

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

Page 12: Apostila C UFU.pdf

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 diversas operaçõ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();

}

Page 13: Apostila C UFU.pdf

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

Page 14: Apostila C UFU.pdf

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();

}

Page 15: Apostila C UFU.pdf

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:

Page 16: Apostila C UFU.pdf

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(); }

Page 17: Apostila C UFU.pdf

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

Page 18: Apostila C UFU.pdf

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ção do 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;

} }

Page 19: Apostila C UFU.pdf

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();

}

Page 20: Apostila C UFU.pdf

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(); }

Page 21: Apostila C UFU.pdf

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(); }

Page 22: Apostila C UFU.pdf

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(); }

Page 23: Apostila C UFU.pdf

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(); }

Page 24: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

24

Função exit() A função exit() provoca uma terminação imediata 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;

} }

Page 25: Apostila C UFU.pdf

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(); }

Page 26: Apostila C UFU.pdf

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.

}

Page 27: Apostila C UFU.pdf

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 */

{

.

.

.

}

Page 28: Apostila C UFU.pdf

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:

Page 29: Apostila C UFU.pdf

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();

}

Page 30: Apostila C UFU.pdf

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();

}

Page 31: Apostila C UFU.pdf

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.

Page 32: Apostila C UFU.pdf

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};

Page 33: Apostila C UFU.pdf

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”.

Page 34: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

34

Atribuição de Ponteiros Do mesmo modo que uma variá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(); }

Page 35: Apostila C UFU.pdf

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();

}

Page 36: Apostila C UFU.pdf

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(); }

Page 37: Apostila C UFU.pdf

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();

}

Page 38: Apostila C UFU.pdf

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);

}

Page 39: Apostila C UFU.pdf

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();

}

Page 40: Apostila C UFU.pdf

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));

}

} }

Page 41: Apostila C UFU.pdf

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(); }

Page 42: Apostila C UFU.pdf

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.

Page 43: Apostila C UFU.pdf

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++)

{ /* aloca as 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(); }

Page 44: Apostila C UFU.pdf

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

}

Page 45: Apostila C UFU.pdf

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); }

Page 46: Apostila C UFU.pdf

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; }

Page 47: Apostila C UFU.pdf

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;

Page 48: Apostila C UFU.pdf

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);

Page 49: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

49

Matrizes de Estruturas Para declarar uma matriz de estruturas, você deve primeiro definir uma estrutura e, então, declarar uma variável matriz desse tipo. Por exemplo, para declarar uma matriz de estruturas com 50 elementos do tipo alunos, que foi definido anteriormente, deve-se escrever struct alunos informacao[50];

Isso cria 50 conjuntos de variáveis que estão organizados como definido na estrutura alunos. Para acessar uma determinada estrutura, deve-se indexar o nome da estrutura. Por exemplo, para imprimir o número de faltas do aluno 4, escreva printf(“%d ”, informação[3].numero_de_faltas);

Como todas as outras matrizes, matrizes de estruturas começam a indexação em zero.

Passando estruturas para funções Até agora, todas as estruturas e matrizes vistas foram declaradas como globais. Mostraremos agora como passar estruturas e seus elementos para funções. Quando você passa um elemento de uma variável estrutura para uma função, está, de fato, passando o valor desse elemento para a função. Veja o exemplo a seguir:

struct novo

{

char x;

int y;

float z;

char s[10];

}var;

Abaixo são mostrados exemplos de cada elemento sendo passado para uma

função: funcao1(var.x); /*passa o valor do caractere de x*/

funcao1(var.y); /*passa o valor inteiro de y*/

funcao1(var.z); /*passa o valor float de z*/

funcao1(var.s); /*passa o endereço da string s*/

funcao1(var.s[2]); /*passa o valor do caractere de s[2]*/

Page 50: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

50

Porém, se você quiser passar o endereço de um elemento individual da estrutura, ponha o operador & antes do nome da estrutura. Por exemplo, escreva: funcao1(&var.x); /*passa o endereço do caractere de x*/

funcao1(&var.y); /*passa o endereço inteiro de y*/

funcao1(&var.z); /*passa o endereço float de z*/

funcao1(var.s); /*passa o endereço da string s*/

funcao1(&var.s[2]);/*passa o endereço do caractere de s[2]*/

Quando uma estrutura é usada como um argumento para uma função, a estrutura inteira é passada usando o método padrão chamado por valor. Quando usar uma estrutura como parâmetro, lembre-se de que o tipo de argumento deve coincidir com o tipo de parâmetro. Por exemplo, esse programa imprime os números 100 e 250, e uma constante string na tela. Definimos uma estrutura global e, então usamos seu nome para declarar variáveis estruturas e parâmetros, conforme necessário.

#include <stdio.h>

#include <conio.h>

void funcao1(struct estrutura param);

void funcao2(struct estrutura param2);

struct estrutura

{

int a, b;

char ch;

};

void main()

{

struct estrutura arg;

arg.a = 100;

arg.b = 250;

arg.ch = "Ola mundo";

funcao1(arg);

funcao2(arg);

}

void funcao1(struct estrutura param1)

{

printf("%d", param1.a);

}

void funcao2(struct estrutura param2)

{

printf("\n%d", param2.a);

printf("\n%s", param2.ch);

_getch();

}

Page 51: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

51

Ponteiros para Estruturas Na linguagem C é permitido usar ponteiros para estruturas assim como ponteiros para outros tipos de variáveis. Com outros ponteiros, você declara ponteiros para estrutura colocando * na frente do nome da variável estrutura. Por exemplo, o código seguinte declara um ponteiro para dados do tipo struct. struct alunos *ptr;

Se várias estruturas são usadas, a performance de seu programa pode ser reduzida a níveis inaceitáveis, a solução para esse problema é passar apenas um ponteiro para uma função. E também, atualmente a grande gama dos programas utilizando estruturas não possui tamanho definido, precisando para isso, realizar uma alocação dinâmica de memória. Para encontrar o endereço da variável estrutura, deve-se colocar o operador & antes do nome desta variável. Por exemplo:

struct alunos

{

char nome[80];

char numero_matricula[15];

int numero_de_faltas;

float notas[4];

} informacao;

struct alunos *ptr;

então ptr = &informacao;

este código põe o endereço da estrutura informacao no ponteiro ptr. Para acessar os elementos de uma estrutura usando um ponteiro para estrutura, você deve usar o operador -> (chamado operador seta). Por exemplo, isso referencia o campo numero_de_faltas: ptr -> numero_de_faltas;

Este operador seta é usado no lugar do operador ponto quando se está acessando um elemento de estrutura através de um ponteiro para a estrutura. Portanto lembre-se de usar o operador ponto para acessar elementos de estruturas quando estiver operando através da variável estrutura, e usar o operador seta quando você estiver operando através de um ponteiro para a estrutura.

Page 52: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

52

Estruturas Aninhadas Quando um elemento de uma estrutura é um elemento de outra estrutura, dizemos que esta é uma estrutura aninhada. Considere o exemplo abaixo:

Como podemos perceber, a estrutura aluno possui dois elementos. O primeiro elemento é a estrutura do tipo endereço, que contém outros dois elementos com dados do aluno relacionado ao seu endereço. O segundo elemento é nome, que é um vetor do tipo char. Para acessar os elementos de uma estrutura, como sabemos, utilizamos uma variável estrutura. Neste caso, como há uma estrutura dentro da outra, devemos utilizar duas variáveis estruturas para acessar algum elemento da estrutura mais interna. O padrão ANSI C especifica que as estruturas podem ser aninhadas até 15 níveis.

#include <stdio.h>

#include <conio.h>

struct endereco

{

char rua[80];

int numero_da_casa;

} var;

struct aluno

{

char nome[80];

struct endereco var;

} info;

void main()

{

printf("Nome: ");

gets(info.nome);

printf("Rua: ");

gets(info.var.rua);

printf("Numero da Casa: ");

scanf("%d", &(info.var.numero_da_casa));

printf("\n\nDados:\n");

printf("Nome: %s", info.nome);

printf("\nEndereco: %s %d", info.var.rua,

info.var.numero_da_casa);

_getch(); }

Page 53: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

53

Capítulo 8 – Entrada/Saída pelo Console

Input/Output traduzindo teríamos Entrada/Saída, mais conhecido como simplesmente I/O ou E/S, são funções como o próprio nome já indica, funções de entrada e saída. O arquivo de cabeçalho dessas funções é STDIO.H, que quer dizer standard input/output. Podemos ter funções de I/O de diversas formas, como através do console, ou através de arquivos. Primeiramente vamos fala de I/O através do console. As funções I/O mais simples são getchar(), que lê um caractere do teclado, e putchar() que escreve um caractere na tela. Ambas retornam EOF se ocorrer algum erro. Vejamos um exemplo de função que lê caracteres do teclado e inverte a caixa deles, isto é, escreve maiúsculas como minúsculas ou vice-versa. Contudo existem alguns problemas com o getchar(), como a versão de original de C era compatível com o UNIX, getchar() armazena em um buffer a entrada até que seja pressionado ENTER. Assim temos as funções getch() e getche(), que pertencem a biblioteca CONIO.H, embora elas não sejam definidas pelo padrão ANSI, elas são mais recomendadas. A função getch() espera até que uma tecla seja pressionada, e retorna imediatamente, e não mostra o caractere na tela. A getche() é igual a getch(), mas a tecla é mostrada. Para ler e escrever strings temos as funções gets() e puts(), assim como temos as funções printf() e scanf(). A diferença básica é que uma chamada a gets() ou puts() requer bem menos tempo que printf() e scanf() porque elas aceitam apenas strings de caracteres, não se pode escrever números ou fazer conversões de formato. Por essa razão gets() e puts() são mais utilizadas quando o mais importante é possuir um código altamente otimizado. Sendo assim vamos tratar apenas das funções printf() e scanf().

#include <stdio.h>

#include <conio.h>

#include <ctype.h> //necessário para as funções islower,

//toupper, tolower

void main()

{

char ch;

printf("Digite uma frase (digite ponto para sair). \n");

do

{

ch = getch();

if (islower(ch))

ch = toupper(ch);

else

ch = tolower(ch);

putchar(ch);

}while (ch != '.'); }

Page 54: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

54

Função: printf() O protótipo para printf() é int printf(char *string_de_controle, lista_de_argumentos); string_de_controle é constituída, basicamente, de dois tipos de itens. O primeiro tipo é formado pelos caracteres que serão impressos na tela. O segundo contém comandos de formato que definem a maneira como os argumentos subseqüentes serão mostrados, veja exemplo: printf(“Estou %s linguagem %c”, “aprendendo”, ‘C’); Será impresso na tela: Estou aprendendo linguagem C Um comando de formato é constituído do símbolo porcentagem (%) seguido pelo código de formato, exemplos: %c ; %s ; %i (veja tabela abaixo). Deve haver o mesmo número de argumentos e de comandos de formato e estes dois são combinados na ordem, da esquerda para a direita.

Código Formato %c Caractere %d Inteiros decimais com sinal %i Inteiros decimais com sinal %e Notação científica (e minúsculo) %E Notação científica (E maiúsculo) %f Ponto flutuante decimal %g Usa %e ou %f, o que for mais curto %G Usa %E ou %F, o que for mais curto %o Octa sem sinal %s String de caracteres %u Inteiros decimais sem sinal %x Hexadecimal sem sinal (letras minúsculas) %X Hexadecimal sem sinal (letras maiúsculas) %p Apresenta um ponteiro

%n Ponteiro utilizado para inteiro no qual o número de caracteres escritos

até esse ponto é colocado %% Escreve o símbolo %

Para se escrever caracteres se utiliza %c, %s para strings, %d ou %i para indicar decimal com sinal, %d e %i são equivalentes, %f representa números em ponto flutuante, %e ou %E indicam que se deve mostrar um double em notação cientifica, o %g ou %G faz com que o printf() decida utilizar %f ou %e, assim selecionando o de saída mais curta. Exemplo de utilização de printf() : char s[20];

gets(s);

printf(“Eu gosto de %s .”, s);

Page 55: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

55

Especificadores de largura mínima de campo O número colocado entre o símbolo % e o código do formato age como especificador de largura mínima de campo. Se a string, ou número, for maior do que o mínimo, este será escrito por inteiro, caso contrário ele preenche a saída com espaços. Ao invés de espaços, se quiser completar com zeros deve se colocar um zero antes do especificador da largura mínima. Por exemplo %05d preencherá um número de menos de cinco dígitos com zeros de forma que seu comprimento total seja 5. Veja o exemplo abaixo: Este programa produz o seguinte resultado:

Especificador de precisão O especificador de precisão segue o especificador de largura mínima de campo (se houver algum), consistindo em um ponto seguido de um inteiro. O seu significado exato depende do tipo de dado a que está sendo aplicado. Especificador de precisão aplicado a um ponto flutuante determina o número de casas decimais a ser mostrado. Por exemplo, %10.4f mostra um número com pelo menos 10 caracteres e 4 casas decimais. Aplicado a %g ou %G ele determina a quantidade de dígitos significativos a serem impressos na tela. Aplicado a tipos inteiros (int), o especificador de precisão adiciona zeros iniciais para completar o número solicitado de dígitos. Aplicados a strings ele determina o comprimento máximo do campo. Por exemplo, %5.7s ele irá mostrar uma string de no mínimo 5 e no máximo 7 caracteres.

#include <stdio.h>

#include <conio.h>

void main()

{

double item = 23.435479;

printf("%f", item);

printf("\n%03f", item);

printf("\n%012f", item);

printf("\n%12f", item);

_getch(); }

Page 56: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

56

Função: scanf() Muito parecida com printf(), seria como seu inverso, scanf() é a rotina de entrada pelo console de uso geral. Ela pode ler todos os tipos de dados intrínsecos e converte automaticamente números ao formato interno apropriado. Seu protótipo é: int printf(char *string_de_controle, lista_de_argumentos); A função scanf() devolve o número de itens de dados que foi atribuído, com êxito, a um valor. Se houver um erro, esta função devolve EOF. A string_de_controle determina como os valores são lidos para as variáveis apontadas na lista_de_argumentos. Veja a tabela de especificadores de formato para scanf().

Código Significado %c Lê um único caractere %d Lê inteiro decimal %i Lê inteiro decimal %e Lê um número em ponto flutuante %f Lê um número em ponto flutuante %g Lê um número em ponto flutuante %o Lê um octal %s Lê uma string %x Lê um número hexadecimal %p Lê um ponteiro %n Recebe um valor igual ao número de caracteres lidos até então %u Lê um inteiro sem sinal % Busca por um conjunto de caracteres

Exemplo de uma utilização do scanf(): char ch, string[80];

float f;

int i, *ptr;

scanf(“%d %d”, &i, ptr);

scanf(“%f”, &f);

scanf(“%c %s”, &ch, string);

Todas as variáveis utilizadas para receber valores através de scanf() devem ser passadas pelos seus endereços. Isso significa que todos os argumentos devem ser ponteiros para as variáveis usadas como argumentos. Essa é a maneira de C criar uma chamada por referência e que permite a uma função alterar o conteúdo de um argumento.

Page 57: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

57

Quando é feita a leitura de um número, a função scanf() termina a leitura de um número quando o primeiro caractere não numérico é encontrado. Já, quando é feita a leitura de strings, esta função lê os caracteres até que seja encontrado um caractere de espaço em branco. Descartando espaços em branco indesejados

Descartar espaços em brancos, nada mais é do que um caractere de espaço em branco na string de controle. Esse espaço em branco faz com que o scanf(), salte um ou mais caracteres, de espaço em branco da stream de entrada, até que seja encontrado o primeiro caractere de espaço não-branco. Exemplo:

string de entrada => “aulas MTP” char entrada1[15], entrada2[15];

scanf(“%s %s”, &entrada, &entrada);

Nesse caso a string entrada1 será aulas e a entrada2 será MTP. Descartando caracteres de espaço não-branco Um caractere não-branco na string de controle faz com que scanf() leia e ignore caracteres iguais na stream de entrada. Se o caractere especificado não for encontrado scanf() termina. Exemplo: sting de entrada => “10,30” int x, y;

scanf(“%d,%d”, &x, &y);

Nesse caso x = 10 e y = 30.

Page 58: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

58

Capítulo 9 – Entrada/Saída com Arquivo A linguagem C não contém nenhum comando E/S (entrada e saída). Ao contrário, todas as operações de E/S ocorrem através de chamadas de funções da biblioteca C padrão. Essa abordagem faz o sistema de arquivos de C extremamente poderoso e flexível.

Streams e Arquivos O sistema de entrada e saída de C fornece uma interface consistente ao programador C, independente do dispositivo real que é acessado, ou seja, esse sistema provê um nível de abstração entre o programador e o dispositivo utilizado. Esta abstração é chamada de stream, enquanto que o dispositivo real é chamado de arquivo. Streams O sistema de arquivos pode trabalhar com uma grande variedade de dispositivos, acionadores de disco, terminais, entre outros. Embora cada um dos dispositivos seja bastante diferente entre si, o sistema de arquivo com buffer transforma-os em um dispositivo lógico chamado de stream, onde todas as stream se comportam de forma semelhante. Existem dois tipos de streams: texto e binária. Uma stream de texto é uma seqüência de caracteres. O padrão ANSI permite (mas não exige) que uma stream de texto seja organizada em linhas e terminada por um caractere de nova linha. Porém, o caractere de nova linha. Porém, o caractere de nova linha é opcional na última linha e é determinado pela implementação. Uma stream binária é uma seqüência de bytes com uma correspondência de um para um com aqueles encontrados no dispositivo externo, ou seja, não ocorre nenhuma tradução de caracteres. Além disso, o número de bytes escritos (ou lidos) é o mesmo que o encontrado no dispositivo externo. Arquivos Para linguagem C, arquivo pode ser qualquer coisa, como um terminal, uma impressora, ou um arquivo salvo no disco rígido. Para associar uma stream a um arquivo deve-se realizar uma operação de abertura. Como já foi dito, todas as streams em C são iguais, porém os arquivos possuem diferenças. Por exemplo, um disco rígido pode suportar acesso aleatório, enquanto que um teclado não pode. Uma vez que, uma stream estiver associada a um arquivo e você não precisar mais trocar informações entre seu programa e o arquivo, você realizar uma operação de fechamento. Se um arquivo aberto para saída for fechado, o conteúdo, se houver algum, de sua stream associada é escrito no dispositivo externo. Esse processo é geralmente referido como descarga (flushing) da stream e garante que nenhuma informação seja acidentalmente deixada no buffer de disco. Quando o programa termina, normalmente com o main() retornando ao sistema operacional ou com um exit(), sendo que, os

Page 59: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

59

arquivos são fechados automaticamente. Sendo que, eles não são fechados quando um programa quebra (crash). Uma stream associada a um arquivo tem uma estrutura de controle de arquivo do tipo FILE. Essa estrutura é definida no cabeçalho STDIO.H.

Sistema de Arquivos Ponteiro de Arquivo Um ponteiro de arquivo é um ponteiro para informações que definem coisas sobre o arquivo, incluindo seu nome, status e a posição atual do arquivo. Um ponteiro de arquivo é uma variável do tipo FILE. Para ler ou escrever em um arquivo, seu programa precisa de um ponteiro de arquivo. Para declarar um ponteiro de arquivo, use o seguinte comando: FILE *fp;

Abrindo um Arquivo Para fins práticos, consideraremos sempre o arquivo neste volume como sendo um arquivo em disco, desconsiderando periféricos, portas externas, entre outros.

Para abrir um arquivo, você deve utilizar a função fopen(), que abre uma stream para uso e associa um arquivo a ela. Para abrir um arquivo utilize a seguinte comando: FILE *fp; fp = fopen(“nome_do_arquivo”, “modo”);

onde nome_do_arquivo é um nome válido para o arquivo que você quiser criar. Por exemplo, se você quiser cadastrar alunos e salvar em um arquivo um nome válido poderia ser Alunos. Ao dar um nome ao arquivo, você pode incluir uma especificação de caminho. E modo determina como o arquivo será aberto. Veja abaixo os modos mais utilizados: Modo “r”: Este modo abre um arquivo texto para leitura. Se este arquivo não existir ou não for encontrado, fopen() retorna um erro. Modo “w”: Este modo abre um arquivo texto vazio para escrita. Se este arquivo não existir, ele será criado, porém se ele existe, este será destruído para criação de um arquivo vazio para escrita, assim seu conteúdo será perdido. Modo “a”: Este modo abre um arquivo texto para escrita no final deste, ou seja, em forma de apêndice. Explicando melhor, todo arquivo quando criado possui no final dele um marcador EOF (end of file, ou final de arquivo); se o arquivo não existir ele será criado para escrita, porém se ele existir, ao contrário do modo “w” seu conteúdo não será destruído, o marcador EOF será removido para que continue escrevendo nele. Modo “rb”: Análogo ao modo “r”, só que ele abre um arquivo binário para leitura. Modo “wb”: Análogo ao modo “w”, só que ele abre um arquivo binário para escrita.

Page 60: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

60

Modo “ab”: Análogo ao modo “a”, só que ele abre um arquivo binário para apêndice. Modo “r+”: Este modo abre um arquivo para leitura e escrita. Este arquivo deve existir, senão fopen() retorna um erro. Para escrever neste modo, ao contrário do apêndice, o indicador de posição não se encontra no final do arquivo, e sim no começo. Modo “w+”: Este modo é análogo ao “w”. Porém ele realiza a leitura e escrita do arquivo. Modo “a+”: Este modo é análogo ao “a”. Porém ele realiza a leitura e apêndice. Modo “rb+”: Análogo ao modo “r+”, só que ele abre um arquivo binário para leitura e escrita. Modo “wb+”: Análogo ao modo “w+”, só que ele abre um arquivo binário para leitura e escrita. Modo “ab+”: Análogo ao modo “a+”, só que ele abre um arquivo binário para leitura e apêndice.

A função fopen() devolve um ponteiro de arquivo. Seu programa nunca deve alterar o valor deste ponteiro. Se ocorrer um erro quando estiver tentando abrir um arquivo, fopen() devolve um ponteiro nulo. Para abrir um arquivo texto chamado teste para escrita, escreva desta forma: O comando if utilizado faz a verificação de erro da abertura do arquivo. Fechando um Arquivo Para fechar uma stream que foi aberta através de uma chamada à fopen() utilize a função fclose(). Ela escreve qualquer dado que ainda permanece no buffer de disco no arquivo e, então, fecha normalmente o arquivo em nível de sistema operacional. Um fclose() também libera o bloco de controle de arquivo associado à stream, deixando-o disponível para reutilização. Por exemplo, para fechar um arquivo como o do exemplo anterior utilize a seguinte notação: fclose(fp);

onde fp é o ponteiro de arquivo devolvido pela chamada à fopen().

FILE *fp;

fp = fopen(“teste”, “w”);

if(!fp)

{

printf(“Erro na abertura de arquivo”);

_getch();

exit(1); }

Page 61: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

61

Funções para Arquivos O sistema de arquivos ANSI contém diversas funções inter-relacionadas. Veja a tabela abaixo: Nome Função fopen() Abre um arquivo fclose() Fecha um arquivo putc() Escreve um caractere em um arquivo fputc() O mesmo que putc() getc() Lê um caractere de um arquivo fgetc() O mesmo que getc() fseek() Posiciona o arquivo em um byte especifico fprintf() É para um arquivo o que printf() é para o console fscanf() É para um arquivo o que scanf() é para o console feof() Devolve verdadeiro se o fim de arquivo for atingido ferror() Devolve verdadeiro se houve um erro rewind() Reposiciona o indicador de posição de arquivo no início do mesmo remove() Apaga um arquivo fflush() Descarrega um arquivo

Funções: putc() e fputc() Estas duas funções são equivalentes, elas escrevem caracteres no arquivo previamente aberto para escrita através da função fopen(). Os protótipos para essas funções são int putc(int ch, FILE *fp);

int fputc(int ch, FILE *fp);

onde fp é um ponteiro de arquivo devolvido por fopen() e ch é o caractere a ser escrito. Por razões históricas ch é definido como int, mas ch é um caractere, veja o exemplo abaixo:

#include <conio.h>

#include <stdio.h>

void main()

{

char ch1 = 'o';

FILE *fp;

fp = fopen("arquivo.txt","w");

if(!fp)

{

printf("Erro na abertura do arquivo");

_getch();

exit(1);

}

putc(ch1, fp);

putc('l', fp);

fputc('a', fp);

fclose(fp);

_getch();

}

Page 62: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

62

Este código cria um arquivo chamado arquivo do tipo texto (extensão .txt) e escreve nele a palavra “ola”, caractere por caractere, exemplificando as diversas formas como pode ser inserido um caractere em um arquivo. Funções: getc() e fgetc() Estas duas funções são equivalentes, elas lêem caracteres no arquivo previamente aberto para leitura através da função fopen(). O protótipo para essa função é int getc(FILE *fp);

int fgetc(FILE *fp);

onde fp é um ponteiro de arquivo devolvido por fopen(). Veja o exemplo abaixo, este programa lê o arquivo escrito pelo exemplo anterior e imprime na tela. O laço do-while é repetido enquanto ch não atingir o final do arquivo. Função: feof() Quando um arquivo é aberto para entrada binária, um valor inteiro igual à marca de EOF pode ser lido. Isso poderia fazer com que a rotina de entrada indicasse uma condição de fim de arquivo apesar do final físico do arquivo não tiver sido de fato alcançado. Como solução deste problema temos a função feof(), que determina quando o final de arquivo foi atingido na leitura de dados binários. Seu protótipo é int feof(FILE *fp);

#include <conio.h>

#include <stdio.h>

void main()

{

char ch;

FILE *fp;

fp = fopen("arquivo.txt","r");

if(!fp)

{

printf("Erro na abertura do arquivo");

_getch();

exit(1);

}

do{

ch = getc(fp);

printf("%c", ch);

}while(ch!=EOF);

fclose(fp);

_getch(); }

Page 63: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

63

Esta função devolve verdadeiro se o final do arquivo for atingido; caso contrário, devolve falso (0). O seguinte código lê um arquivo binário até que o final do arquivo seja encontrado. while(!feof(fp))

ch = getc(fp);

Você pode utilizar esse método tanto para arquivo texto quanto binários. Veja o exemplo a seguir, que é análogo ao da seção anterior, porém utilizando o novo conceito de feof(). Funções: fputs() e fgets() As funções fputs() e fgets() efetuam, respectivamente, operações de escrita e leitura de strings de um arquivo em disco. Elas funcionam de maneira semelhante a putc() e getc(), mas, ao invés de escrever ou ler um único caractere, elas operam com strings. Seus protótipos são: int fputs(const char *str, FILE *fp);

char *fgets(char *str, int length, FILE *fp);

A função fgets() lê uma string da stream especificada até que um caractere de nova linha seja lido ou que length-1 caracteres tenham sido lidos.

#include <conio.h>

#include <stdio.h>

void main()

{

char ch;

FILE *fp;

fp = fopen("arquivo.txt","r");

if(!fp)

{

printf("Erro na abertura do arquivo");

_getch();

exit(1);

}

while(!feof(fp))

{

ch = getc(fp);

printf("%c", ch);

}

fclose(fp);

_getch(); }

Page 64: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

64

Função: rewind() A função rewind() reposiciona o indicador de posição de arquivo no início do arquivo especificado, ou seja, ela “rebobina” o arquivo. Seu protótipo é void rewind(FILE *fp);

Veja o exemplo abaixo, você entra com strings, que é gravado no arquivo, para encerrar basta você teclar ENTER no momento em que se pede uma string. Logo depois o arquivo é rebobinado, e é feito a leitura dele imprimindo as suas strings na tela.

#include <conio.h>

#include <stdio.h>

#include <string.h>

void main()

{

char str[80];

FILE *fp;

fp = fopen("arquivo.txt","w+");

if(!fp)

{

printf("Erro na abertura do arquivo");

_getch();

exit(1);

}

do{

printf("Entre com string (tecle ENTER para sair):\n");

gets(str);

strcat(str, "\n");

fputs(str, fp);

}while(*str!='\n');

rewind(fp);

system("cls");

while(!feof(fp))

{

fgets(str, 79, fp);

printf(str);

}

fclose(fp);

_getch(); }

Page 65: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

65

Função: remove() Para apagar um arquivo basta utilizar o seguinte código: remove(fp);

Ele devolve zero caso seja bem-sucedido e um valor diferente de zero caso contrário. Função: fflush() A função fflush() é utilizada para esvaziar o conteúdo de uma stream de saída. Para utilizar esta função utilize o seguinte código: fflush(fp);

Essa função escreve o conteúdo de qualquer dado existente no buffer arquivo associado a fp. Se fflush() for chamada com um valor nulo, todos os arquivos abertos para saída são descarregados. Esta função devolve 0 para indicar sucesso; caso contrário, devolve EOF. Funções: fread() e fwrite() O sistema de arquivo ANSI C fornece duas funções para leitura e escrita de blocos de qualquer tipo de dado, elas são fread() e fwrite(). Seus protótipos são:

size_t fread(void *buffer, size_t num_bytes, size_t count, FILE*fp); size_t fwrite(const void *buffer, size_t num_bytes, size_t count, FILE *fp);

Para fread(), buffer é um ponteiro para uma região de memória que receberá os dados do arquivo. Para fwrite(), buffer é um ponteiro para as informações que serão escritas no arquivos. O número de bytes a ler ou escrever é especificado por num_bytes. O argumento count determina quantos itens (cada um de comprimento num_byte) serão lidos ou escritos. Finalmente, fp é um ponteiro para uma stream aberta anteriormente. A função fread() devolve o número de itens lidos. Esse valor pode ser menor que count se o final do arquivo for atingido, ou ocorrer um erro. A função fwrite() devolve o número de itens escritos. Esse valor será igual a count a menos que ocorra um erro. Aparentemente, fread() e fwrite() denotam certa complexidade, mas com a prática notará suas vantagens e que é apenas questão aparência, sendo elas funções extremamente importantes e largamente utilizadas já que seu tempo de leitura e escrita em um arquivo são menores comparados a outras funções. Veja o exemplo abaixo, o programa grava em um arquivo binário usando fwrite() e logo depois o lê utilizando fread():

Page 66: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

66

Pegamos uma linha de código para discussão: fwrite(&i, sizeof(int), 1, fp); Isso quer dizer que será gravado uma vez (pois count é 1) a informação contida na variável i, que possui o tamanho de um arquivo do tipo int, no arquivo fp. Sabemos que int possui 2 bytes de tamanho, então o seguinte código seria perfeitamente aceitável: fwrite(&i, 2, 1, fp); Note também que nosso arquivo é binário, portanto ele foi nomeado com a extensão .bin, assim como texto é .txt. O programa anterior é perfeitamente válido, ele grava cada dado individualmente, mas a vantagem do fwrite() e fread() se concentra especialmente em gravar um bloco de dados contendo vários bytes com apenas uma linha de código. Veja o exemplo abaixo, onde é feito o cadastro de um usuário utilizando uma estrutura, escrevendo e lendo seus dados a partir de um arquivo:

#include <conio.h>

#include <stdio.h>

void main()

{

int i = 10;

float f = 21.45;

char ch = 'a';

long l = 8089514;

FILE *fp;

fp = fopen("arquivo.bin","wb+");

if(!fp)

{

printf("Erro na abertura do arquivo");

_getch();

exit(1);

}

fwrite(&i, sizeof(int), 1, fp);

fwrite(&f, sizeof(float), 1, fp);

fwrite(&ch, sizeof(char), 1, fp);

fwrite(&l, sizeof(long), 1, fp);

rewind(fp);

fread(&i, sizeof(int), 1, fp);

fread(&f, sizeof(float), 1, fp);

fread(&ch, sizeof(char), 1, fp);

fread(&l, sizeof(long), 1, fp);

printf("%d\n %f\n %c\n %d", i, f, ch, l);

fclose(fp);

_getch(); }

Page 67: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

67

#include <conio.h>

#include <stdio.h>

struct usuario{

char nome[80];

int idade;

float peso;

float altura;

char endereco[81];

char telefone[20];

} var;

void main()

{

FILE *fp;

fp = fopen("arquivo.bin","wb+");

if(!fp){

printf("Erro na abertura do arquivo");

_getch();

exit(1);

}

printf("Nome: ");

gets(var.nome);

printf("\nEndereco: ");

gets(var.endereco);

printf("\nTelefone: ");

gets(var.telefone);

printf("\nIdade: ");

scanf("%d", &var.idade);

printf("\nPeso: ");

scanf("%f", &var.peso);

printf("\nAltura: ");

scanf("%f", &var.altura);

if(fwrite(&var, sizeof(struct usuario), 1, fp)!=1){

printf("Erro na gravacao do arquivo");

_getch();

exit(1);

}

rewind(fp);

if(fread(&var, sizeof(struct usuario), 1, fp)!=1){

printf("Erro na leitura do arquivo");

_getch();

exit(1);

}

system("cls");

printf("Informacoes do Usuario");

printf("\n\nNome: ");

printf("%s", var.nome);

printf("\nEndereco: ");

printf("%s", var.endereco);

printf("\nTelefone: ");

printf("%s", var.telefone);

printf("\nIdade: ");

printf("%d", var.idade);

printf("\nPeso: ");

printf("%f", var.peso);

printf("\nAltura: ");

printf("%f", var.altura);

fclose(fp);

_getch(); }

Page 68: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

68

O código acima é um bom exemplo de como o fwrite() e o fread() são utilizados, com apenas um fwrite() pode-se escrever no arquivo todo um bloco de dados, que neste caso é a estrutura struct usuario. Analgamente, com apenas um fread() pode-se ler todo um bloco de dados. É importante fazer uma verificação de erro sempre que escrever ou ler um arquivo, pois deixa seu programa menos suscetível a bugs, e se houver realmente um erro, a identificação deste se torna mais fácil. A verificação de erro é exemplificada no código acima, utilizando o seguinte comando: if(fwrite(&var, sizeof(struct usuario), 1, fp)!=1){ bloco }

Para entender este comando, lembremos que fwrite() retorna a quantidade de itens escritos, como o count foi definido com 1, fwrite() deverá retornar 1 se for bem-sucedida a escrita no arquivo. Mas se for diferente de 1, o bloco será executado e o programa será encerrado. Esta lógica é análoga ao fread(). Função: fseek() A função fseek() move o indicador de posição de arquivo para um local específico definido pelo programador. Seu protótipo é: int fseek(FILE *fp, long numbytes, int origin);

Aqui, fp é um ponteiro de arquivo devolvido por uma chamada à fopen(). numbytes, um inteiro longo, é o numero de bytes a partir de origin, que ser tornará a nova posição corrente.

Origin Nome da Macro Início do arquivo SEEK_SET

Posição atual SEEK_CUR

Final do arquivo SEEK_END

Portanto, para se mover numbytes a partir do início do arquivo, origin deve SEEK_SET. Para se mover da posição atual, deve-se utilizar SEEK_CUR e para se mover a partir do final do arquivo, deve-se utilizar SEEK_END. A função fseek() devolve 0 quando vem-sucedida e um valor diferente de zero se ocorrer um erro. Imaginemos o exemplo da seção anterior, onde é feito o cadastro de usuários. Imaginemos que tenhamos um arquivo com 10 usuários cadastrados e queremos ler a informação do décimo, portanto podemos o utilizar o seguinte comando para pular os 9 primeiros usuários e colocarmos o indicador de posição corrente onde desejamos: fseek(fp, 9*sizeof(struct usuario), SEEK_SET);

Com isso podemos perceber que é possível efetuar movimentações em múltiplos de qualquer tipo de dado simplesmente multiplicando o tamanho dos dados pelo número do item que deseja ser alcançado.

Page 69: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

69

Funções: fprintf() e fscanf() O sistema E/S em C inclui duas funções para leitura e escrita em arquivos, elas são fprintf() e fscanf(). Elas se comportam exatamente como printf() e scanf() exceto por operarem com arquivos. Os seus protótipos são

int fprintf(FILE *fp, const char *control_string,…);

int fscanf(FILE *fp, const char *control_string,…);

onde fp é um ponteiro de arquivo devolvido por uma chamada à fopen(). fprintf() e fscanf() direcionam suas operações de E/S para o arquivo apontado por fp. Veja o exemplo abaixo, o programa lê uma string do teclado e os escreve no arquivo, logo em seguida lê do arquivo e imprime na tela: Embora as funções fprintf() e fscanf() geralmente sejam a maneira mais fácil de escrever e ler dados diversos em arquivos em disco, elas não são sempre a escolha mais apropriada. Como os dados são escritos em ASCII e formatados como apareceriam na tela (e não em binário), um tempo extra é perdido a cada chamada. Assim, se há preocupação com velocidade ou tamanho do arquivo deve-se utilizar fread() e fwrite().

#include <conio.h>

#include <stdio.h>

void main()

{

char string[80];

FILE *fp;

fp = fopen("arquivo.txt","w+");

if(!fp)

{

printf("Erro na abertura do

arquivo");

_getch();

exit(1);

}

printf("Entre com uma string: ");

fscanf(stdin, "%s", string);

fprintf(fp, "%s", string);

rewind(fp);

fscanf(fp, "%s", string);

fprintf(stdout, "%s", string);

fclose(fp);

_getch();

}

Page 70: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

70

Capítulo 10 – Interface Gráfica com OpenGL

O objetivo desta aula aprender a utilizar a biblioteca OpenGL. Para isto, serão utilizadas as bibliotecas OpenGL, GLU e GLUT, de maneira que o programa fique portável e possa ser executado tanto no ambiente Windows como no ambiente Linux.

Para implementação de aplicações OpenGL na linguagem de programação C/C++ é necessário criar um projeto para linkar as bibliotecas. A maior vantagem na sua utilização é a rapidez. OpenGL não é uma linguagem de programação, é uma poderosa e sofisticada API (Application Programming Interface) para criação de aplicações gráficas 2D e 3D. A API inclui aproximadamente 250 comandos e funções. Não existe um formato de arquivo OpenGL para modelos ou ambientes virtuais. OpenGL fornece um pequeno conjunto de primitivas gráficas para construção de modelos, tais como pontos, linhas e polígonos.

Características Principais

� Projetada para aplicações gráficas interativas 2D e 3D

� Derivada de GL (Graphics Library – SGI)

� Permite criar programas interativos que produzem imagens coloridas de

objetos em movimento

� Independente do sistema operacional

Aspectos Técnicos

� As primitivas são vértices e imagens

� Não gerencia eventos de controle (mouse, exibição, teclado, etc)

Tipos de Dados

Tipo de dado OpenGL

Representação interna

Tipo de dado C equivalente

Sufixo

GLbyte 8-bit integer signed char b

GLshort 16-bit integer short s

GLint, GLsizei 32-bit integer int ou long i

GLfloat, GLclampf 32-bit floating-point float f

GLdouble, GLclampd

64-bit floating-point double d

GLU (Graphics Utility Library)

� Conjunto de rotinas utilizadas freqüentemente

� Construídas a partir de comandos OpenGL

� Rotinas para:

Page 71: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

71

� Manipulação de projeções

� Desenho de superfícies quádricas

� Curvas e superfícies NURBS (representações geométricas complexas –

Spline)

� Manipulação de superfícies poligonais

Convenções para os Nomes das Funções

<PrefixoBiblioteca><ComandoRaiz><ContadorArgumentosOpcional>

<TipoArgumentosOpcional>

Bibliotecas

• GL – OpenGL: Contém as funções padrões do OpenGl, definidas pelo OpenGL Architeture Review Board.

• GLU - OpenGL Utility Library: contém várias rotinas que utilizam os comandos OpenGL de baixo nível para executar tarefas como, por exemplo, definir as matrizes para projeção e orientação da visualização, e fazer o rendering de uma superfície. Esta biblioteca é fornecida como parte de cada implementação de OpenGL, e suas funções usam o prefixo glu.

• GLUT - OpenGL Utility Toolkit: é um toolkit independente de plataforma, que inclui alguns elementos GUI (Graphical User Interface), tais como menus pop-up e suporte para joystick. Esta biblioteca, escrita por Mark Kilgard, não é domínio público, mas é free. O seu principal objetivo é esconder a complexidade das APIs dos diferentes sistemas de janelas. O seu principal objetivo é esconder a complexidade das APIs dos diferentes sistemas de janelas. As funções desta biblioteca usam o prefixo glut. É interessante comentar que a GLUT substitiu a GLAUX, uma biblioteca auxiliar OpenGL que havia sido criada para facilitar o aprendizado e a elaboração de programas OpenGL independente do ambiente de programação (Linux, Windows, etc.).

• GLAUX – OpenGL Auxiliar Contém os comandos da chamada auxiliar. Permitem desenvolver aplicações simples, independente de plataforma e sistema operacional.

• GLX - OpenGL Extension to the X Window System: fornecido como um "anexo" de OpenGL para máquinas que usam o X Window System. Funções GLX usam o prefixo glX. Para Microsoft Windows 95/98/NT, as funções WGL fornecem as janelas para a interface OpenGL. Todas as funções WGL usam o prefixo wgl. Para IBM/OS2, a PGL é a Presentation Manager para a interface OpenGL, e suas funções usam o prefixo pgl. Para Apple, a AGL é a interface para sistemas que suportam OpenGL, e as funções AGL usam o prefixo agl.

• FSG - Fahrenheit Scene Graph: é um toolkit orientado à objetos e baseado em OpenGL, que fornece objetos e métodos para a criação de aplicações gráficas 3D interativas. FSG, que foi escrito em C++ e é separado de OpenGL, fornece componentes de alto nível para criação e edição de cenas 3D, e a habilidade de trocar dados em outros formatos gráficos.

Primeiro Programa

Page 72: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

72

Estrutura geral de um Programa OpenGL

Podemos imaginar um programa OpenGL como dividido em várias seções. Cada seção é representada por um conjunto de funções, invocadas ou do programa principal ou de outra seção.

#include <gl/glut.h> // Função callback chamada para fazer o desenho void Desenha(void) { //Limpa a janela de visualização com a cor de fundo especificada glClear(GL_COLOR_BUFFER_BIT); //Executa os comandos OpenGL glFlush(); } // Inicializa parâmetros de rendering void Inicializa (void) { // Define a cor de fundo da janela de visualização como preta glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } // Programa Principal int main(void) { glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutCreateWindow("Primeiro Programa"); glutDisplayFunc(Desenha); Inicializa(); glutMainLoop(); }

O Código acima vai gerar esta janela.

Agora vamos explicar passo a passo cada comando utilizado. glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);

Page 73: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

73

Avisa a GLUT que tipo de modo de exibição deve ser usado quando a janela é criada. Neste caso os argumentos indicam a criação de uma janela single-buffered (GLUT_SINGLE) com o modo de cores RGBA (GLUT_RGB). O primeiro significa que todos os comandos de desenho são feitos na janela de exibição. glutCreateWindow("Primeiro Programa"); É o comando da biblioteca GLUT que cria a janela. Neste caso, é criada uma janela com o nome "Primeiro Programa". Este argumento corresponde a legenda para a barra de título da janela.

glutDisplayFunc(Desenha); Estabelece a função "Desenha" previamente definida como a função callback de exibição. Isto significa que a GLUT chama a função sempre que a janela precisar ser redesenhada. Esta chamada ocorre, por exemplo, quando a janela é redimensionada ou encoberta; glClearColor(0.0f, 0.0f, 1.0f, 1.0f);

É a função que determina a cor utilizada para limpar a janela. Seu protótipo é: void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alfa);. GLclampf é definido como um float na maioria das implementações de OpenGL. O intervalo para cada componente red, green, blue é de 0 a 1. O componente alfa é usado para efeitos especiais, tal como transparência.

glClear(GL_COLOR_BUFFER_BIT); "Limpa" um buffer particular ou combinações de buffers, onde buffer é uma área de armazenamento para informações da imagem. Os componentes RGB são geralmente referenciados como color buffer ou pixel buffer. Existem vários tipos de buffer, mas por enquanto só é necessário entender que o color buffer é onde a imagem é armazenada internamente e limpar o buffer com glClear remove o desenho da janela. glFlush(); Faz com que qualquer comando OpenGL não executado seja executado. Neste primeiro exemplo tem apenas a função glClear.

Segundo Programa Antes de entrar no exemplo 2 vamos conhecer mais algumas funções que possibilitaram o desenho no OpenGL.

Linhas, Pontos e Polígonos

Page 74: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

74

Com apenas algumas primitivas simples, tais como pontos, linhas e polígonos, é possível criar estruturas complexas. Em outras palavras, objetos e cenas criadas com OpenGL consistem em simples primitivas gráficas que podem ser combinadas de várias maneiras. Três Pontos

glBegin(GL_POINTS); glColor3f(0.0f, 0.0f, 0.0f); glVertex2i(100, 50); glVertex2i(100, 130); glVertex2i(150, 130); glEnd(); Para desenhar outras primitivas, basta trocar GL_POINTS, que exibe um ponto para cada chamada ao comando glVertex, por:

� GL_LINES: exibe uma linha a cada dois comandos glVertex; � GL_LINE_STRIP: exibe uma seqüência de linhas conectando os pontos

definidos por glVertex; � GL_LINE_LOOP: exibe uma seqüência de linhas conectando os pontos

definidos por glVertex e ao final liga o primeiro como último ponto; � GL_POLYGON: exibe um polígono convexo preenchido, definido por uma

seqüência de chamadas a glVertex; � GL_TRIANGLES: exibe um triângulo preenchido a cada três pontos definidos

por glVertex; � GL_TRIANGLE_STRIP: exibe uma seqüência de triângulos baseados no trio

de vértices v0, v1, v2, depois, v2, v1, v3, depois, v2, v3, v4 e assim por diante; � GL_TRIANGLE_FAN: exibe uma seqüência de triângulos conectados

baseados no trio de vértices v0, v1, v2, depois, v0, v2, v3, depois, v0, v3, v4 e assim por diante;

� GL_QUADS: exibe um quadrado preenchido conectando cada quatro pontos definidos por glVertex;

� GL_QUAD_STRIP: exibe uma seqüência de quadriláteros conectados a cada quatro vértices; primeiro v0, v1, v3, v2, depois, v2, v3, v5, v4, depois, v4, v5, v7, v6, e assim por diante

Função que desenha um Quadrado

void Desenha(void)

Page 75: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

75

{ glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0f, 0.0f, 0.0f); glTranslatef(-100.0f, -30.0f, 0.0f); glScalef(1.5f, 0.5f, 1.0f); glBegin(GL_QUADS); glVertex2i(100,150); glVertex2i(100,100); // Especifica que a cor corrente é azul glColor3f(0.0f, 0.0f, 1.0f); glVertex2i(150,100); glVertex2i(150,150); glEnd(); glFlush(); }

Agora sim vamos Fazer nosso Segundo Programa #include <windows.h> #include "glut/glut.h" // Função callback chamada para fazer o desenho void Desenha(void) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // Limpa a janela de visualização com a cor de fundo especificada glClear(GL_COLOR_BUFFER_BIT); // Especifica que a cor corrente é vermelha // R G B glColor3f(1.0f, 0.0f, 0.0f); // Desenha um quadrado preenchido com a cor corrente glBegin(GL_QUADS); glVertex2i(100,150); glVertex2i(100,100); // Especifica que a cor corrente é azul glColor3f(0.0f, 0.0f, 1.0f); glVertex2i(150,100); glVertex2i(150,150); glEnd(); // Executa os comandos OpenGL glFlush(); } // Inicializa parâmetros de rendering

Page 76: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

76

void Inicializa (void) { // Define a cor de fundo da janela de visualização como preta glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } // Função callback chamada quando o tamanho da janela é alterado void AlteraTamanhoJanela(GLsizei w, GLsizei h) { // Evita a divisao por zero if(h == 0) h = 1; // Especifica as dimensões da Viewport glViewport(0, 0, w, h); // Inicializa o sistema de coordenadas glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Estabelece a janela de seleção (left, right, bottom, top) if (w <= h) gluOrtho2D (0.0f, 250.0f, 0.0f, 250.0f*h/w); else gluOrtho2D (0.0f, 250.0f*w/h, 0.0f, 250.0f); } // Programa Principal int main(void) { glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(400,350); glutInitWindowPosition(10,10); glutCreateWindow("Quadrado"); glutDisplayFunc(Desenha); glutReshapeFunc(AlteraTamanhoJanela); Inicializa(); glutMainLoop(); } Novamente vamos explicar os novos comandos que surgiram em nosso Segundo

Exemplo

glutInitWindowSize(400,350);

Especifica o tamanho em pixels da janela GLUT.

glutInitWindowPosition(10,10); Especifica a localização inicial da janela GLUT, que neste caso é o canto superior esquerdo da tela do computador.

Page 77: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

77

glutReshapeFunc(AlteraTamanhoJanela);

Estabelece a função "AlteraTamanhoJanela" previamente definida como a função callback de alteração do tamanho da janela. Isto é, sempre que a janela é maximizada, minimizada, etc., a função "AlteraTamanhoJanela" é executada para reinicializar o sistema de coordenadas.

glColor3f(1.0f, 0.0f, 0.0f);

Determina a cor que será usada para o desenho (linhas e preenchimento). A seleção da cor é feita da mesma maneira que na função glClearColor, sendo que não é necessário especificar o componente alfa, cujo valor default é 1.0 (completamente opaco).

glBegin(GL_QUADS);… glEnd();

Usada para desenhar um quadrado preenchido a partir dos vértices especificados entre glBegin e glEnd. OpenGL mapeia as coordenadas dos vértices para a posição atual da janela de visualização na função callback “AlteraTamanhoJanela”.

glViewport(0, 0, w, h);

Recebe como parâmetro a nova largura e altura da janela. O protótipo desta função é: void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);. Seus parâmetros especificam o canto inferior esquerdo da viewport (x,y) dentro da janela, e a sua largura e altura em pixels (width e height). Geralmente x e y são zero, mas é possível usar a viewport para visualizar mais de uma cena em diferentes áreas da janela.

gluOrtho2D (0.0f, 250.0f*w/h, 0.0f, 250.0f);

É usada para determinar que a projeção ortográfica (2D) será utilizada para exibir na tela a imagem 2D que está na janela de seleção definida através dos parâmetros passados para esta função. O protótipo desta função é: void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top);. No sistema de coordenadas cartesianas, os valores left e right especificam os limites mínimo e máximo no eixo X; analogamente, bottom e top especificam os limites mínimo e máximo no eixo Y.

glMatrixMode(GL_PROJECTION); e glLoadIdentity();

Servem, respectivamente, para avisar a OpenGL que todas as futuras alterações, tais como operações de escala, rotação e translação, irão afetar a "câmera" (ou observador), e para inicializar o sistema de coordenadas antes da execução de qualquer operação de manipulação de matrizes. Sem este comando, cada chamada sucessiva de gluOrtho2D poderia resultar em uma corrupção do volume de visualização. Em outras palavras, a matriz de projeção é onde o volume de visualização, que neste caso é um plano, é definido;

Exercício construa a casinha mostrada na janela abaixo.

Page 78: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

78

Agora que já temos uma boa noção sobre desenho é hora de aprender a animar.

Animação com OpenGL e GLUT

Um quadrado numa janela com fundo preto. Agora, o quadrado será movido numa direção até bater em uma das bordas da janela e então mudar de direção. É possível criar um laço que continuamente altera as coordenadas do objeto antes de chamar a função "Desenha". Isto daria a impressão de que o quadrado se move na janela. O código do Terceiro Programa é exibido a seguir: #include <windows.h> #include <gl/glut.h> // Tamanho e posição inicial do quadrado GLfloat x1 = 100.0f; GLfloat y1 = 150.0f; GLsizei rsize = 50; // Tamanho do incremento nas direções x e y // (número de pixels para se mover a cada // intervalo de tempo) GLfloat xstep = 1.0f; GLfloat ystep = 1.0f; // Largura e altura da janela GLfloat windowWidth; GLfloat windowHeight; // Função callback chamada para fazer o desenho void Desenha(void) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // Limpa a janela de visualização com a cor de fundo especificada glClear(GL_COLOR_BUFFER_BIT);

Page 79: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

79

// Especifica que a cor corrente é vermelha // R G B glColor3f(1.0f, 0.0f, 0.0f); // Desenha um quadrado preenchido com a cor corrente glBegin(GL_QUADS); glVertex2i(x1,y1+rsize); glVertex2i(x1,y1); // Especifica que a cor corrente é azul glColor3f(0.0f, 0.0f, 1.0f); glVertex2i(x1+rsize,y1); glVertex2i(x1+rsize,y1+rsize); glEnd(); // Executa os comandos OpenGL glutSwapBuffers(); } // Função callback chamada pela GLUT a cada intervalo de tempo // (a window não está sendo redimensionada ou movida) void Timer(int value) { // Muda a direção quando chega na borda esquerda ou direita if(x1 > windowWidth-rsize || x1 < 0) xstep = -xstep; // Muda a direção quando chega na borda superior ou inferior if(y1 > windowHeight-rsize || y1 < 0) ystep = -ystep; // Verifica as bordas. Se a window for menor e o // quadrado sair do volume de visualização if(x1 > windowWidth-rsize) x1 = windowWidth-rsize-1; if(y1 > windowHeight-rsize) y1 = windowHeight-rsize-1; // Move o quadrado x1 += xstep; y1 += ystep; // Redesenha o quadrado com as novas coordenadas glutPostRedisplay(); glutTimerFunc(33,Timer, 1); } // Inicializa parâmetros de rendering void Inicializa (void)

Page 80: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

80

{ // Define a cor de fundo da janela de visualização como preta glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } // Função callback chamada quando o tamanho da janela é alterado void AlteraTamanhoJanela(GLsizei w, GLsizei h) { // Evita a divisao por zero if(h == 0) h = 1; // Especifica as dimensões da Viewport glViewport(0, 0, w, h); // Inicializa o sistema de coordenadas glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Estabelece a janela de seleção (left, right, bottom, top) if (w <= h) { windowHeight = 250.0f*h/w; windowWidth = 250.0f; } else { windowWidth = 250.0f*w/h; windowHeight = 250.0f; } gluOrtho2D(0.0f, windowWidth, 0.0f, windowHeight); } // Programa Principal int main(void) { glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glutInitWindowSize(400,350); glutInitWindowPosition(10,10); glutCreateWindow("Anima"); glutDisplayFunc(Desenha); glutReshapeFunc(AlteraTamanhoJanela); glutTimerFunc(33, Timer, 1); Inicializa(); glutMainLoop(); }

Novamente vamos explicar os novos comandos OpenGL do

exemplo apresentado. glutTimerFunc(33, Timer, 1);

Page 81: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

81

Estabelece a função Timer previamente definida como a função callback de animação. Seu protótipo é: void glutTimerFunc(unsigned int msecs, void (*func)(int value), int value);. Esta função faz a GLUT esperar msecs milisegundos antes de chamar a função func. É possível passar um valor definido pelo usuário no parâmetro value. Como esta função é "disparada" apenas uma vez, para se ter uma animação contínua é necessário reinicializar o timer novamente na função Timer. void Timer(int value ) É a função chamada pela glutTimerFunc. No exemplo, as variáveis utilizadas para determinar a posição do retângulo são atualizadas nesta função. glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB), Foi usada para trocar o modo de exibição de GLUT_SINGLE para GLUT_DOUBLE. Isto faz com que todo o rendering seja feito em um offscreen buffer. glutSwapBuffers(); É usada no lugar da glFlush porque quando é feita a troca (ou swap) de buffers, é realizada implicitamente uma operação de flush. Esta função continua fazendo o flush mesmo que o programa esteja sendo executado no modo single-buffer, porém com uma qualidade bastante inferior.

Gerenciamento de Eventos (teclado e mouse)

A biblioteca GLUT também contém funções para gerenciar eventos de entrada de teclado e mouse. glutKeyboardFunc();

Estabelece a função callback que é chamada pela GLUT cada vez que uma tecla que gera código ASCII é pressionada (por exemplo: a, b, A, b, 1, 2). Além do valor ASCII da tecla, a posição (x,y) do mouse quando a tecla foi pressionada também é retornada. Parâmetros de entrada da função callback: (unsigned char key, int x, int y). glutSpecialFunc();

Estabelece a função callback que é chamada pela GLUT cada vez que uma tecla que gera código não-ASCII é pressionada, tais como Home, End, PgUp, PgDn, F1 e F2. Além da constante que identifica a tecla, a posição corrente (x,y) do mouse quando a tecla foi pressionada também é retornada. Parâmetros de entrada da função callback: (unsigned char key, int x, int y) GLUT_KEY_F1, GLUT_KEY_F2, GLUT_KEY_F3, GLUT_KEY_F4, GLUT_KEY_F5, GLUT_KEY_F6, GLUT_KEY_F7, GLUT_KEY_F8, GLUT_KEY_F9, GLUT_KEY_F10, GLUT_KEY_F11, GLUT_KEY_F12, GLUT_KEY_LEFT, GLUT_KEY_UP, GLUT_KEY_RIGHT, GLUT_KEY_DOWN, GLUT_KEY_PAGE_UP,

Page 82: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

82

GLUT_KEY_PAGE_DOWN, GLUT_KEY_HOME, GLUT_KEY_END, GLUT_KEY_INSERT. glutMouseFunc Estabelece a função callback que é chamada pela GLUT cada vez que ocorre um evento de mouse. Parâmetros de entrada da função callback: (int button, int state, int

x, int y). Três valores são válidos para o parâmetro button: GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON e GLUT_RIGHT_BUTTON. O parâmetro state pode ser GLUT_UP ou GLUT_DOWN. Os parâmetros x e y indicam a localização do mouse no momento que o evento ocorreu.

Utilizando Menus e Exibindo Caracteres A biblioteca GLUT também contém funções para gerenciar menus, exibir caracteres e verificar a posição do mouse na janela em qualquer instante. As funções para realizar estas tarefas estão descritas a seguir. glutBitmapCharacter Uma das fontes suportadas pela GLUT é a bitmap, onde cada caracter corresponde a um bitmap que é gerado com a função glBitmap. A função glutBitmapCharacter exibe um caractere deste tipo usando OpenGL. Os parâmetros de entrada desta função são: (void *font, int character)

O primeiro parâmetro identifica a fonte que será utilizada, e o segundo o caractere. As fontes disponíveis são:

� GLUT_BITMAP_8_BY_13, � GLUT_BITMAP_9_BY_15, � GLUT_BITMAP_TIMES_ROMAN_10, � GLUT_BITMAP_TIMES_ROMAN_24, � GLUT_BITMAP_HELVETICA_10, � GLUT_BITMAP_HELVETICA_12, � GLUT_BITMAP_HELVETICA_18

glutMotionFunc Estabelece a função callback que é chamada pela GLUT cada vez que o mouse é movido sobre a janela corrente enquanto um ou mais de seus botões estão pressionados. Parâmetros de entrada da função callback: (int x, int y). Os parâmetros x e y indicam a posição do mouse em coordenadas da janela. glutPassiveMotionFunc Estabelece a função callback que é chamada pela GLUT cada vez que o mouse é movido sobre a janela corrente enquanto nenhum de seus botões está pressionado. Parâmetros de entrada da função callback: (int x, int y). Os parâmetros x e y indicam a posição do mouse em coordenadas da janela.

Page 83: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

83

glutCreateMenu Cria um novo menu pop-up e estabelece a função callback que será chamada pela GLUT quando uma de suas entradas for selecionada. Parâmetros de entrada da função callback: (int value), onde value corresponde ao valor que identifica a entrada do menu que foi selecionada. glutAddMenuEntry Adiciona uma entrada no final do menu corrente. Os parâmetros de entrada desta função são: (char *name, int value), onde name é o conjunto de caracteres que será exibido como uma entrada do menu, e value é o valor que será passado para a função callback caso esta entrada seja selecionada. glutAddSubMenu Adiciona um submenu no final do menu corrente. Os parâmetros de entrada desta função são: (char *name, int menu), onde name é o conjunto de caracteres que será exibido como uma entrada do menu, a partir da qual será aberto o submenu, e menu é o identificador do submenu. glutAttachMenu Função que "relaciona" um botão do mouse com o identificador do menu corrente. O parâmetro de entrada desta função é (int button), que corresponde ao identificador do botão do mouse (GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON ou GLUT_RIGHT_BUTTON)

Exercício

Aplique uma transformação geométrica de translação na casinha para mudá-la de lugar (importante: a translação, bem como as transformações de escala e rotação, deve ser aplicada antes que a casinha seja desenhada);

Como fazer para interagir com o programa sem que seja necessário compilar o código cada vez que o objeto, por exemplo, é trocado de lugar? Neste caso, utiliza-se uma

Page 84: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

84

função callback para gerenciar eventos do teclado e/ou mouse. Assim como já é feito com a callback que gerencia eventos do teclado "padrão", vamos registrar uma unção callback para gerenciar eventos das teclas especiais acrestando a seguinte linha de código na função main, antes da chamada para a função Inicializa(): glutSpecialFunc(TeclasEspeciais);

Em seguida, declare as seguintes variáveis globais, logo após os includes, utilizando um tipo de dado OpenGL: GLint tx, ty;

Inicialize estas variáveis na função Inicializa() com os seguintes valores: tx = 0; ty = 0;

Agora altere os parâmetros passados para a função glTranslatef que é chamada na função Desenha, para que sejam passadas estas variáveis, da seguinte maneira: glTranslatef(tx, ty, 0);

Finalmente, para fazer o tratamento dos eventos, inclua e termine de implementar a função callback TeclasEspeciais descrita abaixo. Complete código desta função de maneira a permitir transladar o objeto para cima, para baixo, para a esquerda e para a direita sempre que o usuário pressionar cada uma das teclas de setas (incremente e decremente as variáveis tx e ty)

void TeclasEspeciais(int key, int x, int y)

{

switch (key)

{

case GLUT_KEY_UP:

break;

case GLUT_KEY_DOWN:

break;

case GLUT_KEY_LEFT:

break;

case GLUT_KEY_RIGHT:

break;

}

glutPostRedisplay();

}

Page 85: Apostila C UFU.pdf

Universidade Federal De Uberlândia Faculdade de Engenharia Elétrica e Biomédica

85