Escolar Documentos
Profissional Documentos
Cultura Documentos
Apostila - Linguagem C
Apostila - Linguagem C
LINGUAGEM C
ÍNDICE
1. INTRODUÇÃO ______________________________________________________ 1
1.1 História ____________________________________________________________________________1
1.2 Estruturação de um Programa em C ____________________________________________________1
1.3 Tipos ______________________________________________________________________________2
1.4 Variáveis ___________________________________________________________________________2
1.5 Constantes _________________________________________________________________________2
1.6 Entrada e Saída Básicas_______________________________________________________________3
1.6.1 A Função printf() _________________________________________________________________________ 3
1.6.2 A Função scanf() _________________________________________________________________________ 4
2. OPERADORES ______________________________________________________ 5
2.1 Operadores aritméticos _______________________________________________________________5
2.2 Operador de atribuição _______________________________________________________________5
2.3 Operadores relacionais _______________________________________________________________5
2.4 Operadores lógicos ___________________________________________________________________6
2.5 Operadores bit a bit __________________________________________________________________6
2.6 Atribuições reduzidas ________________________________________________________________6
2.7 Operadores pré e pós fixados __________________________________________________________7
2.8 Operadores condicionais ______________________________________________________________7
2.9 Operador vírgula ____________________________________________________________________7
2.10 Precedência de operadores ___________________________________________________________7
3. CONTROLE DE FLUXO ______________________________________________ 9
3.1 if __________________________________________________________________________________9
3.2 while______________________________________________________________________________10
3.3 do-while ___________________________________________________________________________10
3.4 for________________________________________________________________________________11
3.5 break _____________________________________________________________________________11
3.6 switch ____________________________________________________________________________12
4. FUNÇÕES _________________________________________________________ 14
4.1 Definição de Função _________________________________________________________________14
4.1.1 Variáveis Locais_________________________________________________________________________ 14
4.1.2 Chamando Funções ______________________________________________________________________ 14
4.1.3 Programa Exemplo_______________________________________________________________________ 15
4.2 Argumentos________________________________________________________________________15
4.3 Valor de Retorno ___________________________________________________________________16
iii
6. PONTEIROS _______________________________________________________ 29
6.1 Definição __________________________________________________________________________29
6.2 Passagem de Argumentos por Endereço ________________________________________________30
6.3 Operações com Ponteiros_____________________________________________________________31
6.3.1 Atribuição______________________________________________________________________________ 32
6.3.2 Conteúdo ______________________________________________________________________________ 32
6.3.3 Endereço_______________________________________________________________________________ 32
6.3.4 Soma e diferença ________________________________________________________________________ 32
6.3.5 Comparações ___________________________________________________________________________ 32
6.3.6 Ponteiros para void_______________________________________________________________________ 32
6.4 Ponteiros e Vetores__________________________________________________________________33
6.5 Alocação Dinâmica de Memória _______________________________________________________34
6.5.1 Função malloc() _________________________________________________________________________ 34
6.5.2 Função free() ___________________________________________________________________________ 34
6.5.2 Exemplo das funções malloc() e free() _______________________________________________________ 34
iv
7. DADOS ORGANIZADOS_____________________________________________ 40
7.1 Estruturas _________________________________________________________________________40
7.1.1 Acessando dados membro _________________________________________________________________ 40
7.1.2 Estruturas dentro de estruturas ______________________________________________________________ 41
7.1.3 Atribuição entre estruturas _________________________________________________________________ 42
7.1.4 Passando estruturas para funções ____________________________________________________________ 42
7.1.5 Vetores de estruturas _____________________________________________________________________ 43
7.1.6 Ponteiros para estruturas __________________________________________________________________ 43
7.2 Uniões ____________________________________________________________________________45
7.3 Enumeração _______________________________________________________________________45
8. ENTRADA E SAÍDA ________________________________________________ 47
8.1 Arquivos Texto _____________________________________________________________________47
8.1.1 As funções fopen() e fclose() _______________________________________________________________ 48
8.1.2 As funções getc () e putc() _________________________________________________________________ 49
8.1.3 As funções fgets () e fputs() ________________________________________________________________ 49
8.1.4 As funções fprintf () e fscanf() ______________________________________________________________ 50
8.2 Arquivos Binários___________________________________________________________________51
8.2.1 As funções fread () e fwrite() _______________________________________________________________ 51
CAPÍTULO 1
INTRODUÇÃO
1
1.1 História
A origem do nome da linguagem C é muito simples. É a linguagem que sucede a linguagem B. Por sua
vez, a linguagem B teve seu nome retirado da inicial do local onde ela foi desenvolvida: Laboratórios
Bell. A primeira versão da linguagem C foi escrita e implementada por D.M. Ritchie. Foi inicialmente
publicada no livro "The C Programming Language", por B.W. Kernighan & D.M. Ritchie em 1978.
Diversas versões de C, incompatíveis, foram criadas. Estas versões funcionavam somente com um
determinado compilador, rodando apenas em uma única plataforma, o que tornava os códigos
computacionais muito restritos a determinadas condições. Em 1983, a ANSI (American National
Standards Institute) fundou uma comissão para definir uma versão padronizada para a linguagem C.
Esta versão chamou-se ANSI C. Desta forma, simplesmente compilando o código fonte em qualquer
sistema, um programa escrito em ANSI C funciona em praticamente qualquer computador.
main()
{
/* Programa exemplo */
float a, b, c;
A primeira linha do programa, main(), indica que é a primeira função a ser executada, ou seja, é por
onde o programa começa a execução.
O abre-chaves, {, na segunda linha começa o corpo da função.
A terceira linha, /* Programa exemplo */, é um comentário e é ignorada pelo compilador.
Na quarta linha são declaradas as três variáveis que serão utilizadas pelo programa.
A quinta linha é uma linha vazia. É ignorada pelo compilador e pode ser utilizada em qualquer lugar
dentro de um código em C. Normalmente, utiliza-se para separar partes lógicas de um código.
1.3 Tipos
A linguagem C possui 5 tipos básicos. São os tipos char, int, float, double e void. A tabela abaixo
apresenta algumas propriedades de cada tipo.
Tipo Descrição Tamanho Intervalo
char caractere 1 bytes -128 a 127 ou 0 a 255
int inteiro 2 ou 4 bytes -32768 a 32767 ou
-214783648 a 214783647
float ponto flutuante 4 bytes -1.7E38 a 1.7E38 (precisão de 6 digitos)
double ponto flutuante de dupla precisão 8 bytes -1.7E38 a 1.7E38 (precisão de 16 digitos)
void vazio 0 bytes -
O tamanho do tipo inteiro varia com o compilador utilizado. Este tipo possui ainda três variações, a
seguir:
Tipo Descrição Tamanho Intervalo
long int inteiro longo 4 bytes -214783648 a 214783647
short int inteiro curto 2 bytes -32768 a 32767
unsigned int inteiro sem sinal 2 ou 4 bytes 0 a 65535 ou 0 a 4294967295
1.4 Variáveis
Em C, todas as variáveis precisam ser declaradas. A declaração tem a forma
tipo nome-da-variável
ou
1.5 Constantes
Existem diversos tipos de constantes: inteira, ponto flutuante, caractere e cadeia de caracteres.
Constante numérica Significado
10 constante inteira
017 constante octal
0xFF, 0XF0 constante hexadecimal
64L constante longa
78678537 constante longa (implícito)
74.1, 1., .5 constante de ponto flutuante
Constantes de caractere são representadas entre apóstrofos (') e equivalem ao número pelo qual o
caractere é representado na máquina. A maioria das máquinas utiliza a representação ASCII (American
Standard Code for Information Interchange). Para permitir portabilidade, constantes de caractere
devem ser utilizadas no lugar de seus equivalentes inteiros.
Constante de caractere Valor ASCII
'A' 65
'Z' 90
'=' 61
Caracteres especiais são representados com a barra invertida (\) seguida de um determinado caractere.
Constante de caractere Caractere representado
'\n' caractere de mudança de linha (LF)
'\r' caractere de retorno de carro (CR)
'\t' caractere de tabulação (TAB)
'\\' caractere de barra invertida
'\0' caractere nulo
'\'' caractere apóstrofo
'\"' caractere aspas
onde expr. de controle é uma expressão definida, que pode conter alguns códigos, apresentados na
tabela a seguir. Quando a função printf() encontra um destes códigos, ela o substitui pelo argumento
fornecido. Os argumentos podem ser nenhum ou quantos argumentos se fizerem necessários.
Código printf() Formato
%c caractere simples
%d decimal
%e notação científica
%f ponto flutuante
%g %e ou %f (o mais curto)
&o octal
%s cadeia de caracteres
%u decimal sem sinal
%x hexadecimal
%ld decimal longo
%lf ponto flutuante longo (double)
Exemplos:
printf("Teste geral");
Saída: Teste geral
O operador de endereço (&), que precede os argumentos da função, retorna o primeiro byte ocupado
pela variável na memória do computador, e será detalhado no capítulo de ponteiros.
CAPÍTULO 2
OPERADORES
2
Em C, não existem variáveis lógicas. Qualquer valor pode ser testado como verdadeiro ou falso. Se ele
for zero, é falso. Se for qualquer valor diferente de zero, é verdadeiro.
O operador negação (!) inverte o sentido do valor que o segue. Qualquer valor não zero será convertido
para 0 e um valor 0 será convertido para 1.
Expressão Valor
!5 0
!0 1
!(i > 5) 1 se i não for maior que 5
0, se i for maior que 5
Se expr1 for verdadeira (não zero), então o resultado é o valor de expr2. Caso contrário é o valor de
expr3. Por exemplo:
Expressão Valor
5?1:2 1
0?1:2 2
(a > b) ? a : b 1
(a > b) ? ((a > b) ? a : c) : ((b > c) ? b : c) 1
Operador Descrição
() chamada de função
[] elemento de matriz
-> ponteiro para membro de estrutura
. membro de estrutura
! negação lógica
~ negação bit a bit
++ incremento
-- decremento
- menos unário
(tipo) conversão (cast)
* ponteiro
& endereço
sizeof tamanho do objeto
* multiplicação
/ elemento de matriz
% resto da divisão
+ adição
- subtração
<< deslocamento à esquerda
>> deslocamento à direita
< menor que
<= menor ou igual a
> maior que
>= maior ou igual a
== igualdade
!= desigualdade
& e, bit a bit
^ ou exclusivo, bit a bit
| ou, bit a bit
&& e, lógico
|| ou, lógico
?: condicional
= atribuição
op= atribuição
, vírgula
CAPÍTULO 3
CONTROLE DE FLUXO
3
3.1 if
3.1.1 Sintaxe
if (expr) comando
Se expr1 for verdadeira, comando1 é executado. Se for falsa, se expr2 for verdadeira, comando2 é
executado; caso contrário, comando3
3.1.2 Exemplos
/* Exemplo 1 */
if (a == 3)
b = 4;
/* Exemplo 2 */
if (a > b)
c = a * a;
else
c = b * b;
/* Exemplo 3 */
if (a == b)
c = 0;
else if (a > b)
c = a * a;
else
c = b * b;
Atenção:
Exemplo Ação
if (a == 3) b = 4; Testa se o valor de a é 3. Se for, atribui o valor 4 a b
if (a = 3) b = 4; Atribui 3 à variável a. Testa o valor 3 (verdadeiro); portanto, independente
do valor inicial de a será atribuído 4 a b
3.2 while
3.2.1 Sintaxe
while (expr) comando
Se, na primeira vez que o programa testar a expressão, ela for falsa, o controle do
programa passa para a linha seguinte ao laço, sem executar o comando nenhuma vez.
O corpo de um laço while pode ter um único comando terminado por ponto-e-vírgula,
vários comandos entre chaves ou ainda nenhuma instrução, mantendo o ponto-e-
vírgula.
3.2.2 Exemplo
int i;
i = 0;
while (i < 6)
{
printf("%d\n", i);
i++;
}
3.3 do-while
3.3.1 Sintaxe
do comando while (expr)
O laço do-while é bastante parecido com o laço while. A diferença é que no laço do-
while o teste da condição é executado somente depois do laço ser processado. Isso
garante que o laço será executado pelo menos uma vez.
3.3.2 Exemplo
int i;
i = 0;
do
{
printf("%d\n", i);
i++;
} while (i < 6)
3.4 for
3.4.1 Sintaxe
for (inicializacao, condicao, incremento) comando
inicializacao
while (condicao)
{
comando
incremento
}
3.4.2 Exemplos
int i;
Qualquer uma das expressões do laço for pode conter várias instruções separadas por vírgulas.
int i, j;
Qualquer uma das três partes de um laço for pode ser omitida, embora os ponto-e-vírgulas
devam permanecer. Se a expressão de teste for omitida, é considerada verdadeira.
int i = 0;
3.5 break
O comando break pode ser utilizado no corpo de qualquer estrutura de laço C (while, do-while e for).
Causa a imediata saída do laço e o controle passa para o próximo estágio do programa.
3.5.1 Exemplo
int i = 0;
while(1)
{
printf("%d\n", i++);
if (i >= 6) break;
}
3.6 switch
3.6.1 Sintaxe
switch(expr)
{
case constante1:
comando1; /*opcional*/
case constante2:
comando2; /*opcional*/
case constante3:
comando3; /*opcional*/
default: /*opcional*/
comando4; /*opcional*/
}
O comando switch verifica o valor de expr e compara seu valor com os rótulos dos casos. expr
deve ser inteiro ou caractere.
Cada caso deve ser rotulado por uma constante do tipo inteiro ou caractere. Esta constante deve
ser terminada por dois pontos (:) e não por ponto-e-vírgula.
Pode haver uma ou mais instruções seguindo cada case. Estas instruções não necessitam estar
entre chaves.
O corpo de um switch deve estar entre chaves.
Se um caso for igual ao valor da expressão, a execução começa nele.
Se nenhum caso for satisfeito, o controle do programa sai do bloco switch, a menos que exista
um caso default. Se existir, a execução começa nele.
Os rótulos dos casos devem ser todos diferentes.
O comando break causa uma saída imediata do programa. Se não houver um break seguindo
as instruções do caso, o programa segue executando todas as instruções dos casos abaixo, até encontrar
um break ou o fim do corpo do switch.
3.6.2 Exemplo
int mes;
...
switch(mes)
{
case 1:
printf("Janeiro\n");
break;
case 2:
printf("Fevereiro\n");
break;
case 3:
printf("Marco\n");
break;
case 4:
printf("Abril\n");
break;
case 5:
printf("Maio\n");
break;
case 6:
printf("Junho\n");
break;
case 7:
printf("Julho\n");
break;
case 8:
printf("Agosto\n");
break;
case 9:
printf("Setembro\n");
break;
case 10:
printf("Outubro\n");
break;
case 11:
printf("Novembro\n");
break;
case 12:
printf("Dezembro\n");
break;
default:
printf("O numero nao equivale a nenhum mes\n");
break;
}
CAPÍTULO 4
FUNÇÕES
4
onde:
tipo determina o tipo do valor de retorno (se omitido, é assumido int);
nome representa o nome pelo qual a função será chamada ao longo do programa;
argumentos são informações externas transmitidas para a função (podem não existir).
Todo programa é composto de funções, sendo iniciada a execução pela função de nome main().
As funções só podem ser chamadas depois de terem sido declaradas. Caso sejam chamadas sem que
tenham sido declaradas, um erro de compilação ocorre.
main()
{
linha();
printf("Programa exemplo de funcoes \n");
linha();
}
SAIDA
------------------------------
Programa exemplo de funcoes
------------------------------
A função linha(), apresentada neste exemplo, escreve uma linha na tela. Esta função chama uma outra
função, printf(), da biblioteca C. Uma função pode conter em seu corpo chamadas a outras funções.
A função main() apresenta duas chamadas à função linha().
4.2 Argumentos
Argumentos são utilizados para transmitir informações para a função. Já foram utilizados
anteriormente nas funções printf() e scanf().
Uma função pode receber qualquer número de argumentos, sendo possível escrever uma função que
não receba nenhum argumento. No caso de uma função sem argumentos pode-se escrevê-la de duas
formas: deixando a lista de argumentos vazia (mantendo entretanto os parênteses) ou colocando o tipo
void entre parênteses.
O quinto tipo existente em C, void (vazio, em inglês), é um tipo utilizado para representar o nada.
Nenhuma variável pode ser declarada como sendo do tipo void. A função main(), já utilizada em
capítulos anteriores, é um exemplo de função sem argumentos.
Exemplo: o programa abaixo utiliza a função EscreveCaractere(). Esta função recebe como argumento
uma variável caractere (ch) e uma variável inteira (n) e faz com que o caractere ch seja impresso n
vezes.
EscreveCaractere(char ch, int n)
{
int i;
main()
{
EscreveCaractere('-', 27);
printf("\nPrograma exemplo de funcoes\n");
EscreveCaractere('-', 27);
EscreveCaractere('\n', 3);
printf("Teste concluido\n");
}
Nota-se que no exemplo inicialmente é definida a função EscreveCaractere() para, somente depois, ser
definida a função main(), que acessa a função EscreveCaractere(). Caso a função main() venha
primeiro, quando o compilador tentar compilar a linha que chama a função EscreveCaractere(), o
compilador não reconhecerá a função.
Caso deseje-se definir a função EscreveCaractere() antes da função main(), deve-se inicialmente
declarar a função EscreveCaractere(). A declaração de uma função consiste em escrevê-la da mesma
forma que na definição, sem o corpo da função e seguida por ponto-e-vírgula.
O exemplo anterior ficaria da seguinte maneira:
EscreveCaractere(char ch, int n);
main()
{
EscreveCaractere('-', 27);
printf("\nPrograma exemplo de funcoes\n");
EscreveCaractere('-', 27);
EscreveCaractere('\n', 3);
printf("Teste concluido\n");
}
Quando o compilador encontra a primeira linha do código, ele entende que a função
EscreveCaractere() existe e tem a forma apresentada na declaração, mas ainda não está definida; será
definida em algum lugar do código. Portanto, ele consegue compilar as chamadas à esta função no
resto do programa.
Na função, são fornecidos o tipo do valor de retorno (float), o nome da função (Quadrado) e o
argumento (a, do tipo float). No corpo da função, é encontrado o comando return. Este comando
fornece o valor de retorno da função, fazendo com que ela termine. Uma função pode ter diversos
comando return.
Exemplo: a função Maximo() retorna o maior valor entre dois números.
int Maximo(int a, int b)
{
if(a > b)
return a;
else
return b;
}
Esta outra função possui dois argumentos inteiros (a e b) sendo o valor de retorno também inteiro. Este
é um exemplo de função que possui mais de um comando return.
A função EscreveCaractere(), do exemplo fornecido na seção 4.2, é um exemplo de função que não
possui retorno. No caso, se for omitido o valor de retorno de uma função, este valor é assumido como
int. Se uma função não retorna nada, seu tipo de retorno deve ser definido como void.
O comando return pode ser utilizado numa função com tipo de retorno void. neste caso, o comando
não deve retornar nenhum valor, sendo chamado simplesmente seguido do ponto-e-vírgula.
4.4 Recursividade
Como foi visto nas seções anteriores, uma função pode conter em seu corpo chamadas a outras
funções. Nesta seção veremos um caso particular em que uma função é chamada por ela própria. A
este caso, dá-se o nome de recursividade.
Exemplo: Função fatorial(), utilizando recursividade.
Sabe-se que N! = N * (N - 1)! e que 0! = 1.
int fatorial(int n)
{
if(n > 0)
return n * fatorial(n - 1);
else
return 1;
}
Note que existe dentro da função recursiva uma possibilidade de o programa sair dela sem que ocorra a
chamada à própria função (no caso de n menor ou igual a zero a função retorna 1). Se não existisse
esta condição, a função se chamaria infinitamente, causando o travamento do programa.
void incrementa(void)
{
i++;
}
main()
{
printf("%d\n", i);
incrementa();
printf("%d\n", i);
}
main()
{
soma();
soma();
soma();
}
A saída será:
i = 0
i = 1
i = 2
4.6 O Pré-processador C
O pré-processador C é um programa que examina o programa-fonte em C e executa certas
modificações nela, baseado em instruções chamadas diretivas. É executado automaticamente antes da
compilação.
onde nome é o símbolo a ser trocado e xxxxx é qualquer expressão. O comando pode continuar em
mais de uma linha utilizando-se a barra invertida (\) para indicar a continuação na linha de baixo.
Ao ser criado um #define, o precompilador substitui todas as ocorrências de nome no código fonte
pela expressão xxxxx fornecida.
Exemplo: a seguir definimos algumas constantes simbólicas.
#define PI 3.14159
#define ON 1
#define OFF 0
#define ENDERECO 0x378
void main()
{
...
}
No exemplo acima, definimos PI como 3.14159. Isto significa que em todas as ocorrências do texto, PI
será trocado por 3.14159. Assim, a instrução
area = PI * raio * raio;
Dado:
#define MAIS_UM(x) x+1
então:
MAIS_UM(y)
Um uso típico de #define e substituir algumas chamadas a funções por código direto. Vamos supor,
por exemplo, que quiséssemos criar uma macro equivalente a uma função que multiplique dois
números. A macro
#define PRODUTO(a,b) a*b
Para as duas primeiras expressões, a macro funcionou bem. Já para a terceira, a pricípio desejaríamos o
produto de a + b por 2. A expressão pré-compilada não fornece isso, já que o operador * é processado
antes do operador +. A solução para evitar este problema é por os argumentos na definição entre
parênteses, ou seja:
#define PRODUTO(a,b) (a)*(b)
Esta macro continua atendendo às duas primeiras expressões, passa a atender à terceira. Entretanto,
para a quarta expressão, não obtivemos o resultado desejado. A princípio, desejaríamos efetuar os dois
produtos, que são idênticos, dividir um pelo outro, obtendo como resposta 1`. Ao utilizar a macro,
como os operadores * e / possuem a mesma precedência, eles são executados na ordem em que são
encontrados. Ou seja, a divisão é feita antes do segundo produto ter sido efetuado, o que fornece o
resultado 16 para a expressão. A solução para este problema é ser redundante no uso de perênteses ao
se construir macros. A macro PRODUTO é definida então por
#define PRODUTO(a,b) ((a)*(b))
Ainda assim, colocar os parênteses em macros não atende a todos os casos. Por exemplo, a macro
#define DOBRO(a) ((a)+(a))
Esta diretiva anula uma definição de #define, o que torna nome indefinido.
A primeira inclui um arquivo que será procurado no diretório corrente de trabalho. A segunda incluirá
um arquivo que se encontra em algum lugar predefinido.
Os arquivos incluídos normalmente possuem #defines e declarações de funções. Os compiladores
fornecem uma série de funções que são definidas em arquivos de cabeçalho, com extensão .h, que
posem ser incluídos no código-fonte escrito pelo programador. São alguns exemplos a biblioteca
matemática math.h e a biblioteca de entrada e saída padrão stdio.h.
Se os resultados destes testes forem avaliados como verdadeiros, então as linhas seguintes são
compiladas até que um #else ou um #endif seja encontrado.
#else sinaliza o começo das linhas a serem compiladas se o teste
do if for falso
#endif finaliza os comandos #if, #ifdef ou #ifndef
Exemplo: a seqüência seguinte permite que diferentes códigos sejam compilados, baseado na
definição de COMPILADOR.
#define COMPILADOR TC
/* Esta definicao precisa ser trocada para cada compilador*/
#if COMPILADOR == TC
/* Codigo para o Turbo C
#endif
CAPÍTULO 5
VETORES E MATRIZES
5
5.1 Vetores
Um vetor armazena uma determinada quantidade de dados de mesmo tipo. Vamos supor o problema
de encontrar a média de idade de 4 pessoas. O programa poderia ser:
main()
{
int idade0, idade1, idade2, idade3;
Suponhamos agora que desejássemos encontrar a média das idades de 500 pessoas. A tarefa passa a ser
bem mais trabalhosa, sendo em diversos casos impraticável se resolver da maneira apresentada acima.
A solução para este problema é a utilização de vetores. Um vetor é uma série de variáveis de mesmo
tipo referenciadas por um único nome, onde cada variável é diferenciada através de um índice, que é
representado entre colchetes depois do nome da variável.
A declaração
int idade[4];
aloca memória para armazenar 4 inteiros e declara a variável idade como um vetor de 4 elementos.
O programa da média das idades poderia ser substituído por:
main()
{
int idade[4], i, soma = 0;
não é o segundo elemento e sim o terceiro, pois a numeração começa de 0. Em nosso exemplo
utilizamos uma variável inteira, i, como índice do vetor.
Suponhamos o seguinte programa:
main()
{
int idade[5];
idade[0] = 15;
idade[1] = 16;
idade[2] = 17;
idade[3] = 18;
idade[4] = 19;
idade[5] = 20;
}
Nota-se que neste programa, definimos a variável idade como um vetor de 5 elementos (0 a 4).
Definimos os elementos do vetor mas, na última definição, ultrapassamos o limite do vetor. A
linguagem C não realiza verificação de limites em vetores. Quando o vetor foi definido, o compilador
reservou o espaço de memória equivalente a 5 variáveis inteiras, ou seja, 10 bytes. Quando tentamos
acessar um elemento que ultrapasse o limite de um vetor, estamos acessando uma região de memória
que não pertence a esse vetor.
5.1.1 Inicialização
A linguagem C permite que vetores sejam inicializados. No caso, será inicializada uma variável
contendo o número de dias de cada mês:
int numdias[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
Se nenhum número for fornecido para inicializar o vetor, o compilador contará o número de itens da
lista de inicialização e o fixará como dimensão do vetor.
Na falta de inicialização explícita, variáveis extern e variáveis static são inicializadas com valor zero;
variáveis automáticas têm valor indefinido (isto é, lixo).
Se há menos inicializadores que a dimensão especificada, os outros serão zero. Se há mais
inicializadores que o especificado, o compilador acusa um erro.
Em C não há como se especificar a repetição de um inicializador, nem de se inicializar um elemento
no meio de um vetor sem inicializar todos os elementos anteriores ao mesmo tempo.
main()
{
int idade[5], i;
Note que na declaração da função, o argumento que representa o vetor é declarado com colchetes.
Além dele, passamos como argumento da função também o tamanho do vetor. Sem ele, a função não
tem como saber o tamanho do vetor que foi passado a ela como argumento.
Na função main(), chamamos a função media() passando dois atributos: idade e 5. Para acessarmos um
elemento do vetor, escrevemos após o seu nome o índice do elemento desejado entre colchetes (por
exemplo, idade[0]). A chamada ao nome de um vetor sem que seja fornecido o índice de um
determinado elemento representa o primeiro endereço de memória acessado por esse vetor. Ou seja, ao
passarmos um vetor como argumento da função, o compilador não cria uma cópia do vetor para ser
utilizada na função. É o próprio vetor quem é passado como argumento e pode ser alterado na função.
Esta declaração cria a variável Nome como string, ou vetor de caracteres, permitindo um comprimento
de até 50 caracteres. Vale lembrar que o último caractere de uma string deve ser o caractere nulo, ou
seja, temos 49 caracteres para trabalhar livremente. A declaração:
é inválida, por não ter sido reservado espaço suficiente para as 6 variáveis ('P', 'e', 'd', 'r', 'o' e '\0').
Para manipulação de strings, são fornecidas diversas funções na biblioteca C (arquivo string.h).
Algumas dessas funções são descritas a seguir. A sintaxe apresentada para cada função não é
exatamente a sintaxe fornecida pela biblioteca, mas representa basicamente o que a função executa e já
foi explicado até o presente momento.
Exemplo:
main()
{
char Nome[6] = "Navio";
SAÍDA
12
5
Note que os espaços fazem parte da string, e são simplesmente caracteres, assim como letras e
algarismos.
Exemplo:
main()
{
printf("%d\n", strcmp("Ariranha", "Zebra"));
printf("%d\n", strcmp("Zebra", "Ariranha"));
printf("%d\n", strcmp("Zebra", "Zebra"));
}
SAÍDA
(Algum valor menor que 0)
(Algum valor maior que 0)
0
Espaços e outros caracteres especiais também são considerados na comparação, algumas vezes não
fornecendo o que se espera de uma ordenação alfabética.
Exemplo:
main()
{
char Nome[10];
strcpy(Nome, "Teste")
printf("%s\n", Nome);
}
SAÍDA
Teste
Exemplo:
main()
{
char Nome[12];
strcpy(Nome, "Teste")
strcpy(Nome, "geral")
printf("%s\n", Nome);
}
SAÍDA
Testegeral
5.3 Matrizes
A linguagem C permite vetores de qualquer tipo, inclusive vetores de vetores. Por exemplo, uma
matriz é um vetor em que seus elementos são vetores. Com dois pares de colchetes, obtemos uma
matriz de duas dimensões e, para cada par de colchetes adicionado, obtemos uma matriz com uma
dimensão a mais.
Por exemplo, a declaração
int A[5][6];
5.3.1 Inicialização
As matrizes são inicializadas como os vetores, ou seja, os elementos são colocados entre chaves e
separados por vírgulas. Como seus elementos são vetores, estes, por sua vez, também são inicializados
com seus elementos entre chaves e separados por vírgulas. Por exemplo:
int A[5][6] = { { 1, 2, 3, 4, 5, 6},
{ 7, 8, 9, 10, 11, 12},
{13, 14, 15, 16, 17, 18},
{19, 20, 21, 22, 23, 24},
{25, 26, 27, 28, 29, 30} };
Caso as matrizes sejam de caracteres, isto é equivalente a termos um vetor de strings. Sua inicialização
pode se dar da forma
char Nome[5][10] = {"Joao",
"Jose",
"Maria",
"Geraldo",
"Lucia"};
Note que é fornecido a segunda dimensão da matriz. Isto é necessário para que, ao chamarmos o
elemento A[i][j], a função saiba a partir de que elemento ela deve mudar de linha. Com o número de
elementos de cada linha, a função pode obter qualquer elemento multiplicando o número da linha pelo
tamanho de cada linha e somando ao número da coluna.
Por exemplo, o elemento A[2][3], é o elemento de índice 13, já que 2 * 5 + 3 = 13.
CAPÍTULO 6
PONTEIROS
6
6.1 Definição
Uma variável declarada como ponteiro armazena um endereço de memória. A declaração
int *a;
indica que a variável a é um ponteiro que aponta para um inteiro, ou seja, armazena o endereço de uma
variável inteira.
Dois operadores são utilizados para se trabalhar com ponteiros. Um é o operador de endereço (&), que
retorna o endereço de memória da variável. Este operador já foi utilizado em capítulos anteriores. O
outro é o operador indireto (*) que é o complemento de (*) e retorna o conteúdo da variável existente
no endereço (ponteiro). O exemplo a seguir ilustra a utilização desses operadores.
#include <stdio.h>
void main(void)
{
int* a;
int b = 2;
int c;
a = &b;
c = b;
printf("%d %d %d\n", *a, b, c);
b = 3;
printf("%d %d %d\n", *a, b, c);
}
SAIDA
2 2 2
3 3 2
No exemplo acima, as variáveis b e c são declaradas como inteiras, ou seja, cada uma delas ocupa dois
bytes, em endereços de memória diferentes. A variável a é declarada como um ponteiro que aponta
para um inteiro. O comando a = &b indica que a variável a armazenará o endereço da variável b. Já o
comando c = b indica que a variável c, que fica armazenada em outro endereço de memória, guardará
o valor da variável b.
A primeira linha de impressão nos fornece o conteúdo do ponteiro a, obtido através do operador
indireto (*), e o valor das variáveis b e c. Note que o conteúdo do ponteiro a é o valor da variável b, já
que ele aponta para o endereço da variável.
Ao alterarmos o valor da variável b, o conteúdo de seu endereço é alterado. A variável c, por ocupar
outro endereço de memória, permanece inalterada. Já o conteúdo do ponteiro a foi alterado, já que
aponta para o endereço da variável b.
aux = a;
a = b;
b = aux;
}
void main(void)
{
int a = 2;
int b = 3;
A função troca() tem como objetivo receber duas variáveis inteiras e trocar o seu valor. Escrita da
maneira proposta no exemplo acima, ao chamarmos a função, são criadas cópias das variáveis a e b
dentro da função troca(). Estas cópias, que só existem dentro da função, tem o seu valor trocado, mas
isso não implica nenhuma alteração nas variáveis a e b da função main().
Para que possamos alterar o valor de argumentos dentro de uma função, é necessário que estes
argumentos sejam passados por endereço. Desta forma, a função trabalhará com a própria variável
fornecida como argumento. O programa é reescrito como:
#include <stdio.h>
aux = *pa;
*pa = *pb;
*pb = aux;
}
void main(void)
{
int a = 2;
int b = 3;
void main(void)
{
int x = 5;
int y = 6;
int* px;
int* py;
/* Atribuicao de ponteiros */
px = &x;
py = &y;
/* Comparacao de ponteiros */
if(px < py)
printf("py-px = %u\n", py - px);
else
printf("px-py = %u\n", px - py);
printf("px = %u, *px = %d, &px = %u\n", px, *px, &px);
printf("py = %u, *py = %d, &py = %u\n", py, *py, &py);
px++;
printf("px = %u, *px = %d, &px = %u\n", px, *px, &px);
py = px + 3;
printf("py = %u, *py = %d, &py = %u\n", py, *py, &py);
printf("py-px = %u\n", py - px);
}
SAIDA
py-px = 1
px = 65492, *px = 5, &px = 65496
py = 65494, *py = 6, &py = 65498
px = 65494, *px = 6, &px = 65496
py = 65500, *py = -24, &py = 65498
py-px = 3
6.3.1 Atribuição
Um endereço pode ser atribuído a um ponteiro através do operador igual (=). No exemplo, atribuímos
a px o endereço de x e a py o endereço de y.
6.3.2 Conteúdo
O operador indireto (*) fornece o valor armazenado no endereço apontado.
6.3.3 Endereço
Como todas as variáveis, os ponteiros têm um endereço e um valor. Seu valor é o endereço de
memória apontado. O operador (&) retorna o endereço de memória utilizado para armazenar o
ponteiro. Em resumo:
- o nome do ponteiro fornece o endereço para o qual ele aponta;
- o operador & junto ao nome do ponteiro retorna o endereço do ponteiro;
- o operador * junto ao nome do ponteiro fornece o conteúdo da variável apontada.
6.3.5 Comparações
Os operadores ==, !=, >, <, <=, >= são aceitos entre ponteiros e comparam os endereços de memória.
void main(void)
{
int i, *px, x[] = {8, 5, 3, 1, 7};
px = x;
for(i = 0; i < 5; i++)
{
printf("%d\n", *px);
px++;
}
}
Nota-se no exemplo que fazemos com que o ponteiro px aponte para o vetor x. Repare que não é
utilizado o operador endereço. Isto ocorre porque o nome do vetor já é tratado pelo compilador como
um endereço. No caso, seria possível ao invés de
px = x;
utilizarmos
px = &x[0];
O mesmo resultado seria obtido, já que o nome do vetor é tratado como um ponteiro que aponta para o
endereço do primeiro elemento do vetor.
A linha que incrementa o valor de px faz com que o ponteiro aponte para o endereço de memória do
próximo inteiro, ou seja, o próximo elemento do vetor.
Escreveremos agora o mesmo programa de outra maneira:
#include <stdio.h>
void main(void)
{
int x[] = {8, 5, 3, 1, 7};
int i;
Note que neste exemplo chamamos cada elemento do vetor através de *(x + i). Como pode-se notar,
*(x + 0) = *x = x[0];
*(x + 1) = x[1];
*(x + 2) = x[2];
...
*(x + indice) = x[indice].
Como exemplo, vamos fazer uma função equivalente à função strcpy, da biblioteca string.h.
int strcpy(char* s1, char* s2)
{
/* Copia a string s2 em s1 */
while(*s2 != '\0')
{
*s1 = *s2;
s1++;
s2++;
}
*s1 = '\0';
}
Nesta função, todos os caracteres de s2 serão copiados em s1, até que seja encontrado o caractere nulo.
Para passar para o caractere seguinte, foi utilizado o operador ++.
A função malloc aloca um espaço de memória (size bytes) e retorna o endereço do primeiro byte
alocado. Como pode-se alocar espaço para qualquer tipo de variável, o tipo de retorno é void*. Torna-
se necessário converte-lo para um ponteiro do tipo desejado.
No caso de não haver espaço suficiente para alocar a quantidade de memória desejada, a função
retorna um ponteiro para o endereço 0 (ou NULL). Caso a variável size fornecida seja 0, a função
também retorna NULL.
Ao contrário das variáveis estaticamente alocadas apresentadas até agora, o a memória dinamicamente
alocada não é automaticamente liberada. É necessário que o programador libere a memória
dinamicamente alocada através da função free.
#include <string.h>
#include <stdio.h>
#include <alloc.h>
void main(void)
{
char* str = (char*) malloc(10);
void main(void)
{
float** Matriz;
int nLinhas, nColunas, i, j;
printf("Quantas linhas?\n");
scanf("%d", &nLinhas);
printf("Quantas colunas?\n");
scanf("%d", &nColunas);
/* Aloca a matriz */
Matriz = (float**) malloc(nLinhas * sizeof(float*));
for(i = 0; i < nLinhas; i++)
Matriz[i] = (float*) malloc(nColunas * sizeof(float));
/* Define os elementos da matriz */
for(i = 0; i < nLinhas; i++)
for(j = 0; j < nColunas; j++)
Matriz[i][j] = i * j;
/* Imprime a matriz */
for(i = 0; i < nLinhas; i++)
{
for(j = 0; j < nColunas; j++)
printf("%f\t", Matriz[i][j]);
printf("\n");
}
/* Desaloca a matriz */
for(i = 0; i < nLinhas; i++)
free(Matriz[i]);
free(Matriz);
}
Note que neste exemplo não se sabe inicialmente qual será o tamanho da matriz. Caso desejássemos
alocar o espaço estaticamente, seria necessário definir a matriz com número de linhas e colunas
grande, de modo que a escolha do usuário não ultrapassasse entes limites. Por exemplo,
float Matriz[1000][1000];
Há dois inconvenientes imediatos de se alocar vetores e matrizes estaticamente quando não sabemos
de antemão o seu tamanho: um espaço de memória é provavelmente desperdiçado; e ficamos limitados
ao tamanho definido inicialmente. Alocando memória dinamicamente, somente a quantidade
necessária de memória é utilizada, ao passo que nosso limite de tamanho é o limite físico de memória
do computador.
Para alocar a matriz, inicialmente alocamos espaço para um vetor de ponteiros. O tamanho deste vetor
será o número de linhas da matriz. Cada um destes ponteiros apontará para um vetor de float
dinamicamente alocado.
O acesso a cada elemento da matriz é feito de forma semelhante à matriz alocada estaticamente. O
primeiro índice entre colchetes retorna o vetor que contém a linha da matriz equivalente àquele índice.
O segundo índice entre colchetes fornece o elemento contido na coluna.
Note que as linhas não se localizam necessariamente juntas na memória, podendo estar em qualquer
lugar. O vetor de ponteiros indica onde cada linha se encontra na memória.
Para desalocar a matriz, é necessário desalocar primeiro cada linha para só então desalocar o vetor de
ponteiros.
Para chamar esta função, é necessário que se tenha três matrizes de float alocadas dinamicamente.
Estas matrizes devem ter o mesmo tamanho, já que não é possível somar matrizes de tamanhos
diferentes. Note que, desenvolvendo a função para matrizes alocadas dinamicamente, é possível
utilizar matrizes de qualquer tamanho.
Note a utilização do modificador const nos argumentos A e B da função. Isto indica que são
argumentos constantes, isto é, seus elementos não serão alterados dentro da função. A utilização do
modificador const, neste caso, apesar de opcional, fornece segurança ao programador.
Sabe-se, antes de escrever a função, que ela não deve mexer nos argumentos A e B. Como matrizes
são passadas por endereço, a princípio a função poderia alterar estes argumentos. Como não se deseja
que a função altere os argumentos A e B, coloca-se o modificador const à frente dos argumentos
constantes.
Caso tentemos modificar dentro da função algum elemento da matriz A ou da matriz B, o compilador
acusará erro de compilação. Se não utilizássemos o modificador const, só perceberíamos o erro muito
depois, com saídas incorretas do programa. O tempo de depuração de erros de compilação é muito
menor que o de erros de execução.
void ImprimeOla(void)
{
printf("Ola");
}
void main(void)
{
void (*f1)(void);
void (*f2)(const char*);
f1 = ImprimeOla;
f2 = Imprime;
(*f1)();
(*f2)(" mundo!\n");
}
método da bolha: a função qsort. Esta função está presente na biblioteca stdlib.h. Sua sintaxe é a
seguinte.
void qsort(void* base, unsigned long nelem, unsigned long width,
int(*fcmp)(const void*, const void*));
Esta função ordena nelem elementos de tamanho width localizados em base. O usuário deve fornecer
uma função de comparação, fcomp, que compara dois elementos, elem1 e elem2. Esta função deve
retornar
< 0, se elem1 < elem2
= 0, se elem1 == elem2
> 0, se elem1 > elem2
Note que tanto base quanto os argumentos da função, elem1 e elem2 são do tipo void*. Isto é
necessário já que deseja-se que a função ordene qualquer tipo de variável.
Como exemplo, vamos criar um programa que ordena um vetor em ordem crescente e decrescente.
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
int piValor[] = {1, 5, 3, 7, 4, 5, 9};
Na função main(), o programa declara um vetor piValor e o inicializa com números inteiros
desordenados.
Em seguida, o programa chama a função qsort() para ordená-lo em ordem crescente. Na chamada, é
fornecido o vetor, piValor (lembrando: vetores são ponteiros), o número de elementos, 7, o tamanho de
cada elemento, obtido através do operador sizeof, e a função de comparação, OrdenaCrescente().
Completada a ordenação, o programa imprime o vetor, através da função Imprime().
O programa chama novamente a função qsort() para ordená-lo em ordem decrescente. A única
diferença é que agora a função de ordenação fornecida é a função OrdenaDecrescente().
As funções OrdenaCrescente() e OrdenaDecrescente() tem que ter a sintaxe exigida pela função
qsort(), ou seja, recebem dois argumentos void* e retornam um int que indica qual dos dois
argumentos vem na frente na ordenação. Como recebem argumentos void*, é necessário converter
para o tipo int antes de comparar.
A função OrdenaCrescente() retorna a diferença entre o primeiro e o segundo elementos. Isso faz com
que o valor retorno satisfaça às condições impostas pela função qsort(). A função OrdenaDecrescente()
retorna o contrário da função OrdenaCrescente(), de modo que a função qsort() interprete um valor
maior como vindo à frente na ordenação.
A função qsort() pode ordenar um vetor de qualquer tipo de variável, desde que definidos os critérios
de comparação entre dois elementos do vetor.
CAPÍTULO 7
DADOS ORGANIZADOS
7
7.1 Estruturas
Estrutura é um conjunto de variáveis, provavelmente de tipos diferentes, agrupadas e descritas por um
único nome.
Por exemplo, numa folha de pagamento, desejamos guardar diversos registros (ou estruturas)
representando funcionários. Cada funcionário, neste caso, possui alguns atributos, entre eles: nome
(string), número do departamento (inteiro), salário (float), além de outros. Uma estrutura define um
novo tipo composto, contendo diversos tipos básicos (ou compostos).
Para definir uma estrutura, definem-se os elementos dentro dela:
struct etiqueta
{
declaração da variável membro
declaração da variável membro
...
};
cria um novo tipo, chamado Data. Uma variável do tipo Data contém um dia (inteiro), um mês (string)
e um ano (inteiro).
Para declarar variáveis do tipo Data, deve-se utilizar a palavra-chave struct antes do tipo. Por
exemplo:
struct Data hoje;
Cada membro da estrutura é equivalente a uma variável simples de seu tipo. O exemplo a seguir ilustra
a utilização de estruturas.
#include <stdio.h>
#include <string.h>
void main(void)
{
struct Data
{
int Dia;
char Mes[10];
int Ano;
};
hoje.Dia = 4;
strcpy(hoje.Mes, "Outubro");
hoje.Ano = 2001;
printf("%d de %s de %d\n", hoje.Dia, hoje.Mes, hoje.Ano);
}
void main(void)
{
struct Data
{
int Dia;
char Mes[10];
int Ano;
};
struct Pessoa
{
char Nome[50];
char Telefone[20];
char Endereco[50];
struct Data Nascimento;
};
strcpy(pessoa1.Nome, "João");
strcpy(pessoa1.Telefone, "2222-2222");
strcpy(pessoa1.Endereco, "Av. Pres. Vargas, 10/1001");
pessoa1.Nascimento.Dia = 15;
strcpy(pessoa1.Nascimento.Mes, "Janeiro");
pessoa1.Nascimento.Ano = 1980;
printf("%s\n%s\n%s\n%d de %s de %d\n",
pessoa1.Nome, pessoa1.Telefone, pessoa1.Endereco,
pessoa1.Nascimento.Dia, pessoa1.Nascimento.Mes, pessoa1.Nascimento.Ano);
}
struct Data
{
int Dia;
char Mes[10];
int Ano;
};
struct Pessoa
{
char Nome[50];
char Telefone[20];
char Endereco[50];
struct Data Nascimento;
};
void main(void)
{
struct Pessoa pessoa1;
strcpy(pessoa1.Nome, "João");
strcpy(pessoa1.Telefone, "2222-2222");
strcpy(pessoa1.Endereco, "Av. Pres. Vargas, 10/1001");
pessoa1.Nascimento.Dia = 15;
strcpy(pessoa1.Nascimento.Mes, "Janeiro");
pessoa1.Nascimento.Ano = 1980;
ImprimePessoa(pessoa1);
}
Note que, como mais de uma função do programa vai acessar as estruturas, elas são declaradas fora do
escopo de qualquer função, o que as torna globais.
struct Data
{
int Dia;
char Mes[10];
int Ano;
};
struct Pessoa
{
char Nome[50];
char Telefone[20];
char Endereco[50];
struct Data Nascimento;
};
void main(void)
{
struct Pessoa pessoa[100];
strcpy(pessoa[0].Nome, "João");
strcpy(pessoa[0].Telefone, "2222-2222");
strcpy(pessoa[0].Endereco, "Av. Pres. Vargas, 10/1001");
pessoa[0].Nascimento.Dia = 15;
strcpy(pessoa[0].Nascimento.Mes, "Janeiro");
pessoa[0].Nascimento.Ano = 1980;
ImprimePessoa(pessoa[0]);
strcpy(pessoa[1].Nome, "José");
strcpy(pessoa[1].Telefone, "2222-2221");
strcpy(pessoa[1].Endereco, "Av. Pres. Vargas, 10/1002");
pessoa[1].Nascimento = pessoa[0].Nascimento;
ImprimePessoa(pessoa[1]);
}
No exemplo acima, note que cada elemento do vetor é do tipo da estrutura Pessoa. O vetor é
inicialmente dimensionado com 100 elementos, ou seja, podemos utilizar até 100 pessoas diferentes no
vetor.
Para acessarmos os dados membro da estrutura através de seu ponteiro, podemos escrever
(*pHoje).Dia /* Os parenteses sao necessarios */
Vamos alterar a função ImprimePessoa, no exemplo anterior, para que receba o endereço da pessoa, ao
invés de uma cópia. A vantagem de escrever a função desta maneira é o aumento da velocidade de
processamento, já que é necessário copiar apenas o endereço, ao invés de todo o conteúdo da variável.
#include <stdio.h>
#include <string.h>
struct Data
{
int Dia;
char Mes[10];
int Ano;
};
struct Pessoa
{
char Nome[50];
char Telefone[20];
char Endereco[50];
struct Data Nascimento;
};
void main(void)
{
struct Pessoa pessoa[100];
strcpy(pessoa[0].Nome, "João");
strcpy(pessoa[0].Telefone, "2222-2222");
strcpy(pessoa[0].Endereco, "Av. Pres. Vargas, 10/1001");
pessoa[0].Nascimento.Dia = 15;
strcpy(pessoa[0].Nascimento.Mes, "Janeiro");
pessoa[0].Nascimento.Ano = 1980;
ImprimePessoa(&pessoa[0]);
strcpy(pessoa[1].Nome, "José");
strcpy(pessoa[1].Telefone, "2222-2221");
strcpy(pessoa[1].Endereco, "Av. Pres. Vargas, 10/1002");
pessoa[1].Nascimento = pessoa[0].Nascimento;
ImprimePessoa(&pessoa[1]);
}
É recomendável que estruturas muito grandes sejam sempre passadas por endereço. No caso, a
estrutura Pessoa ocupa 134 bytes (50 do nome, 20 do telefone, 50 do endereço e mais 14 da estrutura
Data - 2 do dia, 10 do mês e mais 2 do ano). Passando o argumento por valor, o programa será
obrigado a copiar 134 bytes, ao passo que passando por endereço, apenas os 4 bytes do ponteiro são
copiados.
7.2 Uniões
Uma união permite que as mesmas localizações de memória sejam referenciadas de mais de um modo.
Sua sintaxe é semelhante à da estrutura.
union etiqueta
{
declaração da variável membro
declaração da variável membro
...
};
A diferença é que a estrutura armazena todos os seus dados membro individualmente, enquanto a
união utiliza o mesmo espaço de memória para armazenar todos os seus dados membro. Enquanto o
espaço ocupado por uma estrutura é a soma do espaço utilizado por seus dados membro, a memória
utilizada pela união é a memória ocupada por seu maior dado membro.
Po exemplo, a união definida por:
union Mes
{
int numero;
char nome[10];
};
ocupa 10 bytes (relativos ao dado nome). Podemos acessar seus dados da mesma forma que na
estrutura mas, uma vez alterado um dado membro, os outros são alterados também.
Ao trabalhar com uniões, deve-se acessar apenas um dos dados membro, dependendo do tipo de uso.
Por exemplo, se definirmos
Mes mes;
strcpy(mes.nome, "Janeiro");
e tentarmos obter
mes.numero
o valor retornado é m valor sem sentido, já que o espaço de memória ocupado foi alterado por outra
variável.
Não tendo problemas de falta de memória, deve-se optar pela utilização de estruturas ao invés de
uniões.
7.3 Enumeração
A linguagem C apresenta um tipo de dado adicional, chamado enumeração. Sua sintaxe é:
enum identificacao {enum1, enum2 ...};
Por exemplo:
enum dias {segunda, terca, quarta, quinta, sexta, sabado, domingo};
especifica que dias é uma identificação para uma variável do tipo enum, que somente pode ter os
valores de segunda a domingo.
Podemos declarar variáveis do tipo do enumerado acima, por exemplo:
enum dias dia;
os tipos de enumeração são representados internamente por inteiros. O primeiro identificador recebe o
valor 0, o próximo 1 e assim sucessivamente. Ou seja, na enumeração dias, segunda vale 0, terca vale
1 e assim sucessivamente, até domingo, que vale 6.
Um enumerador pode ser declarado alterando os valores correspondentes a cada termo. Por exemplo,
se declararmos
enum dias {segunda = 2, terca, quarta, quinta, sexta};
nesta enumeração, segunda vale 2, terca vale 3 e assim sucessivamente, até sexta, que vale 6. Se
declararmos
enum dias {segunda = 2, quarta = 4, quinta = 5};
CAPÍTULO 8
ENTRADA E SAÍDA
8
Neste capítulo, serão apresentados as operações de C para leitura e gravação em disco. Operações em
disco são executadas em arquivos. A biblioteca C oferece um pacote de funções para acessar arquivos
de quatro maneiras diferentes:
• Os dados são lidos e escritos um caractere por vez. Oferece as funções getc() e putc().
• Os dados são lidos e escritos como strings. Oferece as funções fgets() e fputs().
• Os dados são lidos e escritos de modo formatado. Oferece as funções fscanf() e fprintf().
• Os dados são lidos e escritos num formato chamado registro ou bloco. É usado para armazenar
seqüências de dados como vetores e estruturas. Oferece as funções fread() e fwrite().
Outra maneira de classificar operações de acesso a arquivos é conforme a forma como eles são abertos:
em modo texto ou em modo binário. Estes conceitos serão apresentados nas seções a seguir.
void main(void)
{
char ch;
FILE *in, *out;
Este programa aguarda a entrada de uma linha de texto e termina quando a tecla <ENTER> for
pressionada. A linha é gravada no arquivo saida.txt.
A estrutura FILE está presente na biblioteca stdio.h e armazena informações sobre o arquivo. Esta
estrutura não será discutida neste curso.
Esta função recebe como argumentos o nome do arquivo a ser aberto (filename) e o modo de abertura
(mode). Retorna um ponteiro para a estrutura FILE, que armazena informações sobre o arquivo aberto.
O nome do arquivo pode ser fornecido com ou sem o diretório onde ele está localizado. Caso se deseje
fornecer o caminho completo do arquivo, lembre-se que a contrabarra em C não é um caractere, e sim
um meio de fornecer caracteres especiais. O caractere contrabarra é representado por '\\'. Ou seja,
em DOS o caminho completo de um arquivo pode ser, por exemplo, "C:\\temp\\saida.txt".
A lista de modos de abertura de arquivos é apresentada a seguir.
"r" Abrir um arquivo para leitura (read). O arquivo deve estar presente no disco.
"w" Abrir um arquivo para gravação (write). Se o arquivo estiver presente, ele será
destruído e reinicializado. Se não existir, ele será criado.
"a" Abrir um arquivo para gravação em anexo (append). Os dados serão adicionados
ao fim do arquivo existente, ou um novo arquivo será criado.
Caso o arquivo possa ser aberto, a função retorna um ponteiro para a estrutura FILE contendo as
informações sobre o arquivo. Caso contrário, retorna NULL. No exemplo, é verificado se o arquivo foi
aberto com êxito. Caso negativo, o programa apresenta uma mensagem de erro e termina a execução.
O erro na abertura de arquivo pode ser causado por diversos fatores: espaço insuficiente em disco (no
caso de gravação), arquivo inexistente (no caso de leitura) etc.
Ao ser aberto um arquivo é necessário que ele seja fechado após utilizado. Isto é feito por meio da
função fclose(). Sua sintaxe é a seguinte:
int fclose(FILE* f);
Para fechar o arquivo, basta chamar a função fclose() e passar como argumento o ponteiro para a
estrutura FILE que contém as informações do arquivo que se deseja fechar. Em caso de êxito, a função
retorna 0. Caso contrário, retorna EOF (end-of-file). EOF é definido na biblioteca stdio.h através da
diretiva #define e indica fim de arquivo.
A função recebe como argumento um ponteiro para FILE e retorna o caractere lido. Em caso de erro,
retorna EOF.
A função putc() é o complemento de getc(). Ela escreve um caractere no arquivo. Sua sintaxe é
int putc(int ch, FILE* f);
A função recebe como argumentos um caractere e um ponteiro para FILE. O valor retornado é o
próprio caractere fornecido. Em caso de erro, a função retorna EOF.
void main(void)
{
char ch[1001];
FILE *in, *out;
fclose(in);
fclose(out);
}
Esta função lê caracteres do arquivo é os coloca na string s. A função pára de ler quando lê n-1
caracteres ou o caractere LF ('\r'), o que vier primeiro. O caractere LF é incluído na string. O
caractere nulo é anexado ao final da string para marcar seu final. Em caso de êxito, a função retorna s.
Em caso de erro ou fim do arquivo, retorna NULL.
A função fputs() é o complemento de fgets(). Ela escreve uma seqüência de caracteres no arquivo. Sua
sintaxe é
int putc(char* s, FILE* f);
A função recebe como argumentos uma string e um ponteiro para FILE. O valor retornado é o último
caractere fornecido. Em caso de erro, a função retorna EOF.
Note que a função copia a string fornecida tal como ela é para o arquivo. Se não existir na string, não é
inserido o caractere de nova linha (LF) nem o caractere nulo.
void main(void)
{
float distancia, tempo, velocidade;
FILE *in, *out;
Este programa lê do arquivo entrada.txt a distância percorrida e o tempo decorrido. É feito o cálculo da
velocidade média e a saída é armazenada num arquivo texto, formatado. As sintaxes de fprintf() e
fscanf() são semelhantes às sintaxes de printf() e scanf() exceto pela inclusão do primeiro argumento,
que é um ponteiro para a estrutura FILE.
As strings de formatação utilizadas nestas funções são as mesmas utilizadas nas funções printf() e
scanf().
void main(void)
{
char ch[20] = "Teste geral";
int valor[20] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
FILE *out;
Ao executar este programa, ele gera um arquivo binário, binario.bin. Se você tentar visualizar este
arquivo num editor como o Bloco de Notas, do Windows, conseguirá distinguir os 20 primeiros bytes,
que é o vetor de caracteres. A partir daí, os bytes utilizados pelo compilador para armazenar os valores
inteiros não fazem mais sentido quando visualizados.
O programa abaixo lê o arquivo binario.bin e apresenta os valores lidos na tela.
#include <stdio.h>
void main(void)
{
char ch[20];
int valor[20];
FILE *in;
in = fopen("binario.bin", "rb");
if(in == NULL)
{
printf("Impossivel abrir arquivo binario.bin.");
return;
}
fread(ch, sizeof(char), 20, in);
fread(valor, sizeof(int), 20, in);
for(i = 0; i < 20; i++)
printf("%c", ch[i]);
for(i = 0; i < 20; i++)
printf("\n%d", valor[i]);
fclose(in);
}
ANEXO A
TABELA ASCII
A
As tabelas mostradas neste apêndice representam os 256 códigos usados nos computadores da família
IBM. Esta tabela refere-se ao American Standard Code for Information Interchange (código padrão
americano para troca de informações), que é um conjunto de números representando caracteres ou
instruções de controle usados para troca de informações entre computadores entre si, entre periféricos
(teclado, monitor, impressora) e outros dispositivos. Estes códigos tem tamanho de 1 byte com valores
de 00h a FFh (0 a 255 decimal). Podemos dividir estes códigos em três conjuntos: controle, padrão e
estendido.
Os primeiros 32 códigos de 00h até 1Fh (0 a 31 decimal), formam o conjunto de controle ASCII.
Estes códigos são usados para controlar dispositivos, por exemplo uma impressora ou o monitor de
vídeo. O código 0Ch (form feed) recebido por ima impressora gera um avanço de uma página. O
código 0Dh (carriage return) é enviado pelo teclado quando a tecla ENTER é pressionada. Embora
exista um padrão, alguns poucos dispositivos tratam diferentemente estes códigos e é necessário
consultar o manual para saber exatamente como o equipamento lida com o código. Em alguns casos o
código também pode representar um caracter imprimível. Por exemplo o código 01h representa o
caracter J (happy face).
Os 96 códigos seguintes de 20h a 7Fh (32 a 127 decimal) formam o conjunto padrão ASCII. Todos
os computadores lidam da mesma forma com estes códigos. Eles representam os caracteres usados na
manipulação de textos: códigos-fonte, documentos, mensagens de correio eletrônico, etc. São
constituídos das letras do alfabeto latino (minúsculo e maiúsculo) e alguns símbolos usuais.
Os restantes 128 códigos de 80h até FFh (128 a 255 decimal) formam o conjunto estendido ASCII.
Estes códigos também representam caracteres imprimíveis porem cada fabricante decide como e quais
símbolos usar. Nesta parte do código estão definidas os caracteres especiais: é, ç, ã, ü ...
ANEXO B
EXERCÍCIOS
B EXERCÍCIOS
B.1 Introdução
1. Fazer um programa que imprima na tela o nome de todos os tipos de dados utilizados pelo C e seus
respectivos tamanhos ocupados em memória.
2. Quais dos seguintes nomes são válidos para variáveis em C?
a) 5ij
b) _abc
c) a_b_c
d) 00TEMPO
e) int
f) A123
g) a123
h) x**x
i) __A
j) a-b-c
k) OOTEMPO
l) \abc
m) *abc
B.2 Operadores
1. Que valor têm as seguintes expressões:
a) 7 / 2
b) 7 % 2
c) 7.0 / 2
d) 7 / 2.0
e) 7.0 / 2.0
2. Fazer um programa para transformar graus Farenheit em Celsius. A fórmula para conversão é a
seguinte:
C F − 32
=
5 9
onde:
C – Temperatura em graus Celsius
F – Temperatura em graus Farenheit
3. Fazer um programa para transformar graus Celsius em Farenheit. A fórmula de conversão é
fornecida no exercício anterior.
if (y<0)
if (y>0)
x = 3;
else
x = 5;
printf(“x= %d\n”, x);
}
5. Supondo que a população de um país, tomado como comparação, seja de 200 milhões de
habitantes em 2000 e que sua taxa de crescimento seja de 1,3% ao ano. Fazer um programa para
calcular o ano em que um outro país, cuja população e taxa de crescimento sejam fornecidas pelo
usuário, iguale ou ultrapasse a população do país base.
6. Implementar um programa que dados:
a) saldo em conta corrente (negativo),
b) limite de cheque especial,
c) taxa de juros cobrada,
calcule quantos meses a conta poderá ficar sem receber depósito sem exceder o limite
7. Fazer um programa que realize a operação de exponenciação, sendo que
a) O usuário deverá digitar a base e o expoente;
b) A base deverá ser um número real e positivo;
c) O expoente deverá ser um número inteiro.
8. Fazer um programa para calcular o fatorial de um número, sendo que
N! = N*(N-1)!
0! = 1.
9. Fazer um programa para calcular o N-ésimo termo da seqüência de Fibonacci. Sendo que
Fn = Fn-1 + Fn-2
F0 = 0
F1 = 1
B.4 Funções
1. Criar uma função para a exponenciação, considerado o expoente inteiro.
2. Criar uma função que calcule o fatorial de um número inteiro
3. Criar uma função recursiva que calcule o fatorial de um número inteiro, sendo que:
N! = N*(N-1)!
0! = 1.
4. Fazer uma função para calcular o N-ésimo termo da seqüência de Fibonacci. Sendo que
Fn = Fn-1 + Fn-2
F0 = 0
F1 = 1
Logo, temos que a seqüência ficará: 0,1,1,2,3,5,8, ...
5. Fazer uma função recursiva para calcular o N-ésimo termo da seqüência de Fibonacci.
6. Fazer um programa que calcule os números palíndromos de 0 a 5000. O programa deverá solicitar
do usuário uma das seguintes alternativas:
a) Calcular números palíndromos
b) Terminar
OBS. Número Palíndromo é aquele que tem igual valor se lido da esquerda para direita ou vice-
versa. Ex. 0,1, ... 9, 11, 22, ..., 99, 101, 111, 121, ...
7. Implementar um programa que calcule o MDC de dois números através do seguinte algorítmo:
Função MDC(M,N)
Início
Se (M < N) então
Resp := MDC(N,M)
Fim-se
Se (N = 0) então
Resp := M;
Senão
Resp := MDC(N,Resto(M,N));
Fim-se
Retorne Resp;
Fim
8. Faca a estrutura de um programa principal que calcule a potência de um número fazendo chamada
a seguinte função:
potencia(int x, int n)
{
int p;
for (p=1;n>0;--n)
p *= x;
return(p);
}
OBS. Caso exista algum erro na função potência, corrija-o.
9. Considere a seguinte função que calcula a área de um triângulo de lados a, b e c:
#include <math.h> /* arquivo onde eestá definida a funcao sqrt() */
AreaTri( )
{
x = (a+b+c)/2.0;
area = x*(x-a)*(x-b)*(x-c);
area = sqrt(area);
}
Faca um programa que calcule a área do triângulo de lados a=3, b=4 e c=5, utilizando a funcao
acima.
d) Caso o lugar indicado não esteja vago, o programa deverá avisar ao usuário para que escolha um
novo lugar.
e) Caso o lugar indicado esteja vago, este deverá ser reservado ao usuário.
f) Sempre que uma dada fileira (1a., 2a. 3a. etc.) estiver totalmente ocupada, o programa deverá
informar ao usuário antes que ele efetue a escolha.
g) Ao final de cada reserva o programa deverá indicar o total de lugares ocupados e o total de
lugares vagos.
4. Analise o código abaixo e responda:
a) para que serve a função dada ?
b) de que maneira deveríamos ter a função main() para chamar corretamente esta função ?
c) indique os possíveis erros de compilação e corrija-os;
int avg(float a[], int size)
{
int i;
float sum;
sum = 0;
for (i=0;i < size; i++)
sum += a[i];
return(sum / size);
}
5. Analise o código abaixo e responda:
a) para que serve a função dada ?
b) de que maneira deveríamos ter a função main() para chamar corretamente esta função ?
strpos(char s1[],char s2[])
{
int len1, len2;
int i, j1, j2;
len1 = strlen(s1);
len2 = strlen(s2);
for (i=0; i+len2 <= len1; i++)
for (j1 = i, j2 = 0; j2 <= len2 && s1[j1] == s2[j2]; j1++, j2+++
if (j2 == len2)
return(i);
return(-1);
}
6. Analise o código abaixo e responda:
a) para que serve a função dada ?
b) de que maneira deveríamos ter a função main() para chamar corretamente esta função ?
strcat(char s1[], char s2[])
{
int i, j;
for (i=0; s1[i] != '\0'; i++)
;
for (j=0; s2[j] != '\0'; s1[i++] = s2[j++])
;
}
B.6 Ponteiros
1. Quais das seguintes instruções são corretas para declarar um ponteiro?
a) int_ptr x;
b) int *x;
c) *int x;
d) *x;
2. Qual é a maneira correta de referenciar ch, assumindo que o endereço de ch foi atribuído ao
ponteiro pch?
a) *pch
b) &pch
c) pch
3. Na expressão float* pf, o que é do tipo float?
a) a variável pf
b) o endereço de pf
c) a variável apontada por pf
d) nenhuma das anteriores
4. Assumindo que o endereço da variável var foi atribuído a um ponteiro pvar, escreva uma expressão
que divida var por 10 sem utilizar a variável var.
union uu
{
char a[10]; int b;
} v;
Em cada declaração, qual é o tamanho (em bytes) do bloco de memória que será alocado para
armazenar os valores das variáveis “v”?
O programa deve levar em conta que fevereiro tem 29 dias se: (resto(ano/4)=0 e
resto(ano/100)<>0) ou (resto(ano/400)=0). Para determinar em que dia da semana cai o
primeiro dia do mês e ano, utilize o seguinte algoritmo:
E1.: se mes>2, então vá para E3
E2.: mes = mes +10, ano = ano-1, vá para E4
E3.: mes = mes - 2
E4.: aux1 = ano / 100, aux2 = resto(ano/100)
E5.: aux3 = 106 + (13*mes-1)/5 + aux2/4 + aux1/4
E6.: dia_semana = resto((aux3+aux2-2aux1+1)/7)
ANEXO C
EXEMPLOS DE FUNÇÕES
C
do
{
iFlag = 0;
for(i = 0; i < iTamanho - 1; i++)
{
if(piDados[i] > piDados[i + 1])
{
iAux = piDados[i];
piDados[i] = piDados[i + 1];
piDados[i + 1] = iAux;
iFlag = 1;
}
}
} while(iFlag == 1);
}
REFERÊNCIAS
1
DEITEL, H. M., DEITEL, P. J., 1999, Como programar em C, LTC - Livros Técnicos e Científicos
Editora, Rio de Janeiro, Brasil.
PUGH, K., 1990, Programando em linguagem C, Trad.: José R. Martins, Roberto C. Mayer,
McGraw-Hill, São Paulo, Brasil.