Estructuras
Informática II (2012)
Estructuras
Una estructura es una colección de una o más
variables, de tipos posiblemente diferentes,
agrupadas bajo un solo nombre para manejarlas de
forma mas conveniente.
En otros lenguajes se suelen llamar registros.
Ayudan a organizar datos complicados, en
particular dentro de programas grandes, debido a
que permiten que a un grupo de variables
relacionadas se les trate como a una unidad en
lugar de como entidades separadas
Ejemplos típicos de estructuras
Un punto manejado por sus coordenadas
Un rectángulo manejado por dos puntos
opuestos en diagonal
La nómina de una empresa: un empleado
está descripto por un conjunto de atributos
tales como: nombre, domicilio, número de
CUIL, salario, etc.
Algunos de estos atributos, tales como el
domicilio, pueden a su vez ser estructuras
Estructuras
Las estructuras pueden inicializarse, copiarse
y asignarse, pasarlas a funciones como
argumentos o ser retornadas por ellas.
Conceptos básicos sobre estructuras
Definamos una
estructura para
graficación.
El objeto básico es un
punto que tiene
coordenadas x e y,
ambas enteras.
Conceptos básicos sobre estructuras
Los dos componentes pueden colocarse en
una estructura declarada así:
struct punto{
int x;
int y;
};
La palabra reservada struct presenta la
declaración de una estructura, que es una
lista de declaraciones entre llaves.
Conceptos básicos sobre estructuras
Un nombre optativo, llamado rótulo de estructura, puede seguir a
la palabra struct (como aquí lo hace punto). El rótulo da
nombre a esa clase de estructura y, en adelante, puede usarse
como una abreviatura para la parte de declaraciones entre
llaves.
Las variables nombradas dentro de la estructura se llaman
miembros. Un miembro de estructura o rótulo y una variable
ordinaria (es decir, no miembro) pueden tener el mismo nombre
sin conflicto, puesto que siempre pueden distinguirse por el
contexto. En diferentes estructuras pueden encontrarse los
mismos nombres de miembros, aunque por cuestiones de estilo
deberían usarse los mismos nombres sólo para objetos
estrechamente relacionados.
Conceptos básicos sobre estructuras
Una declaración struct define un tipo. La
llave derecha (}) que termina la lista de
miembros puede ser seguida de una lista de
variables, como se hace con cualquier tipo
de dato básico. Por ejemplo,
struct {…}x,y,z;
Declara a x, y, z como variables del tipo
nombrado (o listado entre llaves, en el caso
de estructuras anónimas) y causa que se les
reserve espacio
Conceptos básicos sobre estructuras
Una declaración de estructura que no está
seguida por una lista de variables (como en
punto) no reserva espacio de almacenamiento
sino que simplemente describe una plantilla o
la forma de una estructura.
Sin embargo si la declaración está rotulada, el
rótulo puede emplearse posteriormente para
definir instancias de la misma.
Por ejemplo: struct punto pt;
Define una variable pt que es una estructura
de tipo punto
Conceptos básicos sobre estructuras
Una estructura puede inicializarse al seguir su
definición con una lista de inicializadores, cada uno
es una expresión constante, para cada uno de los
miembros:
struct punto maxpt={320,200};
Inicializa la el miembro x de maxpt a 320 y el
miembro y de maxpt a 200
Una estructura local o automática también puede
inicializarse por asignación o llamando a una
función que regrese una estructura del tipo
adecuado
Conceptos básicos sobre estructuras
Se hace referencia a un miembro de una
estructura en particular, en una expresión,
con una construcción de la forma:
[Link]
El operador miembro de estructura “.”
conecta el nombre de la estructura con el
nombre del miembro. Por ejemplo, para
imprimir las coordenadas del punto pt:
printf(“%d,%d”, pt.x, pt.y);
Conceptos básicos sobre estructuras
O, para calcular la distancia del origen (0,0) a
pt:
double dist;
dist=sqrt((double)pt.x * pt.x +
(double)pt.y *pt.y);
Las estructuras pueden anidarse.
Se puede representar un rectángulo como un
par de puntos ubicados en esquinas
diagonalmente opuestas:
Conceptos básicos sobre estructuras
struct rectangulo{
struct punto pt1;
struct punto pt2;
};
Conceptos básicos sobre estructuras
La estructura rectangulo contiene dos
estructuras punto. Si declaramos pantalla
como un rectángulo particular:
struct rectangulo pantalla;
Entonces pantalla.pt1.x se refiere a la
coordenada x del miembro pt1 de pantalla
Estructuras y funciones
Las únicas operaciones legales sobre una
estructura son: copia o asignación como una
unidad, toma de su dirección con & o acceso
a sus miembros.
La copia y la asignación incluyen pasarlas
como argumento a funciones y retornar
valores de funciones.
Las estructuras no pueden compararse
Estructuras y funciones
Hay por lo menos tres acercamientos
posibles al usar funciones que manipulan
estructuras: pasar separadamente los
componentes (miembros), pasar una
estructura completa o pasar un puntero a
ella. Cada uno tiene sus puntos buenos y
malos.
Estructuras y funciones
La primera función generapunto, toma 2 enteros y
retorna una estructura punto:
/*generapunto: crea un punto con los
miembros inicializados con x e y */
struct punto generapunto (int x, int y)
{
struct punto temp;
temp.x=x;
temp.y=y;
return temp;
}
Estructuras y funciones
Nótese que no hay conflictos entre los
nombres de argumentos y los miembros con
los mismos nombres; incluso la reutilización
de los nombres refuerza el vínculo.
Ahora esta función puede usarse para
inicializar dinámicamente cualquier estructura
de tipo punto o para proporcionar
argumentos del tipo de la estructura a una
función:
Estructuras y funciones
struct rectangulo pantalla;
struct punto medio;
//inicializa los miembros del rectángulo pantalla
pantalla.pt1=generapunto(0,0);
pantalla.pt2=generapunto(XMAX, YMAX);
//genera el punto medio de la diagonal entre pt1 y
//pt2
medio=generapunto((pantalla.pt1.x+pantalla.pt2.x)/2,
(pantalla.pt1.y+pantalla.pt2.y)/2);
Estructuras y funciones
El siguiente paso es escribir un conjunto de operaciones para
hacer operaciones aritméticas sobre los puntos, por ejemplo:
/*sumapuntos: suma dos puntos*/
struct punto sumapuntos(struct punto p1, struct
punto p2)
{
p1.x +=p2.x;
p1.y +=p2.y;
return p1;
}
Estructuras y funciones
Aquí, tanto los argumentos como el valor de retorno
son estructuras. Incrementamos los componentes
en p1 en lugar de utilizar explícitamente una
variable temporal (de tipo punto), para hacer énfasis
en que los parámetros de la estructura son pasados
por valor como cualquier otro tipo de parámetro.
Como otro ejemplo, la función enrectangulo
prueba si un punto está dentro de un rectángulo, se
adopta la convención de que un rectángulo incluye
sus lados izquierdo e inferior pero no sus lados
superior y derecho:
Estructuras y funciones
/*enrectangulo: regresa 1 si p esta
en r, 0 si no lo esta*/
int enrectangulo (struct punto p,
struct rectangulo r)
{
return p.x >=r.pt1.x && p.x
<r.pt2.x &&
p.y >= r.pt1.y && p.y <r.pt2.y;
}
Estructuras y funciones
Esto supone que el rectángulo está
representado en una forma estándar en
donde las coordenadas pt1 son menores que
las coordenadas pt2. La siguiente función
regresa un rectángulo garantizado que está
en esta forma canónica:
Estructuras y funciones
#define min(a,b) ((a)<(b) ? (a): (b))
#define max(a,b) ((a)>(b) ? (a): (b))
/*canonrect: pone en forma canonica las
coordenadas de un rectangulo*/
struct rectangulo canonrect(struct rectangulo r)
{
struct rectangulo temp;
temp.pt1.x=min(r.pt1.x, r.pt2.x);
temp.pt1.y=min(r.pt1.y, r.pt2.y);
temp.pt2.x=max(r.pt1.x, r.pt2.x);
temp.pt2.y=max(r.pt1.y, r.pt2.y);
return temp:
}
Estructuras y funciones
Si se pasa a una función una estructura
grande, generalmente es más eficiente pasar
un puntero que copiar la estructura completa.
Los punteros a estructuras son como los
punteros a variables ordinarias.
La declaración struct punto *pp;
Dice que pp es un puntero a una estructura
de tipo punto. Si pp apunta a una estructura
punto, *pp es la estructura y (*pp).x y (*pp).y
son los miembros
Estructuras y funciones
Para emplear pp, podría escribirse, por ej.:
struct punto origen, *pp;
pp=&origen;
printf(“El origen es (%d,%d)\n”, (*pp).x,
(*pp).y);
Los paréntesis son necesarios en (*pp).x debido a
que la precedencia del operador miembro de
estructura . es mayor que la de *. La expresión
*pp.x significa *(pp.x), lo cual es ilegal puesto que x
no es un puntero sino que es un entero
Estructuras y funciones
Los punteros a estructuras se usan con tanta
frecuencia que se ha proporcionado una
notación alternativa como abreviatura. Si p
es un puntero a estructura, entonces
p->miembro de estructura
Se refiere a un miembro en particular. (El
operador -> es un signo menos seguido
inmediatamente por >)
Estructuras y funciones
Así podríamos haber escrito:
printf(“El origen es (%d,%d)\n”, pp-
>x, pp->y);
Tanto . como -> se asocian de izquierda a
derecha, de modo que si tenemos:
struct rectangulo r, *rp=&r;
Entonces estas 4 expresiones son
equivalentes:
Estructuras y funciones
r.pt1.x;
rp->pt1.x;
(r.pt1).x;
(rp->pt1).x;
Los operadores de estructuras . y -> junto
con ( ) para llamadas a funciones y [ ] para
subíndices, están arriba en la jerarquía de
precedencias y se asocian estrechamente.
Estructuras y funciones
Por ejemplo, dada la declaración:
struct{
int len;
char *str;
}*p;
entonces ++p->len;
Incrementa a len, no a p, puesto que los
paréntesis implícitos son ++(p->len);
Estructuras y funciones
Los paréntesis pueden emplearse para alterar la
asociación: (++p)->len; incrementa a p antes de
tener acceso a len, y (p++)->len; incrementa a p
después del acceso a len (este último conjunto de
paréntesis es innecesario)
De la misma manera *p->str; obtiene cualquier cosa
a la que str esté apuntando, *p->str++; incrementa a
str después de hacer el acceso a lo que apunta
(exactamente como *s++); (*p->str)++; incrementa
cualquier cosa a la que str apunte y *p++->str
incrementa a p después de hacer el acceso a lo que
str apunta
Arreglos de estructuras
En el siguiente ejemplo se observa como se
declara un arreglo de estructuras, definidas
de manera tal que, mantienen la información
necesaria para describir un intervalo
numérico y, la cantidad de veces que un
número generado al azar cae en ese
intervalo, como para armar un histograma.
(Ref.:
[Link]
)
Arreglos de estructuras
#include <stdlib.h>
#include <stdio.h>
#define NUM_INTER 100
struct Intervalo
{
/* Límite inferior del intervalo */
double Lower,
Upper; /* Límite superior del intervalo */
int Counter; /* Cantidad de puntos dentro de él*/
};
Arreglos de estructuras
main()
{
struct Intervalo Inter[NUM_INTER];
int Index;
//Delta: ancho intervalo
double Delta = RAND_MAX/NUM_INTER;
//RAND_MAX 32767, mayor [Link] rand()
/* Inicialización del vector de
estructuras */
Arreglos de estructuras
for (Index=0; Index<NUMINTER; Index ++)
{
Inter[Index].Counter = 0;
Inter[Index].Lower = Delta*Index;
Inter[Index].Upper = Delta*(Index+1);
}
Arreglos de estructuras
//genera 10000 nros. al azar
for (Index=0; Index<10000; Index++)
//incrementa contador del intervalo donde cae el nro.
//cuyo índice es determinado por:rand()/Delta
Inter[rand()/Delta].Counter++;
//rand() seudoaleatorio entre 0 y 32767
/* impresión de resultados
*/
for (Index=0; Index<NUM_INTER; Index++)
printf("[%lf,%lf]=%d/n", Inter[Index].Lower,
Inter[Index].Upper, Inter[Index].Counter);
}
Arreglos de estructuras
struct Item{
char material[50] ;
int existencia ;
double costo_unitario ;
};
//inicializo como cualquier array
Item stock1[3] = {
//tamaño stock1:(sizeof(stock1)/sizeof(struct Item))
//o (sizeof(stock1)/sizeof(stock1[0]), mejor
"tornillos" , 120 , .15 ,
"tuercas" , 200 , .09 ,
"arandelas" , 90 , .01
} ;
Estructuras
No hay que suponer que el tamaño de una
estructura es la suma de los tamaños de sus
miembros. Debido a requisitos de alineación
para diferentes objetos, podría haber
“huecos” no identificados dentro de la
estructura
El operador sizeof regresa el valor
apropiado
Estructuras
Punteros a estructuras
[Link]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Persona
{
char *nombre;
int edad;
int altura;
int peso;
};
struct Persona *creaPersona(char *name, int age, int height, int weight){
struct Persona *who = (Persona*) malloc(sizeof(struct Persona));
if(who != NULL){
who->nombre = strdup(name);//crea duplicado, hay que liberar memoria
who->edad = age;
who->altura = height;
who->peso = weight;
}
return who;}
Punteros a estructuras
void destruyePersona(struct Persona *who)
{
if(who != NULL){//libera memoria
free(who->nombre);//en este orden la destrucción
free(who);
}
}
void imprimePersona(struct Persona *who)
{
printf("Nombre: %s\n", who->nombre);
printf("\tEdad: %d\n", who->edad);
printf("\tAltura: %d\n", who->altura);
printf("\tPeso: %d\n", who->peso);
}
Punteros a estructuras
int main(void){
//crea dos personas
struct Persona *joe = creaPersona("Joe Alex", 32, 64, 140);
struct Persona *frank =creaPersona("Frank Blank",20,72,180);
//imprime y muestra donde están en memoria
printf("Joe esta en la direccion de memoria %p:\n", joe);
imprimePersona(joe);
printf("Frank esta en la direccion de memoria %p:\n",frank);
imprimePersona(frank);
Punteros a estructuras
//hace a las personas 20 años mayores e imprime
nuevamente sus datos
joe->edad += 20;
joe->altura -= 2;
joe->peso += 40;
imprimePersona(joe);
frank->edad += 20;
frank->peso += 20;
imprimePersona(frank);
//libera espacio de memoria
destruyePersona(joe);
destruyePersona(frank);
return 0;
}
Typedef
C proporciona una facilidad llamada typedef
(especificador de categoría de almacenamiento)
para crear nuevos tipos de datos. Ejemplo:
typedef int Longitud;
Hace del nombre Longitud un sinónimo de int
El tipo Longitud puede emplearse en
declaraciones, casts, etc., exactamente de la misma
forma en que lo podría hacer int
Longitud len, maxlen;
Longitud *lengths[ ];
Typedef
De modo semejante, la declaración:
typedef char *Cadena;
Hace a Cadena un sinónimo para char* o puntero a
carácter
Cadena p, lineptr[MAXLINES], alloc(int);
int strcmp(Cadena, Cadena);//prototipo
p=(Cadena)malloc(100);
El tipo declarado en un typedef aparece en la
posición del nombre de variable, no justo después
de la palabra typedef
Normalmente se usan los nombres con mayúsculas
para destacarlos
Typedef
Una declaración typedef no crea un nuevo
tipo en ningún sentido; simplemente agrega
un nuevo nombre para algún tipo ya
existente
Las variables declaradas de esta manera
tienen exactamente las mismas propiedades
que las variables cuyas declaraciones se
escriben explícitamente
Typedef
Además de razones puramente estéticas,
hay dos motivos principales para emplear
typedef:
La primera es, parametrizar un programa
contra los problemas de transportabilidad. Si
se emplea typedef para tipos de datos que
pueden ser dependientes de la máquina,
cuando un programa se traslada, sólo los
typedef requieren de cambios.
Typedef
Un segundo propósito es proporcionar mejor documentación del
programa
Uso común con enumeraciones:
typedef enum {FALSE=0, TRUE} Boolean;
Boolean flag = TRUE;
Con estructuras:
typedef struct telephone
{
char *name;
int number;
}TELEPHONE;
TELEPHONE index;
typedef TELEPHONE *pf; //puntero a la estructura TELEPHONE
Uniones
Una unión es una variable que puede
contener (en momentos diferentes) objetos
de diferentes tipos y tamaños y, el
compilador hace el seguimiento del tamaño y
requisitos de alineación
Las uniones proporcionan una forma de
manipular diferentes clases de datos dentro
de una sola área de almacenamiento, sin
incluir en el programa ninguna información
dependiente de la máquina
Uniones
#include <stdio.h>
union marks
{
float perc;//nota en %
char grade; //nota en letras
};
int main ( )
{
union marks student1;
[Link] = 98.5;
//dirección como unsigned long,alineado [Link] ancho 16
printf(" La nota es: %f, la direccion es: %16lu\n",
[Link], &[Link]);
[Link] = 'A';
printf("En letras: %c, la direccion es: %16lu\n",
[Link], &[Link]);
return 0;
}
Uniones
La variable marks será suficientemente
grande como para mantener al mayor de los
tres tipos: el tamaño específico depende de
la implantación.
Cualquiera de los tipos de los miembros
puede asignarse, por ejemplo a student1 y
luego emplearse en expresiones, mientras
que el uso sea consistente
Uniones
Sintácticamente se tiene acceso a miembros
de una unión como:
[Link] o,
Puntero-union->miembro
Al igual que en las estructuras
Las operaciones que pueden realizarse con
uniones: Asignar una unión a otra del mismo
tipo, tomar la dirección (&) de la unión,
acceder a un miembro
Uniones
Las uniones pueden presentarse dentro de
estructuras y arreglos y viceversa.
La notación para tener acceso a un miembro
de una unión en una estructura (o viceversa)
es idéntica a la de estructuras anidadas
Una unión sólo puede inicializarse en la
declaración con un valor del tipo del primer
campo o miembro, en el ejemplo anterior,
con un float
Uniones
Muy usadas en la programación de sistemas
embebidos o en situaciones donde sea necesario
acceder al hardware/memoria directamente
typedef union
{
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
unsigned int dword;
} HW_Register;
HW_Register reg;
Uniones
E intentar acceder a reg:
[Link] = 0x12345678;
[Link].byte3 = 4;
Uniones [Link]
Uniones
[Link]
#include <stdio.h>
enum account_type{personal = 1, business = 2};
/* person name */
struct person
{
char* name;
};
/* company with name and tax no*/
struct company
{
char* name;
char* tax_no;
};
Uniones
typedef union
{
struct person individual;
struct company biz;
} profile;
typedef struct
{
char* username;
char* password;
enum account_type type;
profile info;
} account;
void display(account acc);
Uniones
int main()
{
printf("Union Demo\n");
account acc1, acc2;
[Link] = personal;
[Link] = "acc1";
[Link] = "secret";
[Link] = "John Doe";
display(acc1);
[Link] = business;
[Link] = "acc2";
[Link] = "secret2";
[Link] = "My Company";
[Link].tax_no = "112121";
display(acc2);
return 0;
}
Uniones
/*
displays account on screen*/
void display(account acc){
switch([Link])
{
case personal:
printf("Personal Account\n");
printf("username:%s\nname:
%s\n",[Link],[Link]);
break;
case business:
printf("Business Account\n");
printf("username:%s\ncompany:%s\ntax no.:%s\n
",[Link],[Link],[Link].tax_no);
break;
}
printf("-------------------------------\n");
}
Con punteros y estructuras…
#include <stdio.h>
#define PI 3.141592
struct square{ int side;};
struct circle{ int radius;};
struct rectangle{
int side1;
int side2;
};
union shape_union{
struct rectangle *rect;
struct square *squa;
struct circle *circ;};
enum shape_type { RECTANGLE, SQUARE, CIRCLE };
struct shape_struct{
union shape_union shape_u;
shape_type st;};
typedef struct shape_struct shape;
Con punteros y estructuras…
double area(shape *s){
if(s == NULL)
return -1;
switch(s->st) {
case RECTANGLE:
return (s->shape_u.rect->side1) * (s-
>shape_u.rect->side2);
case SQUARE:
return (s->shape_u.squa->side) * (s-
>shape_u.squa->side);
case CIRCLE:
return (s->shape_u.circ->radius) * (s-
>shape_u.circ->radius)* PI;
default: /* error */
return -1; }}
Con punteros y estructuras…
int main(){
struct circle c,*pc;
[Link]=1.0;
shape_type mitipo=CIRCLE;
pc=&c;
union shape_union su;
[Link]=pc;
shape s,*ps;
s.shape_u=su;
[Link]=mitipo;
ps=&s;
printf("El area del circulo unitario es:
%lf\n",area(ps));
return 0;}
Campos de bits
Cuando el espacio de almacenamiento es
escaso, puede ser necesario empaquetar
varios objetos en una única palabra de
máquina
Se utilizan como estructura de datos (con
formatos impuestos externamente),
normalmente, como interfaz con dispositivos
hardware: tarjetas, controladores, puertos de
comunicaciones, etc.
Campos de bits
C ofrece la posibilidad de definir y acceder a bits
individuales (o en grupos) dentro de una palabra de
máquina
Un campo de bits es un conjunto de de bits
adyacentes dentro de la unidad de almacenamiento
(palabra)
struct Puertos{
unsigned Ch0: 1; // Rango 0,1
unsigned Ch1: 2; // Rango 0 a 3
unsigned Ch2: 3; // Rango 0 a 7
unsigned Ch3: 1; // Rango 0,1 };
Campos de bits
Se define en el ejemplo una tabla de
variables llamada Puertos que contiene 4
campos de bits
Los números que siguen a :, representan el
ancho del miembro en bits
Los miembros o campos individuales se
referencian de la misma forma que en el
caso de cualquier otra estructura o unión:
Campos de bits
struct Puertos PU_1;
PU_1.Ch0 = 0;
PU_1.Ch1 = 3;
PU_1.Ch2 = 5;
PU_1.Ch3 = 1;
Los campos se comportan como enteros
pequeños y pueden participar en expresiones
aritméticas como cualquier entero
Campos de bits
Contenido en memoria de la estructura de campos de bits
Campos de bits
Casi todo lo referente a campos de bits es
dependiente de la implementación
Los campos no necesitan tener nombres, en
caso de campos anónimos (sólo figuran los :
seguido del ancho del campo de bits) se
utilizan como relleno
El ancho especial 0 se utiliza para forzar
alineamiento con el límite de la siguiente
palabra
Campos de bits
Los campos sólo pueden declararse como
enteros (por razones de portabilidad), debe
definirse explícitamente si son signed o
unsigned
No son arrays, no tienen direcciones así que
no se les puede aplicar el operador &
En algunas plataformas los campos son
asignados de izquierda a derecha y en
otras,al revés
Campos de bits
O sea, aunque son útiles para mantener
estructuras de datos definidas internamente,
este tema tiene que ser considerado
cuidadosamente cuando se utilizan para
almacenar datos con formatos impuestos
externamente
Los programas que se basan en tales
consideraciones no son portables
Campos de bits
Campos de bits
Si a veces quiero
acceder a los campos
de bits individuales y a
veces al registro
completo:
Dos formas de leer la
misma posición de
memoria
Campos de bits
Campos de bits
Muy usados para datos en zonas especiales del espacio de
direcciones de memoria dedicados a los registros de
control del hardware (entrada y salida mapeada en
memoria)
Para acceder a estos registros uso punteros. Si el Port D
Data Register (PTD) está en la dirección de memoria 0x03,
podría definir el puntero:
volatile char *PTD = (char*)0x03;
Se declara volatile porque su valor puede cambiar en
cualquier momento aún por causas externas al programa
(interrupciones)
El cast es necesario para evitar errores de compilación
Campos de bits
También el registro podría
manejarse usando campos
de bits