Buscar

ApostilaC-Avancado v2

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 3, do total de 65 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 6, do total de 65 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes
Você viu 9, do total de 65 páginas

Faça como milhares de estudantes: teste grátis o Passei Direto

Esse e outros conteúdos desbloqueados

16 milhões de materiais de várias disciplinas

Impressão de materiais

Agora você pode testar o

Passei Direto grátis

Você também pode ser Premium ajudando estudantes

Prévia do material em texto

#include<math.h>
#include<stdio.h>
#include<stdlib.h>
float max(float x, 
 float y){
 if (x > y)
 return x;
 return y;
}
float mediaHarmPond(float amostra1, 
	float peso1, 
	float amostra2, 
	float peso2, 
	float amostra3, 
	float peso3) {
 return (peso1 + peso2 +peso3)/(peso1/max(amostra1,0.0001)+peso2/max(amostra2,0.0001)+peso3/max(amostra3,0.0001));
}
void main(){
 unsigned long int cod;
 int i, n=0;
 float p1, p2, p3, mh[60], mh_media=0, somatorio=0, s;
 FILE *saida;
 
 if ((saida = fopen("l:medias.DAT", "w"))== NULL){ // Cria um arquivo para escrita
	 printf("Nao conseguiu criar arquivo de saida.\n");
 exit(0); // Aborta execucao
 }
 while(1){
	 printf("Forneca o codigo do aluno\n");
	 scanf("%ld",&cod);
 if (cod==0) break; // Sai do laco
	 printf("Forneca as notas das provas do aluno\n");
	 scanf("%f %f %f",&p1, &p2, &p3);
	 if (p1>=0 && p1<=10 && p2>=0 && p2<=10 && p3>=0 && p3<=10){
 mh[n] = mediaHarmPond(p1,1,p2,2,p3,3);
 mh_media += mh[n];
		fprintf(saida,"Codigo: %8ld Prova 1: %2.2f Prova 2: %2.2f Prova 3: %2.2f Media harmonica ponderada: %2.2f\n", cod, p1, p2, p3, mh[n]);
 n++;
 } else
 printf("Intervalo de notas invalido\n");
 }
 mh_media = mh_media/n;
 for(i=0;i<n;i++)
 somatorio+=(mh[i]-mh_media)*(mh[i]-mh_media);
 s = sqrt(somatorio/(n-1));
 printf("O desvio padrao eh %.4f\n",s);
}
Programação 
em C
Módulo Avançado
Sumário
Prefácio	4
1	Tipos de Dados	5
1.1	Enumeração	7
1.2	Modificadores de Tipo de Acesso	7
1.2.1	Const	7
1.2.2	Volatile	8
1.3	Constantes	8
1.3.1	Constantes pré-definidas	9
2	Operadores	10
2.1	Operadores Bit a Bit	10
2.1.1	Operadores bit a bit de Atribuição	12
2.2	Operador ? :	12
2.3	Operadores de Ponteiros & e *	13
2.4	Precedência dos operadores	13
2.5	Exercícios	14
3	Funções	15
3.1	Localização das funções	15
3.1.1	Corpo da função antes do programa principal (no mesmo arquivo)	16
3.1.2	Corpo da função depois do programa principal (no mesmo arquivo)	16
3.1.3	Corpo da função escrito em arquivo separado	16
3.2	Argumentos para função main()	17
3.3	Protótipo de funções	17
3.4	Retorno de Ponteiros	18
3.5	Classes de Armazenamento	18
3.5.1	auto	18
3.5.2	static	18
3.5.3	extern	19
3.5.4	register	20
3.6	Diretiva #define	20
3.7	Funções Recursivas	20
3.8	Exercícios	21
4	Ponteiros	24
4.1	Expressões com Ponteiros	24
4.1.1	Atribuição de Ponteiros	24
4.1.2	Aritmética de Ponteiros	24
4.2	Inicialização de Ponteiros	25
4.2.1	Comparação de Ponteiros	26
4.3	Ponteiros e Matrizes	26
4.3.1	Matrizes de Ponteiros	27
4.3.2	Acessando partes de Matrizes como vetores	27
4.4	Indireção Múltipla	28
4.5	Ponteiros para Funções	28
4.6	Mais Sobre declarações de Ponteiros	29
4.7	Exercícios	31
5	Estruturas e Uniões	32
5.1	Estruturas	32
5.1.1	Inicializando Estruturas	33
5.1.2	Estruturas Aninhadas	33
5.1.3	Estruturas e funções	34
5.1.4	Vetor de Estruturas	34
5.1.5	Ponteiros para Estruturas	35
5.2	Campos de Bits	35
5.3	Uniões	37
5.4	Sizeof()	38
5.5	Typedef	39
5.6	Exercícios	40
6	Alocação Dinâmica	41
6.1	Mapa de memória	41
6.2	Funções de Alocação dinâmica em C	41
6.2.1	malloc()	42
6.2.2	calloc()	43
6.2.3	free()	43
6.2.4	realloc()	44
6.3	Matrizes Dinamicamente Alocadas	44
6.4	Listas Encadeadas	45
6.4.1	Listas Singularmente Encadeadas	45
6.4.2	Listas Duplamente Encadeadas	46
6.5	Árvores Binárias	48
6.6	Exercícios	50
7	E/S com Arquivo	51
7.1	E/S ANSI x E/S UNIX	51
7.2	Streams	51
7.3	Arquivos	51
7.4	Sistema de Arquivos	51
7.5	Estrutura FILE	52
7.6	Abertura de Arquivos	52
7.7	Fechamento de Arquivo	53
7.8	Verificando fim de arquivo	53
7.9	Condições de erro	53
7.10	Streams Padrão	54
7.11	Leitura e Gravação de caracteres	54
7.12	Trabalhando com Strings	55
7.13	Funções de tratamento de arquivos	56
7.13.1	rewind()	56
7.13.2	ferror()	56
7.13.3	remove()	57
7.13.4	fflush()	57
7.13.5	Acesso aleatório: fseek()	57
7.13.6	ftell()	58
7.14	Comando de gravação em modo texto formatado	58
7.15	Lendo e gravando registros	59
7.15.1	Escrita de um bloco de dados	59
7.15.2	Leitura de um bloco de dados	59
7.15.3	Utilizando os comandos de leitura e gravação de registros	60
7.16	Funções para manipulação de buffers	60
7.17	Exercícios	62
A.	Tabela Ascii	63
8	Bibliografia	64
Prefácio
Este texto tem o objetivo de fornecer os subsídios para o desenvolvimento de programas avançados na linguagem C. Os tópicos estudados neste texto são estruturas, uniões, campos de bits, alocação dinâmica e arquivos. 
Tipos de Dados
	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 intervalo de valores que consegue representar. 
Tabela 1.1 - Tipos de dados básicos para plataformas 32 bits
	Tipo
	Bits
	Faixa Mínima
	char
	8
	-128 a 127
	int
	32
	-2,147,483,648 a 2,147,483,647
	float
	32
	3.4E-38 a 3.4E+38
	double
	64
	1.7E-308 a 1.7E+308
	void
	0
	sem valor
Os tipos char e int armazenam números inteiros, enquanto que os tipos float e Double armazenam números de ponto flutuante (é um formato de representação digital de números reais).
Com exceção de void, os tipos de dados básicos podem estar acompanhados por modificadores na declaração de variáveis. Os modificadores de tipos da linguagem C são:
Linguagem de Programação C
5
· signed;
· long;
· unsigned;
· short.
Os modificadores signed, short, long e unsigned podem ser aplicados aos tipos básicos char e int. Contudo, long também pode ser aplicado à double. 
A função printf() possui especificadores de formato que permitem mostrar inteiros short e long. O %ld, %li, %lo, %lu, %lx especificam que o tipo de dado é long. O %hd, %hi, %ho, %hu, %hx especificam que o tipo de dado é short. 
O especificador de formato long pode ser ainda utilizado para tipo ponto flutuante (indicando que segue um double): %le, %l E, %lf, %lg, e %lG. Outro especificado de formato é o L, o qual é utilizado para associar um long double: %Le, %LE, %Lf, %Lg, e %LG.
A Tabela 1.2 mostra todas as combinações de tipos de dados e as informações sobre tamanho, formatação e intervalo.
Tabela 1.2 - Utilização dos tipos de dados (plataforma 32 bits)
	
	Tipo
	Bits
	Formatação printf()
	Intervalo
	
	
	
	
	Inicio
	Fim
	Inteiros 
	char
	8
	%c
	-128
	127
	
	unsigned char
	8
	%c
	0
	255
	
	signed char
	8
	%c
	-128
	127
	
	short int
	16
	%hi
	-32.768
	32.767
	
	signed short int
	16
	%hi
	-32.768
	32.767 
	
	unsigned short int
	16
	%hu
	0
	65.535
	
	int
	32
	%i
	-2.147.483.648
	2.147.483.647
	
	signed int
	32
	%i
	-2.147.483.648
	2.147.483.647
	
	unsigned int
	32
	%u
	0
	4.294.967.295
	
	long int
	32
	%li
	-2.147.483.648
	2.147.483.647
	
	signed long int
	32
	%li
	-2.147.483.648
	2.147.483.647
	
	unsigned long int
	32
	%lu
	0
	4.294.967.295
	Ponto Flutuante
	float
	32
	%f
	3,4E-38
	3.4E+38 
	
	double
	64
	%lf
	1,7E-308
	1,7E+308
	
	long double
	80
	%Lf
	3,4E-4932
	3,4E+4932
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 implementações em que esse tipo, por padrão, não tem sinal. Algumas implementações podem permitir que unsigned seja aplicado aos tipos de ponto flutuante (como em unsigned double). Porém, isso reduz a portabilidade de seu código e geralmente não é recomendado. O modificador unsigned altera o valor da faixa mínima do tipo através do uso do bit mais significativo (indicador de sinal). 
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 varia de plataforma.
Exemplo 1.1
#include <stdio.h>
int main() {
 int qtde;
 char tam;
 float total;
 qtde = 2; tam = ‘G’;
 total = 20.70;
 printf(“Comprei %d camisas de tamanho %c.”, qtde, tam);
 printf(“\nNo total, paguei R$ %f.”, custo);
 return 0;
}
Execução:
	Comprei 2 camisas de tamanho G.
	No total, paguei R$ 20.70.
	As variáveis podem ser inicializadas no momentoem que se faz a declaração das mesmas. Pode-se ver isto usando o programa anterior, que a execução será a mesma da versão anterior.
Exemplo 1.2
#include <stdio.h>
int main() {
 int qtde=2;
 char tam=‘G’;
 float total=20.70;
 printf(“Comprei %d camisas de tamanho %c.”, qtde, tam);
 printf(“\nNo total, paguei R$ %f.”, custo);
 return 0;
}
Devido às diferenças de tipos em diferentes máquinas e plataformas, pode-se utilizar a função sizeof() para descobrir o tamanho real da variável ou tipo.
Exemplo 1.3
#include <stdio.h>
int main() {
 printf("Tipo\t\tTamanho\n");
 printf("char\t\t%d\n",sizeof(char));
 printf("int\t\t%d\n",sizeof(int));
 printf("float\t\t%d\n",sizeof(float));
 printf("double\t\t%d\n",sizeof(double));
 printf("long int\t%d\n",sizeof(long int));
 return 0;
}
Execução:
Tipo Tamanho
char 1
int 4
float 4
double 8
long int 4
Enumeração
	Enumeração é um conjunto de constantes inteiras que especifica todos os valores legais de uma variável desse tipo pode ser. A forma geral para enumeração é:
Sintaxe:
	enum nome { lista_de_enumeração } lista_de_variáveis;
	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
Exemplo 1.4
enum cores {amarelo, verde, vermelho};
enum cores semaforo;
	Dada essa definição e declaração, os tipos de comandos seguintes são perfeitamente válidos:
semaforo = verde;
if (semaforo==verde) printf(“Passagem permitida \n”);
	Para melhor compreensão da enumeração entende-se que cada símbolo representa um valor inteiro. O valor do primeiro símbolo da enumeração é 0. Assim,
printf (“%d %d”, verde, vermelho);
mostra 1 2 na tela.
	Como extensão, pode-se inicializar os símbolos de forma alternada para algum problema específico.
Exemplo 1.5
enum cores { amarelo, verde=10, vermelho };
	Agora os valores destes símbolos são
	amarelo		0
	verde		10
	vermelho	11
Modificadores de Tipo de Acesso
	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 preceder os modificadores de tipo e os nomes que eles modificam.
Const
	Variáveis do tipo const não podem ser modificadas por seu programa (por isso ela recebe um valor inicial). 
Exemplo 1.6
const int a=10;
	O Exemplo 1.6 cria uma variável inteira chamada a, com um valor inicial 10, que seu programa não pode modificar. 
	Um exemplo do uso do const é para verificar se uma variável em particular é modificada pelo seu programa.
Volatile
	O modificador volatile é usado para informar ao compilador que o valor de uma variável pode 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 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 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.
	É possível usar const e volatile juntos. Por exemplo, se 0x30 é assumido como sendo o valor de uma porta que é mudado por condições externas. Para evitar efeitos colaterais deve-se declarar da seguinte forma:
const volatile unsigned char *port = 0x30;
Constantes
	Uma constante tem valor fixo e inalterável durante a execução do programa. Isto pode ser exemplificado pelos Exemplos 3.1 e 3.2 da função printf().
	Em uma constante caractere é escrita entre aspas simples (‘’), uma constante cadeia de caracteres entre aspas duplas (“”) e constantes numéricas como o número propriamente dito.
Exemplo 1.7
	‘C’
	“programa”
	8
	465.67
	Constantes em C podem ser de qualquer um dos cinco tipos de dados básicos. A maneira como cada constante é representada depende do seu tipo. Pode-se especificar precisamente o tipo da 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 tipos inteiros, o sufixo U representa unsigned e o L representa long. A Tabela 1.3 mostra alguns exemplos de constantes.
Tabela 1.3 - Exemplo de constantes
	Tipo de Dado
	Exemplo de Constantes
	int
	1 123 21000 -234
	long int
	35000L -34L
	short int
	10 -12 90
	unsigned int
	10000U 987U 40000“
	float 
	123.23F 2.34e-3F
	double
	123.23 12312333 -0.9876324
	long double
	1001.2L
	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 constante na forma hexadecimal. Uma constante octal começa com 0.
Exemplo 1.8
	int hex = 0x80;		/* 128 em decimal */
	int oct = 012;		/* 10 em decimal */
Constantes pré-definidas
Em alguns compiladores C, algumas constantes simbólicas já estão pré-definidas. Estas constantes em geral definam alguns valores matemáticos (, /2, e, etc.), limites de tipos etc. A seguir são apresentadas algumas (existem muitas outras) constantes simbólicas pré-definidas no compilador Turbo C++ da Borland.
Biblioteca Constante Valor Significado
math.h M_PI 3.14159... 
math.h M_PI_2 1.57079... /2
math.h M_PI_4 0,78539... /4
math.h M_1_PI 0,31830... 1/
math.h M_SQRT2 1,41421... 2
Operadores
	A linguagem C é muito rica em operadores internos. C define quatro classes de operadores: aritméticos, relacionais, lógicos e bit a bit. Além disso, C tem alguns operadores especiais para tarefas particulares.
Operadores Bit a Bit
Os operadores bit a bit são comumente utilizados para trabalhar com dispositivos (pois os mesmos utilizam bytes ou palavras codificadas para comunicação), modo de armazenamento (um byte pode representar 8 informações binárias), e até compactação de dados (utilização de bits ociosos). A Tabela 2.1 mostra os operadores bit a bit suportados pela linguagem.
Tabela 2.1 - Operadores bit-a-bit
	Operador
	Ação
	
	&
	E (AND)
	
	|
	OU (OR)
	
	^
	OU exclusivo (XOR)
	
	~
	Complemento de um
	
	>>
	Deslocamento à esquerda
	
	<<
	Deslocamento à direita
	
	Os operadores bit a bit só podem ser utilizados sobre um byte ou uma palavra, isto é, aos tipos de dados char e int e variantes do padrão C. Operações bit não podem ser usadas em float, double, long double, void ou outros tipos mais complexos. 
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;
res = x & y;		/* res = 0000 0010 */
res = y & mascara;	/* res = 0000 1000 – desligar os bits 0 e 1 */
res = y & 2		/* res = 0000 0010 – bit ligado qdo res > 0 */
res = y & 4		/* res = 0000 0000 – bit desligado qdo res == 0 */
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çãocom 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;
res = x | y;		/* res = 0000 1111 */
res = y | mascara;	/* res = 0000 1011 – ligar o bit 0 */
res = x | 8;		/* res = 0000 1111 – ligar o bit 3 */
O operador bit a bit ^ executa um ou-exclusivo (XOR) 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 somente um dos bits dos operandos é 1. Caso os bits dos operandos forem iguais, o operador retorna o bit 0. Este operador é mais utilizado para inverter bits (realizando a operação com um operando cujos bits que devam ser invertidos estejam com valor 1, enquanto que os outros estão em 0).
Exemplo 2.3
unsigned char x = 7;		/* 0000 0111 */
unsigned char y = 4;		/* 0000 1010 */
unsigned char mascara = 3;		/* 0000 0011 */
unsigned char res;
res = x ^ y;		/* res = 0000 1101 */
res = y ^ mascara;	/* res = 0000 1001 – inverter os bits 0 e 1 */
res = y ^ 8;		/* res = 0000 0010 – inverter o bit 3 */
O operador bit a bit ~ executa um não lógico (complemento de 1) no valor a sua direita (operador unário), produzindo um novo byte ou palavra com os bits invertidos.
Exemplo 2.4
unsigned char x = 7;	/* 0000 0111 */
unsigned char res;
res = ~x;			/* res = 1111 1000 */
res = ~127;			/* res = 1000 0000 */
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 é:
Sintaxe:
operando << número de bits
operando >> número de bits
	Conforme os bits são deslocados para um direção, zeros são utilizados para preencher a 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 analógico/digitais, portas seriais), e multiplicação (deslocamento para a esquerda) e divisão inteira (deslocamento para a direita) por 2. 
Exemplo 2.5
#include <stdio.h>
int main() {
 unsigned char x=7;
 printf("1. %2i\n",x >> 1);
 printf("2. %2i\n",x << 1);
 printf("3. %2i\n",x >> 2);
 printf("4. %2i\n",x << 2);
 printf("5. %2i\n",8 >> 2);
 printf("6. %2i\n",8 << 2); 
 printf("7. %2i\n",x+3 >> 2);
 printf("8. %2i\n",x+3 << 2);
 return 0;
}
Execução:
1. 3
2. 14
3. 1
4. 28
5. 2
6. 32
7. 2
8. 40
	O Exemplo 2.5 mostra que os operadores de deslocamento podem ser utilizados com variáveis, constantes e até mesmo expressões. Entretanto, deve-se verificar a precedência de operadores quando trabalhando com expressões. 
Exemplo 2.6
unsigned char x = 7;	/* 0000 0111 */
unsigned char res;
res = x << 1;			/* res = 00001110	res = 14 */
res = x << 3; 		/* res = 01110000	res = 112 */
res = x << 2; 		/* res = 11000000	res = 192 */
res = x >> 1; 		/* res = 01100000	res = 96 */
res = x >> 2; 		/* res = 00011000	res = 24 */
Não confunda os operadores relacionais && e || com & e |, respectivamente. Os operadores relacionais trabalham com os operandos como um único valor lógico (verdadeiro ou falso), e eles produzem somente dois valores 0 ou 1. Os operadores bit a bit podem produzir valores arbitrários pois a operação é realizada em nível de bit. 
Operadores bit a bit de Atribuição
	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
	Operador
	Ação
	
	x &= y
	x = x & y
	
	x |= y
	x = x | y
	
	x ^= y
	x = x ^ y
	
	x ~= y
	x = x ~ y
	
	x >>= y
	x = x >> y
	
	x <<= y
	x = x << y
	
	As expressões com este operadores são mais compactas e normalmente produzem um código de máquina mais eficiente. 
A execução da operação bit a bit ocorre por último após a avaliação da expressão à direita do sinal igual. 
Operador ? : 
	O operador ? substitui sentenças da forma Se-então-senão.
Sintaxe:
Exp1 ? Exp2 : Exp3;
	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 o valor da expressão.
Exemplo 2.7
x = 10;
y = x > 9 ? 100 : 200;
	No Exemplo 2.7, y recebe o valor 100, porque x (valor é 10) é maior que 9. Uma expressão equivalente seria
x = 10;
if (x > 9) y = 100;
else y = 200;
Operadores de Ponteiros & e *
	Um ponteiro é um endereço na memória de uma variável. Uma variável de ponteiro é uma 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 memória de seu operando. Por exemplo,
m = &cont;
atribui o endereço de memória da variável cont em m. 
	Este tipo de operando não pode ser utilizado em três casos:
1. &(cont + 4) - sempre se associa a uma variável e não expressão;
2. &3 - constantes não são válidas;
3. variáveis declaradas com classe de armazenamento register (não existe endereço para registrador).
	O segundo operador é *. Ele é um operador unário que devolve o valor da variável localizada no endereço que o segue. Por exemplo, se m contém o endereço da variável cont,
q = *m;
coloca o valor de cont em q.
	Os seguintes operadores * e & colocam o valor 10 na variável chamada target. O resultado (o valor 10) deste programa é mostrado na tela.
Exemplo 2.8
#include <stdio.h>
int main() {
 int target, source;
 int *m;
 source = 10;
 m = &source;
 target = *m;
 printf(“%d”,target);
 return 0;
}
Precedência dos operadores
	A Tabela 2.3 mostra a precedência dos operadores da linguagem C.
Tabela 2.3: Precedência dos operadores 
	Precedência
	Operador
	1º
	() [] ->
	2º
	- (menos unário) ++ -- ! ~ & (endereço) * (ponteiro)
	3º
	* / %
	4º
	+ -
	4º
	<< ≫ 
	5º
	< <= > >=
	6º
	== !=
	7º
	&
	8º
	^
	9º 
	!
	10º
	&&
	11º
	||
	12º
	?:
	13º
	= *= /= %= += -= &= |= ^= ~= <<= >>=
	14º
	,
Exercícios
1. Faça um programa que leia um número binário de 16 bits, armazene-o, e mostre o valor em 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 do bits do valor
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. 
4. Faça uma função que receba um valor do tipo char como parâmetro e devolva quantos bits estão ligados.
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, que é a posição onde o número será partido. Por exemplo:
int main(){
 crossover(10,69,2); // retorna 13 = 0000 1101
 // 10 = 0000 1010, teremos que pegar desse nº, os bits + significativos, ou seja: 0000 1???
 // 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 + metade do outro
}
6. Faça a função rodaEsquerda(int n, int nBits) que retorna o n com nBits rotações à esquerda. Perceba que uma rotação não deve perder bits, ao contrário do operador de deslocamento. Trabalhe pensando apenas nos 8 bits para n.
int main(){
 unsigned char x;
 x = rodaEsquerda (4, 2); // se 4 = 0000 0100, 
			 // então rodaEsquerda (4,2)== 0000 1000
}
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 (7,5,3,1,6,4,2,0)
int main(){
 criptografa(73); 	// se 73 = 0100 1001, 
			// então criptografa(73) == 0010 1001 == 41
}
8. Faça a função descriptografa(int n) que faz o processo invertido da questão 7.
9. Faça uma função que receba um vetor de 32 posições de inteiros (valores 0 e 1) e que retorne um valor com os bits ligados ou desligados conforme o conteúdode cada posição do vetor. 
Funções
	A forma geral de uma função é:
Sintaxe:
	tipo_função nome_função (declaração_parâmetros) {
	 corpo_função;
	}
Exemplo 3.1
 	int soma(int x, int y) {
 ...
 }
As funções retornam um valor (do tipo indicado em tipo_função). O valor retornado pela função é dado pelo comando return (o valor retornado pode ou não ser utilizado).
	Existem dois tipos de passagem de argumentos: por valor e por referência. A segunda é realizada através de apontadores.
Exemplo 3.2
	int pot(int x, int n) { 	/* x elevado na n potência */
 	 int p;
	 for(p=1;n>0;n--)
	 p *= x;
	 return p;
	}
	No Exemplo 3.2, os argumentos foram passados por valor e a função retorna um valor do tipo inteiro. A chamada seria:
	a = pot(10,2);
	No Exemplo 3.3, nenhum valor é retornado (por isso usa-se o tipo void) mas é realizado uma 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*/
void troca(int *a, *b) { 
	 int aux;
	 aux = *a;
	 *a = *b;
	 *b = aux;
	}
	A chamada para esta função seria:
	int x=1,y=2;
	troca(&x,&y);
	Na passagem de parâmetros por referência é passado explicitamente o endereço da variável com o uso do operador &. Quando o argumento for uma matriz automaticamente será passado o endereço da matriz para a função.
A linguagem C aceita chamadas recursivas de funções.
Localização das funções
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 programa principal ou em arquivo separado. 
Corpo da função antes do programa principal (no mesmo arquivo)
Quando escrevemos a definição de uma função antes do programa principal e no mesmo arquivo deste, nenhuma outra instrução é necessária. 
Exemplo 3.4
float media2(float a, float b) { // função
	 float med;
	 med = (a + b) / 2.0;
	 return(med);
	}
int main() { // programa principal
 	 float num_1, num_2, med;
 	 puts(”Digite dois números:”);
 	 scanf(”%f %f”, &num_1, &num_2);
 med = media2(num_1, num_2); // chamada da função 
 	 printf(”\nA media destes números é %f”, med);
 return 0;
}
Corpo da função depois do programa principal (no mesmo arquivo)
Quando escrevemos a definição de uma função depois do programa principal e no mesmo 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 tipo. A sintaxe geral para isto é a seguinte:
Sintaxe:
int main() { // programa principal
 tipo nomef(...); // protótipo da função
 ...
 var = nomef(...) // chamada a função
 ...
}
tipo nomef(...){ // definição da função
 [corpo de função]
}
Exemplo 3.5
#include <stdio.h>
int main() { // programa principal
 	 float media2(float,float); // protótipo de media2()
 	 float num_1, num_2, med;
 	 puts(”Digite dois números:”);
 	 scanf(”%f %f”, &num_1, &num_2);
 med = media2(num_1, num_2); // chamada a função 
 	 printf(”\nA media destes números é %f”, med);
}
float media2(float a, float b){ // função media2()
	 float med;
	 med = (a + b) / 2.0;
	 return(med);
	}
Protótipo de uma função nada mais é que a declaração da função sem o seu corpo. Por isso, a lista de argumentos do protótipo podem ser escritas apenas com os tipos dos argumentos. 
Corpo da função escrito em arquivo separado
Em C, como em muitas outras linguagens, é permitido que o usuário crie uma função em um arquivo e um programa que a chame em outro arquivo distinto. Esta facilidade permite a criação de bibliotecas de usuário: um conjunto de arquivos contendo funções escritas pelo usuário. Esta possibilidade é uma grande vantagem utilizada em larga escala por programadores profissionais. 
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 inclusão é feita com a diretiva #include. Esta diretiva, vista nas seções 2.4.2 e 3.7.1, instrui o compilador para incluir na compilação do programa outros arquivos que contem a definição das funções de usuário e de biblioteca. 
Sintaxe:
#include ”path” // inclusão da função
int main() { // programa principal
...
 var = nomef(...) // chamada a função
...
}
Na diretiva #include, indicamos entre aspas duplas o caminho de localização do arquivo onde está definida a função chamada.
Exemplo 3.6
#include ”c:\tc\userbib\stat.h” // inclusão da função
int main() { // programa principal
 	 float num_1, num_2, med;
 	 puts(”Digite dois números:”);
 	 scanf(”%f %f”, &num_1, &num_2);
 med = media2(num_1, num_2); // chamada a função 
 	 printf(”\nA media destes números é %f”, med);
 return 0;
}
Argumentos para função main()
	A função main() aceita argumentos para a passagem de parâmetros realizada através da chamada do programa. Os dois argumentos são:
argc: contador de argumentos;
argv: vetor de argumentos (vetor de apontadores para strings).
Sintaxe:
main(int argc, char *argv[])
	É importante lembrar que 
Exemplo 3.7
#include <stdio.h>
int main(int argc, char *argv[]) {
 int cont;
 printf(“Foram encontrados %d argumentos \n”,argc -1);
 for (cont=1;cont < argc;cont++)
 printf(“Argumento %d: %s \n”, cont, argv[cont]);
 return 0;
}
	O primeiro argumento (argv[0]) é o nome do programa.
Protótipo de funções
	O padrão ANSI C expandiu a declaração tradicional de função, permitindo que a quantidade e os tipos dos argumentos das funções sejam declarados. A definição expandida é chamada protótipo de função. Protótipos de funções não faziam parte da linguagem C original. 
Protótipos permitem que C forneça uma verificação mais forte dos tipos. Protótipos de funções ajudam a detectar erros antes que eles ocorram. É verificado número de parâmetros, compatibilidade de tipos, entre outras.
	Existem três tipos de declaração de protótipos:
Sintaxe								Exemplo
tipo_função nome_função ();					int pot();
tipo_função nome_função (lista_tipo_argumentos);			int pot(int,int);
tipo_função nome_função (lista_tipo_nome_argumentos);		int pot(int x, int y);
Retorno de Ponteiros
	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:
	tipo_função *nome_função(lista_de_argumentos);
Classes de Armazenamento
	A linguagem C possui quatro classes de armazenamento de variáveis:
· auto			(automáticas)
· extern		(externas)
· static		(estáticas)
· register		(em registradores)
auto
	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 e elas são criadas quando a função é chamada e destruídas quando a função ou o bloco de código termina a sua execução.
	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
Linguagem de Programação C
35
int main() { 
 auto int x;
 ...
} 
é equivalente a
int main() {
 int x;
 ...
}
static
	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 variáveis globais.
Uma variável estática é inicializada uma única vez, no momento em que é criada.
O Exemplo 3.7 mostra o uso de uma variável estática. Note que a variável i foi declarada como static enquanto que j foi declarado normalmente (ou auto). A cada chamada, a variável j é inicializada com zero enquantoque a variável i mantém o último valor. Além disso, apesar de i ser inicializada, isto ocorre somente na primeira vez. 
Exemplo 3.7
#include <stdio.h>
void imprimeValor() {
 static int i = 10;
 int j =0;
 for (; j<5; j++)
 printf("%3d\t",i++);
 printf("\n");
}
int main() {
 imprimeValor();
 imprimeValor();
 imprimeValor();
 return 0;
}
Execução
10 11 12 13 14
15 16 17 18 19
20 21 22 23 24
Variáveis estáticas podem manter a contagem de quantas vezes uma função foi chamada. 
Variáveis Locais static
	Quando o modificador static é aplicado a uma variável local, o compilador cria armazenamento permanente para ela quase da mesma forma como cria armazenamento para uma variável global. Em termos simples, uma variável local static é uma variável local que retém seu valor entre chamadas. Mas ela só é reconhecida apenas no bloco em que está declarada.
Variáveis Globais static
	Quando o modificador static é aplicado a uma variável global, o compilador cria uma variável que é reconhecida apenas no arquivo na qual a mesma foi declarada.
extern
	Toda variável declarada fora de qualquer função têm a classe de armazenamento extern. 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 arquivos onde a variável é utilizada. Se não proceder assim, o compilador acusará um erro de duplicidade de variável.
Exemplo 3.8
Arquivo 1
int x,y;
char ch;
int main() {
 ...
}
void func1() {
 x = 123;
}
Arquivo 2
extern int x,y;
extern char ch;
void func22() {
 x = y / 10;
}
void func23() {
 y = 10;
}
	No arquivo 2, a lista de variáveis globais foi copiada do arquivo 1 e o especificador extern foi 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 memória para essas variáveis declaradas com o especificador extern na certeza de estarem declaradas em outro módulo.
register
	A classe de armazenamento register indica que a variável associada deve ser guardada fisicamente numa memória de acesso muito mais rápido, chamada registrador. No caso do IBM-PC pode ser colocado o tipo int e char associado a register pois o registrador tem apenas 16 bits. 
	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.
Diretiva #define
	A diretiva #define pode ser usada para definir constantes simbólicas com nomes apropriados. Por exemplo, a constante PI pode ser definida com o valor 3.14159.
#define PI 3.14159
	Só pode ser escrito um comando destes por linha, e não há ponto-e-vírgula após qualquer diretiva do pré-processador.
	Esta diretiva é usada para definir macros com argumentos. 
#define AREA(x) (4*PI*x*x)
	A declaração acima define a função AREA() a qual calcula a área de uma esfera. A vantagem desta declaração é a não tipagem do argumento x. Não deve haver espaços entre o nome da macro e seus identificadores. 
Funções Recursivas
Uma função é dita recursiva quando se é definida dentro dela mesma. Isto é, uma função é recursiva quando dentro dela está presente uma instrução de chamada a ela própria.
Exemplo 10.9
// imprime uma frase invertida utilizando recursão
#include <stdio.h>
#include <conio.h>
void inverte()
int main() {
 clrscr( );
 inverte( );
 return 0;
}
void inverte() {
 char ch ;
 if ((ch=getche( )) != ‘\r’ ) inverte();
 scanf(“%c”,ch)
}
Toda função recursiva deve possuir uma condição que termina a recursividade, senão ela pode causar uma parada inesperada do sistema
Exercícios
1. Escreva um programa que receba como parâmetro um índice (float). Após, ler uma seqüência de números (a qual termina por 0) e exibir o seu valor multiplicado pelo índice. A função que transforma uma string em um float é atof(char *x).
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. Existe um algoritmo interessante para se obter a raiz quadrada de um número quando ela é exata. Para isso, basta subtrair números ímpares consecutivos do número do qual se deseja retirar a raiz quadrada. O número de vezes será a raiz do número. Por exemplo: 
No exemplo, subtraíram-se de 25 os 5 primeiros números ímpares consecutivos até que se chegasse 0. Assim, a raiz quadrada de 25 é 5. Escreva uma função que receba um inteiro n e retorne a raiz quadrada de n. Por exemplo, se a função receber 49, ele retornará 7. O calculo da raiz quadrada deverá ser feito usando o algoritmo acima, sem usar qualquer função pré-existente de alguma biblioteca C. 
4. Seja o seguinte programa:
#include<stdio.h>
void x(int n){
 int i, resto;
 i = n;
 do{
 resto = i%16;
 i=i/16;
 switch(resto){
 case 10: printf("A"); break;
 case 11: printf("B"); break;
 case 12: printf("C"); break;
 case 13: printf("D"); break;
 case 14: printf("E"); break;
 case 15: printf("F"); break;
 default: printf("%d", resto);
 }
 }while(i>0);
 printf("\n");
}
void main(){
int N;
 scanf("%d",&N);
 x(N);
}
O que será escrito na tela, supondo que o valor fornecido para N seja 10846? Mostre o teste de mesa completo utilizado para determinar a saída. 
5. Simule a execução do programa abaixo mostrando todas as mudanças de valores de variáveis e o resultado da impressão. 
#include<stdio.h>
int perf(long int N){
 long int i, divs=0;
 for(i=1; i<= N/2; i++)
 if (N%i == 0) divs = divs + i;
 if (divs == N) return 1;
 else return 0;
}
void main(){
 long int x=14;
 if (perf(x)==1) printf("%d é perfeito",x);
 else printf("%d não é perfeito",x);
}
6. Escreva uma função em C que receba como argumentos a altura (alt) e o sexo de uma pessoa e retorne o seu peso ideal. Para homens, calcular o peso ideal usando a fórmula
e , para mulheres,
.
7. Escreva uma função em C com o seguinte protótipo
long int multiplicatorio(int i, int n)
A função deve retornar o multiplicatório de i a n. Por exemplo, a chamada
multiplicatorio(3,10)
retorna 1814400 (3×4×5×6×7×8×9×10).
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, 
onde 
é a variação da velocidade ou a velocidade final menos a velocidade inicial. Escreva uma função em C que receba como parâmetros a velocidade inicial, a velocidade final e o intervalo de tempo correspondente e retorne a aceleração. Mostre, também, uma função main() que chame essa função. 
11. O valor de π/2 pode ser calculado pela seguinte série de produtos: 
Escreva uma função em C que receba como argumento um número inteiro n e retorne o valor de π calculado através da série acima com n termos.
12. Um aço é classificado de acordo com o resultado de três testes, que devem verificar se ele satisfaz às seguintes especificações: 
· Teste 1: conteúdo de carbono abaixo de 7%;
· Teste 2: dureza Rokwell maior que 50;
· Teste 3: resistência à tração maior do que 80.000 psi.
O aço recebe grau 10 se passar pelos três testes; 9, se passar apenas nos testes 1 e 2; 8, se passar no teste 1; e 7, se não passou nos três testes. Escreva uma função em C que o conteúdo de carbono (em %), a dureza Rokwell e a resistência à tração (em psi) de uma amostra de aço e retorne o grau obtido. 
13. Escreva uma função em C que receba um número n e retorne 1 se a soma dos dígitos formantes de n for 10; 0 caso contrário. Por exemplo, se o valor de n recebido for 145 a função retorna 1.
14. Escreva uma função em C que recebaum numero e retorne seu fatorial. O fatorial de n é representado por n! sendo que
0! = 1
1! = 1
2! = 1×2 = 2
3! = 1×2×3 = 6
4! = 1×2×3×4 = 24
5! = 1×2×3×4×5 = 120
Ponteiros
	Para uma boa utilização dos ponteiros deve-se compreender corretamente o seu uso. Existem três razões para isso: primeiro, ponteiros fornecem os meios pelos quais as funções podem modificar seus argumentos; segundo, eles são usados para suportar as rotinas de alocação dinâmica de C, e terceiro, o uso de ponteiros para aumentar a eficiência de certas rotinas.
	Por ser um dos aspectos mais poderosos da linguagem também são os mais perigosos. Por erros no uso de ponteiros (como a não inicialização de ponteiros - ponteiros selvagens) podem provocar quebra do sistema.
	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. 
	Uma declaração de ponteiros consiste no tipo base, um "*" e o nome da variável. A forma geral é:
tipo *nome;
onde tipo é qualquer tipo válido em C e nome é o nome da variável ponteiro.
	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 ponteiros é feita através do tipo base. 
	Os operadores utilizados são * e &, como já foi explicado na seção 6.6.
Expressões com Ponteiros
	Nesta seção serão analisados alguns aspectos especiais de expressões com ponteiros.
Atribuição de Ponteiros
	Como é o caso com qualquer variável, um ponteiro pode ser usado no lado direito de um comando de atribuição para passar seu valor para outro ponteiro.
Exemplo 4.1
#include <stdio.h>
void main() {
 int x;
 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 */
 return 0;
}
	Agora, tanto p1 quanto p2 apontam para x. O endereço de x é mostrado usando o modificador de formato de printf() %p, que faz com que printf() apresente um endereço no formato usado pelo computador hospedeiro.	
Aritmética de Ponteiros
	Existem apenas duas operações aritméticas que podem ser usadas com ponteiros: adição e subtração. Os operadores permitidos no caso seriam: +, -, ++, --.
	O incremento é sempre realizado do tamanho básico de armazenamento do tipo base. Isto é, 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 (char) será de um byte.
Exemplo 4.2
int *ptri=3000;
char *ptrc=4000;
float *ptrf=4000;
ptri++;			/* ptri apontará para o endereço 3002 */
ptrc++;			/* ptrc apontará para o endereço 4001 */
ptrf++;			/* ptrf apontará para o endereço 5004 */
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 */
	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 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.
Não se altera o valor de um ponteiro constante (ponteiro para um tipo de dado básico - int, float double, …), somente de um ponteiro variável (ponteiro de estruturas complexas - vetores, matrizes, strings, …).
Inicialização de Ponteiros
	Após um ponteiro ser declarado, mas antes que lhe seja atribuído um valor, ele contém um valor desconhecido. Ao usar este ponteiro antes de inicializar, provavelmente provocará uma falha do programa ou até do sistema operacional.
	Como um ponteiro nulo é assumido como sendo não usado, pode-se utilizar o ponteiro nulo 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 abordagem.
Exemplo 4.3
/* procura um nome */
search(char *p[], char *name)
{
 register int t;
 for (t=0;p[t];++t)
 if(!strcmp(p[t],name)) return t;
 return -1;		/* não encontrado */
}
	O laço for dentro de search() é executado até que seja encontrada uma coincidência ou um ponteiro nulo. Como o final da matriz é marcado com um ponteiro nulo, a condição de controle do laço falha quando ele é atingido.
	Outra utilização de inicialização de ponteiros é a inicialização de strings. Isto pode ser levado como uma variação no tema de inicialização usado na variável argv.
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 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 aloca menos memória que a inicialização através de matriz.
Exemplo 4.5
#include <stdio.h>
#include <string.h>
char *p=“alo mundo”;
int main() { 
 register int t;
 printf(p);
 for (t=strlen(p) - 1; t > -1; t--) printf(“%c”,p[t]);
 return 0;
}
Comparação de Ponteiros
	É possível comparar dois ponteiros em uma expressão relacional. 
Exemplo 4.6
if (p<q) printf(“p aponta para uma memória mais baixa que q /n”);
	Geralmente, a utilização de comparação entre ponteiros é quando os mesmos apontam para um objeto comum. Exemplo disto é a pilha, onde é verificado se os ponteiros de início e fim da pilha estão apontando para a mesma posição de memória, significando pilha vazia. 
Ponteiros e Matrizes
	Existe uma estreita relação entre matrizes e ponteiros. Pois C fornece dois métodos para acessar elementos de matrizes: aritmética de ponteiros e indexação de matrizes. Aritmética de ponteiros pode ser mais rápida que indexação de matrizes. Normalmente utilizam-se ponteiros para acessar elementos de matrizes devido a velocidade de acesso.
Exemplo 4.7
char str[80], *p1;
p1 = str;
Para acessar a string str pode-se utilizar estes dois mecanismos
str[4]		/* indexação de matrizes */
ou
*(p1 + 4)	/* aritmética de ponteiros */
Os dois comandos devolvem o quinto elemento.
*(matriz + índice) é o mesmo que matriz[índice].
	Para uma melhor compreensão ou facilidade de programação as funções de indexação trabalham com ponteiros (como mostra o Exemplo 4.8 a implementação da função puts()).
Exemplo 4.8
/* Indexa s como uma matriz */
void put(char *s) {
 register int t;
 for (t=0;s[t]; ++t) putchar(s[t]);
}
/* Acessa s como um ponteiro */
void put(char *s) {
 while (*s) putchar(*s++);
}
	No caso da passagem de parâmetros é possível tratar uma matriz como se fosse um ponteiro.
Exemplo 4.9
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void le_tab(int *p) {
 register int i;
 for(i=0; i<20; i++)
 scanf(“%d”,(p+i));
}
void mostra_tab(int *p) {
 register int i;
 for(i=0; i<20; i++)
 printf(“%d”,*(p+i));
}
void main(void) {
 int mat[20];
 le_tab(mat);
 mostra_tab(mat);
}
Matrizes de Ponteiros
	Ponteiros podem ser organizados em matrizes como qualquer outro tipo de dado. A declaração de uma matriz de ponteiros int, de tamanho 10, é
int *x[10];
	Para atribuir o endereço de uma variável inteira, chamada var, ao terceiro elemento da matriz de ponteiros, deve-se escrever
x[2] = &var;
para encontrar o valor de var, escreve-se
*x[2];
	Se for necessário passar uma matriz de ponteiros para uma função, pode ser usado o mesmo método que é utilizado para passar outras matrizes - simplesmente chama-se a função com o nome da matriz sem qualquer índice.
Exemplo 4.10
void display_array(int *q[]) {
 register int t;
 for (t=0; t<10; t++)
 printf(“%d”,*q[t]);
}
	Lembre-se de que q não é um ponteiro para inteiros; q é um ponteiro para uma matriz de ponteiros para inteiros. Portanto, é necessário declarar o parâmetroq como uma matriz de ponteiros para inteiros, como é mostrado no Exemplo 4.10. Isto é, não é uma passagem de parâmetros por referência por dois motivos: primeiro, matriz como argumento de função é automaticamente passada por referência por questão da implementação da linguagem, e segundo, é uma matriz de ponteiros e conseqüentemente sua declaração é caracterizada pelo asterisco na frente do nome da variável.
	Matrizes de ponteiros são usadas normalmente como ponteiros de strings como, por exemplo, o argumento da linha de comandos argv.
Acessando partes de Matrizes como vetores
	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 muito útil no tratamento de matrizes. O Exemplo 4.11 mostra a atribuição de uma linha da matriz nome para um ponteiro.
Exemplo 4.11
int main() {
 static char nome[10][10];
 char *p[10];
 for (i = 0;i<10;i++)
 p[i] = nome[i];
 ordena(p);
 return 0;
}
Indireção Múltipla
	Indireção múltipla é uma situação onde o ponteiro aponta para outro ponteiro e que o mesmo aponta para um valor final. A Figura 4.1 mostra o conceito de indireção múltipla. 
Ponteiro							Variável
endereço					 valor
Indireção Simples
	 Ponteiro			 Ponteiro			 Variável
	endereço			 endereço			 valor
Indireção Múltipla
Figura 4.1 Indireção simples e múltipla
A indireção múltipla pode ser levada a qualquer dimensão desejada, mas raramente é necessário mais de um ponteiro para um ponteiro.
Não confunda indireção múltipla com listas encadeadas.
	A declaração deste tipo de variável é feita colocando-se um * adicional em frente ao nome da variável, como mostra o Exemplo 4.12. Tal exemplo mostra a declaração da variável ptrptrint como um ponteiro para um ponteiro do tipo int.
Exemplo 4.12
int **ptrptrint;
	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:
Exemplo 4.13
#include <stdio.h>
int main() {
 int x, *p, **q;
 x = 10;
 p = &x;
 q = &p;
 printf(“%d”, **q); 	/* imprime o valor de x */
 return 0;
}
Ponteiros para Funções
	A linguagem C permite apontadores para funções. Isto é permitido pois toda função tem uma posição física na memória que pode ser atribuída a um ponteiro. Portanto, um ponteiro de função pode ser usado para chamar uma função.
	O endereço de uma função é obtido usando o nome da função sem parênteses ou argumentos. Mas para declarar este tipo de apontador tem que se seguir uma sintaxe especial como mostra o Exemplo 4.14.
Exemplo 4.14
#include <stdio.h>
#include <string.h>
void check(char *a, char *b, int (*cmp)());
int main() { 
 char s1[80], s2[80];
 int (*p)();
 p = strcmp;
 gets(s1);
 gets(s2);
 check(s1,s2,p);
 return 0;
}
void check(char *a, char *b, int (*cmp)()) {
 if (!(*cmp) (a, b)) printf(“igual”);
 else printf(“diferente”);
}
	Quando a função check() é chamada, dois ponteiros para caractere e um ponteiro para uma função são passados como parâmetros. Dentro da função check(), note como o ponteiro para função é declarado, pois esta é a forma correta de se declarar este tipo de ponteiro. Os parênteses ao redor de *cmp são necessários para que o compilador interprete o comando corretamente.
	Outra forma de fazer a chamada é mostrada no Exemplo 4.14 o qual dispensa o uso de um ponteiro adicional.
check(s1, s2, strcmp);
	Uma das grandes utilidades é o uso de drivers de dispositivos (placas de som, placas de vídeo, 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 fabricante. 
	Outra utilidade é o programador poder enviar a função que se apropria para a comparação por exemplo. Isto é, no caso de strings pode-se pensar em um comparador de strings genérico onde como terceiro parâmetro é enviado a função que vai realizar a comparação. Antes da chamada da função genérica pode verificar se a string é composta por caracteres alfanuméricos (através da função 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()).
Mais Sobre declarações de Ponteiros
	As declarações de ponteiros podem ser complicadas e é necessário algum cuidado na sua interpretação. principalmente em declarações que envolvem funções e matrizes. Assim, a declaração
int *p(int a);
indica uma função que aceita um argumento inteiro e retorna um ponteiro para um inteiro. Por outro lado, a declaração
int (*p)(int a);
indica um ponteiro para uma função que aceita um argumento inteiro e retorna um inteiro. Nessa última declaração, o primeiro par de parênteses é usado para o aninhamento e o segundo par, para indicar uma função.
	A interpretação de declarações mais complexas pode ser extremamente mais trabalhosa. Por exemplo, considere a declaração
int *(*p)(int (*a)[]);
	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 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, 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.
Se logo após um identificador existir um “abre parênteses” indica que o identificador representa uma função e os colchetes representam uma matriz. Os parênteses e colchetes têm maior precedência do que qualquer operador.
	A seguir será mostrado várias declarações envolvendo ponteiros e seu significado.
	int *p;
	p é um ponteiro para um valor inteiro
	int *p[10];
	p é uma matriz de ponteiros com 10 elementos para valores inteiros
	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
	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
	int (*p)(char *a);
	p é m ponteiro para uma função que aceita um argumento que é 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 um caractere e retorna um ponteiro para uma matriz inteira de 10 elementos
	int p(char (*a)[]);
	p é 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 é 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 ponteiroscom 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
Exercícios
1. Como referenciar mat[x][y] em notação de ponteiros.
2. Qual será a saída deste programa?
int main() {
 int i=5;
 int *p;
 p = &i;
 printf(“%u %d %d %d %d \n”, p, *p+2,**&p,3**p,**&p+4);
 return 0;
}
3. Escreva uma função que inverta a ordem dos caracteres de uma string.
4. Crie uma função que receba como parâmetro uma matriz de ponteiros para strings e devolve a matriz ordenada.
5. Faça uma função que receba um ponteiro para uma matriz e que realize a ordenação da mesma.
6. Faça a declaração de uma função (nome: teste) que receba um ponteiro para uma função que possui dois argumentos (int e char) e retorne um ponteiro para um float.
7. Faça a declaração e inicialização de uma matriz de ponteiros para os dias da semana.
8. Faça uma função que receba uma matriz de ponteiros para caracteres e realize a ordenação alfabética da mesma.
 Estruturas e Uniões
	A linguagem C permite criar tipos de dados definíveis pelo usuário de cinco formas diferentes. 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 é 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 de variáveis. Um quarto tipo de dado é a enumeração, que é uma lista de símbolos, como foi visto na seção 1.5. O último tipo definido pelo usuário é criado através do uso de typedef e define um novo nome para um tipo existente.
Estruturas
	O tipo estruturado struct possibilita a criação de estruturas de dados complexas, isto é, pode-se obter estruturas que contenham mais de um tipo de dado. Tal estrutura é conhecida em outras linguagens como registros.
	Cada elemento que compõe a estrutura (chamado campo) pode ser acessado individualmente, assim como a estrutura pode ser acessada como um todo. Em C, a declaração de uma estrutura é feita da seguinte forma:
struct [nome_struct] {
	 tipo var1;
	 tipo var2;
	 …
	 tipo varN;} [nome_var];
	Deve-se encerrar com um ponto-e-vírgula a declaração porque a definição de estrutura é na realidade uma instrução C.
	A declaração de estruturas pode se apresentar de diversas formas. Tais como:
struct {
 	char nome[30];
 	int idade;
 	int codigo;
 	float saldo; 
} conta1, conta2;
	Na declaração acima, o nome_struct não é utilizado, pois esta estrutura será utilizada pelas variáveis de estrutura conta1 e conta2. Para utilizar esta estrutura na definição de outras variáveis tem-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
struct cad_conta {
	char nome[30];
	int idade;
	int codigo;
	float saldo; 
} conta1, conta2;
	Como mostra Exemplo 5.1, foram declaradas as variáveis conta1 e conta2 como sendo uma 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.
Exemplo 5.2
struct cad_conta {
	char nome[30];
	int idade;
	int codigo;
	float saldo; 
};
	Para usar esta estrutura em outras declarações deve-se especificar desta forma:
struct cad_conta conta1, conta2;
	As estruturas seguem o padrão do escopo de variáveis, isto é, se a declaração estiver contida 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.
	Para acessar um campo específico de uma struct utiliza-se o operador . (ponto).
Exemplo 5.3
conta1.saldo = 0;
conta1.codigo = 0;
strcpy(conta1.nome,”Joao”);
conta1.idade = 21;
	É permitida a atribuição entre struct. Neste caso todos os campos são copiados.
Exemplo 5.4
conta2 = conta1;
Inicializando Estruturas
	Uma estrutura só pode ser inicializada se pertencer às classes static ou extern. Observe que a classe de uma estrutura é dada pelo ponto em que as variáveis foram declaradas e não pelo ponto onde a estrutura foi definida.
	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.
Exemplo 5.5
struct cad_conta {
	char nome[30];
	int idade;
	int codigo;
	float saldo; 
};
int main() {
 static struct cad_conta
		 conta1 = {“Andre”, 23, 9507, 1567.89},
		 conta2 = {“Carlos”, 33, 9678, 1000.59};
 …
}
Estruturas Aninhadas
	Como os campos da estrutura podem ser de qualquer tipo, também é permitido o uso de estruturas na declaração.
Exemplo 5.6
struct data {
 int dia;
 char mes[10];
 int ano;
};
struct func {
 char nome[20];
 int codigo;
 float salario;
 struct data nascimento;
};
int main() {
 static struct func
 funcionario = {“Marcio”, 1234, 3743.44, {10, “Janeiro”, 1967}},
	 gerente = {“Jose”, 456, 5634.28, {18, “Marco”, 1950}};
 return 0;
}
	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:
funcionário.nascimento.dia = 10;
strcpy(gerente.nascimento.mes,”Abril”);
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 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 operador de endereço (&).
	No Turbo C e outros compiladores mais recentes, a responsabilidade da decisão fica a cargo do programador. Assim, uma função pode passar ou retornar uma estrutura. 
Exemplo 5.7
struct cad_conta {
	char nome[30];
	int idade;
	int codigo;
	float saldo; 
};
int main() {
 static struct cad_conta conta1;
 conta1 = ins_conta();
 lista(conta1); 
 …
}
struct cad_conta ins_conta() {
 struct cad_conta aux;
 gets(aux.nome);
 scanf(“%d”, &aux.idade);
 scanf(“%d”, &aux.codigo);
 scanf(“%f”, &aux.saldo);
 return(aux);
}
void lista(struct cad_conta aux) {
 printf(“Nome : %s\n”,aux.nome);
 printf(“Idade : %d\n”, aux.idade);
 printf(“Codigo : %d\n”, aux.codigo);
 printf(“Saldo : %.2f\n”, aux.saldo);
}
Vetor de Estruturas
	A criação de tabela de estruturas mantém a sintaxe normal de definição de matrizes, como é mostrada no Exemplo 5.8:
Exemplo 5.8
struct cad_conta {
	char nome[30];
	int idade;
	int codigo;
	float saldo; 
};
int main() {
 int i
 static struct cad_conta conta[10]=
			{ {“Andre”, 23, 9507, 1567.89},
			 {“Carlos”, 33, 9678, 1000.59},
			...
			};
 for (i=0;i<10;i++) {
 printf(“Nome : %s\n”,conta[i].nome);
 printf(“Idade : %d\n”, conta[i].idade);
 printf(“Codigo : %d\n”, conta[i].codigo);
 printf(“Saldo : %.2f\n”, conta[i].saldo);
 }
…
}
Ponteiros para Estruturas
	C permite ponteiros para estruturas exatamente como permite ponteiros para outros tipos de variáveis. No entanto, há alguns aspectos especiais de ponteiros de estruturas.
	Como outros ponteiros, declara-se colocando um * na frente do nome da estrutura. No Exemplo 5.9 declara-se ptr_cta como um apontador da estrutura previamente definida cad_conta.
Exemplo 5.9
struct cad_conta *ptr_cta;
	Há dois usos primários para ponteiros de estrutura:gerar uma chamada por referência para uma função e criar estruturas de dados dinâmicas (listas, pilhas, filas, entre outras) utilizando-se do sistema de alocação de C.
	Na forma de acessar os elementos ou campos de uma estrutura usando um ponteiro para a estrutura, deve-se utilizar o operador -> (seta). A seta é usada sempre no caso de apontador de estruturas. No Exemplo 5.10 é mostrada a declaração, atribuição e utilização de ponteiros de estruturas.
Exemplo 5.10
struct cad_conta {
	char nome[30];
	int idade;
	int codigo;
	float saldo; 
} conta;
int main() {
 struct cad_conta *ptr;
 ptr = &conta;		/* o ptr recebe o end. da estrutura */
 ptr->idade = 23;
 ptr->codigo = 1000;
 return 0;
}
Campos de Bits
	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:
· Se o armazenamento é limitado, você pode armazenar diversas variáveis Booleanas (verdadeiro/falso) em um byte.
· Certos dispositivos transmitem informações codificadas nos bits dentro de um byte.
· Certas rotinas de criptografia precisam acessar os bits dentro de um byte.
	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 de uma definição de campo de bit é:
struct nome {
		 tipo var1 : comprimento;
		 tipo var2 : comprimento;
		 …
		tipo varN : comprimento;
} [lista_de_variaveis];
Um campo de bit deve ser declarado como int, unsigned ou signed. Campos de bit de comprimento 1 devem ser declarados como unsigned, porque um único bit não pode ter sinal. (Alguns compiladores só permitem campos do tipo unsigned).
Um exemplo de campos de bits é a comunicação via serial que devolve um byte de estado organizado como mostra a Tabela 5.1.
Tabela 5.1: Estado da comunicação serial.
	Bit
	Significado quando ligado
	0
	alteração na linha clear-to-send
	1
	alteração em data-set-ready
	2
	borda de subida da portadora detectada
	3
	alteração na linha de recepção
	4
	clear-to-send
	5
	data-set-ready
	6
	chamada do telefone
	7
	sinal recebido
	Pode-se representar a informação em um byte de estado utilizando o seguinte campo de bits:
Exemplo 5.11
struct status_type {
	unsigned delta_cts	: 1;
	unsigned delta_dsr 	: 1;
	unsigned tr_edge 	: 1;
	unsigned delta_rec 	: 1;
	unsigned cts 	: 1;
	unsigned dsr 	: 1;
	unsigned ring 	: 1;
	unsigned rec_line 	: 1;
} status;
	Para atribuir um valor a um campo de bit, simplesmente utiliza-se a forma para atribuição de outro tipo de elemento de estrutura.
status.ring = 0;
	Não é necessário dar um nome a todo campo de bit. Isto torna fácil alcançar o bit que se deseja acessar, contornando os não usados. Por exemplo, se apenas cts e dtr importam, pode-se declarar a estrutura status_type desta forma:
struct status_type {
	unsigned	: 4;
	unsigned cts	: 1;
	unsigned dsr	: 1;
} status;
	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:
· Não pode obter o endereço de uma variável de campo de bit.
· Variáveis de campo de bit não podem ser organizadas em matrizes.
· Não pode ultrapassar os limites de um inteiro.
· Não pode saber, de máquina para máquina, se os campos estarão dispostos da esquerda para a direita ou da direita para a esquerda.
· Em outras palavras, qualquer código que use campos de bits pode ter algumas dependências da máquina.
	Finalmente, é válido misturar elementos normais de estrutura com elementos de campos de bit. O Exemplo 5.12 define um registro de um empregado que usa apenas um byte para conter três informações: o estado do empregado, se o empregado é assalariado e o número de deduções. Sem o campo de bits, essa variável ocuparia três bytes.
Exemplo 5.12
struct emp {
	struct addr endereco;
	float salario;
	unsigned ativo : 1;	/* ocioso ou ativo */
	unsigned horas : 1;	/* pagamento por horas */
	unsigned deducao : 3;	/* deduções de imposto */
};
Uniões
	Uma união é um tipo de dado que pode ser usado de muitas maneiras diferentes. Por exemplo, uma união pode ser interpretada como sendo um inteiro numa operação e um float ou double em outra. Embora, as uniões possam tomar a aparência de uma estrutura, elas são muito diferentes.
	Uma união pode conter um grupo de muitos tipos de dados, todos eles compartilhando a 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:
union [nome_union] {
		tipo var1;
		tipo var2;
		…
		tipo varN;
} [nome_var];
	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:
Exemplo 5.13
union {
	char c;
	int i;
	double d;
	float f; 
} data;
	Na declaração acima, o nome_union não é utilizado pois esta união será utilizada pela variável data. Para utilizar esta união na definição de outras variáveis tem-se que declará-las juntas com a definição da união. No caso de um programa que utilize esta união em várias partes do programa a linguagem C permite a criação de rótulos de estruturas (nome_union).
Exemplo 5.14
union tipos {
	char c;
	int i;
	double d;
	float f; 
} data;
	Como mostra o Exemplo 5.14, foi declarada a variável data como sendo uma união do tipo tipos. Quando rotula-se a união pode-se omitir a declaração das variáveis, como é mostrado no Exemplo 5.15:
Exemplo 5.15
union tipos {
	char c;
	int i;
	double d;
	float f; 
};
	Para usar esta união em outras declarações deve-se especificar desta forma:
union tipos data1, data2;
	As estruturas seguem o padrão do escopo de variáveis, isto é, se a declaração estiver contida 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.
	Para acessar um campo específico de uma union utiliza-se o operador . (ponto). Pode-se declarar estruturas dentro de uniões.
Exemplo 5.16
struct so_int {
	int i1,i2;
};
struct so_float {
	float f1,f2;
};
union {
	struct so_int i;
	struct so_float f;
} teste;
int main() {
 teste.i.i1 = 2;
 teste.i.i2 = 3;
 printf(“i1 = %-3d i2 = %-3d\n”,teste.i.i1,teste.i.i2);
 teste.f.f1 = 2.5;
 teste.f.f2 = 3.5;
 printf(“f1 = %.1f f2 = %.1f\n”,teste.f.f1,teste.f.f2);
 return 0;
}
Sizeof()
	Com uso de estruturas, uniões e enumerações pode-se utilizá-las para a criação de variáveis de diferentes tamanhos e que o tamanho real dessas variáveis pode mudar de máquina para máquina. O operador unário sizeof() calcula o tamanho de qualquer variável ou tipo e pode ajudar a eliminar códigos dependentes da máquina de seus programas.
Exemplo 5.17
union tipos {
 char c;
 int i;
 double d;
 float f; 
} data;
	O sizeof(data) é 8. No tempo de execução, não importa o que a união data está realmente guardando. Tudo o que importa é o tamanho da maior variável que pode ser armazenada porque a união tem de ser do tamanho do seu maior elemento.
Typedef
	A linguagem C permite que defina-se explicitamente novos nomes aos tipos de dados, utilizando a palavra-chave typedef. Não há criação de uma nova variável, mas sim, definindo-se um novo nome para um tipo já existente. Serve para uma boa documentação ou até tornar os programas dependentes de máquina um pouco mais portáteis. A forma geral de um comando typedef é
typedef tipo nome;
	Por exemplo, poderia ser criado um novo nome para char utilizando
typedef char boolean;
	Esse comando diz ao compilador para reconhecer boolean como outro nome para char. Assim, para se criar uma variável char, usando boolean
boolean ok;
	Também é válida a redefinição, isto é, utilizar um novo nome para um nome atribuído a um dado previamente estabelecido.
Exemplo 5.18
#include <stdio.h>
typedef char boolean;
typedef boolean bool;
int main() {
 boolean a;
 bool b;
 a = 1;
 b = 2;
 printf("%d%d",a,b);
 return 0;
}
	A declaração typedef é usado também para definir tipos estruturados (struct e union) para facilitar a nomenclatura dos tipos na declaração de variáveis.
Exemplo 5.19
typedef struct conta {
	char nome[30];
	int idade;
	int codigo;
	float saldo; 
} cad_conta;
int main() {
 cad_conta *ptr;
 ptr = &conta;
 ptr->idade = 23;
 ptr->codigo = 1000;
 return 0;
}
ou
struct conta {
	char nome[30];
	int idade;
	int codigo;
	float saldo; 
};
typedef struct conta cad_conta;
int main() {
 cad_conta *ptr;
 ptr = &conta;
 ptr->idade = 23;
 ptr->codigo = 1000;
 return 0;
}
Exercícios
1. Faça um programa que leia os dados de 10 clientes de um banco e após leia 100 conjuntos de 3 valores:
· código de operação - 0 depósito, 1 - retirada,
· valor da operação 
· código do cliente.
	Realize as movimentações nas contas correspondentes e ao final escreva o nome e saldo de cada cliente.
2. Faça um programa de cadastro de clientes que contenham as seguintes opções: incluir, alteração, excluir e consultar por código ou por nome. O cadastro deve ser da seguinte forma:
· nome (30 caracteres);
· código (0 a 255);
· idade(char);
Alocação Dinâmica
	Programas consistem em duas coisas: algoritmos e estruturas de dados. Um bom programa é uma combinação de ambos. A escolha e a implementação de uma estrutura de dados são tão importantes quanto as rotinas que manipulam os dados. 
	Para a manipulação de dados é utilizado mecanismos que auxiliam tanto na forma de como é armazenado ou recuperado. Existem vários mecanismos que realizam este tipo de processamento. Abaixo estão listados alguns mecanismos básicos:
· 
· Listas 
· Pilhas
· Filas
· Árvores 
	Cada um destes mecanismos pode ter variações de acordo com a política de processamento (armazenamento/recuperação). Neste capítulo será abordado com mais ênfase as listas encadeadas, porque serão como base para a construção dos demais.
Mapa de memória
	Quando um programa em C é executado, quatro regiões de memória são criadas: código do programa, variáveis globais, heap e pilha, como ilustrado Figura 6.1. A região da pilha é a porção de 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 mais alto para o mais baixo. O heap é a região utilizada para uso do programador através das funções de alocação dinâmica. Como o heap cresce de baixo para cima (endereço mais baixo par o mais alto), pode 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
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 antigos devolvem ponteiros para char. Nesse caso, deve-se usar um cast quando atribuir a ponteiros de tipos diferentes.
malloc()
	Esta função devolve um ponteiro para o primeiro byte de uma região de memória de tamanho size que foi alocada do heap. No caso em que não houver memória suficiente, a função devolve um ponteiro nulo.
Cuidado! Ao usar um ponteiro nulo, pode causar uma falha no programa.
Sintaxe.:
void *malloc(size_t size);
	Onde size_t pode ser considerado um inteiro sem sinal e size é o número de bytes de memória que se quer alocar. Essa função devolve um ponteiro void, como mostra a sintaxe, portanto pode-se atribuir a qualquer tipo de ponteiro.
	Para assegurar a portabilidade de um programa que utilize a alocação dinâmica, faz-se necessário a utilização da função sizeof().
Exemplo 6.1: 
/* Esta função aloca memória suficiente para conter uma 
 estrutura do tipo addr */
struct addr { 
	char nome[40];
	char rua[40];
	char cidade[40];
	char estado[3];
	char cep[10];
};
struct addr *get_struct(void) {
 struct addr *p;
 if ((p=(struct addr*)malloc(sizeof(addr)))==NULL)
 {
 printf(“ erro de alocação - abortando”);
 exit(1);
 }
 return p;
}
O fragmento do código mostra a alocação de 1000 bytes de memória.
char *p;
p = (char*)malloc(1000);
No fragmento abaixo é alocado memória suficiente para 50 inteiros.
int *p;
p = (int*)malloc(50 * sizeof(int));
	O compilador deve conhecer duas informações sobre qualquer ponteiro: o endereço da 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 que o valor retornado por malloc() é do tipo ponteiro para struct addr.
p=(struct addr*) malloc(sizeof(addr))
	Este tipo de conversão deve ser realizado em todas as funções de alocação como calloc(), realloc() e malloc().
calloc()
	Esta função devolve um ponteiro para o primeiro byte de uma região de memória de tamanho size * num que foi alocada do heap. No caso em que não houver memória suficiente, a função devolve um ponteiro nulo.
Sintaxe.:
void *calloc(size_t num, size_t size);
	Onde size_t pode ser considerado um inteiro sem sinal e size é o número de bytes de memória que se quer alocar. Essa função devolve um ponteiro void, como mostra a sintaxe, portanto pode-se atribuir a qualquer tipo de ponteiro.
	Para assegurar a portabilidade de um programa que utilize a alocação dinâmica, faz-se necessário a utilização da função sizeof().
	A diferença entre calloc() e malloc() é que a primeira aloca a memória e inicializa-a com zeros.
Exemplo 6.2
/* Esta função aloca memória suficiente para conter um 
 vetor de 100 elementos */
#include <stdlib.h>
#include <stdio.h>
float *get_mem() {
 float *p;
 p=(float*)calloc(100, sizeof(float));
 if (!p) {
 printf(“ erro de alocação - abortando”);
 exit(1);
 }
 return p;
}
No fragmento abaixo é alocado memória suficiente para 50 inteiros.
int *p;
p = (int*)calloc(50,sizeof(int));
free()
	Esta função devolve ao heap a memória apontada por ptr, tornando a memória disponível para alocação futura.
	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 provavelmente destruirá o mecanismo de gerenciamento de memória e provocará uma quebra do sistema.
Exemplo 6.3
#include <string.h>
#include <stdio.h>
#include <alloc.h>
int main() {
 char *str;
 /* aloca memória para uma string */
 str = (char*)malloc(10);
 /* copia "Hello" para a string */
 strcpy(str, "Hello");
 /* mostra a string */
 printf("String: %s\n", str);
 /* libera a memória */
 free(str);
 return 0;
}
realloc()
	Esta função modifica o tamanho da memória previamente alocada apontada por ptr para aquele especificado por size. O valor de size pode ser maior ou menor que o original. Um ponteiro para o bloco de memória é devolvido porque realloc() pode precisar mover o bloco para aumentar seu tamanho. Se isso ocorre, o conteúdo do bloco antigo é copiado no novo bloco; nenhuma informação é perdida.
Sintaxe.:
void *realloc(void *ptr, size_t size);
	Se ptr é um nulo, realloc() simplesmente aloca size bytes de memória e devolve um ponteiro para a memória alocada. Se size é zero, a memória apontada por ptr é liberada.
	Se não há memória livre suficiente no heap para alocar size bytes, é devolvido um ponteiro nulo e o bloco original é deixado inalterado.
Exemplo 6.4: 
/* Esta programa primeiro

Outros materiais