AULA 19 - Introdução à Linguagem C - FIC
TIPOS DE DADOS COMPLEXOS E ESTRUTURADOS
A linguagem C permite que o usuário "crie" seus próprios tipos complexos de variáveis.
Enumerações, uniões, estruturas e definição de tipos serão os tópicos estudados neste capítulo.
ENUMERAÇÕES
Enumerações são classes, conjuntos de valores relacionados, criados para melhorar a legibilidade do código-fonte.
Uma variável de um tipo enumeração somente pode receber valores que foram declarados para aquele tipo.
A sintaxe da declaração de um tipo enum é:
enum rótulo {enum1, enum2, ..., enumn};
onde rótulo é uma identificação para esta enumeração e enum1, enum2, ..., enumn são os valores possíveis para rótulo.
A sintaxe para a definição de uma variável como sendo de um tipo rótulo é:
enum rótulo nome_variável;
onde nome_variável é o nome da variável que vai assumir algum valor dentre os valores
possíveis da enumeração rótulo.
Como qualquer tipo em C, a definição de uma variável como sendo de um certo tipo enumeração
rótulo, pode ser feita na própria declaração do tipo.
enum rótulo {enum1, enum2, ..., enumn} nome_variável;
Ex:
enum dias { seg, ter, qua, qui, sex, sab, dom};
...
enum dias hoje, dia_semana;
ou
enum dias { seg, ter, qua, qui, sex, sab, dom} hoje, dia_semana;
Neste exemplo, definimos uma enumeração dias, que pode ter os valores seg, ter, qua, qui, sex, sab, dom.
Logo abaixo, declaramos as variáveis hoje e dia_semana, como sendo do tipo dias.
Pode-se fazer os dois passos ao mesmo tempo, como na terceira linha do exemplo.
A definição da variável será feita com a seguinte sintaxe:
nome_variável = enumx;
onde enumx é qualquer um dos n valores contidos na enumeração.
Ex:
dia_semana = ter;
Cada valor possível na enumeração recebe um valor inteiro com o qual pode ser representado.
Caso não seja explicitado, o primeiro valor vai receber o inteiro 0, o segundo, o inteiro 1 e assim por diante.
Isto é feito para que se possa comparar cada valor da enumeração.
Por exemplo, na enumeração dias:
| Identificador | Valor |
|---|---|
| seg | 0 |
| ter | 1 |
| quar | 2 |
| qui | 3 |
| sex | 4 |
| sab | 5 |
| dom | 6 |
Extra options... Preview
se fizermos:
| Expressão | Valor |
|---|---|
| dia_semana == seg | 1 se dia_semana for seg, 0 caso contrário |
| hoje > sex | 1 se hoje for sab ou dom, 0 caso contrário |
| ter > quar | 0 (falso) |
Alguns compiladores permitem que se altere o inteiro atríbuido a cada valor da enumeração.
Exs:
enum estações { primavera = 1, verão = 2, outono = 3, inverno = 4} estação;
enum fim_de_semana { sab = 6, dom );
No exemplo acima, dom terá automaticamente, o valor 7.
ESTRUTURAS
Estrutura é um grupo de variáveis, cujo formato é definido pelo programador e ao contrário das matrizes, pode ser composto por tipos diferentes.
Em outras linguagens, por exemplo Pascal, estruturas são conhecidas como registros.
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.
Como provavelmente existirão vários funcionários, pode-se criar uma matriz desta estrutura como sendo o banco de dados completo de pagamentos.
Definição
A definição de uma estrutura é feita da seguinte forma:
struct rótulo
{ declaração da variável1;
declaração da variável2;
...
declaração da variáveln;
};
onde rótulo é uma identificação para esta estrutura e como veremos mais tarde, é opcional.
As linhas de comando declaração da variável são declarações de variáveis de tipos convencionais (int, float, char) que vão compor os campos da estrutura.
Declaração de uma variável do tipo estrutura:
A declaração de uma variável como sendo do tipo estrutura rótulo é feita com a seguinte sintaxe:
struct rótulo nome_variável;
O exemplo acima declara a variável nome_variável como sendo uma estrutura do tipo rótulo.
A declaração da variável pode ser feita já na definição do tipo estrutura:
struct rótulo
{ declaração da variável1;
declaração da variável2;
...
declaração da variáveln;
} nome_variavel;
como usualmente é feito.
Ex:
struct registro
{ char nome[20];
int departamento;
float salario;
};
struct registro meu_registro; ... struct registro folha_pagamento[50];
Neste exemplo, definimos a estrutura registro que contém dados sobre um determinado empregado, como nome, número de departamento e salário.
Declaramos também, a variável meu_registro como sendo do tipo registro, ou seja, uma estrutura de dados.
Matriz de estruturas:
A seguir, no exemplo acima, declaramos a matriz de estruturas folha_pagamento, como tendo 50 elementos.
Isto significa que a folha de pagamento da empresa conterá 50 registros, cada qual com o nome, número de departamento e salário do empregado.
A sintaxe para a declaração de uma matriz de estruturas é:
struct rótulo nome_mat_estrut[dimensão];
onde nome_mat_estrut é o nome da matriz de estruturas e dimensão é o número de elementos
da matriz.
Rótulo da estrutura:
O rótulo de um tipo estrutura é opcional quando declaramos uma estrutura na própria definição do seu tipo.
Caso façamos a declaração da variável em linha de comando subsequente ou queiramos declarar outras estruturas como sendo daquele tipo deve-se utilizar o rótulo.
Inicialização da estrutura:
A inicialização de estruturas assemelha-se à inicialização de matrizes:
static struct registro meu_registro = { "Fernanda Marques", 21, 10000};
ou
static struct registro meu_registro = { { 'F', 'e', 'r', 'n', 'a', 'n', 'd', 'a' , ' ', 'M', 'a', 'r', 'q', 'u', 'e', 's',
'\0'}, 21, 10000};
ou
static struct registro meu_registro = { 'F', 'e', 'r', 'n', 'a', 'n', 'd', 'a' , ' ', 'M', 'a', 'r', 'q', 'u', 'e', 's', '\0',
0, 0, 0, 21, 10000};
Na memória, os campos de uma estrutura são armazenados um ao lado do outro.
Portanto, o endereço da estrutura é o endereço do primeiro byte do primeiro campo desta. Os 3 zeros (entre o caractere nulo, '\0', e o valor do segundo campo, 21) na inicialização da estrutura meu_registro acima, foram justamente acrescentados para completar o espaço de memória alocado na declaração do campo nome (string de 20 bytes).
Acessando membros da estrutura:
Para acessar individualmente cada campo de uma estrutura, utilizamos o operador de seleção ".".
A sintaxe é:
estrutura.campo = valor;
onde campo é cada variável declarada na estrutura estrutura.
Exs:
meu_registro.nome = "Fernanda Marques"; meu_registro.nome[0] = 'F';
Exemplo:
Programa que cria um tipo estrutura relacionado a dados de alunos e lê os campos correspondentes, calculando a média das notas:
#include <stdio.h>
#include <stdlib.h>
#define NALUNOS 5
#define NNOTAS 3
struct TipoAluno{
char Nome[81];
int idade;
float notas[NNOTAS]} turma[NALUNOS];
int main()
{ float soma;
for(int i=0;i<NALUNOS; i++)
{
soma = 0;
printf("\nEntre com o nome do aluno %d : ", i+1);
gets(turma[i].Nome);
printf("\nEntre com a idade de %s: ", turma[i].Nome);
scanf("%d",&turma[i].idade);
for(int j=0;j<NNOTAS;j++)
{
printf("\n\t\t Nota %d: ", j+1);
scanf("%f", &turma[i].notas[j]);
soma += turma[i].notas[j];
}
printf("\n\t\t\tMEDIA = %f ", soma/NNOTAS);
printf("\n******");
fflush(stdin);
}
return 0;
}
Atribuições entre estruturas:
Na maioria dos compiladores mais modernos é possível igualar-se duas estruturas do mesmo tipo da seguinte forma:
estrutura1 = estrutura2;
Note que não foi preciso igualar cada campo individualmente!
Endereço da estrutura:
A sintaxe do endereço de uma estrutura, como um todo, é:
&nome_estrutura
Passando e devolvendo estruturas para funções:
Para passar (por valor) uma estrutura como argumento para uma função simplesmente passamos o nome da estrutura:
...
struct xyz { /* tipo estrutura xyz, com dois campos: a (inteiro) e b (caractere) */
int a;
char b;
} estr1, estr3(struct xyz); /* declaração da variável global estr1 e função estr3() como tipo xyz */
...
void main(void)
{ struct xyz estr2; /* declaração da variável local estr2 como tipo xyz */
...
estr1.a = 234; /* atribuição ao campo a da variável estr1
estr1.b = 'J'; /* atribuição ao campo b da variável estr1
estr2 = estr3(estr1); /* atribuição do valor retornado pela função estr3 à variável estr2...
... /* ... (ambos os campos). O argumento é uma cópia de estr1
}
struct xyz estr3(struct xyz estr) /* função estr3 - recebe (estr) e retorna (estrlocal) vars xyz */
{ struct xyz estrlocal; /* declaração da variável local estrlocal */
estrlocal.a = estr.a + 1; /* atribuição ao campo a da variável estrlocal */
estrlocal.b = estr.b + 2; /* atribuição ao campo b da variável estrlocal */
...
return(estrlocal); /* retorna valor atualizado de estrlocal */
}
O algoritmo acima não é trivial e devemos fazer algumas considerações sobre o mesmo:
- Note que o tipo estrutura xyz foi definido como global;
- A variável global estr1 e a função estr3() foram definidas como sendo do tipo xyz;
- Dentro da função main() foi declarada uma variável local, str2, do tipo xyz;
- Os campos a e b da variável estrutura estr1 recebem valores em main();
- A variável estr2 vai receber o valor retornado pela função estr3(), que por sua vez recebeu o valor de estr1 como argumento;
- Na declaração da função, deve-se colocar "struct xyz str3(argumentos)", para que o programa saiba que o tipo retornado pela função também é uma estrutura;
- Também na lista de argumentos, "(struct xyz estr)", deve-se especificar que o tipo recebido pela função é uma estrutura xyz;
- Dentro da função estr3() foi declarada uma variável local, estrlocal, do tipo xyz;
- Os campos a e b da variável estrutura estrlocal recebem valores (no caso, o inteiro 235 e o caractere 'L') em estr3();
- No final, a função estr3() retorna a estrutura estrlocal para a expressão chamadora.
Estruturas aninhadas:
Assim como podemos ter matrizes de matrizes (várias dimensões), podemos também ter estruturas que contém outras estruturas.
Por exemplo, se dentro de uma estrutura de cadastro de Funcionários, quiséssemos criar uma estrutura para conter os campos de data. Ficaria algo assim:
#include <stdio.h> #include <stdlib.h> #include <string.h>
struct Data{ //declaração da estrutura Data, contendo campos dia, mês número, mês literal e ano
int dia;
int mes;
char Mes[11];
int ano;} hoje;
struct Cadastro{ //declaração da estrutura Cadastro, contendo a estrutura Data como campo
char Nome[81];
struct Data DataNascimento;
char RG[21];
} Funcionario; //declaração da variável estrutura Funcionario
int main()
{
printf("Entre com os dados do Funcionario :\n");
printf("\n\t\tNOME: ");
gets(Funcionario.Nome);
printf("\n\t\tDATA DE NASCIMENTO: [DD MM AAAA]");
scanf("%d %d %d", &Funcionario.DataNascimento.dia, &Funcionario.DataNascimento.mes, &Funcionario.DataNascimento.ano);
switch(Funcionario.DataNascimento.mes)
{
case 1: strcpy(Funcionario.DataNascimento.Mes ,"Janeiro"); break;
case 2: strcpy(Funcionario.DataNascimento.Mes ,"Fevereio"); break;
case 3: strcpy(Funcionario.DataNascimento.Mes ,"Marco"); break;
case 4: strcpy(Funcionario.DataNascimento.Mes ,"Abril"); break;
case 5: strcpy(Funcionario.DataNascimento.Mes ,"Maio"); break;
case 6: strcpy(Funcionario.DataNascimento.Mes ,"Junho"); break;
case 7: strcpy(Funcionario.DataNascimento.Mes ,"Julho"); break;
case 8: strcpy(Funcionario.DataNascimento.Mes ,"Agosto"); break;
case 9: strcpy(Funcionario.DataNascimento.Mes ,"Setembro"); break;
case 10: strcpy(Funcionario.DataNascimento.Mes ,"Outubro"); break;
case 11: strcpy(Funcionario.DataNascimento.Mes ,"Novembro"); break;
case 12: strcpy(Funcionario.DataNascimento.Mes ,"Dezembro"); break;
default: printf("\n Mes invalido!"); break;
};
fflush(stdin);
printf("\n\t\t RG: ");
gets(Funcionario.RG);
return 0;
}
Adaptando o código acima para criar um vetor de Funcionários:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define NFUNCIONARIOS 3
struct Data{
int dia;
int mes;
char Mes[11];
int ano;} hoje;
struct Cadastro{
char Nome[81];
struct Data DataNascimento;
char RG[21];
} Funcionario[NFUNCIONARIOS];
int main()
{
for(int i=0;i<NFUNCIONARIOS;i++){
printf("Entre com os dados do Funcionario :\n");
printf("\n\t\tNOME: ");
gets(Funcionario[i].Nome);
printf("\n\t\tDATA DE NASCIMENTO: [DD MM AAAA]");
scanf("%d", &Funcionario[i].DataNascimento.dia);
scanf("%d", &Funcionario[i].DataNascimento.mes);
scanf("%d", &Funcionario[i].DataNascimento.ano);
switch(Funcionario[i].DataNascimento.mes)
{
case 1: strcpy(Funcionario[i].DataNascimento.Mes ,"Janeiro"); break;
case 2: strcpy(Funcionario[i].DataNascimento.Mes ,"Fevereio"); break;
case 3: strcpy(Funcionario[i].DataNascimento.Mes ,"Marco"); break;
case 4: strcpy(Funcionario[i].DataNascimento.Mes ,"Abril"); break;
case 5: strcpy(Funcionario[i].DataNascimento.Mes ,"Maio"); break;
case 6: strcpy(Funcionario[i].DataNascimento.Mes ,"Junho"); break;
case 7: strcpy(Funcionario[i].DataNascimento.Mes ,"Julho"); break;
case 8: strcpy(Funcionario[i].DataNascimento.Mes ,"Agosto"); break;
case 9: strcpy(Funcionario[i].DataNascimento.Mes ,"Setembro"); break;
case 10: strcpy(Funcionario[i].DataNascimento.Mes ,"Outubro"); break;
case 11: strcpy(Funcionario[i].DataNascimento.Mes ,"Novembro"); break;
case 12: strcpy(Funcionario[i].DataNascimento.Mes ,"Dezembro"); break;
default: printf("\n Mes invalido!"); break;
};
fflush(stdin);
printf("\n\t\t RG: ");
gets(Funcionario[i].RG);
}
return 0;
}
Ex:
/***********************************************************/
/* Programa para biblioteca que separa livros em dois grupos: Dicionario e Literatura */
/* mostra o uso de estruturas aninhadas */
/*****************************************************************************/
...
struct livro {
char titulo[30];
int regnum;
};
struct grupo {
struct livro dicionario;
struct livro literatura;
};
struct grupo grupo1 = { {"Aurélio", 134},
{"Iracema", 321} };
void main(void)
{
printf("\nDicionario: \n");
printf(" Titulo: %s \n", grupo1.dicionario.titulo);
printf(" No. do registro: %03d\n", grupo1.dicionario.regnum);
printf(" \nLiteratura: \n");
printf(" Titulo: %s\n", grupo1.literatura.titulo);
printf(" No. do registro: %03d\n", grupo1.literatura.regnum);
}
Considerações:
- Quando uma matriz de várias dimensões é inicializada, usamos chaves dentro de chaves; do mesmo modo inicializamos estruturas dentro de estruturas;
- Para acessar um elemento da estrutura que é parte de outra estrutura utilizamos:
grupo.dicionario.titulo
No exemplo acima temos o elemento titulo da estrutura dicionario, que por sua vez é um elemento da estrutura grupo;
Exemplo 2:
Para compor um banco de dados completo, vamos utilizar tudo o que aprendemos até agora:
matrizes de estruturas, passagem de estruturas para funções, estruturas aninhadas, etc. Seguindo o
exemplo da livraria, temos:
/*****************************************************/ /*** PROGRAMA LIVRARIA - Demonstra como criar bancos de */ /*** dados em C, utilizando estruturas *********************/
#include <stdlib.h> /* para a função atof(), que transforma uma string em float */ #include <stdio.h> #include <conio.h>
void novonome(void); void listatot(void);
struct livro
{ char titulo[30];
char autor[30];
int regnum;
double preco;
};
struct livro lista[50]; /* declara uma matriz com 50 livros (estruturas do tipo lista) */
int n=0;
void main (void)
{ char ch;
for(;ch != 's'&& ch!= 'S';)
{ printf("\nDigite: 'e' para adicionar um livro\n");
printf("\t'l ' para listar os livros\n");
printf("\t's' para sair:\n");
ch = getche();
printf("\n");
switch(ch)
{ case 'e' :
case 'E' : novonome(); break;
case 'l' :
case 'L' : listatot(); break;
case 's' :
case 'S' : printf("Fim do programa"); break;
default : puts("\nDigite somente opções válidas: ");
}
}
}
/***** FUNÇÃO NOVONOME - adiciona um novo livro ao arquivo ***/
void novonome(void)
{ char numstr[81];
printf("\n Registro %d. \nDigite titulo: ", n+1);
gets (lista[n].titulo);
printf("Digite autor: ");
gets(lista[n].autor);
printf("Digite o número do livro (3 digitos): ");
gets(numstr);
lista[n].regnum = atoi(numstr); /* função atoi() converte uma string para inteiro */
printf("Digite preço:");
gets(numstr);
lista[n++].preco = atof(numstr);
printf("\n_______________________________\n");
}
/***** FUNÇÃO LISTATOT - lista os dados de todos os livros *****/
void listatot(void)
{
int i;
if( !n)
printf("\n\nLista vazia!!\n");
else
for( i=0; i<n; i++)
{ printf("\n Registro %d.\n", i+1);
printf("Titulo: %s. \n", lista[i].titulo);
printf("Autor: %s.\n", lista[i].autor);
printf("Numero do registro: %3d.", lista[i].regnum);
printf("Preço: %4.2f. \n\n", lista[i].preco);
}
printf("\n_______________________\n");
}
Note que para acessarmos cada elemento da matriz de estruturas utilizamos "livro[2].titulo", por
exemplo.
Isto mostra que o índice da matriz é atribuído à livro e não ao campo, título.
Se tivéssemos a construção "livro[2].titulo[3]", por exemplo, estaríamos referindo-nos ao quarto elemento (caractere) da string titulo, da terceira estrutura de livros.
Campos de bits
Uma estrutura pode conter campos de bits, em vez de bytes.
Estes campos são normalmente utilizados para acessar valores dependentes da máquina (como registradores, por exemplo).
Campos de bits são uma alternativa à utilização dos operadores bit a bit no acesso individual de bits dentro de um inteiro.
A sintaxe é:
struct{
unsigned int var1: num_bits1;
unsigned int var2: num_bits2;
...
unsigned int varn: num_bitsn;
} var_estrut;
onde:
- unsigned int é o tipo de todos os campos da estrutura;
- var1, var2,..., varn são os campos da
estrutura;
- num_bits é o número de bits que cada campo tem e
- var_estrut é a variável declarada como
estrutura de campos de bits.
Um campo não pode ultrapassar o tamanho de um int. Se isto ocorrer, o campo será alinhado no próximo inteiro.
Um exemplo de uma declaração para campos de bits é:
struct {
unsigned int pronto: 1;
unsigned int desligado: 2;
unsigned int outros: 5;
} modem;
Isto declara modem como sendo uma estrutura de 8 bits com os membros:
Membro Referência
modem.pronto primeiro bit modem.desligado próximos dois bits modem.outros próximos cinco bits
Estas expressões podem ser utilizadas em qualquer lugar onde um inteiro unsigned possa ser
utilizado.
modem terá a distribuição de memória mostrada no diagrama abaixo (assumindo que ints são obrigatoriamente alinhados em bytes pares e que bits, em nossa máquina-exemplo, são atribuídos da esquerda para a direita, já que isto depende do computador):
0 1 2 3 4 5 6 7 8 ... pronto desligado outros Bit
O operador de endereço (&) não pode ser utilizado com campos.