Curso de C - 10. Estruturas, Uniões, Enumerações e Definição de Tipois
(2547 total de palavras neste texto) (3791 vizualização(ões)) 
Curso de C - 10. Estruturas, Uniões, Enumerações e Definição de Tipos
Como já vimos, as matrizes nos trazem um meio de organizar coleções de dados do mesmo tipo, que poupa o trabalho do programador de ter
que declarar muitas variáveis. Por outro lado, o que podemos fazer quando queremos organizar uma coleção de dados de tipos diferentes??
Neste caso, as matrizes não podem nos ajudar. Para esse fim existem as estruturas e as uniões, que agrupam dados de tipos diferentes
e nos fornecem um meio bastante prático de acessar estes dados.
As enumerações são uma forma de nomear coleções de constantes de modo a facilitar e tornar mais intuitivo o seu uso.
Aprenderemos também nesta aula como criar tipos de dados. Have fun!!
10.1. Estruturas
Uma estrutura em C é similar aos registros em Pascal: é um agrupamento de dados de tipos diferentes, referenciados por um nome, o que
facilita bastante o acesso e a organização. Sua forma geral é a seguinte:
struct nome
{
tipo1 nome1;
tipo2 nome2;
...
tipoN nomeN;
};
Geralmente, os dados agrupados em uma estrutura estão relacionados. Como exemplo podemos citar os dados do endereço de uma pessoa, onde temos
o nome da rua (uma string), o número da casa (um inteiro), o complemento (uma string), o CEP (um inteiro longo), o nome do bairro (uma
string), o nome da cidade (uma string) e a sigla do estado (uma string de 2 caracteres). Vamos declarar esta estrutura então, para ver como
fica:
struct endereco
{
char rua[30];
int numero;
char complem[30];
long int cep;
char bairro[15];
char cidade[15];
char estado[3];
};
Quando declaramos uma estrutura como acima, ainda não declaramos nenhuma variável. Estamos apenas especificando o tipo, ou a estrutura da
estrutura... Para declarar uma variável da estrutura struct endereco, fazemos assim:
struct endereco dados;
Neste exemplo, declaramos a variável dados como sendo do tipo struct endereco. Outra forma de declarar uma variável de uma estrutura
é colocar o nome da variável após a segunda chave da estrutura, antes do ponto-e-vírgula:
struct endereco
{
char rua[30];
int numero;
char complem[30];
long int cep;
char bairro[15];
char cidade[15];
char estado[3];
} dados;
Aqui também declaramos a variável dados do tipo struct endereco. Se você precisar de apenas uma variável da estrutura em seu
programa, não é necessário dar nome a estrutura:
struct /* sem nome... */
{
char rua[30];
int numero;
char complem[30];
long int cep;
char bairro[15];
char cidade[15];
char estado[3];
} dados;
Para acessar as variáveis internas à estrutura, usamos o ponto entre o nome da variável e o nome do elemento. Exemplo:
#include <stdio.h>
/* definimos a forma da estrutura */
struct endereco
{
char rua[30];
int numero;
char complem[30];
long int cep;
char bairro[15];
char cidade[15];
char estado[3];
};
main()
{
struct endereco dados; /* declaração */
/* Lendo ... */
printf("Rua: ");
fgets(dados.rua, 29, stdin);
printf("Número: ");
scanf("%d", &dados.numero); getchar();
printf("Complemento (se não houver, digite '-'): ");
fgets(dados.complem, 29, stdin);
printf("CEP: ");
scanf("%ld", &dados.cep); getchar();
printf("Bairro: ");
fgets(dados.bairro, 14, stdin);
printf("Cidade: ");
fgets(dados.cidade, 14, stdin);
printf("Estado (sigla): ");
fgets(dados.estado, 3, stdin);
/* Agora, imprimindo... */
printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
printf("RUA: %s", dados.rua);
printf("NÚMERO: %d\n", dados.numero);
printf("COMPLEMENTO: %s", dados.complem);
printf("BAIRRO: %s", dados.bairro);
printf("CEP: %ld\n", dados.cep);
printf("CIDADE: %s", dados.cidade);
printf("ESTADO: %s\n", dados.estado);
printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
return 0;
}
Como você pode ver, para acessar a variável (ou campo) numero, interna à estrutura dados, basta usar a notação dados.numero. Para
ler com scanf, basta colocar o & antes de dados.numero. As strings também são tratadas como se estivessem fora da estrutura. Para ler o nome
da rua, basta fazer fgets(dados.rua, 29, stdin).
Inicialização de Estruturas
Uma estrutura só pode ser inicializada se for de classe extern ou static, e isto depende de onde a variável estrutura foi declarada.
Da mesma forma que inicializamos matrizes, podemos inicializar uma estrutura:
struct produto
{
char nome[20];
float preco;
};
struct produto item= { "Drive CD-ROM 52X", 175.00 };
Os elementos devem aparecer na mesma ordem em que foram definidos na estrutura.
Atribuição de Estruturas
O padrão ANSI C permite que você atribua uma estrutura à outra, sem a necessidade de fazer isso campo por campo. Vamos criar a estrutura
produto:
struct produto
{
char nome[20];
float preco;
};
Agora, declaramos duas variáveis: p1 e p2.
struct produto p1, p2;
O trecho de código a seguir é válido:
strcpy(p1.nome, "mouse"); /* atribui "mouse" a string p1.nome */
p1.preco= 30.55;
p2= p1; /* aqui os campos de p2 recebem os valores dos campos de p1 */
printf("%s = %.2f", p2.nome, p2.preco);
Será impresso: Mouse = 30.55.
Matrizes de Estruturas
Como uma estrutura não deixa de ser um tipo, você pode criar uma matriz de estruturas facilmente, utilizando a mesma notação para declaração
de matrizes de outros tipos. Vamos usar como exemplo a estrutura struct produto. Criaremos uma matriz de 10 elementos dessa estrutura:
#include <stdio.h>
#include <string.h>
/* definição da "forma" da estrutura */
struct produto
{
char nome[20];
float preco;
};
main()
{
struct produto itens[10]; /* uma matriz de estruturas */
int i;
for (i=0; i<10; i++)
{
printf("Nome do produto: ");
fgets(itens[i].nome, 19, stdin);
printf("Preço do produto: ");
/* esse %*c limpa o buffer (o mesmo que getchar()) */
scanf("%f%*c", &itens[i].preco);
}
for (i=0; i<10; i++)
{
printf("Nome: %s", itens[i].nome);
printf("Preço: %.2f\n\n", itens[i].preco);
}
return 0;
}
Para acessar os campos de uma matriz de estruturas, basta colocar o nome da matriz, seguido do índice, ponto e nome do campo. Assim:
itens[0].preco
Você estará acessando o campo preco do primeiro elemento da matriz de estruturas.
Ponteiros para Estruturas
Os ponteiros e as estruturas fazem o casamento perfeito para a criação de estruturas de dados mais avançadas, como listas ligadas, pilhas e
filas. Os dois quase sempre aparecem juntos em muitos problemas de programação. Vamos ver nas próximas aulas que passar um ponteiro de uma
estrutura para uma função pode aumentar drasticamente a performance do programa, pois apenas o endereço é passado. Esta é apenas uma vantagem
dos ponteiros para estruturas.
Declarar um ponteiro para estrutura é como declarar um ponteiro para qualquer tipo de dado. Considerando a estrutura struct produto:
struct produto *ptr_dados;
Declaramos o ponteiro ptr_dados, que aponta para estruturas do tipo struct produto.
Veja o programa a seguir, que ilustra a utilização de ponteiros para estruturas:
#include <stdio.h>
struct produto
{
char nome[20];
float preco;
};
main()
{
struct produto dados; /* declara variável */
struct produto *ptr; /* declara ponteiro */
strcpy(dados.nome, "Mouse");
dados.preco= 35.00;
ptr= &dados; /* o ponteiro ptr recebe o endereço da estrutura dados */
printf("%s = %.2f", ptr->nome, ptr->preco);
return 0;
}
Notou algo diferente?? Porque não usamos o ponto ao imprimir os campos nome e preco, apontados por ptr?
Quando vamos referenciar os campos de uma estrutura através de um ponteiro, não podemos usar o ponto entre o nome da variável e o campo, e
sim o operador ->. Usar essa notação é semelhante a usar o asterisco antes de um ponteiro para inteiro, por exemplo, onde é referenciado
o conteúdo da variável apontada, não o endereço. Aqui, ptr->preco é 35.00 e ptr->nome é "Mouse".
Estruturas dentro de estruturas
Os campos de uma estrutura podem perfeitamente ser uma outra estrutura, assim como também matrizes, strings e ponteiros. Por exemplo:
struct address
{
char rua[20];
int num;
char bairro[15];
long int cep;
char cidade[20];
char estado[4];
};
struct aluno
{
long int ra;
char curso[15];
struct address ender; /* estrutura aninhada */
};
A estrutura struct aluno possui o campo ender, que também é uma estrutura, a struct address. Aí você me pergunta: como fazer para
acessar um campo da estrutura struct address que está dentro de struct aluno? Basta fazer assim:
struct aluno a1; /* declarar variável */
a1.ender.num= 1200;
a1.ender.cep= 13098756;
É só ir usando os pontos para acessar os campos. As estruturas podem ser aninhadas até 15 vezes, segundo o padrão ANSI C. A maioria dos
compiladores, porém, permite mais que isso.
10.2. Campos de Bits
Além dos operadores bit-a-bit, C oferece uma outra forma de acessar e manipular os bits de um byte: os Campos de Bits. Esse tipo especial de
estrutura é bastante usado em programas que manipulam algum tipo de hardware.
Os campos de bits devem ser declarados como int, unsigned ou signed, porém alguns compiladores só admitem campos de bits
unsigned. Campos de bits de comprimento 1 devem ser obrigatoriamente unsigned (não podem ter sinal).
A forma geral da definição de um campo de bits é a seguinte:
struct nome
{
tipo nome1: comprimento;
tipo nome2: comprimento;
tipo nome3: comprimento;
...
tipo nomeN: comprimento;
};
Para você entender, vamos mostrar um exemplo bem bobo, onde criamos um campo de bits para um byte:
#include <stdio.h>
struct campo /* o campo de bits */
{
unsigned int a: 1; /* comprimento 1, só pode ser 0 ou 1 (1 bit) */
unsigned int b: 1;
unsigned int c: 1;
unsigned int d: 1;
unsigned int e: 1;
unsigned int f: 2; /* comprimento 2, pode ter valor 0, 1, 2 ou 3 (2 bits) */
unsigned int g: 1;
} byte;
main()
{
/* vá alterando os valores dos campos e
veja o resultado da impressão. */
byte.a= 1;
byte.b= 0;
byte.c= 0;
byte.d= 0;
byte.e= 0;
byte.f= 3; /* 3 em binário é 11 (2 bits) */
byte.g= 1;
/* como um caractere tem 1 byte, imprimimos como caractere */
printf("%c\n", byte);
return 0;
}
As variáveis de campo de bits possuem algumas restrições:
- você não pode obter o endereço delas;
- dependência da máquina, por causa da ordem dos campos (da esquerda para direita ou da direita para esquerda);
- não podem ser organizadas em matrizes;
10.3. Uniões
As uniões são bastante parecidas com as estruturas, na declaração, na forma de acessar os campos, etc. A única e crucial coisa que difere uma
união de uma estrutura é que o espaço de memória alocado por uma variável união é compartilhado por seus campos. Isto quer dizer
que uma variável união (ou union) ocupa o espaço do tamanho do seu maior campo.
A forma geral de uma union é a seguinte:
union nome
{
tipo nome1;
tipo nome2;
...
tipo nomeN;
};
Suponhamos a seguinte union:
union teste
{
int num;
float fp;
double dfp;
char ch;
} info;
Pergunto: qual o espaço ocupado pela variável info na memória? Acertou quem respondeu 8 bytes, ou 64 bits, ou o tamanho de um double (o
seu maior elemento).
Uma estrutura com os mesmos campos ocuparia 17 bytes (considerando int de 4 bytes).
Um exemplo do uso de uniões é gravar um inteiro byte por byte em um arquivo. Considerando um inteiro de 4 bytes:
union teste
{
int num;
char ch[4];
} info;
Agora para gravar o inteiro, byte por byte:
FILE *fp;
/* suponhamos que o arquivo já esteja aberto */
putc(info.ch[0], fp);
putc(info.ch[1], fp);
putc(info.ch[2], fp);
putc(info.ch[3], fp);
A função putc grava um caractere no arquivo cujo descritor é fp. Como um caractere tem 1 byte, gravamos o inteiro todo, byte por byte. Este é
um exemplo do uso de uniões.
10.4. Enumerações
Uma enumeração é uma coleção de constantes inteiras, referenciadas por um nome, que define todos os valores possíveis que a variável
enumeração pode ter. A forma geral de uma enumeração é:
enum nome { constante1, constante2, ..., constanteN };
Para declarar uma variável enum:
enum nome variavel;
Por exemplo:
enum patente { soldado, cabo, sargento, tenente }; /* definimos a forma */
enum patente militar; /* só aqui houve a declaração da variável */
Em uma enumeração, o valor do primeiro símbolo (soldado) é 0, o segundo símbolo (cabo) é 1, o terceiro símbolo (sargento) é 2 e assim por
diante. Cada símbolo nada mais é do que um inteiro com um apelido (não confundir com strings). A variável militar é uma variável
inteira e pode ter somente os valores 0, 1, 2 ou 3.
Exemplo:
enum patente { soldado, cabo, sargento, tenente };
enum patente militar;
printf("0 - Soldado\n");
printf("1 - Cabo\n");
printf("2 - Sargento\n");
printf("3 - Tenente\n");
printf("Digite a escolha: ");
scanf("%d", &militar);
switch(militar)
{
case soldado:
printf("Soldado!");
break;
case cabo:
printf("Cabo!");
break;
case sargento:
printf("Sargento!");
break;
case tenente:
printf("Tenente!");
break;
default:
printf("Digitou errado!");
}
Como você pode ver, enumerações podem tornar o código mais legível e organizado. Sempre que possível é bom utilizá-las.
Você também pode especificar um valor para as constantes de uma enumeração:
enum patente { soldado, cabo, sargento=50, tenente };
Os elementos terão os seguintes valores:
| soldado |
0 |
| cabo |
1 |
| sargento |
50 |
| tenente |
51 |
10.5. Definição de Tipos
Em C nós podemos criar novos tipos ou dar nomes diferentes aos tipos existentes. Basta usar a palavra-chave typedef. A forma geral é:
typedef tipo nome_novo;
Exemplos:
typedef char byte; /* criamos o tipo byte (simplesmente um char) */
Para declarar uma variável:
byte ch;
Com as estruturas, uniões e enumerações não é diferente. Veja:
typedef struct produto Prod; /* Criamos o tipo Prod */
Prod item; /* a declaração fica assim, bem mais enxuta */
typedef enum {verdade, falso} bool;
bool valor;
Com as matrizes:
typedef int mat3x3[3][3];
mat3x3 teste; /* declara a variável teste (matriz 3 por 3) */
typedef float M[10];
M mat; /* declara a variável mat (matriz unidimensional de 10 elementos) */
Bem... De nada adianta fazer todos esses malabarismos com dados e depois perdermos tudo após a execução do programa. Por isso que na próxima
aula vamos aprender a manipular arquivos em C.
Até lá!!
Exercício
1) Faça uma agenda que guarde os seguintes dados: nome, telefone e e-mail. Esta agenda deve ter capacidade para 50 entradas, e você deverá
fornecer as seguintes opções:
- adicionar uma nova entrada;
- buscar e modificar uma existente;
- excluir uma entrada.
O programa deverá avisar o usuário quando a agenda estiver cheia, não permitindo novas entradas neste caso. Se o usuário tentar adicionar um
telefone ou e-mail já existente, o programa deverá avisar que o registro já consta na agenda.
Powered by txt2tags |