Jiraspiom

Blog Pessoal

Lição 10

Lição 10 – Estruturas de Dados

Estruturas de Dados

Uma estrutura de dados é uma junção de elementos de dados agrupados recebendo um nome. Esses elementos ou membros podem ter tipos diferentes e tamanhos diferentes. Estruturas de dados são declaradas em C++ na sintaxe:

struct nome_da_estrutura{
membro_tipo1 membro_nome1;
membro_tipo2 membro_nome2;
.
.
.
membro_tipox membro_nomex;
} nome_do_objeto;

onde nome_da_estrutura é um nome para o tipo de estrutura, nome_do_objeto pode ser um set de identificadores válidos para objetos que têm o tipo desta estrutura. Entre chaves, existe uma lista com os membros de dados, cada um especificado com um tipo e um indentificador válido como nome.

Uma estrutura de dados cria um novo tipo: assim que uma estrutura é declarada, um novo tipo com um identificador especificado como nome_da_estrutura é criado e pode ser usado no resto do programa como se fosse um novo tipo. Exemplo:

struct produto{
int peso;
float preco;
} ;

produto carne;
produto arroz, feijao;

Primeiro declaramos uma estrutura com 2 membros: peso e preço, e depois usamos essa estrutura para declarar três objetos do tipo “produto”: carne, arroz e feijao. Depois de declarado, produto se tornou um novo tipo válido, como int, char ou short, e dali em diante podemos declarar objetos (variáveis) deste novo tipo. No fim da declaração e antes dos dois-pontos finais podemos usar o campo opcional nome_do_objeto para declarar diretamente objetos do tipo da estrutura. Por exemplo, podemos também declarar os objetos da estrutura carne, arroz e feijao no momento em que definimos o tipo da estrutura de dados assim:

struct produto{
int peso;
float preco;
carne, arroz, feijao;
}

É importante diferenciar o que é o nome do tipo da estrutura e o que é um objeto (variável) que tem este tipo de estrutura. Podemos instanciar muitos objetos de um simples tipo de estrutura. Uma vez tendo declarado os 3 objetos de um determinado tipo de estrutura (carne, arroz, feijao), podemos operar diretamente com seus membros. Para isto usamos o ponto (.) inserido entre o nomedo objeto e o nome do membro. Por exemplo, podemos operar com qualquer destes elementos como se eles fossem variáveis padrão de seus respectivos tipos:

carne.peso
carne.preco
arroz.peso
arroz.preco
feijao.peso
feijao.preco

Cada uma destas tem o tipo de dado correspondente ao membro a que eles se referem: carne.peso, arroz.peso e feijao.peso são do tipo int, enquanto carne.preco, arroz.preco e feijao.preco são do tipo float. Vamos ver um exemplo real:

#include <iostream>
#include <string>
#include <sstream>
using namespace std;

struct filmes_t {
string titulo;
int ano;
} meu, seu;

void mostrafilme(filmes_t filme);

int main ()
{
string str;

meu.titulo = “Titanic”;
meu.ano = 1997;

cout << “Insira o título do filme: “;
getline (cin,seu.titulo);
cout << “Insira o ano: “;
getline (cin,str);
stringstream(str) >> seu.ano;

cout << “Meu filme favorito é:\n “;
mostrafilme(meu);
cout << “E o seu é:\n “;
mostrafilme(seu);
return 0;
}

void mostrafilme(filmes_t filme)
{
cout << filme.titulo;
cout << ” (” << filme.ano << “)\n”;
}

, que retorna na tela:

Insira o título do filme: Alien
Insira o ano: 1979
Meu filme favorito é:
Titanic (1997)
E o seu é:
Alien (1979)

O exemplo anterior mostra como podemos usar os membros de um objeto como variáveis comuns. Por exemplo, o membro seu.ano é uma variável válida do tipo int e meu.titulo é uma variável válida do tipo string. Os objetos meu e seu também podem ser tratados como variáveis válidas do tipo filmes_t, por exemplo se as tivéssemos passado para a função mostrafilme como teríamos feito com variáveis comuns. Assim, uma das mais importantes vantagens das estruturas de dados é que podemos nos referir tanto a seus membros individualmente quanto à estrutura inteira como um bloco com apenas 1 identificador.

Estrutura de dados podem ser usadas para representar bases de dados, especialmente se considerarmos a possibilidade de construir vetores com elas.

#include <iostream>
#include <string>
#include <sstream>
using namespace std;

#define N_FILMES 3

struct filmes_t {
string titulo;
int ano;
} films [N_FILMES];

void mostrafilme (filmes_t filme);

int main ()
{
string str;
int n;

for (n=0; n<N_FILMES; n++)
{
cout << “Insira o título: “;
getline (cin,films[n].titulo);
cout << “Insira o ano: “;
getline (cin,str);
stringstream(str) >> films[n].ano;
}

cout << “\nVocê inseriu estes filmes:\n”;
for (n=0; n<N_FILMES; n++)
mostrafilme (films[n]);
return 0;
}

void mostrafilme (filmes_t filme)
{
cout << filme.titulo;
cout << ” (” << filme.ano << “)\n”;
}

, o que retorna na tela:

Insira o título: Blade Runner
Insira o ano: 1982
Insira o título: Matrix
Insira o ano: 1999
Insira o título: Taxi Driver
Insira o ano: 1976

Você inseriu estes filmes:
Blade Runner (1982)
Matrix (1999)
Taxi Driver (1976)

Ponteiros para Estruturas

Como qualquer outro tipo, estruturas podem ser apontadas por seus próprios tipos de ponteiros.

struct filmes_t {
string titulo;
int ano;
};

filmes_t afilme;
filmes_t * pfilme;

Aqui, afilme é um objeto de estrutura que tem o tipo filmes_t, e pfilme é um ponteiro para objetos da estrutura filmes_t. Então, o seguinte código também seria válido:

pfilme = &afilme;

O valor do ponteiro pfilme seria atribuído à referência ao objeto afilme (seu endereço de memória). Um outro exemplo incluíndo ponteiros, que servirão para introduzir o operador flecha (->).

#include <iostream>
#include <string>
#include <sstream>
using namespace std;

struct filmes_t {
string titulo;
int ano;
};

int main ()
{
string str;

filmes_t afilme;
filmes_t * pfilme;
pfilme = &afilme;

cout << “Insira um título: “;
getline (cin, pfilme->titulo);
cout << “Insira o Ano: “;
getline (cin, str);
(stringstream) str >> pfilme->ano;

cout << “\nVocê inseriu:\n”;
cout << pfilem->titulo;
cout << ” (” << pfilme->ano << “)\n”;

return 0;
}

, o que retorna na tela:

Insira um título: Titanic
Insira o Ano: 1997

Você inseriu:
Titanic (1997)

A flecha do código anterior (->) é um operador de referência que é usado exclusivamente com ponteiros para objetos com membros. Este operador serve para acessar um membro de um objeto para o qual temos uma referência. No exemplo usamos:

pfilme->titulo

, que é equivalente a:

(*pfilme).titulo

Ambas as expressões são válidas e ambas significam que estamos avaliando o membro titulo da estrutura de dados apontada por um ponteiro chamado pfilme. Deve ser diferenciada de:

*pfilme.titulo

,que é equivalente a

*(pfilme.titulo)

, e acessaria o valor do ponteiro por um membro de um ponteiro hipotético chamado titulo do objeto da estrutura pfilme (que neste caso não seria um ponteiro). O seguinte quadro resume possíveis combinações de ponteiros e membros de estruturas:

Expressão O que é avaliado Equivalente
a.b Membro b do objeto a
a->b Membro b do objeto apontado por a (*a).b
*a.b Valor apontado pelo membro b do objeto a *(a.b)

Estruturas Aninhadas

Estruturas também podem ser aninhadas:

struct filmes_t {
string titulo;
int ano;
};

struct amigos_t {
string nome;
string email;
filmes_t favorito_filme;
} fulano, beltrano;

amigos_t * pamigos = &fulano;

Após a declaração anterior, podemos usar qualquer uma das seguintes expressões:

fulano.nome
beltrano.favorito_filme.titulo
fulano.favorito_filme.ano
pamigos->favorito_filme.ano

onde, por sinal, as duas últimas expressões referem-se ao mesmo membro.

Tipos de Dados Definidos (typedef)

Podemos definir nossos próprios tipos baseados em tipos existentes no C++. Para isto, usaremos a palavra-chave typedef, que tem a seguinte sintaxe:

typedef [tipo_existente] [novo_tipo];

Exemplo:

typedef char C;
typedef char field [40];
typedef unsigned int WORD;
typedef char * pChar;

Aqui, definimos, respectivamente: C (char), field (char[40]), pChar (char*) e WORD (unsigned int). Com isto, poderíamos usá-los em declarações posteriores. Exemplo:

C carac, carac2, *ppc1;
WORD vpal;
pChar ppc2;
field nome;

É importante notar que typedef não cria um tipo diferente, mas apenas cria sinônimos os tipos existentes. Assim, vpal pode ser considerada ou WORD ou unsigned int, já que os dois são na realidade o mesmo tipo. Typedef pode ser útil para definir um alias para um tipo que é freqüentemente utilizado em um programa. Também é útil para definir tipos quando é possível que precisamos modificar o tipo em versões posteriores do nosso programa, ou se um tipo que queremos usar tem um nome que é muito longo ou confuso.

Uniões (Unions)

Permitem uma mesma porção de memória ser acessada como diferentes tipos de dados, desde que todos são na verdade o mesmo local na memória. Sua declaração e uso é similar a das estruturas mas sua funcionalidade é totalmente diferente:

union nome_da_uniao {
tipo_do_membro1 nome_do_membro1;
tipo_do_membro2 nome_do_membro2;
tipo_do_membro3 nome_do_membro3;
.
.
} nomes_dos_objetos;

Todos os elementos da declaração da união ocupam o mesmo espaço físico na memória. Seu tamanho é um dos maiores elementos da declaração. Por exemplo:

union tipos_t {
char c;
int i;
float f;
} tipos;

define três elementos:

tipos.c
tipos.i
tipos.f

cada um com um tipo de dado diferente. Como todos estão se referindo ao mesmo local na memória, a modificação de um dos elementos afetará o valor de todos eles. Não podemos armazenar valores diferentes neles independentemente. Um dos usos do union é unir um tipo elementar com um array ou estrutura de elementos menores. Exemplo:

union mix_t {
long l;
struct {
short hi;
short lo;
} s;
char c[4];
} mix;

define três nomes que nos permitem acessar o mesmo grupo de 4 bytes: mix.l, mix.s, mix.c e que podemos usar de acordo com como queremos acessar esses bytes, como se fossem um tipo long de dados, ou dois elementos short, ou mesmo um array de elementos char, respectivamente.

O exato alinhamento e ordem dos membros de uma union na memória depende da plataforma. Assim, é importante levarmos em conta possiveis erros de portabilidade com este tipo de uso.

Uniões Anônimas (Anonymous unions)

Se declararmos uma union sem um nome, ela será uma união anônima e será capaz de acessar seus membros diretamente por seus nomes. Veja a diferença:

-estrutura com union usual

struct {
char titulo[50];
char autor[50];
union {
float dolares;
int ienes;
} preco;
} livro;

-estrutura com union anônima

struct {
char titulo[50];
char autor[50];
union {
float dolares;
int ienes;
};
} livro;

A única diferença entre os dois códigos é que no primeiro demos um nome para a union (preco) e no segundo não. A diferença é vista quando nós acessamos os membros dolares e ienes de um objeto deste tipo. Para um objeto do primeiro tipo, seria:

livro.preco.dolares
livro.preco.ienes

Em contrapartida, para um objeto do segundo tipo, seria:

livro.dolares
livro.ienes

Lembrando novamente, por serem uma union e não uma struct os membros dolares e ienes ocupam o mesmo espaço físico na memória de maneira que eles não podem ser usados para armazenar dois diferentes valores simultaneamente. Podemos setar um valor para preco em dolares ou em ienes, mas não em ambos.

Enumerações (enum)

Enumerações criam novos tipos de dados para conter algo diferente que não é limitado aos valores que tipos de dados fundamentais podem ter.
Têm forma:

enum nome_da_enumeracao{
valor1,
valor2,
valor3,
.
.
} nomes_dos_objetos;

Por exemplo, podemos criar um novo tipo de variável chamada cores para armazenar as cores com a seguinte declarações:

enum cores_t {preto, azul, verde, cinza, vermelho, roxo, amarelo, branco};

Note que não incluímos nenhum tipo de dado fundamental na declaração. O que fizemos foi criar todo um novo tipo de dado sem nos basearmos em nenhum outro tipo existente. Os valores possíveis que variáveis do novo tipo cores_t podem ter são novos valores constantes incluídos entre aspas {}. Por exemplo, serão válidas após a declaração anterior:

cores_t mycolor;

cor = azul;
if (cor == verde) cor = vermelho;

Enumerações são tipos compatíveis com variáveis numéricas, então suas constantes são sempre atribuídas a um valor numérico inteiro internamente. Se não for especificado, o valor inteiro equivalente ao primeiro valor possível é equivalente a 0 e os seguintes seguem uma progressão de +1. Assim, em nosso tipo cores_t, o preto, por exemplo, seria equivalente a 0, azul equivalente a 1, verde a 2 e assim sucessivamente. Podemos especificar um valor inteiro para cada um dos valores constantes que nosso tipo enumerado pode ter. Se o valor que o segue não é um valor inteiro, é automaticamente assumido o mesmo valor que o anterior mais 1. Exemplo:

enum meses_t { janeiro=1, fevereiro, marco, abril,
maio, junho, julho, agosto,
setembro, outubro, novembro, dezembro} y2k;

Neste caso, a variável y2k do tipo enumerado meses_t pode conter qualquer uma das 12 possibilidades, que vão de janeiro a dezembro e que são equivalentes a valores entre 1 e 12 (não entre 0 e 11, já que fizemos janeiro ser igual a 1) .


Anúncios
%d blogueiros gostam disto: