Beruflich Dokumente
Kultur Dokumente
INTRODUÇÃO
Criada nos laboratórios da Bell nos meados da década de 70, tornou-se conhecida
associadamente ao UNIX, surgido no mesmo laboratório. Seu criador é Dennis M. Ritchie.
A "bíblia" sobre o assunto é o livro "The "C" Programming Language" (Englewood Cliffs,
New Jersey, Prentice Hall, 1978), por ele escrito, juntamente com Brian W. Kernigham.
Apresenta uma restrição que deve ser observada com carinho pelos altos riscos
nela embutidos. O preço da quase absoluta liberdade de programação é, na-
turalmente, a "eterna vigilância". O desconhecimento de tal norma poderá conduzir a
erros não detectados, com conseqüências imprevisíveis.
Pascal Linguagem C
program exemplo1; #include <biblioteca1.h>
uses crt; .... (* bibliotecas *) #include <biblioteca2.h>
var (* declaração das variáveis *) main()
x: integer; {
y: real; /* declaração das variaveis */
int x;
float y;
begin
(* comandos *) /* comandos */
x := 0; x = 0;
y := 1.8; y = 1.8;
end. }
Pascal Linguagem C
program exemplo1; #include <stdio.h>
uses crt; .... (* bibliotecas *) #include “biblioteca2.h”
var (* declaração das variáveis *) /* declaração das variáveis globais */
x: integer; main()
{
procedure … /* procedimentos */ /* declaração das variaveis locais */
begin int x;
end; /* comandos */
function …. /* funções */ x = 20;
begin ..........
}
end;
função1(.......)
begin {
(* comandos *)
x := 20; }
............
............ função2(.......)
end. {
A intensa procura de compiladores C fez com que surgissem vários produtos no mercado,
inicialmente de forma independente e não normalizada. Recentemente a ANSI,
organização americana, editou as normas para C de forma que, hoje, a maioria dos
compiladores existentes estão a elas se adaptando. Diferentes entre si, mantem um núcleo
central normalizado, que aumenta ainda mais a portabilidade já referida, desde que sejam
evitados os comandos, instruções e construções específicos, tão somente, do compilador
em uso. Ao longo desta apostila utilizaremos conceitos e critérios correntes e, quando
particularizarmos, usaremos o Turbo C, produto e marca registrada da BORLAND
INTERNATIONAL, INC.
O "C" reservou algumas palavras que não podem ser usadas, a não ser em seu contexto
correto.
Para incluir uma bliblioteca, deve-se usar a diretiva #include, como por exemplo:
#include <stdio.h>
ou
#include “conio.h”
VARIÁVEIS
As variáveis devem ser declaradas antes da primeira referência a elas no corpo do
programa.
Os nomes das variáveis são formados por letras e dígitos, sendo o primeiro caractere,
obrigatoriamente, uma letra. O símbolo _(sublinhado) é considerado uma letra, podendo
portanto ser também utilizado, inclusive no começo do nome. Contrariamente, $ pode ser
usado, mas não no início.
Como as letras são consideradas pelo valor de seu código numérico, as maiúsculas e
minúsculas não são equivalentes. É comum o uso das letras minúsculas para os nomes
das variáveis, enquanto as maiúsculas são usadas como constantes simbólicas,
resultantes do uso da instrução de pré-compilação #define.
Os nomes externos, tais como os nomes de funções ou de variáveis externas, devem ter
até oito caracteres.
Tipos e tamanhos
int - ocupa 16 bits, variando seu valor de -32768 a 32767. Normalmente reflete o inteiro
natural da máquina em uso.
float - ocupa 32 bits, representando o valor em ponto flutuante de precisão simples. Seu
valor varia de 3.4E-38 a 3.4E+38 no PC.
void - sem valor. Usada para identificar as funções que não retornam valor algum.
unsigned - ocupa dois bites, representando o inteiro sem o sinal, com o valor variando, no
PC, de 0 a +65535.
short e long - são qualificadores que se referem ao tamanho dos inteiros. No PC o tipo
short é igual ao tipo int. O tipo long, ocupando 32 bits, equivale ao dobro de int, variando o
seu valor de -2E+9 a +2E+9.
Declaração de variáveis
char c;
int maior, menor, passo;
int maior;
int menor;
int passo;
int i = 0;
float x = 3.14;
1) O nome da variável é apenas uma referência e portanto não ocupa espaço na memória.
Desta forma, procure sempre dar nome significativo às variáveis para tornar o programa
mais fácil de ser entendido e modificado no futuro.
float x, y, z;
y = x - z;
main() main()
{ int x; { int x;
x = 0; int y; /* correto */
int y; /* ERRADO */ x = 0;
y = 20; y = 20;
} }
Typedef
Um tipo complexo de variável pode ainda ser renomeado, o que será crescentemente útil.
Para fazê-lo emprega-se a palavra chave typedef. Por exemplo:
#include <stdio.h>
main()
{
typedef unsigned char uchar;
uchar alpha=224, beta=225;
printf("%c %c", alpha, beta);
}
typedef não cria um novo tipo de variável, apenas renomeia, permitindo utilizar nomes
menores (uchar, no exemplo), para identificar as variáveis complexas correspondentes
(alpha e beta, que são na verdade do tipo unsigned char).
Entende-se como escopo de uma variável os limites dentro dos quais tem seu valor
disponível.
As variáveis externas, que são definidas fora de qualquer função, são também
chamadas variáveis globais e têm por duração a do programa que as criou, podendo ser
referidas por quaisquer das funções que o compõe.
Como são accessíveis globalmente, as variáveis externas provêm uma forma alternativa
de passagem de argumentos e retorno de valores para funções. O único incoveniente é
que todas as funções devem referencia-las pelo mesmo nome, tirando a liberdade dos
programadores em definir o nome das variáveis internas às funções.
Regras de limites
As fronteiras dentro das quais uma variável é reconhecida são as partes do programa onde
a variável pode ser referida sem perda de valor.
Variáveis automáticas
Existem enquanto durar a função, e retêm o seu valor para futura referência, quando a
função for reativada.
no próprio corpo do programa, permitindo dai em diante o uso do nome em vez do valor.
- Constantes hexadecimais:
0x123
0Xfad4
0XDAB2
As constantes do tipo hexadecimal pode ser seguida pelas letras l ou L para identificá-las
como do tipo long.
- Constantes de caractere:
- Constante "string"
Ex: main()
{ /* Este programa tem por objetivo mostrar o uso dos comentários */
int x,y; /* variáveis inteiras */
float w; /* variável real */
x = x + 1; /* incrementa x */
}
OPERADORES
Operadores são aqueles elementos da linguagem que, colocados junto aos respectivos
operandos, forçam o compilador a executar determinadas operações matemáticas, lógicas,
de atribuição ou especiais.
Operador de atribuição
nome_da_variável = expressão;
Exemplos:
x = 10;
y = x + 3;
z = y * 2;
A regra básica para a convesão de tipos em uma atrubuição é simples: o valor do lado
direito (o lado da expressão) é convertido para o tipo da variável do lado esquerdo (a
variável de destino).
Deve ser tomado o devido cuidado para a possível perda de valores quando efetuada a
conversão.
Por exemplo: Na conversão de float (real) para int (inteiro), além de perder as casas
decimais, os valores podem ser truncados para os limites de -32768 a 32767.
Observação:
em C: Algoritmo:
Em C, quando x ultrapassar o valor de 32767, o seu valor será -32768, o que acarretará
um "looping" (execução sem fim) do comando for (para).
explicação:
a variável do tipo int ocupa 2 bytes, ou seja 16 bits. Um bit para armazenar o sinal (0
positivo e 1 negativo) e 15bits que possibilitam armazenar valores inteiros entre 0 a 32767
Atribuições múltiplas:
x = y = z = w = 0;
Observação:
A linguagem C, diferentemente das outras, permite que uma variável no lado direito de
uma expressão tenha o seu valor alterado, com no exemplo abaixo:
x = (y = 3) + (z = 7);
Operadores aritméticos
+ soma
- subtração ou, como prefixo, invertendo o sinal do operando.
* multiplicação
/ divisão
% módulo (resto da divisão)
-- decremento
++ incremento
++ e - - Mais alta
- (menos unário, como prefixo)
* / %
+ - Mais baixa
Ex:
++x; (pre-incremento)
x++; (pos-incremento)
--y; (pre-decremento)
y--; (pos-decremento)
Exemplo de pre-incremento:
x=10;
y = ++x; (incrementa e retorna) após a execução x e y terão o valor 11,
pois o valor de será incrementado antes de ser atribuído à y.
Exemplo de pos-incremento:
x=10;
y = x++; (retorna e incrementa) após a execução y valerá 10, enquanto
que x terá o valor 11, pois o valor de x será primeiro atribuído a
y e depois incrementado de uma unidade.
exemplo:
void main(void)
{ int x,y,z;
x = 10;
y = ++x;
z = x ++;
prinft(" x = %d y = %d e z = %d",x,y,z);
return(0);
}
Em:
x= 2+y*3; primeiro se efetuada a multiplicação de Y por 3 para depois
somar 2 ao resultado.
Os parênteses também podem ser utilizados para alterar a ordem natural de avaliação e
execução de uma expressão aritmética ou lógica.
Operadores relacionais
Operadores lógicos
Tabela Verdade:
p q p && q p || q !p
0 0 0 0 1
0 1 0 1 1
1 0 0 1 0
1 1 1 1 0
! mais alta
> >= < <=
== !=
&&
|| mais baixa
Por exemplo:
Observação:
x *= y + 1; corresponde a x = x * ( y + 1 );
e não a x = x * y + 1;
PRINTF
A cadeia de controle determina de que forma a saída será formatada, conforme a tabela
abaixo:
código formato
%d decimal inteiro
%c caractere
%s cadeia (string) de caracteres
%f numérico, ponto flutuante, expresso em decimal
%e numérico, ponto flutuante, expresso em exponencial
%g como %f ou %e, o que for mais expressivo
%u inteiro decimal, sem sinal
%p ponteiro
%x inteiro hexadecimal, sem sinal
Exemplo:
main()
{
int x , y;
float z;
x = 1; y = 2; z = 1.75;
printf(Variável X = %d Variável Y = %d e Variável Z = %f", x,y,z);
}
Para evitar que variáveis reais sejam exibidas com um número excessivo de casas
decimais, podemos ainda utilizar algumas especificações de formato:
# (tralha) garante que haverá um ponto decimal, mesmo que não haja digitos decimais.
Exemplos:
x = 1.75;
Exemplo:
int matr;
float média;
char nome[20];
printf("Informe a matricula e o nome de um aluno \n").
scanf ("%d %s", &matr, &nome);
.....
Observação:
Para ler uma "string" , a função scanf() lê os caracteres até que seja encontrado um
caractere de espaço em branco. Desta forma para ler "strings" com mais de uma nome,
devemos utilizar a função gets(), que lê os caracteres até que seja pressionado "enter"
(retorno e carro) como no exemplo abaixo:
printf ("Informe o nome de um aluno \n"); printf ("Informe o nome de um aluno \n");
printf ("Nome do aluno = %s", nome); printf ("Nome do aluno = %s", nome);
while ( expressão )
comando;
main()
{ int x;
x=1;
while (x < 11 )
{ printf(%d \n, x);
x = x + 1;
)
}
Comando FOR
main() main()
{ int x; { int x;
for (x=1; x < 11; x=x+1 ) for (x=1; x < 11; x++)
printf(%d \n, x); printf(%d \n, x);
} }
main() main()
{ {
for (exp1; exp2; exp3 ) exp1;
comando; while (exp2)
} comando;
exp3;
}
do while
do
comando;
while (expressão);
main()
{ int x;
x = 1;
do
{ printf(%d \n, x);
x = x + 1;
} while (x < 11);
}
BREAK
CONTINUE
O comando continue faz com que em um comando while, for ou do, o restante do
módulo não seja executado e força uma iteracão.
Toda vez que (x % 2) for verdadeiro, ou seja, houver resto, continue força o retorno ao for
sem passar pelo printf.
No retorno ao for este executa o incremento antes do teste condicional, o que não se
daria se o comando fosse while ou do-while quando o retorno seria diretamente ao teste.
Os comandos de fluxo em uma linguagem são aqueles que modificam a ordem natural de
execução de um programa.
Comandos de bloco
Para entendermos o que se segue é bom fixar o conceito de bloco.
Os símbolos { (abre chave) e } ( fecha chave ) são usados para agrupar declarações de
dados ou comandos compostos em blocos, de tal maneira que estes sejam equivalentes a
um só comando.
if - else
O comando if - else é usado para desvios condicionais. Sua sintaxe é :
if ( expressão )
comando-1;
else
comando-2;
O else é opcional, podendo ou não ser usado. Sua execução se inicia pela resolução da
expressão. Se a mesma for verdadeira, (o seu resultado for diferente de zero), o
comando-1 é executado; caso contrário, o comando-2 é desenvolvido.
No bloco
if ( n < 0)
if ( a > b )
z=a;
else
z= b;
if ( n < 0 )
{
if ( a > b )
z=a;
}
else
z=b;
A construção
if (expressão)
comando1;
else
if (expressão)
comando2;
else
if (expressão)
comando3;
else
comando4;
Atenção :
if (a == b)
x = 10; /* atribuirá 10 a x se a e b forem iguais */
if (a = b)
x = 10; /* atribuirá 10 a x se b for maior que zero */
if (expressão-1)
expressão-2;
else
expressão-3;
e1 ? e2 : e3
if ( a > b )
z = 10;
else
z = 20;
a > b ? z = 10 : z = 20;
EXEMPLOS:
A primeira é clássica:
y = 0;
if (x == 5)
y = 1;
y = (x == 5) ? 1 : 0;
y = x == 5;
Switch é uma forma especial de construção if - else, onde o teste ao invés de ocorrer
sobre expressões, é feito sobre um conjunto de valores constantes.
switch ( expressão )
{
case constante-1:
comando;
case constante-2:
comando;
.
.
default:
comando;
}
O case pode ser vazio. Tal recurso é utilizado quando o comando para um grupo de
constantes é o mesmo. Não utilizando o break, que será visto a seguir, obtêm-se o
resultado desejado explicitando todas as constantes e condições e escrevendo a ação a
tomar uma só vez.
Caso uma opção case seja aceita, após a execução do comando associado, os cases
posteriores continuam a ser testados. Para que isto não ocorra um comando break
deve ser colocado ao final da expressão contida em cada case.
main()
{ int x;
clrscr();
for (x=0;t<7;x++)
switch (x)
{ case 1: printf(" Este ");
break;
case 2: printf(" exemplo ");
case 3:
case 4: printf("\n mostra ");
printf(" como o switch ");
break;
case 5:
case 6:
case 7: printf( "\n funciona ");
break;
}
}
main()
{
float notas[50]; /*50 elementos float */
char code[12]; /* 12 elementos char */
...............
Como as variáveis escalares, as matrizes também podem ser inicializadas. Para que tal
possa ser feito a matriz deve ser externa ou estática. Quando da definição de uma matriz
especificada como externa ou estática, seus elementos são inicializados em 0 (zero), por
default. Para outros valores, a inicialização pode ser:
int dias[12]={31,28,31,30,31,30,31,31,30,31,30,31};
main()
{ int x, dias[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
for ( índice = 0; x < 12; x++ )
printf (" Mês %d tem %d dias. \n", x+1, dias[x]);
}
A saída será:
Nota:
É bom lembrar que o "índice+1" contido em printf é necessário. De outra forma, o primeiro
mês será 0 e não 1, pois o primeiro elemento de um vetor é o de número 0 e não 1.
main()
{
int x, pares[50];
for (x = 0; x < 50; x++)
pares [x] = 2 * x;
...........
}
As matrizes de dimensão única são essencialmente listas de dados do mesmo tipo e seu
uso mais comum é na criação de "strings" de caracteres.
Toda "string" de caracteres tem como seu último elemento \0, elemento terminal, o
que obriga a declaração, sempre, de mais um elemento do que os que serão ali
colocados.
"C" não controla o fim das matrizes, sendo esta tarefa estritamente do programador que,
para isto, deverá montar suas próprias rotinas, principalmente na atribuição. Ao
ultrapassar os limites de uma matriz, você estará gravando sobre os campos próximos,
talvez sobre o próprio programa, o que poderá ocasionar erros imprevisíveis.
Trata-se de uma matriz de inteiros, com duas dimensões, uma delas contendo 2
elementos e outra 5 ( 2 linhas e 5 colunas).
Certamente não há necessidade de montar uma visão espacial como a do exemplo. Ela aí
está para facilitar o entendimento. Fica claro que o elemento quads [5] [2] tem o valor de
25.
Esta inicialização pode ser feita sem especificar o tamanho. Em "C", se não for
especificado o tamanho numa operação de inicialização, este será criado grande o
suficiente para conter os valores apresentados:
O uso das técnicas citadas permitiu dividir entre vários profissionais a tarefa de programar
um problema complexo, cada um realizando uma parte perfeitamente delimitada.
Um programa em "C" é , na verdade, um conjunto de definições de funções isoladas. A
comunicação entre as diferentes funções se dá, normalmente, através da passagem de
parâmetros entre a função chamada e a função que a ativou.
Podem ser distribuídas em qualquer ordem ao longo do programa fonte e, mesmo este,
pode ser dividido em vários programas fonte.
Observação:
1) Não se pode omitir o tipo na declaração dos parâmetros, nem se forem do mesmo tipo.
2) Pode-se omitir o comando return ao término de uma função. Caso isto ocorra, quando
for encontrado o símbolo } , que encerra a sua execução, nenhum valor será retornado à
função ou ao programa chamador.
#include <stdio.h>
main ()
{ int x;
x = 5;
printf("\n Dobro de %d = %d",x,dobro(x));
}
Passagem de parâmetro por valor: o valor do argumento é copiado para uma variável
definida na função. Neste caso, a função não pode alterar o valor do argumento no módulo
chamador.
OBS: Quando um nome de um vetor (matriz) aparece como argumento de uma função,
o endereço do início do vetor é passada e os elementos não são copiados. Assim sendo,
a função pode alterar diretamente os valores do vetor.
#include <stdio.h>
main()
{ int x=10, y=30;
printf("Antes: x = %d y = %d\n", x, y);
troca(&x, &y);
printf("Depois: x = %d y = %d\n", x, y);
}
troca(int *a, int *b)
{ int temp;
temp = *a;
*a = *b;
*b = temp;
}
& - quando antecede o nome de uma variável fornece o endereço daquela variável.
por exemplo:
main()
{ int x, y , *p;
x = 10; /* atribui 10 a x */
p = &x; /* atribui o endereço da variável x ao ponteiro p */
printf (" x = %d *p = %d",x,*p);
}
No exemplo acima,
*p = *p + 1; equivale a x = x + 1;
*p + 1 é diferente de *p++
Exemplo1:
main()
{ int *p, x=1, y =5;
Exemplo2:
main()
{
int *p, x[3]={10,20.30};
clrscr(); clrscr();
p = x; p = x;
for(i=0; i<6; i++) for(i=0; i<6; i++)
printf("\n posicao %d = %d”,i,*(p + i)); printf("\n posicao %d = %d”,i,x[i]));
} }
p = x; p recebe o endereço de x
p = &x; p recebe o endereço de x.
p = &x[0]; p recebe o endereço de x.
p = &x; p = &x;
for(i=0; i<6; i++) for(i=0; i<6; i++)
printf("\n %d",x[i]); printf("\n %d",*(p + i));
Exemplo4:
p = &x;
printf("\n %d", *(p + 3));
printf("\n %d", *p + 3);
}
Observações:
1) p aponta para o primeiro elemento do vetor (célula de número 0)
2) o primeiro printf exibirá 9, ou seja, o valor da célula de número 3.
3) o segundo printf exibirá 5, ou seja, o valore da célula de número 0 mais 3.
Exemplo4:
main()
{
int i,*p,x[5];
clrscr();
for(i=0;i<5;i++)
x[i] = i * 3;
p = &x;
printf("\n %d ",*(p+4));
printf("\n %d ",*p+4);
}
Observações:
1) p aponta para o primeiro elemento do vetor (célula de número 0)
2) o primeiro printf exibirá 12, ou seja, o valor da célula de número 4.
3) o segundo printf exibirá 4, ou seja, o valore da célula de número 0 mais 4.
Atenção:
Ao trabalhar com ponteiros devemos observar atentamente o endereçamento dos
mesmos para não invadir endereços de memória não alocados, ou até mesmo
comprometer o conteúdo de outras variáveis, provocando erros inesperados.
Quando uma função chama a si própria (recursividade) ela cria um novo conjunto de
variáveis automáticas que são independentes das do conjunto anterior, que fez a
chamada. Este processo, se repetido muitas vezes, tende a consumir rapidamente a
memória disponível na pilha (stack área).
Portando, deve-se tomar muito cuidado para não estourar essa área.
Geralmente é usada para agregar neste único nome algumas variáveis que se relacionam
em termos lógicos, tais como uma tupla de uma tabela em banco de dados.
typedef struct
{ int matricula;
int cpf;
char nome[40];
} tipoAluno;
ATENÇÂO! A expressão *p.cpf, que equivale a *(p.cpf), tem significado muito diferente
de (*p).cpf .
A expressão p->cpf é uma abreviatura muito útil para a expressão (*p).cpf :
Exemplo:
main()
{
typedef struct /* definindo um novo tipo em C */
{ char nome[30];
int matricula;
long int cpf;
} tipoAluno;
getch();
tipoAgenda agenda;
strcpy(agenda.nome,"Ana Maria");
agenda.telef = 2223040;
agenda.cpf = 123456;
/* *************************************************************************
Exemplo 1.a – Idem ao anterior com a utilização de ponteiro
************************************************************************ */
#include <stdio.h>
main()
{ typedef struct
{ char nome[30];
long int telef;
long int cpf;
} tipoAgenda;
strcpy(p->nome,"Ana Maria");
p->telef = 2223040;
p->cpf = 123456;
#include <stdio.h>
main()
{
typedef struct
{ char nome[30];
long int telef;
long int cpf;
} tipoAgenda;
tipoAgenda agenda;
clrscr();
puts("\n Informe o nome: ");
gets(agenda.nome);
puts("\n Informe o telefone: ");
scanf("%lu",&agenda.telef);
puts("\n Informe o cpf: ");
scanf("%lu",&agenda.cpf);
clrscr();
clrscr();
Código Executável
(Assembler)
Área das
Variáveis Globais
•Stack (pilha) – Área onde são alocados os
parâmetros e as variáveis locais das
Stack funções.
(pilha)
Quando chamamos uma função, é alocada
memória para os seus parâmetros e
variáveis locais no topo da pilha.
Ao término da função estes são
Área Livre desempilhados (espaço é liberado).
Exemplo:
#include <stdio.h>
main()
{ typedef struct
{ int matricula;
char nome[30];
int cpf; } tipoAluno;
tipoAluno *p;
Função free()
No exemplo acima, para liberar a memória alocada no heap, apontada pelo ponteiro p,
basta utilizar o seguinte comando:
free(p);
main()
{
typedef struct
{ int dia;
int mes;
int ano;
} tipoData;
typedef struct
{ int matr;
int cpf;
tipoData dtnasc;
} tipoAluno;
tipoAluno x, *p;
clrscr();
if (p != NULL)
{ printf("\n Ponteiro p aponta para uma estrutura de %d bytes",sizeof(tipoAluno));
free(p);
}
if (p == NULL)
printf("\n Espaco de memoria apontado por p foi liberado....");
getch();
main()
{
typedef struct
{ int matr;
int cpf;
float cr;
} tipoAluno;
p[0]->matr = 123;
p[0]->cpf = 456;
p[0]->cr = 8.5;
clrscr();
printf("matricula: %d cpf: %d cr: %5.2f \n", p[0]->matr, p[0]->cpf, p[0]->cr);
for (i=0;i<10;i++)
free(p[i]);
if (p[0] == NULL)
printf("\n Espaco liberado....");
getch();
Vetores, ou arrays, são estruturas de dados lineares e estáticas, isto é, são compostas por
um número fixo (finito) de elementos de um determinado tipo de dados.
O tempo de acesso aos elementos de um vetor é muito rápido, sendo considerado
constante: os elementos são acessados pelo seu índice no vetor.
Porém, a remoção de elementos pode ser custosa se não for desejável que haja espaços
"vazios" no meio do vetor, pois nesse caso é necessário "arrastar" de uma posição todos
os elementos depois do elemento removido.
Essa é uma estrutura muito recomendada para casos em que os dados armazenados não
mudarão, ou pouco mudarão, através do tempo.
Lista
A Lista é uma estrutura de dados linear, composta por uma sequência de nós ou nodos.
Existem duas formas de representar as listas:
- Alocação sequencial: os nós ocupam posições sequenciais contíguas
- Alocação dinâmica (encadeada): os nós (celulas) apontam para o próximo
elemento da lista.
Para "ter" uma lista dinâmica (ligada), devemos guardar seu primeiro elemento,
Lista simplesmente encadeada (LSE): cada nó da lista ponta para o próximo nó.
A LSE só pode ser percorrida em um único sentido
Lista Circular: O primeiro nó aponta para o último e o último aponta para o primeiro.
Pilha
.
É uma lista linear em que todas as inserções, as remoções e os acessos são feitos numa
só extremidade, chamada topo.
São baseadas no princípio LIFO (last in, first out) ou UEPS (Último a entrar, primeiro a sair)
onde os dados que foram inseridos por último na pilha serão os primeiros a serem
removidos.
Existem duas funções que se aplicam a todas as pilhas: PUSH, que insere um dado no
topo da pilha, e POP, que remove o item no topo da pilha.
Exemplos de pilhas são as pilhas de pratos, pilhas de livros, etc.
13
19
14
10
Pop x
Pop y
Pop z
Push y 14
Push x 13
Push z 19
10
Resposta:
Qual será o estado final das pilhas abaixo, após executar as seguintes instruções:
4
3
2
1
A B C
Push(B,pop(A))
Push(C,pop(A))
Push(B,pop(A))
Push(C,pop(A))
Push(A,pop(B))
Push(C,pop(B))
Push(C,pop(A))
2
4
1
3
A B C
Resposta:
A) FILO
B) LIFO
C) SIFO
D) LILO
E) FIFO
Resposta: E
A) LIFO
B) FILO
C) MIFO
D) LILO
E) FIFO
Resposta: A
inicio
Y F C
INC “X”
INC “T”
DEL
DEL
INC “R”
INC “J” Resposta:
DEL C–X–T–R-J
Deque de entrada restrita: A remoção poder ser feita por qualquer extremidade, porém as
inclusões podem ser feitas apenas por uma.
Deque de saída restrita: A inclusão poder ser feita por qualquer extremidade, porém as
remoções podem ser feitas apenas por uma.
A grande diferença da lista para as outras estruturas de dados, é que as listas não
possuem critério de inclusão e remoção de dados.
Uma lista encadeada tem necessariamente uma variável ponteiro apontando para o seu
primeiro elemento. Essa variável será utilizada sempre, mesmo que a lista esteja vazia, e
deverá apontar sempre para o início da lista (primeiro elemento). Caso esta primeira
variável não seja atualizada corretamente (no caso da inclusão de um elemento na
primeira posição), a lista poderá se perder na memória e não ser mais acessível.
Uma vez que o primeiro elemento será uma célula, e cada célula é uma estrutura, então a
variável que apontará para o início da lista será um ponteiro de estrutura.
inicio = NULL;
Para uma lista vazia (sem células), a variável inicio possui valor NULL:
// reserva de memória para nova célula com endereço de memória alocada armazenado
em aux:
aux = malloc (sizeof (tipoCelula));
Temos então:
e precisamos atualizar os ponteiros para que a lista receba seu primeiro elemento. Sendo
assim:
1) aux->proximaCelula = NULL; // atribui NULL ao campo proximacelula da célula
apontada por aux
2) inicio = aux; // copia o endereco de aux em inicio
Para inserir dados nesta primeira célula, podemos utilizar o ponteiro aux, que permanece
apontando para a célula.
aux->dados = 10; /*atribui o valor 10 ao campo dados da célula pontada por aux
O programa a seguir cria uma lista com tres nós, conforme exibida a abaixo
inicio
matricula proximo
11 2 3
main( )
{ typedef struct no
{int matricula;
stuct no *proximo;
} tipoNo;
#include "stdio.h"
main()
{ typedef struct no
{int matricula;
struct no *proximo;
}tipoNo;
}
/* exibe a lista */
atual = inicio;
while (atual != NULL)
{ printf("\n matricula = %d ",atual->matricula);
atual=atual->proximo;
}
getch();
Inicio
matricula prox
11 2 3 4 5
clrscr();
atual = inicio;
while (atual != NULL)
{ printf("\n %d ",atual->valor);
atual=atual->proximo;
}
getch();
topo
matricula prox
#include "stdio.h" a
main()
3 2 1
{
typedef struct no
{int matricula;
struct no *proximo;
}tipoNo;
clrscr();
/* inclui primeiro registro na pilha */
novo = malloc(sizeof(tipoNo));
novo->matricula = 1;
novo->proximo = NULL;
topo = novo;
#include "stdio.h"
main()
{
typedef struct no
{int matricula;
struct no *proximo;
}tipoNo;
clrscr();
topo
matricula proximo
15 4 3 2 1
Para percorrer uma lista (fila ou pilha) temos que utilizar uma variável auxiliar (ponteiro
auxiliar).
Inicialmente, aux deverá para o início da fila (ou para o topo, no caso das pilhas).
Depois, aux receberá o endereço contido no campo proximo, ou seja, receberá o
endereço da próxima célula.
Enquanto aux for diferente de NULL, repete-se o processo acima.
Observe o trecho de programa a seguir que exibirá todos os nós da lista.
aux = inicio;
while (aux != NULL)
{ printf("\n matricula = %d ",aux->matricula);
aux = aux->proximo;
}
getchar();
}
Inicio
matricula proximo
11 2 3 4 5
O último nó da lista não apontava para nenhum outro, agora, este deverá apontar para o
novo nó.
Início 10 27
valor Próximo valor Proximo
- se a lista não é vazia, percorrer a lista até a última célula (aux apontará para o último nó)
- alocar memória para o novo nó (malloc)
- atribuir dados aos campos de dados
- atribuir NULL ao campo ponteiro da nova célula incluída
- atribuir ao campo ponteiro da última célula o endereço da nova célula
if (inicio != NULL)
{ aux = inicio;
while(aux->proximo != NULL)
aux = aux->proximo;
novo = malloc(sizeof(tipoNo))
novo->valor = 55;
novo->proximo = NULL;
aux->proximo = novo;
}
Início
10 27 55
valor Proximo valor Proximo Valor Proximo
Início 2 7 3
Dado Prox Dado Prox Dado Prox
Dado prox
17 3
Observe a lista:
Início 2 7 3
Dado Prox Dado Prox Dado Prox
Início 2 7 3
Dado Prox Dado Prox Dado Prox
/* ********************
Exemplo de Pilhas
********************** */
#include "stdio.h"
main()
{
typedef struct no
{int matricula;
struct no *proximo;
}tipoNo;
clrscr();
novo = malloc(sizeof(tipoNo));
novo->matricula = 1;
novo->proximo = NULL;
topo = novo;
atual = topo;
while (atual != NULL)
{ printf("\n matricula = %d ",atual->matricula);
atual=atual->proximo;
}
getch();
#include "stdio.h"
#include <stdlib.h>
main()
{
typedef struct no
{int matricula;
struct no *proximo;
}tipoNo;
clrscr();
atual = topo;
while (atual != NULL)
{ printf("\n matricula = %d ",atual->matricula);
atual=atual->proximo;
}
getch();
As listas duplamente encadeadas são aquelas em que cada nó possui não só o endereço
do nó anterior mas também o endereço do próximo. Observe o gráfico e a declaração
abaixo:
typedef struct no
{
struct no *anterior; // ponteiro da celula anterior
int indice; // campo de dado
char nome[15]; // campo de dado
struct no *proxima; // ponteiro da proxima celula
} tipoNo;
#include <stdio.h>
#define FALSO 0
#define VERDADEIRO 1
#define OK 1
#define ERRO 0
typedef struct no
{ int dado;
struct no *proximo, *anterior;
} tipoNo;
if (*LD == NULL)
{
*LD = novo;
novo -> Anterior = NULL;
novo -> Proximo = NULL;
}
else
{
aux=(*LD)->Anterior;
(*LD)->Anterior=novo;
novo->Proximo=(*LD);
novo->Anterior=aux;
if (aux != NULL)
aux->Proximo=novo;
}
return(OK);
}
/*****
Programa Principal
*****/
main()
{
Nodo_LD *ListaDupla;
inicializa_ld(&ListaDupla);
insere_inicio_ld(&ListaDupla,1);
insere_inicio_ld(&ListaDupla,2);
insere_inicio_ld(&ListaDupla,3);
posiciona_inicio_ld(&ListaDupla);
getch();
}
As filas são listas que possuem um critério especial. As inserções somente podem ser realizadas no final e as
remoções somente no início da estrutura. O critério da fila é conhecido como FIFO (First In First Out) ou
PEPS (primeiro a entrar primeiro a sair). Basta lembrar de uma fila de banco (não pode furar a fila!) onde os
novos clientes sempre se posicionam no final da fila e o caixa atende (retira da fila) os primeiro cliente e
assim sucessivamente.
O ponto de inserção pode ser definido no inicio ou no final, sabendo que as remoções serão sempre na
extremidade oposta.
- variável ponteiro X (auxiliar para a operação de inserção) recebe uma alocação de memória (nova célula)
- cópia dos dados na nova célula
- ponteiro da nova célula recebe o valor do início (apontará para o primeiro elemento)
- início aponta para nova célula (recebe o endereço armazenado em X)
- variável ponteiro X (auxiliar para a operação de exclusão) recebe o valor de início e passa a apontar para o
início da lista
- verificar se a fila não está vazia!
- verificar se o endereço da próxima célula é diferente de 0 (nulo). Essa informação está disponível no
ponteiro de conexão da célula apontada atualmente por X.
- variável X recebe o endereço da próxima célula da fila (este endereço está armazenado na célula
atualmente apontada por X). Repetir isso até que o passo anterior não seja satisfeito (X deverá estar
apontando para o penúltimo elemento da lista)
- variável ponteiro AUX (outra variável auxiliar para exclusão) recebe o endereço da última célula da fila. Este
endereço está armazenado na célula atualmente apontada por X.
- liberar a memória apontada por AUX
- ponteiro de próxima célula do elemento apontado por X recebe 0 (zero).
Pilha
Nas pilhas, a extremidade escolhida é sempre a que será utilizada para inserir novos elementos e remover os
existentes. O critério da pilha é LIFO (Last In First Out) ou UEPS (último a entrar, primeiro a sair).
O TOPO da pilha pode ser no início ou no final da lista, sendo que as operações deverão
acontecer sempre na extremidade (TOPO) escolhida.
Usaremos os comandos PUSH e POP para inserir e retirar dados na pilha, respectivamente.
Observe os exemplos:
13
19
14
10
Pop x
Pop y
Pop z
Push y 14
Push x
Para a operação de inserção, com TOPO no início da lista, são necessários os seguintes passos:
- variável ponteiro X (auxiliar para a operação de inserção) recebe uma alocação de memória (nova célula)
- cópia dos dados na nova célula
- ponteiro da nova célula recebe o valor de TOPO (apontará para o primeiro elemento)
- TOPO aponta para nova célula (recebe o endereço armazenado em X)
Para exclusão:
- variável ponteiro X (auxiliar para a operação de exclusão) recebe o valor de TOPO e passa a apontar para o
início da lista
- verificar se a pilha não está vazia!
- TOPO recebe o endereço da próxima célula da pilha (este endereço está armazenado na célula atualmente
apontada por X).
- liberar a memória apontada por X.
Exercícios
1) Qual será o estado final das pilhas abaixo, após executar as seguintes instruções:
4
3
2
1
A B C
Push(B,pop(A))
Push(C,pop(A))
Push(B,pop(A))
Push(C,pop(A))
Push(A,pop(B))
Push(C,pop(B))
Push(C,pop(A))
2
4
1
3
A B C
Resposta:
typedef struct no
{ int matricula;
struct no *proximo;
} tipoNo;
Pode ser a estrutura de uma fila estática
Pode ser a estrutura de um registro de vetor
Pode ser a estrutura de uma lista duplamente encadeada
Pode ser a estrutura de uma lista encadeada
Pode ser a estrutura de uma pilha estática
Resposta: D
4) Sobre o trecho de programa abaixo, pode-se afirmar que:
Typedef struct no
struct no *anterior ;
int matricula;
struct no *proximo;
} tipoNo;
Pode ser a estrutura de uma fila estática
Pode ser a estrutura de um registro de vetor
Pode ser a estrutura de uma lista duplamente encadeada
Pode ser a estrutura de uma lista encadeada
Pode ser a estrutura de uma pilha estática
Resposta: C
FILO
LIFO
SIFO
LILO
FIFO
Resposta: E
LIFO
FILO
MIFO
LILO
FIFO
Resposta: A
7) Sobre o deque de saída restrita podemos afirmar que:
INI FIM
A G F
INC C
INC D
INC H
DEL
DEL
INC B
Resposta: F – C – D – H - B
11) Considerando a lista abaixo que implementa uma PILHA qual será o último valor a ser excluído desta
lista?
End 00 01 02 03 04 05 06
Valor K W I A H Z B
Link 03 Null 06 05 02 04 01
a) W
b) K
c) A
d) B
e) H
13) Suponha que a tabela deva representar uma lista duplamente encadeada de cores, organizada sobre os
5 elementos de um vetor:
Sabendo-se que a ordem das cores na lista é BEGE – VERDE – AZUL – VERMELHO –
AMARELO, a coluna intitulada cor, na tabela acima, deveria apresentar, de cima para
baixo, o seguinte preenchimento:
a) BEGE- VERMELHO-AMARELO-AZUL-VERDE
b) AZUL-BEGE-VERDE-VERMELHO-AMARELO
c) AMARELO-AZUL-BEGE-VERMELHO-VERDE
d) AZUL-VERMELHO-AMARELO-VERDE-BEGE