Você está na página 1de 6

o

Fundamentos de Programac
a
neos
Tipos de Dados Compostos Heteroge
Depto. de Estatstica e Matematica Aplicada
Universidade Federal do Ceara
Semestre 2015.1
Profssor: Tiberius O. Bonates (tb@ufc.br)

Registros

O registro (ou tipo composto heterogeneo) e um recurso da linguagem que permite a criacao de
um novo tipo de dados, alem dos tipos primitivos (por exemplo, bool, float) e do tipo composto
homogeneo (vetor). O registro permite ao programador agregar duas ou mais variaveis (cujos tipos
podem ser tipos b
asicos da linguagem ou outros tipos compostos) que possuem alguma relacao logica
entre si. Em geral, um registro e utilizado com o proposito de armazenar conjuntamente duas ou
mais vari
aveis que possuem uma relac
ao l
ogica entre si. Como exemplo, considere o armazenamento
de dois n
umeros reais que representam um par ordenado. Uma opcao e proceder da seguinte forma:
int main() {
float x; // valor da abcissa
float y; // valor da ordenada
return 0;
}
Contudo, neste caso, as duas vari
aveis nao estao formalmente associadas entre si no programa.
A u
nica associac
ao existente entre as mesmas e uma associacao informal, feita mentalmente pelo
programador. Este tipo de situac
ao e uma porta de entrada para erros no programa, especialmente
quando temos muitas vari
aveis e um c
odigo longo. Afinal, apos algumas paginas de codigo, pode ser
difcil lembrar que x e y n
ao eram vari
aveis individuais, mas duas variaveis que estavam intrinsicamente ligadas para formar um par ordenado.
O registro e um recurso de C/C++ que oferece uma alternativa para essa situacao. O registro e
um novo tipo de vari
avel que passa a estar disponvel para o programador, da mesma forma que um
int, um float, etc. No caso de um par ordenado, podemos chamar este novo tipo de ParOrdenado,
j
a que este nome remete diretamente ao conceito que desejamos representar. O registro e uma
entidade u
nica, que contem duas vari
aveis de ponto flutuante como partes integrantes. Eis sua
definic
ao e uma declarac
ao de vari
avel do novo tipo:
// Definicao do novo tipo:
struct ParOrdenado {
float x; // valor da abcissa
float y; // valor da ordenada
};
1

int main() {
// Declaracao de uma variavel do novo tipo ParOrdenado
ParOrdenado par; // "par" e uma variavel do tipo ParOrdenado
return 0;
}
Naturalmente, ao se criar um registro e desejavel se acessar as variaveis individuais que o constituem, que s
ao chamadas de campos ou atributos do registro. Este acesso acontece por meio de
um deslocamento relativo `
a posic
ao inicial onde o registro esta armazenado, similar ao que acontece
durante o acesso `
as posic
oes de um vetor. Na definicao de um registro, os identificadores e os tipos
de todos os campos que comp
oem o registro sao especificados. Na manipulacao de um registro, os
campos constituintes podem ser acessados por meio de seus identificadores. Em C/C++, a sintaxe
e: <identificador da variavel>.<identificador do campo>. A seguir, mostramos uma versao
mais elaborada do exemplo acima:
struct ParOrdenado {
float x; // valor da abcissa
float y; // valor da ordenada
};
int main() {
ParOrdenado par;
par.x = 5.2; //
//
par.y = 3.01; //
//

// "par" e uma variavel do novo tipo ParOrdenado


identificador da variavel: "par"
identificador do campo:
"x"
identificador da variavel: "par"
identificador do campo:
"y"

cout << "O par ordenado e: (" << par.x << "," << par.y << ")." << endl;
return 0;
}
importante notar que C/C++ n
E
ao oferece funcionalidade para se realizar certas operacoes
elementares sobre registros, tais como as operacoes de soma, subtracao, produto, etc, que estao
definidas para o tipo int, por exemplo. Muito embora a soma de dois pares ordenados possa estar
bem definida em um contexto matem
atico, ela nao e realizada automaticamente pela linguagem de
programac
ao. No entanto, e possvel se fazer a operacao de atribui
c
ao entre registros do mesmo
tipo. No c
odigo abaixo, os campos do registro x receberao copias dos valores armazenados nos
campos do registro y.
struct novotipo {
int valorInt;
string palavra;
};
int main() {
struct novotipo reg1, reg2;
reg1.palavra = "teste";
reg1.valorInt = 4;
reg2 = reg1; // Os campos de reg2 recebem os valores dos campos de reg1
return 0;
}
2

Embora n
ao seja possvel realizar operacoes de soma, produto, etc, automaticamente com registros, e sempre possvel realizar explicitamente a operacao desejada, utilizando-se de acesso direto aos
campos do registro. No c
odigo a seguir, por exemplo, mostramos como armazenar em um registro
ParOrdenado P3 o resultado da soma de dois outros registros do tipo ParOrdenado, P1 e P2. Neste
exemplo, estamos considerando que a soma de dois pares ordenados, p1 e p2 , e um par ordenado p3
obtido a partir da soma das coordenadas correspondentes de p1 e p2 .
struct ParOrdenado {
float x; // valor da abcissa
float y; // valor da ordenada
};
int main() {
ParOrdenado P1, P2, P3;
P1.x = 5.2; P1.y = 3.01;
P2.x = 0.7; P2.y = 12.0;
P3.x = P1.x + P2.x;
P3.y = P1.y + P2.y;
cout << "O par ordenado P3 e: (" << P3.x << "," << P3.y << ")." << endl;
return 0;
}
O uso da palavra struct ao se criar variaveis de tipo registro e opcional em C++ (porem,
obrigat
orio em C). Assim, a instruc
ao struct novotipo reg1, reg2; que cria duas variaveis do
tipo novotipo poderia ser escrita em C++ apenas como novotipo reg1, reg2;. Uma construcao
comum em exemplos de definic
ao de registros e mostrado abaixo. O ponto a ser observado neste
exemplo e que e possvel criar uma ou mais variaveis do tipo registro que esta sendo definido,
juntamente com a pr
opria definic
ao do tipo (as variaveis var1 e var2 sao do tipo Teste). Apesar
de comum em exemplos, este n
ao e um uso tao frequente, ou necessario, na pratica. A diferenca
entre se criar vari
aveis desta forma e que tais variaveis estao em um escopo global, mais geral do
que o escopo do programa principal (main). Desta forma, tais variaveis estao visveis para quaisquer
outros subprogramas, e n
ao apenas para o programa principal main (essa afirmacao ficara mais clara
quando estudarmos subprogramas, tambem chamados de func
oes).
struct Teste {
int valorInt;
float valorFloat;
} var1, var2;
int main() {
var1.valorInt = 4;
return 0;
}

var1.valorFloat = 6.2;

Um registro pode conter outro registro como membro. Digamos que desejamos definir um registro chamado B, que contem dentro de si um campo cujo tipo e um registro do tipo A. Uma restricao
em relac
ao a isso e que a definic
ao de A deve acontecer antes da definicao de B. Caso contrario, o
compilador rejeita a tentativa de definic
ao do registro B. Em outras palavras, e necessario definir
o registro A (declarar seu nome, alem do tipo e dos nomes de seus campos) antes de definirmos o
registro B, para que o compilador possua todas as informacoes necessarias para definir B.

1.1

Inicializac
ao

Ate agora, atribuimos valores aos campos de um registro utilizando o operador ponto (.), ou por
possivel fazer a inicializacao de um registro de uma
intermedio do operador de atribuic
ao (=). E
s
o vez, fornecendo uma lista de valores, de forma similar ao que aprendemos a fazer com vetores.
Os valores de tal lista s
ao atribudos aos campos do registro em questao na mesma ordem em que os
campos s
ao definidos, conforme exemplo a seguir. Note que a linguagem verifica se os valores em tal
lista s
ao compatveis com os tipos dos respectivos campos do registro. Portanto, e importante ter
atenc
ao `
a ordem em que os campos s
ao especificados na definicao do registro e aos tipos dos valores
que colocamos nesta lista. A inicializac
ao via lista de valores funciona apenas no ato da criacao da
vari
avel; n
ao depois. O exemplo da vari
avel beltrano abaixo ilustra esse caso.
struct Pessoa {
int idade;
string sobrenome;
};
int main() {
Pessoa fulano = {19, "Andre"};
Pessoa ciclano = {"Barnabe", 20}; // Isso resulta em erro!
Pessoa beltrano;
beltrano = {19, "Andre"}; // Isso tambem resulta em erro!
return 0;
}

1.2

Registros Auto-Referenciados

Um registro pode conter, como um de seus campos, outro registro. No entanto, em C++ nao e
permitida a declarac
ao de um registro que contenha um campo do mesmo tipo do registro (ou um
campo de um tipo que contenha uma variavel do tipo do registro). A razao para este fato e que
n
ao haveria uma forma razo
avel de se representar uma estrutura desta natureza em memoria. No
entanto, e possvel se construir registros que possuem ponteiros para registros do mesmo tipo.
Um registro com esta caracterstica e conhecido como um registro auto-referenciado. Registros
auto-referenciados s
ao muito comuns nas definicoes de estruturas de dados dinamicas, tais como
listas encadeadas,
arvores, grafos, etc. A seguir, fornecemos um exemplo de um registro que poderia
ser utilizado para a implementac
ao de uma lista simplesmente encadeada, conforme veremos mais
adiante.
struct Objeto {
int dado;
Objeto * proximo;
};
O campo proximo possui uma vari
avel do tipo ponteiro que aponta para outro registro do tipo
Objeto. Desta forma, embora o registro nao possua, de fato, uma variavel do tipo Objeto dentro
de si, ele possui uma referencia a um registro Objeto externo. Este artifcio permite a definicao
de uma sequencia de registros, na qual um registro faz referencia a outro registro, que por sua vez
referencia outro registro, e assim por diante. Varias estruturas de dados sao definidas por meio do
uso de registros auto-referenciados similares a Objeto.

No caso de um registro X possuir um membro do tipo registro Y, e de o registro Y conter como


membro um ponteiro para um registro do tipo X, temos uma situacao conhecida como referencia
circular, onde X precisa conhecer a definic
ao de Y, e Y precisa conhecer a declarac
ao 1 de X. Isto e, Y
n
ao precisa conhecer os campos de X, mas precisa saber que existe um tipo de registro com o nome
X. Isto e feito atraves da declarac
ao do nome de um registro de forma antecipada `a sua definicao.
Desta maneira, durante a definic
ao do registro Y o compilador ja conheceria o nome do registro X
para o qual Y conteria um ponteiro. O c
odigo abaixo exemplifica a situacao:
struct X; // declaracao antecipada
struct Y {
int dado;
X *ponteiro;
};
struct X {
int informacao;
Y outro;
};
Note aqui a diferenca entre a inclus
ao de um membro que e um registro de certo tipo e a inclusao
de um ponteiro para um registro daquele tipo. No primeiro caso, o compilador precisa conhecer
a exata definic
ao do registro para reservar o espaco de memoria necessario para a criacao de uma
vari
avel daquele tipo de registro. No segundo caso, nao e necessario conhecermos exatamente a
definic
ao do tipo (apenas a declarac
ao de seu nome), ja que ponteiros possuem um tamanho fixo,
independente do tipo de estrutura para onde apontam.

Enumera
co
es (leitura opcional)

Uma enumerac
ao e um tipo de dado que contem um conjunto de valores nominais. O proposito de
se utilizar uma enumerac
ao e o de permitir a escolha de um valor constante dentre um conjunto
limitado de opc
oes. Como exemplo, considere o conjunto de naipes em um baralho. O seguinte
exemplo ilustra o conceito:
enum Naipe { OUROS, PAUS, COPAS, ESPADAS };
int main() {
Naipe x = COPAS;
}
Internamente, cada item em um tipo enumeracao e representado como um valor inteiro, comecando
a partir do valor 0 (zero). Desta forma, no codigo acima, OUROS e igual a 0, PAUS e igual a 1, e
assim por diante. O seguinte c
odigo imprimiria a mensagem COPAS igual a 2 na tela:
Naipe x = COPAS;
if (x == COPAS)
cout << "O naipe de x e Copas" << endl;
possvel alterar este comportamento ao se especificar explicitamente valores para cada item:
E
enum Naipe { OUROS = 1, PAUS = 3, COPAS = 5, ESPADAS = 7 };
1 Considere

a declara
c
ao de um novo tipo de registro, de nome X, como um an
uncio de que estamos introduzindo
o conceito de um novo tipo, chamado X. A defini
c
ao propriamente dita de X (que consiste em especificar os tipos e
nomes de seus campos) pode acontecer juntamente de sua declarac
ao, ou ent
ao em uma posic
ao adiante no c
odigo.

Um valor do tipo enumerac


ao Naipe pode ser utilizado como inteiro para proposito de atribuicoes,
comparac
oes ou mesmo operac
oes aritmeticas.
enum Naipe { OUROS = 1, PAUS = 3, COPAS = 5, ESPADAS = 7 };
int main() {
Naipe x = OUROS;
int y = x + 4;
if (y == COPAS) cout << "Copas" << endl;
}

Uni
oes (leitura opcional)

Uma uni
ao e um tipo de estrutura que permite o armazenamento de um tipo de dado dentre uma
lista de dois ou mais tipos numa certa regiao de memoria. Em outras palavras, uma uniao reserva
um espaco de mem
oria que e suficiente para armazenar dois ou mais diferentes tipos de dados, mas
que s
o ser
a efetivamente ocupado por um tipo de dados por vez. Diferentemente de um registro, que
requer espaco para armazenar cada um de seus membros, uma uniao requer apenas a quantidade de
mem
oria necess
aria para armazenar o maior dos membros da uniao.
O interesse em se utilizar este tipo de estrutura e o de evitar o desperdcio de memoria quando
se sabe que apenas um dos valores de um registro sera efetivamente usado. Um cuidado especial que
se deve ter ao se utilizar uni
oes e o de saber com precisao qual o tipo de dado que foi armazenado
mais recentemente na uni
ao, pois um valor armazenado na uniao sobrescreve qualquer outro valor
previamente armazenado. Caso contr
ario, e possvel que se obtenha um valor incorreto, por exemplo,
na situac
ao em que um valor do tipo X foi armazenado na uniao, mas um valor do tipo Y foi lido
da uni
ao.
Observa
c
ao 3.1 Assim como no caso de registros, os itens armazenados em uma uni
ao podem ser
vari
aveis primitivas, ou mesmo tipos compostos, como um registro ou uma uni
ao.
O seguinte c
odigo ilustra a declarac
ao e uso de uma uniao.
union tipoNumerico {
int i;
double d;
};
int main() {
tipoNumerico t;
t.i = 39;
t.d = 3.52;
}

Exerccio
1. Tentar resolver todos os exerccios sobre registros que voce conseguir encontrar.

Você também pode gostar