documentación para el trabajo con la plataforma guadalbot

Anuncio
DOCUMENTACIÓN PARA EL
TRABAJO CON LA
PLATAFORMA GUADALBOT
I.E.S VIRGEN DE LAS
NIEVES
Programación C para microcontroladores
Tema 5. Estructuras, uniones, matrices y punteros
Índice de contenidos
Estructuras............................................................................................................................................2
Uniones.................................................................................................................................................2
Matrices................................................................................................................................................3
Punteros................................................................................................................................................4
Dirección (Lvalue)...........................................................................................................................5
Valor (Rvalue)..................................................................................................................................5
Relación entre vectores (matrices) y punteros......................................................................................6
1
Estructuras
Podemos declarar variables discretas, matriciales y punteros para almacenar uno o varios
valores, pero dichos datos deben ser siempre del mismo tipo. Seria deseable poder
disponer de la posibilidad de declarar datos que estén relacionados entre sí y que no sean
del mismo tipo, como por ejemplo los datos de una agenda. Es evidente que se pueden
declarar tantas variables como necesitemos para nuestra agenda o bien una matriz con
todos los datos de tipo string, pero ambas cosas no resultan totalmente satisfactorias.
Existe un método bastante cómodo de relacionar datos del tipo descrito mediante la
definición de una estructura de datos. Podríamos decir que una estructura es una
asociación en un mismo tipo de múltiples datos que pueden ser de cualquiera de los tipos
vistos; por lo que podemos considerar que una estructura también es un tipo de dato. La
sintaxis de una estructura es la siguiente:
struct [<nombre y tipo de estructura>] {
[<tipo> <nombre-variable[,nombre-variable, ...]>] ;
…
…
…
} [<variables-Estructura>] ;
Donde: <nombre y tipo de estructura> es un nombre opcional de etiqueta que se refiere al
tipo de estructura y <variables-Estructura> es la definición de datos, también opcional.
Aunque ambos elementos sean opcionales al menos debe aparecer uno de ellos. Se
definen elementos nombrando un tipo seguido del nombre de una o más variables
separadas por comas y separando los distintos tipos de variables con punto y coma. El
acceso a elementos o miembros de una estructura se realiza mediante el operador (->).
Uniones
Los tipos unión son tipos derivados que comparten muchas de las características
sintácticas y funcionales de los tipos estructura. La diferencia dominante es que una unión
solo permite que uno de sus miembros este "activo" en un momento dado. El tamaño de
una unión es el tamaño de su miembro más grande. El valor de cualquiera de sus
miembros se puede almacenar en cualquier momento.
En el ejemplo siguiente se realiza una declaración de una unión.
union mi_union {
int Entero;
char Caracter;
} mu;
El acceso a miembros de mi_union se realiza con el operador o selector (->).
La sintaxis de declaración de uniones es similar a la de estructuras, siendo las principales
diferencias las siguientes:
• Las uniones pueden contener campos a nivel de bit, pero solamente uno puede
estar activo.
• En cualquier caso las estructuras de C y los tipo unión de C no pueden utilizar los
especificadores de acceso a clases: public, private, y protected. Todos los campos
de una unión son de tipo public.
• Las uniones se pueden inicializar solamente a través de su primer miembro
declarado:
union local {
int Entero;
float Doble;
} a={ 20 };
•
Una unión no puede participar en clases jerárquicas, no puede ser derivada de ninguna clase
2
ni puede ser una clase base. Una unión debe tener un constructor.
Una de las principales diferencias con las estructuras radica en que una unión es una
región de almacenamiento compartida por dos o más miembros lo que permite manipular
diferentes tipos de datos utilizando una misma zona de memoria, es decir que los
miembros de una unión no tiene cada uno su propio espacio de almacenamiento, sino que
todos comparten un único espacio de tamaño igual al del miembro de mayor longitud.
Matrices
Una matriz es un conjunto de elementos de un mismo tipo a los que podemos acceder de
forma individual mediante un indice. El uso de matrices nos va a permitir manipular un
gran número de datos sin necesidad de definir una variable para cada uno de ellos. Cada
elemento de una matriz puede ser de cualquiera de los tipos vistos. Una matriz también
se suele denominar array, arreglo o vector. Aunque matriz suele usarse para referirse a
matrices unidimensionales, en realidad una matriz unidimensional es un vector y es
propiamente matriz si tiene más de una dimensión. La forma de declarar una matriz
unidimensional o vector es:
tipo_dato identificador[Elementos];
Donde tipo_dato es un dato cualquiera de los tipos vistos, identificador es el nombre del
vector Elementos es la cantidad de elementos que tendrá, que deberá ser
necesariamente una constante o una expresión cuyo resultado sea constante.
Los elementos de una matriz tienen un indice que se inicia en cero (primer elemento) y se
incrementa hasta el número de elemento menos uno. Las matrices de tipo char se pueden
utilizar con un solo valor, o sea, como una cadena de caracteres. Algunos ejemplos de
matrices son:
int var[10]; //reserva espacio para 10 variables de tipo int
const int DiasMes[12]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
char Dias[ ]={'L', 'M', 'X', 'J', 'V', 'S', 'D'};
char Dias1[ ]="LMXJVSD";
En el primer caso las 10 variables se llaman var y se accede a una u otra por medio de un
subíndice, que es una expresión entera escrita a continuación del nombre entre
corchetes.
En los demás casos hemos asignado un valor inicial a cada elemento de forma similar a
como la hacemos con una variable. Disponemos, encerrados entre llaves, los valores
correspondientes a cada elemento separados por comas recordando que el indice 0
corresponde al primer elemento. Si se trata de caracteres lo podemos hacer de la misma
forma que antes delimitando cada valor con comillas simples o bien entre comillas dobles
uno a continuación de otro y sin emplear llaves.
En el ejemplo anterior las matrices Días y Dias1 tienen exactamente el mismo valor.
Observamos también que en las matrices char se ha obviado el número de elementos,
asignándose automáticamente según el número de valores facilitados.
Aparentemente Días y Dias1 tendrían 7 elementos, pero no es exactamente así,
estribando la diferencia en Dias1 se define como cadena y el lenguaje añade
automáticamente un carácter 0 o NULL (\0) de fin de cadena, que implica que Dias1 tenga
8 elementos. Este extremo es importante tenerlo en cuenta al trabajar con matrices de
caracteres.
3
Acceso a elementos
Dias[0]='L'
Dias[1]='M'
Dias[2]='X'
Dias[3]='J'
Dias[4]='V'
Dias[5]='S'
Dias[6]='D'
Dias[0]='L'
Dias[1]='M'
Dias[2]='X'
Dias[3]='J'
Dias[4]='V'
Dias[5]='S'
Dias[6]='D'
Dias[7]=\0
Como ya hemos dicho, para acceder a un elemento de un vector tenemos que incluir en
una expresión su nombre seguido del subíndice entre corchetes. No es posible operar con
un vector completo o toda una matriz como una única entidad, sino que hay que tratar sus
elementos uno a uno por medio de bucles. Los elementos de un vector se utilizan en C
como cualquier otra variable. Algunos ejemplos de uso de vectores:
var[0]
var[1]
var[2]
…
var[9]
= 1.4142;
= 10.0 *v ar[0];
= var[9] – var[1]/var[9];
= (var[0] + var[7])/var[3];
Es evidente que hasta ahora hemos hablado de matrices unidimensionales (vectores),
pero en C se pueden definir matrices de varias dimensiones. Supongamos que queremos
crear una matriz que permita almacenar un número en cada uno de los elementos, y que
estos elementos son los que representan cada uno de los días de cada mes.
Necesitaremos una matriz de 12 elementos (tantos como meses), cada uno de los que
tendrá a su vez una matriz de 31 elementos (uno por día). Esta matriz se declara así:
int MesDias [12] [31];
Para acceder a un determinado elemento de una matriz los distintos indices se delimitan
cada uno con su pareja de corchetes. Las siguientes expresiones muestran accesos
correctos a elementos de las matrices declaradas anteriormente.
Dias[0] = ‘L’;
MesDias [1][6] = 0;
También podemos emplear el operador sizeof() para determinar la ocupación en memoria
de una variable tipo matriz.
Punteros
Diremos en primer lugar que la programación de punteros plantea algunos problemas
prácticos dada su naturaleza bastante ‘delicada’. Un pequeño despiste con ellos puede
hacer que nuestro programa de resultados extraños o, incluso, que se "cuelgue".
Los punteros son un tipo de variables que almacenan las direcciones físicas de memoria
de otras variables. Si tenemos la dirección de una variable, tenemos acceso a esa
variable de manera indirecta y podemos hacer con ellas todo lo que queramos.
Ya vimos someramente que en lenguaje C se dispone del operador de dirección (&) que
permite determinar la dirección de memoria de una variable. Hay también en C un tipo de
variables que se caracterizan por contener direcciones de variables y que se denominan
punteros (pointers).
Un puntero es una variable que se declara, se le asignan valores, se utiliza en
expresiones, etc.; pero se diferencia de cualquier otra variable en que no contiene o
almacena un dato, sino que contiene una dirección de memoria en la que se almacena la
información.
Podemos definir un puntero como que es una variable que contiene una dirección que
apunta a un valor de un cierto tipo, denominado tipo base. Podremos por tanto definir
4
punteros de cualquiera de los tipos que conocemos, por ejemplo, si en un puntero
almacenamos la dirección de memoria donde se almacena un entero, el puntero deberá
ser de tipo int.
Para declarar un puntero basta con disponer detrás del tipo el operador *, que indicará al
compilador que lo que estamos declarando no es una variable, sino un puntero. La
sintaxis general de declaración de un puntero es:
tipo *Puntero; // Puntero sin inicializar
Podemos imaginar un puntero como una flecha que apunta a una dirección de memoria.
Por ejemplo, si declaramos el puntero: char *Puntero; tenemos declarada una "flecha" que
apunta a una dirección de memoria.
No debemos confundir una dirección de memoria con el contenido de esa dirección de
memoria. Por ejemplo, si declaramos int x=10; podemos considerar una estructura de
memoria como:
Direcciones de memoria DirMem10
Contenido
---
DirMem20
DirMem30
DirMem40
---
10
---
La dirección de la variable x es &x = DirMem30 mientras que el contenido de esa
dirección de memoria es el valor de la variable x, es decir: 10.
Podemos decir que hay dos valores para x. El primero es el valor del entero guardado, en
nuestro caso 10 denominado rvalue, mientras que el segundo es la dirección de
memoria, que se denomina lvalue.
Dirección (Lvalue)
La dirección o localización de memoria o Lvalue de la variable-dato es la dirección de
memoria donde comienza el almacenamiento.
Este nombre viene históricamente de “left value” o valor a la izquierda, en referencia a que
normalmente la variable receptora del dato está a la a la izquierda. Por ejemplo, una
expresión del tipo x=3; que leemos como asignar el valor 3 a la variable x, podemos
enunciarlo de una forma más extensa y real diciendo que asignamos o guardamos el valor
3 en la dirección de memoria señalada por la variable x.
El Lvalue puede ser fijo (constante) o variable. Un Lvalue modificable significa que la
dirección puede ser accedida y su contenido modificado. Por ejemplo: x=3; es una
expresión válida si x es una variable de tipo entero, lo que significa que en su dirección
puede guardarse un patrón de bits que significará un valor 3 para el compilador
Un Lvalue constante significa lo contrario, por ejemplo, la expresión 3=x no es correcta
porque el Lvalue de 3 no es modificable. La expresión x+y=2 tampoco es correcta, porque
x+y no tiene Lvalue ya que no puede interpretarse como una dirección de memoria).
El Lvalue de las variables estáticas y globales se asigna en tiempo de compilación y el de
las variables dinámicas se asigna en tiempo de ejecución.
Valor (Rvalue)
El Rvalue es la interpretación que hace el programa del patrón de bits alojado en la
dirección asignada a la variable. Históricamente Rvalue es abreviatura del inglés “right
value”, valor a la derecha, en referencia a los valores que normalmente suelen estar a la
derecha en una expresión de asignación.
Un Rvalue puede ser una constante, variable, o expresión que pueda traducirse en un
valor. Por ejemplo 4+3 es un Rvalue.
5
Las variables que no son constantes pueden modificar su Rvalue a lo largo del programa.
Una expresión del tipo x=a; normalmente leido como el valor de la variable o constante a
sea asignado a la variable x, podemos leerlo de una manera más formal diciendo que el
Rvalue de la variable nombrada a sea asignado a la dirección de memoria (Lvalue) de la
variable de nombre x.
Bien, en este momento veamos algún ejemplo:
int x, y;
y= 2;
x = 7; //línea 1
y = x; //línea 2
En la línea 1 el compilador interpreta la x como la dirección de la variable (su lvalue) y
crea código para copiar el valor 7 a esa dirección.
En la línea 2, sin embargo, la x es interpretada como su rvalue (ya que está del lado
derecho del operador de asignación). Esto significa que aquí x hace referencia al valor
alojado en la dirección de memoria asignado a x, 7 en este caso. Así que el 7 es copiado
a la dirección designada por el lvalue de y.
Si la declaración de un puntero (por ejemplo int *punt;) se hace fuera de cualquier
función, los compiladores ANSI la inicializarán automáticamente a cero. De modo similar,
*punt no tiene un valor asignado, esto es, no hemos almacenado una dirección de
memoria en la declaración hecha arriba. En este caso, y si la declaración fuese hecha
fuera de cualquier función, es inicializado a un valor que asegure que no apunte a un
objeto de C o a una función. Esta forma de definición de un puntero es lo que se conoce
como puntero "null" (null pointer).
Supongamos ahora que queremos almacenar en punt la dirección de memoria de una
variable entera y. Para hacerlo debemos usar el operador unitario & y escribir punt=&y;
Lo que el operador & hace es obtener la dirección de y, aún cuando y está en el lado
derecho del operador de asignación y copia esa dirección en el contenido de nuestro
puntero punt. Ahora, punt es un “puntero a y”.
El operador de indirección se usa, por ejemplo escribiendo *punt=7; que copiará el 7 a la
dirección a la que apunta punt. Así que como punt “apunta a y” (contiene la dirección de
y), la instrucción de arriba asignará por tanto, a y el valor de 7. Esto es, que cuando
usemos el * hacemos referencia al valor al que punt está apuntando, no al valor del
puntero en si.
Relación entre vectores (matrices) y punteros
La relación entre un puntero y una matriz unidimensional o vector resulta casi evidente ya
que el nombre de un vector es un puntero constante (no puede apuntar a otra variable
distinta) a la dirección de memoria que contiene el primer elemento del vector. Veamos
una ejemplos de sentencias:
int vector[10];
int *p;
p = &vector[0]; // Hace que p = vect;
El identificador vector (el nombre vector) es un puntero al primer elemento de vector[ ] que
es lo mismo que decir que el valor de vector es &vector[0].
Como el nombre de un vector es un puntero, podemos escribir, por ejemplo:
• Si vector apunta a vector[0]
vector+1 apuntará a vector[1]
y vector+i apuntará a vector[i]
6
Podemos poner subindices a los punteros como se ponen a los vectores. Por ejemplo:
• Si p apunta a vector[0] podremos poner
p[3]=p[2]*2.0 que sería lo mismo que vector[3]=vector[2]*2.0.
Si p=vector, podemos decir que:
*p será equivalente a vector[0], a *vector y a p[0]
*(p+1) será equivalente a vector[1], a *(vector+1) y a p[1]
*(p+2) será equivalente a vector[2], a *(vector+2) y a p[2]
Para ver la relación entre punteros y matrices vamos a partir de la sentencia siguiente:
int matriz[5][3], **punt1 //punt1 es un puntero a puntero
El nombre de la matriz (matriz) es un puntero al primer elemento de un vector de punteros
matriz[ ] y sus elementos contienen las direcciones del primer elemento de cada fila de la
matriz. Por lo tanto el nombre matriz es un puntero a puntero. El vector de punteros
matriz[ ] se crea al crearse la matriz.
Podemos establecer las siguientes igualdades:
matriz = &matriz[0];
matriz[0] = &matriz[0][0]
matriz[1] = &matriz[1][0]
matriz[2] = &mat[2][0]
La dirección base sobre la que se direccionan todos los elementos de la matriz no es
matriz, sino &mat[0][0].
Recordando que matriz+i apunta a matriz[i], para direccionar una matriz de F filas y C
columnas tendremos que establecer la dirección del elemento (i, j) de la siguiente forma:
dirección (i, j) = dirección (0, 0) + i*C + j
Según esto y haciendo **punt1 = matriz; podemos acceder a los elementos de la matriz
con cualquiera de estas formas:
*punt1 es matriz[0]
**punt1 es matriz[0][0]
*(punt1+1) es matriz[1]
**(punt1+1) es matriz[1][0]
*(*(punt+1)+1) es matriz[1][1]
Podemos profundizar más en el tema consultando el
TUTORIAL SOBRE APUNTADORES Y ARREGLOS EN C por Ted Jensen
distribuido libremente en distintos formatos en la dirección web:
http://www. netcom.com/~tjensen/ptr/cpoint.htm
La Versión 1.2 de Febrero de 2000 (pointersC.pdf) la podemos ver aquí.
7
Descargar