Trabalho de Formatura: Curso de Linguagem C

Propaganda
Trabalho de Formatura:
Curso de Linguagem C
Autor: Jair Gustavo de Mello Torres
Orientador: Carlos Antônio Alves
Ilha Solteira - Junho de 2006
Sumário
Página
Módulo 1 – Apresentação do Histórico e das Características Básicas da
Linguagem C
1.1 – História da Linguagem C
1.2 – Compiladores e Interpretadores
1.3 – Características da Linguagem C
1.4 – Aplicações Escritas em C
1.5 – A Linguagem C Comparada à Outras Linguagens
1.6 – Estrutura de um Programa em Linguagem C
1.6.1 – Comentários
1.6.2 – Diretiva #include
1.6.3 – Uso do ponto-e-vírgula
1.7 – Compreendendo os Tipos de Arquivo
Módulo 2 – Características e Definições Gerais da Linguagem C
2.1 – Palavras Reservadas em Linguagem C
2.2 – Nomes e Identificadores Usados na Linguagem C
2.3 – Tipos e Dados
2.3.1 – Modificadores de Tipos
2.4 – Definição de Variáveis
2.5 – Definição de Constantes
2.5.1 – Constantes Hexadecimais e Octais
2.5.2 – Constantes Strings
2.5.3 – Códigos de Barra Invertida
2.6 – Operadores
2.6.1 – Operador de atribuição
2.6.2 – Operadores Aritméticos
2.6.3 – Operadores Relacionais
2.6.4 – Operadores Lógicos
2.6.5 – Manipulação de bits
2.6.6 – Operadores de assinalamento
2.6.7 – Operadores de Pré e Pós-Incremento
2.6.8 – Operadores de Endereço
2.7 – Tabela de Operadores da Linguagem C
2.8 – Expressões
2.8.1 –Conversões de Tipos em Expressões
2.8.2 – Modeladores (CASTS)
2.8.3 – Espaçamento e Parênteses
Módulo 3 – Funções na Linguagem C
3.1 – Funções
01
01
02
02
03
05
05
05
06
06
08
08
08
09
09
10
10
11
11
11
12
12
12
12
13
13
13
14
14
14
15
15
16
17
20
20
3.2 –Variáveis dentro das Funções
3.3 –Main como uma Função
3.4 – Introdução aos Parâmetros
3.5 – Parâmetros Múltiplos
3.6 – Retornando um Valor de uma Função
3.7 – Comando Return
3.8 – Protótipos de Função
3.9 – Biblioteca de Execução
3.10 – Funções que não Retornam Int
3.11 – Variáveis Locais
3.12 – Sobrecarga da Função
3.13 – Declarando Variáveis Globais
3.14 – Solucionando os Conflitos de Nomes de Variáveis Locais e Globais
3.15 – Chamada por Valor
3.16 – Chamada por Referência
3.17 – Obtendo um Endereço
3.18 – Usando um Endereço de Memória
3.19 – Alterando o Valor de um Parâmetro
3.20 – Alterando Somente Parâmetros Específicos
3.21 – Funções Recursivas
Módulo 4 - Funções PRINTF() e SCANF()
4.1 – Função PRINTF()
4.1.1 – Exibindo Valores do Tipo int Usando Printf()
4.1.2 – Exibindo um Valor Inteiro Octal ou Hexadecimal
4.1.3 – Exibindo Valores do Tipo Unsigned Int
4.1.4 – Exibindo Valores do Tipo Long Int
4.1.5 – Exibindo Valores do Tipo Float
4.1.6 – Exibindo Valores do Tipo Char
4.1.7 – Exibindo Valores de Ponto Flutuante em um Formato Exponencial
4.1.8 – Exibindo Valores em Ponto Flutuante
4.1.9 – Exibindo uma String de Caracteres
4.1.10 – Exibindo um Endereço de Ponteiro
4.1.11 – Precedendo um Valor com um Sinal de Adição ou de Subtração
4.1.12 – Formatando um Valor Inteiro
4.1.13 – Saída de Inteiros Preenchida com Zeros
4.1.14 – Exibindo um Prefixo Antes dos Valores Octais ou Decimais
4.1.15 – Formatando um Valor em Ponto Flutuante
4.1.16 – Formatando a Saída Exponencial
4.1.17 – Justificando à Esquerda a Saída de Printf()
4.1.18 – Combinando os Especificadores de Formato de Printf()
4.1.19 – Trabalhando com os Caracteres Escape de Printf()
4.1.20 – Determinando o Número de Caracteres que Printf() Exibiu
4.1.21 – Usando o Valor de Retorno de Printf()
4.2 – Função SCANF():
4.2.1 – Usando Caracteres em Scanf() que Serão Descartados
21
21
22
23
23
24
25
26
26
27
27
28
29
29
30
31
31
32
33
33
37
37
37
38
38
38
39
39
39
39
40
40
40
41
41
41
42
42
42
42
43
43
43
43
44
4.2.2 – Lendo Valores em Variáveis
4.2.3 – Lendo String de Caracteres
4.2.4 – Formatando a Entrada de Scanf()
4.2.5 – Determinando o Número Máximo de Caracteres a ser Lido
4.2.6 – Lendo Somente Caracteres Pré-Determinados
Módulo 5 - Estruturas de Fluxo de Controle
5.1 – A Declaração If
5.1.1 – Usando a Declaração Else
5.1.2 – O Encadeamento If-Else-If
5.1.3 – A Expressão Condicional
5.1.4 – Ifs Aninhados
5.2 – A Declaração For
5.2.1 – Partes do Comando For são Opcionais
5.2.2 – Laço Null
5.2.3 – Laço Infinito
5.2.4 – Usando o operador vírgula da Linguagem C dentro de um laço for
5.3 – A Declaração While
5.3.1 – Partes de um Laço While
5.4 – A Declaração Do-While
5.5 – Comando Continue
5.6 – Finalizando um Laço Usando o Comando Break
5.7 – Desvios com o comando goto
5.8 – A Declaração Switch
5.8.1 – As Declarações Switch Aninhadas
5.9 – Laços Aninhados
Módulo 6 – Matrizes
6.1 – Compreendendo as Matrizes
6.2 – Declarando uma Matriz
6.3 – Requisitos de Armazenamento de uma Matriz
6.4 – Inicializando uma Matriz
6.5 – Acessando Elementos da Matriz
6.6 – Percorrendo em um Laço Elementos da Matriz
6.7 – Usando Constantes Para Definir as Matrizes
6.8 – Passando uma Matriz a uma Função
6.9 – Revisitando as Matrizes Como Funções
6.10 – Como as Matrizes de String Diferem
6.11 – Quantos Elementos Uma Matriz Pode Armazenar
6.12 – Matrizes Multidimensionais
6.13 – Inicializando Elementos em Uma Matriz Bidimensional
6.14 – Percorrendo em Um Laço Uma Matriz Bidimensional
6.15 – Percorrendo Uma Matriz Tridimensional
6.16 – Passando uma Matriz Bidimensional Para uma Função
6.17 – Tratando as Matrizes Multidimensionais Como Uma Dimensão
45
45
45
45
46
50
50
51
51
52
53
53
54
55
55
56
56
57
58
59
60
61
61
63
64
68
68
68
68
69
69
70
70
71
71
72
72
72
73
73
73
74
74
Módulo 7 – Ponteiros
7.1 – Ponteiros como Endereços
7.2 – Determinando o Endereço de uma Variável
7.3 – Como a Linguagem C trata Matrizes como Ponteiros
7.4 – Aplicando o Operador de Endereço (&) a uma Matriz
7.5 – Declarando Variáveis Ponteiros
7.6 – Desreferenciando um Ponteiro
7.7 – Usando Valores de Ponteiro
7.8 – Ponteiros com Parâmetros de Função
7.9 – A Aritmética de Ponteiros
7.10 – Incrementando e Decrementando um Ponteiro
7.11 – Percorrendo uma String usando um Ponteiro
7.12 – Funções que retornam Ponteiros
7.13 – Criando uma Função que retorna um Ponteiro
7.14 – Uma Matriz de Ponteiros
7.15 – Percorrendo em um Laço uma Matriz de Strings de Caracteres
7.16 – Usando um Ponteiro para um Ponteiro para String de Caracteres
7.17 – Declarando uma Constante String usando um Ponteiro
7.18 – O Ponteiro do tipo Void
7.19 – Ponteiros para as Funções
7.20 – Ponteiro para uma Função
7.21 – Usando um Ponteiro para um Ponteiro para um Ponteiro
77
77
77
77
78
78
79
79
79
80
80
81
81
81
82
82
82
83
83
83
84
84
Módulo 8 - Sistema de Arquivos
87
8.1 – Introdução
8.2 – O Ponteiro de Arquivo
8.3 – Abrindo um Arquivo Usando FOPEN
8.4 – A Estrutura FILE
8.5 – Fechando um Arquivo Aberto
8.6 – Lendo e Gravando Informações no Arquivo um Caracter de Cada Vez
8.7 – O Ponteiro de Posição do Ponteiro de Arquivo
8.8 – Determinando a Posição Atual no Arquivo
8.9 – O Significado de Canais de Arquivo
8.10 – Lendo Linhas de Textos
8.11 – Gravando Linhas de Texto
8.12 – Exemplo do Uso das Funções Fgets e Fputs
8.13 – As Funções Fread( ) E Fwrite( )
8.14 – As Funções Fprintf( ) e Fscanf( )
8.15 – Testando o Final do Arquivo
8.16 – Posicionamento do Ponteiro de Arquivo com Base em sua Posição
Atual
8.17 – Excluindo um Arquivo
87
87
87
89
89
90
90
91
91
92
92
92
93
95
96
97
Módulo 9 – Funções Gráficas na Linguagem C
100
9.1 – A Tela Gráfica
97
100
9.2 – Detectando o Adaptador Gráfico
9.3 – Inicializando a Parte Gráfica
9.4 – Obtendo as Dimensões da Tela
9.5 – Desenhando uma Reta na Tela
9.6 – Desenhando um Retângulo na Tela
9.7 – Desenhando um Ponto na Tela
9.8 – Desenhando um Círculo na Tela
9.9 – Desenhando um Arco Circular na Tela
9.10 – Limpando a Tela Gráfica
9.11 – Mudando as Cores do um Desenho
9.12 – Mudando a Cor de Fundo e o Preenchimento do um Desenho
Módulo 10 – Estruturas, Uniões, Enumerações e Typedef
10.1 – Estruturas
10.1.1 – Referenciando os Campos da Estrutura
10.1.2 – Matrizes de Estruturas
10.1.3 – Atribuindo Estruturas
10.1.4 – Passando Estruturas para Funções
10.1.5 – Ponteiros para Estruturas
10.1.6 – Matrizes e Estruturas dentro de Estruturas
10.2 – Uniões
10.3 – Enumerações
10.4 – A Palavra Reservada Typedef
101
102
102
103
103
104
104
105
105
106
106
109
109
110
110
110
111
112
114
115
116
118
Módulo 11 – Funções de Entrada/Saída (I/O) e Programa Analisador
Espectral
11.1 – Introdução
11.2 – As Funções inport e outport
11.3 – Placas de Aquisição de Sinais Analógicos
11.3.1 - Conversor A/D
11.3.2 - Conversor D/A
11. 4 – Fatores de Escala para o Modo Gráfico
11.4.1 Fator de Escala Eixo X
11.4.2 - Fator de Escala Eixo Y
11.4.3. Programa: Análise Espectral
121
Referências Bibliográficas
134
121
121
122
123
123
124
124
125
126
Módulo 1 – Apresentação do Histórico e das
Características Básicas da Linguagem C
1.1 – História da Linguagem C
A Linguagem C é uma linguagem de programação que tem sua origem em outras
duas linguagens anteriores: a Linguagem BCPL e a Linguagem B. A Linguagem BCPL foi
desenvolvida por Martin Richards. Esta linguagem influenciou a linguagem inventada por
Ken Thompson, chamado B. Logo em seguida, Dennis Ritchie desenvolveu a Linguagem C
que foi implementada em um DEC PDP-11, usando o sistema operacional UNIX.
A Linguagem C, dada a sua simplicidade e flexibilidade, tornou-se ao longo do
tempo uma das linguagens de programação mais usadas, sendo utilizada na criação e
desenvolvimento de softwares e sistemas operacionais que se tornaram famosos em todo
mundo, como por exemplo o Sistema Operacional Windows. Entretanto, a Linguagem C
atinge seus limites a partir do ponto em que os programas escritos na linguagem atingem
um certo tamanho, entre 25.000 e 100.000 linhas, devido à problemas de gerenciamento do
código. Para resolver este problema, em 1980, enquanto trabalhava nos laboratórios da
Bell, em Murray Bill, New Jersey, Bjarne Stroustrup acrescentou várias extensões à
linguagem C e chamou inicialmente esta nova linguagem de “C com classes”.
Em 1983, o nome adotado para esta nova linguagem foi C++. Muitas modificações
foram feitas na Linguagem C++ para que ela pudesse suportar a programação orientada a
objetos (POO).
1.2 – Compiladores e Interpretadores
Compiladores e Interpretadores são simplesmente programas sofisticados que agem
sobre o código-fonte do seu programa, ou seja, depois que o código-fonte de um
determinado programa é escrito ele é submetido à um Compilador ou um Interpretador que
fará com que seja possível sua execução em uma determinada máquina.
O Compilador lê o programa inteiro e converte-o em um código executável. Uma
vez o programa compilado, uma linha de código-fonte está significativamente distante do
código executável. O compilador não é necessário para executar o programa, desde que ele
já esteja compilado, ou seja, neste caso não será necessário que se tenha um programa
instalado na máquina que reconheça o código em questão.
O Interpretador lê o código-fonte do seu programa uma linha por vez e executa
uma instrução específica contida naquela linha. O interpretador deverá estar presente toda
vez que o programa estiver sendo executado, ou seja, é necessário que um interpretador
específico da linguagem utilizada esteja instalado na máquina.
Existem dois termos freqüentes: tempo de compilação e tempo de execução. O
termo tempo de compilação refere-se aos eventos que acontecem durante o processo de
compilação e tempo de execução, aos eventos que ocorrem enquanto o programa está sendo
executado. Infelizmente, constantemente esses termos estão relacionados a mensagens de
erros, como em erros de tempo de compilação e erros de tempo de execução.
1.3 – Características da Linguagem C
Portabilidade entre máquinas e sistemas operacionais, ou seja, um código
escrito em linguagem C poderá ser executado em diferentes máquinas independentemente
da sua configuração física (hardware) e do sistema operacional residente.
A linguagem C é estruturada, com isso desencoraja a utilização dos goto's –
desvios incondicionais -, que geram os chamados códigos "macarronada". O "goto" é
substituído por diversos tipos de laços e desvios, tais como: while, do-while, for; if-thenelse, switch, que permitem ao programador exercer um controle lógico mais eficaz sobre os
códigos fontes de seus programas.
A linguagem C/C++ possui sub-rotinas com variáveis locais, isto é, funções
cujas variáveis são visíveis apenas dentro desta função e somente no momento em que estas
funções estejam sendo usadas. Assim as variáveis com mesmo nome, que pertençam a
funções distintas, são protegidas dos efeitos colaterais (proteção de variáveis), isto é, uma
modificação em nível funcional não acarretará mudança na variável em nível global. Desta
forma, mudanças de valores de variáveis no corpo do programa principal não afetam as
variáveis de funções e vice-versa, a não ser que o programador assim o queira.
Código compacto e rápido, quando comparado ao código de outras linguagem
de complexidade análoga.
A linguagem C é Case-Sensitive. É importante saber que as letras maiúsculas e
minúsculas são tratadas como caracteres distintos. Por exemplo, em algumas linguagens, os
nomes de variáveis count, Count e COUNT são três maneiras de se especificar a mesma
variável. Entretanto na linguagem C, serão três variáveis diferentes. Então, quando você
digitar programas em C seja cuidadoso na utilização correta das letras.
1.4 – Aplicações Escritas em C
Atualmente, nos Estados Unidos, C é a linguagem mais utilizada pelos
programadores, por permitir, dadas suas características, a escrita de programas típicos do
Assembler, BASIC, COBOL e Clipper, sempre com maior eficiência e portabilidade, como
podemos constatar pelos exemplos abaixo relacionados:
Sistema Operacional: UNIX (Sistema Operacional executável em micro computadores e em
mainframes).
Montadores: Clipper (O utilitário de banco de dados mais usado no Brasil).
Planilhas: 1,2,3 e Excel (A planilha eletrônica com maior volume de vendas mundial).
Banco de Dados: dBase III, IV e Access (o gerenciador de base de dados mais utilizado no
mundo).
InfoStar: O Editor de Texto mais utilizado nos USA no Sistema Operacional UNIX.
Utilitários: FormTool (Editor de formulário mais vendido no mundo).
Aplicações Gráficas: Efeitos Especiais de filmes com Star Trek e Star War.
Linguagens como o Power Builder e o Visual Basic, respectivamente as linguagens mais
utilizadas nos EUA e no Brasil.
No Brasil utilizada por empresas especializadas na elaboração de vinhetas e outros
efeitos especiais.
1.5 – A Linguagem C Comparada à Outras Linguagens
Deve-se entender Nível Alto como sendo a capacidade da linguagem em
compreender instruções escritas em “dialetos” próximos do inglês (Ada e Pascal, por
exemplo) e Nível Baixo para aquelas linguagens que se aproximam do Assembly, que é a
linguagem própria da máquina, compostas por instruções binárias e outras
incompreensíveis para o ser humano não treinado para este propósito. Infelizmente, quanto
mais clara uma linguagem for para o humano (simplicidade >) mais obscura o será para a
máquina (velocidade <).
A Linguagem C é freqüentemente referenciada como uma linguagem de nível
médio, posicionando-se entre o Assembly (baixo nível) e o Pascal (alto nível). Uma das
razões da invenção da Linguagem C foi dar ao programador uma linguagem de alto nível
que poderia ser utilizada como uma substituta para a linguagem Assembly. Entretanto,
ainda que a Linguagem C possua estruturas de controle de alto nível, como é encontrado na
Pascal, ela também permite que o programador manipule bits, bytes e endereços de uma
maneira mais proximamente ligada à máquina, ao contrário da abstração apresentadas por
outras linguagens de alto nível. Por esse motivo, a Linguagem C tem sido ocasionalmente
chamada de “código Assembly de alto nível”. Por sua natureza dupla, a Linguagem C
permite que sejam criados programas rápidos e eficientes sem a necessidade de se recorrer
a linguagem Assembly.
A filosofia que existe por trás da Linguagem C é que o programador sabe realmente
o que está fazendo. Por esse motivo, a Linguagem C quase nunca “coloca-se no caminho”
do programador, deixando-o livre para usar (ou abusar) dela de qualquer forma que queira.
O motivo para essa “liberdade na programação” é permitir ao compilador C criar códigos
muito rápidos e eficientes, já que ele deixa a responsabilidade da verificação de erros para o
programador.
Observemos o esquema a seguir:
Nível Baixo
Nível Médio
VELOCIDADE
Assembler
Macro Assembler
Forth
Nível Alto
CLAREZA
C
Fortran
Basic
COBOL
Pascal
Ada
MODULA-2
Antes da Linguagem C tornar-se um padrão de fato (meados de 1.988, nos USA),
tínhamos, aproximadamente, o seguinte perfil de mercado:
- Aplicações de Banco de Dados
- Mainframe: COBOL e gerenciadores
- Micros: dBase, Clipper e BASIC e gerenciadores como Btrieve.
- Aplicações Gráficas: Pascal.
- Aplicações Científicas: FORTRAN e Pascal.
- Utilitários, Sistemas Operacionais e Compiladores: Assembler.
A chegada de poderosos compiladores C (Borland, Microsoft e Zortech-Symantec),
revolucionou totalmente estes conceitos pois passou a permitir a construção de
praticamente qualquer tipo de aplicação na Linguagem C, normalmente mais rápidas do
que na linguagem original e portável entre os diversos ambientes (“roda” em DOS, UNIX,
etc. com poucas mudanças).
Devemos entender no entanto, que apenas temos uma relativa portabilidade, pois a
verdadeira portabilidade depende necessariamente da implementação do sistema
operacional, necessariamente aberto, o que não existe fora do mundo Unix.
Quadro de características de linguagens:
Linguagens /
Características Ideais
Executáveis Curtos
Executáveis Rápidos
Portáveis
Manipulação de Bits
Assembler
ótimo
ótimo
péssimo
ótimo
BASIC
Pascal
fraco
Bom
Bom
razoável
Clipper
COBOL
C
péssimo
razoável
ótimo
péssimo
fraco
fraco
ótimo
fraco
ótimo
bom
bom
ótimo
O quadro anterior, deixa claro o porquê da revolução causada pela Linguagem C,
dados os inúmeros pontos fortes da linguagem e a inexistência de pontos fracos da mesma.
Não deve-se concluir apressadamente que poderemos desenvolver tudo em C e
abandonarmos todas as outras linguagens, pelos seguintes motivos:
- Alta base de programas escritos em Assembler, COBOL, BASIC, Pascal.
- Conhecimento amplo de linguagens concorrentes como COBOL, Clipper e BASIC.
- Melhor adaptação de alguma linguagem para tarefa específica como Gráficos (Pascal),
Inteligência Artificial (Prolog, LISP), matemáticas (Pascal e FORTRAN), aplicações
comerciais (COBOL, Clipper e BASIC).
As versões atuais das linguagens BASIC, C, Clipper, Pascal e COBOL, estão muito
mais semelhantes do que o eram ao tempo em que este quadro foi elaborado, portanto na
prática muitas vezes este quadro, meramente teórico, pode tornar-se inaplicável.
À médio prazo, pode-se afirmar que a Linguagem C deverá ir desalojando as outras
linguagens podendo até mesmo tornar-se um padrão de direito, porém devemos lembrar
que algumas linguagens foram propostas para isto (Algol, PL/1) e não só não conseguiram
atingir seus objetivos como praticamente desapareceram.
1.6 – Estrutura de um Programa em Linguagem C
Um programa em Linguagem C é formado por uma ou mais funções. Cada função
possui um nome exclusivo e corresponde à um bloco de código, delimitado por um par de
chaves {}, contendo um conjunto de declarações, expressões, comandos de controle e
chamadas à outras funções. Uma função denominada main é obrigatória em todos os
programas, pois é o seu ponto de entrada, isto é, o programa começa a ser executado no
início da função main e termina ao final desta função. Normalmente a declaração desta
função possui a seguinte forma: int main(void), mas adiante será estudada outra forma de
declaração para esta função. Ao concluir a função main, com o comando return, a
execução do programa é finalizada, sendo que pelo padrão ANSI, esta função deve retornar
0 (zero) se o programa foi finalizado com sucesso, ou um valor maior que zero caso ele
tenha sido finalizado por uma situação anormal. Além da função main, o programa pode
possuir outras funções, sendo que estas devem ser, direta ou indiretamente, chamadas pela
função main.
Abaixo, um programa escrito em linguagem C, muito simples que você pode rodar
no seu compilador:
/* Seu primeiro programa em linguagem C*/
#include <stdio.h>
void main ()
{
printf ("Bem vindo ao mundo da programação em C!!!\n");
getchar(); /* Aguarda pressionar Enter */
}
1.6.1 – Comentários
Os comentários servem principalmente para documentação do programa e são
ignorados pelo compilador, portanto não irão afetar o programa executável gerado. Os
comentário iniciam com o símbolo /* e se estendem até aparecer o símbolo */. Um
comentário pode aparecer em qualquer lugar no programa onde possa aparecer um espaço
em branco e pode se estender por mais de uma linha.
1.6.2 – Diretiva #include
Toda a diretiva, em C, começa com o símbolo # no início da linha. A diretiva
#include inclui o conteúdo de um outro arquivo dentro do programa atual, ou seja, a linha
que contêm a diretiva é substituída pelo conteúdo do arquivo especificado. Sintaxe:
#include <nome do arquivo>
ou
#include “nome do arquivo”
O primeiro caso é o mais utilizado. Ele serve para incluir alguns arquivos que
contêm declaração das funções da biblioteca padrão, entre outras coisas. Estes arquivos,
normalmente, possuem a extensão .h e se encontram em algum diretório pré-definido pelo
compilador. Sempre que o programa utilizar alguma função da biblioteca-padrão deve ser
incluído o arquivo correspondente. A tabela a seguir apresenta alguns dos principais .h da
linguagem C: Descrição
stdio.h - Funções de entrada e saída (I/O)
string.h - Funções de tratamento de strings
math.h - Funções matemáticas
ctype.h - Funções de teste e tratamento de caracteres
stdlib.h - Funções de uso genérico
A segunda forma, onde o nome do arquivo aparece entre aspas duplas, serve
normalmente para incluir algum arquivo que tenha sido criado pelo próprio programador ou
por terceiros e que se encontre no diretório atual, ou seja, no mesmo diretório do programa
que está sendo compilado.
1.6.3 – Uso do Ponto-e-Vírgula
Você pode não ter percebido, mas no código em C apresentado no item 1.6 usamos
duas vezes o ponto-e-vírgula, quando terminamos a declaração dos comandos printf() e
getchar(). Ele é usado para separar diferentes comandos dentro do seu código em C.
Durante o processo de compilação executado pelo compilador, o ponto-e-vírgula mostra ao
compilador quando uma linha de comando termina e quando outra linha de comando se
inicia. Ou seja, se você esquecer do ponto-e-vírgula seu compilador irá acusar um erro pois
ele não irá saber quando termina ou começa um determinado comando dentro do código
que você digitou.
1.7 – Compreendendo os Tipos de Arquivo
Ao criar um programa em linguagem C, você coloca seus comando em um arquivofonte, que tem a extensão .c . Se o seu programa for compilado com sucesso, o compilador
irá criar um programa executável com a extensão .exe . Durante a compilação, o
compilador cria em seu diretório outros arquivos com extensão .obj . Estes arquivos contém
as instruções em forma binária, ou seja 1s e 0s, que o computador compreende. Vale
ressaltar que os códigos estudados neste material foram testados no compilador Turbo C
2.0.
1.8 – Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
1 1.1 – História da Linguagem C: apresenta um breve relato sobre o
desenvolvimento da Linguagem de Programação C. Nele é mostrado que a Linguagem C
foi desenvolvida por Dennis Ritchie e que ela é na verdade uma linguagem derivada de
outras duas linguagens de programação anteriores, a BCPL e a Linguagem B. Mesmo
sendo uma linguagem flexível e poderosa, a Linguagem C chegou a seus limites, e com
isso, o desenvolvimento de uma nova linguagem tornou-se inevitável, sendo desenvolvida a
Linguagem C++, que na verdade era a Linguagem C com novas características que
superaram seus limites. Foram feitas várias modificações e melhorias na Linguagem C++
para que ela pudesse suportar a programação orientada a objeto (POO).
2 1.2 – Compiladores e Interpretadores: neste item, foi mostrada a diferença
entre compilador e interpretador. Compiladores e Interpretadores são programas que
“traduzem” um código-fonte de um programa para a linguagem que a máquina entende. No
caso do Interpretador, ele faz isso linha por linha do código-fonte, toda vez que o programa
em questão é executado. Ou seja, é necessário que exista na máquina um interpretador
específico instalado para a linguagem de programação em que o programa em questão foi
escrito. No caso do Compilador é gerado um programa executável, ou seja, o Compilador
cria um arquivo que pode ser lido pelo sistema operacional da máquina, eliminando assim a
necessidade de um interpretador instalado. Foi visto também que o termo tempo de
compilação refere-se aos eventos que acontecem durante o processo de compilação e tempo
de execução, aos eventos que ocorrem enquanto o programa está sendo executado.
3 1.3 – Características da Linguagem C: neste item, apresentou-se as
principais características da Linguagem C, entre elas: que a Linguagem C é portável; a
Linguagem C é estruturada; a linguagem C/C++ possui sub-rotinas com variáveis locais;
seu código é compacto e rápido; a linguagem é case-sensitive.
4 1.4 – Aplicações Escritas em C: devido à sua compatibilidade com outras
linguagens de programação, como por exemplo, Assembly, Cobol, Clipper, Basic, a
Linguagem C é largamente utilizada no desenvolvimento de programas, sistemas
operacionais e até em efeitos especiais em filmes.
5 1.5 – A Linguagem C Comparada à Outras Linguagens: neste item foi
feita uma comparação entre a Linguagem C e outras linguagens de programação. Mostrouse que a Linguagem C se classifica como uma linguagem de nível médio, pois mesmo
possuindo estruturas de controle de alto nível, ela permite a manipulação de bits, bytes e
endereços de uma maneira bem próxima à máquina. Além do mais, a Linguagem C é veloz
e tem uma alta portabilidade. Mas mostra-se que não é possível pensar em abandonar as
outras linguagens e usar-se somente a Linguagem C.
6 1.6 – Estrutura de um Programa em Linguagem C: foi estudado neste item
que um programa em Linguagem C é formado por uma ou mais funções. Cada função
possui um nome exclusivo e corresponde à um bloco de código, delimitado por um par de
chaves {}, contendo um conjunto de declarações, expressões, comandos de controle e
chamadas à outras funções. Uma função denominada main é obrigatória em todos os
programas, pois é o seu ponto de entrada, isto é, o programa começa a ser executado no
início da função main e termina ao final desta função.
7 1.6.1 – Comentários: comentários servem principalmente para documentação
do programa e são ignorados pelo compilador, portanto não irão afetar o programa
executável gerado. Os comentário iniciam com o símbolo /* e se estendem até aparecer o
símbolo */.
8 1.6.2 – Diretiva #include: neste subitem foi estudada a diretiva #include
inclui o conteúdo de um outro arquivo dentro do programa atual, ou seja, a linha que
contêm a diretiva é substituída pelo conteúdo do arquivo especificado. Ela serve para
incluir alguns arquivos que contêm declaração das funções da biblioteca padrão, entre
outras coisas.
9 1.6.3 – Uso do ponto-e-vírgula: foi visto que o ponto-e-vírgula é usado para
separar diferentes comandos dentro do seu código em C. Durante o processo de compilação
executado pelo compilador, o ponto-e-vírgula mostra ao compilador quando uma linha de
comando termina e quando outra linha de comando se inicia.
10 1.7 – Compreendendo os Tipos de Arquivo: foi explicado detalhadamente
os tipos de arquivos usados na programação em Linguagem C, como por exemplo, os
arquivos com extensão .c, .exe e .obj .
Módulo 2 – Características e Definições Gerais da
Linguagem C
2.1 – Palavras Reservadas na Linguagem C
Na Linguagem C existem palavras que são de uso reservado, ou seja, não pode-se
usá-las para escrever programas, por exemplo, usando o nome de uma palavra reservada
para referenciar uma variável. Uma palavra reservada é essencialmente um comando e, na
maioria das vezes, as palavras reservadas de uma linguagem definem o que pode ser feito e
como pode ser feito.
O conjunto de palavras reservadas em Linguagem C especificado pelo padrão ANSI
C são as relacionadas abaixo:
auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
Como já foi dito no Módulo 1, a Linguagem C diferencia letras maiúsculas e
minúsculas, ou seja, char é uma palavra reservada da Linguagem C mas CHAR ou ChAr
não é. Reforçando o que já foi mencionado, as palavras reservadas só irão executar os
comandos que lhes foram designados.
2.2 – Nomes e Identificadores Usados na Linguagem C
A Linguagem C chama o que é usado para referenciar variáveis, funções, rótulos e
vários outros objetos definidos pelo usuário de identificadores. Quanto aos nomes ou
identificadores usados na declaração de variáveis, deve-se considerar as seguintes regras:
('_');
1
nomes de variáveis começam com uma letra ('A'..'Z', 'a'..'z') ou pelo underscore
2
após podem ser seguidos dígitos, letras e underscores;
3 evitar o uso do '_' no primeiro caractere do identificador de uma variável, pois
este tipo de identificadores é de uso do sistema;
4 normalmente ao declarar-se uma variável esta será inicializada com zero. Não se
deve, no entanto, contar que isto sempre seja verdadeiro, portanto inicializa-se sempre as
variáveis.
Aqui estão alguns exemplos de nomes de identificadores corretos e incorretos:
Correto
count
test23
high_balance
Incorreto
1count
Olá! Aqui
high..balance
Em C os primeiros 32 caracteres de um nome de identificador são significantes. Isso
quer dizer que duas variáveis com os 32 primeiros caracteres em comum, diferindo somente
no 33º , são consideradas iguais. Como você deve lembrar, em C, letras maiúsculas e
minúsculas são tratadas como diferentes e distintas umas das outras. Por isso, count, Count
e COUNT são três identificadores distintos.
Um identificador não pode ser o mesmo que uma palavra reservada e não devem ter
o mesmo nome de uma função – tanto uma função que você tenha escrito como uma função
de biblioteca da linguagem C.
2.3 – Tipos e Dados
Quando um programa é escrito em qualquer linguagem de programação é necessário
a definição de algumas variáveis. Variáveis são instâncias em que serão armazenados
valores utilizados durante a execução de programas. Estas variáveis podem ser modificadas
para suportar diferentes tipos de dados. Na tabela abaixo constam os tipos básicos de dados
da Linguagem C:
Tipo
Char
Int
Float
double
void
Tamanho (em bits)
8
16
32
64
0
Intervalo
-128 a 127
-32768 a 32767
3,4E-38 a 3,4E+38
1,7E-308 a 1,7E+308
sem valor
2.3.1 – Modificadores de Tipos
Possuindo os tipos básicos de dados, pode-se ainda formatá-los para atender melhor
as necessidades de cada situação. Com exceção do tipo void, todos os outros tipos básicos
podem ter modificadores precedendo-os. Você pode ter como modificadores signed,
unsigned, long e short. Os modificadores signed, unsigned, long e short podem ser
aplicados aos tipos de base caractere e inteira. Entretanto, long, também pode ser aplicado
ao tipo double. A tabela a seguir mostra todas as combinações permitidas dos tipos básicos
e dos modificadores de tipo.
Tipo
char
unsigned char
signed char
int
unsigned int
signed int
short int
unsigned short int
signed short int
long int
signed long int
unsigned long int
float
double
long double
Tamanho (em bits)
8
8
8
16
16
16
16
16
16
32
32
32
32
64
80
Intervalo
-128 a 127
0 a 255
-128 a 127
-32768 a 32767
0 a 65535
-32768 a 32767
-32768 a 32767
0 a 65535
-32768 a 32767
-2147483648 a 2147483647
-2147483648 a 2147483647
0 a 4294967295
3,4E-38 a 3,4E+38
1,7E-308 a 1,7E+308
3,4E-4932 a 1,1E+4932
A linguagem C permite uma notação simplificada para declaração de inteiro
unsigned, short ou long. Você pode simplesmente usar a palavra unsigned, short ou long
sem o int. O int está implícito. Por exemplo:
unsigned x;
unsigned int x;
declaram igualmente variáveis inteiras sem sinal.
Variáveis do tipo char podem ser usadas para armazenar valores outros que são
simplesmente o conjunto de caracteres ASCII. Uma variável char pode ser usada também
como um “pequeno” inteiro com intervalo de –128 a 127, e no lugar de um inteiro quando a
situação não requer números grandes.
2.4 – Definição de Variáveis
Variáveis são instâncias onde o programa em execução coloca os dados que estão
sendo processados durante sua execução. As variáveis devem ser declaradas, ou seja,
devem ser definidos nome, tipo e algumas vezes seu valor inicial. As variáveis são
classificadas em variáveis locais e globais.
Variáveis globais são aquelas declaradas fora do escopo das funções.
Variáveis locais são aquelas declaradas no início de um bloco e seus escopos estão
restritos aos blocos em que foram declaradas. A declaração de variáveis locais deve
obrigatoriamente ser a primeira parte de um bloco, ou seja, deve vir logo após um caractere
de “abre chaves”, '{'; e não deve ser intercalada com instruções ou comandos. Veja a
sintaxe para se declarar uma variável:
<tipo> <nome>
Exemplos:
int mul = 100;
char a, b, c;
float valor;
2.5 – Definição de Constantes
O conceito de constantes em linguagens de programação é atribuir um certo valor
constante a um nome, e quando este nome for referenciado dentro do código do programa,
será utilizado nas operações o valor atribuído a este nome. Ou seja, se for definida a
constante PI com o valor “3,1415926536”, quando for encontrado no código o nome PI,
será utilizado em seu lugar o valor “3,1415926536”. Na Linguagem C, constantes podem
ser definidas da seguinte maneira :
#define <nome_da_constante> valor
Exemplos:
#define PI 3,1415926536
#define SINAL "aberto"
#define MULT A*B
Observe que na definição de uma constante não há o “;” no final. Se for colocado,
este fará parte do valor associado à constante.
2.5.1 – Constantes Hexadecimais e Octais
Em programação algumas vezes é comum usar um sistema de numeração baseado
em 8 ou 16 em vez de 10. O sistema numérico baseado em 8 é chamado octal e usa os
dígitos de 0 a 7. Em octal, o número 10 é o mesmo que 8 em decimal. O sistema numérico
de base 16 é chamado hexadecimal e usa os dígitos de 0 a 9 mais as letras de A até F, que
equivalem a 10, 11, 12, 13, 14 e 15. Por exemplo, o número hexadecimal 10 é 16 em
decimal. Por causa da freqüência com que estes dois sistemas numéricos são usados, a
linguagem C permite que se especifique constantes inteiro em hexadecimal ou octal em vez
de decimal, se preferir. Uma constante hexadecimal deve começar com “0x” (um zero
seguido de um x), seguido pela constante em formato hexadecimal. Uma constante octal
começa com um zero. Aqui estão alguns exemplos:
hex = 0xFF; /* 255 em decimal */
oct = 011; /* 9 em decimal */
2.5.2 – Constantes Strings
Outro tipo de constante suportada pela Linguagem C é o tipo string. Uma string é
um conjunto de caracteres entre aspas. Por exemplo, “você é um vencedor” é uma string.
Não confunda strings com caractere. Uma constante caractere simples fica entre
dois apóstrofos, por exemplo ‘a’. Entretanto “a” é uma string que contém somente uma
letra.
2.5.3 – Códigos de Barra Invertida
Colocar todas as constantes caractere entre aspas funciona para muitos caracteres,
mas alguns, como o retorno de carro, são impossíveis de serem inseridos em uma string a
partir do teclado. Por isso, a linguagem C fornece constantes caractere mais barra invertida
especiais. Estes códigos são mostrados na tabela a seguir:
Código
\b
\n
\t
\’
\\
\a
\xN
Significado
Retrocesso
Nova linha
Tabulação horizontal
Apóstrofo
Barra invertida
Sinal sonoro
Constante hexadecimal
Código
\f
\r
\”
\0
\v
\N
Significado
Alimentação de formulário
Retorno de carro
Aspas
Nulo
Tabulação vertical
Constante octal
Usa-se um código de barra invertida exatamente da mesma maneira como usa
qualquer outro caractere. Por exemplo:
ch = ‘\t’;
printf (“este é um teste\n”);
Esse fragmento de código primeiro atribui uma tabulação a ch e, então, imprime
“este é um teste” na tela, seguido de uma nova linha.
2.6 – Operadores
A linguagem C é muito rica em operadores internos. Um operador é um símbolo
que diz ao compilador para realizar manipulações matemáticas e lógicas específicas. A
linguagem C possui três classes gerais de operadores: aritméticos, relacionais e lógicos e
bit-a-bit.
2.6.1 – Operador de atribuição
O operador “=” atribui um valor ou resultado de uma expressão contida a sua direita
para a variável especificada a sua esquerda. Exemplos:
a = 10;
b = c * valor + getval(x);
a = b = c = 1; /*Aceita associação sucessiva de valores*/
2.6.2 – Operadores Aritméticos
São aqueles que operam sobre números e expressões, resultando valores numéricos.
São eles:
Operador
+
*
Soma
subtração
multiplicação
Ação
/
%
-
divisão
módulo da divisão (resto da divisão inteira)
sinal negativo (operador unário)
2.6.3 – Operadores Relacionais
Operam sobre expressões, resultando valores lógicos de TRUE (verdadeiro) ou
FALSE (falso). são eles:
Operador
Ação
>
>=
<
<=
==
!=
Maior
maior ou igual
Menor
menor ou igual
Igual
não igual (diferente)
Atenção!
Não existem os operadores relacionais: “=<“, “=>“ e “<>“.
Não confunda a atribuição (“=“) com a comparação (“==“).
2.6.4 – Operadores Lógicos
Operam sobre expressões, resultando valores lógicos de TRUE (verdadeiro) ou
FALSE (falso). Possuem a característica de “short circuit”, ou seja, sua execução é curta e
só é executada até o ponto necessário. São eles:
Operador
Ação
&&
||
!
operação AND
operação OR
operador de negação NOT (operador
unário)
Exemplos de “short circuit”:
(a == b) && (b == c)
(a == b) || (b == c)
/*
/*
Se a != b não avalia o resto da expressão */
Se a == b não avalia o resto da expressão */
2.6.5 – Manipulação de bits
A manipulação é feita em todos os bits da variável a qual não pode ser do tipo float
ou double. Os operadores que manipulam bits estão relacionados abaixo:
Operador
&
|
^
Ação
bit and
bit or
bit xor - exclusive or
<<
>>
~
Rotação a esquerda
Rotação a direita
bit not (complemento)
Observação: x << n irá rotacionar n vezes a variável x à esquerda.
2.6.6 – Operadores de assinalamento
É expresso da seguinte forma: (operadores combinados)
var = var op expr
var op = expr
->
Onde tempos op como um dos seguintes operadores:
Operador
Ação
+
*
/
%
>>
<<
&
^
|
Soma
Subtração
Multiplicação
Divisão
módulo (resto da divisão)
Rotação a direita
Rotação a esquerda
And
xor - exclusive or
Or
Exemplo de aplicação:
i+= 2; /* É equivalente a: i = i + 2 */
j-= 3; /* É equivalente a: j = j – 3 */
k >>= 3; /* É equivalente a: k = k >> 3;*/
z &= flag; /* É equivalente a: z = z & flag;*/
2.6.7 – Operadores de Pré e Pós-Incremento
Operadores de pré e pós-incremento são aqueles usados quando é necessário
incrementar ou decrementar um determinado valor.
As operações abaixo podem ser representadas assim:
i = i + 1;
i = i – 1;
z = a; a = a + 1;
z = a; a = a – 1;
a = a + 1; z = a;
a = a - 1; z = a;
i = ++i;
i = --i;
z = a++;
z = a--;
z = ++a;
z = --a;
2.6.8 - Operadores de Endereço
++i;
--i;
São operadores usados com ponteiros, para acesso a endereços de memória.
Operador
&
*
Significado
endereço de uma variável
conteúdo do endereço especificado
Exemplos:
int var, *x;
x = &var;
var = *x;
2.7 – Tabela de Operadores da Linguagem C
+
!
~
&
*
sizeof
++
-*
/
/
%
+
>>
<<
>
>=
<
<=
==
!=
&
|
^
&&
||
=
OP=
Operador
Função
menos unário
mais unário
negação lógica
bitwise not
endereço de
referência a ptr
tamanho de var
incremento
decremento
multiplicação
divisão inteira
divisão real
resto da divisão
soma
subtração
shift right
shift left
maior que
maior ou igual a
menor que
menor ou igual a
igual a
diferente de
bitwise AND
bitwise OR
bitwise XOR
logical AND
logical OR
assinalamento
assinalamento
Exemplo “C”
a = -b;
a = +b ;
! flag
a = ~b ;
a = &b ;
a = *ptr ;
a = sizeof(b) ;
++a; ou a++;
--a; ou a--;
a = b * c;
a = b / c;
a = b / c;
a = b % c;
a = b + c;
a = b – c;
a = b >> n;
a = b << n;
a>b
a >= b
a<b
a <= b
a == b
a != b
a = b & c;
a = b | c;
a = b ^ c;
flag1 && flag2
flag1 || flag2
a = b;
a OP= b;
2.8 – Expressões
Operadores, constantes e variáveis constituem expressões. Uma expressão em C é
qualquer combinação válida dessas partes. Uma vez que muitas expressões tendem a seguir
as regras gerais da álgebra, estas regras são freqüentemente consideradas. Entretanto,
existem alguns aspectos das expressões que estão especificamente relacionadas com a
linguagem C e serão discutidas agora.
2.8.1 – Conversões de Tipos em Expressões
Quando constantes e variáveis de tipos diferentes são misturadas em uma expressão,
elas são convertidas para o mesmo tipo. O compilador C converterá todos os operandos
para o tipo do operando maior. Isso é feito na base de operação a operação, como descrito
nestas regras de conversão de tipos:
Todos os chars e shor ints são convertidos para ints.
Todos os floats são convertidos para doubles;
Para todos os pares de operandos, se um deles é um long double, o outro operando é
convertido para uma long double. Se um dos operandos é double, o outro é convertido
para double. Se um é long, o outro é convertido para long. Se um é unsigned, o outro é
convertido para unsigned.
Uma vez que essas regras de conversão tenham sido aplicadas, cada par de
operandos será do mesmo tipo e o resultado de cada operação terá o mesmo tipo dos dois
operandos. Note que a regra 2 tem muitas condições que devem ser aplicada em seqüência.
Por exemplo, considere a conversão de tipos que ocorre na expressão a seguir:
Primeiro, o caractere ch é convertido para um inteiro e float f é convertido para
double. Então, o resultado de ch/i é convertido para um double, já que f * d é um double.
O resultado final é um double, tendo em vista que, neste caso, os dois operandos são
double.
2.8.2 - Modeladores (CASTS)
É possível forçar uma expressão a ser de um tipo específico usando-se uma
construção chamada de modelador. A forma geral de um modelador é:
(tipo) expressão
(float) x / 2;
Onde tipo é um dos tipos dado-padrão da linguagem C. Por exemplo, se x é um
inteiro e você quer certificar-se de que a expressão x/2 resulta em um tipo float, garantindo
um componente fracionário, pode escrever:
Aqui, o modelador (float) é associado com o x, o que faz com que o 2 seja elevado
ao tipo float e o resultado seja um float. Entretanto, tome cuidado: se você escrever a
expressão acima como segue, o componente fracionário não será considerado:
(float) (x / 2);
float.
Neste caso, uma divisão de inteiros é levada a cabo e o resultado é transformado em
Modeladores são freqüentemente considerados como operadores. Como um
operador, um modelador é unário e tem a mesma precedência de qualquer outro operador
unário.
2.8.3 – Espaçamento e Parênteses
Pode-se colocar espaços numa expressão para torná-la mais legível. O uso de
parênteses redundantes ou adicionais não causará erros ou diminuirá a velocidade de
execução da expressão. É estimulado o uso de parênteses para tornar clara e exata a ordem
de avaliação, tanto para você como para outros que precisarem entender o seu programa
mais tarde. Por exemplo, as duas expressões seguintes são as mesmas:
x=645/(num_entry)-y*(3217/balance);
x = 645 / (num_entry) – y * (3127 / balance);
Qual das duas expressões seguintes é mais fácil de ler?
x=y/3-24*temp-127;
x = (y/3) – (34*temp) – 127;
2.9 – Síntese do Módulo
É apresentada, a seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
1 2.1 – Palavras Reservadas em Linguagem C: foi visto neste item, que na
Linguagem C existem palavras que são de uso reservado, ou seja, referenciam comandos ou
ações e, portanto, não podem ser usadas pelo programador para referenciar por exemplo
uma variável em um determinado programa. Foi apresentado uma tabela relacionando as
palavras reservadas da Linguagem C.
2 2.2 – Nomes e Identificadores Usados na Linguagem C: a Linguagem C
chama o que é usado para referenciar variáveis, funções, rótulos e vários outros objetos
definidos pelo usuário de identificadores. Em C os primeiros 32 caracteres de um nome de
identificador são significantes. Um identificador não pode ser o mesmo que uma palavra
reservada e não devem ter o mesmo nome de uma função.
3 2.3 – Tipos e Dados: variáveis são instâncias em que serão armazenados
valores utilizados durante a execução de programas. Estas variáveis podem ser modificadas
para suportar diferentes tipos de dados. Foi visto que os tipos básicos de dados da
Linguagem C são: char, int, float, doublé, void.
4 2.3.1 – Modificadores de Tipos: possuindo os tipos básicos de dados, pode-se
ainda formatá-los para atender melhor as necessidades de cada situação. Com exceção do
tipo void, todos os outros tipos básicos podem ter modificadores precedendo-os. Foi
apresentado uma tabela mostrando todas as combinações permitidas dos tipos básicos e
dos modificadores de tipo.
5 2.4 – Definição de Variáveis: foi visto que variáveis são instâncias onde o
programa em execução coloca os dados que estão sendo processados durante sua execução.
As variáveis devem ser declaradas, ou seja, devem ser definidos nome, tipo e algumas
vezes seu valor inicial. As variáveis são classificadas em variáveis locais e globais.
6 2.5 – Definição de Constantes: definimos uma constante quando atribuímos
um certo valor constante à um nome. Quando este nome é referenciado dentro de um
programa, será utilizado nas operações o valor atribuído a este nome.
7 2.5.1 – Constantes Hexadecimais e Octais: em programação algumas vezes é
comum usar um sistema de numeração baseado em 8 ou 16 em vez de 10. Por causa da
freqüência com que estes dois sistemas numéricos são usados, a linguagem C permite que
se especifique constantes inteiro em hexadecimal ou octal em vez de decimal, se preferir.
8 2.5.2 – Constantes Strings: Outro tipo de constante suportada pela Linguagem
C é o tipo string. Uma string é um conjunto de caracteres entre aspas. Por exemplo, “você é
um vencedor” é uma string.
9 2.5.3 – Códigos de Barra Invertida: colocar todas as constantes caractere entre
aspas funciona para muitos caracteres, mas alguns, como o retorno de carro, são
impossíveis de serem inseridos em uma string a partir do teclado. Por isso, a linguagem C
fornece constantes caractere mais barra invertida especiais. Estes códigos foram mostrados
em uma tabela.
10 2.6 – Operadores: Um operador é um símbolo que diz ao compilador para
realizar manipulações matemáticas e lógicas específicas. A linguagem C possui três classes
gerais de operadores: aritméticos, relacionais e lógicos e bit-a-bit.
11 2.6.1 – Operador de atribuição: o operador “=” atribui um valor ou resultado
de uma expressão contida a sua direita para a variável especificada a sua esquerda.
12 2.6.2 – Operadores Aritméticos: são aqueles que operam sobre números e
expressões, resultando valores numéricos.
13 2.6.3 – Operadores Relacionais: operam sobre expressões, resultando valores
lógicos de TRUE (verdadeiro) ou FALSE (falso).
14 2.6.4 – Operadores Lógicos: operam sobre expressões, resultando valores
lógicos de TRUE (verdadeiro) ou FALSE (falso). Possuem a característica de “short
circuit”, ou seja, sua execução é curta e só é executada até o ponto necessário.
15 2.6.5 – Manipulação de bits: a manipulação é feita em todos os bits da variável
a qual não pode ser do tipo float ou double. Os operadores que manipulam bits foram
relacionados em uma tabela.
16 2.6.6 – Operadores de assinalamento: assinalam o uso de uma operação,
combinando operadores de tal forma a diminuir o código e facilitar sua posterior
compreensão.
17 2.6.7 – Operadores de Pré e Pós-Incremento: operadores de pré e pósincremento são aqueles usados quando é necessário incrementar ou decrementar um
determinado valor.
18 2.6.8 – Operadores de Endereço: são operadores usados com ponteiros, para
acesso a endereços de memória.
19 2.7 – Tabela de Operadores da Linguagem C: foi apresentada uma tabela
com os operadores disponíveis na Linguagem C.
20 2.8 – Expressões: operadores, constantes e variáveis constituem expressões.
Uma expressão em C é qualquer combinação válida dessas partes.
21 2.8.1 –Conversões de Tipos em Expressões: quando constantes e variáveis de
tipos diferentes são misturadas em uma expressão, elas são convertidas para o mesmo tipo.
O compilador C converterá todos os operandos para o tipo do operando maior.
22 2.8.2 – Modeladores (CASTS): é possível forçar uma expressão a ser de um
tipo específico usando-se uma construção chamada de modelador. Modeladores são
freqüentemente considerados como operadores. Como um operador, um modelador é
unário e tem a mesma precedência de qualquer outro operador unário.
2.8.3 – Espaçamento e Parênteses: pode-se colocar espaços numa expressão para torná-la
mais legível. O uso de parênteses redundantes ou adicionais não causará erros ou diminuirá
Módulo 3 – Funções na
a velocidade de execução da expressão.
Linguagem C
3.1 – Funções
A maioria dos programas apresentam a função main. Quando os programas tornamse maiores e mais complexos, pode-se melhorar a clareza e compreensão do trabalho
dividindo-o em partes menores, chamadas funções. Vamos citar o exemplo de um
determinado programa, suponha um programa de contabilidade. Poderia haver no programa
uma função que efetuasse as operações normais de um contador, uma função diferentes
para contas a pagar, uma terceira para contas a receber e uma quarta para gerar um balanço.
Se fossem colocados todos os comandos do programa dentro da função main, o programa
ficaria muito grande, e seria difícil de compreende-lo. À medida que o tamanho e a
complexidade do programa aumentam, aumenta também a possibilidade de erros. Se o
programa for dividido em blocos menores e mais facilmente gerenciáveis, poderá evitar
erros. Uma função é uma coleção nomeada de comandos que efetuam uma tarefa
específica. Por exemplo, a função ola_mundo, usa a função printf para exibir uma
mensagem:
void ola_mundo (void)
{
printf(“Olá mundo!\n”);
}
A palavra chave void diz a Linguagem C que a função não retorna um valor. Em
muitos casos, as funções usarão return para retornar o valor de um cálculo para a função
chamadora. Se a função não usa return para retornar um resultado, deve-se preceder o
nome da função com void. O void que aparece nos parâmetros diz que a função não usa
parâmetros. Um parâmetro é a informação que o programa passa para a função. Por
exemplo, quando os programas chamam printf, as informações que se especifica dentro dos
parênteses são parâmetros. Quando uma função não usa parâmetros, deve-se colocar void
dentro dos parênteses. Para usar uma função, especifica-se o nome da função seguido de
parênteses. Os programadores referenciam o uso de uma função como uma chamada da
função. O programa a seguir, usa a função oi_pessoal:
#include <stdio.h>
void ola_pessoal(void)
{
printf("Ola pessoal!\n");
}
void main(void)
{
ola_pessoal();
}
Ao executar esse programa, a função main é executada primeiro. Como se pode ver,
o único comando em main é a chamada da função oi_pessoal. Quando C encontra a
chamada da função, imediatamente transfere a execução do programa para a função,
iniciando a execução do programa com o primeiro comando da função. Depois que o
último comando da função termina, a Linguagem C transfere a execução para o comando
que segue imediatamente a chamada da função.
Para compreender melhor esse processo, mude a função main dentro do programa
anterior como mostrado aqui:
void main(void)
{
printf(“Prestes à chamar a função\n”);
oi_pessoal();
printf(“Voltei da chamada da função\n”);
}
3.2 – Variáveis dentro das Funções
À medida que as funções vão se tornando mais úteis nos programas, muitas delas
requerem que as variáveis gerem resultados valiosos. Para usar uma variável dentro de uma
função, precisa-se primeiro declarar a variável, exatamente como feito em main.
Por exemplo, o programa a seguir chama a função três_olas, que usa a variável
contador em um laço for para exibir uma mensagem 3 vezes:
#include <stdio.h>
void tres_olas(void)
{
int contador; /* Variavel */
for (contador = 1; contador <= 3; contador++)
printf("Oi pessoal!\n");
}
void main(void)
{
tres_olas();
}
Quando se declara variáveis dentro de uma função, os nomes usados para essas
variáveis são exclusivos para a função.
Portanto, se o programa usa dez funções diferentes e cada função usa uma variável
chamada contador, a Linguagem C considera a variável de cada função como distinta. Se
uma função requer muitas variáveis, elas deverão ser declaradas no início da função,
exatamente como se faria dentro de main.
3.3 – Main como uma Função
Quando um programa é criado, usa-se o nome da função main para determinar o
primeiro comando que o programa executará. Na verdade, main é uma função, de modo
que, caso tenha perguntas sobre os tipos de operações que podem ser executadas dentro das
funções, a regra é bem simples: tudo o que se pode fazer em main, pode-se fazer em uma
função. Exatamente como pode declarar variáveis em main, também pode declarar
variáveis nas funções. Também é possível utilizar construções tais como if, while, e for em
suas funções. Finalmente, uma função pode chamar (usar) outra. Por exemplo, o programa
a seguir usa duas funções. Quando o programa inicia, main chama a função tres_olas, que,
por sua vez, chama a função oi_pessoal três vezes para exibir mensagens na tela, como
mostrado aqui:
#include <stdio.h>
void ola_pessoal(void)
{
printf("Ola, pessoal!\n");
}
void tres_olas(void)
{
int contador;
for (contador = 1; contador <= 3; contador++)
ola_pessoal();
}
void main(void)
{
tres_olas();
}
3.4 – Introdução aos Parâmetros
Um parâmetro é um valor passado a uma função. A maioria dos programas
apresentados passou parâmetros para a função printf. À medida que o uso de funções for
maior, poderá passar parâmetros para as funções de modo a torná-las mais úteis. Por
exemplo, considere a seguinte construção da função tres_olas, que chama a função
oi_pessoal três vezes:
void tres_olas(void)
{
int contador;
for (contador = 1; contador <= 3; contador++)
ola_pessoal();
}
Uma função mais útil permite especificar, como um parâmetro, o número de vezes
que quer que o programa exiba a mensagem. Para usar um parâmetro, a função precisa
especificar o nome e o tipo do parâmetro, como mostrado aqui:
void ola_conta(int msg_conta)
Neste caso, a função ola_conta suporta um parâmetro do tipo int chamado
msg_conta. Quando outra função, tal como main, quiser usar ola_conta, a função precisa
especificar o valor que a Linguagem C atribui para o parâmetro msg_conta:
ola_conta(2);
ola_conta(100);
ola_conta(1);
/*Exibe a mensagem 2 vezes*/
/*Exibe a mensagem 100 vezes*/
/*Exibe a mensagem uma vez*/
O programa a seguir ilustra como pode usar uma função com um parâmetro:
#include <stdio.h>
void oi_pessoal(void)
{
printf("Ola, pessoal!\n");
}
void ola_conta(int msg_conta)
{
int contador;
for (contador = 1; contador <= msg_conta; contador++)
oi_pessoal();
}
void main(void)
{
printf("Exibe a msg duas vezes\n");
ola_conta(2);
printf("Exibe a msg cinco vezes\n");
ola_conta(5);
}
3.5 – Parâmetros Múltiplos
Em geral, pode-se passar um número irrestrito de parâmetros para uma função. No
entanto, as pesquisas mostram que, quando o número de parâmetros excede sete, a função
mostra-se mais difícil de se compreender e usar corretamente, ficando, portanto, mais
susceptível a erros. Quando a função usa mais de um parâmetro, precisa especificar o tipo e
o nome de cada parâmetro e separar os parâmetros por vírgulas, como mostrado aqui:
void uma_funcao(int idade, float sal, int num_cargo)
{
/*Comandos da função*/
}
Quando o programa chamar a função, será necessário especificar valores para cada
parâmetro, como mostrado aqui:
uma_funcao(33, 40000.00, 534);
3.6 – Retornando um Valor de uma Função
Funções mais complexas normalmente realizam cálculos e retornam resultados. Pra
fornecer um resultado ao chamador, uma função precisará usar o comando return que será
implementado como segue:
return (resultado);
O tipo de valor que a função retorna (int, float, char, etc.) determina o tipo da
função. Por exemplo, se uma função retorna um valor do tipo int, é necessário preceder o
nome da função com o nome do tipo, como mostrado aqui:
int uma_função(int valor)
{
/*Comandos da função*/
}
A função a seguir, retorna o cubo do valor inteiro que o programa especifica como
seu parâmetro. Por exemplo, se o chamador passar o valor 5 para a função, a função
retornará o valor 5*5*5, ou 125:
int i_cubo(int valor)
{
return (valor *valor *valor);
}
Como visto, a função usa o comando return para retornar o resultado do cálculo ao
chamador. O código dentro da função de chamada pode atribuir o resultado da função
chamada (também conhecido como valor de retorno) a uma variável, ou o código pode usar
o valor de retorno dentro de uma terceira função, tal como printf, como mostrado aqui:
result = i_cubo(5);
printf(“O cubo de 5 é %d\n”, I_cubo(5));
O programa a seguir usa a função i_cubo para determinar vários valores diferentes
ao cubo:
#include <stdio.h>
int i_cubo(int valor)
{
return(valor * valor * valor);
}
void main(void)
{
printf("O cubo de 3 ‚ %d\n", i_cubo(3));
printf("O cubo de 5 ‚ %d\n", i_cubo(5));
printf("O cubo de 7 ‚ %d\n", i_cubo(7));
}
Os valores que são passados para uma função precisam corresponder aos tipos de
parâmetros contidos dentro da declaração dessa função. Por exemplo, se quiser determinar
o cubo de um valor em ponto flutuante, crie uma segunda função chamada f_cubo, como
mostrado a seguir (observe que o valor de retorno também é do tipo float):
float f_cubo(float valor)
{
return (valor *valor *valor);
}
3.7 – Comando Return
Quando a Linguagem C encontra um comando return em uma função, ela finaliza
imediatamente a execução da função e retorna o valor especificado para o chamador. O
programa não executa quaisquer outros comandos dentro da função após o comando
return. Em vez disso, ele continua a execução da função chamadora.
Outros programas possuem funções que contêm múltiplos comandos return, cada
um dos quais retornando um valor para uma condição específica. Por exemplo, considere a
função compara_valores, mostrada a seguir:
int compara_valores(int primeiro, int segundo)
{
if (primeiro == segundo)
return (0);
else if (primeiro > segundo)
return (1);
else if (primeiro < segundo)
return (2);
}
A função compara_valores examina dois valores listados na tabela abaixo:
Resultado
0
1
2
Significado
Os valores são iguais
O primeiro valor é maior que o segundo
O segundo valor é maior que o primeiro
Como regra, deve-se tentar limitar as funções a usar somente um comando return.
À medida que as funções se tornarem maiores e mais complexas, ter muitos comandos
return normalmente tornará as funções mais difíceis de compreender. Na maioria dos
casos, pode-se reescrever a função para que ela use somente um comando return, como
mostrado aqui:
int compara_valores(int primeiro, int segundo)
{
int result;
if (primeiro == segundo)
return = 0;
else if (primeiro > segundo)
return = 1;
else if (primeiro < segundo)
return = 2;
return(result);
}
Neste caso, como a função é simples, pode-se ter dificuldades em compreender qual
a vantagem de usar um único comando de return. No entanto, à medida que as funções se
tornarem mais complexas, a vantagem ficará mais clara. Pode-se observar que, algumas
vezes, usar mais de um comando return produz um código mais legível do que a alternativa
de um único return. Deve-se escrever o código mais legível e facilmente modificável
quanto possível; se usar múltiplos return atingir os objetivos pré-estabelecidos, então utilize
quantos comandos return forem necessários.
3.8 – Protótipos de Função
A maioria dos novos compiladores C precisa conhecer os tipos de retorno e de
parâmetro de uma função antes do programa chamar a função.Colocando as funções antes
dos seus chamadores dentro do código do seu programa, permitirá que o compilador C
conheça as informações que ele deverá ter antes de encontrar a chamada da função. No
entanto, à medida que os programas ficam mais complexos, pode se tornar impossível
colocar as funções sempre na ordem correta. Portanto, a Linguagem C permite colocar
protótipos de função no seu programa, para descrever os tipos de parâmetro e de retorno de
uma função. Por exemplo, considere um programa que use as funções i_cubo e f_cubo.
Antes que a função seja usada pela primeira vez, o programa pode incluir um protótipo
semelhante ao seguinte:
int i_cubo(int);
float f_cubo(float);
/*Retorna um parâmetro int*/
/* Retorna um parâmetro float*/
Como pode-se ver, o protótipo da função especifica os tipos de parâmetro e de
retorno da função. O programa a seguir usa dois protótipos de função para eliminar a
necessidade de ordem da função:
#include <stdio.h>
int i_cubo(int);
float f_cubo(float);
void main(void)
{
printf("O cubo de 3 ‚ %d\n", i_cubo(3));
printf("O cubo de 3.7 ‚ %f\n", f_cubo(3.7));
}
int i_cubo(int valor)
{
return(valor * valor * valor);
}
float f_cubo(float valor)
{
return(valor * valor * valor);
}
3.9 – Biblioteca de Execução
Muitas vezes, uma função criada para um determinado programa atende as
necessidades de um segundo programa. A capacidade de reutilizar as funções em mais de
um programa pode poupar um tempo considerável de programação e teste. Para isto é só
copiar a função de um para outro programa.
Antes de gastar um tempo enorme desenvolvendo uma ampla variedade de funções
de propósito geral, não deixe de examinar as funções que seu compilador fornece. Muitos
compiladores referenciam essas funções internas como biblioteca de execução. A maioria
dos compiladores fornece centenas de funções de biblioteca de execução com propósito que
vão de abertura e trabalho com arquivos para acessar informações do disco ou de diretório
para determinar o tamanho de uma string de caracteres. As duas ou três horas que serão
gastas para ler a documentação da biblioteca de execução pouparão muitas horas de
programação.
3.10 – Funções que não retornam Int
Muitas funções retornam valores do tipo int. Quando uma determinada função não
retorna um valor do tipo int (em vez disso ela pode retornar float, double, char, etc.)
precisa-se informar ao compilador o tipo de retorno da função. O programa a seguir usa a
função valor_medio para determinar a média de três valores do tipo int. A função retorna a
média usando um valor do tipo float (repare que o cabeçalho da função especifica o tipo de
retorno da função):
#include <stdio.h>
float valor_medio(int a, int b, int c)
{
return ((a + b + c) / 3.0);
}
void main(void)
{
printf("A média de 100, 133 e 155 ‚ %f\n",
valor_medio(100, 133, 155));
}
3.11 – Variáveis Locais
A Linguagem C permite declarar variáveis dentro de suas funções. Essas variáveis
são chamadas de variáveis locais, pois seus nomes e valores somente têm significado dentro
da função que contém a declaração da variável. O programa a seguir ilustra o conceito de
uma variável local. A função valores_locais declara 3 variáveis a, b e c, e atribui às
variáveis os valores 1, 2 e 3, respectivamente. A função main tenta imprimir o valor de
cada variável. No entanto, como os nomes dos valores são locais à função, o compilador
gera erros, dizendo que os símbolos a, b, e c estão indefinidos.
#include <stdio.h>
void valores_locais(void)
{
int a=1, b=2, c=3;
printf("a cont‚m %d b cont‚m %d c cont‚m %d\n", a,b,c);
}
void main(void)
{
printf("a cont‚m %d b cont‚m %d c cont‚m %d\n", a,b,c);
}
3.12 - Sobrecarga da Função
Quando um programa usa uma função, a Linguagem C armazena o endereço de
retorno, os parâmetros e as variáveis locais na pilha. Quando a função termina, a
Linguagem C descarta o espaço da pilha que continha as variáveis locais e parâmetros, e,
depois, usa o valor de retorno para retornar a execução do programa para a posição correta.
Embora o uso da pilha seja poderoso porque permite que o programa chame e passe
as informações para as funções, também consome tempo de processamento. Os
programadores chamam a quantidade de tempo que o computador requer para colocar e
retirar informações da pilha de sobrecarga da função. Para compreender melhor o impacto
da sobrecarga da função no desempenho do seu programa, considere o programa a seguir.
Primeiro o programa usa um laço para somar os valores de 1 a 100.000. Em seguida, repete
um laço novamente, mas usa uma função para somar os valores, como mostrado aqui:
#include <stdio.h>
#include <time.h>
float soma(long int a, float b)
{
float result;
result = a + b;
return(result);
}
void main(void)
{
long int i;
float result = 0;
time_t hora_inicio, hora_parada;
printf("Trabalhando...\n");
time(&hora_inicio);
for (i = 1; i <= 100000L; i++)
result += i;
time(&hora_parada);
printf("Usando laço %d segundos\n", hora_parada - hora_inicio);
printf("Trabalhando...\n");
time(&hora_inicio);
for (i = 1; i <= 100000L; i++)
result = soma(i, result);
time(&hora_parada);
printf("Usando função %d segundos \n", hora_parada hora_inicio);
}
Na maioria dos sistemas, os cálculos baseados em funções podem requerer quase o
dobro do tempo de processamento. Portanto, quando usar funções dentro dos programas, é
necessário considerar os benefícios que elas oferecem versus a sobrecarga no desempenho
que introduzem.
3.13 – Declarando Variáveis Globais
Além das variáveis locais, a Linguagem C permite que os programas usem variáveis
globais, cujos nomes, valores e existência são conhecidos em todo o programa. Em outras
palavras, todos os programas em Linguagem C podem usar variáveis globais. O programa a
seguir ilustra o uso de três variáveis globais a, b e c:
#include <stdio.h>
int a = 1, b = 2, c = 3;
/* Variaveis globais */
void valores_globais(void)
{
printf("a cont‚m %d b cont‚m %d c cont‚m %d\n", a, b, c);
}
void main(void)
{
valores_globais();
printf("a cont‚m %d b cont‚m %d c cont‚m %d\n", a, b, c);
}
Quando este programa é compilado e executado, as funções variaveis_globais e
main exibem os valores da variável global. Observe que as variáveis globais são declaradas
fora de todas as funções. Declarando variáveis globais deste modo, todas as funções do
programa podem usar e alterar os valores da variável global simplesmente referenciando o
nome da variável global. Embora as variáveis globais possam parecer convenientes, o uso
incorreto delas podem causar erros que são difíceis de depurar.
3.14 – Solucionando os Conflitos de Nomes de Variáveis Locais e
Globais
Se um programa usa variáveis globais, algumas vezes o nome de uma variável
global é o mesmo que aquele de uma variável local que seu programa declara dentro de
uma função. Por exemplo, o programa a seguir usa as variáveis globais a, b e c. A função
conflito_a usa uma variável local chamada a e as variáveis globais b e c:
#include <stdio.h>
int a=1, b=2, c=3;
/* Variáveis globais */
void conflito_a(void)
{
int a=100;
printf("a cont‚m %d b cont‚m %d c cont‚m %d\n", a, b, c);
}
void main(void)
{
conflito_a();
printf("a cont‚m %d b cont‚m %d c cont‚m %d\n", a, b, c);
}
Quando nomes de variáveis globais e locais estiverem em conflito, a Linguagem C
usará sempre a variável local. Como você pode ver, as alterações que a função conflito_a
fez na variável a somente aparecem dentro da função.
3.15 – Chamada por Valor
Os programas passam informações para funções usando parâmetros. Quando um
parâmetro é passado a uma função, a Linguagem C usa uma técnica conhecida como
chamada por valor para fornecer à função uma cópia dos valores dos parâmetros. Usando a
chamada por valor, quaisquer modificações que a função fizer nos parâmetros existem
apenas dentro da própria função. Quando a função termina, o valor das variáveis que a
função chamadora passou para a função não é modificada dentro da função chamadora. Por
exemplo, o programa a seguir passa três parâmetros (as variáveis a, b e c) para a função
exibe_e_altera. A função, por sua vez, exibirá os valores, somará 100 aos valores e depois
exibirá o resultado. Quando a função terminar, o programa exibirá os valores das variáveis.
Como a Linguagem C usa chamada por valor, a função não altera os valores das variáveis
dentro do chamador, como mostrado a seguir:
#include <stdio.h>
void exibe_e_altera(int primeiro, int segundo, int terceiro)
{
printf("Valores originais da função %d %d %d\n",
primeiro, segundo, terceiro);
primeiro += 100;
segundo += 100;
terceiro += 100;
printf("Valores finais da função %d %d %d\n", primeiro,
segundo, terceiro);
}
void main(void)
{
int a = 1, b = 2, c = 3;
exibe_e_altera(a, b, c);
printf("Valores finais em main %d %d %d\n", a,b,c);
}
Como pode ser visto, as alterações que a função faz nas variáveis somente são
visíveis dentro da própria função. Quando a função termina, as variáveis dentro de main
estão inalteradas.
3.16 - Chamada por Referência
Usando a chamada por valor, as funções não podem modificar o valor de uma
variável passada para uma função. No entanto, na maioria dos programas, as funções
modificarão as variáveis de um modo ou de outro. Por exemplo, uma função que lê
informações de um arquivo precisa colocar as informações em uma matriz de string de
caracteres. Da mesma forma, uma função tal como strupr precisa converter as letras em
uma string de caractere para maiúsculas. Quando as funções alteram o valor de um
parâmetro, os programas precisam passar o parâmetro para a função usando chamada por
referência. A diferença entre chamada por valor e chamada por referência é que, usando a
chamada por valor, as funções recebem uma cópia do valor de um parâmetro. Por outro
lado, com a chamada por referência, as funções recebem o endereço de memória da
variável. Portanto, as funções podem alterar o valor armazenado na posição de memória
específica (em outras palavras, o valor da variável); alterações essas que permanecem após
a função terminar. Para usar a chamada por referência, seu programa precisar usar
Ponteiros. O Módulo sobre Ponteiros, mais adiante, discute os Ponteiros em detalhes. No
entanto, por ora, pense em um Ponteiro simplesmente como um endereço de memória. Para
atribuir o endereço de uma variável a um ponteiro, é necessário usar o operador de
endereço "&". Para acessar posteriormente o valor na posição de memória para o qual o
Ponteiro aponta, use o operador de direção "*".
3.17 – Obtendo um Endereço
Uma variável é essencialmente um nome atribuído a uma ou mais posições de
memória. Quando um programa roda, cada variável reside em um seu próprio endereço de
memória. O programa localiza as variáveis na memória usando o endereço de memória da
variável. Para determinar o endereço de uma variável, usa-se o operador de endereço "&".
Por exemplo, o programa a seguir usa o operador de endereço para exibir o endereço (em
hexadecimal) das variáveis a., b e c:
#include <stdio.h>
void main(void)
{
int a=1, b=2, c=3;
printf("O endereço de a ‚ %x o valor de a ‚ %d\n", &a, a);
printf("O endereço de b ‚ %x o valor de b ‚ %d\n", &b, b);
printf("O endereço de c ‚ %x o valor de c ‚ %d\n", &c, c);
}
Para os programas passarem parâmetros para funções cujos valores a função precisa
alterar, os programas passarão as variáveis por referência (endereço de memória), usando o
operador de endereço, como mostrado a seguir:
alguma_funcao(&a, &b, &c);
3.18 – Usando um Endereço de Memória
Quando se passa um endereço para uma função, precisa-se informar ao compilador
que a função estará usando um ponteiro (o endereço de memória) de uma variável, e não o
valor de uma variável. Para fazer isto, é necessário declarar uma variável ponteiro. Declarar
uma variável ponteiro é muito similar à declaração de uma variável padrão, em que se
especifica um tipo e o nome da variável. A diferença, no entanto, é que um asterisco (*)
precede os nomes das variáveis ponteiro. As declarações a seguir criam variáveis ponteiro
do tipo int, float e char:
int *i_ponteiro;
float *f_ponteiro;
char *c_ponteiro
Após declarar uma variável ponteiro, precisa atribuir um endereço de memória à
ela. Por exemplo, o comando a seguir, atribui o endereço da variável inteira a à variável
ponteiro i_ponteiro:
i_ponteiro = &a;
Em seguida, para usar o valor apontado para a variável ponteiro, os programas
precisam usar o operador de redireção da Linguagem C - o asterisco (*). Por exemplo, o
comando a seguir atribui o valor 5 à variável a (cujo endereço está contido na variável
i_ponteiro):
i_ponteiro = 5;
De um modo similar, o comando a seguir atribui à variável b o valor ao qual a
variável i_ponteiro aponta atualmente:
b = *i_ponteiro;
Quando quiser usar o valor apontado por uma variável ponteiro, usa-se o operador
de redireção (*). Quando quiser atribuir o endereço de uma variável à uma variável
ponteiro, usa-se o operador de endereço (&). O programa a seguir ilustra o uso de uma
variável ponteiro. Ele atribui à variável ponteiro i_ponteiro o endereço da variável ª O
programa então usa a variável ponteiro para alterar, exibir e atribuir o valor da variável:
#include <stdio.h>
void main(void)
{
int a = 1, b = 2;
int *i_ponteiro;
i_ponteiro = &a;
*i_ponteiro = 5;
para 5 */
/* Atribui um endereço*/
/* Altera o valor apontado por i_ponteiro
printf("O valor apontado por i_ponteiro ‚ %d a variavel a ‚
%d\n",*i_ponteiro, a);
/* Exibe o valor */
b = *i_ponteiro;
/* Atribui o valor */
printf("O valor de b ‚ %d\n", b);
printf("Valor de i_ponteiro %x\n", i_ponteiro);
}
3.19 – Alterando o Valor de um Parâmetro
Como foi visto anteriormente, para alterar o valor de um parâmetro dentro de uma
função, os programas precisam usar a chamada por referência, passando o endereço da
variável. Dentro da função, precisa-se usar ponteiros. O programa a seguir usa ponteiros e
endereços (chamada por referência) para exibir e, depois, alterar os parâmetros que o
programa passa para a função exibe_e_altera:
#include <stdio.h>
void exibe_e_altera(int *primeiro, int *segundo, int *terceiro)
{
printf("Valores originais da função %d %d %d\n",*primeiro,
*segundo, *terceiro);
*primeiro += 100;
*segundo += 100;
*terceiro += 100;
printf("Valores finais da função %d %d %d\n",*primeiro,
*segundo, *terceiro);
}
void main(void)
{
int a=1, b=2, c=3;
exibe_e_altera(&a, &b, &c);
printf("Valores finais em main %d %d %d\n", a, b ,c);
}
3.20 – Alterando Somente Parâmetros Específicos
Como foi visto, as funções podem modificar o valor de um parâmetro usando a
chamada por referência. Por exemplo, o item anterior, apresentou a função exibe_e_altera,
que usou chamada por referência para alterar o valor de cada um de seus parâmetros. No
entanto, em muitos casos, suas funções podem alterar o valor de um parâmetro e ao mesmo
tempo deixar o valor de um segundo parâmetro inalterado. Por exemplo, o programa a
seguir usa a função muda_primeiro para atribuir ao parâmetro primeiro o valor do
parâmetro segundo:
#include <stdio.h>
void muda_primeiro(int *primeiro, int segundo)
{
*primeiro = segundo;
/* Atribui o valor de segundo a primeiro */
}
void main(void)
{
int a = 0, b = 5;
muda_primeiro(&a, b);
printf("Valor de a %d
valor de b %d\n", a, b);
}
Como pode ser visto, a função muda_primeiro usa a chamada por referência para
alterar o valor do parâmetro primeiro, e chamada por valor para o parâmetro segundo.
Quando os programas usam ambas as técnicas, precisa ter-se em mente quando usar
ponteiros e quando referenciar diretamente a variável. Como regra, os parâmetros cujos
valores serão alterados irão requerer chamada por referência. Para compreender melhor o
impacto da chamada por referência versus chamada por valor, modifique a função
muda_primeiro, como mostrado aqui:
void muda_primeiro(int *primeiro, int segundo)
{
*primeiro = segundo; /*Atribui o valor de segundo a primeiro*/
segundo = 100;
}
Quando este programa for compilado e executado, verá que o valor de primeiro foi
alterado, mas o valor de segundo não foi. Como o parâmetro segundo foi passado usando
chamada por valor, a alteração do parâmetro é visível fora da função.
3.21 – Funções Recursivas
Em C, as funções podem chamar a si próprias. Uma função é recursiva se um
comando no corpo da função chama ela mesma. Algumas vezes chamada de definição
circular, a recursividade é o processo de definição de algo em termos de si mesmo.
Exemplos de recursividade existem em grande número. Uma maneira de definir um
número inteiro sem sinal por meio de recursividade é utilizando-se os dígitos 0, 1, 2, 3, 4, 5,
6, 7, 8, 9 mais ou menos outro número inteiro. Por exemplo, o número 15 é o número 7
mais o número 8; 21 é 9 mais 12 e 12 é 9 mais 3.
Para uma linguagem ser recursiva, uma função deve estar apta a chamar a si própria.
O exemplo clássico de recursividade é mostrado na função fatorial_recursivo(), que calcula
o fatorial de um número inteiro. O fatorial de um número N é o produto de todos os
números inteiros entre 1 e N. Por exemplo, o fatorial de 3 é 1 x 2 x 3, ou 6. Tanto a função
fatorial_recursivo( ) como sua equivalente interativa são mostradas aqui:
#include <stdlib.h>
#include <stdio.h>
unsigned long int fatorial_recursivo (int n){
unsigned long int resposta;
if ((n == 1) || (n == 0))return(1);
resposta = n * fatorial_recursivo(n – 1);
return(resposta);
void main()
{
unsigned long f;
int n;
printf(“Digite um número: ”);
scanf(“%d”,&n);
f = fatorial_recursivo(n);
printf(“O fatorial de %d é %ld\n”, n, f);
}
#include <stdlib.h>
#include <stdio.h>
unsigned long int fatorial (int n){
unsigned long int t, resposta;
resposta = 1;
for (t = 1; t < n; t++) resposta = resposta * t;
return(resposta);
void main()
{
unsigned long f;
int n;
printf(“Digite um número: ”);
scanf(“%d”,&n);
f = fatorial(n);
printf(“O fatorial de %d é %ld\n”, n, f);
}
Quando uma função chama a si própria, as novas variáveis locais e os argumentos
são alocados na pilha, e o código da função é executado com esses novos valores a partir do
início. Uma chamada recursiva não faz uma nova cópia da função. Somente os argumentos
e as variáveis são novos. Quando cada chamada recursiva retorna, as antigas variáveis
locais e os parâmetros são removidos da pilha e a execução recomeça no ponto de chamada
da função dentro da função.
Tendo em vista que o local para os argumentos de funções e para as variáveis locais
é a pilha e que a cada nova chamada é criado uma cópia destas variáveis na pilha, é
possível ocorrer overflow da pilha (stack overflow) e o programa terminar com um erro.
3.22 – Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
24 3.1 – Funções: é dada uma explicação detalhada sobre as características gerais
de funções na Linguagem C.
25 3.2 –Variáveis dentro das Funções: foi visto que é possível e é muito valioso o
uso de variáveis dentro das funções. Foi mostrado que as variáveis declaradas dentro de
uma função, são válidas apenas para aquela função.
26 3.3 –Main como uma Função: foi visto que quando um programa é criado,
usa-se o nome da função main para determinar o primeiro comando que o programa
executará. A função main é a principal função em um programa.
27 3.4 – Introdução aos Parâmetros: foi visto que um parâmetro é uma valor
passado a uma função. Com eles a função se torna mais útil ao programa e facilita vária
operações.
28 3.5 – Parâmetros Múltiplos: foi mostrado que em geral, pode-se passar um
número irrestrito de parâmetros para uma função. Quando a função usa mais de um
parâmetro, precisa especificar o tipo e o nome de cada parâmetro e separar os parâmetros
por vírgulas.
29 3.6 – Retornando um Valor de uma Função: foi visto que funções usam o
comando return para retornar o resultado do cálculo ao chamador. O tipo de valor que a
função retorna (int, float, char, etc.) determina o tipo da função. Os valores que são
passados para uma função precisam corresponder aos tipos de parâmetros contidos dentro
da declaração dessa função.
30 3.7 – Comando Return: foi visto que quando a Linguagem C encontra um
comando return em uma função, ela finaliza imediatamente a execução da função e retorna
o valor especificado para o chamador. O programa não executa quaisquer outros comandos
dentro da função após o comando return. Em vez disso, ele continua a execução da função
chamadora.
31 3.8 – Protótipos de Função: foi explicado que o protótipo de uma função
especifica os tipos de parâmetro e de retorno da função.
32 3.9 – Biblioteca de Execução: foi explicado que a maioria dos compiladores
fornece centenas de funções de biblioteca de execução com propósito que vão de abertura e
trabalho com arquivos para acessar informações do disco ou de diretório para determinar o
tamanho de uma string de caracteres, poupando horas de trabalho de programação.
33 3.10 – Funções que não Retornam Int: foi visto que quando uma determinada
função não retorna um valor do tipo int (em vez disso ela pode retornar float, double, char,
etc.) precisa-se informar ao compilador o tipo de retorno da função.
34 3.11 – Variáveis Locais: foi explicado que variáveis são locais, quando seus
nomes e valores somente têm significado dentro da função que contém a declaração da
variável.
35 3.12 – Sobrecarga da Função: foi visto que a quantidade de tempo que o
computador requer para colocar e retirar informações da pilha de sobrecarga da função.
Este tempo tem um papel fundamental no desempenho dos programas em execução.
36 3.13 – Declarando Variáveis Globais: foi visto que variáveis globais são
aquelas cujos nomes, valores e existência são conhecidos em todo o seu programa. Foi visto
também como declará-las.
37 3.14 – Solucionando os Conflitos de Nomes de Variáveis Locais e Globais:
foi visto que quando nomes de variáveis globais e locais estiverem em conflito, a
Linguagem C usará sempre a variável local.
38 3.15 – Chamada por Valor: foi explicado que na chamada por valor, quaisquer
modificações que a função fizer nos parâmetros existem apenas dentro da própria função.
Quando a função termina, o valor das variáveis que a função chamadora passou para a
função não é modificada dentro da função chamadora.
39 3.16 – Chamada por Referência: foi visto que quando as funções alteram o
valor de um parâmetro, os programas precisam passar o parâmetro para a função usando
chamada por referência.
40 3.17 – Obtendo um Endereço: foi visto que para determinar o endereço de
uma variável, usa-se o operador de endereço "&".
41 3.18 – Usando um Endereço de Memória: foi visto que quando se passa um
endereço para uma função, precisa-se informar ao compilador que a função estará usando
um ponteiro (o endereço de memória) de uma variável, e não o valor de uma variável. Para
fazer isto, é necessário declarar uma variável ponteiro.
42 3.19 – Alterando o Valor de um Parâmetro: foi mostrado um programa que
usa ponteiros e endereços (chamada por referência) para exibir e, depois, alterar os
parâmetros que o programa passa para uma função.
43 3.20 – Alterando Somente Parâmetros Específicos: foi mostrado como fazer
para que as funções alterem o valor de um parâmetro e ao mesmo tempo deixe o valor de
um segundo parâmetro inalterado.
44 3.21 – Funções Recursivas: foi explicado que uma função é recursiva se um
comando no corpo da função chama ela mesma. Além disso, foi dada uma visão geral sobre
funções recursivas.
Módulo 4 – Funções PRINTF() e SCANF()
4.1 – Função PRINTF()
Quando é necessário imprimir na tela um dado, um valor, uma string ou mesmo um
endereço de memória, usa-se a função printf(). Quando o programa usa a função printf(), as
informações que instrui printf() a imprimir são chamadas parâmetros ou argumentos de
printf(). A estrutura básica da função printf() é dada a seguir:
printf ("string de controle", lista de argumentos);
A string de controle contém tanto caracteres para serem impressos na tela como
códigos de formato que especificam como apresentar o restante dos argumentos. Quando os
programas trabalharem com variáveis, será possível usar printf() para exibir os valores de
cada variável. A função printf() suporta mais de um parâmetro. O primeiro parâmetro
precisa ser sempre uma string de caracteres. Pode-se fazer os parâmetros que seguem a
primeira string de caracteres serem números, expressões, variáveis, ou até outras strings de
caracteres. Quando for necessário que printf() exiba um valor ou uma variável, é preciso
fornecer informações sobre o tipo da variável dentro do primeiro parâmetro. Além de
especificar caracteres dentro do primeiro parâmetro, pode-se incluir especificadores de
formato, que instruem printf() como imprimir os outros parâmetros. Esses especificadores
de formato têm a forma de um sinal de porcentagem (%) seguido por uma letra. Por
exemplo, para exibir um valor inteiro, usa-se o %d. Da mesma forma para exibir um valor
em ponto flutuante, pode-se usar %f. A seguir, uma tabela com os especificadores de
formato:
Código
%c
%d
%i
%e
%E
%f
%g
%G
%o
%s
%u
%x
%X
%%
%p
Significado
Exibe um caractere
Exibe um inteiro em formato decimal
Exibe um inteiro
Exibe um número em notação científica (com e minúsculo)
Exibe um número em notação científica (com E maiúsculo)
Exibe um ponto flutuante em formato decimal
Usa %e ou %f, o que for menor
O mesmo que %g, só que um E maiúsculo é usado se o formato %e
for escolhido
Exibe um número em notação octal
Exibe uma string
Exibe um decimal sem sinal
Exibe um número em hexadecimal com letras minúsculas
Exibe um número em hexadecimal com letras maiúsculas
Exibe um sinal de %
Exibe um ponteiro
4.1.1 – Exibindo Valores do Tipo int Usando Printf()
Para exibir valores do tipo int com printf(), usa-se o especificador de formato %d. O
programa a seguir usa o especificador de formato %d para exibir valores e variáveis do tipo
int:
#include <stdio.h>
void main(void)
{
int idade = 41;
int altura = 182;
int peso = 80;
printf("Idade do usuario: %d peso: %d altura: %d\n", idade, peso,
altura);
printf("%d mais %d igual a %d\n", 1, 2, 1 + 2);
}
Uma observação importante é que muitos compiladores C tratam o especificador de
formato %i como idêntico a %d. No entanto, para criar-se novos programas , use o
especificador %d, pois %i é um legado do passado, e os compiladores futuros talvez
deixem de aceitá-lo.
4.1.2 – Exibindo um Valor Inteiro Octal ou Hexadecimal
Muitas vezes nos programas é preciso exibir um valor inteiro em seu formato octal
(base 8) ou hexadecimal (base 16). O especificador de formato %o (letra "o", não zero)
instrui printf() a exibir uma valor em octal. Da mesma forma, %x e %X instruem printf() a
exibir uma valor em hexadecimal, sendo que no primeiro caso em minúsculo e no segundo
imprime os valores em maiúsculo.
Veja a seguir um exemplo que utiliza estes especificadores de formato:
#include <stdio.h>
void main(void)
{
int valor = 255;
printf("O valor decimal %d em octal ‚ %o\n", valor, valor);
printf("O valor decimal %d em hexadecimal ‚ %x\n", valor, valor);
printf("O valor decimal %d em hexadecimal ‚ %X\n", valor, valor);
}
4.1.3 – Exibindo Valores do Tipo Unsigned Int
Para exibir valores do tipo unsigned int com a função printf(), deve-se usar o
especificador de formato %u. Se usar %d em lugar de %u, printf() tratará o valor
especificado como tipo int, provavelmente exibindo o valor incorreto. O exemplo a seguir
ilustra o uso dos dois especificadores, %d e %u.
#include <stdio.h>
void main(void)
{
unsigned int valor = 42000;
printf("Exibindo 42000 como unsigned %u\n", valor);
printf("Exibindo 42000 como int %d\n", valor);
}
4.1.4 – Exibindo Valores do Tipo Long Int
Para exibir valores do tipo long int com a função printf(), deve-se usar o
especificador de formato %ld. Se usar %d em lugar de %ld, printf() tratará o valor
especificado como tipo int, provavelmente exibindo o valor incorreto.
O exemplo a seguir ilustra o uso dos dois especificadores, %d e %ld:
#include <stdio.h>
void main(void)
{
long int um_milhao = 1000000;
printf ("Um milhão é %ld\n", um_milhao);
printf ("Um milhão é %d\n", um_milhao);
}
4.1.5 – Exibindo Valores do Tipo Float
Para exibir valores do tipo float com a função printf(), deve-se usar o especificador
de formato %f. A seguir um exemplo que usa %f para exibir valores em ponto flutuante.
#include <stdio.h>
void main(void)
{
float preco = 525.75;
float imposto_vendas = 0.06;
printf("O custo do item ‚ %f\n", preco);
printf("O imposto sobre a venda do item ‚ %f\n", preco *
imposto_vendas);
}
4.1.6 – Exibindo Valores do Tipo Char
Para exibir valores do tipo char com a função printf(), deve-se usar o especificador
de formato %c. A seguir um exemplo que usa %c para exibir a letra A em sua tela.
#include <stdio.h>
void main(void)
{
printf("A letra ‚ %c\n", 'A');
printf("A letra ‚ %c\n", 65);
}
4.1.7 – Exibindo Valores de Ponto Flutuante em um Formato Exponencial
Para exibir um valor em ponto flutuante em um formato exponencial com a função
printf(), deve-se usar o especificador de formato %e ou %E. A diferença entre os dois é
que %E instrui printf() a usar uma letra E maiúscula na saída.
#include <stdio.h>
void main(void)
{
float pi = 3.14159;
float raio = 2.0031;
printf("A área do círculo é %e\n", 2 * pi * raio);
printf("A área do círculo é %E\n", 2 * pi * raio);
}
4.1.8 – Exibindo Valores em Ponto Flutuante
A função printf() também suporta os especificadores %g e %G para imprimir
valores em ponto flutuante. Quando usa-se esses especificadores de formato, printf()
decide se deve usar o formato %f ou %e, dependendo da técnica que exibirá a saída no
formato mais significativo para o usuário.
Veja o exemplo que ilustra o uso do especificador %g:
#include <stdio.h>
void main(void)
{
printf("Exibir 0.1234 resulta em %g\n", 0.1234);
printf("Exibir 0.00001234 resulta em %g\n", 0.00001234);
}
4.1.9 – Exibindo uma String de Caracteres
Para exibir uma string de caracteres com a função printf(), deve-se usar o
especificador de formato %s.
A seguir um exemplo que usa %s para exibir uma string de caracteres:
#include <stdio.h>
void main(void)
{
char faculdade[255] = "Universidade Estadual Paulista";
printf("O nome da minha universidade é %s\n", faculdade);
}
4.1.10 – Exibindo um Endereço de Ponteiro
Para exibir um endereço de ponteiro com a função printf(), deve-se usar o
especificador de formato %p.
A seguir um exemplo que usa %p para exibir um endereço de memória.
#include <stdio.h>
void main(void)
{
int valor;
printf("O endereço da variável valor é %p\n", &valor);
}
4.1.11 - Precedendo um Valor com um Sinal de Adição ou de Subtração
Em muitos programas é necessário que printf() exiba o sinal para os valores
positivos e negativos. Para instruir printf() a exibir um sinal de um valor, simplesmente
inclua um sinal de adição imediatamente após o % no especificador de formato. O exemplo
a seguir ilustra o uso do sinal de adição dentro do especificador de formato.
#include <stdio.h>
void main(void)
{
int neg_int = -5;
int pos_int = 5;
float neg_flt = -100.23;
float pos_flt = 100.23;
printf("Os valores inteiros são %+d and %+d\n",neg_int, pos_int);
printf("Os valores em ponto flutuante são %+f %+f\n", neg_flt,
pos_flt);
}
4.1.12 – Formatando um Valor Inteiro
Usando o especificador de formato %d, será possível instruir printf() a exibir um
número mínimo de caracteres. O dígito que for colocado após o % especifica o número
mínimo de caracteres que printf() usará para exibir um valor inteiro. Por exemplo, caso seja
especificado %5d e o valor a ser exibido for 10, printf() predecerá o valor com três espaços.
Observe que o valor especifica o número mínimo de caracteres que a saída consumirá. Se o
valor a ser exibido requer mais caracteres do que o especificado, printf() usará o número de
caracteres que printf() requer para exibir o valor corretamente. Veja um exemplo que ilustra
este caso logo a seguir:
#include <stdio.h>
void main(void)
{
int valor = 5;
printf ("%1d\n",
printf ("%2d\n",
printf ("%3d\n",
printf ("%4d\n",
}
valor);
valor);
valor);
valor);
4.1.13 - Saída de Inteiros Preenchida com Zeros
No item anterior, foi visto como formatar uma saída instruindo printf() a exibir um
determinado número de dígitos. No caso anterior, printf() colocava espaços antes do valor a
ser exibido, mas é possível configurá-lo de tal maneira que ele exiba zeros antes do valor
ou caracter que é necessário exibir. Esses zeros são chamados de zeros de preenchimento.
Para instruir printf() a preencher um valor com zeros, coloca-se um 0 (zero) imediatamente
após o % no especificador de formato, antes do número desejado de dígitos. O exemplo
abaixo ilustra o uso desta propriedade.
#include <stdio.h>
void main(void)
{
int valor = 5;
printf ("%01d\n",
printf ("%02d\n",
printf ("%03d\n",
printf ("%04d\n",
}
valor);
valor);
valor);
valor);
4.1.14 Exibindo um Prefixo Antes dos Valores Octais ou Decimais
Em muitos programas que apresentam valores octais ou hexadecimais, em muitos
casos é necessário que se preceda os valores em octal com um zero (0777, por exemplo), e
os hexadecimais com 0x (oxFF, por exemplo). Para instruir printf() a preceder um valor
octal ou hexadecimal com o prefixo apropriado, coloca-se um sinal # imediatamente após o
% no especificador de formato. Veja o exemplo abaixo que ilustra o uso do sinal #.
#include <stdio.h>
void main(void)
{
int valor = 255;
printf("O valor decimal %d em octal é %#o\n", valor, valor);
printf("O valor decimal %d em hexadecimal é %#x\n", valor, valor);
printf("O valor decimal %d em hexadecimal é %#X\n", valor, valor);
}
4.1.15 – Formatando um Valor em Ponto Flutuante
No item 3.1.11 foi visto como formatar um valor inteiro colocando o número
desejado de dígitos imediatamente após o % no especificador de formato %d. Usando uma
técnica similar, printf() permite formatar a saída em ponto flutuante. Quando formata-se um
valor em ponto flutuante especifica dois valores. O primeiro valor diz a printf() o número
mínimo de caracteres a serem exibidos. O segundo valor diz a printf() o número de dígitos
a serem exibidos à direita do ponto decimal. O exemplo abaixo ilustra esta técnica.
#include <stdio.h>
void main(void)
{
float valor = 1.23456;
printf ("%8.1f\n", valor);
printf ("%8.3f\n", valor);
printf ("%8.5f\n", valor);
}
4.1.16 – Formatando a Saída Exponencial
No item anterior foi visto como usar o especificador de formato %f para formatar
valores em ponto flutuante. Usando técnicas de formatação similares, pode-se instruir
pintf() a exibir a saída em ponto flutuante em um formato exponencial. Veja o exemplo a
seguir que ilustra este caso.
#include <stdio.h>
void main(void)
{
float valor = 1.23456;
printf ("%12.1e\n", valor);
printf ("%12.3e\n", valor);
printf ("%12.5e\n", valor);
}
4.1.17 – Justificando à Esquerda a Saída de Printf()
Por padrão, quando for exibido o texto usando os caracteres de formatação, printf()
exibirá o texto justificado à direita. Dependendo do programa, algumas vezes é necessário
que printf() justifique o texto à esquerda. Para justificar o texto à esquerda, coloque um
sinal de subtração (-) imediatamente após o % no especificador de formato. O exemplo a
seguir ilustra o uso desta técnica de formatação.
#include <stdio.h>
void main(void)
{
int int_valor = 5;
float flt_valor = 3.33;
printf("Justificado à direita %5d valor\n", int_valor);
printf("Justificado à esquerda %-5d valor\n", int_valor);
printf("Justificado à direita %7.2f valor\n", flt_valor);
printf("Justificado à esquerda %-7.2f valor\n", flt_valor);
}
4.1.18 – Combinando os Especificadores de Formato de Printf()
Muitas vezes por rapidez e para que o código torne-se mais enxuto pode-se
aproveitar dois ou mais especificadores de formato de printf(). Em tais casos, simplesmente
coloque cada um dos especificadores logo após o %. Veja um exemplo.
#include <stdio.h>
void main(void)
{
int int_valor = 5;
printf("Justificado à esquerda com sinal %-+3d\n", int_valor);
}
4.1.19 – Trabalhando com os Caracteres Escape de Printf()
Quando trabalha-se com string de caracteres, pode-se usar caracteres especiais, tais
como tabulação, retorno do carro, ou alimentação de linha. A Linguagem C define vários
caracteres de escape (referenciados no Módulo 2 item 2.4.3) para facilitar para você a
inclusão de caracteres especiais dentro de uma string. Um exemplo é o uso do caracter de
nova linha (\n) para avançar a saída para o início da próxima linha.
printf("Linha 1\nLinha2\nLinha 3\n");
4.1.20 – Determinando o Número de Caracteres que Printf() Exibiu
Quando usa-se o especificador de formato %n, printf() atribuirá à uma variável
(passada por ponteiro) um contador do número de caracteres que printf() exibiu.
#include <stdio.h>
void main(void)
{
int primeiro_conta;
int segundo_conta;
printf("Universidade%n Estadual Paulista%n\n",&primeiro_conta,
&segundo_conta);
printf("Primeiro conta %d Segundo conta %d\n", primeiro_conta,
segundo_conta);
}
4.1.21 – Usando o Valor de Retorno de Printf()
Usar o especificador de formato %n é um modo de garantir que printf() teve sucesso
ao exibir sua saída. Além disso, quando printf() termina, ele retorna o número total de
caracteres que escreveu. Se printf() encontrar um erro, retornará a constante EOF (que
como será visto no módulo sobre sistema de arquivo, indica o fim de um arquivo). O
exemplo a seguir, usa o valor de retorno de printf() para garantir que printf() foi bem
sucedido.
#include <stdio.h>
void main(void)
{
int result;
result =
printf("Universidade Estadual Paulista\n");
if (result == EOF)
fprintf(stderr, "Erro dentro de printf\n");
}
4.2 – Função SCANF()
A função scanf( ) é uma das funções de entrada de dados da Linguagem C, que
pode ser usada para ler virtualmente qualquer tipo de dado inserido por meio do teclado,
freqüentemente ela é usada para a entrada de números inteiros ou de ponto flutuante. A
forma geral da função scanf( ) é:
scanf (“string de controle”, lista de argumentos);
Os especificadores de formato de entrada são precedidos por um sinal % e dizem à
função scanf( ) qual tipo de dado deve ser lido em seguida. Esses códigos são listados na
tabela a seguir.
Código
%c
%d
%i
%u
%e
%f
%g
%o
%s
%x
%p
Significado
Lê um único caractere
Lê um decimal inteiro
Lê um decimal inteiro (não pode ser octal ou hexadecimal)
Lê um decimal sem sinal
Lê um número em ponto flutuante com sinal opcional
Lê um número em ponto flutuante com ponto opcional
Lê um número em ponto flutuante com expoente opcional (double)
Lê um número em base octal
Lê uma string
Lê um número em base hexadecimal
Lê um ponteiro
Os caracteres de conversão d, i, o, u e x podem ser precedidos por h para indicarem
que um apontador para short ao invés de int aparece na lista de argumentos, ou por l (letra
ele) para indicar que um apontador para long aparece na lista de argumentos.
Semelhantemente, os caracteres de conversão e, f e g podem ser precedidos por l (letra ele)
para indicarem que um apontador para double ao invés de float está na lista de argumentos.
A cadeia de formato geralmente contém especificações de conversão, que são
usadas para controlar a conversão da entrada. A cadeia de formato pode conter:
espaços, tabulações e novas linhas, que serão ignorados;
caracteres comuns (não %), que devem combinar com o próximo caractere
não espaço do fluxo de entrada;
especificações de conversão, consistindo no caractere %, um caractere *
opcional de supressão de atribuição, um número opcional especificando um tamanho
máximo do campo, um h ou l opcional indicando o tamanho do destino, e um caractere de
conversão.
4.2.1 – Usando Caracteres em Scanf() que Serão Descartados
Um caractere que não seja um espaço em branco faz com que a função scanf( ) leia
e descarte o caractere correspondente. Por exemplo, “%d,%d” faz com que a função scanf()
leia um inteiro, então, leia uma vírgula (que será descartada) e, finalmente, leia outro
inteiro. Se o caractere especificado não é encontrado, a função scanf( ) terminará. Pode-se
usar esta técnica para separa os especificadores de formato, tornando o código mais legível.
#include <stdio.h>
void main(void)
{
int a,b;
scanf("%d,%d", &a, &b);
}
4.2.2 - Lendo Valores em Variáveis
Todas as variáveis usadas para receber valores por meio da função scanf() deverão
ser passadas pelos seus endereços. Por exemplo, para ler um inteiro em uma variável count,
poderia usar a seguinte chamada à função scanf():
#include <stdio.h>
void main(void)
{
int count;
scanf("%d ", &count);
}
4.2.3 – Lendo String de Caracteres
As strings serão lidas em vetores (cadeias de caracteres) e o nome do vetor é o
endereço do primeiro elemento do vetor. Então, para ler uma string no vetor de caracteres
nome, deve-se usar o seguinte comando:
#include <stdio.h>
void main(void)
{
char nome[40];
scanf("%s", nome);
}
Nesse caso, nome já é um endereço e não precisa ser precedido pelo operador &.
4.2.4 – Formatando a Entrada de Scanf()
Os itens de dados de entrada devem ser separados por espaços, tabulações ou novas
linhas. Pontuações como vírgula, ponto-e-vírgula e semelhantes não contam como
operadores. Isso significa que scanf(“%d%d”, &r, &c); aceitará uma entrada dos números
10 20, mas falhará com 10,20. Como na função printf( ), os códigos de formato da função
scanf( ) devem ser correspondidos na ordem com as variáveis que estão recebendo a
entrada na lista de argumento.
Um * colocado depois do % e antes do código de formato lerá um dado de um tipo
especificado, mas suprimirá a sua atribuição. Assim, scanf(“%d%*c%d”, &x, &y); dandose a entrada 10/20, colocará o valor 10 em x descartando o sinal de divisão, e dará a y o
valor 20.
Ainda que espaços, tabulações e novas linhas sejam usados como separadores de
campos, quando da leitura de um único caractere, esses últimos são lidos como qualquer
outro caractere. Por exemplo, com uma entrada de “x y”; scanf(“%c%c%c”, &a, &b, &c);
retornará com o caractere “x” em a, um espaço em b e o caractere “y” em c.
4.2.5 – Determinando o Número Máximo de Caracteres a ser Lido
Os comandos de formato podem especificar um campo modificador de
comprimento máximo. Esse modificador é um número inteiro colocado entre o sinal % e o
código de comando de formato, que limita o número de caracteres lidos para qualquer
campo. Por exemplo, para ler não mais que 20 caracteres em str, você pode escrever:
#include <stdio.h>
void main(void)
{
char str[40];
scanf("%20s", str);
}
Se o apontador de entrada é maior do que 20 caracteres, uma chamada subseqüente
para entrada começará onde ela pára. Por exemplo, se for digitado
ABCDEFGHIJKLMNOPQRSTUVWXYZ em resposta à chamada scanf( ), nesse
exemplo, somente os 20 primeiros caracteres, até o “T”, serão colocados em str por causa
do especificador de tamanho máximo. Isso significa que os caracteres restantes
“UVWXYZ” não são usados. Se uma outra chamada à função scanf( ) é feita, tal como
scanf(“%s”, str); então “UVWXYZ” é colocado em str. A entrada para o campo pode ser
terminada, antes que o campo de comprimento máximo seja alcançado, se um caractere de
espaço em branco é encontrado. Nesse caso, a função scanf( ) move-se para o próximo
campo.
4.2.6 - Lendo Somente Caracteres Pré-Determinados
A função scanf( ) inclui também uma característica muito poderosa chamada
scanset. Um scanset define uma lista de caracteres que serão correspondidos por scanf(). A
função scanf( ) continuará a ler caracteres enquanto eles estiverem no scanset. Assim que
um caractere entrado não corresponder a qualquer um do scanset, a função scanf( ) segue
para o próximo especificador de formato (se existir). Um scanset é definido colocando-se
uma lista de caracteres que quer-se que seja examinada entre chaves. A chave inicial deve
ser prefixada por um sinal de porcentagem. Por exemplo, este scanset diz à função scanf( )
para ler somente os dígitos de 0 a 9: %[1234567890].
O argumento correspondente a scanset deve ser uma variável string.
Após retornar de função scanf( ), a variável conterá uma string terminada com um
nulo com os caracteres lidos. Para ver como isso funciona, considere este programa:
#include <stdio.h>
#include <conio.h>
void main()
{
char s1[80], s2[80];
scanf (“%[1234567890]%s”, s1, s2);
printf (“\n%s| |%s”, s1, s2);
getch();
}
Pode-se testar esse programa usando a entrada “123456789abcdefg987654” seguida
por um retorno de carro. O programa exibirá, então: 123456789| |abcdefg987654. Uma vez
que “a” não faz parte do scanset, a função scanf( ) pára de ler os caracteres em s1 quando
ele é encontrado e os caracteres restantes são colocados em s2.
Você pode especificar um intervalo dentro de um scanset usando um hífen. Por
exemplo, isto diz à função scanf( ) para aceitar os caracteres de A a Z:
%[A-Z]
Pode-se especificar mais de um intervalo dentro de um scanset. Por exemplo, este
programa lê dígitos e letras. Ele também ilustra que pode-se usar o especificador de campo
máximo como um scanset.
#include <stdio.h>
#include <conio.h>
void main()
{
char str[80];
printf (“Informe dígitos e letras: ”);
scanf (“%78[a-z0-9]”, str);
printf (“\n%s, str);
getch();
}
Pode-se especificar um conjunto invertido se o primeiro caractere é um ^. Quando
está presente, o ^ instrui a função scanf( ) a aceitar quaisquer caracteres que não estão
definidos no scanset.
Um ponto importante a lembrar é que o scanset difere letras minúsculas de
maiúsculas. Portanto, se quiser examinar tanto letras maiúsculas como minúsculas, deve
especificá-las individualmente.
4.3 - Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
1 4.1 – Função PRINTF(): neste item foi apresentada uma introdução da função
printf(), listando suas características e propriedades bem como sua estrutura.
2 4.1.1 – Exibindo Valores do Tipo int Usando Printf(): neste item foi mostrado
como é possível exibir uma saída printf() para valores inteiros, usando-se o especificador de
formato %d.
3 4.1.2 – Exibindo um Valor Inteiro Octal ou Hexadecimal: foi visto que é
possível exibir um valor inteiro em seu formato octal (base 8) ou hexadecimal (base 16)
através dos especificadores de formato %o, %x e %X.
4 4.1.3 – Exibindo Valores do Tipo Unsigned Int: neste item foi apresentado
como exibir valores do tipo unsigned int com a função printf(), usando o especificador de
formato %u.
5 4.1.4 – Exibindo Valores do Tipo Long Int: foi visto que para exibir valores
do tipo long int com a função printf(), deve-se usar o especificador de formato %ld.
6 4.1.5 – Exibindo Valores do Tipo Float: foi visto que para exibir valores do
tipo float com a função printf(), deve-se usar o especificador de formato %f.
7 4.1.6 – Exibindo Valores do Tipo Char: foi visto que para exibir valores do
tipo char com a função printf(), deve-se usar o especificador de formato %c.
8 4.1.7 – Exibindo Valores de Ponto Flutuante em um Formato Exponencial:
foi visto que para exibir um valor em ponto flutuante em um formato exponencial com a
função printf(), deve-se usar o especificador de formato %e ou %E.
9 4.1.8 – Exibindo Valores em Ponto Flutuante: foi visto que a função printf()
também suporta os especificadores %g e %G para imprimir valores em ponto flutuante.
10 4.1.9 – Exibindo uma String de Caracteres: foi visto que para exibir uma
string de caracteres com a função printf(), deve-se usar o especificador de formato %s.
11 4.1.10 – Exibindo um Endereço de Ponteiro: foi visto que para exibir um
endereço de ponteiro com a função printf(), deve-se usar o especificador de formato %p.
12 4.1.11 – Precedendo um Valor com um Sinal de Adição ou de Subtração: foi
visto que para instruir printf() a exibir um sinal de um valor, deve-se incluir um sinal de
adição imediatamente após o % no especificador de formato.
13 4.1.12 – Formatando um Valor Inteiro: neste item foi visto como configurar
printf() de tal forma que sua valores a serem exibidos tenha seu formato de saída na tela
especificado pelo programador.
14 4.1.13 – Saída de Inteiros Preenchida com Zeros: foi visto neste item que é
possível configurar printf() de tal maneira que ele exiba zeros antes do valor ou caracter
que é necessário exibir.
15 4.1.14 – Exibindo um Prefixo Antes dos Valores Octais ou Decimais: foi
visto como preceder os valores em octal com um zero, e os hexadecimais com 0x .
16 4.1.15 – Formatando um Valor em Ponto Flutuante: foi visto que quando
formata-se um valor em ponto flutuante especifica dois valores. O primeiro valor diz a
printf() o número mínimo de caracteres a serem exibidos. O segundo valor diz a printf() o
número de dígitos a serem exibidos à direita do ponto decimal. Com isto foi apresentada
uma técnica de formatação de printf().
17 4.1.16 – Formatando a Saída Exponencial: foi visto como configurar printf()
de tal forma a exibir a saída em ponto flutuante em um formato exponencial.
18 4.1.17 – Justificando à Esquerda a Saída de Printf(): foi apresentado que para
justificar o texto à esquerda, coloca-se um sinal de subtração (-) imediatamente após o % no
especificador de formato.
19 4.1.18 – Combinando os Especificadores de Formato de Printf(): é
apresentado neste item a possibilidade de aproveitar dois ou mais especificadores de
formato de printf() com intuito de deixar o código mais enxuto.
20 4.1.19 – Trabalhando com os Caracteres Escape de Printf():. Foi visto que a
Linguagem C define vários caracteres de escape (referenciados no Módulo 2 item 2.4.3)
para facilitar para você a inclusão de caracteres especiais dentro de uma string.
21 4.1.20 – Determinando o Número de Caracteres que Printf() Exibiu: Foi
visto que quando usa-se o especificador de formato %n, printf() atribuirá à uma variável
(passada por ponteiro) um contador do número de caracteres que printf() exibiu.
22 4.1.21 – Usando o Valor de Retorno de Printf(): foi mostrado que é possível
utilizar o valor que printf() retorna quando ele finaliza suas tarefas. Este valor retornado
pode ser utilizado de várias formas, variando o uso de acordo com as características do
programa.
23 4.2 – Função SCANF(): foi feita neste item uma introdução à função scanf().
Foi mostado que a função scanf( ) é uma das funções de entrada de dados da Linguagem C,
que pode ser usada para ler virtualmente qualquer tipo de dado inserido por meio do
teclado. Além da introdução à função foi apresentada sua estrutura e características.
24 4.2.1 – Usando Caracteres em Scanf() que Serão Descartados: foi mostrado
que é possível inserir dentro da função scanf() caracteres que não serão lidos. Estes
caracteres servem apenas na tornar o código mais legível e de fácil entendimento.
25 4.2.2 – Lendo Valores em Variáveis: foi visto que é possível utilizar a função
scanf() para obter valores para determinadas variáveis.
26 4.2.3 – Lendo String de Caracteres: foi mostrado como usar a função scanf()
para ler string de caracteres que serão armazenadas em vetores.
27 4.2.4 – Formatando a Entrada de Scanf(): neste item é mostrado como
formatar scanf()de tal forma a atender as especificações de cada programa para entrada de
dados.
28 4.2.5 – Determinando o Número Máximo de Caracteres a ser Lido: foi
mostrado que é possível especificar o número de caracteres lidos por scanf(), de tal forma a
desprezar caracteres que forem digitados a mais, por exemplo.
29 4.2.6 – Lendo Somente Caracteres Pré-Determinados: foi visto que pode-se
previamente especificar os caracteres que serão lidos por scanf(), sendo que o caracter que
não estiver dentro destes especificados será ignorado.
Módulo 5 - Estruturas de Fluxo de Controle
5.1 – A Declaração If
A declaração if é usada quando é necessário testar em um programa duas ou mais
condições. Isto permite ao programador o desenvolvimento de programas complexos. A
forma geral da declaração if é:
if (condição)
{
comandos;
}
else
{
comandos;
}
A cláusula else é opcional. Se condição for verdadeira (qualquer coisa diferente de
0), o bloco que forma o destino de if será executado; caso contrário o bloco que forma o
destino de else será executado (desde que exista else).
Lembre-se que somente o código associado a if ou o código associado a else será
executado, nunca os dois. O destino dos dois, if e else, pode ser um comando simples ou
um bloco de comandos.
O comando if será demonstrado através de um programa simples que converte bases
numéricas. Este programa será capaz de apresentar as seguintes conversões:
1 Decimal para Hexadecimal;
2 Hexadecimal para Decimal.
O programa permitirá que primeiro seja selecionado o tipo de conversão a partir de
um menu e, então, solicitará o número a ser convertido.
#include <stdio.h>
void main()
{
int opcao;
int valor;
printf (“Converter: \n”);
printf (“1: decimal para hexadecimal\n”);
printf (“2: hexadecimal para decimal\n”);
printf (“\nInforme sua opção: ”);
scanf (“%d”, &opcao);
if (opcao == 1)
{
printf (“\nInforme o valor em decimal: ”);
scanf (“%d”, &valor);
printf (“%d em hexadecimal e: %x”, valor, valor);
}
if (opcao == 2)
{
printf (“\nInforme o valor em hexadecimal: ”);
scanf (“%x”, &valor);
printf (“%x em decimal e: %d”, valor, valor);
}
}
5.1.1 - Usando a Declaração Else
É possível associar um else com qualquer if. Com esta declaração podemos
acrescentar ao teste condicional várias opções de escolha. Se a expressão condicional
associada a if é verdadeira, a instrução ou bloco de instruções associada será executada. Se
for falsa, então a instrução ou bloco de instruções do else será executada. O programa
seguinte demonstra este princípio fundamental:
#include <stdio.h>
void main()
{
int i;
printf (“Informe um número: ”);
scanf (“%d”, &i);
if (i < 0) printf (“O número é negativo”);
else printf (“O número é positivo ou nulo”);
}
5.1.2 – O Encadeamento If-Else-If
Uma construção comum em programação é o encadeamento if-else-if. O seguinte
exemplo ilustra esta construção:
if (condição)
{
comandos;
}
else if (condição)
{
comandos;
}
else if (condição)
{
comandos;
}
else
{
comandos;
}
As expressões condicionais serão avaliadas de cima para baixo. Assim que uma
condição verdadeira é encontrada, o bloco associado a ela será executado, e o resto do
encadeamento é ignorado. Se nenhuma das condições for verdadeira, então o else final será
executado.
Se o else final não estiver presente e todas as outras condições forem falsas, então
nenhuma ação será realizada.
Pode-se usar o encadeamento if-else-if para implementar o programa de conversão
de base numérica desenvolvido anteriormente. Na versão original, cada declaração if era
avaliada sucessivamente, mesmo se uma das declarações anteriores tivesse êxito. Ainda que
não haja grande significado neste caso, a avaliação redundante de todos os ifs não é muito
eficiente ou elegante. O seguinte programa resolve este problema. Nessa versão de
encadeamento if-else-if, tão logo uma declaração if é satisfeita, o resto das declarações é
ignorado.
/* Programa de conversão de base numérica – if-else-if
decimal ---> hexadecimal
hexadecimal ---> decimal
*/
#include <stdio.h>
void main()
{
int opcao;
int valor;
printf (“Converter: \n”);
printf (“1: decimal para hexadecimal\n”);
printf (“2: hexadecimal para decimal\n”);
printf (“\nInforme sua opção: ”);
scanf (“%d”, &opcao);
if (opcao == 1)
{
printf (“\nInforme o valor em decimal: ”);
scanf (“%d”, &valor);
printf (“%d em hexadecimal e: %x”, valor, valor);
}
else if (opcao == 2)
{
printf (“\nInforme o valor em hexadecimal: ”);
scanf (“%x”, &valor);
printf (“%x em decimal e: %d”, valor, valor);
}
else
{
printf (“\nA opção escolhida é inválida.”)
}
}
5.1.3 – A Expressão Condicional
Algumas vezes, iniciantes na linguagem C confundem-se pelo fato de que qualquer
expressão válida na linguagem C pode ser usada para controlar a declaração if. Isto é, o tipo
de expressão não precisa se restringir àquelas envolvendo operadores relacionais e lógicos.
Só é requerido que a expressão resulte em um valor zero ou não zero. Por exemplo, este
programa lê dois inteiros do teclado e mostra o quociente. Para evitar um erro de divisão
por zero, uma declaração if é usada para controlar o segundo número.
#include <stdio.h>
void main()
{
int a, b;
printf (“Informe dois números: ”);
scanf (“%d%d”, &a, &b);
if (b) printf (“%d\n”, a/b);
else printf (“Não posso dividir por zero\n”);
}
Essa abordagem funciona porque, se b for zero, a condição controlando o if é falsa e
a instrução else é executada. Caso contrário, a expressão é verdadeira (não zero) e a divisão
é realizada. Não é necessário escrever uma declaração if como esta
if (b != 0) printf (“%d\n”, a/b);
porque é redundante.
5.1.4 – Ifs Aninhados
Um dos muitos aspectos que causam confusão na declaração if, em qualquer
linguagem de programação, são os ifs aninhados. Um if aninhado é uma declaração if que é
objeto de um if ou um else. Os ifs aninhados são incômodos por poderem dificultar saber
qual else está associado a qual if.
Considere este exemplo:
if (x)
if (y) printf (“1”);
else printf (“2”);
Em C, o else é ligado ao if mais próximo dentro do mesmo bloco de código que já
não tenha uma declaração else associada a ele. Neste caso o else é associado à declaração
if(y).
Para fazer com que else seja associado à declaração if(x), deve-se usar chaves para
sobrepor a sua associação normal, como mostrado aqui:
if (x)
{
if (y) printf (“1”);
}
else printf (“2”);
if(y).
O else agora está associado ao if(x), já que ele não é parte do bloco de código do
5.2 – A Declaração For
Uma operação que vários programas executarão comumente é repetir um conjunto
de comandos um número específico de vezes. Por exemplo, pode-se querer calcular as
notas dos exames de 30 alunos ou soar três vezes o alto-falante interno do computador. Para
auxiliar programas a repetir um ou mais comandos um certo número de vezes, a Linguagem
C fornece o comando for.
for(valor_inicial; condição_final; valor_incremento)
comando;
Quando um programa repetir comandos um número específico de vezes,
normalmente usará uma variável de controle, que contará o número de vezes que for
executado os comandos. O comando for contém quatro seções. A seção valor_inicial
atribui à variável de controle o valor inicial da variável, que é, na maioria das vezes, 0 ou 1.
A seção condição_final normalmente testa o valor da variável de controle para determinar
se o programa executou os comandos um número desejado de vezes. A seção
valor_incremento normalmente adiciona o valor 1 para a variável de controle toda a vez
que os comandos são executados. Finalmente a quarta seção do comando for é o comando
ou comandos especificados. O comando for é geralmente chamado de laço for. Considere o
seguinte laço for, ele exibirá os números de 1 a 10 na sua tela:
for(contador=1; contador <= 10; contador++)
printf("%d\n", contador);
Para compreender melhor o processamento do laço for execute em seu compilador o
seguinte programa:
#include <stdio.h>
void main(void)
{
int contador;
for (contador = 1; contador <= 5; contador++)
printf("%d ", contador);
printf("\nIniciando o segundo laco\n");
for (contador = 1; contador <= 10; contador++)
printf("%d ", contador);
printf("\nIniciando o terceiro laco\n");
for (contador = 100; contador <= 5; contador++)
printf("%d ", contador);
}
Como pode-se verificar, o primeiro laço for exibe os números de 1 até 5. O segundo
laço exibe os valores de 1 até 10. O terceiro laço não exibe nenhum valor. Se for
examinado com atenção, será visto que o programa inicialmente atribui à variável de
controle do laço o valor 100. Quando o comando for testa o valor, o laço for atende
imediatamente à condição final, de modo que o laço não é executado.
5.2.1 – Partes do Comando For são Opcionais
Como foi visto no item anterior, o laço for usa três seções dentro do comando for:
uma inicialização, um teste e um incremento:
for(inicialização; teste; incremento)
Dependendo do programa, algumas vezes pode não ser necessário o uso de cada
uma das seções do comando for. Por exemplo, se já foi atribuido o valor 0 à variável conta,
pode-se pular a seção de inicialização do laço. Depois, para exibir os números de 0 até 999,
o laço conterá o seguinte:
for(; conta <1000; conta++)
printf("%d", conta);
No entanto, se for omitido uma das seções do laço for, precisa-se incluir o ponto-evírgula correspondente. Por exemplo, o laço for a seguir omite as seções de inicialização e
de incremento:
for(; conta < 1000; )
printf("%d", conta++);
Da mesma forma, o comando for à seguir ficará em execução perpetuamente. Este é
o chamado laço infinito:
for (; ; ; )
/*comando*/
5.2.2 – Laço Null
No passado, quando os programadores queriam que seus programas fizessem uma
breve pausa, talvez para exibir alguma mensagem, eles colocavam um laço nulo ou "não
faz nada" em seus programas. Por exemplo, o seguinte laço for não faz nada 100 vezes:
for(contador=1; contador <= 100; contador++)
;
/* não faz nada */
Quando se coloca um laço nulo nos programas, a Linguagem C efetuará a
inicialização do laço, e, depois, repetidamente, testará e incrementará a variável de controle
até que a variável de controle atenda a condição final. O teste repetido do laço consome
tempo do processador, o que faz o programa retardar. Se o programa precisar de um retardo
maior, você poderá aumentar a condição final:
for(contador=1; contador <= 10000; contador++)
;
/* não faz nada */
Usar as técnicas de retardo, tais como o laço nulo, poderá causar problemas.
Primeiro, se o programa estiver rodando em um computador 286, 386 ou 486, a duração do
retardo diferirá simplesmente devido à diferença de velocidade entre os diferentes
microprocessadores. Segundo, se o programa estiver rodando em um ambiente multitarefa,
tal como o Windows, OS/2 ou Unix, os laços "não fazem nada" consomem tempo que o
processador poderia estar gastando fazendo trabalho importante em outro programa.
5.2.3 – Laço Infinito
Quando são usados laços for, precisa-se garantir que o laço atenderá à sua condição
final. Caso contrário, o laço continuará sua execução para sempre. Esses laços
intermináveis são chamados laços infinitos. Na maioria dos casos, os laços infinitos
ocorrem como resultado de erro na programação. Por exemplo, considere o seguinte laço:
for(i = 0; i <100; i++)
{
printf("%d", i);
resultado = valor * --i;
/*causa do erro*/
}
Como pode-se verificar, o segundo comando do laço decrementa o valor da variável
de controle i. Especificamente, o laço decrementa o valor para -1, e, depois, incrementa o
valor para 0. Como resultado, o valor nunca atinge 100, de modo que o laço não termina.
Quando o programa entra em um laço infinito, pode-se pressionar Ctrl+C para finalizar o
programa.
O programa a seguir ilustra um laço infinito:
#include <stdio.h>
void main(void)
{
int i;
int result = 0;
int valor = 1;
for (i = 0; i < 100; i++)
{
printf("%d ", i);
result = valor * --i;
}
printf("Resultado %d\n", result);
}
5.2.4 – Usando o operador vírgula da Linguagem C dentro de um laço for
A Linguagem C permite a declaração de múltiplas variáveis do mesmo tipo
separando os nomes das variáveis com vírgulas:
int idade, altura, peso;
De um modo similar, a Linguagem C permite inicializar e incrementar múltiplas
variáveis em um laço for separando as operações com vírgula. Considere o laço a seguir,
que trabalha com as variáveis i e j:
for (i=0, j=100; i <= 100; i++, j++)
printf("i = %d j = %d\n", i, j);
Alguns programas irão trabalhar com múltiplas variáveis em um laço for em
programas que trabalham com matrizes. O programa a seguir ilustra o uso do operador
vírgula em um laço for:
#include <stdio.h>
void main(void)
{
int i, j;
for (i = 0, j = 100; i <= 100; i++, j++)
printf("i = %d j = %d\n", i, j);
}
5.3 – A Declaração While
Em muitos casos, os programas precisam repetir um ou mais comandos
até que o laço atenda a uma condição específica que não envolva necessariamente uma
contagem. Por exemplo, para um programa que exiba o conteúdo de um arquivo na tela,
será necessário que o programa exiba cada linha do arquivo. Na maioria dos casos, não
sabe-se quantas linhas o arquivo contém. Portanto, não se pode usar um laço for para exibir,
por exemplo, 100 linhas. O arquivo poderia conter mais ou menos linhas. Em vez disso,
será necessário que o programa leia e exiba linhas até chegar ao final do arquivo. Para fazer
isso, os programas podem usar o laço while, como segue:
while (condição)
comando;
Ao encontrar um laço while no programa, a Linguagem C testa a condição
especificada. Se a condição for verdadeira, efetuará os comandos contidos no laço. Se o
comando for falso, continuará a execução do programa ao primeiro comando que segue.
Um laço while pode repetir um único comando ou um comando composto delimitado por
abre e fecha chaves, como segue:
while (condição)
{
/*comandos*/
}
O programa a seguir, usa o laço while para repetir um laço até que seja pressionado
a tecla S ou N em resposta a uma pergunta:
#include <stdio.h>
#include <ctype.h>
#include <conio.h>
void main(void)
{
char letra;
/* Letra digitada pelo usuário */
printf("Quer continuar? (S/N): ");
letra = getch();
/* Lê a letra */
letra = toupper(letra); /* Converte letra para maiúscula */
while ((letra != 'S') && (letra != 'N'))
{
putch(7);
/* Soa o alto-falante */
letra = getch();
/* Lê a letra */
letra = toupper(letra);
/* Converte a letra para maiúscula */
}
printf("\nSua resposta foi %c\n", letra);
}
Primeiro, o programa exibirá a mensagem que o primeiro comando printf contém.
Segundo, o programa usa getch para ler a tecla pressionada. Para simplificar o teste do laço,
o programa converte a letra para maiúscula, de modo que o laço somente precisa testar as
letras S ou N. Terceiro, o laço while testará a letra que o usuário digitou. Se a letra for um S
ou N, a condição falhará, e os comandos do laço não serão executados. Se a letra
pressionada não for S ou N, a condição do laço será verdadeira e seus comandos serão
executados. No laço, o comando soará o alto-falante interno do computador para indicar um
caractere inválido. Em seguida, o programa lerá a nova tecla e converterá a letra para
maiúscula. O laço depois repetirá o seu teste para determinar se o usuário digitou um S ou
um N. se não, os comando do laço serão repetidos. Caso contrário, a execução do programa
continuará no primeiro comando que segue o laço.
5.3.1 – Partes de um Laço While
Foi visto anteriormente que um laço for realmente contém quatro seções: uma
inicialização, um teste, um comando de execução e um incremento. Por outro lado, um laço
while contém somente um teste e os comandos que quer-se repetir, como visto no item
anterior.
Foi visto também, que um laço infinito é aquele cuja condição final nunca é
atendida, e portanto o laço continua a execução para sempre. Ao escrever programas que
usa laços while, pode-se reduzir a possibilidade de um laço infinito garantindo que seus
laços while efetuem os mesmos passos executados por um laço for. Veja a seguir os quatro
passos relacionados na tabela:
Ação
Inicializa
Testa
Executa
Modifica
Descrição
Inicializa a variável de controle do laço
Testa a variável de controle ou a condição do laço
Executa os comando desejados no laço
Modifica o valor da variável de controle ou efetua uma operação que
afetará a condição que você está testando
Ao contrário do laço for, que permite explicitamente inicializar e incrementar uma
variável de controle, um laço while requer que sejam inclusos comandos no programa que
efetuem esses passos. O programa a seguir, ilustra como o programa efetua esses quatro
passos. Ao contrário dos programas escritos anteriormente, este usa um laço while para
exibir os números de 1 a 100:
#include <stdio.h>
void main(void)
{
int contador = 1;
/* Inicializa a variável de controle */
while (contador <= 100) /* Testa a variável de controle */
{
printf("%d ", contador); /* Executa os comandos */
contador++;
/* Modifica a variável de controle */
}
}
5.4 – A Declaração Do-While
Ao contrário dos laços for e while, que testam a condição do laço no início, o laço
do-while verifica sua condição no final do laço. Isso significa que um laço do-while será
executado pelo menos uma vez. A forma geral do laço do-while é:
do{
comandos;
}while(condição);
Ainda que as chaves não sejam necessárias quando somente um comando será
executado no laço, elas são usadas para aumentar a legibilidade do código do programa.
Este programa usa um laço do-while para ler números do teclado até que um deles seja
menos que 100.
#include <stdio.h>
void main()
{
int num;
do
{
scanf (“%d”, &num);
}
while(num > 100);
}
Talvez o uso mais comum do laço do-while seja em uma rotina de seleção em
menu. Já que sempre será preciso de uma rotina de seleção em menu para ser executada no
mínimo uma vez, o laço do-while é uma opção óbvia. O seguinte fragmento mostra como
adicionar um laço do-while no menu do programa de conversão numérica.
/* Assegura que o usuário especificou uma opção válida */
do
{
printf (“Converte:\n”);
printf (“ 1: decimal para hexadecimal\n”);
printf (“ 2: hexadecimal para decimal\n”);
printf (“informe sua opção: ”);
scanf (“%d”, &opcao);
}while(opcao < 1 || opcao > 2);
Depois que a opção tiver sido apresentada, o programa se repetirá até que uma
opção válida seja selecionada.
5.5 – Comando Continue
Dependendo do propósito do programa, algumas vezes, com base em uma segunda
condição específica, poderá ser necessário que o programa pule a iteração atual. O
comando continue da Linguagem C lhe permite fazer exatamente isso. Se a Linguagem C
encontrar um comando continue em um laço for, a Linguagem C automaticamente
executará a porção de incremento do laço, e, depois, efetuará o teste da condição final. Se a
Linguagem C encontrar um comando continue em um laço while ou do, então a Linguagem
C imediatamente efetuará o teste da condição final. Para compreender melhor o comando
continue, considere o seguinte programa, que usa continue em um laço for e um laço while
para exibir os números pares e ímpares entre 1 e 100:
#include <stdio.h>
void main(void)
{
int contador;
printf("\nValores pares\n");
for (contador = 1; contador <= 100; contador++)
{
if (contador % 2) /* Ímpar */
continue;
printf("%d ", contador);
}
printf("\nValores ímpares\n");
contador = 0;
while (contador <= 100)
{
contador++;
if (! (contador % 2)) /* Par */
continue;
printf("%d ", contador);
}
}
É importante observar que normalmente pode-se eliminar a necessidade de usar um
comando continue reprojetando o uso dos comandos if e else em seu programa. Por
exemplo, o programa a seguir também exibe valores pares e ímpares sem usar continue:
#include <stdio.h>
void main(void)
{
int contador;
printf("\nValores pares\n");
for (contador = 1; contador <= 100; contador++)
{
if (!(contador % 2)) /* Par */
printf("%d ", contador);
}
printf("\nValores ímpares\n");
contador = 0;
while (contador <= 100)
{
contador++;
if (contador % 2) /* Ímpar */
printf("%d ", contador);
}
}
5.6 – Finalizando um Laço Usando o Comando Break
Dependendo do propósito do programa, algumas vezes, com base em uma segunda
condição específica, poderá querer que o laço termine automaticamente, com seu programa
continuando seu processamento no comando que segue o laço. O comando break lhe
permite fazer exatamente isso. Quando a Linguagem C encontra um break em um laço, a
execução do laço terminará de imediato. O próximo comando que o programa executa é o
comando que segue imediatamente o laço. No caso de um comando for, a Linguagem C
não efetuará a seção de incremento do laço, ao invés disso, o laço termina imediatamente.
O programa a seguir ilustra o uso do comando break. O programa percorre os números de 1
a 100, e, depois, de 100 a 1. Toda vez que o laço chega ao valor 50, o comando break
termina imediatamente o laço:
#include <stdio.h>
void main(void)
{
int contador;
for (contador = 1; contador <= 100; contador++)
{
if (contador == 50)
break;
printf("%d ", contador);
}
printf("\nPróximo laço\n");
for (contador = 100; contador >= 1; contador--)
{
if (contador == 50)
break;
printf("%d ", contador);
}
}
5.7 – Desvios com o comando goto
Como a maioria das linguagens de programação, a Linguagem C fornece um comando
goto, que permite que a execução do programa desvie-se para uma localização específica,
chamada rótulo. O formato do comando goto é como segue:
goto rotulo;
rotulo:
O programa a seguir usa o comando goto para exibir os números de 1 a 100:
#include <stdio.h>
void main(void)
{
int conta = 1;
rotulo:
printf("%d ", conta++);
if (conta <= 100)
goto rotulo;
}
Quando usa-se o comando goto, o rótulo precisa residir na função atual. Em outras
palavras, não pode-se usar goto para desviar-se de main para um rótulo que aparece em
outra função e vice - versa.
5.8 – A Declaração Switch
Ainda que o encadeamento if-else-if possa realizar testes de múltipla escolha, ele
quase nunca é elegante. O código pode ser muito difícil de acompanhar e pode confundir
até mesmo o seu autor. Por esse motivo, a linguagem C tem internamente uma declaração
de decisão de múltipla escolha chamada de switch. Na declaração switch, a variável é
sucessivamente testada contra uma lista de inteiros ou constantes caractere. Quando uma
associação é encontrada, o conjunto de comandos associado com a constante é executado.
As constantes não precisam sequer estar em qualquer ordem especial. A forma geral
da declaração switch é:
switch (variável)
{
case constante1:
commandos;
break;
case constante2:
commandos;
break;
case constante3:
commandos;
break;
M
default
commandos;
}
Onde a declaração default é executada se nenhuma correspondência for encontrada.
O default é opcional e, se ele não estiver presente, nenhuma ação será realizada se todas as
correspondências falharem. Quando uma correspondência é encontrada, os comandos
associados a case são executados até que o break seja encontrado ou até que se encontre o
final do switch.
Há três coisas importantes a saber sobre a declaração switch:
1) Ele difere do if, já que o switch pode testar somente igualdades, enquanto a
expressão condicional if pode ser de qualquer tipo;
2) Nunca duas constantes case no mesmo switch podem ter valores iguais.
Obviamente, uma declaração switch dentro de outra declaração switch pode ter as mesmas
constantes case;
3) Uma declaração switch é mais eficiente que um encadeamento if-else-if.
É possível especificar comandos no switch que serão executados caso nenhuma
correspondência seja encontrada. Basta adicionar uma declaração default. A declaração
default é uma boa maneira de direcionar qualquer final livre que possa ficar pendente na
declaração switch. Por exemplo, no programa de conversão de base numérica, pode-se usar
uma declaração default para informar ao usuário que uma resposta inválida foi dada e para
tentar outra vez.
Usa-se freqüentemente o switch para desviar uma seleção de menu para a rotina
apropriada. Seguindo essa linha, pode-se usá-la para fazer um melhoramento adicional ao
programa de conversão de base numérica. A versão mostrada aqui elimina as séries
anteriores de ifs e substitui-as por uma clara declaração switch.
/* Programa de conversão de base numérica – switch
decimal ---> hexadecimal
hexadecimal ---> decimal*/
#include <stdio.h>
void main()
{
int opcao;
int valor;
printf (“Converter: \n”);
printf (“1: decimal para hexadecimal\n”);
printf (“2: hexadecimal para decimal\n”);
printf (“\nInforme sua opção: ”);
scanf (“%d”, &opcao);
switch(opcao)
{
case 1:
printf (“\nInforme o valor em decimal: ”);
scanf (“%d”, &valor);
printf (“%d em hexadecimal e: %x”, valor, valor);
break;
case 2:
printf (“\nInforme o valor em hexadecimal: ”);
scanf (“%x”, &valor);
printf (“%x em decimal e: %d”, valor, valor);
break;
default:
printf (“\nOpção inválida. Tente outra vez.”)
}
}
5.8.1 – As Declarações Switch Aninhadas
É possível ter um switch como parte da seqüência de declaração de um switch
externo. Mesmo que as constantes case do switch interno e externo contenham valores
comuns, nenhum conflito surgirá. Por exemplo, o seguinte fragmento de código é
perfeitamente aceitável.
switch(x){
case 1:
switch(y){
case 0:
printf (“erro de divisão por zero”);
break;
case 1:
process (x, y);
}
break;
case 2:
.
.
.
Como outro exemplo, um programa de banco de dados bastante simples mostrado a
seguir ilustra como se pode usar uma declaração switch aninhada. Este programa solicita ao
usuário a região e a letra inicial do nome do vendedor e, então, mostra o demonstrativo de
vendas do mesmo. Switchs aninhados são requeridos uma vez que muitos vendedores têm a
mesma letra inicial. Note que uma nova função, toupper( ), é introduzida. Ela retorna a
letra maiúscula correspondente do seu argumento caractere. Ela é usada neste programa
para permitir que o usuário insira informações tanto em letra maiúscula como em letra
minúscula. A função complementar de toupper( ) é tolower( ), que converte caracteres
maiúsculos em minúsculos. Ambas as funções estão contidas na biblioteca ctype.h.
/* Um banco de dados simples de vendedores por região */
#include <stdio.h>
#include <ctype.h>
void main()
{
char regiao, vendedor;
printf (“As regiões são: Leste, Oeste e Norte\n”);
printf (“Informe a primeira letra da região: ”);
regiao = getche();
regiao = toupper(regiao); /*converte para maiúsculas*/
printf (“\n”);
switch (regiao)
{
case ‘L’:
printf (“Vendedores são: Rafael, João e Maria \n”);
printf (“Informe a primeira letra do vendedor: ”);
vendedor = toupper(getche());
printf (“\n”);
switch (vendedor)
{
case ‘R’:
printf (“Vendas: R$%d\n”, 10000);
break;
case ‘J’:
printf (“Vendas: R$%d\n”, 12000);
break;
case ‘M’:
printf (“Vendas: R$%d\n”, 14000);
}
break;
case ‘O’:
printf (“Vendedores são: Ronaldo, Lisa e Hilton\n”);
printf (“Informe a primeira letra do vendedor: ”);
vendedor = toupper(getche());
printf (“\n”);
switch (vendedor)
{
case ‘R’:
printf (“Vendas: R$%d\n”, 10000);
break;
case ‘L’:
printf (“Vendas: R$%d\n”, 9500);
break;
case ‘H’:
printf (“Vendas: R$%d\n”, 13000);
}
break;
case ‘N’:
printf (“Vendedores são: Tomás, João e Raquel\n”);
printf (“Informe a primeira letra do vendedor: ”);
vendedor = toupper(getche());
printf (“\n”);
switch (vendedor)
{
case ‘R’:
printf (“Vendas: R$%d\n”, 5000);
break;
case ‘J’:
printf (“Vendas: R$%d\n”, 9000);
break;
case ‘T’:
printf (“Vendas: R$%d\n”, 14000);
}
break;
}
}
Para ver como o programa funciona, seleciona-se a região Oeste, digitando O. Isso
indica que case ‘O’ é selecionado pela declaração switch externa. Para ver o total de vendas
de Hilton, digite H. Isso faz com que o valor 13000 seja apresentado.
Note que a declaração break em um switch aninhado não tem efeito no switch
externo.
5.9 – Laços Aninhados
Quando um laço está dentro de outro, diz-se que o laço mais interno é aninhado.
Laços aninhados propiciam o meio de resolver alguns problemas interessantes de
programação. Por exemplo, este pequeno programa exibe as quatro primeiras potências dos
números de 1 a 9.
/* Exibe uma tabela das 4 primeiras potencies de 1 a 9 */
#include <stdio.h>
void main()
{
int i, j, k, temp;
printf(“ i i^2 i^3 i^4\n”);
for (i = 1; i < 10; i++)
{ /* laço externo */
for (j = 1; j < 5; j++)
{ /* primeiro aninhamento */
temp = 1;
for (k = 0; k < j; k++) /* segundo aninhamento */
temp = temp * i;
printf (“%9d”, temp);
}
printf (“\n”);
}
}
Algumas vezes, é importante determinar quantas interações o laço interno executa.
Este número é conseguido multiplicando-se o número de vezes que o laço externo intera
pelo número de vezes que o laço interno é repetido cada vez que é executado. No exemplo
do programa de potência, o laço externo é repetido nove vezes e o segundo laço, quatro
vezes; assim, o segundo laço interagirá 36 vezes. O laço interno é executado, em média,
duas vezes; dessa forma, o número total de interações é 72.
Como no último exemplo, um melhoramento final para o programa de conversão de
base numérica usando-se laços aninhados é mostrado aqui. O laço externo faz com que o
programa seja executado até que o usuário diga para parar. O laço interno assegura que o
usuário informará uma seleção válida do menu. Agora, em vez de simplesmente converter
um número toda vez que for executado, o programa repete até que o usuário queira parar.
/* Programa de conversão de base numérica – versão final
decimal ---> hexadecimal
hexadecimal ---> decimal*/
#include <stdio.h>
void main()
{
int opcao;
int valor;
/*repete até que o usuário diga para terminar*/
do
{
/*assegura que o usuário especificou uma opção válida*/
do
{
printf (“Converter: \n”);
printf (“1: decimal para hexadecimal\n”);
printf (“2: hexadecimal para decimal\n”);
printf (“3: fim\n”);
printf (“\nInforme sua opção: ”);
scanf (“%d”, &opcao);
}
while(opcao < 1 || opcao > 3);
switch(opcao)
{
case 1:
printf (“\nInforme o valor em decimal: ”)
scanf (“%d”, &valor);
printf (“%d em hexadecimal e: %x”, valor, valor);
break;
case 2:
printf (“\nInforme o valor em hexadecimal: ”)
scanf (“%x”, &valor);
printf (“%x em decimal e: %d”, valor, valor);
break;
case 3:
default:
printf (“\nOpção inválida. Tente outra vez.”)
}
printf (“\n”);
}
while(opcao != 5);
}
5.10 – Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
45 5.1 – A Declaração If: neste item foi apresentada uma introdução da declaração
if, listando suas características e propriedades bem como sua estrutura. Foi apresentado um
exemplo com o uso da declaração if para facilitar o entendimento e compreensão.
46 5.1.1 – Usando a Declaração Else: foi apresentada a declaração else, que tem
como função dar seqüência às opções apresentadas pela declaração if.
47 5.1.2 – O Encadeamento If-Else-If: foi mostrado o encadeamento de ifs e elses
que permite inserir dentro das alternativas à um primeiro if novos teste condicionais.
48 5.1.3 – A Expressão Condicional: foi visto que qualquer expressão válida na
linguagem C pode ser usada para controla a declaração if. Isto é, o tipo de expressão não
precisa se restringir àquelas envolvendo operadores relacionais e lógicos. Só é requerido
que a expressão resulte em um valor zero ou não zero.
49 5.1.4 – Ifs Aninhados: foi visto que um if aninhado é uma declaração if que é
objeto de um if ou um else, ou seja, uma declaração if dentro da outra.
50 5.2 – A Declaração For: neste item foi apresentada uma introdução da
declaração for, listando suas características e propriedades bem como sua estrutura. Foi
apresentado que a declaração for é usada para repetir um ou mais comandos um certo
número de vezes.
51 5.2.1 – Partes do Comando For são Opcionais: foi visto que muitas partes
constituintes da declaração for são opcionais.
52 5.2.2 – Compreendendo um Laço Null: foi visto que este é um recurso para
obter um retardo ou uma breve pausa durante a execução de um programa.
53 5.2.3 –Laço Infinito: foi visto que muitas vezes, devido à erros de
programação, alguns laços for nunca atingem sua condição final, sendo executados
infinitamente.
54 5.2.4 – Usando o operador vírgula da Linguagem C dentro de um laço for:
foi mostrado que a Linguagem C permite inicializar e incrementar múltiplas variáveis em
um laço for separando as operações com vírgula.
55 5.3 – A Declaração While: foi visto que ao encontrar um laço while no
programa, a Linguagem C testa a condição especificada. Se a condição for verdadeira,
efetuará os comandos contidos no laço. Se o comando for falso, continuará a execução de
seu programa ao primeiro comando que segue.
56 5.3.1 – Partes de um Laço While: foi estudado as partes componentes do laço
while, de tal forma a conhece-las evitando laços infinitos e erros de programação.
57 5.4 – A Declaração Do-While: neste item foi mostrado que o laço do-while
verifica sua condição no final do laço. Isso significa que um laço do-while será executado
pelo menos uma vez.
58 5.5 – Comando Continue: foi visto neste item que o comando continue em um
programa é utilizado quando quer-se pular a iteração atual.
59 5.6 – Finalizando um Laço Usando o Comando Break: o comando Break é
utilizado para terminar imediatamente a execução de um laço e a partir daí continuar
executando a próxima linha de código após o laço.
60 5.7 – Desvios com o comando goto: o comando goto permite que a execução
do programa desvie-se para uma localização específica, chamada rótulo.
61 5.8 – A Declaração Switch: na declaração switch, a variável é sucessivamente
testada contra uma lista de inteiros ou constantes caractere. Quando uma associação é
encontrada, o conjunto de comandos associado com a constante é executado.
62 5.8.1 – As Declarações Switch Aninhadas: da mesma forma vista para a
declaração for, a declaração switch pode ser posta no código de programação da forma
aninhada.
63 5.9 – Laços Aninhados: Quando um laço está dentro de outro, diz-se que o
laço mais interno é aninhado. Laços aninhados propiciam o meio de resolver alguns
problemas interessantes de programação.
Módulo 6 – Matrizes
6.1 – Compreendendo as Matrizes
Muitas vezes em programas, é necessário que uma variável contenha muitos
valores. Por exemplo, a variável nota pode controlar as notas obtidas por 100 alunos em um
exame. Da mesma maneira, a variável salários poderia controlar os salários de cada
funcionário em uma companhia. Uma matriz é uma estrutura de dados que pode armazenar
múltiplos valores do mesmo tipo. Por exemplo, pode-se criar uma matriz que possa conter
100 valores do tipo int e uma segunda matriz com 25 valores do tipo float.
Todo valor que se atribui a uma matriz precisa ser do mesmo tipo que o tipo da
matriz. Neste módulo será mostrado como criar e trabalhar com matrizes em programas.
Com o tempo, após o uso de matrizes, ficará claro como é simples o uso de matrizes.
6.2 – Declarando uma Matriz
Para declarar uma matriz, precisa-se especificar o tipo desejado (tal como int, float
ou double), bem como o tamanho da matriz. Para especificar o tamanho de uma matriz,
coloca-se o número de valores que a matriz pode armazenar dentro de colchetes após o
nome da matriz. Por exemplo, a declaração à seguir cria uma matriz chamada notas, que
pode armazenar 100 notas de exame do tipo int:
int notas [100];
De forma similar, a seguinte declaração cria uma matriz do tipo float, que contém
50 salários:
float salarios [50];
Quando uma matriz é declarada, a Linguagem C aloca memória suficiente para
conter todos os elementos. O primeiro item está na posição 0. Por exemplo, nas matrizes
notas e salarios, os comandos a seguir atribuem os valores 80 e 35000 aos primeiros
elementos da matriz:
notas[0] = 80;
salarios[0] = 35000;
Como o primeiro elemento da matriz inicia no deslocamento 0, o último elemento
da matriz ocorre uma posição antes do tamanho da matriz. Dadas as matrizes anteriores, os
comandos a seguir atribuem valores ao último elemento de cada matriz:
notas[99] = 65;
salarios[49] = 250000;
6.3 – Requisitos de Armazenamento de uma Matriz
Ao declarar uma matriz, o compilador aloca memória suficiente para conter o
número de valores especificado. A quantidade real de memória que o compilador aloca
depende do tipo da matriz. Por exemplo, uma matriz de 100 elementos do tipo int
normalmente irá requerer 100*2 ou 200 bytes de memória. Por outro lado, uma matriz de
100 elementos do tipo float irá requerer 100*4 bytes ou 400 bytes. O programa abaixo, usa
o operador sizeof para exibir a quantidade de memória que os diferentes tipos de matrizes
requerem:
#include <stdio.h>
void main(void)
{
int notas[100];
float salar[100];
char string[100];
printf("Memoria para conter int notas[100] %d bytes\n",
sizeof(notas));
printf("Memoria para conter float salar[100] %d bytes\n",
sizeof(salar));
printf("Memoria para conter char string[100] %d bytes\n",
sizeof(string));
}
6.4 - Inicializando uma Matriz
segue:
Muitos programas em Linguagem C inicializam as strings de caracteres como
char titulo[] = "UNESP - Ilha Solteira";
char secao[64] = "Matrizes";
No primeiro caso, o compilador alocará 22 bytes para armazenar a string. No
segundo, o compilador alocará uma matriz de 64 bytes, inicializando os primeiro 8
caracteres com as letras "Matrizes" e o caracter NULL. A maioria dos compiladores
também inicializará as posições de bytes restantes com NULL. Quando declara-se matrizes
de outros tipos, pode inicializar matrizes da mesma forma. Por exemplo, o comando a
seguir inicializa a matriz de inteiros notas com os valores 80, 70, 90, 85 e 80:
int notas[5] = {80, 70, 90, 85, 80};
Quando atribui-se valores iniciais a uma matriz, é necessário delimitar os valores
por abre e fecha chaves ( {} ). No caso anterior, o tamanho da matriz é igual ao número de
valores atribuídos. O comando a seguir, no entanto, atribui quatro valores de ponto
flutuante a uma matriz que pode armazenar 64 valores:
float salar[64] = {25000.0, 32000.0; 44000.0, 23000.0};
Dependendo do compilador, ele pode atribuir 0 aos elementos aos quais o programa
não atribui valores explícitos. No entanto, como regra, não se deve assumir que o
compilador inicializará os outros elementos. Além disso, se não for especificado um
tamanho de matriz, o compilador alocará memória suficiente para conter somente os
valores que você especificar. Por exemplo, a seguinte declaração de matriz cria uma matriz
grande o suficiente para conter três valores do tipo long:
long planetas[] = {1234567L, 654321L, 1221311L};
6.5 – Acessando Elementos da Matriz
Os valores armazenados em uma matriz são chamados elementos de matriz. Para
acessar um elemento da matriz, você especifica o nome da matriz e o elemento que deseja.
O programa a seguir, inicializa a matriz notas e depois usa printf para exibir os valores dos
elementos:
#include <stdio.h>
void main(void)
{
int notas[5] = {80, 70, 90, 85, 80};
printf("Valores da Matriz\n");
printf("notas[0] %d\n", notas[0]);
printf("notas[1] %d\n", notas[1]);
printf("notas[2] %d\n", notas[2]);
printf("notas[3] %d\n", notas[3]);
printf("notas[4] %d\n", notas[4]);
}
6.6 – Percorrendo em um Laço Elementos da Matriz
Quando se referencia muitos elementos de uma matriz, especificar números para
cada elemento da matriz individualmente pode ser demorado e tedioso. Como uma
alternativa, os programas podem usar uma variável para referenciar os elementos da matriz.
Por exemplo, assumindo que a variável i contenha o valor 2, o comando a seguir atribuiria
o valor 80 a matriz[2]:
i = 2;
matriz[i] = 80;
notas:
O código a seguir usa a variável i e um laço for para exibir os elementos da matriz
#include <stdio.h>
void main(void)
{
int notas[5] = {80, 70, 90, 85, 80};
int i;
printf("Valores da Matriz\n");
for (i = 0; i < 5; i++)
printf("notas[%d] %d\n", i, notas[i]);
}
6.7 – Usando Constantes Para Definir as Matrizes
Como visto, quando os programas trabalham com matrizes, é necessário especificar
o tamanho da matriz. Por exemplo, o programa a seguir declara uma matriz de cinco
valores e depois usa um laço for para exibir os valores da matriz:
#include <stdio.h>
void main(void)
{
int valores[5] = {80, 70, 90, 85, 80};
int i;
for (i = 0; i < 5; i++)
printf("valores[%d] %d\n", i, valores[i]);
}
Por exemplo, suponha que seja necessário alterar o código anterior de tal forma que
ele suporte 10 valores. Precisará alterar não somente a declaração da matriz, mas também o
laço for. Quanto mais alterações forem feitas em um programa, maiores as chances de errar.
O programa a seguir declara uma matriz com base na constante TAM_MATRIZ. Como se
vê, o programa não somente usa a constante para declarar a matriz, mas também usa a
constante como a condição final para o laço for:
#include <stdio.h>
#define TAM_MATRIZ 5
void main(void)
{
int valores[TAM_MATRIZ] = {80, 70, 90, 85, 80};
int i;
for (i = 0; i < TAM_MATRIZ; i++)
printf("valores[%d] %d\n", i, valores[i]);
}
Se mais tarde for necessário alterar o tamanho da matriz, poderá alterar o valor
atribuído à constante TAM_MATRIZ para que o programa automaticamente atualize os
laços que controlam a matriz como o tamanho da matriz.
6.8 – Passando uma Matriz a uma Função
Quando declara-se uma função que trabalha com um parâmetro matriz, precisa
informar o compilador. Por exemplo, o seguinte programa usa a função exibe_matriz para
exibir os valores em uma matriz. Como pode-se ver, o programa passa para a função tanto a
matriz como o número de elementos que a matriz contém, como mostrado a seguir:
#include <stdio.h>
void exibe_matriz(int valores[], int num_de_elementos)
{
int i;
for (i = 0; i < num_de_elementos; i++)
printf("%d\n", valores[i]);
}
void main(void)
{
int notas[5] = {70, 80, 90, 100, 90};
exibe_matriz(notas, 5);
}
Quando uma função recebe uma matriz como parâmetro, o programa não precisa
especificar o tamanho da matriz na declaração do parâmetro. No caso da função
exibe_valores, os colchetes após o nome da variável valor informam o compilador de que o
parâmetro é uma matriz. Sabendo que o parâmetro é uma matriz, o compilador não se
preocupa com o tamanho da matriz que o programa passa para a função.
6.9 – Revisitando as Matrizes Como Funções
No item anterior, foi visto que ao declarar o parâmetro formal para uma matriz, não
é necessário declarar o tamanho da matriz. Em vez disso, pode-se especificar somente o
abre e fecha colchetes. O programa a seguir passa três matrizes diferentes (de diferentes
tamanhos) para a função exibe_valores:
#include <stdio.h>
void exibe_matriz(int valores[], int num_de_elementos)
{
int i;
printf("Prestes a exibir %d valores\n",num_de_elementos);
for (i = 0; i < num_de_elementos; i++)
printf("%d\n", valores[i]);
}
void main(void)
{
int notas[5] = {70, 80, 90, 100, 90};
int conta[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int pequeno[2] = {-33, -44};
exibe_matriz(notas, 5);
exibe_matriz(conta, 10);
exibe_matriz(pequeno, 2);
}
6.10 – Como as Matrizes de String Diferem
Muitos programas passam string para funções. Em muitos casos, as funções não
especificaram o tamanho da string. Por exemplo, o comando a seguir usa a função strupr
para converter uma string para maiúsculas:
char titulo[64] = "Unesp Ilha Solteira";
strupr(titulo);
Como foi visto, na Linguagem C o caracter NULL representa o final de uma string
de caracteres. Portanto, as funções podem procurar o caracter NULL nos elementos da
matriz para determinar onde a matriz termina. No entanto, as matrizes de outros tipos, tais
como int, float ou long, não tem um caracter finalizador equivalente. Portanto, você
normalmente precisa passar para as funções que trabalham com matrizes o número de
elementos que a matriz contém.
6.11 – Quantos Elementos Uma Matriz Pode Armazenar
Já foi visto anteriormente que, dependendo do tipo de uma matriz, a quantidade real
de memória que uma matriz pode consumir diferirá. Se o ambiente de trabalho for o DOS, a
quantidade de memória que as matrizes podem consumir dependerá do modelo de memória
atual. Em geral, uma matriz não pode consumir mais do que 64Kb de espaço. O programa a
seguir pode não passar na compilação porque as matrizes consomem muita memória:
void main(void)
{
char string[66000L];
int values[33000L];
float numbers[17000];
}
/* 66,000 bytes */
/* 33,000 * 2 = 66,000 bytes */
/* 17,000 * 4 = 68,000 bytes */
6.12 – Matrizes Multidimensionais
Como foi visto, uma matriz é uma variável que pode armazenar múltiplos valores
do mesmo tipo. Em todos os exemplos apresentados até o momento, as matrizes
consistiram de uma fileira de dados. No entanto, a Linguagem C permite matrizes bi, tri e
multidimensionais. O melhor modo de visualizar uma matriz bidimensional é com uma
tabela com linhas e colunas. Se uma matriz contém três dimensões, visualize-a como várias
páginas, cada uma das quais contendo uma tabela bidimensional.
Por exemplo, ao declarar-se uma matriz bidimensional, o primeiro valor que for
especificado informará o número de linhas, e o segundo valor, o número de colunas:
int tabela [2] [3];
6.13 - Inicializando Elementos em Uma Matriz Bidimensional
No item 5.4 foi visto que, para inicializar elementos de matriz, você pode colocar os
valores do elemento dentro de abre e fecha colchetes após a declaração da matriz. O
comando a seguir usa a mesma técnica para inicializar uma matriz bidimensional. No
entanto, neste caso, o comando especifica os valores para cada linha da matriz dentro de
chaves:
int tabela [2] [3] = {{1,2,3}, {4,5,6}};
O compilador inicializará os elementos da matriz como mostrado a seguir:
1
2
3
4
5
6
6.14 – Percorrendo em Um Laço Uma Matriz Bidimensional
Quando os programas trabalham com matrizes bidimensionais, normalmente usa-se
duas variáveis para acessar elementos da matriz. O programa a seguir usa as variáveis linha
e coluna para exibir os valores contidos dentro da matriz tabela:
#include <stdio.h>
void main(void)
{
int linha, coluna;
float tabela[3][5] = {{1.0, 2.0, 3.0, 4.0, 5.0},
{6.0, 7.0, 8.0, 9.0, 10.0},
{11.0, 12.0, 13.0, 14.0, 15.0}};
for (linha = 0; linha < 3; linha++)
for (coluna = 0; coluna < 5; coluna++)
printf("tabela[%d][%d] = %f\n", linha, coluna,
tabela[linha][coluna]);
}
Colocando laços for um dentro do outro, como mostrado, o programa exibirá os
elementos contidos na primeira linha da matriz (1.0 até 5.0). Em seguida, o programa irá se
mover para a próxima linha, e, depois, para a terceira linha, exibindo os elementos dentro
de cada linha.
6.15 – Percorrendo Uma Matriz Tridimensional
No item anterior foi visto como percorrer uma matriz bidimensional usando duas
variáveis chamadas linha e coluna. O programa a seguir usa as variáveis linha, coluna e
tabela para percorrer uma matriz tridimensional:
#include <stdio.h>
void main(void)
{
int linha, coluna, tabela;
float valores[2][3][5] = {
{{1.0, 2.0, 3.0, 4.0, 5.0},
{6.0, 7.0, 8.0, 9.0, 10.0},
{11.0, 12.0, 13.0, 14.0, 15.0}},
{{16.0, 17.0, 18.0, 19.0, 20.0},
{21.0, 22.0, 23.0, 24.0, 25.0},
{26.0, 27.0, 28.0, 29.0, 30.0}}
};
for (linha = 0; linha < 2; linha++)
for (coluna = 0; coluna < 3; coluna++)
for (tabela = 0; tabela < 5; tabela++)
printf("valores[%d][%d][%d] = %f\n", linha,
coluna, tabela,
valores[linha][coluna][tabela]);
}
6.16 – Passando uma Matriz Bidimensional Para uma Função
No item 5.8, foi visto que, ao passar matrizes para uma função, não será necessário
especificar o número de elementos na matriz. Em matrizes bidimensionais, não será
necessário especificar o número de linha na matriz, mas, sim, especificar o número de
colunas. O programa a seguir usa a função exibe_2d_matriz para exibir o conteúdo de
variáveis matrizes bidimensionais:
#include <stdio.h>
void exibe_2d_matriz(int matriz[][10], int linhas)
{
int i, j;
for (i = 0; i < linhas; i++)
for (j = 0; j < 10; j++)
printf("matriz[%d][%d] = %d\n", i, j, matriz[i][j]);
}
void main(void)
{
int a[1][10] = {{1, 2, 3, 4, 5,
int b[2][10] = {{1, 2, 3, 4, 5,
{11, 12, 13, 14,
int c[3][10] = {{1, 2, 3, 4, 5,
{11, 12, 13, 14,
{21, 22, 23, 24,
exibe_2d_matriz(a, 1);
exibe_2d_matriz(b, 2);
exibe_2d_matriz(c, 3);
}
6, 7, 8, 9,
6, 7, 8, 9,
15, 16, 17,
6, 7, 8, 9,
15, 16, 17,
25, 26, 27,
10}};
10},
18, 19,20}};
10},
18, 19, 20},
28, 29, 30}};
6.17 – Tratando as Matrizes Multidimensionais Como Uma
Dimensão
Quando for necessário trabalhar com os elementos de uma matriz multidimensional,
mas sem precisar acessar os elementos em suas posições de linha ou coluna, as funções
poderão tratar a matriz multidimensional como se ela tivesse uma dimensão. O programa a
seguir retorna a soma dos valores em uma matriz bidimensional:
#include <stdio.h>
long soma_matriz(int matriz[], int elementos)
{
long soma = 0;
int i;
for (i = 0; i < elementos; i++)
soma += matriz[i];
return(soma);
}
void main(void)
{
int a[10] =
{1,
int b[2][10]={{1,
{11,
int c[3][10]={{1,
{11,
{21,
printf("Soma dos
soma_matriz(a,
printf("Soma dos
soma_matriz(b,
printf("Soma dos
soma_matriz(c,
2, 3, 4, 5,
2, 3, 4, 5,
12, 13, 14,
2, 3, 4, 5,
12, 13, 14,
22, 23, 24,
6, 7, 8, 9,
6, 7, 8, 9,
15, 16, 17,
6, 7, 8, 9,
15, 16, 17,
25, 26, 27,
10};
10},
18, 19,20}};
10},
18, 19, 20},
28, 29,30}};
elementos da primeira matriz %d\n",
10));
elementos da segunda matriz %d\n",
20));
elementos da terceira matriz %d\n",
30));
}
6.18 – Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
64 6.1 – Compreendendo as Matrizes: é dada uma explicação que uma matriz é
uma estrutura de dados que pode armazenar múltiplos valores do mesmo tipo.
65 6.2 – Declarando uma Matriz: neste item é visto como declarar uma matriz,
precisando-se especificar o tipo desejado (tal como int, float ou double), bem como o
tamanho da matriz.
66 6.3 – Requisitos de Armazenamento de uma Matriz: foi visto que ao
declarar uma matriz, o compilador aloca memória suficiente para conter o número de
valores especificado. A quantidade real de memória que o compilador aloca depende do
tipo da matriz.
67 6.4 – Inicializando uma Matriz: neste item é explicada de maneira clara
como inicializar diferentes tipos de matrizes.
68 6.5 – Acessando Elementos da Matriz: foi visto que para acessar um
elemento da matriz, você especifica o nome da matriz e o elemento que deseja.
69 6.6 – Percorrendo em um Laço Elementos da Matriz: foi mostrado que
quando se referencia muitos elementos de uma matriz, especificar números para cada
elemento da matriz individualmente pode ser demorado e tedioso. Como uma alternativa,
os programas podem usar uma variável para referenciar os elementos da matriz.
70 6.7 – Usando Constantes Para Definir as Matrizes: mostra como os
programas usam constantes para definir matrizes.
71 6.8 – Passando uma Matriz a uma Função: é visto que programas passam
para a função tanto a matriz como o número de elementos que a matriz contém.
72 6.9 – Revisitando as Matrizes Como Funções: . foi visto um programa que
passa três matrizes diferentes (de diferentes tamanhos) para a função exibe_valores.
73 6.10 – Como as Matrizes de String Diferem: explica as principais diferenças
das matrizes de string.
74 6.11 – Quantos Elementos Uma Matriz Pode Armazenar: foi visto que se o
ambiente de trabalho for o DOS, a quantidade de memória que as matrizes podem consumir
dependerá do modelo de memória atual. Em geral, uma matriz não pode consumir mais do
que 64Kb de espaço.
75 6.12 – Matrizes Multidimensionais: foi visto que a Linguagem C permite
matrizes bi, tri e multidimensionais.
76 6.13 – Inicializando Elementos em Uma Matriz Bidimensional: foi
mostrado como inicializar uma matriz bidimensional.
77 6.14 – Percorrendo em Um Laço Uma Matriz Bidimensional: foi mostrado
um programa que usa as variáveis linha e coluna para exibir os valores contidos dentro da
matriz tabela.
78 6.15 – Percorrendo Uma Matriz Tridimensional: foi mostrado um
programa que usa as variáveis linha, coluna e tabela para percorrer uma matriz
tridimensional.
79 6.16 – Passando uma Matriz Bidimensional Para uma Função: foi visto
que para passar matrizes bidimensionais para uma função, não será necessário especificar o
número de linha na matriz, mas, sim, especificar o número de colunas.
80 6.17 – Tratando as Matrizes Multidimensionais Como Uma Dimensão: foi
visto que quando for necessário trabalhar com os elementos de uma matriz
multidimensional, mas sem precisar acessar os elementos em suas posições de linha ou
coluna, as funções poderão tratar a matriz multidimensional como se ela tivesse uma
dimensão.
Módulo 7 – Ponteiros
7.1 – Ponteiros como Endereços
Como já foi visto, uma variável é o nome de uma posição na memória que pode
armazenar um valor de um determinado tipo. Os programas referenciam cada posição na
memória usando um endereço exclusivo. Um ponteiro é uma variável ou um valor que
contem um endereço. A Linguagem C utiliza muito os ponteiros. Quando se passa matrizes
ou string para as funções, o compilador C passa um ponteiro. Da mesma forma, quando
uma função precisa alterar o valor de um parâmetro, o programa deve passar para a função
um ponteiro para o endereço de memória da variável.
7.2 – Determinando o Endereço de uma Variável
Um ponteiro é um endereço de uma posição na memória. Quando os programas
trabalham com matrizes (e strings), o programa trabalha com um ponteiro para o primeiro
elemento da matriz. Quando os programas precisarem determinar o endereço de uma
variável, deverão usar o operador de endereço da Linguagem C “&”.
Por exemplo, o programa a seguir usa o operador de endereço para exibir o
endereço de várias variáveis diferentes:
#include <stdio.h>
void main(void)
{
int conta = 1;
float salario = 40000.0;
long distancia = 1234567L;
printf("O endereço de conta ‚ %x\n", &conta);
printf("O endereço de salario ‚ %x\n", &salario);
printf("O endereço de distancia ‚ %x\n", &distancia);
}
7.3 – Como a Linguagem C trata Matrizes como Ponteiros
Já foi visto anteriormente que um compilador C trata as matrizes como ponteiros.
Por exemplo, quando um programa passa uma matriz para uma função, o compilador passa
o endereço inicial da matriz.
O programa a seguir exibe o endereço inicial de várias matrizes diferentes:
#include <stdio.h>
void main(void)
{
int conta[10];
float salarios[5];
long distancia[10];
printf("O endereço da matriz conta ‚ %x\n", conta);
printf("O endereço da matriz salarios ‚ %x\n", salarios);
printf("O endereço da matriz distancia ‚ %x\n", distancia);
}
7.4 – Aplicando o Operador de Endereço (&) a uma Matriz
Se o operador de endereço for aplicado a uma matriz, a Linguagem C retornará o
endereço inicial da matriz. Portanto, aplicar o operador de endereço a uma matriz é
redundante. O programa a seguir exibe o endereço inicial de uma matriz, seguido pelo
ponteiro que o operador de endereço retorna:
#include <stdio.h>
void main(void)
{
int conta[10];
float salarios[5];
long distancias[10];
printf("O endereço da matriz conta ‚ %x &conta ‚ %x\n",
conta, &conta);
printf("O endereço da matriz salarios ‚ %x &conta ‚ %x\n",
salarios, &salarios);
printf("O endereço da matriz distancias ‚ %x &distancias ‚
%x\n", distancias, &distancias);
}
7.5 – Declarando Variáveis Ponteiros
À medida que os programas tornarem-se mais complexos, ponteiros são usados com
muita freqüência. Para armazenar ponteiros, os programas precisam declarar variáveis
ponteiros. Para declarar um ponteiro, precisa-se especificar o tipo do valor ao qual o
ponteiro aponta (tal como int, float, char, etc.) e um asterisco (*) antes do nome da
variável. Por exemplo, o comando a seguir declara um ponteiro para um valor do tipo int:
int *iptr;
Como qualquer variável, precisa-se atribuir um valor a uma variável ponteiro antes
de poder usar o ponteiro dentro do programa. Quando atribui-se um valor a um ponteiro,
realmente atribui um endereço. Assumindo que anteriormente tenha-se declarado int conta,
o comando a seguir atribui o endereço da variável conta ao ponteiro iptr:
iptr = &conta;
/* Atribui o endereço de conta a iptr */
O programa a seguir declara a variável ponteiro iptr e atribui ao ponteiro o endereço
da variável conta. O programa então exibe o valor da variável ponteiro, juntamente com o
endereço de conta:
#include <stdio.h>
void main(void)
{
int *iptr;
int conta = 1;
/* Declara variavel ponteiro */
iptr = &conta;
printf("Valor de iptr %x Valor de conta %d Endereço de conta
%x\n", iptr, conta, &conta);
}
7.6 – Desreferenciando um Ponteiro
Desreferenciar um ponteiro é o processo de acessar o valor de uma posição de
memória específica. Para desreferenciar o valor de um ponteiro, usa-se o operador asterisco
de indireção (*). Por exemplo, o comando printf a seguir exibe o valor apontado pelo
ponteiro inteiro iptr:
printf(“O valor apontado po iptr é %d\n”, *iptr);
Da mesma forma, o comando a seguir atribui o valor apontado pela variável iptr
para a variável conta:
conta = *iptr;
Finalmente, o comando a seguir atribui o valor 7 à posição de memória apontada
por iptr:
*iptr = 7;
7.7 – Usando Valores de Ponteiro
O programa a seguir atribui ao ponteiro int iptr o endereço da variável conta. O
programa depois exibe o valor do ponteiro e o valor armazenado na posição apontada pelo
ponteiro (o valor contador).
O programa então modifica o valor apontado pelo ponteiro, como mostrado aqui:
#include <stdio.h>
void main(void)
{
int contador = 10;
int *iptr;
/* Declara valor do ponteiro */
iptr = &contador;
/* Atribui o endereço */
printf("Endereço em iptr %x Valor em *iptr %d\n", iptr, *iptr);
*iptr = 25;
/* Altera o valor na memória */
printf("Valor de contador %d\n", contador);
}
7.8 – Ponteiros com Parâmetros de Função
O Módulo de Funções examina em detalhes o processo de passar parâmetros para as
funções. Quando for necessário alterar o valor de um parâmetro, deverá passar para a
função um ponteiro para um parâmetro. O programa a seguir usa os ponteiros para dois
parâmetros do tipo int para permutar os valores das variáveis, como mostrado a seguir:
#include <stdio.h>
void troca_valores(int *a, int *b)
{
int temp;
temp = *a; /* Armazena temporariamente o valor */
/* apontado por a */
*a = *b;
/* Atribui o valor de b a a */
*b = temp; /* Atribui o valor de a a b */
}
void main(void)
{
int um = 1, dois = 2;
troca_valores(&um, &dois);
printf("um contém %d dois contém %d\n", um, dois);
}
Como pode ser visto, dentro da função, os comandos desreferenciam os ponteiros
usando o operador de indireção (*). O programa passa o endereço de cada variável para a
função usando o operador de endereço (&).
7.9 – A Aritmética de Ponteiros
Um ponteiro é um endereço que aponta para um valor de um determinado tipo na
memória. Nos termos mais simples possíveis, um ponteiro é um valor que aponta para uma
posição de memória específica. Se somar-se o valor 1 a um ponteiro, o ponteiro apontará
para a próxima posição de memória. Se somar-se 5 ao valor de um ponteiro, o ponteiro
apontará para a posição de memória de cinco posições adiante do endereço atual. No
entanto, a aritmética de ponteiro não é tão simples quanto parece. Por exemplo, assuma que
um ponteiro contenha o endereço 1000. Se fosse somado 1 ao ponteiro, poderia se esperar
que o resultado fosse 1001. No entanto, o endereço resultante depende do tipo de ponteiro.
Por exemplo, se fosse somado 1 a um ponteiro do tipo char (que contém 1000), o endereço
resultante será 1001. Se fosse somado 1 a um ponteiro do tipo int (que requer dois bytes na
memória), o endereço resultante será 1002. Quando for efetuada a aritmética de ponteiro é
necessário ter em mente o tipo de ponteiro. Além de somar valores aos ponteiros, os
programas poderão subtrair valores ou somar e subtrair dois ponteiros.
7.10 – Incrementando e Decrementando um Ponteiro
Uma das operações mais comuns com ponteiros é o incremento e o decremento do
valor de um ponteiro para apontar para a próxima posição ou para a posição anterior na
memória. O programa a seguir atribui o endereço inicial de uma matriz de valores inteiros
ao ponteiro iptr. O programa depois incrementa o valor do ponteiro para exibir os cinco
elementos que a matriz contém:
#include <stdio.h>
void main(void)
{
int valores[5] = {1, 2, 3, 4, 5};
int contador;
int *iptr;
iptr = valores;
for (contador = 0; contador < 5; contador++)
{
printf("%d\n", *iptr);
iptr++;
}
}
7.11 - Percorrendo uma String usando um Ponteiro
Uma string é uma matriz de caracteres terminada pó NULL. O programa a seguir
usa a função exibe_string para exibir uma string de caracteres usando um ponteiro:
#include <stdio.h>
void exibe_string(char *string)
{
while (*string)
putchar(*string++);
}
void main(void)
{
exibe_string("Unesp Ilha Solteira");
}
Como pode ser visto, a função exibe_string declara a variável string como um
ponteiro. Usando o ponteiro, a função simplesmente percorre os caracteres da string até
encontrar o caractere NULL. Para exibir o caractere, a função exibe_string primeiro
desreferencia o endereço do ponteiro (obtendo o caractere). Em seguida, a função
incrementa o ponteiro para apontar para o próximo caractere na string.
7.12 - Funções que retornam Ponteiros
O valor que uma função retorna é sempre do tipo declarado no protótipo ou
cabeçalho da função. Além de retornar esses tipos básicos, as funções podem declarar
ponteiros para os valores. Por exemplo, a função fopen, que a maioria das funções da
Linguagem C usam para abrir um canal de arquivo, retorna um ponteiro para uma estrutura
do tipo FILE, como mostrado aqui:
FILE *fopen(const char *nomecaminho, const char *modo);
7.13 – Criando uma Função que retorna um Ponteiro
O programa a seguir cria uma função chamada string_maiusc que converte todos os
caracteres de uma string para maiúsculas e depois retorna um ponteiro para uma string:
#include <stdio.h>
#include <ctype.h>
char *string_maiusc(char *string)
{
char *ender_inicial, *temp;
ender_inicial = temp = string;
while (*string)
*(temp++) = toupper(*string++);
return(ender_inicial);
}
void main(void)
{
char *titulo = "UNESP Ilha Solteira";
char *string;
string = string_maiusc(titulo);
printf("%s\n", string);
printf("%s\n", string_maiusc("Matrizes e Ponteiros"));
}
7.14 - Uma Matriz de Ponteiros
Assim como pode-se criar funções que retornam ponteiros, também pode-se criar
matrizes de ponteiros. Mais comumente serão usadas matrizes para conter strings de
caracteres.
Como exemplo, a declaração a seguir cria uma matriz chamada dias que contém
ponteiros para string de caracteres:
char *dias[7]= {“Domingo”,
“Sexta”, “Sábado”};
“Segunda”,
“Terça”,
“Quarta”,
“Quinta”,
Se for examinado o tipo da matriz da direita para a esquerda, será visto que a matriz
contém sete elementos. O asterisco antes do nome da variável especifica um ponteiro. Se
combinar o nome do tipo char que precede o nome da variável, a declaração se tornará
uma matriz de ponteiros para strings de caractere.
7.15 - Percorrendo em um Laço uma Matriz de Strings de
Caracteres
Ao criar uma matriz de string de caracteres, a Linguagem C armazena os ponteiros
para cada string dentro dos elementos da matriz.
O programa a seguir percorre em um laço a matriz dias, que contém ponteiros para
strings que contém os nomes dos dias da semana, como mostrado aqui:
#include <stdio.h>
void main(void)
{
char *dias[7] = {"Domingo", "Segunda", "Ter‡a",
"Quarta", "Quinta", "Sexta", "S bado"};
int i;
for (i = 0; i < 7; i++)
printf("dias[%d] cont‚m %s\n", i, dias[i]);
}
7.16 – Usando um Ponteiro para um Ponteiro para String de
Caracteres
A declaração a seguir cria um ponteiro para uma string de caracteres:
char **dia_útil_ptr;
O programa a seguir usa um ponteiro para um ponteiro para string de caracteres
para exibir o conteúdo da matriz diasuteis:
#include <stdio.h>
void main(void)
{
char *diasuteis[] = {"Segunda", "Terça", "Quarta",
"Quinta", "Sexta", "" };
char **dia_util;
dia_util = diasuteis;
while (*dia_util)
printf("%s\n", *dia_util++);
}
Quando o programa inicia, ele atribui ao ponteiro dia_útil o endereço inicial da
matriz diasuteis (o endereço da string Segunda). O programa repete então um laço até
encontrar o ponteiro para a string NULL (a condição final).
7.17 – Declarando uma Constante String usando um Ponteiro
Muitas vezes strings de caracteres são inicializadas como segue:
char titulo[] = “Unesp Ilha Solteira”;
Quando se declara uma matriz com colchetes vazios, o compilador C aloca memória
suficiente para armazenar os caracteres especificados (e o terminador NULL), atribuindo à
variável titulo um ponteiro para o primeiro caractere. Como o compilador C
automaticamente aloca a memória necessária e depois trabalha com um ponteiro para a
memória, os programas podem usar um ponteiro de string de caracteres, em vez de uma
matriz, como mostrado a seguir:
char *titulo = “Unesp Ilha Solteira”;
7.18 – O Ponteiro do tipo Void
Ao declarar uma variável ponteiro, é preciso especificar o tipo do valor para o qual
o ponteiro aponta. Quanto se faz isso, o compilador pode, mais tarde, efetuar aritmética de
ponteiros corretamente e adicionar os valores de deslocamentos corretos quando
incrementar ou decrementar o ponteiro. Em alguns casos, no entanto, os programas não
manipularão o valor de um ponteiro de qualquer maneira. Em vez disso, os programas
somente irão obter um ponteiro para uma posição de memória com a qual o programa
determinará o uso do ponteiro.
Nesses casos, os programas poderão criar um ponteiro para o tipo void, como
mostrado aqui:
void *ponteiro_memoria;
7.19 – Ponteiros para as Funções
A Linguagem C permite criar ponteiros para todos os tipos de dados. Além disso,
ela permite que os programas criem e usem ponteiros para as funções. O uso mais comum
dos ponteiros para funções é permitir que os programas passem uma função como um
parâmetro para outra função. As seguintes declarações criam ponteiros para funções:
int (*min)();
int (*max)();
float (*media)();
Observe o uso dos parênteses entre os nomes das variáveis. Se os parênteses fossem
removidos, as declarações serviriam como protótipos de função para as funções que
retornam ponteiros para um tipo específico, como mostrado aqui:
int *min();
int *max();
float *media();
Ao ler uma declaração de variável, deve-se começar com a declaração mais interna
que aparece dentro dos parênteses, e, depois, trabalha-se da direita para a esquerda:
int (*min)();
7.20 – Ponteiro para uma Função
O uso mais comum de um ponteiro para uma função é passar essa função como um
parâmetro para outra função. O programa a seguir passa a função min ou max para a
função pega_result. Dependendo da função que o programa passa, o valor que pega_result
retorna será diferente:
#include <stdio.h>
int pega_result(int a, int b, int (*compare)())
{
return(compare(a, b)); // Chama a função passada
}
int max(int a, int b)
{
printf("Em max\n");
return((a > b) ? a: b);
}
int min(int a, int b)
{
printf("Em min\n");
return((a < b) ? a: b);
}
void main(void)
{
int result;
result = pega_result(1, 2, &max);
printf("O maximo entre 1 e 2 ‚ %d\n", result);
result = pega_result(1, 2, &min);
printf("O minimo de 1 e 2 ‚ %d\n", result);
}
7.21 – Usando um Ponteiro para um Ponteiro para um Ponteiro
A Linguagem C permite criar variáveis que são ponteiros para outros ponteiros. Em
geral, não há limite no número de indireções (ponteiros para ponteiros) que os programas
podem usar. No entanto, para a maioria dos programadores, usar mais do que um ponteiro
para um ponteiro resultará em confusão considerável, e tornará seus programas muito
difíceis de compreender. Por exemplo, o programa a seguir usa três níveis de ponteiros para
um valor do tipo int. Separe um tempo para experimentar este programa e desenhe os níveis
de indireção em um pedaço de papel até compreender o processamento que ele executa:
#include <stdio.h>
int qual_e_o_valor(int ***ptr)
{
return(***ptr);
}
void main(void)
{
int *nivel_1, **nivel_2, ***nivel_3, valor = 1001;
nivel_1 = &valor;
nivel_2 = &nivel_1;
nivel_3 = &nivel_2;
printf("O valor ‚ %d\n", qual_e_o_valor(nivel_3));
}
7.22 – Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
81 7.1 – Ponteiros como Endereços: é visto que um ponteiro é uma variável ou
um valor que contem um endereço.
82 7.2 – Determinando o Endereço de uma Variável: foi visto como obter um
endereço de uma variável e quando os programas precisarem determinar o endereço de
uma variável, deverão usar o operador de endereço da Linguagem C “&”.
83 7.3 – Como a Linguagem C trata Matrizes como Ponteiros: é mostrado o
código de um programa que exibe o endereço inicial de várias matrizes diferentes.
84 7.4 – Aplicando o Operador de Endereço (&) a uma Matriz: foi visto que se
o operador de endereço for aplicado a uma matriz, a Linguagem C retornará o endereço
inicial da matriz.
85 7.5 – Declarando Variáveis Ponteiros: foi visto que para declarar um ponteiro,
precisa-se especificar o tipo do valor ao qual o ponteiro aponta (tal como int, float, char,
etc.) e um asterisco (*) antes do nome da variável.
86 7.6 – Desreferenciando um Ponteiro: foi explicado que desreferenciar um
ponteiro é o processo de acessar o valor de uma posição de memória específica. Para
desreferenciar o valor de um ponteiro, usa-se o operador asterisco de indireção (*).
87 7.7 – Usando Valores de Ponteiro: foi apresentado o código de um programa
em que o valor de um ponteiro é usado para receber o endereço de uma variável de depois o
ponteiro é usado para indicar o valor armazenado na posição de memória que ele indica.
88 7.8 – Ponteiros com Parâmetros de Função: foi visto que quando for
necessário alterar o valor de um parâmetro, deverá passar para a função um ponteiro para
um parâmetro. Foi apresentado um programa que usa os ponteiros para dois parâmetros do
tipo int para permutar os valores das variáveis.
89 7.9 – A Aritmética de Ponteiros: foi explicada de manira clara a aritmética de
ponteiros, que trata os ponteiros como valores que podem ser somados ou subtraídos.
90 7.10 – Incrementando e Decrementando um Ponteiro: foram vistas as
operações de incremento e o decremento do valor de um ponteiro, que servem para apontar
para a próxima posição ou para a posição anterior na memória.
91 7.11 – Percorrendo uma String usando um Ponteiro: foi visto um programa
que usa uma função para exibir uma string de caracteres usando um ponteiro.
92 7.12 – Funções que retornam Ponteiros: foi explicado que o valor que uma
função retorna é sempre do tipo declarado no protótipo ou cabeçalho da função. Além de
retornar esses tipos básicos, as funções podem declarar ponteiros para os valores.
93 7.13 – Criando uma Função que retorna um Ponteiro: foi visto um
programa que cria uma função que converte todos os caracteres de uma string para
maiúsculas e depois retorna um ponteiro para uma string.
94 7.14 – Uma Matriz de Ponteiros: foi explicado que assim como pode-se criar
funções que retornam ponteiros, também pode-se criar matrizes de ponteiros. Mais
comumente serão usadas matrizes para conter strings de caracteres.
95 7.15 – Percorrendo em um Laço uma Matriz de Strings de Caracteres: foi
visto um programa que percorre em um laço uma matriz , que contém ponteiros para strings
que contém os nomes dos dias da semana.
96 7.16 – Usando um Ponteiro para um Ponteiro para String de Caracteres:
foi visto um programa que usa um ponteiro para um ponteiro para string de caracteres para
exibir o conteúdo de uma matriz.
97 7.17 – Declarando uma Constante String usando um Ponteiro: foi visto
como declarar uma constante string usando um ponteiro.
98 7.18 – O Ponteiro do tipo Void: foi dada uma explicação geral sobre o
ponteiro do tipo void.
99 7.19 – Ponteiros para as Funções: foi visto que a Linguagem C permite que os
programas criem e usem ponteiros para as funções.
1007.20 – Ponteiro para uma Função: foi visto que o uso mais comum de um
ponteiro para uma função é passar essa função como um parâmetro para outra função.
1017.21 – Usando um Ponteiro para um Ponteiro para um Ponteiro: foi visto
que a Linguagem C permite criar variáveis que são ponteiros para outros ponteiros. Em
geral, não há limite no número de indireções (ponteiros para ponteiros) que os programas
podem usar.
Módulo 8 - Sistema de Arquivos
8.1 – Introdução
Na Linguagem C, um arquivo é um conceito lógico que pode ser aplicado a tudo,
desde arquivos em disco até terminais. Um fluxo é associado a um arquivo específico pela
realização de uma operação de abertura. Uma vez que um arquivo esteja aberto,
informações podem ser intercambiadas entre o arquivo e o seu programa.
Nem todos os arquivos têm as mesmas capacidades. Por exemplo, um arquivo em
disco pode suportar acesso randômico enquanto um acionador de fita não pode. Isso ilustra
um ponto importante sobre o sistema de E/S da linguagem C: todos os fluxos são os
mesmos, mas nem todos os arquivos os são.
Se pode suportar acesso randômico, então uma abertura de um arquivo também
inicializa um indicador de posição do arquivo no começo do arquivo. À medida que cada
caractere é lido ou escrito para o arquivo, o indicador de posição é incrementado,
assegurando, assim, a progressão através do arquivo.
Um arquivo é desassociado de um fluxo específico por meio de uma operação de
fechamento. Em fluxos abertos para saída, o fechamento do fluxo faz com que o conteúdo,
se houver, da área intermediária (buffer) seja descarregado para o dispositivo externo. Esse
processo é geralmente referenciado como uma limpeza/descarga de apontador e garante
que nenhuma informação será acidentalmente esquecida na área intermediária do disco.
Todos os arquivos são fechados automaticamente quando o programa termina
normalmente.
8.2 – O Ponteiro de Arquivo
A linha comum que une o sistema de E/S de disco aos programas escritos em C é o
ponteiro de arquivo. Um ponteiro de arquivo é um ponteiro para uma área na memória
(buffer) onde estão contidos vários dados sobre o arquivo a ler ou escrever, tais como o
nome do arquivo, estado e posição corrente. O buffer apontado pelo ponteiro de arquivo é a
área intermediária entre o arquivo no disco e o programa.
Este buffer intermediário entre arquivo e programa é chamado “fluxo”, e no jargão
dos programadores é comum falar em funções que operam fluxos em vez de arquivos. Isto
se deve ao fato de que um fluxo é uma entidade lógica genérica, que pode estar associada a
uma unidade de fita magnética, um disco, uma porta serial, etc. Adotaremos esta
nomenclatura aqui.
Um ponteiro de arquivo é uma variável ponteiro do tipo FILE que é definida em
stdio.h. Para ler ou escrever em um arquivo de disco, o programa deve declarar uma (ou
mais de uma se formos trabalhar com mais de um arquivo simultaneamente) variável
ponteiro de arquivo. Para obter uma variável ponteiro de arquivo, usa-se uma declaração
semelhante a esta:
FILE *fp;
onde fp é o nome que escolhemos para a variável (podia ser qualquer outro).
8.3 – Abrindo um Arquivo Usando FOPEN
Muitos programas em Linguagem C armazenam e lêem informações de um arquivo.
Antes que os programas possam ler ou gravar informações em um arquivo, será preciso
abrir o arquivo. A função fopen permite que seus programas abram um arquivo. O formato
de fopen é como segue:
#include <stdio.h>
FILE *fopen(const char *nomearq, const char *modo);
O parâmetro nomearq é uma string de caracteres que contém o nome do arquivo
desejado, tal como "c:\arqdados.dat". O parâmetro modo especifica como quer-se usar o
arquivo - para ler, gravar ou anexar. A tabela abaixo descreve os valores de modo que
fopen suporta:
Modo
"r"
"w"
"a"
"rb"
"wb"
"ab"
"r+"
"w+"
"a+"
"r+b"
"w+b"
"a+b"
"rt"
"wt"
"at"
"r+t"
"w+t"
Abre um arquivo para leitura
Significado
Cria um arquivo para escrita
Acrescenta dados para um arquivo existente
Abre um arquivo binário para leitura
Cria um arquivo binário para escrita
Acrescenta dados a um arquivo binário existente
Abre um arquivo para leitura/escrita
Cria um arquivo para leitura/escrita
Acrescenta dados ou cria um arquivo para leitura/escrita
Abre um arquivo binário para leitura/escrita
Cria um arquivo binário para leitura/escrita
Acrescenta ou cria um arquivo binário para leitura/escrita
Abre um arquivo texto para leitura
Cria um arquivo texto para escrita
Acrescenta dados a um arquivo texto
Abre um arquivo-texto para leitura/escrita
Cria um arquivo texto para leitura/escrita
"a+t"
Acrescenta dados ou cria um arquivo texto para leitura/escrita
A função fopen retorna um ponteiro (chamado ponteiro de arquivo) para uma
estrutura do tipo FILE que o arquivo de cabeçalho stdio.h define. Seus programas usarão o
ponteiro de arquivo para suas operações de entrada e saída. Se a função fopen não puder
abrir o arquivo especificado, ela retornará o valor NULL. Seus programas sempre devem
testar o valor de retorno de fopen para garantir que abriu o arquivo com sucesso, como
mostrado aqui:
if ((pa = fopen("NOMEARQ.EXT", "r")) != NULL)
{
/*Arquivo aberto com sucesso*/
}
else
{
/*Erro ao abrir o arquivo*/
}
Dentro do programa, é preciso declarar a variável ponteiro de arquivo como segue:
void main()
{
FILE *pa /*Ponteiro para uma estrutura do tipo FILE*/
Muitos programas abrem um arquivo para entrada e outro para saída. Em tais casos,
você poderia declarar dois ponteiros de arquivo, como mostrado aqui:
FILE *entrada, *saida;
8.4 – A Estrutura FILE
Como você aprendeu, quando os programas efetuam operações de entrada e saída,
elas normalmente declaram ponteiros de arquivos usando a estrutura FILE, como visto no
final do item 3.3.
Se você examinar o arquivo de cabeçalho stdio.h, encontrará a definição da
estrutura FILE. Esta definição foi tomada do Turbo C++ Lite:
typedef struct
{
short level;
/*Nivel do buffer cheio/vazio*/
unsigned flags;
/* Sinalizadores de status*/
char fd;
/* Descritor de arquivo*/
unsigned char hold;
/*Caracter ungetc se não existir um buffer*/
short bsize;
/*Tamanho do buffer*/
unsigned char *buffer; /*Buffer de transferência*/
unsigned char *curp;
/*Ponteiro ativo atual*/
unsigned stemp;
/*Indicador de arquivo temporário*/
short token;
/*Usado para verificação de validade*/
} FILE
/*Esse é o objeto FILE */
A estrutura FILE contém o descritor de arquivo de baixo nível que o sistema
operacional usa para acessar o arquivo, o tamanho do buffer do arquivo e a localização, o
buffer de caracteres que unget usa, um sinalizador que indica se o arquivo é um arquivo
temporário, e outras variáveis sinalizadoras. Além disso, a estrutura FILE armazena o
ponteiro de arquivo que controla sua localização atual dentro do arquivo.
Se estiver trabalhando no ambiente do DOS, a maioria dos compiladores definirá
uma matriz de tamanho fixo (normalmente 20) dos ponteiros de arquivo que contém as
informações para cada arquivo que seu programa abrirá. Se o programa precisa abrir mais
de 20 arquivos, consulte a documentação do compilador para conhecer os passos que
precisará seguir para modificar o tamanho da matriz de ponteiros de arquivo.
8.5 – Fechando um Arquivo Aberto
Fechar um arquivo instrui o sistema operacional a esvaziar todos os buffers de disco
associados com o arquivo e a liberar os recursos do sistema que o arquivo consumiu, tais
como os dados de ponteiros de arquivo. A função fclose fecha o arquivo associado com o
ponteiro de arquivo especificado, como mostrado aqui:
#include <stdio.h>
int fclose (FILE *pont_arquivo);
Se fclose for bem sucedida, retornará o valor 0. Se ocorrer um erro, fclose retornará
a constante EOF, como mostrado aqui:
if (fclose(pa) == EOF)
printf ("Erro ao fechar o arquivo de dados\n");
À medida que for examinando programas em Linguagem C, será visto que a maioria
deles não testa o valor de retorno de fopen, como mostrado aqui:
fclose(pa);
Na maioria das vezes, se uma operação fechar arquivo apresentar erro, o programa
poderá fazer muito pouco para corrigir a situação. No entanto, se estiver trabalhando com
arquivos de dados críticos, deverá exibir uma mensagem de erro para o usuário para que ele
possa examinar o conteúdo do arquivo.
Se não chamar a função fclose, C fechará os arquivos abertos quando o programa
terminar.
8.6 – Lendo e Gravando Informações no Arquivo um Caracter
de Cada Vez
Quando os programas efetuam operações de entrada e saída em arquivos, podem ler
e gravar dados um caracter de cada vez ou uma linha de cada vez. Para as operações de
entrada e saída de caractere, os programas podem usar as funções fgetc e fputc, cujos
formatos são mostrados aqui:
#include <stdio.h>
int fgetc (FILE *pont_entrada);
int fputc (int caractere, FILE *pont_saida);
A função fgetc lê o caracter atual do arquivo de entrada especificado. Se o ponteiro
de arquivo tiver chegado ao final do arquivo, fgetc retornará a constante EOF. A função
fputc gravará um caractere na posição do ponteiro de arquivo atual dentro do arquivo de
saída especificado. Se um erro ocorrer, fputc retornará a constante EOF. O programa a
seguir, usa fgetc e fputc para copiar o conteúdo do arquivo do diretório raiz config.sys para
um arquivo chamado config.tst:
#include <stdio.h>
void main(void)
{
FILE *entrada, *saida;
int letra;
if ((entrada = fopen("\\CONFIG.SYS", "r"))==NULL)
printf("Erro ao abrir \\CONFIG.SYS\n");
else if ((saida = fopen("\\CONFIG.TST", "w"))==NULL)
printf("Erro ao abrir \\CONFIG.TST\n");
else
{
/* Lê e grava cada caractere no arquivo*/
while ((letra = fgetc(entrada)) != EOF)
fputc(letra, saida);
fclose(entrada);
/* Fecha o arquivo entrada */
fclose(saida);
/* Fecha o arquivo saida */
}
}
8.7 – O Ponteiro de Posição do Ponteiro de Arquivo
O item 8.4 apresentou a estrutura FILE. Como foi visto, um dos campos da estrutura
armazena um ponteiro da posição para localização atual dentro do arquivo. Quando abre-se
um arquivo para operações de leitura ou gravação, o sistema operacional define o ponteiro
da posição no início do arquivo. Toda vez que ler ou gravar um caractere, o ponteiro da
posição avançará um caractere. Se ler uma linha de texto do arquivo, o ponteiro da posição
avançará para o início da próxima linha. Usando o ponteiro da posição, a funções de
entrada e saída de arquivo sempre podem controlar a localização atual dentro do arquivo.
Quando abre-se um arquivo no modo de anexação, o sistema operacional define o ponteiro
da posição no final do arquivo. A tabela a seguir especifica a localização na qual fopen
coloca o ponteiro da posição quando abre-se o arquivo nos modos de leitura, gravação e
anexação.
Modo de Abertura
a
r
w
Posição do Ponteiro do Arquivo
Imediatamente após o último caractere do arquivo
No início do arquivo
No final do arquivo
8.8 – Determinando a Posição Atual no Arquivo
No item anterior foi visto como a Linguagem C controla a posição atual em
arquivos abertos para as operações de entrada e saída. Dependendo do programa, algumas
vezes será necessário determinar o valor do ponteiro de posição.
Em tais casos usa-se a função ftell, como segue:
#include <stdio.h>
long int ftell (FILE *pont_arquivo);
A função ftell retorna um valor inteiro longo que especifica o byte de deslocamento
à partir da posição atual no arquivo especificado. O programa a seguir usa ftell para exibir
informações do ponteiro de posição. O programa começa abrindo o arquivo do diretório
raiz config.sys no modo de leitura. O programa então usa ftell para exibir a posição atual.
Em seguida, o programa lê e exibe o conteúdo do arquivo. A pós encontrar o final do
arquivo, o programa novamente usará ftell para exibir a posição atual, como segue:
#include <stdio.h>
void main(void)
{
FILE *entrada;
int letra;
if ((entrada = fopen("\\CONFIG.SYS", "r")) == NULL)
printf("Erro ao abrir \\CONFIG.SYS\n");
else
{
printf("A posição atual é o byte %d\n\n",
ftell(entrada));
/* Lê e grava cada caractere no arquivo*/
while ((letra = fgetc(entrada)) != EOF)
fputc(letra, stdout);
printf("\nA posição atual é o byte %d\n",
ftell(entrada));
fclose(entrada);
/* Fecha o arquivo entrada */
}
}
8.9 - O Significado de Canais de Arquivo
Muitos livros e revistas referem-se aos ponteiros de arquivos da Linguagem C como
ponteiros stream (ou de canais) de arquivos. Ao contrário de outras linguagens de
programação, a Linguagem C não assume que os arquivos contêm informações em um
formato específico. Em vez disso, a Linguagem C considera todos os arquivos como nada
mais que uma coleção de bytes. À medida que se lê um arquivo, lê um byte após o outro,
em outras palavras, uma seqüência de bytes. Seus programas e funções precisam interpretar
os bytes. Por exemplo, a função fgets considera o caractere de alimentação de linha como o
final de uma linha e o início de outra.
Logo, quando se escrevem funções ou programas que manipulem arquivos, pensa-se
nos arquivos como uma coleção de bytes.
8.10 – Lendo Linhas de Textos
Quando os programas lêem arquivos de texto, normalmente fazem isso uma linha de
cada vez. Para ler uma linha de um arquivo, os programas podem usar a função fgets, cujo
formato é mostrado a seguir:
#include <stdio.h>
char *fgets(char string, int limite, FILE *canal);
O parâmetro string é o buffer de caracteres no qual fgets lê os dados do arquivo.
Normalmente os programas irão declarar uma matriz de 128 ou 256 bytes para conter os
dados. O parâmetro limite especifica o número de caracteres que o buffer pode conter.
Quando fgets lê caracteres do arquivo, fgets lerá até limite - 1 (limite menos um) ou até o
primeiro caractere de nova linha (\n), o que vier primeiro. A função então colocará um
caractere NULL no buffer para indicar o final da string.
Muitos programas usam a função sizeof para especificar o tamanho do buffer, tal
como sizeof(string). Finalmente o parâmetro canal especifica o arquivo a partir do qual
fgets precisa ler a string. Precisa-se ter aberto previamente o canal usando fopen. Se fgets
ler informações com sucesso do arquivo, fgets retornará um ponteiro para a string. Se
ocorrer um erro ou se chegar ao final do arquivo, fgets retornará NULL.
8.11 – Gravando Linhas de Texto
No item anterior, vimos que os arquivos lerão uma linha de cada vez de um arquivo.
Ao gravar em um arquivo, seus programas gravarão uma linha de cada vez. Para gravar
uma string em um arquivo, seus programas podem usar a função fputs, como segue:
#include <stdio.h>
int fputs(const char *string, FILE *canal);
A função fputs grava os caracteres em uma string especificada até o caractere
NULL de finalização. Se fputs grava a string com sucesso, ela retornará um valor positivo
para a função chamadora. Se ocorrer um erro, fputs retornará uma constante EOF.
8.12 – Exemplo do Uso das Funções Fgets e Fputs
Nos dois itens anteriores foi mostrada a sintaxe das funções fgets e fputs,
respectivamente. Agora será visto como usá-las através do programa abaixo que usa as
funções fgets e fputs para copiar o conteúdo do primeiro arquivo especificado na linha de
comando para o segundo arquivo:
#include <stdio.h>
void main(int argc, char **argv)
{
FILE *entrada, *saida;
char string[256];
if ((entrada = fopen(argv[1], "r")) == NULL)
printf("Erro ao abrir %s\n", argv[1]);
else if ((saida=fopen(argv[2], "w")) == NULL)
{
printf("Erro ao abrir %s\n", argv[2]);
fclose(entrada);
}
else
{
while (fgets(string, sizeof(string), entrada))
fputs(string, saida);
fclose(entrada);
fclose(saida);
}
}
8.13 – As Funções Fread( ) E Fwrite( ):
são:
Esta funções permitem a leitura e a escrita de blocos de dados. Os seus protótipos
unsigned fread(void *buffer, int num_bytes, int count, FILE *fp);
unsigned fwrite(void *buffer, int num_bytes, int count, FILE *fp);
No caso da função fread( ), buffer é um ponteiro para uma região de memória que
receberá os dados lidos do arquivo.
No caso de fwrite( ), buffer é um ponteiro para uma região de memória onde se
encontram os dados a serem escritos no arquivo. O buffer usualmente é um ponteiro para
uma matriz ou uma estrutura.
O número de bytes a ser lido ou escrito é especificado por num_bytes.O argumento
count determina quantos itens (cada um tendo num_bytes de tamanho) serão lidos ou
escritos. Finalmente, fp é um ponteiro para um arquivo de um fluxo previamente aberto por
fopen( ).
A função fread( ) retorna o número de itens lidos, que pode ser menor que count
caso o final de arquivo seja encontrado ou ocorra um erro.
A função fwrite( ) retorna o número de itens escritos, que será igual a count a
menos que ocorra um erro.
Quando o arquivo for aberto para dados binários, fread( ) e fwrite( ) podem ler e
escrever qualquer tipo de informação. O programa a seguir escreve um float em um arquivo
de disco chamado “teste.dat”.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void main()
{
FILE *fp;
float f = 3.141592654;
if ((fp = fopen(“teste.dat”, “wb”)) == NULL)
{
puts (“Não posso abrir arquivo!”);
exit(1);
}
if (fwrite(&f, sizeof(float), 1, fp)!=1)
{
puts(“Erro escrevendo no arquivo!”);
}
fclose(fp);
}
Como o programa anterior ilustra, a área intermediária de armazenamento pode ser,
e freqüentemente é, simplesmente uma variável.
Uma das aplicações mais úteis de fread( ) e fwrite( ) envolve leitura e escrita em
matrizes e estruturas. Por exemplo, este fragmento de programa escreve o conteúdo da
matriz em ponto flutuante exemplo no arquivo “exemplo.dat” usando uma simples
declaração fwrite( ).
#include <stdio.h>
#include <stdlib.h>
void main()
{
FILE *fp;
float exemplo[10][10];
int i, j;
if ((fp = fopen(“exemplo.dat”, “wb”)) == NULL)
{
puts(“Não posso abrir arquivo!”);
exit(1);
}
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
{
exemplo[i][j] = (float) i+j;
}
}
/*O código a seguir grava a matriz inteira num único passo*/
if (fwrite(exemplo, sizeof(exemplo), 1, fp) != 1)
{
puts(“Erro ao escrever arquivo!”);
exit(1);
}
fclose(fp);
}
Note como sizeof é usado para determinar o tamanho da matriz exemplo. O próximo
exemplo usa a função fread( ) para ler a informação escrita pelo programa anterior. Ele
exibe os números na tela para verificação.
#include <stdio.h>
#include <stdlib.h>
void main()
{
FILE *fp;
float exemplo[10][10];
int i,j;
if ((fp = fopen(“exemplo.dat”, “rb”)) == NULL)
{
puts(“Não posso abrir arquivo!”);
exit(1);
}
/*O código a seguir lê a matriz inteira num único passo*/
if (fread(exemplo, sizeof(exemplo), 1, fp) != 1)
{
puts(“Erro ao ler arquivo!”);
exit(1);
}
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
{
printf (“%3.1f “, exemplo[i][j]);
printf(“\n”);
}
}
fclose(fp);
}
8.14 – As Funções Fprintf( ) e Fscanf( )
Estas funções comportam-se como printf( ) e scanf( ) só que escrevem e lêem de
arquivos em disco. Todos os códigos de formato e modificadores são os mesmos. Os
protótipos destas funções são:
int fprintf(FILE *fp, char *string_controle, lista_de_argumentos);
int fscanf(FILE *fp, char *string_controle, lista_de_argumentos);
Embora fprintf( ) e fscanf( ) sejam a maneira mais fácil de ler e escrever tipos de
dados nos mais diversos formatos, elas não são as mais eficientes em termos de tamanho de
código resultante e velocidade. Assim, se a velocidade e tamanho são uma preocupação,
deve-se dar preferência as funções fread( ) e fwrite( ).
Para ilustrar o quão útil essas funções podem ser, o seguinte programa mantém uma
lista de telefones em um arquivo em disco. Pode-se inserir nomes e números ou verificar
um número dado um nome.
#include
#include
#include
#include
#include
<stdio.h>
<string.h>
<stdlib.h>
<ctype.h>
“biblioteca.c” /*biblioteca com funções de tela*/
void add_num (void); /*protótipos de funções*/
void consulta(void);
int menu(void);
void main()
{
char opcao;
do
{
opcao = menu();
switch (opcao)
{
case ‘A’: add_num();
break;
case ‘C’: consulta();
break;
}
}while (opcao != ‘T’)
}
int menu(void)
{
char ch;
do
{
printf (“(A)diciona, (C)onsulta, (T)ermina: ”);
ch = toupper(getche());
printf(“\n”);
}while (ch != ‘T’ && ch != ‘A’ && ch != ‘C’);
return(ch);
}
void add_num(void)
{
FILE *fp;
char nome[80];
int prefixo, num;
if (fp = fopen(“fones.db”, “a”)) == NULL)
{
printf (“O arquivo da lista não pode ser aberto!\n”);
exit(1);
}
printf (“Informe o nome e o número: ”);
fscanf (stdin, “%s%d%d”, nome, &prefixo, &num);
fscanf (stdin, “%*c”); //remove o CR do buffer do teclado
fprintf (fp, “%s %d %d”, nome, prefixo, num);
fclose (fp);
}
void consulta(void)
{
FILE *fp;
char nome[80], nome2[80];
int prefixo, num;
if (fp = fopen(“fones.db”, “r”)) == NULL)
{
printf (“O arquivo da lista não pode ser aberto!\n”);
exit(1);
}
printf (“Nome? ”);
gets (nome);
/*consulta um número*/
while (!feof(fp))
{
fscanf (fp, “%s%d%d”, nome2, &prefixo, &num);
if (!strcmp(nome, nome2))
{
printf (“%s: %d-%d\n”, nome, prefixo, num);
break;
}
}
fclose(fp);
}
8.15 - Testando o Final do Arquivo
Como foi visto, quando a função fgets encontra o final de um arquivo, ela retorna
NULL. Da mesma forma, quando fgetc atinge o final de um arquivo, ela retorna EOF.
Algumas vezes os programas precisam determinar se o ponteiro de um arquivo está no final
do arquivo antes de efetuar uma operação específica. Nesses casos, os programas podem
chamar a função feof, como segue:
#include <stdio.h>
int feof(FILE *canal);
Se o ponteiro de arquivo especificado estiver no final do arquivo, feof retornará um
valor diferente de zero (verdadeiro). Se ainda não tiver atingido o final de arquivo, feof
retornará 0 (falso). O laço a seguir lê e exibe os caracteres do arquivo que correspondem ao
ponteiro do arquivo entrada:
while (! feof(entrada))
fputc (fgetc(entrada), stdout);
8.16 – Posicionamento do Ponteiro de Arquivo com Base em sua
Posição Atual
Foi visto que o ponteiro de arquivo contém um ponteiro de posição para controlar
sua posição atual dentro do arquivo. Quando se conhece o formato do arquivo, algumas
vezes quer avançar o ponteiro de posição para um local específico antes de começar a ler o
arquivo. Por exemplo, os primeiros 256 bytes podem conter informações de cabeçalho que
você não quer ler. Nesses casos, os programas podem usar a função fseek para posicionar o
ponteiro de arquivo, como segue:
#include <stdio.h>
int fseek (FILE *canal, long desloc, int relativo_a);
O parâmetro canal especifica o ponteiro de arquivo que se quer posicionar. Os
parâmetros desloc e relativo_a combinam para especificar a posição desejada. O desloc
contém o byte de deslocamento no arquivo. O parâmetro relativo_a especifica a posição no
arquivo a partir do qual fseek deve aplicar o deslocamento. A tabela abaixo especifica os
valores que se pode usar para o parâmetro relativo_a:
Constante
SEEK_CUR
SEEK_SET
SEEK_END
Significado
Da posição atual no arquivo
Do início do arquivo
Do final do arquivo
Para posicionar o ponteiro de arquivo imediatamente após os primeiros 256 bytes de
informações de cabeçalho em um arquivo, poderia usar fseek, como segue:
fseek(pa, 256, SEEK_SET); /*o deslocamento 0 é o início*/
Se tiver sucesso, fseek retornará o valor 0. Se ocorrer um erro, a função retornará
um valor diferente de zero.
8.17 – Excluindo um Arquivo
Quando os programas trabalham com arquivos, algumas vezes precisa-se excluir um
ou mais arquivos. Em tais casos, os programas podem usar a função remove, que tem o
seguinte formato:
#include <stdio.h>
int remove (const char *nomearq);
Se a função remover o arquivo com sucesso, ela retornará o valor 0. Se um erro
ocorrer, remove retornará o valor -1, e atribuirá à variável global erro um dos valores
listados na tabela abaixo:
Valor
EACES
ENOENT
Significado
Acesso negado
Arquivo não encontrado
O programa a seguir, usa a função remove para apagar todos os arquivos
especificados na linha de comando:
#include <stdio.h>
void main(int argc, char *argv[])
{
while (*++argv)
if (remove(*argv))
printf("Erro ao remover %s\n", *argv);
}
Além da função remove, a maioria dos compiladores C suporta a função unlink, que
também exclui um arquivo:
#include <stdio.h>
int unlink(const char *nomearq);
Se unlink excluir com sucesso um arquivo, ela retornará um valor 0. Se ocorrer um
erro, a função retornará o status de erro -1, atribuindo à variável global erro as constantes
de status de erro já listadas na tabela anterior. O programa a seguir usa a função unlink para
excluir os arquivos especificados na linha de comandos do programa:
#include <stdio.h>
void main(int argc, char *argv[])
{
while (*++argv)
if (unlink(*argv))
printf("Erro ao remover %s\n", *argv);
}
8.18 – Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
1028.1 – Introdução: neste item é mostrado que na linguagem C, um arquivo é
um conceito lógico que pode ser aplicado a tudo, desde arquivos em disco até terminais.
Um fluxo é associado a um arquivo específico pela realização de uma operação de abertura.
Uma vez que um arquivo esteja aberto, informações podem ser intercambiadas entre o
arquivo e o seu programa.
1038.2 – O Ponteiro de Arquivo: foi visto que um ponteiro de arquivo é um
ponteiro para uma área na memória (buffer) onde estão contidos vários dados sobre o
arquivo a ler ou escrever, tais como o nome do arquivo, estado e posição corrente. O buffer
apontado pelo ponteiro de arquivo é a área intermediária entre o arquivo no disco e o
programa.
1048.3 – Abrindo um Arquivo Usando FOPEN: foi visto que a função fopen é
utilizada para abrir um arquivo. Além disso foi vista a estrutura de fopen e que a função
fopen retorna um ponteiro (chamado ponteiro de arquivo) para uma estrutura do tipo FILE
que o arquivo de cabeçalho stdio.h define.
1058.4 – A Estrutura FILE: neste item é mostrado o conteúdo da definição da
estrutura FILE.
1068.5 – Fechando um Arquivo Aberto: fechar um arquivo instrui o sistema
operacional a esvaziar todos os buffers de disco associados com o arquivo e a liberar os
recursos do sistema que o arquivo consumiu, tais como os dados de ponteiros de arquivo. A
função fclose faz exatamente o processo descrito anteriormente.
1078.6 – Lendo e Gravando Informações no Arquivo um Caracter de Cada
Vez: foi visto que usando as funções fgetc e fputc, pode-se ler e gravar dados um caracter
de cada vez ou uma linha de cada vez.
1088.7 – O Ponteiro de Posição do Ponteiro de Arquivo: foi visto que o
ponteiro de posição é utilizado para controlar a posição atual dentro de um arquivo e é
através dele que as funções se localizam dentro de um arquivo.
1098.8 – Determinando a Posição Atual no Arquivo: usa-se a função ftell
determinar o valor do ponteiro de posição e assim determina-se a posição atual do arquivo.
1108.9 – O Significado de Canais de Arquivo: explica o significado de canais de
arquivo.
1118.10 – Lendo Linhas de Textos: foi visto os programas podem usar a função
fgets para ler uma linha de um arquivo.
1128.11 – Gravando Linhas de Texto: foi visto que para gravar uma linha em
um arquivo é necessário utilizar a função fputs.
1138.12 – Exemplo do Uso das Funções Fgets e Fputs: foi visto como usar as
funções fgets e fputs para copiar o conteúdo do primeiro arquivo especificado na linha de
comando para o segundo arquivo.
1148.13 – As Funções Fread( ) E Fwrite( ): funções que permitem a leitura e a
escrita de blocos de dados. Neste item foi apresentada a sintaxe e a função de cada
parâmetro.
1158.14 – As Funções Fprintf( ) e Fscanf( ): foi apresentado que estas funções
comportam-se como printf( ) e scanf( ) só que escrevem e lêem de arquivos em disco.
Embora sejam a maneira mais fácil de ler e escrever tipos de dados nos mais diversos
formatos, foi visto que elas não são as mais eficientes em termos de tamanho de código
resultante e velocidade.
1168.15 – Testando o Final do Arquivo: algumas vezes os programas precisam
determinar se o ponteiro de um arquivo está no final do arquivo antes de efetuar uma
operação específica. Nesses casos, os programas podem chamar a função feof. Se o
ponteiro de arquivo especificado estiver no final do arquivo, feof retornará um valor
diferente de zero (verdadeiro). Se ainda não tiver atingido o final de arquivo, feof retornará
0 (falso).
1178.16 – Posicionamento do Ponteiro de Arquivo com Base em sua Posição
Atual: quando se conhece o formato do arquivo, algumas vezes quer avançar o ponteiro de
posição para um local específico antes de começar a ler o arquivo. Nesses casos, os
programas podem usar a função fseek para posicionar o ponteiro de arquivo.
1188.17 – Excluindo um Arquivo: Quando os programas trabalham com
arquivos, algumas vezes precisa-se excluir um ou mais arquivos. Em tais casos, os
programas podem usar a função remove.
Módulo 9 – Funções Gráficas na Linguagem C
9.1 – A Tela Gráfica
No modo gráfico, a tela do monitor de vídeo é dividida numa fina malha de pontos
individuais, chamados de elementos de imagem, ou pixel. Qualquer pixel pode ser apagado
ou iluminado, o que vem a ser o princípio básico para a criação de imagens gráficas. A
resolução da tela é descrita em termos do número de pixeis por linha ou por coluna. Uma
tela VGA (Adaptador Gráfico Colorido) tem 640 linhas por 480 colunas, como está
mostrado na Figura (a). É importante notar que o sistema de numeração inicia no canto
superior esquerdo da tela no ponto (x,y) = (0,0).
(0,479
)
EIXO X
EIXO Y
(0,0)
(639,0)
(639,479)
(a)
(b)
(a) Eixos de coordenadas no modo VGA
(b) Eixos de coordenada no modo carteziano
Segundo as figuras (a) e (b) acima, vemos que o eixo y na tela do PC está invertido
em relação ao eixo yc do plano cartesiano. Por isto, para mostrar na tela do PC no gráfico
como se fosse no plano cartesiano deve-se fazer a seguinte equivalência:
yv = ymax/2 - yc
onde: yv = eixo y da tela de vídeo
yc = eixo y do plano cartesiano
ymax = n. total de linhas do vídeo
abaixo:
Então a tela do vídeo terá o sistema de eixos conforme dado na figura
(0,320)
yc
(479,0)
(0,0)
xc
(0,-319)
(c)Eixos cartesianos no monitor CGA
9.2
– Detectando
o Adaptador Gráfico
Provavelmente em seu programa seja necessário detectar qual o adaptar gráfico está
em uso. Para isso usamos a função detectgraph. Esta função detecta qual adaptador (CGA,
VGA, EGA, Hercules etc.) está conectado e informa o modo de resolução mais alto do
driver daquele adaptador. A sintaxe da função é:
detectgraph(&gdriver,&gmode);
Os valores que os parâmetros gdriver e gmode poderão assumir são:
Gdriver
1 - CGA
3 - EGA
Placa Gráfica
CGAC0 CGAC1 CGAC2
CGAC3 CGAHI
MCGAC0 MCGAC1
MCGAC2 MCGAC3
MCGAMED MCGAHI
EGALO EGAHI
01
4 - EGA64
EGA64LO EGA64HI
01
5 - EGA-MONO
6 - IBM8514
EGAMONOHI
IBM8514HI IBM8514LO
3
01
7 - HERCULES
8 - ATT400
HERCMONOHI
ATT400C0 ATT400C1
ATT400C2 ATT400C3
ATT400MED ATT400HI
VGALO VGAMED
VGAHI
PC3270HI
0
012345
2 - MCGA
9 - VGA
10 - PC3270
Gmode
01234
012345
012
0
Nº LinhasxColunas
320x200 320x200 320x200 320x200
640x200 - 2 color
320x200 320x200 320x200 320x200
640x200 - 2 color 640x480 - 2 color
640x200 - 16 colors 640x350 - 16
colors
640x200 - 16 colors 640x350 - 4
colors
640x350 - 2 colors
640x480 - 256 colors 1024x768 256 colors
720X348 - 2 color
320x200 320x200 320x200 320x200
640x200 - 2 colors 640x400 - 2
colors
640x200 - 16 colors 640x350 - 16
colors 640x480 - 16 colors
720x350 - 2 colors
A seguir temos um exemplo de um programa que detecta o adaptador imformando o
mesmo:
#include <stdio.h>
#include <graphics.h>
main()
{
int adaptador_grafico,i,j;
int modo_grafico;
clrscr();
detectgraph(&adaptador_grafico,&modo_grafico);
printf("\n O adaptador detectado é %d ",adaptador_grafico);
printf("\n O modo de alta resolucao %d",modo_grafico);
getch();
}
No programa anterior, por exemplo, se o monitor de vídeo for CGA aparecerá:
O adaptador detectado é 9
O modo de alta resolucao é 2
Indicando que o adaptador é 9 (VGA) e as dimensões da tela são as do modo 2 (640
linhas x 480 colunas).
9.3 – Inicializando a Parte Gráfica
Quando os programas que você escrever usarem os recursos gráficos da Linguagem
C, você terá de preparar o sistema para isto. A função initgraph() inicializa o sistema para a
placa e o modo gráfico detectados pela função detectgraph(), tirando a tela do modo texto e
colocando-a no modo gráfico. A função closegraph() cancela o modo gráfico estabelecido
por initgraph() liberando a memória utilizada para gráficos. A seguir um exemplo que
ilustra a sintaxe e o uso destas duas funções initgraph() e closegraph():
#include <stdio.h>
#include <graphics.h>
main()
{
int adaptador_grafico;
int modo_grafico;
detectgraph(&adaptador_grafico,&modo_grafico);
initgraph(&adaptador_grafico,&modo_grafico,"c:\\tc");
getch();
closegraph();
}
O programa anterior detecta a resolução gráfica da tela, inicia a tela no modo
gráfico e depois sai do modo gráfico.
9.4 – Obtendo as Dimensões da Tela
Trabalhando com funções gráficas, seus programas precisarão conhecer as
dimensões da tela gráfica, visto que sem estas informações as manipulações gráficas
incorrerão em erros. As funções getmaxx() e getmaxy() retornam com valores inteiros os
valores máximos das coordenadas x e y da tela. Com estas funções determinam-se as
dimensões máximas da tela, com isto, o programa está habilitado a utilizar toda tela sendo
ela CGA, VGA, EGA, etc. A seguir, o programa ilustra o uso destas duas funções gráficas:
#include <stdio.h>
#include <graphics.h>
main()
{
int adapt, mod, xmax, ymax;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
xmax=getmaxx();
ymax=getmaxy();
outtextxy(100,100,”Estamos no modo gráfico!
tecla.”);
getch();
closegraph();
printf("\n O eixo x da tela vai de 0 a %d", xmax);
printf("\n O eixo y da tela vai de 0 a %d", ymax);
getch();
}
Aperte
uma
Este programa informa quais são as dimensões máximas x e y da tela gráfica. Se a
tela em questão for VGA o resultado será:
O eixo x da tela vai de 0 a 639
O eixo y da tela vai de 0 a 479
9.5 – Desenhando uma Reta na Tela
Muitas são as aplicações gráficas em que é necessário desenhar-se uma reta na tela,
para separar textos ou para outras funções específicas. Para este propósito, a Linguagem C
tem a função line() que tem a seguinte sintaxe:
line(xi,yi,xf,yf);
Os parâmetros entre parênteses indica para o seu programa que ele desenhe uma
linha ligando o ponto xi,yi ao ponto xf,yf.
A seguir, o uso da função line(), com um programa exemplo:
#include <graphics.h>
main()
{
int adapt, mod ;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
line(0, 0, getmaxx(), getmaxy());
getch();
closegraph();
}
9.6 – Desenhando um Retângulo na Tela
Muitas são as aplicações gráficas em que é necessário desenhar-se um retângulo na
tela. Para este propósito, a Linguagem C tem a função rectangle() que tem a seguinte
sintaxe:
rectangle(xsup, ysup, xinf, yinf);
Os parâmetros entre parênteses indica para o seu programa que ele desenhe um
retângulo na tela com o canto superior esquerdo no ponto (xsup, ysup) e o canto inferior
direito no ponto (xinf, yinf).
A seguir, ilustramos o uso da função rectangle(), com um programa exemplo:
#include <graphics.h>
main()
{
int adapt, mod ;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
rectangle(5, 10, 300, 100);
getch();
closegraph();
}
9.7 Desenhando um Ponto na Tela
Muitas são as aplicações gráficas em que é necessário desenhar-se um ponto na tela,
ou seja, ela acende um único pixel na tela. Para este propósito, a Linguagem C tem a função
putpixel() que tem a seguinte sintaxe:
putpixel(x, y, cor);
Os parâmetros entre parênteses indicam para o seu programa que ele imprima um
pixel (ponto) no ponto (x, y),usando a cor especificada por cor.
A seguir, ilustramos o uso da função putpixel(), com um programa exemplo:
#include <graphics.h>
main()
{
int adapt, mod ;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
putpixel(50,100,15);
outtextxy(55,110," aqui 1" );
putpixel(300,100,15); outtextxy(303, 102," aqui 2");
getch();
closegraph();
}
9.8 – Desenhando um Círculo na Tela
Muitas são as aplicações gráficas em que é necessário desenhar-se um círculo na
tela. Para este propósito, a Linguagem C tem a função circle() que tem a seguinte sintaxe:
circle(xcentro, ycentro, raio);
Os parâmetros entre parênteses indicam para o seu programa que ele desenhe um
círculo à partir das coordenadas (xcentro, ycentro),de raio raio.
A seguir, ilustramos o uso da função circle (), com um programa exemplo:
#include <graphics.h>
main()
{
int adapt, mod ;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
circle(300,100,50);
getch();
closegraph();
}
O exemplo a seguir é um programa que desenha uma reta, um retângulo, um círculo
e dois pontos:
#include <graphics.h>
main()
{
int adapt, mod ;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
line(5, 10, 300, 100);
rectangle(5, 10, 300, 100);
putpixel(100, 50, 1);
putpixel(200, 50, 1);
circle(300, 100, 50);
getch();
closegraph();
}
9.9 – Desenhando um Arco Circular na Tela
Muitas são as aplicações gráficas em que é necessário desenhar-se um arco círcular
na tela. Para este propósito, a Linguagem C tem a função arc() que tem a seguinte sintaxe:
arc(xcentro, ycentro, angulo_inicial, angulo_final, raio);
O centro é dado pelas coordenadas (xcentro, ycentro) e o raio por raio. Os
parâmetros angulo_inicial e angulo_final especificam os ângulos inicial e final em graus,
onde o 0 está na posição horizontal apontando para a direita e 90 na posição vertical
apontando para cima.
A seguir, ilustramos o uso da função arc (), com um programa exemplo:
#include <graphics.h>
main()
{
int adapt, mod ;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
arc(300, 100, 0, 90, 100);
getch();
closegraph();
}
9.10 – Limpando a Tela Gráfica
Em muitas ocasiões, seus programas irão desenhar várias formas geométricas e logo
em seguida será necessário apagá-las. Para este propósito, a Linguagem C tem a função
cleardevice(), que tem uma sintaxe bastante simples, que será ilustrada através do programa
exemplo abaixo:
#include <graphics.h>
main()
{
int adapt, mod,i ;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
for (i=0; i<200;i++)
{
line(i+5,i+10,i+300,i+100);
rectangle(i+5, i+10,i+300,i+ 100);
putpixel(100, 50, 15);
putpixel(200, 50, 15);
circle(300+i, 100+i, 50+i);
delay(1000);
cleardevice();
}
getch();
closegraph();
}
9.11 – Mudando as Cores do um Desenho
Em muitos casos será necessário alterar a cor das linhas que delimitam os desenhos
que são feitos pelos seus programas. Para este propósito, a Linguagem C tem a função
setcolor() que tem a seguinte sintaxe:
setcolor(cor);
seguir:
Os valores que se pode atribuir ao parâmetro cor para a tela VGA são dados a
COR
PRETO
VERDE
VERMELHO
MARROM
CINZA ESCURO
VERDE CLARO
VERMELHO CLARO
AMARELO
0
2
4
6
8
10
12
14
VALOR
COR
AZUL
CIANO
MAGENTA
CINZA CLARO
AZUL CLARO
CIANO CLARO
MAGENTA CLARO
BRANCO
1
3
5
7
9
11
13
15
VALOR
9.12 – Mudando a Cor de Fundo e o Preenchimento do um
Desenho
Em muitos casos será necessário alterar a cor de fundo e o padrão de preenchimento
dos desenhos que são feitos pelos seus programas. Para este propósito, a Linguagem C tem
a função setfillstyle() e floodfill() que tem a seguinte sintaxe:
setfillstyle(padrao,cor);
floodfill(x, y, borda);
A função floodfill(x, y, borda) preenche uma área na tela gráfica ao redor do ponto
dado por (x, y) com a cor e o padrão atuais de preenchimento, setados pela instrução
setfillstyle(), até encontrar a cor especificada por borda.
A função setfillstyle() ajusta o padrão e a cor de preenchimento gráfico atuais, sendo
que o parâmetro padrao assume um dos valores abaixo:
0
2
4
6
8
10
Número
Padrão
Cor de fundo
Linhas horizontais
Linhas inclinadas grossas ///
linhas inclinadas grossas \\\
cruzes grossas agrupadas
pontos esparsos
1
3
5
7
9
11
Número
Padrão
Cor sólida
Linhas inclinadas finas ///
Linhas inclinadas finas \\\
cruzes finas agrupadas
linhas intercaladas
pontos densos
A seguir, ilustramos o uso das funções setfillstyle() e floodfill() com um programa
exemplo:
#include <graphics.h>
main()
{
int adapt, mod, xm,ym ;
detectgraph(&adapt,&mod);
initgraph(&adapt,&mod,"c:\\tc");
xm = getmaxx()/2;
ym = getmaxy()/2;
setcolor(LIGHTGREEN);
circle(xm,ym,100);
circle(xm,ym,150);
circle(xm,ym,250);
setfillstyle(2,1);
floodfill(xm,ym+10,10);
setfillstyle(7,1);
floodfill(xm,ym+160,10);
setfillstyle(3,1);
floodfill(xm,ym+260,10);
setfillstyle(7,1);
floodfill(xm*2,ym*2,10);
getch();
closegraph();
}
9.13 Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
1199.1 – A Tela Gráfica: é dada uma explicação detalhada de como funciona
a tela gráfica na Linguagem C.
1209.2 – Detectando o Adaptador Gráfico: explicita o uso da função
detectgraph que identifica o adaptador gráfico em uso.
1219.3 – Inicializando a Parte Gráfica: explica em detalhes que a função
initgraph() inicializa o sistema para a placa e o modo gráfico detectados pela função
detectgraph(), tirando a tela do modo texto e colocando-a no modo gráfico.
1229.4 – Obtendo as Dimensões da Tela: explica que com as funções
getmaxx() e getmaxy() determinam-se as dimensões máximas da tela, conhecendo-se as
dimensões da tela.
1239.5 – Desenhando uma Reta na Tela: fala sobre a função line() que
desenha uma reta na tela.
1249.6 – Desenhando um Retângulo na Tela: fala sobre a função rectangle()
que desenha um retângulo na tela.
1259.7 – Desenhando um Ponto na Tela: fala sobre a função putpixel() que
desenha um ponto na tela.
1269.8 – Desenhando um Círculo na Tela: fala sobre a função circle() que
desenha um ponto na tela.
1279.9 – Desenhando um Arco Circular na Tela: fala sobre a função arc()
que desenha um arco circular na tela.
limpa a tela.
1289.10 – Limpando a Tela Gráfica: fala sobre a função cleardevice() que
1299.11 – Mudando as Cores do um Desenho: fala sobre a função setcolor()
que muda as cores do desenho.
1309.12 – Mudando a Cor de Fundo e o Preenchimento do um Desenho:
fala sobre as funções setfillstyle() e floodfill() que muda as cores de fundo e o
preenchimento de um desenho.
Módulo 10 – Estruturas, Uniões, Enumerações e
Typedef
A Linguagem C permite criar diversos tipos diferentes de dados particulares.
Podemos citar a estrutura, a união, o tipo enumerado e o typedef. A estrutura é um
agrupamento de variáveis sobre um nome, que algumas vezes é chamado de conglomerado
ou agregado de tipos de dados. A união habilita um mesmo pedaço de memória a ser
definido como dois ou mais tipos de dados diferentes. O tipo enumerado é uma lista de
símbolos. O typedef cria um novo nome para um tipo existente.
10.1 – Estruturas
Na Linguagem C, uma estrutura é uma coleção de variáveis referenciadas sobre um
nome, provendo um meio conveniente de manter informações relacionadas juntas. Uma
declaração de estrutura forma uma fonte que pode ser usada para criar variáveis de
estruturas. As variáveis que compreendem a estrutura são chamadas de campos.
Em geral, todos os campos na estrutura estarão logicamente relacionados uns aos
outros. Por exemplo, a informação sobre o nome e o endereço em uma lista de endereços
deve ser normalmente representada em uma estrutura.
O seguinte fragmento de código declara uma estrutura-fonte que define os campos
nome e endereço de tal estrutura. A palavra reservada struct diz ao compilador que uma
estrutura está sendo definida:
struct endereço{
char nome[30];
char rua[40];
char cidade[20];
char estado[3];
unsigned long int cep;
};
Observa-se que a declaração termina com um ponto-e-vírgula. É por isso que uma
estrutura é uma declaração. Temos ainda que, a etiqueta endereço, identifica essa estrutura
de dados particular e é o seu especificador de tipo.
Neste ponto do código, nenhuma variável foi declarada. Somente a forma dos dados
foi definida. Para declarar uma variável com essa estrutura, deve-se escrever:
struct endereço info_adr;
Esta expressão declarará uma variável estrutura do tipo endereço chamada
info_adr. Quando declara-se uma estrutura, define-se um tipo de variável complexa
composto por campos. Até que se declare uma variável desse tipo, ela não existe.
Pode-se também declarar uma ou mais variáveis enquanto declara uma estrutura.
Veja a seguir um exemplo:
struct endereço{
char nome[30];
char rua[40];
char cidade[20];
char estado[3];
unsigned long int cep;
} info_adr, binfo, cinfo;
Isso definirá um tipo de estrutura chamada endereço e declarará as variáveis
info_adr, binfo e cinfo como desse tipo.
10.1.1 – Referenciando os Campos da Estrutura
Campos da estrutura são referenciados pelo uso do operador de seleção de campo: o
ponto. Por exemplo, o seguinte código atribuirá o cep 12345777 ao campo cep da variável
estrutura info_adr declarada anteriormente:
info_adr.cep = 12345777;
O nome da variável estrutura seguido por um ponto e o nome do campo
referenciarão um campo individual da estrutura. Todos os campos da estrutura são
acessados da mesma maneira. A forma geral é:
nome_da_variável_estrutura.nome_do_campo
Portanto, para imprimir o cep na tela, pode-se escrever:
printf (“%lu”, info_adr.cep);
Isso imprimirá o cep contido no campo cep da variável estrutura info_adr.
Da mesma maneira, a matriz de caracteres info_adr.nome pode ser usada em uma
chamada à função gets( ), como mostrada aqui:
gets (info_adr.nome);
Isso passará um ponteiro para caractere para o começo do campo nome. Se fosse
necessário acessar os elementos individuais de info_adr.nome, poderia indexar nome. Por
exemplo, pode-se imprimir o conteúdo da info_adr.nome, um caractere por vez, usando
este código:
int t;
for (t = 0; info_adr.nome[t]; ++t) putchar (info_adr.nome[t]);
10.1.2 – Matrizes de Estruturas
O uso de matrizes de estruturas é o mais comum das estruturas. Para declarar uma
matriz de estruturas, deve-se primeiro definir uma estrutura e, então, declarar uma variável
matriz daquele tipo. Por exemplo, para declarar uma matriz de 100 elementos de estrutura
do tipo endereço (definida anteriormente), deve-se escrever:
struct endereço info_adr[100];
Isso cria 100 conjuntos de variáveis que são organizados como definido na estrutura
endereço. Para acessar uma estrutura específica, o nome da estrutura é indexado.
Por exemplo, para imprimir o código cep na estrutura 3, deve-se escrever:
printf (“%lu”, info_adr[2].cep);
Como todas as variáveis matrizes, as matrizes de estruturas começam sua indexação
em zero.
10.1.3 – Atribuindo Estruturas
Se duas variáveis são do mesmo tipo, pode-se atribuir uma a outra. Nesse caso,
todos os elementos da estrutura no lado esquerdo da atribuição receberão os valores dos
elementos correspondentes da estrutura do lado direito.
Por exemplo, este programa atribui o valor da estrutura um para a estrutura dois e
exibe o resultado 10 98.6.
#include <stdio.h>
void main()
{
struct exemplo
{
int i;
double d;
} um, dois;
um.i = 10;
um.d = 98.6;
dois = um; /*atribui uma estrutura a outra*/
printf (“%d %lf”, dois.i, dois.d);
}
10.1.4 – Passando Estruturas para Funções
Até aqui, todas as estruturas e matrizes de estruturas dos exemplos são assumidas
como globais ou definidas dentro da função que as usa.
Nesta seção, será dada atenção especial à passagem das estruturas e seus elementos
para funções.
Passando Campos de uma Estrutura para Funções: Quando um campo de uma
variável estrutura é passado para uma função, está passando o valor daquele elemento para
a função. Portanto, você está passando uma simples variável. Por exemplo, considere esta
estrutura:
/*observe que neste exemplo a estrutura não possui nome.*/
struct{ /*isso é permitido quanto declaramos apenas uma*/
char x; /*variável com o formato da estrutura definida.*/
int y;
float z;
char s[10];
} exemplo;
Aqui estão exemplos de cada campo sendo passado para uma função:
func1
func2
func3
func4
func5
(exemplo.x); //passa o valor do
(exemplo.y); //passa o valor do
(exemplo.z); //passa o valor do
(exemplo.s); //passa o endereço
(exemplo.s[2]); //passa o valor
caractere em x
inteiro em y
float em x
da string em s
do caractere em s[2]
Contudo, se for necessário passar o endereço de um campo individual da estrutura,
criando uma passagem de parâmetro por referência, deve-se colocar o operador & antes do
nome da variável. Por exemplo, para passar o endereço dos campos na variável estrutura
exemplo, você deve escrever isto:
func1
func2
func3
func4
func5
(&exemplo.x); /*passa o endereço do caractere em x*/
(&exemplo.y); /*passa o endereço do inteiro em y*/
(&exemplo.z); /*passa o endereço do float em x*/
(exemplo.s); /*passa o endereço da string em s*/
(&exemplo.s[2]); /*passa o endereço do caractere em s[2]*/
Note que o operador & precede o nome da variável estrutura, não o nome do campo
individual. Note também que o elemento string s já significa o endereço, assim, o operador
& não é requerido.
Passando Estruturas Inteiras para Funções: Quando uma estrutura é usada como
argumento para uma função, a estrutura inteira é passada usando-se o método padrão de
chamada por valor (passagem de parâmetro por cópia). Isso, obviamente, significa que
qualquer mudança feita no conteúdo de uma estrutura dentro da função para qual ela é
passada não afeta a estrutura usada como argumento.
A consideração mais importante para se ter em mente quando se usa uma estrutura
como parâmetro é que o tipo do argumento deve corresponder ao tipo do parâmetro. Por
exemplo, este programa declara o argumento arg e o parâmetro parm para serem do mesmo
tipo de estrutura:
#include <stdio.h>
/*define um tipo de estrutura*/
struct exemplo{
int a, b;
char ch;
};
void fl(struct exemplo parm)
{
printf (“%d”, parm.a);
}
void main()
{
struct exemplo arg;
arg.a = 1000;
fl(arg);
}
Esse programa, como é fácil de se ver, imprimirá o número 1000 na tela. Como é
mostrado, é melhor definir um tipo estrutura globalmente e, então, usar o seu nome para
declarar variáveis estrutura e parâmetros, quando necessário. Isso ajuda a assegurar a
correspondência entre os argumentos e os parâmetros. Também, mostram para outras
pessoas que estiverem lendo o programa que parm e arg são do mesmo tipo.
10.1.5 – Ponteiros para Estruturas
A Linguagem C permite ponteiros para estruturas da mesma maneira que aceita
ponteiros para qualquer outro tipo de variável. Contudo, existem alguns aspectos especiais
dos ponteiros para estruturas que devem ser vistos.
Declarando um Ponteiro para Estrutura: Ponteiros para estruturas são declarados
colocando-se o * na frente do nome da variável. Por exemplo, assumindo-se a estrutura
endereço definida previamente, a linha abaixo declara ponteiro_endereço para ser um
ponteiro para dados daquele tipo:
struct endereço *ponteiro_endereço;
Usando Ponteiros para Estruturas: Existem muitos usos para ponteiros para
estruturas. Um deles é na obtenção de uma chamada por referência para uma função. Outro
é na criação de estruturas dinâmicas (listas, filas, pilhas, árvores, etc.).
Existe uma desvantagem maior em passar tudo, até a mais simples estrutura, para
funções: o esforço necessário para levar (e trazer) todos os campos da estrutura para a pilha.
Em estruturas simples, com poucos campos, esse esforço não é tão importante, mas, se
muitos campos são usados e se algum deles é matriz, a performance de tempo de execução
pode se tornar inaceitável. A solução para esse problema é passar somente um ponteiro para
estrutura.
Quando um ponteiro para estrutura é passado para uma função, somente o endereço
da estrutura é empurrado (e puxado) para a pilha. Isso significa que uma chamada de
função extremamente rápida pode ser executada. Também, como estará referenciando a
estrutura em si e não uma cópia, a função terá condições de modificar o conteúdo dos
campos da estrutura usada na chamada.
Para encontrar o endereço de uma variável estrutura, o operador & é colocado antes
do nome da variável estrutura. Por exemplo, este fragmento coloca o endereço de cliente no
ponteiro p.:
struct sal{
float saldo;
char nome[80];
} cliente;
struct sal *p; /* declaração de um ponteiro para estrutura*/
Para acessar o campo saldo, pode-se escrever:
(*p).saldo
Raramente será visto referências feitas a um campo de uma estrutura com uso
explícito do operador *, como mostrado no exemplo anterior. Uma vez que o acesso a um
campo de uma estrutura por meio de um ponteiro para a dada estrutura é tão comum, um
operador especial definido pela Linguagem C realiza esta tarefa. Ele é o ->, chamada de
operador seta. Ele é formado utilizando-se o sinal de menos seguido por um sinal de maior
que. A seta é usada no lugar do operador ponto, quando se acessa um campo da estrutura
utilizando-se um ponteiro para a variável estrutura. Por exemplo, a declaração anterior é
usualmente escrita assim:
p->saldo
Para ver como um ponteiro para uma estrutura pode ser usado, examine este
programa simples que imprime horas, minutos e segundos na sua tela, usando uma rotina
de espera cronometrada:
#include <stdio.h>
#include <conio.h>
struct estrut_horas{
int horas;
int minutos;
int segundos;
};
void espera()
{
long int t;
for (t = 1; t < 128000; ++t);
}
void atualiza (struct estrut_horas *t)
{
t->segundos++;
if (t->segundos == 60)
{
t->segundos = 0;
t->minutos++;
}
if (t->minutos == 60)
{
t->minutos = 0;
t->horas++;
}
if (t->horas == 24) t->horas = 0;
espera();
}
void exibe(struct estrut_horas *t)
{
printf (“%d:”, t->horas);
printf (“%d:”, t->minutos);
printf (“%d\n”, t->segundos);
}
void main()
{
struct estrut_horas tempo;
tempo.horas = 0;
tempo.minutos = 0;
tempo.segundos = 0;
for ( ; !kbhit(); )
{
atualiza (&tempo);
exibe (&tempo);
}
}
A cronometragem desse programa é ajustada variando-se o contador do laço na
função espera( ). Como pode-se observar, a estrutura global, chamada estrut_hora, é
definida, mas nenhuma variável é declarada. Dentro da função main( ), a estrutura tempo é
declarada e inicializada como 00:00:00. Isso significa que tempo é conhecida diretamente
apenas na função main( ).
Às duas funções – atualiza( ), que atualiza a hora, e exibe( ), que imprime a hora – é
passado o endereço de tempo. Nas duas funções, o argumento é declarado para ser da
estrutura estrut_hora. Isso é necessário para que o compilador saiba como referenciar os
campos da estrutura.
A referência a cada campo da estrutura é feita pelo uso de um ponteiro. Por
exemplo, se fosse necessário retornar as horas para zero quando chegasse 24:00, poderia-se
escrever:
if (t->horas == 24) t->horas = 0
Essa linha de código diz ao compilador para pegar o endereço da variável t (que é o
tempo na função main( )) e atribuir zero ao seu campo chamado horas.
10.1.6 – Matrizes e Estruturas dentro de Estruturas
Campos de estruturas podem ser de qualquer tipo de dado válido na Linguagem C,
incluindo-se matrizes e estruturas. Já foi visto um exemplo de um campo matriz: a matriz
de caracteres usada em info_adr.
Um exemplo de estrutura que é uma matriz, é tratado como nos exemplos
anteriores. Por exemplo, seja a estrutura:
struct x{
int a[10][10]; /*matriz 10x10 de inteiros*/
float b;
} y;
Para referenciar o inteiro em [3][7] na variável a da estrutura y, deve-se escrever:
y.a[3][7]
Quando uma estrutura é um campo de outra estrutura, ela é chamada de estrutura
aninhada. Por exemplo, o elemento da variável estrutura info_adr do tipo endereço está
aninhado em emp neste exemplo:
struct emp{
struct endereço info_adr;
float salário;
} funcionário;
Aqui endereço é a estrutura definida anteriormente e emp foi definida como tendo
dois elementos. O primeiro elemento é a estrutura do tipo endereço, que conterá um
endereço de um empregado. O segundo é salário, que armazena o salário do empregado. O
seguinte fragmento de código atribui o cep 98765777 para o campo cep do endereço do
funcionário:
funcionário.endereço.cep = 98765777;
Como pode-se verificar, os elementos de cada estrutura são referenciados da
esquerda para a direita do mais externo para o mais interno.
10.2 – Uniões
Na Linguagem C, uma union é uma localização de memória usado por muitos tipos
de variáveis diferentes. A declaração de uma union é similar ao de uma estrutura, como
mostrado neste exemplo:
union u_tipo{
int i;
char ch;
};
Assim como com estruturas, essa declaração não declara qualquer variável. Pode-se
declarar uma variável tanto colocando-se o nome dela no fim da declaração como pelo uso
de uma declaração em separado. Para declarar uma variável union, chamada cnvt, do tipo
u_tipo, usando a declaração dada anteriormente, pode-se escrever:
union u_tipo cnvt;
Em cnvt, o inteiro i e o caractere ch compartilham a mesma localização de
memória.
Quando uma variável union é declarada, o compilador cria automaticamente uma
variável grande o bastante para armazenar o tipo da variável de maior tamanho na union.
Para acessar um elemento union deve-se usar a mesma sintaxe das estruturas: os
operadores ponto e seta. Quando se opera diretamente na union, usa-se o operador ponto.
Se a variável union é acessada por meio de um ponteiro, usa-se o operador seta. Por
exemplo, para atribuir o inteiro 10 ao elemento i de cnvt, deve-se escrever:
cnvt.i = 10;
Uniões são usadas freqüentemente quando são necessárias conversões de tipos, uma
vez que elas permitem que se considere uma região de memória de mais de uma maneira.
10.3 – Enumerações
Uma enumeração é um conjunto de constantes inteiras que especifica todos os
valores legais que uma variável de um tipo específico pode ter. As enumerações não são
incomuns no dia-a-dia. Por exemplo, uma enumeração das moedas usadas nos Estados
Unidos é:
penny, nickel, dime, quarter, half_dollar, dollar
As enumerações são definidas muito semelhantemente às estruturas com a palavra
reservada enum usada para indicar o início de um tipo enumerado. A forma geral é
mostrada aqui:
enum nome_do_tipo_enumerado {lista_de_enumeração} lista de variáveis;
A lista de enumeração é uma lista de nomes separados por vírgulas que representam
os valores que uma variável da enumeração pode ter. Tanto o nome do tipo enumerado
como a lista de variáveis são opcionais. Assim como com estruturas, o nome do tipo
enumerado é usado para declarar variáveis do seu tipo. O fragmento seguintes define uma
enumeração chamada moeda e declara dinheiro para ser desse tipo:
enum moeda {penny, nickel, dime, quarter,
half_dollar, dollar};
enum moeda dinheiro;
Dadas essas declarações, as seguintes utilizações são perfeitamente válidas:
dinheiro = dime;
if (dinheiro == quarter) printf (“É um quarter\n”);
O ponto-chave para entender as enumerações é que cada um dos símbolos é
representado por um valor inteiro. Assim sendo, eles podem ser usados em qualquer
expressão válida com inteiros. A menos que seja inicializado de outra maneira, o valor do
primeiro símbolo da enumeração é zero, o valor do segundo símbolo é 1, e assim por
diante. Portanto, a declaração a seguir exibe 0 2 na tela:
printf (“%d %d”, penny, dime);
É possível especificar o valor de um ou mais símbolos usando-se um inicializador.
Isso é feito seguindo-se o símbolo com um sinal de igual e um valor inteiro. Sempre que
um inicializador é usado, aos símbolos que aparecem depois dele são associados valores
maiores que o valor de inicialização anterior. Por exemplo, o código a seguir atribui o valor
100 para quarter:
enum moeda {penny, nickel, dime, quarter = 100,
half_dollar, dollar};
Uma suposição comum, mas errônea, feita a respeito das enumerações é que os
símbolos podem ser atribuídos e exibidos diretamente. Esse não é o caso. Por exemplo, o
seguinte fragmento de código não procederá como desejado:
/*isto não funciona*/
dinheiro = dollar;
printf (“%s”, dinheiro);
/*este código está incorreto*/
gets(s);
strcpy (dinheiro, s);
Isto é, uma string que contém o nome de um símbolo não é convertida
automaticamente naquele símbolo.
Criar um código para entrada e saída de símbolos enumerados é bastante monótono
(a menos que se esteja disposto a tornar claro os seus valores inteiros). Por exemplo, o
código seguinte é necessário para exibir, em palavras, o tipo de moeda que dinheiro
contém:
switch (dinheiro){
}
case penny: printf (“penny”);
break;
case nickel: printf (“nickel”);
break;
case dime: printf (“dime”);
break;
case quarter: printf (“quarter”);
break;
case half_dollar: printf (“half_dollar”);
break;
case dollar: printf (“dollar”);
break;
Algumas vezes, é possível declarar uma matriz de strings e usar o valor de uma
enumeração como índice na transformação de uma valor enumerado na sua string
correspondente. Por exemplo, este código também produzirá a string apropriada:
char nome[][20] = {
“penny”,
“nickel”,
“dime”,
“quarter”,
“half_dollar”,
“dollar”
};
.
.
.
printf (“%s”, nome[dinheiro]);
Esse código funcionará se nenhuma inicialização de símbolos for usada, uma vez
que a matriz de strings deve ser indexada começando em zero. Por exemplo, este programa
imprime os nomes das moedas:
#include <stdio.h>
enum moeda {penny, nickel, dime, quarter,
half_dollar, dollar};
char nome[][20] =
{
“penny”,
“nickel”,
“dime”,
“quarter”,
“half_dollar”,
“dollar”
};
void main()
{
enum moeda dinheiro;
for (dinheiro = penny; dinheiro <= dollar; dinheiro++)
{
printf (“%s”, nome[dinheiro]);
}
}
Como os valores enumerados devem ser convertidos manualmente nos valores
correspondentes em português para E/S no console, eles encontram grande uso em rotinas
que não fazem tais conversões. É comum ver uma enumeração ser usada para definir uma
tabela de símbolos de um compilador, por exemplo, que não requer interações com
usuários.
10.4 – A Palavra Reservada Typedef
A Linguagem C permite definir explicitamente novos nomes de tipos de dados
usando-se a palavra reservada typedef. Não se cria um novo tipo de dado, mas, ao contrário,
define-se um novo nome para um tipo existente. Este processo pode auxiliar programas
dependentes de máquinas a serem portáveis; somente a declaração typedef tem de ser
modificada. Ele também auxilia na documentação do seu código por permitir nomes
descritivos para tipos de dados padrões. A forma geral da declaração typedef é da a seguir:
typedef tipo_nome;
Nela, temos que tipo é qualquer tipo de dado permitido e nome é o novo nome para
esse tipo.
O novo nome definido é uma adição, não uma substituição, ao nome existente. Por
exemplo, pode-se criar um novo nome para o tipo float usando-se a seguinte declaração:
typedef float flutuante;
Essa declaração diz ao compilador para reconhecer flutuante como um outros nome
para float. A seguir, pode-se criar uma variável float usando-se flutuante:
flutuante atraso;
Aqui, atraso é uma variável de ponto flutuante do tipo flutuante, que é um
sinônimo para float.
Pode-se usar typedef para criar nomes para tipos mais complexos também. Por
exemplo:
typedef struct cliente_tipo{
float divida;
int atraso;
char nome[40];
} cliente;
cliente clist[NUM_CLIENTES]; /*define uma matriz de estruturas*/
/*do tipo cliente*/
Nesse exemplo, cliente não é uma variável do tipo cliente_tipo mas, ao contrário,
um outro nome para struct cliente_tipo. A utilização de typedef pode ajudas a tornar
códigos mais fáceis de serem lidos e portados para outro computador. Mas, lembre-se: não
está sendo criado qualquer tipo de dado novo.
10.5 – Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
13110.1 – Estruturas: foi visto que uma estrutura é uma coleção de variáveis
referenciadas sobre um nome, provendo um meio conveniente de manter informações
relacionadas juntas. Uma declaração de estrutura forma uma fonte que pode ser usada para
criar variáveis de estruturas.
13210.1.1 – Referenciando os Campos da Estrutura: foi visto que os campos da
estrutura são referenciados pelo uso do operador de seleção de campo: o ponto. O nome da
variável estrutura seguido por um ponto e o nome do campo referenciarão um campo
individual da estrutura. Todos os campos da estrutura são acessados da mesma maneira.
13310.1.2 – Matrizes de Estruturas: foi visto que o uso de matrizes de estruturas
é o mais comum das estruturas. Para declarar uma matriz de estruturas, deve-se primeiro
definir uma estrutura e, então, declarar uma variável matriz daquele tipo.
13410.1.3 – Atribuindo Estruturas: foi visto que se duas variáveis são do mesmo
tipo, pode-se atribuir uma a outra. Nesse caso, todos os elementos da estrutura no lado
esquerdo da atribuição receberão os valores dos elementos correspondentes da estrutura do
lado direito.
13510.1.4 – Passando Estruturas para Funções: foi visto neste item como passar
campos de uma estrutura para funções e como passar estruturas inteiras para funções.
13610.1.5 – Ponteiros para Estruturas: foi visto que Linguagem C permite
ponteiros para estruturas da mesma maneira que aceita ponteiros para qualquer outro tipo
de variável. Foi visto como declarar e usar ponteiros em estruturas.
13710.1.6 – Matrizes e Estruturas dentro de Estruturas: foi visto que campos de
estruturas podem ser de qualquer tipo de dado válido na Linguagem C, incluindo-se
matrizes e estruturas. Quando uma estrutura é um campo de outra estrutura, ela é chamada
de estrutura aninhada. Os elementos de cada estrutura são referenciados da esquerda para a
direita do mais externo para o mais interno.
13810.2 – Uniões: foi visto que na Linguagem C, uma union é uma localização de
memória usado por muitos tipos de variáveis diferentes. Para acessar um elemento union
deve-se usar a mesma sintaxe das estruturas: os operadores ponto e seta.
13910.3 – Enumerações: foi visto que uma enumeração é um conjunto de
constantes inteiras que especifica todos os valores legais que uma variável de um tipo
específico pode ter.
14010.4 – A Palavra Reservada Typedef: foi visto que a Linguagem C permite
definir explicitamente novos nomes de tipos de dados usando-se a palavra reservada
typedef. Não se cria um novo tipo de dado, mas, ao contrário, define-se um novo nome para
um tipo existente. Este processo pode auxiliar programas dependentes de máquinas a serem
portáveis.
Módulo 11 – Funções de Entrada/Saída (I/O) e
Programa Analisador Espectral
11.1 – Introdução
As funções de entrada/saída (I/O – input/output) da linguagem C são as equivalentes
das instruções in e out da linguagem Assembly. Elas permitem acesso direto ao hardware
do microcomputador e, portanto, os endereços e modo de programação dos dispositivos de
I/O a serem manipulados tem que conhecidos adequadamente para evitar algum travamento
do sistema por uso inadequado dessas funções.
O mapa de endereços de I/O do PC padrão, apresentado na tabela a seguir, deixam
livre os endereços 220H a 22FH e 300H a 30FH. Portanto, todo tipo de placa que não
tenham grande demanda de endereços, como placas de protótipo ou de aquisição de sinais
podem ser alocadas nesses endereços sem risco de causar conflito com outros dispositivos
de I/O. A placa a ser inserida no slot ou barramento de expansão deve ser previamente
configurada num desses endereços e conectada com o computador desligado. No caso de
placas de aquisição de sinais, os sinais analógicos a serem convertidos só devem ser
conectados com o computador ligado. Também estes sinais devem ser desconetados antes
de se desligar o computador.
Tabela 11.1 – Mapa de Endereços de I/O do PC
11.2 – As Funções inport e outport
As funções inport() e outrport() permitem acesso direto a endereços de dispositivos
de I/O, em 8 ou 16 bits. O arquivo header dessas funções é dos.h. Seus formatos são
apresentados a seguir:
int inport( int port_id);
Le um valor inteiro de 16 bits do endereço port_id especificado;
int inportb( int port_id);
Le um valor inteiro de 8 bits do endereço port_id especificado;
void outport(int port_id, int value)
Envia o conteúdo da variável value, de 16 bits, para o endereço port_id
especificado;
void outportb(int port_id, unsigned char value)
Envia o conteúdo da variável value, de 8 bits para o endereço port_id especificado;
11.3 – Placas de Aquisição de Sinais Analógicos
A placa de aquisição de sinais CIO-DAS-AO-08H é uma placa de baixo custo para
barramento ISA, com as seguintes características:
Entradas analógicas: 8 canais diferenciais multiplexados, 12 bits de resolução do
tipo aproximações sucessivas, tempo de conversão de 25 microssegundos;
Saída analógica: dois canais de 12 bits, com faixa de tensão de saída selecionável
por programação;
A tabela de endereçamento dessa placa é mostrada a seguir:
Tabela 11.2 – Tabela de Endereçamento da Placa CIO-DAS08-AOH
A placa tem um endereço-base setado por meio de chave de configuração. Os
valores normalmente utilizados são 220H ou 300H, em hexadecimal.
11.3.1 - Conversor A/D
A conversão A/D é iniciada com uma escrita no endereço BASE para operação
em 8 bits ou escrita no endereço (BASE +1) para operação em 12 bits.
O final de conversão é sinalizado através da leitura do bit mais significativo do
endereço (BASE+2), que é zerado quando a conversão termina, conforme esquema:
Endereço BASE+2 - Leitura
7
6
5
EOC
IP3
IP2
4
IP1
3
IRQ
2
MUX2
1
MUX1
0
MUX0
O resultado final da conversão pode então ser lido nos endereços BASE e
BASE+1 com a seguinte configuração:
Endereço BASE - Leitura
7
6
5
AD9
AD10
AD11
Endereço BASE+1 - Leitura
4
AD12 (LSB)
3
0
2
0
1
0
0
0
7
AD1 (MSB)
6
AD2
5
AD3
4
AD4
3
AD5
2
AD6
1
AD7
0
AD8
Observa-se acima que o LSB é é lido no endereço (BASE), com apenas 4 bits
menos significativos. No endereço (BASE+1) estão os 8 bits mais significativos. Para
ordenação nos 12 bits, deve, portanto, realizar a leitura nos respectivos endereços,
rotacionar o byte menos significativo 4 casas a direita e somar com o byte superior
rotacionado 4 casas a esquerda.
O seguinte trecho de programa em linguagem C realiza a conversão de um valor
analógico em digital, em 12 bits, já provendo a ordenação dos bits.
outportb(BASE+1,0);
/* Partida no conversor A/D, 12 bits */
a=0x80;
while(a==0x80)
a=inportb(BASE+2)&0x80; /*espera por EOC = 0, fim conversão */
BS=(inportb(BASE)&0xF0)>>4; /* leitura Byte inferior e correção */
Bi=inportb(BASE+1);
/* leitura byte superior */
vec = 16 * BS + Bi ;
/* rearranjo valor digital em 12 bits */
11.3.2 - Conversor D/A
A conversão é realizada diretamente, enviando o valor digital de 12 bits para os
respectivos endereços no seguinte formato (Canal D/A 0):
Endereço BASE+8 - Escrita
7
6
5
DA7
DA6
DA5
Endereço BASE+9 - Escrita
7
6
5
X
X
X
4
DA4
4
X
3
DA3
3
DA11 (MSb)
2
DA2
2
DA10
1
DA1
0
DA0 (LSb)
1
DA9
0
DA8
O canal D/A 1 tem a mesma configuração dos bits, porém os endereços são
(BASE+10) e (BASE+11) respectivamente.
A seguir é mostrado um trecho de programa em linguagem C que faz o envio de um
valor de 12 bits para o conversor D/A. O formato é o mesmo apresentado na aquisição do
sinal, em 12 bits, já conveniente ordenados.
BDAI = vec & 0xFF;
/* mascara os 4 bits mais significativos */
BDAS =( vec & 0xF00 ) >> 8; /* mascara 8 bits menos significativo
e faz rotação de 8 bits a direita */
outportb(BASE+8,BDAI);
/* envia LSB
*/
outportb(BASE+9,BDAS);
/* envia 4 bits do MSB */
11. 4 – Fatores de Escala para o Modo Gráfico
Os fatores de escala nos eixos x e y são usados para que um conjunto qualquer de
dados possam ser exibidos numa região da tela pré-determinada.
Para a dedução das expressões, considere as seguintes definições:
1
NP – número de pontos, valor constante;
2
vec – vetor de dados com NP pontos;
3
vec[i] – i-ésimo elemento do vetor de dados vec, 0
4
vecmin – valor mínimo do vetor vec;
5
vecmax – valor máximo do vetor vec;
6
i – índice do elemento no vetor vec;
7
k – coordenada do eixo x correspondente ao i-ésimo elemento;
8
xmin – coordenada mínima do eixo x da janela especificada;
9
xmax – coordenada máxima do eixo x da janela especificada;
i < NP;
10 ymin - coordenada mínima do eixoy da janela especificada;
11 ymax - coordenada máxima do eixo y da janela especificada;
12 y – coordenada do eixo y correspondente ao elemento vec[i];
11.4.1 Fator de Escala Eixo X
Este fator de escala permite que um vetor com qualquer número de pontos possa ser
plotado no eixo x da tela. O vetor de dados começa no elemento 0 e vai até o índice (NP-1).
0
i
NP-1
xmin
k
xmax
x max x min
NP 1
k
x min
i*
k x min
i
x max x min
NP 1
[11.1]
x
x max x min
NP 1
k i * x x min
11.4.2 - Fator de Escala Eixo Y
Nesse caso são envolvidos os valores máximo e mínimo do vetor, devendo ser
desenvolvida uma rotina para tal objetivo.
ymin
vecmax
[11.2]
[11.3]
y
vec[i]
ymax
vecmin
y max y min
vec min vec max
y y min
vec[i] vecmax *
y
y
y y min
vec[i] vec max
y max
vec min
y max y min
vecmin vecmax
y min
vec max
vec[i] vec max * y y min
[11.4]
[11.5]
[11.6]
Como exemplo, considere uma janela com coordenadas de 10 a 90 % da tela no
eixo x e de 5 a 45 % do eixo y. Os respectivos fatores de escala poderiam ser obtidos como:
float deltax, deltay;
detectgraph(&graf,&gmode);
initgraph(&graf,&gmode,"c:\\tc");
max_min();
xmax =
ymax =
deltax
deltay
getmaxx();
getmaxy();
= (0.9*xmax-0.1*xmax)/(NP-1));
/* [11.2] */
= ((0.45*ymax-0.05*ymax)/(vec_min-vec_max)); /* [11.5]*/
for (i=1;i<NP ;i++)
{ ki = ((i-1)*deltax)+0.08*xmax ;
/* [11.3] */
yi = ((1.0*(vec[i-1]-vec_max))*deltay)+0.1*ymax; /* [11.6] */
kf = (i*deltax)+0.08*xmax;
yf = ((1.0*(vec[i]-vec_max))*deltay)+0.1*ymax;
line(ki,yi,kf,yf);
}
.
.
.
max_min()
{ int i;
vec_max = vec_min = vec[0];
for ( i = 1; i < NP ; i++ )
{
if( vec[i] > vec_max)
if( vec[i] < vec_min)
}
vec_max = vec[i];
vec_min = vec[i];
}
11.4.3 Programa: Análise Espectral
Como exemplo de aplicação, apresenta-se a seguir um programa em linguagem C
que realiza a análise espectral (decomposição de fourier) de um sinal, armazenado no vetor
vec[i]. As fontes de sinais são:
[A] - arquivo de dados com 512 pontos: datan.c;
[G] – gerador de sinais, composto de duas senóides com amplitudes aleatórias;
[P] – placa de aquisição de sinais, modelo CIO-DAS-AO.
O sinal e seu espectro são exibidos em duas janelas, sendo o sinal no domínio do
tempo mostrado na janela superior e o sinal no domínio da freqüência, como metade dos
pontos do vetor, na janela inferior.
#define PI
3.1415926
#define NP
512
#define BASE 0x300
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<graphics.h>
<math.h>
<stdlib.h>
<ctype.h>
<conio.h>
<dos.h>
<stdarg.h>
float vec[NP+1],vec_max,vec_min,xmax,ymax;
float A=1,R=1,K=200,deltax,deltay,ki,kf,yi,yf;
float K2,B;
int BS[NP+1],Bi[NP+1];
int BDAS[NP+1],BDAI[NP+1];
double magr[NP+1], magi[NP+1];
int y1,x1,k,vetor[NP+1];
void main(void)
{
char L;
int graf,gmode;
detectgraph(&graf,&gmode);
initgraph(&graf,&gmode,"c:\\tc");
xmax = getmaxx();
ymax = getmaxy();
L = 'G';
while(L!='S')
{
cleardevice();
switch(L)
{ case 'A': datan(); superior(); fft();
inferior(); break;
case 'G': seno();
superior(); fft();
inferior(); break;
case 'P': convad(); superior(); fft();
inferior(); convda();
break;
}
L=toupper(getch());
}
closegraph();
}
datan()
{
int i;
FILE *infile;
if((infile = fopen("datan.c","rb")) == NULL)
{ perror("Desculpe mas nao encontrei o datan.c");
exit(1);
}
for(i=0;i< NP; i++ )
fscanf(infile,"%f ", &vec[i]);
fclose(infile);
}
superior()
{
int i;
tela();
max_min();
deltax = (0.92*xmax/(NP-1));
deltay = (0.35*ymax/(vec_min-vec_max));
settextstyle(0,0,1); setcolor(YELLOW);
x1=0.008*xmax; y1=0.44*ymax;
gprintf(&x1,&y1,"%3.1f",vec_min);
x1=0.008*xmax; y1=0.1*ymax;
gprintf(&x1,&y1,"%3.1f",vec_max);
setcolor(RED); setlinestyle(1,0,0);
if(vec_min<0)
{
k=(-vec_max)*deltay+0.1*ymax;
line(0.08*xmax,k,xmax,k);
x1=0.03567*xmax;
gprintf(&x1,&k,"0.0");
}
setlinestyle(0,0,0);
setcolor(BLACK);
for (i=1;i<NP ;i++)
{ ki = ((i-1)*deltax)+0.08*xmax ;
yi = ((1.0*(vec[i-1]-vec_max))*deltay)+0.1*ymax;
kf = (i*deltax)+0.08*xmax;
yf = ((1.0*(vec[i]-vec_max))*deltay)+0.1*ymax;
line(ki,yi,kf,yf);
}
}
max_min()
{ int i;
vec_max = vec_min = vec[0];
for ( i = 1; i < NP ; i++ )
{
if( vec[i] > vec_max)
if( vec[i] < vec_min)
}
}
vec_max = vec[i];
vec_min = vec[i];
tela()
{
int cor_quadro = WHITE;
setfillstyle(1,BLACK);
floodfill(0.05*xmax,0.01*ymax,WHITE);
setcolor(WHITE);
rectangle(0.08*xmax,0.1*ymax,xmax,0.45*ymax);
setfillstyle(1,cor_quadro);
floodfill(0.2*xmax,0.2*ymax,WHITE);
rectangle(0.08*xmax,0.55*ymax,xmax,0.9*ymax);
setfillstyle(1,cor_quadro);
floodfill(0.2*xmax,0.8*ymax,WHITE);
rectangle(0,0,xmax,ymax);
line(0,0.95*ymax,xmax,0.95*ymax);
setfillstyle(1,8);
floodfill(0.2*xmax,0.96*ymax,WHITE);
setfillstyle(1,YELLOW);
floodfill(0.95*xmax,0.98*ymax,WHITE);
nome();
}
nome()
{
setcolor(LIGHTRED); settextstyle(0,0,1);
outtextxy(0.05*xmax,0.03*ymax,"DEMO");
outtextxy(0.02*xmax,0.04*ymax,"");
outtextxy(0.84*xmax,0.02*ymax,"MICRO - 2");
outtextxy(0.85*xmax,0.05*ymax,"06/2006");
setcolor(WHITE); rectangle(.2*xmax,0,0.80*xmax,.08*ymax);
setcolor(GREEN); settextstyle(0,0,2);
outtextxy(0.26*xmax,0.03*ymax,"ANALISADOR ESPECTRAL");
setcolor(MAGENTA); settextstyle(5,0,2);
outtextxy(0.47*xmax,0.48*ymax,"Tempo");
outtextxy(0.45*xmax,0.92*ymax,"Frequencia");
setcolor(BLACK);
outtextxy(0.25*xmax,0.97*ymax,"[A]rquivo [G]erador [P]laca [S]air");
setcolor(MAGENTA); settextstyle(0,1,1);
outtextxy(0.025*xmax,0.24*ymax,"Tensao");
outtextxy(0.025*xmax,0.67*ymax,"Tensao");
}
seno()
{
int i;
float xi,yi;
double teta,B,K2;
B=random(50)+1.0;
K2=random(250)+1.0;
A=random(100)+1.0;
for(i=0;i<NP;i++)
{ teta=360.*i*PI/(180.*NP);
vec[i]=A*sin(100.0*teta)+ B*sin(K2*teta)+
10.0*random(100)/101.0;
}
}
inferior()
{
int i;
max_min();
setcolor(YELLOW);
settextstyle(0,0,1);
x1=0.02*xmax;
y1=0.89*ymax;
gprintf(&x1,&y1,"%3.1f",vec_min);
settextstyle(0,0,1);
x1=0.01*xmax;
y1=0.55*ymax;
gprintf(&x1,&y1,"%3.1f",vec_max);
setcolor(BLACK);
deltax = (2*0.92*xmax/(NP-1));
deltay = (0.35*ymax/(vec_min-vec_max));
for (i=1;i<NP/2;i++)
{
ki = (i-1)*deltax+0.08*xmax ;
yi = ((1.0*(vec[i-1]-vec_max))*deltay)+0.55*ymax;
kf = (i)*deltax+0.08*xmax;
yf = ((1.0*(vec[i]-vec_max))*deltay)+0.55*ymax;
line(ki,yi,kf,yf);
}
}
convad()
{ int i,a;
for(i=0;i<NP;i++)
{
outportb(BASE+1,0);
a=0x80;
while(a==0x80)
a=inportb(BASE+2)&0x80;
BS[i]=(inportb(BASE)&0xF0)>>4;
Bi[i]=inportb(BASE+1);
vec[i]=16*BS[i]+Bi[i];
vetor[i]=vec[i];
}
}
convda()
{ int i;
for(i=0;i<=NP;i++)
{
BDAI[i]=vetor[i]&0xFF;
BDAS[i]=(vetor[i]&0xF00)>>8;
}
while(!kbhit())
{ for(i=0;i<=NP;i++)
{ outportb(BASE+8,BDAI[i]);
outportb(BASE+9,BDAS[i]);
}
}
}
/*
SUBROTINA FFT - CALCULA A FFT DE NP PONTOS DO VETOR VEC,
RETORNANDO OS VETOR VEC
*/
fft()
{
int ii,jj,n,mudado,ni,m,l,k;
int N,gm,gd;
double ur,ui,wr,wi,tr,ti,tur;
float ji;
for(ii=0;ii<=NP;ii++) magr[ii]=magi[ii]=0.0;
n = log10(NP)/log10(2);
/ ******** bit reverso *************/
for(jj = 0; jj < NP; jj++)
{
mudado=0;
ni = 1;
for(ii=1;ii<=n; ii++)
{ if((ni&jj)!= 0)
mudado|=(1<<(n+1-ii));
ni<<=1;
}
magr[(mudado>>1)+1]=vec[jj]/NP;
}
for(m=1;m<=n;m++)
{ ur = 1; ui =0;
jj = 1;
k = 1<<(m-1);
wr = cos(PI/k); wi = -sin(PI/k);
for(jj=1;jj<=k;jj++)
{ for(l=jj;l<=NP;l+= (1<<m))
{ tr = magr[l+k]*ur-magi[l+k]*ui;
ti = magr[l+k]*ui+magi[l+k]*ur;
magr[l+k] = magr[l] - tr;
magi[l+k] = magi[l] - ti;
magr[l] = magr[l] + tr;
magi[l] = magi[l] + ti;
}
tur = ur*wr-ui*wi;
ui = ur*wi+ui*wr;
ur = tur;
}
}
for(ii=0;ii<NP;ii++)
{ magr[ii] = magr[ii+1];
magi[ii] = magi[ii+1];
vec[ii]=2*sqrt(pow(magi[ii],2.)+ pow(magr[ii],2.));
}
vec[0]=0;
/* faz o valor medio do vetor igual a zero */
}
int gprintf( int *xloc, int *yloc, char *fmt, ... )
{
va_list argptr;
/* Argument list pointer
*/
char str[140];
/* Buffer to build sting into */
int cnt;
/* Result of SPRINTF for return */
va_start( argptr, format ); /* Initialize va_ functions */
cnt = vsprintf(str,fmt,argptr); /*prints string to buffer*/
outtextxy(*xloc,*yloc,str); /* Send string in graphics mode */
*yloc += textheight("H")+2; /* Advance to next line
*/
va_end( argptr );
/* Close va_ functions
*/
return( cnt );
/* Return the conversion count
*/
}
A seguir são apresentados alguns resultados da simulação desse programa, para as
opções Arquivo e Gerador.
Figura 11.1 – Analisador Espectral – Duas senóides
Figura 11.2 – Analisador Espectral – Duas senóides próximas
11.3 – Analisador Espectral – Arquivos datan.c
Figura
11.5 - Síntese do Módulo
É apresentado à seguir, uma síntese do que foi tratado em cada item deste módulo.
Com esta síntese você poderá relembrar conceitos vistos durante nosso estudo ou mesmo
direcionar seu estudo, caso você já tenha conhecimentos na Linguagem C.
11.1 – Introdução: é feita uma breve introdução sobre as funções de entrada/saída
(I/O – input/output) da linguagem C. Nele é visto que elas permitem acesso direto ao
hardware do microcomputador.
11.2 – As Funções inport e outport: é visto que as funções inport() e outrport()
permitem acesso direto a endereços de dispositivos de I/O, em 8 ou 16 bits.
11.3 – Placas de Aquisição de Sinais Analógicos: foi visto que a placa de
aquisição de sinais CIO-DAS-AO-08H é uma placa de baixo custo para barramento ISA.
Também foi mostrado suas características de entrada e saída.
11.3.1 - Conversor A/D: é detalhado como ocorre o processo de conversão
analógico para digital.
11.3.2 - Conversor D/A: é detalhado como ocorre o processo de conversão digital
para analógico.
11. 4 – Fatores de Escala para o Modo Gráfico: foi visto que os fatores de escala
nos eixos x e y são usados para que um conjunto qualquer de dados possam ser exibidos
numa região da tela pré-determinada.
11.4.1 Fator de Escala Eixo X: é explicado que o fator de escala do eixo X
permite que um vetor com qualquer número de pontos possa ser plotado no eixo x da tela.
O vetor de dados começa no elemento 0 e vai até o índice (NP-1).
11.4.2 - Fator de Escala Eixo Y: no caso do fator de escala do eixo Y são
envolvidos os valores máximo e mínimo do vetor, devendo ser desenvolvida uma rotina
para tal objetivo.
11.4.3. Programa: Análise Espectral: foi apresentado um programa em linguagem C que
Referências
realiza a análise espectral (decomposição de fourier) de um sinal.
Bibliográficas
[1] UNIPAM – União Pan-Americana de Ensino. Linguagem C/C++. Cascavel: Faculdade
de Ciências Aplicadas de Cascavel – FACIAP. Curso de Ciência da Computação. 2004.
[2] JAMSA, Kris Ph.D., KLANDER, Lars. Programando em C/C++. A Bíblia .O melhor
guia para programação em C/C++. São Paulo: Makron Books.1999.
[3] KERNIGHAN, Brian W., and RITCHIE, Dennis M.. The C Programming Language.
Englewood Cliffs, N. J.: Prentice-Hall Inc, 1978.
[4] MIZRAHI, Victorine Viviane. Treinamento em Linguagem C++. São Paulo: Makron
Books, 1995.
[5] HOLZNER, Steven. Borland C++ Programação for Windows. São Paulo: Makron
Books, 1995.
Download