Escolar Documentos
Profissional Documentos
Cultura Documentos
h>
#include<stdio.h>
2 Linguagem de Programação C
#include<stdlib.h>
float max(float x,
float y){
if (x > y)
return x;
return y; SUMÁRIO
} PREFÁCIO ........................................................................................................................................... 4
5.1.1 Inicializando Estruturas ...................................................................................................... 32
5.1.2 Estruturas Aninhadas ......................................................................................................... 32 PREFÁCIO
5.1.3 Estruturas e funções ........................................................................................................... 33
Este texto tem o objetivo de fornecer os subsídios para o desenvolvimento de programas
5.1.4 Vetor de Estruturas ............................................................................................................. 33
avançados na linguagem C. Os tópicos estudados neste texto são estruturas, uniões, campos de bits,
5.1.5 Ponteiros para Estruturas ................................................................................................... 34
alocação dinâmica e arquivos.
5.2 CAMPOS DE BITS ............................................................................................................................. 34
5.3 UNIÕES ......................................................................................................................................... 36
5.4 SIZEOF() ........................................................................................................................................ 37
5.5 TYPEDEF ........................................................................................................................................ 38
5.6 EXERCÍCIOS..................................................................................................................................... 39
6 ALOCAÇÃO DINÂMICA ...............................................................................................................40
6.1 MAPA DE MEMÓRIA ......................................................................................................................... 40
6.2 FUNÇÕES DE ALOCAÇÃO DINÂMICA EM C ............................................................................................. 40
6.2.1 malloc() ............................................................................................................................... 41
6.2.2 calloc() ................................................................................................................................ 42
6.2.3 free() ................................................................................................................................... 42
6.2.4 realloc() ............................................................................................................................... 43
6.3 MATRIZES DINAMICAMENTE ALOCADAS ............................................................................................... 43
6.4 LISTAS ENCADEADAS ........................................................................................................................ 44
6.4.1 Listas Singularmente Encadeadas ...................................................................................... 44
6.4.2 Listas Duplamente Encadeadas .......................................................................................... 45
6.5 ÁRVORES BINÁRIAS .......................................................................................................................... 47
6.6 EXERCÍCIOS..................................................................................................................................... 49
7 E/S COM ARQUIVO ....................................................................................................................50
7.1 E/S ANSI X E/S UNIX ..................................................................................................................... 50
7.2 STREAMS ....................................................................................................................................... 50
7.3 ARQUIVOS ...................................................................................................................................... 50
7.4 SISTEMA DE ARQUIVOS ..................................................................................................................... 50
7.5 ESTRUTURA FILE ............................................................................................................................. 51
7.6 ABERTURA DE ARQUIVOS .................................................................................................................. 51
7.7 FECHAMENTO DE ARQUIVO ............................................................................................................... 52
7.8 VERIFICANDO FIM DE ARQUIVO ........................................................................................................... 52
7.9 CONDIÇÕES DE ERRO ........................................................................................................................ 52
7.10 STREAMS PADRÃO ........................................................................................................................... 53
7.11 LEITURA E GRAVAÇÃO DE CARACTERES ................................................................................................. 53
7.12 TRABALHANDO COM STRINGS ............................................................................................................ 54
7.13 FUNÇÕES DE TRATAMENTO DE ARQUIVOS ............................................................................................. 55
7.13.1 rewind() .............................................................................................................................. 55
7.13.2 ferror() ................................................................................................................................ 55
7.13.3 remove() ............................................................................................................................. 56
7.13.4 fflush() ................................................................................................................................. 56
7.13.5 Acesso aleatório: fseek() ..................................................................................................... 56
7.13.6 ftell() ................................................................................................................................... 57
7.14 COMANDO DE GRAVAÇÃO EM MODO TEXTO FORMATADO ........................................................................ 57
7.15 LENDO E GRAVANDO REGISTROS ......................................................................................................... 58
7.15.1 Escrita de um bloco de dados ............................................................................................. 58
7.15.2 Leitura de um bloco de dados ............................................................................................. 58
7.15.3 Utilizando os comandos de leitura e gravação de registros ............................................... 59
7.16 FUNÇÕES PARA MANIPULAÇÃO DE BUFFERS ........................................................................................... 59
7.17 EXERCÍCIOS..................................................................................................................................... 61
A. TABELA ASCII .............................................................................................................................62
8 BIBLIOGRAFIA ............................................................................................................................63
5 Linguagem de Programação C 6 Linguagem de Programação C
Flutuant
Ponto
double 64 %lf 1,7E‐308 1,7E+308
e
Em C existem 5 tipos de variáveis básicas. Nos computadores da linha IBM‐PC (plataforma 32
bits), a Tabela 1.1 é válida. Estes tipos de dados definem a quantidade de memória que ocupa e o long double 80 %Lf 3,4E‐4932 3,4E+4932
intervalo de valores que consegue representar.
Tabela 1.1 ‐ Tipos de dados básicos para plataformas 32 bits O uso de signed com inteiros é redundante. No entanto, ele é permitido porque a declaração
default de inteiros assume um número com sinal. O uso mais importante de signed é modificar char em
Tipo Bits Faixa Mínima
implementações em que esse tipo, por padrão, não tem sinal. Algumas implementações podem permitir
char 8 ‐128 a 127 que unsigned seja aplicado aos tipos de ponto flutuante (como em unsigned double). Porém, isso reduz
int 32 ‐2,147,483,648 a 2,147,483,647 a portabilidade de seu código e geralmente não é recomendado. O modificador unsigned altera o valor
float 32 3.4E‐38 a 3.4E+38 da faixa mínima do tipo através do uso do bit mais significativo (indicador de sinal).
double 64 1.7E‐308 a 1.7E+308
void 0 sem valor O tamanho, e conseqüentemente o intervalo de valores, pode variar de
plataforma para plataforma. Por exemplo, o long double em algumas
plataformas possui 10 bytes de tamanho. O char já é um tipo que não
Os tipos char e int armazenam números inteiros, enquanto que os tipos varia de plataforma.
float e Double armazenam números de ponto flutuante (é um formato de
representação digital de números reais). Exemplo 1.1
#include <stdio.h>
Com exceção de void, os tipos de dados básicos podem estar acompanhados por modificadores na int main() {
int qtde;
declaração de variáveis. Os modificadores de tipos da linguagem C são: char tam;
float total;
• signed; • unsigned; qtde = 2; tam = ‘G’;
• long; • short. total = 20.70;
printf(“Comprei %d camisas de tamanho %c.”, qtde, tam);
Os modificadores signed, short, long e unsigned podem ser aplicados aos tipos básicos char e int. printf(“\nNo total, paguei R$ %f.”, custo);
return 0;
Contudo, long também pode ser aplicado à double. }
A função printf() possui especificadores de formato que permitem mostrar Execução:
inteiros short e long. O %ld, %li, %lo, %lu, %lx especificam que o tipo de Comprei 2 camisas de tamanho G.
No total, paguei R$ 20.70.
dado é long. O %hd, %hi, %ho, %hu, %hx especificam que o tipo de dado é
short.
As variáveis podem ser inicializadas no momento em que se faz a declaração das mesmas.
O especificador de formato long pode ser ainda utilizado para tipo ponto Pode‐se ver isto usando o programa anterior, que a execução será a mesma da versão anterior.
flutuante (indicando que segue um double): %le, %l E, %lf, %lg, e %lG. Exemplo 1.2
Outro especificado de formato é o L, o qual é utilizado para associar um #include <stdio.h>
long double: %Le, %LE, %Lf, %Lg, e %LG. int main() {
int qtde=2;
A Tabela 1.2 mostra todas as combinações de tipos de dados e as informações sobre tamanho, char tam=‘G’;
formatação e intervalo. float total=20.70;
printf(“Comprei %d camisas de tamanho %c.”, qtde, tam);
Tabela 1.2 ‐ Utilização dos tipos de dados (plataforma 32 bits) printf(“\nNo total, paguei R$ %f.”, custo);
return 0;
Tipo Bits Formatação Intervalo }
printf() Inicio Fim
char 8 %c ‐128 127 Devido às diferenças de tipos em diferentes máquinas e plataformas,
unsigned char 8 %c 0 255 pode‐se utilizar a função sizeof() para descobrir o tamanho real da
variável ou tipo.
signed char 8 %c ‐128 127
short int 16 %hi ‐32.768 32.767 Exemplo 1.3
#include <stdio.h>
Inteiros
} Exemplo 1.6
const int a=10;
Execução:
Tipo Tamanho O Exemplo 1.6 cria uma variável inteira chamada a, com um valor inicial 10, que seu programa
char 1 não pode modificar.
int 4
float 4
double 8 Um exemplo do uso do const é para verificar se uma variável em particular é modificada pelo
long int 4 seu programa.
Enumeração é um conjunto de constantes inteiras que especifica todos os valores legais de O modificador volatile é usado para informar ao compilador que o valor de uma variável pode
uma variável desse tipo pode ser. A forma geral para enumeração é: ser alterado de maneira não explicitamente especificada pelo programa. Por exemplo, um endereço de
uma variável global pode ser passado para a rotina de relógio do sistema operacional e usado para
Sintaxe: guardar o tempo real do sistema. Nessa situação, o conteúdo de uma variável é alterado sem nenhum
comando de atribuição explicito no programa. Isso ajuda o programa no sentido de avisar ao
enum nome { lista_de_enumeração } lista_de_variáveis; compilador que o conteúdo de uma variável é mutável, mesmo que sua referência não aparecer no lado
esquerdo da expressão.
Aqui, tanto o nome da enumeração quanto a lista de variáveis são opcionais. O nome da
enumeração é usado para declarar variáveis daquele tipo. Com isso pode‐se declarar as cores É possível usar const e volatile juntos. Por exemplo, se 0x30 é assumido como sendo o valor de
Exemplo 1.4 uma porta que é mudado por condições externas. Para evitar efeitos colaterais deve‐se declarar da
enum cores {amarelo, verde, vermelho}; seguinte forma:
enum cores semaforo; const volatile unsigned char *port = 0x30;
semaforo = verde; Uma constante tem valor fixo e inalterável durante a execução do programa. Isto pode ser
if (semaforo==verde) printf(“Passagem permitida \n”);
exemplificado pelos Exemplos 3.1 e 3.2 da função printf().
Para melhor compreensão da enumeração entende‐se que cada símbolo representa um valor Em uma constante caractere é escrita entre aspas simples (‘’), uma constante cadeia de
inteiro. O valor do primeiro símbolo da enumeração é 0. Assim, caracteres entre aspas duplas (“”) e constantes numéricas como o número propriamente dito.
printf (“%d %d”, verde, vermelho); Exemplo 1.7
‘C’
mostra 1 2 na tela. “programa”
8
465.67
Como extensão, pode‐se inicializar os símbolos de forma alternada para algum problema
específico. Constantes em C podem ser de qualquer um dos cinco tipos de dados básicos. A maneira como
Exemplo 1.5 cada constante é representada depende do seu tipo. Pode‐se especificar precisamente o tipo da
enum cores { amarelo, verde=10, vermelho }; constante numérica através da utilização de um sufixo. Para tipos em ponto flutuante coloca‐se um F
após o número, ele será tratado como float. Se for colocado um L, ele tornar‐se‐á um long double. Para
Agora os valores destes símbolos são tipos inteiros, o sufixo U representa unsigned e o L representa long. A Tabela 1.3 mostra alguns
exemplos de constantes.
amarelo 0
Tabela 1.3 ‐ Exemplo de constantes
verde 10
vermelho 11 Tipo de Dado Exemplo de Constantes
int 1 123 21000 ‐234
1.2 MODIFICADORES DE TIPO DE ACESSO
long int 35000L ‐34L
short int 10 ‐12 90
O padrão ANSI introduziu dois novos modificadores de tipo que controlam a maneira como a
variáveis podem ser acessadas ou modificadas. Esses modificadores são const e volatile. Devem unsigned int 10000U 987U 40000“
preceder os modificadores de tipo e os nomes que eles modificam. float 123.23F 2.34e‐3F
double 123.23 12312333 ‐0.9876324
1.2.1 CONST long double 1001.2L
Variáveis do tipo const não podem ser modificadas por seu programa (por isso ela recebe um
valor inicial).
9 Linguagem de Programação C 10 Linguagem de Programação C
Além deste tem‐se as constantes Hexadecimais e Octais. Usam‐se tais sistemas numéricos para
facilitar a programação. Uma constante hexadecimal deve consistir em um 0x seguido por uma
2 OPERADORES
constante na forma hexadecimal. Uma constante octal começa com 0.
A linguagem C é muito rica em operadores internos. C define quatro classes de operadores:
Exemplo 1.8
int hex = 0x80; /* 128 em decimal */ aritméticos, relacionais, lógicos e bit a bit. Além disso, C tem alguns operadores especiais para tarefas
int oct = 012; /* 10 em decimal */ particulares.
Em alguns compiladores C, algumas constantes simbólicas já estão pré‐definidas. Estas Os operadores bit a bit são comumente utilizados para trabalhar com dispositivos (pois os
constantes em geral definam alguns valores matemáticos (π, π/2, e, etc.), limites de tipos etc. A seguir mesmos utilizam bytes ou palavras codificadas para comunicação), modo de armazenamento (um byte
são apresentadas algumas (existem muitas outras) constantes simbólicas pré‐definidas no compilador pode representar 8 informações binárias), e até compactação de dados (utilização de bits ociosos). A
Turbo C++ da Borland. Tabela 2.1 mostra os operadores bit a bit suportados pela linguagem.
Biblioteca Constante Valor Significado Tabela 2.1 ‐ Operadores bit‐a‐bit
math.h M_PI 3.14159... π Operador Ação
math.h M_PI_2 1.57079... π/2 & E (AND)
math.h M_PI_4 0,78539... π/4 | OU (OR)
^ OU exclusivo (XOR)
math.h M_1_PI 0,31830... 1/π
~ Complemento de um
math.h M_SQRT2 1,41421... √2
>> Deslocamento à esquerda
<< Deslocamento à direita
O operador bit a bit & executa um e lógico para cada par de bits, produzindo um novo byte ou
palavra. Para cada bit dos operandos, o operador & retorna o bit em 1 se ambos os bits dos operandos é
1. Caso algum bit dos operandos for 0, o operador retorna o bit 0. Este operador é mais utilizado para
desligar bits (realizando a operação com um operando ‐ também chamado de máscara ‐ cujos bits que
devam ser desligados estejam com valor 0, enquanto que os outros estão em 1) ou verificar se um
determinado bit está ligado ou desligado (realizando a operação com um operando cujo bit que deva
ser checado esteja com valor 1, enquanto que os outros estão em 1).
Exemplo 2.1
unsigned char x = 7; /* 0000 0111 */
unsigned char y = 4; /* 0000 1010 */
unsigned char mascara = 252; /* 1111 1100 */
unsigned char res;
O operador bit a bit | executa um ou lógico para cada par de bits, produzindo um novo byte ou
palavra. Para cada bit dos operandos, o operador | retorna o bit em 1 se algum dos bits dos operandos
é 1. Caso ambos os bits dos operandos for 0, o operador retorna o bit 0. Este operador é mais utilizado
para ligar (realizando a operação com um operando cujos bits que devam ser ligados estejam com valor
1, enquanto que os outros que não devem ser alterados estão em 0).
Exemplo 2.2
unsigned char x = 7; /* 0000 0111 */
unsigned char y = 4; /* 0000 1010 */
unsigned char mascara = 1; /* 0000 0001 */
unsigned char res;
11 Linguagem de Programação C 12 Linguagem de Programação C
5. 2
res = x | y; /* res = 0000 1111 */ 6. 32
res = y | mascara; /* res = 0000 1011 – ligar o bit 0 */ 7. 2
res = x | 8; /* res = 0000 1111 – ligar o bit 3 */ 8. 40
O operador bit a bit ^ executa um ou‐exclusivo (XOR) lógico para cada par de bits, produzindo O Exemplo 2.5 mostra que os operadores de deslocamento podem ser utilizados com variáveis,
um novo byte ou palavra. Para cada bit dos operandos, o operador ^ retorna o bit em 1 se somente um constantes e até mesmo expressões. Entretanto, deve‐se verificar a precedência de operadores quando
dos bits dos operandos é 1. Caso os bits dos operandos forem iguais, o operador retorna o bit 0. Este trabalhando com expressões.
operador é mais utilizado para inverter bits (realizando a operação com um operando cujos bits que Exemplo 2.6
devam ser invertidos estejam com valor 1, enquanto que os outros estão em 0). unsigned char x = 7; /* 0000 0111 */
Exemplo 2.3 unsigned char res;
unsigned char x = 7; /* 0000 0111 */
unsigned char y = 4; /* 0000 1010 */ res = x << 1; /* res = 00001110 res = 14 */
unsigned char mascara = 3; /* 0000 0011 */ res = x << 3; /* res = 01110000 res = 112 */
unsigned char res; res = x << 2; /* res = 11000000 res = 192 */
res = x >> 1; /* res = 01100000 res = 96 */
res = x ^ y; /* res = 0000 1101 */ res = x >> 2; /* res = 00011000 res = 24 */
res = y ^ mascara; /* res = 0000 1001 – inverter os bits 0 e 1 */
res = y ^ 8; /* res = 0000 0010 – inverter o bit 3 */ Não confunda os operadores relacionais && e || com & e |,
respectivamente. Os operadores relacionais trabalham com os operandos
O operador bit a bit ~ executa um não lógico (complemento de 1) no valor a sua direita como um único valor lógico (verdadeiro ou falso), e eles produzem
(operador unário), produzindo um novo byte ou palavra com os bits invertidos. somente dois valores 0 ou 1. Os operadores bit a bit podem produzir
Exemplo 2.4 valores arbitrários pois a operação é realizada em nível de bit.
unsigned char x = 7; /* 0000 0111 */
unsigned char res;
2.1.1 OPERADORES BIT A BIT DE ATRIBUIÇÃO
res = ~x; /* res = 1111 1000 */
res = ~127; /* res = 1000 0000 */ A Tabela 2.2 mostra os operadores bit a bit de atribuição suportados pela linguagem.
Tabela 2.2 ‐ Operadores aritméticos de atribuição
Os operadores de deslocamento, e , movem todos os bits de um operando para a
esquerda ou direita, respectivamente. A forma geral do comando de deslocamento é: Operador Ação
x &= y x = x & y
Sintaxe: x |= y x = x | y
operando << número de bits
operando >> número de bits x ^= y x = x ^ y
x ~= y x = x ~ y
Conforme os bits são deslocados para um direção, zeros são utilizados para preencher a
x >>= y x = x >> y
extremidade contrária da direção (isto é, deslocamento para a direita coloca zeros os bits mais
significativos). Estes operadores são utilizados para recebimento e envio de dados bit a bit (conversores x <<= y x = x << y
analógico/digitais, portas seriais), e multiplicação (deslocamento para a esquerda) e divisão inteira
(deslocamento para a direita) por 2. As expressões com este operadores são mais compactas e normalmente produzem um código
de máquina mais eficiente.
Exemplo 2.5
#include <stdio.h>
A execução da operação bit a bit ocorre por último após a avaliação da
int main() { expressão à direita do sinal igual.
unsigned char x=7;
printf("1. %2i\n",x >> 1);
printf("2. %2i\n",x << 1); 2.2 OPERADOR ? :
printf("3. %2i\n",x >> 2);
printf("4. %2i\n",x << 2);
printf("5. %2i\n",8 >> 2); O operador ? substitui sentenças da forma Se‐então‐senão.
printf("6. %2i\n",8 << 2);
printf("7. %2i\n",x+3 >> 2); Sintaxe:
printf("8. %2i\n",x+3 << 2); Exp1 ? Exp2 : Exp3;
return 0;
}
Onde Exp1, Exp2 e Exp3 são expressões. Onde Exp1 é avaliada e se a mesma for verdadeira,
então Exp2 é avaliada e se torna o valor da expressão. Se Exp1 é falso, então Exp3 é avaliada e se torna
Execução:
1. 3
o valor da expressão.
2. 14 Exemplo 2.7
3. 1 x = 10;
4. 28 y = x > 9 ? 100 : 200;
13 Linguagem de Programação C 14 Linguagem de Programação C
No Exemplo 2.7, y recebe o valor 100, porque x (valor é 10) é maior que 9. Uma expressão 5º < <= > >=
equivalente seria 6º == !=
7º &
x = 10; 8º ^
if (x > 9) y = 100;
else y = 200; 9º !
10º &&
11º ||
2.3 OPERADORES DE PONTEIROS & E * 12º ?:
13º = *= /= %= += ‐= &= |= ^= ~= <<= >>=
Um ponteiro é um endereço na memória de uma variável. Uma variável de ponteiro é uma 14º ,
variável especialmente declarada para guardar um ponteiro para seu tipo especificado.
O primeiro operador de ponteiro é &. Ele é um operador unário que devolve o endereço na 2.5 EXERCÍCIOS
memória de seu operando. Por exemplo,
1. Faça um programa que leia um número binário de 16 bits, armazene‐o, e mostre o valor em
m = &cont; hexadecimal, decimal e octal.
2. Faça uma função que receba um valor do tipo int como parâmetro e escreva na tela os valores
atribui o endereço de memória da variável cont em m.
do bits do valor
Este tipo de operando não pode ser utilizado em três casos: 3. Faça uma função que receba um valor do tipo int como parâmetro e devolva um novo valor int
com a ordem dos bits invertidos.
1. &(cont + 4) ‐ sempre se associa a uma variável e não expressão;
4. Faça uma função que receba um valor do tipo char como parâmetro e devolva quantos bits
2. &3 ‐ constantes não são válidas;
estão ligados.
3. variáveis declaradas com classe de armazenamento register (não existe endereço para
registrador). 5. Faça uma função crossover(n, m, pontoDeCorte) que retorna um inteiro que representa os bits
mais significativos de n e os bits menos significativos de m, de acordo com o ponto de corte,
O segundo operador é *. Ele é um operador unário que devolve o valor da variável localizada que é a posição onde o número será partido. Por exemplo:
no endereço que o segue. Por exemplo, se m contém o endereço da variável cont,
int main(){
q = *m; crossover(10,69,2); // retorna 13 = 0000 1101
// 10 = 0000 1010, teremos que pegar desse nº, os bits +
significativos, ou seja: 0000 1???
coloca o valor de cont em q. // 69 = 0100 0101, teremos que pegar desse nº, os bits menos
significativos, ou seja: ???? ?101
crossover(10,69,3); // retorna 5 = 0000 0101, ou seja, metade de um +
Os seguintes operadores * e & colocam o valor 10 na variável chamada target. O resultado (o metade do outro
valor 10) deste programa é mostrado na tela. }
Exemplo 2.8 6. Faça a função rodaEsquerda(int n, int nBits) que retorna o n com nBits rotações à esquerda.
#include <stdio.h> Perceba que uma rotação não deve perder bits, ao contrário do operador de deslocamento.
int main() { Trabalhe pensando apenas nos 8 bits para n.
int target, source;
int *m;
source = 10; int main(){
m = &source; unsigned char x;
target = *m; x = rodaEsquerda (4, 2); // se 4 = 0000 0100,
printf(“%d”,target); // então rodaEsquerda (4,2)== 0000 1000
return 0; }
} 7. Escreva uma função criptografa(int n) que recebe um inteiro n com 8 bits (índices:
7,6,5,4,3,2,1,0) e que retorna esse inteiro embaralhando esses bits para a seguinte seqüência
2.4 PRECEDÊNCIA DOS OPERADORES (7,5,3,1,6,4,2,0)
int main(){
A Tabela 2.3 mostra a precedência dos operadores da linguagem C. criptografa(73); // se 73 = 0100 1001,
Tabela 2.3: Precedência dos operadores // então criptografa(73) == 0010 1001 == 41
}
Precedência Operador
1º () [] ‐> 8. Faça a função descriptografa(int n) que faz o processo invertido da questão 7.
2º ‐ (menos unário) ++ ‐‐ ! ~ & (endereço) * (ponteiro) 9. Faça uma função que receba um vetor de 32 posições de inteiros (valores 0 e 1) e que retorne
3º * / % um valor com os bits ligados ou desligados conforme o conteúdo de cada posição do vetor.
4º + ‐
4º <<
15 Linguagem de Programação C 16 Linguagem de Programação C
3.1.1 CORPO DA FUNÇÃO ANTES DO PROGRAMA PRINCIPAL (NO MESMO
3 FUNÇÕES ARQUIVO)
A forma geral de uma função é: Quando escrevemos a definição de uma função antes do programa principal e no mesmo
arquivo deste, nenhuma outra instrução é necessária.
Sintaxe:
Exemplo 3.4
tipo_função nome_função (declaração_parâmetros) { float media2(float a, float b) { // função
corpo_função; float med;
} med = (a + b) / 2.0;
return(med);
Exemplo 3.1 }
int soma(int x, int y) {
... int main() { // programa principal
} float num_1, num_2, med;
puts(”Digite dois números:”);
scanf(”%f %f”, &num_1, &num_2);
As funções retornam um valor (do tipo indicado em tipo_função). O valor retornado pela med = media2(num_1, num_2); // chamada da função
função é dado pelo comando return (o valor retornado pode ou não ser utilizado). printf(”\nA media destes números é %f”, med);
return 0;
Existem dois tipos de passagem de argumentos: por valor e por referência. A segunda é }
realizada através de apontadores.
3.1.2 CORPO DA FUNÇÃO DEPOIS DO PROGRAMA PRINCIPAL (NO MESMO
Exemplo 3.2
int pot(int x, int n) { /* x elevado na n potência */ ARQUIVO)
int p;
for(p=1;n>0;n--) Quando escrevemos a definição de uma função depois do programa principal e no mesmo
p *= x;
return p; arquivo deste, devemos incluir um protótipo da função chamada. Um protótipo é uma instrução que
} define o nome da função, seu tipo de retorno e a quantidade e o tipo dos argumentos da função. O
protótipo de uma função indica ao compilador quais são as funções usadas no programa principal os
No Exemplo 3.2, os argumentos foram passados por valor e a função retorna um valor do tipo tipo. A sintaxe geral para isto é a seguinte:
inteiro. A chamada seria:
Sintaxe:
a = pot(10,2); int main() { // programa principal
tipo nomef(...); // protótipo da função
...
No Exemplo 3.3, nenhum valor é retornado (por isso usa‐se o tipo void) mas é realizado uma var = nomef(...) // chamada a função
troca dos valores das variáveis, necessitando de uma passagem de parâmetros por referência. ...
}
Exemplo 3.3
/* troca os valores de duas variáveis*/ tipo nomef(...){ // definição da função
void troca(int *a, *b) { [corpo de função]
int aux; }
aux = *a;
*a = *b; Exemplo 3.5
*b = aux; #include <stdio.h>
} int main() { // programa principal
float media2(float,float); // protótipo de media2()
float num_1, num_2, med;
A chamada para esta função seria: puts(”Digite dois números:”);
scanf(”%f %f”, &num_1, &num_2);
int x=1,y=2; med = media2(num_1, num_2); // chamada a função
troca(&x,&y); printf(”\nA media destes números é %f”, med);
}
Na passagem de parâmetros por referência é passado explicitamente o endereço da variável float media2(float a, float b){ // função media2()
float med;
com o uso do operador &. Quando o argumento for uma matriz automaticamente será passado o med = (a + b) / 2.0;
endereço da matriz para a função. return(med);
}
A linguagem C aceita chamadas recursivas de funções.
Protótipo de uma função nada mais é que a declaração da função sem o
3.1 LOCALIZAÇÃO DAS FUNÇÕES seu corpo. Por isso, a lista de argumentos do protótipo podem ser escritas
apenas com os tipos dos argumentos.
Existem basicamente duas posições possíveis para escrevermos o corpo de uma função: ou
antes ou depois do programa principal. Podemos ainda escrever uma função no mesmo arquivo do 3.1.3 CORPO DA FUNÇÃO ESCRITO EM ARQUIVO SEPARADO
programa principal ou em arquivo separado.
17 Linguagem de Programação C 18 Linguagem de Programação C
Em C, como em muitas outras linguagens, é permitido que o usuário crie uma função em um Protótipos permitem que C forneça uma verificação mais forte dos tipos. Protótipos de funções
arquivo e um programa que a chame em outro arquivo distinto. Esta facilidade permite a criação de ajudam a detectar erros antes que eles ocorram. É verificado número de parâmetros, compatibilidade
bibliotecas de usuário: um conjunto de arquivos contendo funções escritas pelo usuário. Esta de tipos, entre outras.
possibilidade é uma grande vantagem utilizada em larga escala por programadores profissionais.
Existem três tipos de declaração de protótipos:
Quando escrevemos a definição de uma função em arquivo separado do programa principal
devemos incluir este arquivo no conjunto de arquivos de compilação do programa principal. Esta Sintaxe Exemplo
inclusão é feita com a diretiva #include. Esta diretiva, vista nas seções 2.4.2 e 3.7.1, instrui o tipo_função nome_função (); int pot();
compilador para incluir na compilação do programa outros arquivos que contem a definição das funções tipo_função nome_função (lista_tipo_argumentos); int pot(int,int);
de usuário e de biblioteca. tipo_função nome_função (lista_tipo_nome_argumentos); int pot(int x, int y);
Sintaxe:
#include ”path” // inclusão da função
int main() { // programa principal 3.4 RETORNO DE PONTEIROS
...
var = nomef(...) // chamada a função
...
Ponteiros para variáveis não são variáveis e tampouco inteiros sem sinal. São endereços na
} memória. A forma geral é:
Sintaxe:
Na diretiva #include, indicamos entre aspas duplas o caminho de localização do arquivo onde tipo_função *nome_função(lista_de_argumentos);
está definida a função chamada.
Exemplo 3.6 3.5 CLASSES DE ARMAZENAMENTO
#include ”c:\tc\userbib\stat.h” // inclusão da função
int main() { // programa principal
float num_1, num_2, med; São quatro as classes de armazenamento de variáveis C:
puts(”Digite dois números:”);
scanf(”%f %f”, &num_1, &num_2); • auto (automáticas)
med = media2(num_1, num_2); // chamada a função
printf(”\nA media destes números é %f”, med);
• extern (externas)
return 0; • static (estáticas)
} • register (em registradores)
3.2 ARGUMENTOS PARA FUNÇÃO MAIN()
3.5.1 AUTO
A função main() aceita argumentos para a passagem de parâmetros realizada através da
chamada do programa. Os dois argumentos são: As variáveis declaradas nos exemplos anteriores só podem ser acessadas somente às funções
onde estão declaradas. Tais variáveis são chamadas locais ou automáticas, são criadas quando a função
argc: contador de argumentos; é chamada e destruídas quando a função ou o bloco de código termina a sua execução.
argv: vetor de argumentos (vetor de apontadores para strings). As variáveis declaradas dentro de uma função são automáticas por padrão. A classe de
variáveis automáticas pode ser explicitada usando‐se a palavra auto. O código
Sintaxe:
main(int argc, char *argv[]) int main() { int main() {
auto int x; int x;
...
é equivalente a ...
É importante lembrar que
} }
Exemplo 3.7
#include <stdio.h>
int main(int argc, char *argv[]) {
int cont; 3.5.2 EXTERN
printf(“Foram encontrados %d argumentos \n”,argc -1);
for (cont=1;cont < argc;cont++)
printf(“Argumento %d: %s \n”, cont, argv[cont]); Toda variável declarada fora de qualquer função têm a classe de armazenamento extern.
return 0;
} Como pode‐se somente uma única vez declarar uma variável global, ao usar diversos arquivos
para um mesmo programa (por ser grande, por exemplo) deve‐se utilizar a declaração extern nos outros
O primeiro argumento (argv[0]) é o nome do programa. arquivos onde a variável é utilizada. Se não proceder assim, o compilador acusará um erro de
duplicidade de variável.
3.3 PROTÓTIPO DE FUNÇÕES
Exemplo 3.7
int main() {
O padrão ANSI C expandiu a declaração tradicional de função, permitindo que a quantidade e Arquivo 1 ...
os tipos dos argumentos das funções sejam declarados. A definição expandida é chamada protótipo de int x,y; }
função. Protótipos de funções não faziam parte da linguagem C original. char ch;
19 Linguagem de Programação C 20 Linguagem de Programação C
void func1() { void func22() { A declaração acima define a função AREA() a qual calcula a área de uma esfera. A vantagem
x = 123; x = y / 10;
} } desta declaração é a não tipagem do argumento x. Não deve haver espaços entre o nome da macro e
seus identificadores.
void func23() {
Arquivo 2 y = 10; 3.7 FUNÇÕES RECURSIVAS
extern int x,y; }
extern char ch;
Uma função é dita recursiva quando se é definida dentro dela mesma. Isto é, uma função é
No arquivo 2, a lista de variáveis globais foi copiada do arquivo 1 e o especificador extern foi recursiva quando dentro dela está presente uma instrução de chamada a ela própria.
adicionado às declarações. O especificador extern diz ao compilador que os tipos de variável e nomes
que o seguem foram declarados em outro lugar. Isto é, o compilador não reserva um espaço de Exemplo 10.9
// imprime uma frase invertida . Usa recursão
memória para essas variáveis declaradas com o especificador extern na certeza de estarem declaradas #include <stdio.h>
em outro módulo. #include <conio.h>
void inverte()
int main() {
3.5.3 STATIC clrscr( );
inverte( );
return 0;
Dentro de sua própria função ou arquivo, variáveis static são variáveis permanentes. Ao }
contrário das variáveis globais, elas não são reconhecidas fora de sua função ou arquivo, mas mantêm
seus valores entre chamadas. O especificador static tem efeitos diferentes em variáveis locais e em void inverte() {
char ch ;
variáveis globais. if ((ch=getche( )) != ‘\r’ ) inverte();
scanf(“%c”,ch)
}
Quando o modificador static é aplicado a uma variável local, o compilador cria armazenamento 1. Escreva um programa que receba como parâmetro um índice (float). Após, ler uma seqüência de
permanente para ela quase da mesma forma como cria armazenamento para uma variável global. Em números (a qual termina por 0) e exibir o seu valor multiplicado pelo índice. A função que
termos simples, uma variável local static é uma variável local que retém seu valor entre chamadas. Mas transforma uma string em um float é atof(char *x).
ela só é reconhecida apenas no bloco em que está declarada.
2. Escreva uma função que receba um caractere como argumento e que retorne a letra maiúscula se a
mesma for minúscula. funções: islower(int ch), toupper(int ch).
3.5.3.2 VARIÁVEIS GLOBAIS STATIC
3. Existe um algoritmo interessante para se obter a raiz quadrada de um número quando ela é exata.
Quando o modificador static é aplicado a uma variável global, o compilador cria uma variável Para isso, basta subtrair números ímpares consecutivos do número do qual se deseja retirar a raiz
que é reconhecida apenas no arquivo na qual a mesma foi declarada. quadrada. O número de vezes será a raiz do número. Por exemplo:
25 = 25 − 1 − 3 − 5 − 7 − 9 = 0
3.5.4 REGISTER
No exemplo, subtraíram‐se de 25 os 5 primeiros números ímpares consecutivos até que se chegasse
A classe de armazenamento register indica que a variável associada deve ser guardada 0. Assim, a raiz quadrada de 25 é 5. Escreva uma função que receba um inteiro n e retorne a raiz
fisicamente numa memória de acesso muito mais rápido, chamada registrador. No caso do IBM‐PC pode quadrada de n. Por exemplo, se a função receber 49, ele retornará 7. O calculo da raiz quadrada
ser colocado o tipo int e char associado a register pois o registrador tem apenas 16 bits. deverá ser feito usando o algoritmo acima, sem usar qualquer função pré‐existente de alguma
biblioteca C.
Basicamente, variáveis register são usadas para aumentar a velocidade de processamento. Por
exemplo, variáveis de uso freqüente como variáveis de laços e argumentos de funções. 4. Seja o seguinte programa:
3.6 DIRETIVA #DEFINE #include<stdio.h>
void x(int n){
int i, resto;
A diretiva #define pode ser usada para definir constantes simbólicas com nomes apropriados. i = n;
Por exemplo, a constante PI pode ser definida com o valor 3.14159. do{
resto = i%16;
#define PI 3.14159 i=i/16;
switch(resto){
case 10: printf("A"); break;
Só pode ser escrito um comando destes por linha, e não há ponto‐e‐vírgula após qualquer case 11: printf("B"); break;
diretiva do pré‐processador. case 12: printf("C"); break;
case 13: printf("D"); break;
case 14: printf("E"); break;
Esta diretiva é usada para definir macros com argumentos. case 15: printf("F"); break;
default: printf("%d", resto);
#define AREA(x) (4*PI*x*x) }
21 Linguagem de Programação C 22 Linguagem de Programação C
}while(i>0); onde
printf("\n");
} Δv = v f − vi
void main(){
int N; é a variação da velocidade ou a velocidade final menos a velocidade inicial. Escreva uma função em C
scanf("%d",&N); que receba como parâmetros a velocidade inicial, a velocidade final e o intervalo de tempo
x(N); correspondente e retorne a aceleração. Mostre, também, uma função main() que chame essa
}
função.
O que será escrito na tela, supondo que o valor fornecido para N seja 10846? Mostre o teste de
11. O valor de π/2 pode ser calculado pela seguinte série de produtos:
mesa completo utilizado para determinar a saída.
π 22446688
= L
5. Simule a execução do programa abaixo mostrando todas as mudanças de valores de variáveis e o 2 13355778
resultado da impressão.
#include<stdio.h> Escreva uma função em C que receba como argumento um número inteiro n e retorne o valor de π
int perf(long int N){ calculado através da série acima com n termos.
long int i, divs=0;
for(i=1; i<= N/2; i++) 12. Um aço é classificado de acordo com o resultado de três testes, que devem verificar se ele satisfaz às
if (N%i == 0) divs = divs + i;
if (divs == N) return 1; seguintes especificações:
else return 0;
} • Teste 1: conteúdo de carbono abaixo de 7%;
retorna 1814400 (3×4×5×6×7×8×9×10). 4! = 1×2×3×4 = 24
5! = 1×2×3×4×5 = 120
8. Escreva uma função em C com o seguinte protótipo
long int somatório(int i, int n)
A função deve retornar o somatório de i a n. Por exemplo, a chamada
somatório(3,10)
retorna 52 (3+4+5+6+7+8+9+10).
9. Escreva uma função em C que receba dois números e retorne o maior deles.
10. A aceleração é a taxa de variação da velocidade em relação ao tempo, isto é, a razão entre a
variação da velocidade e o intervalo de tempo. Matematicamente,
Δv
a=
Δt
23 Linguagem de Programação C 24 Linguagem de Programação C
O incremento é sempre realizado do tamanho básico de armazenamento do tipo base. Isto é,
4 PONTEIROS se o tipo base for um inteiro e incrementarmos em uma unidade, o apontador apontará para o próximo
inteiro (no caso do inteiro ocupar 2 bytes o incremento será de dois bytes), no caso de um caractere
Para uma boa utilização dos ponteiros deve‐se compreender corretamente o seu uso. Existem (char) será de um byte.
três razões para isso: primeiro, ponteiros fornecem os meios pelos quais as funções podem modificar Exemplo 4.2
seus argumentos; segundo, eles são usados para suportar as rotinas de alocação dinâmica de C, e int *ptri=3000;
terceiro, o uso de ponteiros para aumentar a eficiência de certas rotinas. char *ptrc=4000;
float *ptrf=4000;
ptri++; /* ptri apontará para o endereço 3002 */
Por ser um dos aspectos mais poderosos da linguagem também são os mais perigosos. Por ptrc++; /* ptrc apontará para o endereço 4001 */
erros no uso de ponteiros (como a não inicialização de ponteiros ‐ ponteiros selvagens) podem provocar ptrf++; /* ptrf apontará para o endereço 5004 */
quebra do sistema. ptri = ptri - 10; /* ptri apontará para o endereço 2982 */
ptrc = ptrc - 10; /* ptrc apontará para o endereço 3991 */
ptrf = ptrf - 10; /* ptrf apontará para o endereço 4964 */
Por definição, um ponteiro é uma variável que contém um endereço de memória. Esse
endereço é normalmente uma posição de outra variável na memória. Além de adição e subtração entre um ponteiro e um inteiro, nenhuma outra operação
aritmética pode ser efetuada com ponteiros. Isto é, não pode multiplicar ou dividir ponteiros; não pode
Uma declaração de ponteiros consiste no tipo base, um "*" e o nome da variável. A forma geral aplicar os operadores de deslocamento e de mascaramento bit a bit com ponteiros e não pode
é: adicionar ou subtrair o tipo float ou o tipo double a ponteiros.
tipo *nome;
Não se altera o valor de um ponteiro constante (ponteiro para um tipo de
onde tipo é qualquer tipo válido em C e nome é o nome da variável ponteiro. dado básico ‐ int, float double, …), somente de um ponteiro variável
(ponteiro de estruturas complexas ‐ vetores, matrizes, strings, …).
O tipo base do ponteiro define que tipo de variáveis o ponteiro pode apontar. Basicamente,
qualquer tipo ponteiro pode apontar para qualquer lugar, na memória. Mas, para a aritmética de 4.2 INICIALIZAÇÃO DE PONTEIROS
ponteiros é feita através do tipo base.
Após um ponteiro ser declarado, mas antes que lhe seja atribuído um valor, ele contém um
Os operadores utilizados são * e &, como já foi explicado na seção 6.6. valor desconhecido. Ao usar este ponteiro antes de inicializar, provavelmente provocará uma falha do
programa ou até do sistema operacional.
4.1 EXPRESSÕES COM PONTEIROS
Como um ponteiro nulo é assumido como sendo não usado, pode‐se utilizar o ponteiro nulo
Nesta seção serão analisados alguns aspectos especiais de expressões com ponteiros. para fazer rotinas fáceis de codificar e mais eficientes. Por exemplo, pode‐se utilizar um ponteiro nulo
para marcar o fim de uma matriz de ponteiros. Uma rotina que acessa essa matriz sabe que chegará ao
final ao encontrar o valor nulo. A função search(), mostrada no Exemplo 4.3, ilustra esse tipo de
4.1.1 ATRIBUIÇÃO DE PONTEIROS abordagem.
Como é o caso com qualquer variável, um ponteiro pode ser usado no lado direito de um Exemplo 4.3
/* procura um nome */
comando de atribuição para passar seu valor para outro ponteiro. search(char *p[], char *name)
{
Exemplo 4.1
register int t;
#include <stdio.h>
for (t=0;p[t];++t)
void main() {
if(!strcmp(p[t],name)) return t;
int x;
return -1; /* não encontrado */
int *p1,*p2; /* declaração do ptr p1 e p2 com o tipo base int. */
}
p1 = &x;
p2 = p1;
printf(“%p”,p2); /* escreve o endereço de x, não seu valor */ O laço for dentro de search() é executado até que seja encontrada uma coincidência ou um
return 0; ponteiro nulo. Como o final da matriz é marcado com um ponteiro nulo, a condição de controle do laço
}
falha quando ele é atingido.
Agora, tanto p1 quanto p2 apontam para x. O endereço de x é mostrado usando o modificador
Outra utilização de inicialização de ponteiros é a inicialização de strings. Isto pode ser levado
de formato de printf() %p, que faz com que printf() apresente um endereço no formato usado pelo
como uma variação no tema de inicialização usado na variável argv.
computador hospedeiro.
Exemplo 4.4
char *p= “alo mundo \n”;
O ponteiro p (Exemplo 4.4) não é uma matriz, mas como o compilador C cria uma tabela de
4.1.2 ARITMÉTICA DE PONTEIROS strings, a constante string é colocada em tal tabela sendo que a mesma pode ser utilizada em todo o
programa como se fosse uma string comum. Por isso, inicializar uma matriz de strings usando ponteiros
Existem apenas duas operações aritméticas que podem ser usadas com ponteiros: adição e aloca menos memória que a inicialização através de matriz.
subtração. Os operadores permitidos no caso seriam: +, ‐, ++, ‐‐.
Exemplo 4.5
25 Linguagem de Programação C 26 Linguagem de Programação C
Se for necessário passar uma matriz de ponteiros para uma função, pode ser usado o mesmo
Para acessar a string str pode‐se utilizar estes dois mecanismos
método que é utilizado para passar outras matrizes ‐ simplesmente chama‐se a função com o nome da
str[4] /* indexação de matrizes */ matriz sem qualquer índice.
Exemplo 4.10
ou void display_array(int *q[]) {
register int t;
*(p1 + 4) /* aritmética de ponteiros */ for (t=0; t<10; t++)
printf(“%d”,*q[t]);
}
Os dois comandos devolvem o quinto elemento.
Lembre‐se de que q não é um ponteiro para inteiros; q é um ponteiro para uma matriz de
*(matriz + índice) é o mesmo que matriz[índice]. ponteiros para inteiros. Portanto, é necessário declarar o parâmetro q como uma matriz de ponteiros
para inteiros, como é mostrado no Exemplo 4.10. Isto é, não é uma passagem de parâmetros por
Para uma melhor compreensão ou facilidade de programação as funções de indexação referência por dois motivos: primeiro, matriz como argumento de função é automaticamente passada
trabalham com ponteiros (como mostra o Exemplo 4.8 a implementação da função puts()). por referência por questão da implementação da linguagem, e segundo, é uma matriz de ponteiros e
Exemplo 4.8 conseqüentemente sua declaração é caracterizada pelo asterisco na frente do nome da variável.
/* Indexa s como uma matriz */
void put(char *s) {
register int t;
Matrizes de ponteiros são usadas normalmente como ponteiros de strings como, por exemplo,
for (t=0;s[t]; ++t) putchar(s[t]); o argumento da linha de comandos argv.
}
/* Acessa s como um ponteiro */
void put(char *s) { 4.3.2 ACESSANDO PARTES DE MATRIZES COMO VETORES
while (*s) putchar(*s++);
}
A linguagem C trata partes de matrizes como matrizes. Mais especificamente, cada linha de
uma matriz de duas dimensões pode ser considerada como uma matriz de uma dimensão. Isto pode ser
No caso da passagem de parâmetros é possível tratar uma matriz como se fosse um ponteiro.
muito útil no tratamento de matrizes. O Exemplo 4.11 mostra a atribuição de uma linha da matriz nome
Exemplo 4.9 para um ponteiro.
#include <stdlib.h>
#include <stdio.h> Exemplo 4.11
#include <string.h> int main() {
void le_tab(int *p) { static char nome[10][10];
27 Linguagem de Programação C 28 Linguagem de Programação C
A indireção múltipla pode ser levada a qualquer dimensão desejada, mas raramente é
Uma das grandes utilidades é o uso de drivers de dispositivos (placas de som, placas de vídeo,
necessário mais de um ponteiro para um ponteiro.
modems, entre outros) que fornecem rotinas de tratamento para aquele hardware específico. Onde o
programador lê o arquivo do driver para a memória e o executa de acordo com as especificações do
Não confunda indireção múltipla com listas encadeadas. fabricante.
A declaração deste tipo de variável é feita colocando‐se um * adicional em frente ao nome da Outra utilidade é o programador poder enviar a função que se apropria para a comparação por
variável, como mostra o Exemplo 4.12. Tal exemplo mostra a declaração da variável ptrptrint como um exemplo. Isto é, no caso de strings pode‐se pensar em um comparador de strings genérico onde como
ponteiro para um ponteiro do tipo int. terceiro parâmetro é enviado a função que vai realizar a comparação. Antes da chamada da função
Exemplo 4.12 genérica pode verificar se a string é composta por caracteres alfanuméricos (através da função
int **ptrptrint; isalpha()) e enviar a função strcmp(), caso contrário uma função que realize uma comparação de
números inteiros (nesta função conterá a conversão das strings em um inteiro (função atoi()).
Para acessar o valor final apontado indiretamente por um ponteiro a um ponteiro, você deve
utilizar o operador asterisco duas vezes, como no Exemplo 4.13: 4.6 MAIS SOBRE DECLARAÇÕES DE PONTEIROS
Exemplo 4.13
#include <stdio.h> As declarações de ponteiros podem ser complicadas e é necessário algum cuidado na sua
int main() { interpretação. principalmente em declarações que envolvem funções e matrizes. Assim, a declaração
int x, *p, **q;
x = 10; int *p(int a);
p = &x;
q = &p;
printf(“%d”, **q); /* imprime o valor de x */ indica uma função que aceita um argumento inteiro e retorna um ponteiro para um inteiro. Por outro
return 0; lado, a declaração
}
int (*p)(int a);
4.5 PONTEIROS PARA FUNÇÕES
indica um ponteiro para uma função que aceita um argumento inteiro e retorna um inteiro. Nessa
A linguagem C permite apontadores para funções. Isto é permitido pois toda função tem uma última declaração, o primeiro par de parênteses é usado para o aninhamento e o segundo par, para
posição física na memória que pode ser atribuída a um ponteiro. Portanto, um ponteiro de função pode indicar uma função.
ser usado para chamar uma função.
A interpretação de declarações mais complexas pode ser extremamente mais trabalhosa. Por
O endereço de uma função é obtido usando o nome da função sem parênteses ou argumentos. exemplo, considere a declaração
Mas para declarar este tipo de apontador tem que se seguir uma sintaxe especial como mostra o
Exemplo 4.14. int *(*p)(int (*a)[]);
Exemplo 4.14
29 Linguagem de Programação C 30 Linguagem de Programação C
Nessa declaração, (*p)(..) indica um ponteiro para uma função. Por isso, int *(*p)(...) indica um
ponteiro para uma função que retorna um ponteiro para um inteiro. Dentro do último par de
parênteses (a especificação dos argumentos da função), (*a)[] indica um ponteiro para um vetor. Assim 4.7 EXERCÍCIOS
int (*a)[] representa um ponteiro para um vetor de inteiros. Juntando todas as peças, (*p)(int (*a)[])
representa um ponteiro para uma função cujo argumento é um ponteiro para um vetor de inteiros. E, 1. Como referenciar mat[x][y] em notação de ponteiros.
finalmente, a declaração original representa um ponteiro para uma função que aceita um ponteiro para
um vetor de inteiros como argumento e devolve um ponteiro para um inteiro. 2. Qual será a saída deste programa?
int main() {
int i=5;
Se logo após um identificador existir um “abre parênteses” indica que o int *p;
identificador representa uma função e os colchetes representam uma p = &i;
matriz. Os parênteses e colchetes têm maior precedência do que qualquer printf(“%u %d %d %d %d \n”, p, *p+2,**&p,3**p,**&p+4);
return 0;
operador. }
A seguir será mostrado várias declarações envolvendo ponteiros e seu significado. 3. Escreva uma função que inverta a ordem dos caracteres de uma string.
int *p; p é um ponteiro para um valor inteiro
int *p[10]; p é uma matriz de ponteiros com 10 elementos para valores 4. Crie uma função que receba como parâmetro uma matriz de ponteiros para strings e devolve a
inteiros matriz ordenada.
int (*p)[10]; p é um ponteiro para uma matriz de inteiros com 10 elementos
int *p(void); p é uma função que retorna um ponteiro para um valor inteiro 5. Faça uma função que receba um ponteiro para uma matriz e que realize a ordenação da mesma.
int *p(char *a); p é uma função que aceita um argumento que é um ponteiro para
um caractere e retorna um ponteiro para um valor inteiro 6. Faça a declaração de uma função (nome: teste) que receba um ponteiro para uma função que possui
int (*p)(char *a); p é m ponteiro para uma função que aceita um argumento que é dois argumentos (int e char) e retorne um ponteiro para um float.
um ponteiro para um caractere e retorna um valor inteiro
int (*p(char *a))[10]; p é uma função que aceita um argumento que é um ponteiro para 7. Faça a declaração e inicialização de uma matriz de ponteiros para os dias da semana.
um caractere e retorna um ponteiro para uma matriz inteira de 10
elementos 8. Faça uma função que receba uma matriz de ponteiros para caracteres e realize a ordenação
int p(char (*a)[]); p é uma função que aceita um argumento que é um ponteiro para alfabética da mesma.
uma matriz de caractere e retorna um valor inteiro
int p(char *a[]); p é uma função que aceita um argumento que é uma matriz de
ponteiros para caractere e retorna um valor inteiro
int *p(char a[]); p é uma função que aceita um argumento que é uma matriz de
caractere e retorna um ponteiro para um valor inteiro
int *p(char (*a)[]); p é uma função que aceita um argumento que é um ponteiro para
uma matriz de caractere e retorna um ponteiro para um valor
inteiro
int *p(char *a[]); p é uma função que aceita um argumento que é uma matriz de
ponteiros para caracteres e retorna um ponteiro para um valor
inteiro
int (*p)(char (*a)[]); p é um ponteiro para uma função que aceita um argumento que é
um ponteiro para uma matriz de caractere e retorna um valor
inteiro
int *(*p)(char (*a)[]); p é um ponteiro para uma função que aceita um argumento que é
um ponteiro para uma matriz de caractere e retorna um ponteiro
para um valor inteiro
int *(*p)(char *a[]); p é um ponteiro para uma função que aceita um argumento que é
uma matriz de ponteiros para caracteres e retorna um ponteiro
para um valor inteiro
int (*p[10])(char a); p é uma matriz de ponteiros com 10 elementos para funções;
cada função aceita um argumento que é um caractere e retorna
um valor inteiro
int *(*p[10])(char a); p é uma matriz de ponteiros com 10 elementos para funções;
cada função aceita um argumento que é um caractere e retorna
um ponteiro para um valor inteiro
int *(*p[10])(char *a); p é uma matriz de ponteiros com 10 elementos para funções;
cada função aceita um argumento que é um ponteiro para um
caractere e retorna um ponteiro para um valor inteiro
31 Linguagem de Programação C 32 Linguagem de Programação C
int codigo;
5 ESTRUTURAS E UNIÕES float saldo;
};
A linguagem C permite criar tipos de dados definíveis pelo usuário de cinco formas diferentes. Para usar esta estrutura em outras declarações deve‐se especificar desta forma:
A primeira é estrutura, que é um agrupamento de variáveis sobre um nome e, algumas vezes, é
chamada de tipo de dado conglomerado. O segundo tipo definido pelo usuário é o campo de bit, que é struct cad_conta conta1, conta2;
uma variação da estrutura que permite o fácil acesso aos bits dentro de uma palavra. O terceiro é a
união, a qual permite que a mesma porção da memória seja definida por dois ou mais tipos diferentes As estruturas seguem o padrão do escopo de variáveis, isto é, se a declaração estiver contida
de variáveis. Um quarto tipo de dado é a enumeração, que é uma lista de símbolos, como foi visto na numa função, a estrutura tem escopo local para aquela função; se a declaração estiver fora de todas as
seção 1.5. O último tipo definido pelo usuário é criado através do uso de typedef e define um novo funções, ela terá um escopo global.
nome para um tipo existente.
Para acessar um campo específico de uma struct utiliza‐se o operador . (ponto).
5.1 ESTRUTURAS
Exemplo 5.3
conta1.saldo = 0;
O tipo estruturado struct possibilita a criação de estruturas de dados complexas, isto é, pode‐ conta1.codigo = 0;
se obter estruturas que contenham mais de um tipo de dado. Tal estrutura é conhecida em outras strcpy(conta1.nome,”Joao”);
linguagens como registros. conta1.idade = 21;
Cada elemento que compõe a estrutura (chamado campo) pode ser acessado individualmente, É permitida a atribuição entre struct. Neste caso todos os campos são copiados.
assim como a estrutura pode ser acessada como um todo. Em C, a declaração de uma estrutura é feita Exemplo 5.4
da seguinte forma: conta2 = conta1;
struct [nome_struct] {
tipo var1; 5.1.1 INICIALIZANDO ESTRUTURAS
tipo var2;
… Uma estrutura só pode ser inicializada se pertencer às classes static ou extern. Observe que a
tipo varN;} [nome_var]; classe de uma estrutura é dada pelo ponto em que as variáveis foram declaradas e não pelo ponto onde
a estrutura foi definida.
Deve‐se encerrar com um ponto‐e‐vírgula a declaração porque a definição de estrutura é na
realidade uma instrução C. Da mesma forma que os vetores, as estruturas são inicializadas com uma lista de valores (cada
um correspondente a um campo de estrutura) entre chaves e separados por vírgulas.
A declaração de estruturas pode se apresentar de diversas formas. Tais como: Exemplo 5.5
struct cad_conta {
struct { char nome[30];
char nome[30]; int idade;
int idade; int codigo;
int codigo; float saldo;
float saldo; };
} conta1, conta2;
int main() {
static struct cad_conta
Na declaração acima, o nome_struct não é utilizado pois esta estrutura será utilizada pelas conta1 = {“Andre”, 23, 9507, 1567.89},
variáveis de estrutura conta1 e conta2. Para utilizar esta estrutura na definição de outras variáveis tem‐ conta2 = {“Carlos”, 33, 9678, 1000.59};
se que declará‐las juntas com a definição da estrutura. No caso de um programa que utilize esta …
estrutura para passar parâmetros, declarar variáveis locais, entre outros, a linguagem permite a criação }
de rótulos de estruturas (nome_struct).
Exemplo 5.1 5.1.2 ESTRUTURAS ANINHADAS
struct cad_conta {
char nome[30]; Como os campos da estrutura podem ser de qualquer tipo, também é permitido o uso de
int idade; estruturas na declaração.
int codigo;
float saldo; Exemplo 5.6
} conta1, conta2; struct data {
int dia;
char mes[10];
Como mostra Exemplo 5.1, foram declaradas as variáveis conta1 e conta2 como sendo uma int ano;
estrutura do tipo cad_conta. Quando rotula‐se a estrutura pode‐se omitir a declaração das variáveis, };
como é mostrado no Exemplo 5.2.
struct func {
Exemplo 5.2 char nome[20];
struct cad_conta { int codigo;
char nome[30]; float salario;
int idade; struct data nascimento;
33 Linguagem de Programação C 34 Linguagem de Programação C
}; int idade;
int codigo;
int main() { float saldo;
static struct func };
funcionario = {“Marcio”, 1234, 3743.44, {10, “Janeiro”, 1967}},
gerente = {“Jose”, 456, 5634.28, {18, “Marco”, 1950}}; int main() {
return 0; int i
} static struct cad_conta conta[10]=
{ {“Andre”, 23, 9507, 1567.89},
{“Carlos”, 33, 9678, 1000.59},
Observe a inicialização das variáveis. A estrutura é inicializada também com uma lista de ...
valores entre chaves e separados por vírgulas. O acesso a um campo de uma estrutura aninhada é feito };
na forma: for (i=0;i<10;i++) {
printf(“Nome : %s\n”,conta[i].nome);
funcionário.nascimento.dia = 10; printf(“Idade : %d\n”, conta[i].idade);
strcpy(gerente.nascimento.mes,”Abril”); printf(“Codigo : %d\n”, conta[i].codigo);
printf(“Saldo : %.2f\n”, conta[i].saldo);
}
…
5.1.3 ESTRUTURAS E FUNÇÕES }
Em versões mais antigas de compiladores C, as estruturas não podiam ser usadas em passagem
de parâmetros por valor para funções. Isto se devia a razões de eficiência, uma vez que uma estrutura 5.1.5 PONTEIROS PARA ESTRUTURAS
pode ser muito grande e a cópia de todos os seus campos para a pilha poderia consumir um tempo
exagerado. Desta forma, as estruturas eram obrigatoriamente passadas por referência, usando‐se o C permite ponteiros para estruturas exatamente como permite ponteiros para outros tipos de
operador de endereço (&). variáveis. No entanto, há alguns aspectos especiais de ponteiros de estruturas.
No Turbo C e outros compiladores mais recentes, a responsabilidade da decisão fica a cargo do Como outros ponteiros, declara‐se colocando um * na frente do nome da estrutura. No
programador. Assim, uma função pode passar ou retornar uma estrutura. Exemplo 5.9 declara‐se ptr_cta como um apontador da estrutura previamente definida cad_conta.
Exemplo 5.7 Exemplo 5.9
struct cad_conta { struct cad_conta *ptr_cta;
char nome[30];
int idade; Há dois usos primários para ponteiros de estrutura: gerar uma chamada por referência para
int codigo;
float saldo; uma função e criar estruturas de dados dinâmicas (listas, pilhas, filas, entre outras) utilizando‐se do
}; sistema de alocação de C.
int main() { Na forma de acessar os elementos ou campos de uma estrutura usando um ponteiro para a
static struct cad_conta conta1;
conta1 = ins_conta(); estrutura, deve‐se utilizar o operador ‐> (seta). A seta é usada sempre no caso de apontador de
lista(conta1); estruturas. No Exemplo 5.10 é mostrada a declaração, atribuição e utilização de ponteiros de estruturas.
…
} Exemplo 5.10
struct cad_conta {
struct cad_conta ins_conta() { char nome[30];
struct cad_conta aux; int idade;
gets(aux.nome); int codigo;
scanf(“%d”, &aux.idade); float saldo;
scanf(“%d”, &aux.codigo); } conta;
scanf(“%f”, &aux.saldo);
return(aux); int main() {
} struct cad_conta *ptr;
ptr = &conta; /* o ptr recebe o end. da estrutura */
void lista(struct cad_conta aux) { ptr->idade = 23;
printf(“Nome : %s\n”,aux.nome); ptr->codigo = 1000;
printf(“Idade : %d\n”, aux.idade); return 0;
printf(“Codigo : %d\n”, aux.codigo); }
printf(“Saldo : %.2f\n”, aux.saldo);
} 5.2 CAMPOS DE BITS
5.1.4 VETOR DE ESTRUTURAS Ao contrário das linguagens de computador, C tem um método intrínseco para acessar um
único bit dentro de um byte. Isso pode ser útil por diversas razões:
A criação de tabela de estruturas mantém a sintaxe normal de definição de matrizes, como é
• Se o armazenamento é limitado, você pode armazenar diversas variáveis Booleanas
mostrada no Exemplo 5.8:
(verdadeiro/falso) em um byte.
Exemplo 5.8
struct cad_conta { • Certos dispositivos transmitem informações codificadas nos bits dentro de um byte.
char nome[30];
35 Linguagem de Programação C 36 Linguagem de Programação C
• Certas rotinas de criptografia precisam acessar os bits dentro de um byte. Além disso, nota‐se que os bits após dsr não precisam ser especificados se não são usados.
Variáveis de campo de bit têm certas restrições:
Para acessar os bits, C usa um método baseado na estrutura. Um campo de bits é, na verdade,
apenas um tipo de elemento de estrutura que define o comprimento, em bits, do campo. A forma geral • Não pode obter o endereço de uma variável de campo de bit.
de uma definição de campo de bit é: • Variáveis de campo de bit não podem ser organizadas em matrizes.
struct nome { • Não pode ultrapassar os limites de um inteiro.
tipo var1 : comprimento; • Não pode saber, de máquina para máquina, se os campos estarão dispostos da esquerda
tipo var2 : comprimento; para a direita ou da direita para a esquerda.
… • Em outras palavras, qualquer código que use campos de bits pode ter algumas
tipo varN : comprimento; dependências da máquina.
} [lista_de_variaveis];
Finalmente, é válido misturar elementos normais de estrutura com elementos de campos de
Um campo de bit deve ser declarado como int, unsigned ou signed. Campos de bit de bit. O Exemplo 5.12 define um registro de um empregado que usa apenas um byte para conter três
comprimento 1 devem ser declarados como unsigned, porque um único bit não pode ter sinal. (Alguns informações: o estado do empregado, se o empregado é assalariado e o número de deduções. Sem o
compiladores só permitem campos do tipo unsigned). campo de bits, essa variável ocuparia três bytes.
Exemplo 5.12
Um exemplo de campos de bits é a comunicação via serial que devolve um byte de estado struct emp {
organizado como mostra a Tabela 5.1. struct addr endereco;
float salario;
Tabela 5.1: Estado da comunicação serial. unsigned ativo : 1; /* ocioso ou ativo */
Bit Significado quando ligado unsigned horas : 1; /* pagamento por horas */
unsigned deducao : 3; /* deduções de imposto */
0 alteração na linha clear‐to‐send };
1 alteração em data‐set‐ready
2 borda de subida da portadora detectada 5.3 UNIÕES
3 alteração na linha de recepção
4 clear‐to‐send Uma união é um tipo de dado que pode ser usado de muitas maneiras diferentes. Por exemplo,
5 data‐set‐ready uma união pode ser interpretada como sendo um inteiro numa operação e um float ou double em
6 chamada do telefone outra. Embora, as uniões possam tomar a aparência de uma estrutura, elas são muito diferentes.
7 sinal recebido
Uma união pode conter um grupo de muitos tipos de dados, todos eles compartilhando a
Pode‐se representar a informação em um byte de estado utilizando o seguinte campo de bits: mesma localização na memória. No entanto, uma união só pode conter informações de um tipo de
dados de cada vez. Para criar uma união utiliza‐se a seguinte sintaxe:
Exemplo 5.11
struct status_type { union [nome_union] {
unsigned delta_cts : 1;
unsigned delta_dsr : 1; tipo var1;
unsigned tr_edge : 1; tipo var2;
unsigned delta_rec : 1; …
unsigned cts : 1;
unsigned dsr : 1;
tipo varN;
unsigned ring : 1; } [nome_var];
unsigned rec_line : 1;
} status; Deve‐se encerrar com um ponto‐e‐vírgula a declaração porque a definição de união é na
realidade uma instrução C. A declaração de uniões pode se apresentar de diversas formas. Tais como:
Para atribuir um valor a um campo de bit, simplesmente utiliza‐se a forma para atribuição de
outro tipo de elemento de estrutura. Exemplo 5.13
union {
char c;
status.ring = 0; int i;
double d;
Não é necessário dar um nome a todo campo de bit. Isto torna fácil alcançar o bit que se deseja float f;
} data;
acessar, contornando os não usados. Por exemplo, se apenas cts e dtr importam, pode‐se declarar a
estrutura status_type desta forma:
Na declaração acima, o nome_union não é utilizado pois esta união será utilizada pela variável
struct status_type { data. Para utilizar esta união na definição de outras variáveis tem‐se que declará‐las juntas com a
unsigned : 4; definição da união. No caso de um programa que utilize esta união em várias partes do programa a
unsigned cts : 1; linguagem C permite a criação de rótulos de estruturas (nome_union).
unsigned dsr : 1;
} status; Exemplo 5.14
union tipos {
char c;
37 Linguagem de Programação C 38 Linguagem de Programação C
int i; O sizeof(data) é 8. No tempo de execução, não importa o que a união data está realmente
double d;
float f; guardando. Tudo o que importa é o tamanho da maior variável que pode ser armazenada porque a
} data; união tem de ser do tamanho do seu maior elemento.
Como mostra o Exemplo 5.14, foi declarada a variável data como sendo uma união do tipo 5.5 TYPEDEF
tipos. Quando rotula‐se a união pode‐se omitir a declaração das variáveis, como é mostrado no
Exemplo 5.15:
A linguagem C permite que defina‐se explicitamente novos nomes aos tipos de dados,
Exemplo 5.15 utilizando a palavra‐chave typedef. Não há criação de uma nova variável, mas sim, definindo‐se um
union tipos {
char c; novo nome para um tipo já existente. Serve para uma boa documentação ou até tornar os programas
int i; dependentes de máquina um pouco mais portáteis. A forma geral de um comando typedef é
double d;
float f; typedef tipo nome;
};
Por exemplo, poderia ser criado um novo nome para char utilizando
Para usar esta união em outras declarações deve‐se especificar desta forma:
typedef char boolean;
union tipos data1, data2;
Esse comando diz ao compilador para reconhecer boolean como outro nome para char. Assim,
As estruturas seguem o padrão do escopo de variáveis, isto é, se a declaração estiver contida
para se criar uma variável char, usando boolean
numa função, a estrutura tem escopo local para aquela função; se a declaração estiver fora de todas as
funções, ela terá um escopo global. boolean ok;
Para acessar um campo específico de uma union utiliza‐se o operador . (ponto). Pode‐se Também é válida a redefinição, isto é, utilizar um novo nome para um nome atribuído a um
declarar estruturas dentro de uniões. dado previamente estabelecido.
Exemplo 5.16 Exemplo 5.18
struct so_int { #include <stdio.h>
int i1,i2;
}; typedef char boolean;
typedef boolean bool;
struct so_float {
float f1,f2; int main() {
}; boolean a;
bool b;
union { a = 1;
struct so_int i; b = 2;
struct so_float f; printf("%d %d",a,b);
} teste; return 0;
}
int main() {
teste.i.i1 = 2;
teste.i.i2 = 3; A declaração typedef é usado também para definir tipos estruturados (struct e union) para
printf(“i1 = %-3d i2 = %-3d\n”,teste.i.i1,teste.i.i2); facilitar a nomenclatura dos tipos na declaração de variáveis.
teste.f.f1 = 2.5;
teste.f.f2 = 3.5; Exemplo 5.19
printf(“f1 = %.1f f2 = %.1f\n”,teste.f.f1,teste.f.f2); typedef struct conta {
return 0; char nome[30];
} int idade;
int codigo;
5.4 SIZEOF() float saldo;
} cad_conta;
};
6 ALOCAÇÃO DINÂMICA
typedef struct conta cad_conta;
• Listas • Filas
1. Faça um programa que leia os dados de 10 clientes de um banco e após leia 100 conjuntos de 3
valores:
• Pilhas • Árvores
• código de operação ‐ 0 depósito, 1 ‐ retirada, Cada um destes mecanismos pode ter variações de acordo com a política de processamento
• valor da operação (armazenamento/recuperação). Neste capítulo será abordado com mais ênfase as listas encadeadas,
• código do cliente. porque serão como base para a construção dos demais.
Realize as movimentações nas contas correspondentes e ao final escreva o nome e saldo de 6.1 MAPA DE MEMÓRIA
cada cliente.
Quando um programa em C é executado, quatro regiões de memória são criadas: código do
2. Faça um programa de cadastro de clientes que contenham as seguintes opções: incluir, alteração, programa, variáveis globais, heap e pilha, como ilustrado Figura 6.1. A região da pilha é a porção de
excluir e consultar por código ou por nome. O cadastro deve ser da seguinte forma: memória reservada para armazenar o endereço de retorno das chamadas de funções, argumentos de
funções e variáveis locais, o estado atual da CPU. A pilha cresce de cima para baixo, isto é, do endereço
• nome (30 caracteres); mais alto para o mais baixo. O heap é a região utilizada para uso do programador através das funções de
• código (0 a 255); alocação dinâmica. Como o heap cresce de baixo para cima (endereço mais baixo par o mais alto), pode
• idade(char); haver uma colisão entre as duas regiões, o que causa uma falha no programa. Mas isto pode ocorrer
somente quando determinados modelos de memória são utilizados para executar o programa.
Figura 6.1: Mapa de memória de um programa em C
6.2 FUNÇÕES DE ALOCAÇÃO DINÂMICA EM C
O padrão C ANSI define apenas quatro funções para o sistema de alocação dinâmica: calloc(),
malloc(), free(), realloc(). No entanto, serão estudadas, além das funções descritas, algumas funções
que estão sendo largamente utilizadas.
O padrão C ANSI especifica que os protótipos para as funções de alocação dinâmica definidas
pelo padrão estão em STDLIB.H. Entretanto, tais funções estão especificadas na biblioteca ALLOC.H,
onde encontram‐se mais funções de alocação dinâmica.
O padrão C ANSI especifica que o sistema de alocação dinâmica devolve ponteiros void, que
são ponteiros genéricos, podendo apontar para qualquer objeto. Porém, alguns compiladores mais
41 Linguagem de Programação C 42 Linguagem de Programação C
Esta função devolve ao heap a memória apontada por ptr, tornando a memória disponível para
O fragmento do código mostra a alocação de 1000 bytes de memória.
alocação futura.
char *p;
p = (char*)malloc(1000); A função free() deve ser chamada somente com um ponteiro que foi previamente alocado com
uma das funções do sistema de alocação dinâmica. A utilização de um ponteiro inválido na chamada
No fragmento abaixo é alocado memória suficiente para 50 inteiros. provavelmente destruirá o mecanismo de gerenciamento de memória e provocará uma quebra do
sistema.
int *p;
p = (int*)malloc(50 * sizeof(int)); Exemplo 6.3
#include <string.h>
#include <stdio.h>
O compilador deve conhecer duas informações sobre qualquer ponteiro: o endereço da #include <alloc.h>
variável apontada e seu tipo. Por isso, precisa‐se fazer uma conversão de tipo (cast) do valor retornado
por malloc(), já que o mesmo retorna um void. Portanto, no Exemplo 6.1 deve‐se indicar ao compilador int main() {
char *str;
que o valor retornado por malloc() é do tipo ponteiro para struct addr.
/* aloca memória para uma string */
p=(struct addr*) malloc(sizeof(addr)) str = (char*)malloc(10);
Uma lista singularmente encadeada requer que cada item de informação contenha um elo A construção de uma lista duplamente encadeada é semelhante à de uma lista singularmente
como o próximo da lista. Cada item de dado geralmente consiste em uma estrutura que inclui campos encadeada, exceto pelo fato de que dois elos devem ser mantidos. Utilizando a estrutura ponto, será
de informação e ponteiro de enlace (ou de ligação). mostrado a declaração de um nodo de lista duplamente encadeada.
Há três casos a considerar ao excluir um elemento de uma lista duplamente encadeada: excluir A chamada desta função é realizada desta forma:
o primeiro item, excluir um item intermediário ou excluir o último item.
if (!rt)
void delord(figura *i, /* item a apagar */ rt = stree(rt, rt, info);
figura **inicio, /* primeiro elemento da lista */ else
figura **fim) { /* ultimo elemento da lista */ stree (rt, rt, info);
figura *old, *p;
p = *inicio; Dessa forma, tanto o primeiro quanto os elementos subseqüentes podem ser inseridos
old = NULL;
while (p && (p->x != i->x) &&(p->y != i->y)) {
corretamente. A função stree() é um algoritmo recursivo.
old = p;
p = p->prox; Existem três formas de acessar os dados de uma árvore: ordenada, preordenada e pós‐
} ordenada. Onde a ordenada, é visitado a subárvore da esquerda, a raiz e em seguida a subárvore da
if ((p->x = i->x) &&(p->y = i->y)) { direita. Na preordenada, visita‐se a raiz, subárvore da esquerda e, em seguida, a subárvore da direita.
if (!old) /* exclusão único elemento da lista */ Na pós‐ordenada, visita‐se a subárvore da esquerda, subárvore da direita e, depois, a raiz.
*inicio=*fim=NULL;
else {
if (!p->prox) { /* exclusão do ultimo elemento da lista */ d
old->prox = NULL;
*fim = old;
} else { /* excluir item de uma posição */
old->prox = p->prox; /* intermediária da lista */ b f
p->prox->ant = old;
}
}
free(p); a c e g
}
} Figura 6.2 Exemplo de uma árvore binária
Exemplo 6.7 Para o acesso de forma ordenada, pelas formas descritas anteriormente, pode‐se utilizar as
struct tree {
char info;
funções descritas abaixo:
struct tree *esq;
struct tree *dir; void inorder(arvore *raiz) {
}; if(!raiz)
return;
typedef struct tree arvore; inorder(raiz->esq);
printf(“%c”, raiz->info);
arvore *stree ( inorder(raiz->dir);
arvore *raiz; }
arvore *r;
char info) { void preorder(arvore *raiz) {
if (!r) { if(!raiz)
r = (arvore *) malloc(sizeof(arvore)); return;
if (!r) { printf(“%c”, raiz->info);
printf(“Sem memória \n”); preorder(raiz->esq);
exit(0); preorder(raiz->dir);
} }
r->esq = NULL;
r->dir = NULL; void postorder(arvore *raiz) {
r->info = info; if(!raiz)
if (!raiz) return;
return r; /* primeira entrada */ postorder(raiz->esq);
if (info < raiz->info) postorder(raiz->dir);
raiz->esq = r; printf(“%c”, raiz->info);
else }
raiz->dir = r;
return r; Para exclusão de um nó de uma árvore tem que ser verificado se o nó é a raiz, um nodo
}
if (info < r->info) esquerdo ou direito e que os mesmos podem ter subárvores ligadas a ele. Na função a seguir é realizada
stree(r,r->esq, info); uma exclusão recursiva observando as restrições delineadas anteriormente.
else
stree(r,r->dir, info); arvore *dtree(arvore *raiz, char key) {
}
49 Linguagem de Programação C 50 Linguagem de Programação C
3. Escreva um programa que gerencie uma fila circular do tipo FIFO (Primeiro que entra é o primeiro Cada stream associada a um arquivo tem uma estrutura de controle de arquivo do tipo FILE.
que sai). Essa estrutura é definida no cabeçalho STDIO.H.
9. Faça um programa que leia o número de alunos; construa uma matriz dinamicamente alocada de Todos os arquivos são fechados automaticamente quando o programa termina, normalmente
tamanho N X 4, onde N é o número de alunos e 4 as respectivas notas de cada aluno. Calcule a com main() retornando ao sistema operacional ou uma chamada à exit(). Os arquivos não são fechados
média e mostre na tela conforme descrição a seguir: quando um programa quebra (crash).
Esta função pode ser aplicada tanto para arquivo texto como para arquivos binários.
A função fopen() abre uma stream para uso e associa um arquivo a ela. Retorna o ponteiro de
arquivo associado a esse arquivo. 7.9 CONDIÇÕES DE ERRO
Sintaxe:
FILE *fopen(const char * <nome_arquivo>, const char * <modo_abertura>); Para determinar se um erro ocorreu utiliza‐se a função ferror(), mas esta informação não basta
para a solução por parte do usuário. Para isso utiliza‐se a função perror() em conjunto com a função
O modo de abertura define a forma como é feito o acesso aos dados (somente leitura, leitura e ferror(). O argumento de perror() é uma string fornecida pelo programa que normalmente é uma
escrita, etc). As forma principais são apresentadas na Tabela 7.2. mensagem de erro que indica em que parte do programa ocorreu erro.
Tabela 7.2 ‐ Os modos de abertura válidos Sintaxe:
Modo Significado void perror (const char *str);
r Abre um arquivo texto para leitura
Se for detectado um erro de disco, ferror() retornará um valor verdadeiro (não zero) e perror()
w Cria um arquivo texto para escrita imprimirá a seguinte mensagem
a Anexa a um arquivo texto
Erro de Busca: Bad data
rb Abre um arquivo binário para leitura
wb Cria um arquivo binário para escrita
53 Linguagem de Programação C 54 Linguagem de Programação C
A primeira parte da mensagem é fornecida pelo programa, e a segunda parte, pelo sistema if(argc !=2) {
printf(“Você esqueceu de entrar o nome do arquivo \n”);
operacional. exit(1);
}
7.10 STREAMS PADRÃO
if((fp=fopen(argv[1],”w”))==NULL) {
printf(“Arquivo não pode ser aberto\n”);
Sempre que um programa é iniciado três streams padrões são abertas automaticamente: exit(1);
}
• stdin (entrada padrão ‐ teclado); do {
ch = getchar();
• stdout (saída padrão ‐ tela); putc(ch,fp);
• stderr (saída de erro padrão ‐ tela); } while(ch!=‘$’);
• stdaux (saída auxiliar padrão ‐ porta serial); fclose(fp);
return 0;
• stdprn (impressora padrão ‐ impressora paralela). }
Essas streams podem ser utilizadas normalmente para executar operações de E/S bufferizada. O programa complementar descrito a seguir lê qualquer arquivo ASCII e mostra o conteúdo na
tela.
7.11 LEITURA E GRAVAÇÃO DE CARACTERES
Exemplo 7.5
#include <stdio.h>
Para as operações de leitura e gravação são utilizadas duas funções padrões: getc() e putc() #include <stdlib.h>
(consideradas tecnicamente macros) . Para cada uma destas funções existem duas equivalentes: fgetc() int main(int argc, char *argv[]) {
e fputc(). Nesta seção são apresentadas as funções declaradas padrão ANSI (as duas primeiras). As FILE *fp;
char ch;
outras funções têm a mesma sintaxe que suas equivalentes. if (argc !=2) {
printf(“Você esqueceu de entrar o nome do arquivo \n”);
Para a escrita de caracteres em um arquivo utilizam‐se as funções putc() e fputc(), as quais são exit(1);
}
equivalentes (putc() é uma macro). O protótipo para essa função é if ((fp=fopen(argv[1],”r”))==NULL) {
printf(“Arquivo não pode ser aberto\n”);
Sintaxe: exit(1);
int putc(int ch, FILE *fp); }
ch = getc(fp);
while (ch!=EOF) {
onde fp é um ponteiro de arquivo devolvido por fopen() e ch é o caractere a ser escrito. putchar(ch);
ch = getc(fp);
Se a operação putc() foi bem‐sucedida, ela devolve o caractere escrito. Caso contrário, ela }
fclose(fp);
devolve EOF. return 0;
}
Para leitura de caracteres em um arquivo utilizam‐se as funções getc() e fgetc(), as quais são
equivalentes (getc() é uma macro). O protótipo para essa função é 7.12 TRABALHANDO COM STRINGS
Sintaxe: Para a gravação e leitura de strings de caractere para e de um arquivo em disco são utilizadas
int getc(FILE *fp);
as funções fgets() e fputs(), respectivamente. São os seguintes os seus protótipos:
onde fp é um ponteiro de arquivo devolvido por fopen().
Sintaxe:
int fputs(const char *str, FILE *fp);
Se a operação getc() foi bem‐sucedida, ela devolve o caractere lido. Caso contrário, ela devolve char *fgets(char *str, int length, FILE *fp);
EOF. O Exemplo 7.3 mostra um laço que realiza uma leitura de um arquivo texto até que a marca de
final de arquivo seja lida. A função fputs() opera como puts(), mas escreve a string na stream especificada. EOF é
Exemplo 7.3 devolvido se ocorre um erro.
do {
ch = getc(fp); A função fgets() lê uma string da stream especificada até que um caractere de nova linha seja
} while (ch!=EOF);
lido ou que length‐1 caracteres tenham sido lidos. Se uma nova linha é lida, ela será parte da string
(diferente de gets()). A string resultante será terminada por um nulo. A função devolve um ponteiro
As funções fopen(), getc(), putc() e fclose() constituem o conjunto mínimo de rotinas de
para str se bem‐sucedida ou um ponteiro nulo se ocorre um erro.
arquivos. O programa a seguir lê caracteres do teclado e os escreve em um arquivo em disco até que o
usuário digite um cifrão ($). O nome do arquivo é passado pela linha de comando. O programa a seguir lê strings do teclado e escreve‐as no arquivo chamado frase.dat. A
Exemplo 7.4 condição de saída é uma linha em branco. Como gets() não armazena o caractere de nova linha, é
#include <stdio.h> adicionado um antes que a string seja escrita no arquivo para que o arquivo possa ser lido mais
#include <stdlib.h>
int main(int argc, char *argv[]) { facilmente.
FILE *fp; Exemplo 7.6
char ch; #include <stdio.h>
55 Linguagem de Programação C 56 Linguagem de Programação C
Sintaxe: Sintaxe:
void rewind(FILE *fp); int fflush(FILE *fp);
O Exemplo 7.7 reposiciona o indicador de posição do arquivo do programa anterior e mostra o Essa função escreve o conteúdo de qualquer dado existente no buffer arquivo associado a fp.
conteúdo do mesmo. Se fflush() devolve 0 para indicar sucesso; caso contrário, devolve EOF.
Exemplo 7.7
#include <stdio.h>
#include <stdlib.h> 7.13.5 ACESSO ALEATÓRIO: FSEEK()
#include <string.h>
int main() { Operações de leitura e escrita aleatórias podem ser executadas utilizando o sistema de E/S
char str[80];
FILE *fp; bufferizado com a ajuda de fseek(), que modifica o indicador de posição de arquivo. Seu protótipo é
if((fp=fopen(“frase.dat”,”w”))==NULL) { mostrado aqui:
printf(“Arquivo não pode ser aberto\n”);
exit(1); Sintaxe:
}
int fseek (FILE *fp, long numbytes, int origem);
do {
printf(“entre uma string (CR para sair): \n”);
gets(str); Onde, numbytes, um inteiro longo, é o número de bytes a partir de oriem, que se tornará a
strcat(str,”\n”); nova posição corrente, e origem é uma das seguintes macros definidas em STDIO.H.
fputs(str,fp);
} while (*str != ‘\n”); Tabela 7.3 – Macros definidas em STDIO.H para as posições permitidas na função fseek().
rewind(fp); /* reinicializa o file pointer */
while(!feof(fp)) { Origem Nome da Macro
fgets(str, 79, fp); Início do arquivo SEEK_SET
printf(str); Posição atual SEEK_CUR
}
fclose(fp);
Final do arquivo SEEK_END
return 0;
} A função fseek() devolve 0 se a operação for bem‐sucedida e um valor diferente de zero se
ocorre um erro.
7.13.2 FERROR()
O Exemplo 7.9 mostra a utilização da função fseek() num programa que recebe pela linha de
A função ferror() determina se uma operação com arquivo produziu um erro. A função ferror() comando o deslocamento a ser realizado a partir do início do arquivo (SEEK_SET).
tem esse protótipo: Exemplo 7.9
57 Linguagem de Programação C 58 Linguagem de Programação C
7.15 LENDO E GRAVANDO REGISTROS
7.13.6 FTELL()
As funções fread() e fwrite() possibilitam uma maneira de transferir blocos de dados do disco
Esta função retorna a posição do ponteiro de um arquivo binário em relação ao seu começo. para a memória do computador e vice‐versa. A grande vantagem destes comandos é poder ler e gravar
Esta função aceita um único argumento, que é o ponteiro para a estrutura FILE do arquivo. Seu dados maiores que um byte e que formem estruturas complexas (vetor, matriz, ou um registro, ou até
protótipo é mostrado aqui: um vetor de registros).
Sintaxe:
long ftell (FILE *fp); 7.15.1 ESCRITA DE UM BLOCO DE DADOS
Retorna um valor do tipo long, que representa o número de bytes do começo do arquivo até a Para se gravar um bloco de dados maiores que um byte o sistema de arquivo ANSI C fornece a
posição atual. função fwrite(). Seu protótipo está definido a seguir:
A função ftell() pode não retornar o número exato de bytes se for usada com arquivos em Sintaxe:
modo texto, devido à combinação CR/LF que é representada por um único caractere em C. size_t fwrite(void *buffer, size_t num_bytes, size_t count, FILE *fp);
Para se ler um bloco de dados maiores que um byte o sistema de arquivo ANSI C fornece a A função memchr() procura, no vetor apontado por buffer, pela primeira ocorrência de ch nos
função fread(). Seu protótipo está definido a seguir: primeiros count caracteres. Devolve um ponteiro para a primeira ocorrência de ch em buffer ou um
ponteiro nulo se ch não for encontrado. O protótipo da função é
Sintaxe:
size_t fread(void *buffer, size_t num_bytes, size_t count, FILE *fp); Sintaxe:
void *memchr(const void*buffer, int ch, size_t count);
onde buffer é um ponteiro para uma região de memória que receberá os dados do arquivo. O número Exemplo 7.14
de bytes para ler é especificado por num_bytes. O argumento count determina quantos itens serão #include <stdio.h>
lidos. E, finalmente, fp é um ponteiro de arquivo para uma stream aberta anteriormente. #include <string.h>
int main() {
char *p;
Esta função devolve o número de itens lidos. O número retornado pode ser menor que count p = memchr(“isto e um teste”. ´ ´,14);
quando o final de arquivo for atingido ou ocorrer um erro de leitura. printf(p);
return 0;
Exemplo 7.12 }
int var_int;
FILE *arq;
arq = fopen(“dados.dat”,”rb”); A função memcmp() compara os primeiros count caracteres das matrizes apontadas por buf1 e
fread(&var_int,sizeof(var_int),1,arq); buf2. O valor devolvido segue os valores da função strcmp(). O protótipo da função é
Sintaxe:
int memcmp(const void*buf1, const void*buf2, size_t count);
7.15.3 UTILIZANDO OS COMANDOS DE LEITURA E GRAVAÇÃO DE REGISTROS
A função memcpy() copia os primeiros count caracteres do vetor origem para o vetor apontado
Uma das mais úteis aplicações de fread() e fwrite() envolve ler e escrever tipos de dados por destino. Ela devolve um ponteiro para destino. O protótipo da função é
definidos pelo usuário, especialmente estruturas.
Exemplo 7.13 Sintaxe:
#include <stdio.h> void *memcpy(void*destino, const void*origem, size_t count);
struct pto {
Exemplo 7.15
int x,y; #include <stdio.h>
}; #include <string.h>
typedef struct pto ponto; #define SIZE 80
int main() { int main() {
FILE *fp; char buf1[SIZE], buf2[SIZE];
int i; strcpy(buf1, “Quando, no curso do ...”);
ponto coord[10]; memcpy(buf2, buf1, SIZE);
for (i=0; i < 10; i++) { printf(buf2);
printf(“Coordenada x:”); return 0;
scanf(“%d \n”,&coord[i].x); }
printf(“Coordenada y:”);
scanf(“%d \n”,&coord[i].y); A função memmove() copia count caracteres do vetor apontado por origem para o vetor
} apontado por destino. Se as matrizes se sobrepõem, a cópia ocorrerá corretamente, colocando o
if ((fp=fopen(“figura.dat”,”w”))==NULL) {
conteúdo correto em destino, porém origem será modificado. Ela devolve um ponteiro para destino. O
printf(“Arquivo não pode ser aberto\n”);
exit(1); protótipo da função é
}
for (i=0; i < 10; i++) Sintaxe:
fwrite(&coord[i], sizeof(ponto),1, fp);
rewind(fp); void *memmove(void*destino, const void*origem, size_t count);
i = 0;
while(!feof(fp)) { Exemplo 7.16
fread(&coord[i], sizeof(ponto), 1, fp); #include <stdio.h>
printf(“Coordenadas (x,y) = (%d,%d),coord[i].x,coord[i].y); #include <string.h>
} #define SIZE 80
fclose(fp);
return 0; int main() {
} char buf1[SIZE], buf2[SIZE];
strcpy(buf1, “Quando, no curso do ...”);
memmove(buf2, buf1, SIZE);
7.16 FUNÇÕES PARA MANIPULAÇÃO DE BUFFERS printf(buf2);
}
Para trabalhar com buffers utilizam‐se algumas funções especializadas que são independentes
de tipo. A função memset() copia o byte menos significativo de ch nos primeiros count caracteres do
vetor apontado por buf. Ela devolve buf. é muito utilizado na inicialização de uma região de memória
com algum valor conhecido. O protótipo da função é
61 Linguagem de Programação C 62 Linguagem de Programação C
Sintaxe:
void *memset(void*buf, int ch, size_t count);
Exemplo 7.17 A. TABELA ASCII
/* Inicializa com nulo os 100 primeiros bytes */
memset(buf, ’\0’, 100); /*do vetor apontado por buf */
Cód Car Cód Car Cód Car Cód Car Cód Car Cód Car Cód Car Cód Car
/* Inicializa com X os 10 primeiros bytes */
memset(buf, ’X’, 10); 0 32 64 @ 96 ‘ 128 Ç 160 á 192 └ 224 Ó
printf(buf); 1 ☺ 33 ! 65 A 97 a 129 ű 161 í 193 ┴ 225 ß
2 ☻ 34 ” 66 B 98 b 130 é 162 ó 194 ┬ 226 Ô
7.17 EXERCÍCIOS 3 ♥ 35 # 67 C 99 c 131 â 163 ú 195 ├ 227 Ò
4 ♦ 36 $ 68 D 100 d 132 ä 164 ñ 196 ─ 228 õ
1. Faça um programa que escreva os números de 0 a 10 em um arquivo. 5 ♣ 37 % 69 E 101 e 133 à 165 Ñ 197 ┼ 229 Õ
6 ♠ 38 & 70 F 102 f 134 å 166 ª 198 ã 230 µ
2. Faça um programa que leia 11 números de um arquivo.
7 • 39 ’ 71 G 103 g 135 ç 167 º 199 Ã 231 þ
3. Escrever um programa em C que leia um número indeterminado de códigos (inteiro) e taxas de 8 40 ( 72 H 104 h 136 ê 168 ¿ 200 ╚ 232 Þ
consumo (real), em Kw, dos consumidores de uma cidade e grave os dados em um arquivo 9 41 ) 73 I 105 i 137 ë 169 ® 201 ╔ 233 Ú
chamado medidas.txt. O programa pára de ler quando o código fornecido for zero. 10 42 * 74 J 106 j 138 è 170 ¬ 202 ╩ 234 Û
11 ♂ 43 + 75 K 107 k 139 ï 171 ½ 203 ╦ 235 Ù
4. Escrever um programa em C que leia o arquivo gerado no exercício 1 e mostre quantas taxas
12 ♀ 44 , 76 L 108 l 140 î 172 ¼ 204 ╠ 236 ý
existem e o maior consumo.
13 45 - 77 M 109 m 141 ì 173 ¡ 205 ═ 237 Ý
14 ♫ 46 . 78 N 110 n 142 Ä 174 « 206 ╬ 238 ¯
5. Escrever um programa em C que leia um arquivo texto (temp.txt) e grave em outro arquivo
(tempMax.txt). O arquivo origem possuiu um número indeterminado de linhas, onde cada linha 15 ☼ 47 / 79 O 111 o 143 Å 175 » 207 ¤ 239 ´
possui 5 colunas: dia, mês, ano, temperatura mínima (real), temperatura máxima (real). O 16 4 48 0 80 P 112 p 144 É 176 ░ 208 ð 240
programa deve escrever no arquivo tempMax.txt o dia, mês, ano, e a temperatura máxima 17 3 49 1 81 Q 113 q 145 æ 177 ▒ 209 Ð 241 ±
somente quando ela for maior que a temperatura máxima do dia anterior. Por exemplo, 18 ↕ 50 2 82 R 114 r 146 Æ 178 ▓ 210 Ê 242 _
assuma que o arquivo temp.txt possua o seguinte conteúdo: 19 !! 51 3 83 S 115 s 147 ô 179 │ 211 Ë 243 ¾
10 10 2007 16.5 32.2 20 ¶ 52 4 84 T 116 t 148 ö 180 ┤ 212 È 244 ¶
11 10 2007 15.4 31.0 21 § 53 5 85 U 117 u 149 ò 181 Á 213 245 §
12 10 2007 17.5 32.7 22 ▬ 54 6 86 V 118 v 150 û 182 Â 214 Í 246 ÷
13 10 2007 18.6 32.4 23 ↕ 55 7 87 W 119 w 151 ù 183 À 215 Î 247 ¸
14 10 2007 17.5 30.3 24 ↑ 56 8 88 X 120 x 152 ÿ 184 © 216 Ï 248 °
15 10 2007 18.3 31.2 25 ↓ 57 9 89 Y 121 y 153 ö 185 ╣ 217 ┘ 249 ¨
16 10 2007 16.8 30.2 26 → 58 : 90 Z 122 z 154 Ü 186 ║ 218 ┌ 250 ·
17 10 2007 17.6 31.3 27 ← 59 ; 91 [ 123 { 155 ø 187 ╗ 219 █ 251 ¹
28 ¬ 60 < 92 \ 124 | 156 £ 188 ╝ 220 ▄ 252 ³
O programa deve gerar o arquivo tempMax.txt com o seguinte conteúdo: 29 ↔ 61 = 93 ] 125 } 157 Ø 189 ¢ 221 ¦ 253 ²
12 10 2007 32.7 30 t 62 > 94 ^ 126 ~ 158 × 190 ¥ 222 Ì 254
15 10 2007 31.2 31 u 63 ? 95 _ 127 Δ 159 ƒ 191 ┐ 223 ▀ 255
17 10 2007 31.3
63 Linguagem de Programação C
8 BIBLIOGRAFIA
BERRY, J. Programando em C++. São Paulo: Makron Books, 1991.
ECKEL, B. C++. São Paulo: McGraw‐Hill, 1991.
ELLIS, M. A. et alli C++: Manual de referência completo. Rio de Janeiro: Campus, 1993.
IBPI. Dominando a Linguagem C. Rio de Janeiro: IBPI, 1993.
MIZRAHI, V. V. Treinamento em Linguagem C. São Paulo: McGraw‐Hill, 1990.
PAPPAS, C. H.; MURRAY, W. Turbo C++ completo e total. São Paulo: McGraw‐Hill, 1991.
SCHILDT, H. C Completo e total. São Paulo: McGraw‐Hill, 1991.