 |
Aula
do dia 08 de Maio de 2003 |
|
Estruturas
Você provavelmente já deve ter deparado com um problema de programação, onde você deseja
agrupar um conjunto de tipos de dados não similares sob um único nome. Seu primeiro impulso
seria talvez usar uma matriz.
Como matrizes requerem que todos os seus elementos sejam do mesmo tipo você provavelmente
forçará a resolução do problema selecionando uma matriz para cada tipo de dado. O resultado
tornaria o programa ineficiente na maneira de manejar os dados.
O problema de agrupar dados desiguais em C é resolvido pelo uso de estruturas.
Uma estrutura é uma coleção de uma ou mais variáveis, possivelmente de tipos diferentes,
colocadas juntas sob um único nome. (Estruturas são chamadas "registros" em algumas
linguagens).
O exemplo tradicional de uma estrutura é o registro de uma folha de pagamento: um
funcionário é descrito por um conjunto de atributos tais como nome (uma "string"), o
número do seu departamento (um inteiro), salário (um float), e assim por diante.
Provavelmente, haverão outros funcionários, e você vai querer que o seu programa os guarde
formando uma matriz de estruturas.
O uso de uma matriz de várias dimensões não resolverá o problema, pois todos os elementos
de uma matriz devem ser de um tipo único; você deveria usar várias diferentes matrizes:
uma de caracteres para os nomes, uma float para os salários, uma de inteiros para número
do departamento, e assim por diante. Esta não seria uma forma prática de manejar um grupo
de características que você gostaria que tivessem um único nome: funcionário.
Uma estrutura consiste de um certo número de itens de dados, chamados membros da estrutura,
que não necessitam ser de mesmo tipo, agrupados juntos.
Uma Estrutura Simples
O programa seguinte usa uma simples estrutura contendo dois itens de dados: uma variável
inteira num e uma variável caractere ch.
#include "stdio.h"
#include "conio.h"
main()
{
struct facil // define tipo de dado
{
int num; // inteiro na estrutura
char ch; // caractere na estrutura
};
clrscr();
struct facil xx1; // declara xxl como tipo estrutur
xx1.num=2;
xx1.ch='Z';
printf("xx1.num= %d, xx1.ch= %c\n",xx1.num,xx1.ch);
getch();
}
A saída deste programa será:
xx1.num=2, xx1.ch=Z
Este programa mostra os três principais aspectos de estruturas: definição do tipo struct,
declaração de variáveis estrutura e acesso a seus membros.

Uma estrutura pode conter qualquer número de membros de diferentes tipos; o programa deve avisar o compilador de como é formada uma estrutura particular antes de seu uso. Isto não acontece com, por exemplo, o tipo int que é predefinido, pelo compilador, como sendo sempre de 2 bytes.
No exemplo anterior as seguintes instruções definem o tipo da estrutura:
struct facil
{
int num;
char ch;
};
Estas instruções definem um novo tipo de dado chamado struct facil.
Cada variável deste tipo será composta por dois elementos: uma variável inteira chamada
num, e uma variável caractere chamada ch.
Note que esta instrução não declara qualquer variável, e então não é reservado nenhum
espaço de memória. Somente é mostrado ao compilador como é formado o tipo struct facil.
A palavra struct informa ao compilador que um tipo de dado está sendo declarado e o nome
facil é chamado "etiqueta" e nomeia a estrutura particular que está sendo definida.
Note que a "etiqueta" não é o nome de uma variável, isto é, nós não declaramos uma
variável; é um nome de um tipo.
Os membros da estrutura devem estar entre chaves, e a instrução completa termina por
ponto e vírgula.
Declarando as Variáveis da Estrutura
Em primeiro lugar nós definimos nosso novo tipo de dado. Podemos, então, declarar uma ou
mais variáveis deste tipo. Em nosso programa declaramos a variável xx1 como sendo do tipo
struct facil.
Esta instrução executa uma função similar às declarações de variáveis que já conhecemos como:
float x;
int num;
ela solicita ao compilador a alocação de espaço de memória suficiente para armazenar a
variável xx1 que é do tipo struct facil, neste caso 3 bytes (dois para o inteiro e um para o caractere).
Acessando Membros da Estrutura
Agora que temos criado uma variável do tipo estrutura, precisamos de um meio para
referenciar os seus membros.
Quando usamos uma matriz, podemos acessar um elemento individual através do subscrito:
matriz[7].
Estruturas usam uma maneira de acesso diferente: o "operador ponto" (.), que é também
chamado "operador de associação".
A sintaxe apropriada para referenciar num que é parte da estrutura xx1, é:
xx1.num
O nome da variável que precede o ponto é o nome da estrutura e o nome que o segue é o de
um membro específico da estrutura. As instruções:
xx1.num = 2;
xx1.ch = 'Z'
atribui 2 ao membro num da estrutura xx1 e 'Z' ao membro ch.
O operador ponto é uma poderosa e esclarecedora maneira de especificar membros de uma
estrutura. Por exemplo, a expressão funcionario.salario é mais compreensível que
funcionário[7].
Múltiplas Estruturas de Mesmo Tipo
Do mesmo modo como podemos ter várias variáveis do tipo int em um programa, podemos
também ter qualquer número de variáveis do tipo de uma estrutura predefinida.
Como exemplo, vamos declarar duas variáveis, xx1 e xx2, do tipo struct facil.
#include "stdio.h"
#include "conio.h"
// declara 2 variáveis tipo estrutura facil
main()
{
struct facil //define tipo de dado
{
int num; // inteiro na estrutura
char ch; //caractere na estrutura
};
struct facil xxl;
struct facil xx2;
clrscr();
xxl.num=2;
xxl.ch='Z';
xx2.num=3;
xx2.ch = 'Y';
printf("xxl.num=%d, xxl.ch= %c \n",xxl.num,xxl.ch);
printf("xx2.num=%d, xx2.ch= %c \n",xx2.num,xx2.ch);
getch();
}
Combinando Declarações
Você pode combinar em uma instrução a definição do tipo estrutura e a declaração das
variáveis tipo estrutura.
Como exemplo, o programa anterior será reescrito:
#include "stdio.h"
#include "conio.h"
// combina definicao de tipo estrutura com declaracao de variavel
main()
{
struct facil // define tipo de dado
{
int num; // inteiro na estrutura
char ch; // caractere na estrutura
}xxl,xx2;
clrscr();
xxl.num=2;
xxl.ch='Z';
xx2.num=3;
xx2.ch='Y';
printf("xxl.num=%d, xxl.ch= %c \n",xxl.num,xxl.ch);
printf("xx2.num=%d, xx2.ch= %c \n",xx2.num,xx2.ch);
getch();
}
O efeito é o mesmo, mas o programa torna-se mais compacto, entretanto menos legível.
Definição de Estruturas sem "Etiqueta"
A convenção normal é a de usar a etiqueta da estrutura quando a expectativa é criar várias
variáveis do mesmo tipo estrutura. Se você espera usar uma única declaração de variável do
tipo estrutura, você pode combinar a declaração com a definição da estrutura e omitir a
etiqueta:
struct
{
int num;
char ch;
}xxl,xx2;
Programa Exemplo: Criando Uma Lista de Livros
A partir deste ponto vamos examinar um exemplo um pouco mais realista que envolve a
elaboração de uma lista de livros. A lista de livros compreende uma variedade de
informações como: seu título, autor, editora, número de páginas, número do registro
de biblioteca, seu preço etc.
Nosso objetivo é o de desenvolver um programa que controla um arquivo simples e mostra
um dos mais úteis caminhos de organização de dados em C: uma matriz de estruturas.
Para simplificar o problema, a nossa lista será formada, a princípio, somente pelo título
do livro, representado por uma matriz de caracteres e o número do registro de biblioteca representado por um inteiro. Estas informações são fornecidas pelo usuário através da entrada padrão (teclado) e depois impressas na saída padrão (vídeo).
Na primeira versão a nossa lista será limitada em apenas dois livros.
#include "stdio.h"
#include "conio.h"
#include "stdlib.h"
// guarda e imprime dados de 2 livros
main()
{
struct livro
{
char titulo[30];
int regnum;
};
struct livro livrol;
struct livro livro2;
char numstr[81];
printf("\nLivro 1.\nDigite titulo: ");
gets(livrol.titulo);
printf("Digite o numero do registro (3 digitos): ");
gets(numstr);
livrol.regnum=atoi (numstr);
printf("\nLivro 2.\nDigite titulo: ");
gets(livro2.titulo);
printf("Digite o numero do registro (3 digitos): ");
gets(numstr);
livro2.regnum=atoi (numstr);
printf("\nLista de livros:\n");
printf(" Titulo: %s\n",livrol.titulo);
printf(" Numero do registro: %03d\n",livrol.regnum);
printf(" Titulo: %s\n",livro2.titulo);
printf(" Numero do registro: %03d\n",livro2.regnum);
getch();
}
Uma simples execução do programa terá a seguinte saída:
Livro 1.
Digite titulo: Helena
Digite o numero do registro: 102
Livro 2.
Digite titulo: Iracema
Digite o numero do registro: 321
Lista de livros:
Titulo: Helena
Numero do registro: 102
Titulo: Iracema
Numero do registro: 321
A estrutura criada tem duas partes: uma para armazenar o título e uma para armazenar o
número do registro.
As variáveis livro1 e livro2 são declaradas como sendo do tipo struct livro.
Os dados são colocados nos membros apropriados da estrutura usando o operador (.) e
trabalhando com eles como se fossem variáveis simples.
Poderíamos ter usado a função scanf() para ler o número do registro dos livros, como na
instrução:
scanf("%d",&livrol.regnum);
o que surtiria o mesmo efeito do uso de gets() e atoi().
Preferimos usar gets() e atoi(), pois são bem menores que scanf() e não provocam problemas
com o buffer do teclado, se o usuário digitar espaços antes de digitar o número.
Conforme já vimos, a função atoi(), de biblioteca C, converte uma "string" ASCII num
inteiro correspondente.
Inicializando Estruturas
Nós já aprendemos como inicializar variáveis simples e matrizes:
int num = 5;
int mat[ ] = { 1, 2, 3, 4, 5 };
Uma estrutura só pode ser inicializada se for de classe extern ou static. O ponto crítico
a saber é que a classe extern ou static para estruturas depende do lugar onde ela foi
declarada e não do lugar onde foi feita a sua definição.
O exemplo seguinte é uma versão modificada do programa anterior em que os dados dos 2
livros estão contidos na instrução de inicialização dentro do programa, em vez de serem
solicitados ao usuário.
#include "stdio.h"
#include "conio.h"
#include "stdlib.h"
// mostra inicializa‡Æo das estruturas
struct livro
{
char titulo[30];
int regnum;
};
struct livro livrol = {"Helena", 102};
struct livro livro2 = {"Iracema", 321};
main()
{
clrscr();
printf("\nLista de livros:\n");
printf(" Titulo: %s\n",livrol.titulo);
printf(" Numero do registro: %03d\n",livrol.regnum);
printf(" Titulo: %s\n",livro2.titulo);
printf(" Numero do registro: %03d\n",livro2.regnum);
getch();
}
Aqui, depois da declaração usual do tipo estrutura, as duas variáveis estrutura são
declaradas e inicializadas.
Da mesma forma como inicializamos matrizes, o sinal de igual usado e em seguida a
abertura da chave que irá conter a lista de valores. Os valores são separados por vírgulas.
Atribuições Entre Estruturas
Nas versões mais modernas de C, é possível atribuir o valor de uma variável estrutura a
outra do mesmo tipo, usando uma simples expressão de atribuição. Se livro1 e livro2 são
variáveis estrutura do mesmo tipo, a seguinte expressão pode ser usada;
livro2 = livro1;
Nesta modificação do nosso programa de lista de livros, tomamos as informações do usuário,
sobre um livro, e as atribuímos à segunda variável estrutura usando uma simples expressão
de atribuição:
#include "stdio.h"
#include "conio.h"
// mostra atribuicoes entre estruturas
main()
{
struct livro
{
char titulo[30];
int regnum;
};
struct livro livrol;
struct livro livro2;
printf("\nLivro 1.\nDigite titulo: ");
gets(livrol.titulo);
printf("Digite o numero do registro (3 digitos): ");
scanf("%d",&livrol.regnum);
livro2=livrol;
printf("\nLista de livros:\n");
printf(" Titulo: %s\n",livrol.titulo);
printf(" Numero do registro: %03d\n",livrol.regnum);
printf(" Titulo: %s\n",livro2.titulo);
printf(" Numero do registro: %03d\n",livro2.regnum);
getch();
}
Quando executamos este programa, os dados de dois livros serão impressos como anteriormente,
mas eles serão exatamente os mesmos dados para os 2 livros.
Certamente verificamos que esta é uma estupenda capacidade quando pensamos a respeito:
quando você atribui uma estrutura a outra, todos os valores na estrutura estão realmente
sendo atribuídos de uma vez para os correspondentes elementos da outra estrutura.
Uma expressão de atribuição tão simples não pode ser usada para matrizes, que devem ser
atribuídas elemento a elemento.
Estruturas Aninhadas
Exatamente como podemos ter matrizes de matrizes, podemos ter estruturas que contêm outras
estruturas. O que pode ser um poderoso caminho para a criação de tipos de dados complexos.
Como exemplo, imagine que os nossos livros são divididos grupos, consistindo de um "dicionário"
e um livro de "literatura".
O programa seguinte cria uma estrutura de etiqueta grupo. Esta estrutura consiste de duas
outras do tipo livro.
#include "stdio.h"
#include "conio.h"
// mostra o uso de estruturas aninhadas
struct livro
{
char titulo[30];
int regnum;
};
struct grupo
{
struct livro dicionario;
struct livro literatura;
};
struct grupo grupol =
{
{"Aurelio",134 },
{"Iracema",321 }
};
main()
{
clrscr();
printf("\nDicionario:\n");
printf(" Titulo: %s\n" ,grupol.dicionario.titulo);
printf(" No. do registro: %03d\n",grupol.dicionario.regnum);
printf("\nLiteratura:\n");
printf(" Titulo: %s\n",grupol.literatura.titulo);
printf(" No.do registro: %03d\n",grupol.literatura.regnum);
getch();
}
Vamos analisar alguns detalhes deste programa:
Primeiro, declaramos uma variável estrutura grupo1, do tipo grupo, e inicializamos esta
estrutura com os valores mostrados.
Quando uma matriz de várias dimensões é inicializada, usamos chaves dentro de chaves,
do mesmo modo inicializamos estruturas dentro de estruturas.
Segundo, note como acessamos um elemento da estrutura que é parte de outra estrutura.
O operador ponto é usado duas vezes:
grupo1.dicionario.titulo
Isto referencia o elemento titulo da estrutura dicionario da estrutura grupo 1.
Logicamente este processo não pára neste nível, podemos ter uma estrutura dentro de outra
dentro de outra. Tais construções aumentam o nome da variável, podendo descrever surpreendentemente o seu
conteúdo.
Passando Estruturas para Funções
Suponhamos que você queira usar uma chamada a uma função a obter os dados sobre os livros.
A nova versão de C ANSI permite que a função passe ou retorne uma estrutura completa para
outra função (compiladores mais antigos não permitem esta facilidade).
Como exemplo, vamos reescrever o programa dos livros para que use uma função para obter os
dados sobre os livros pelo usuário e outra para imprimi-los.
#include "stdio.h"
#include "conio.h"
#include "stdlib.h"
// armazena dois livros e mostra a passagem de estruturas p/ funcoes
struct livro
{
char titulo[30];
int regnum;
};
// list - funcao que imprime os dados dos livros
list(liv)
struct livro liv;
{
printf("\nLivro: \n");
printf(" Titulo: %s\n",liv.titulo);
printf(" Numero do registro: %03d\n",liv.regnum);
}
main()
{
struct livro livrol;
struct livro livro2;
struct livro novonome();
livrol=novonome();
livro2=novonome();
list(livrol);
list(livro2);
getch();
}
// novonome() adiciona um novo livro ao arquivo
struct livro novonome()
{
char numstr[81];
struct livro livr;
clrscr();
printf("\nNovo livro\nDigite titulo: ");
gets(livr.titulo);
printf("Digite o numero do registro (3 digitos) ");
gets(numstr);
livr.regnum=atoi (numstr);
return(livr);
}
Visto que as duas funções, como também o programa main() devem conhecer como a estrutura
livro é declarada, esta declaração é feita como global colocando-a fora de qualquer função e
antes de main().
As funções main(), novonome() e list() declaram internamente suas próprias variáveis
estrutura, chamadas livro1 e livro2, livr e liv; para serem deste tipo.
Observe que a função list() recebe uma cópia da estrutura e coloca num endereço conhecido
somente por ela; não é a mesma estrutura declarada em main().
A função novonome() é chamada pelo programa principal para obter informações do usuário
sobre os 2 livros. Esta função guarda as informações em uma variável interna, livr, e
retorna o valor desta variável para o programa principal usando o comando return,
exatamente como faria para devolver uma simples variável. A função novonome() deve
ser declarada como sendo do tipo struct livro em main(), visto que ela retorna um
valor deste tipo.
O programa principal atribui o valor retornado por novonome() variável estrutura livro1
e livro2. Finalmente main() chama a função para imprimir os valores de livro1 e livro2,
passando os valores destas duas estruturas para a função como variáveis.
A função list() atribui estes valores à variável estrutura interna liv e acessa os
elementos individuais desta estrutura para imprimir seus valores.
Matrizes de Estruturas
Evidentemente, uma lista de livros é composta de muitos livros (provavelmente mais de 2).
Cada livro é descrito por uma variável estrutura do tipo livro. Para descrever dois livros
nós usamos duas variáveis. Para tratar vários livros é perfeitamente correto pensar numa
matriz de estruturas.
O programa seguinte, além de realizar o nosso objetivo principal de criar uma matriz de
estruturas onde cada elemento da matriz representa a descrição de um livro, ele irá
proporcionar uma simples interface com o usuário constituída da escolha entre duas
opções assinaladas pelas letras 'e' e 'l'.
Se o usuário pressionar 'e', o programa permitirá a entrada das informações de um livro.
Se o usuário pressionar 'l', o programa listará todos os livros armazenados na memória.
Vamos ainda aumentar as informações de cada livro acrescentando o autor (uma "string") e o preço
(um double).
Como queremos trabalhar com uma variável double como membro da estrutura livro, usaremos
a função atof(), que converte uma "string" em um número em ponto flutuante de dupla precisão.
A função atof() é uma função do tipo double pois retorna um double, assim é necessário
declará-la. Como esta declaração já está feita no arquivo stdlib.h, vamos usá-lo.
#include "stdio.h"
#include "conio.h"
#include "stdlib.h"
//prototipando funcoes
void novonome();
void listatot();
//definindo tipo
struct lista
{
char titulo[30];
char autor[30];
int regnum;
double preco;
};
struct lista livro[50];
int n=0;
main()
{
char ch;
do
{
clrscr();
printf("\nDigite 'e' para adicionar um livro");
printf("\n'l' para listar todos os livros");
printf("\nESC para sair");
ch=getche();
switch(ch)
{
case 'e': novonome();break;
case 'l': listatot();break;
case 27: break;
default: puts ("\nDigite somente opcoes validas");
}
}while(ch!=27);
}
// novonome() - adiciona um novo livro ao arquivo
void novonome()
{
char numstr[81];
printf("\nRegistro %d.\nDigite titulo: ", n+1) ;
gets(livro[n].titulo);
printf("\nDigite autor: ");
gets (livro[n].autor);
printf("\nDigite o numero do livro (3 digitos): ");
gets (numstr);
livro[n].regnum=atoi (numstr);
printf("\nDigite preco: ");
gets(numstr);
livro[n++].preco=atof(numstr);
}
//listatot() - lista os dados de todos os livros
void listatot()
{
int i;
clrscr();
if( !n )
printf("\nLista vazia.\n");
else
for(i=0;i<n;i++)
{
printf("\n\nRegistro %d.\n",i+1);
printf("Titulo: %s.\n",livro[i].titulo);
printf("Autor : %s.\n",livro[i].autor);
printf("Numero do registro: %3d.\n",livro[i].regnum);
printf("preco: %4.2f.\n",livro[i].preco);
}
getch();
}
A seguir mostraremos uma simples execução do programa:
Digite 'e' para adicionar um livro
'l' para listar todos os livros:
ESC para sair: e
Registro 1.
Digite titulo: Helena
Digite autor : Machado de Assis
Digite o numero do registro (3 dígitos): 102
Digite preco: 70.5
Digite 'e' para adicionar um livro
'l' para listar todos os livros:
ESC para sair: e
Registro 2.
Digite titulo: Iracema
Digite autor : Jose de Alencar
Digite o numero do registro (3 digitos): 321
Digite preco: 63.25
Digite 'e' para adicionar um livro
'l' para listar todos os livros:
ESC para sair: e
Registro 3.
Digite titulo: Macunaima
Digite autor : Mano de Andrade
Digite o numero do registro (3 digitos): 056
Digite preco: 73.3
Digite 'e' para adicionar um livro
'l' para listar todos os livros:
ESC para sair: l
Registro 1.
Titulo: Helena
Autor: Machado de Assis
Numero do registro: 102
Preco: 70.50
Registro 2.
Titulo: Iracema
Autor : Jose de Alencar
Numero do registro: 321
Preco: 63.25 Registro 3.
Titulo: Macunaima
Autor : Mano de Andrade
Numero do registro: 056
Preco: 73.30
Depois desta iteração podemos continuar adicionando mais livros ou listando os livros
novamente.
Os dois pontos principais a serem levantados sobre uma matriz estruturas são como
declará-las e como acessar membros individuais estrutura.
Declarando Uma Matriz de Estruturas
O processo de declaração de uma matriz de estruturas é perfeitamente análogo à declaração
de qualquer outro tipo de matriz.
struct lista livro[50];
Esta instrução declara livro como sendo uma matriz de 50 elementos. Cada elemento da
matriz é uma estrutura do tipo lista. Então, livro[0] é uma estrutura do tipo lista,
livro[1] é a segunda estrutura lista e assim por diante.
O nome livro não é o nome de uma estrutura e sim o nome de uma matriz em que os elementos
são estruturas.
Observe que através desta instrução o compilador providencia espaço de memória para 50
estruturas do tipo lista.
Para simplificar, declaramos a matriz de estruturas como variável global, assim todas as
funções do programa poderão acessá-la.
Acessando Membros da Matriz Estruturas
Membros individuais de cada estrutura são acessados aplicando-se o operador ponto seguido
ao nome da estrutura, que neste caso é um elemento da matriz, e finalmente o nome do
membro desejado.
livro[2].titulo
Observe que o subscrito da matriz é associado a livro e não ao fim do nome.
A razão do uso de livro[2].titulo é que livro[2] é o nome da variável estrutura, livro[1]
é um outro nome de variável estrutura. A expressão livro[n].titulo refere-se ao membro
titulo da n-ésima estrutura da matriz de estruturas do tipo lista.
O que representa a seguinte expressão?
livro[2].titulo[3]
É uma referência ao quarto elemento do título do livro descrito pela terceira estrutura.
No nosso exemplo, é o caractere 'u'.
O esquema global deste programa pode ser aplicado a uma grande variedade de situações
para armazenar dados sobre quaisquer entidades particulares. Poderá, por exemplo, ser
usado para controle de um estoque, onde cada variável estrutura conterá dados sobre um
item do estoque, como número do material, preço, tipo, número de peças no estoque etc.
Ou poderá ser usado para um programa de orçamento, onde cada estrutura conterá informações
sobre a categoria do orçamento, como o nome da categoria, a quantia orçada etc.
A única coisa que falta para tornar-se um banco de dados útil é armazenar os dados num
arquivo em disco, o que exploraremos adiante.
|