viernes, 30 de octubre de 2015

ESTRUCTURA DE DATOS

Como parte del reforzamiento del aprendizaje de la asignatura de Estructura de Datos, se crea este blog para conocer un poco más de este tema, enriquecer lo aprendido, comentar las dudas y entre todos aclararlas. Así pues, empecemos desde el principio.

FUNCIONES

Al principio se programaba en una manera secuencial o lineal, los lenguajes basados en esta forma de programación ofrecían ventajas al principio, pero el problema ocurre cuando los sistemas se vuelven complejos, ya que no ofrecen flexibilidad y el mantener una gran cantidad de líneas de código en sólo bloque se vuelve una tarea complicada. Frente a esta dificultad aparecieron los lenguajes basados en la programación estructurada. La idea principal de esta forma de programación es separar las partes complejas del programa en módulos o segmentos que sean ejecutados conforme se requieran. De esta manera tenemos un diseño modular, compuesto por módulos independientes que puedan comunicarse entre sí.
Una función es un conjunto de instrucciones que desarrollan una tarea particular dentro del programa.
Todas las funciones tienen las siguientes características:
·         Nombre
·         Argumentos o Parámetros
·         Valor de retorno
El nombre es el identificador de la función, es el medio por el cual vamos a hacer el llamado y debe ser único, no es posible tener dos o más funciones con el mismo nombre.
En C, las funciones se declaran en las cabeceras, de acuerdo al tipo de valor o dato retornado se declararán int, float o char. En caso de no retornar ningún valor son de tipo void. La forma general de declaración es la siguiente:
Tipo dato Nombre función(parámetros) {
cuerpo de instrucciones;
return [dato, var, expresión];
}
* Tipo dato específica el tipo de dato que regresará la función.
Los parámetros, estos son variables que pueden pasar su valor a un procedimiento o función desde el principal o desde otro procedimiento.
* No olvidar declarar el procedimiento antes del main(), incluyendo sus parámetros.
Existen funciones que no retornan ningún valor, convencionalmente se les llama procedimientos.
Dentro de un programa, pueden existir 2 tipos de variables, las globales y las locales. El lugar donde sea declarada una variable afectara el uso que el programa haga de esa variable.
Las reglas básicas que determinan como una variable puede ser usada dependen de 3 lugares donde se puede declarar una variable.
Dentro de cualquier función o procedimiento, a estas se les llama variables locales y solo pueden ser usadas por instrucciones que estén dentro de esa función o procedimiento.
Como parámetro de una función, donde después de haber recibido el valor podrá actuar como variable local en esa función o procedimiento. En esencia una variable local solo es conocida por el código de esa función o procedimiento y es desconocida por otras funciones o procedimientos.
Fuera de los procedimiento o funciones, a este tipo de variables se les llama variables globales y podrán ser usadas por cualquier función o procedimiento del programa, sin embargo hay que agregarle la palabra reservada STATIC y a partir del momento en que se declara, se considera y puede usarse como variable global.
La principal razón por la cual no se acostumbra a usar muchas variables globales es porque todo el conjunto de procedimiento y funciones que componen un programa tienen acceso o comparten su valor y se corre el riesgo de que inadvertidamente alguno de ellos modifique su valor.
Reglas para el uso de parámetros.
·         Cuando se usan variables como parámetros, la variable que se manda debe ser declarada dentro del principal o del procedimiento de donde se está enviando.
·         La variable que se manda tiene un nombre, la que recibe puede tener otro nombre o el mismo nombre por claridad de programa, pero recordar que internamente en la memoria del computador existirán dos variables diferentes.
·         La cantidad de variables que se envían debe ser igual en cantidad, orden y tipo a las variables que se reciben.
·         La variable que se recibe tiene un ámbito local dentro del procedimiento, es decir solo la puede usar ese procedimiento.
·         Se puede mandar a un procedimiento un dato, una variable o una expresión algebraica (no ecuación o fórmula), pero siempre se deberán recibir en una variable.
La forma en que usualmente se declaran y pasan los parámetros de las funciones es la que normalmente se conoce como "por valor". Esto quiere decir que cuando el control pasa a la función, los valores de los parámetros en la llamada se copian a "objetos" locales de la función, estos "objetos" son de hecho los propios parámetros.
Si queremos que los cambios realizados en los parámetros dentro de la función se conserven al retornar de la llamada (no confundir con variables globales), deberemos pasarlos por referencia. Esto se hace declarando los parámetros de la función como referencias a objetos. Las referencias sirven para definir "alias" o nombres alternativos para un mismo objeto. Para ello se usa el operador de referencia (&).
Sintaxis: < tipo > &< alias > = < objeto de referencia >
< Tipo > &< alias >
La primera forma es la que se usa para declarar objetos que son referencias, la asignación es obligatoria ya que no pueden definirse referencias indeterminadas.
Para finalizar, les dejo unos enlaces a unos videos muy interesantes que hablan de las funciones en C y nos aclaran de mejor manera lo anterior descrito, por parte del profesor Mendoza.



Arreglos como parámetros

•          Todos los arreglos, independientemente de su declaración, se pasan por dirección.
•          Dependiendo de la dimensión del arreglo sera necesario, o no, precisar el tamaño del arreglo, (a lo más una dimensión puede no estar especificada).
•          Existen dos formas de pasar un arreglo como parámetro, y de declarar un parámetro como arreglo.
•          Considerando la siguiente declaración de variables:
int tab[10];
float prom;
•          En la función se pueden precisar los arreglos como parámetros de alguna de las dos siguientes formas:
1. int funcion(float p; int tabla[]);
2. int funcion(float p; int tabla[10]);
Independientemente de la declaración los arreglos se pueden pasar de algunas de las dos siguientes formas:
(a) x = funcion(prom; tabla);
(b) x = funcion(prom; &tabla[0]);

Ejemplo de arreglos como parámetros

/* Cálculo de la variancia de 10 números

#include <stdio.h>
/* Definición función promedio(t) */
float promedio(t)
float t[ ];
{

int i;
float s;

for (s=0, i=0; i < 10; i=i+1)
s=s+t[i];
return(s/10);
}
/* Función que eleva los elementos del arreglo al cuadrado */
void potencia(t,res)
float t[10],res[];
{
int i;
for (i=0; i < 10; i=i+1)
res[i]=t[i]*t[i];
}
int main()
{
float t[10], t2[10];
float m1, m2;
int i;
for (i=0; i!10; i=i+1)
{
printf(''De valor de t[%d]:'',i);
scanf(''%f'',&t[i]);
}
m1=promedio(t);
potencia(t,t2);
m2=promedio(&t2[0]);
printf(''varianza:%dnn'',m2-(m1*m1));

Registro como parámetros


cómo usar los registros de propósito específico (SFR’s) de los microcontroladores AVR como parámetros en funciones o clases (en C++), esto puede resultar muy útil a la hora de crear librerías que manipulen, por ejemplo, los puertos del microcontrolador para llevar acabo una tarea, como el uso de un Display LCD, al cual lo podríamos conectar a cualquier puerto del micro y decir en las funciones a que puerto y a que pines se conecta, haciendo el código muy portable para otros modelos de AVR’s.
“Un registro es un espacio de almacenamiento especial del microcontrolador que esta relacionado (conectado) a algún periférico y que cada bit representa el funcionamiento o estado del mismo. Los registros tienen un nombre único de acuerdo a su funcionamiento, y los bits individuales del registro también suelen tener un nombre único y un propósito específico.”

En los microcontroladores AVR, como en otros muchos micros, los registros están mapeados a memoria, es decir, que se accede a ellos como si de la memoria RAM se tratase, y cada registro tiene una dirección única mediante la cual se puede acceder. La mayoría de los registros son de escritura y lectura, algunos son de solo lectura y otros de solo escritura.

El compilador avr-gcc maneja los registros como apuntadores a memoria, con esto cada registro es una dirección de memoria y esta dirección varía de un modelo de micro a otro, aunque varios microcontroladores contengan los mismos registros (ejemplo DDRB) no es obligatorio que se encuentren en la misma dirección, para eso se usa la librería <avr/io.h>, que contiene todas las definiciones de los registros y de los bits en registros con un nombre simbólico común para muchos microcontroladores, pero con un mapeo de direcciones distinto.

Adentrándonos en la librería avr-libc veremos que existen una serie de macros (una tras otra) para declara registros SFR, pero al final termina en una dirección de memoria, como se mencionó anteriormente, que es un puntero a volatile uint8_t. uint8_t a su ves es un typedef de unsigend char declarado como volatile, se declara volatile para decir al compilador que es una dirección de memoria y que no se debe optimizar, para que no cambie su valor a la hora de compilar.

Ahora, si deseamos pasar un registro a una función o a un método, bastará con que el parámetro a recibir sea un apuntador volatile de unsigned char  (o uint8_t). Haciendo un ejemplo muy ilustrativo (solo como referencia):

/*Esto es para C*/
void LCD_Init(volatile unsigned char* DDRx, volatile uint8_t* PORTx){

    *DDRx=0xFF;
    *PORTx=0x0;
    .
    .
    .
}

/*Esto es para C++*/
void LCD_Init(volatile unsigned char& DDRx, volatile uint8_t& PORTx){

    DDRx=0xFF;
    PORTx=0x0;
    .
    .
    .
}

/*La llamada a la función sería:*/
int main(void) {

   LCD_Init(DDRB,PORTB); //C++
   LCD_Init(&DDRB,&PORTB); //C
   .
   .
   .
}
Como ven, es relativamente sencillo pasar registros SFR a las funciones, en otro post usaremos esta habilidad para crear una librería que manipule un Display LCD de 16×2 caracteres configurando el puerto y los pines a los que está conectado.

Actividades

1.     Programa donde se visualiza la conversión del programa estructurado en programa modular utilizando las funciones:

           
{programa que realiza descuentos segun el codigo del articulo}

Uses crt;

Var

N,a,b,c,d:string;

P,t,t1:real;

Resp:char;

Begin

Repeat

Clrscr;

Writeln('el nombre de el articulo es:');

Readln(n);

Writeln('el precio de el articulo es:');

Readln(p);

If (p>=5000) then

Begin

Clrscr;

Writeln('el articulo ', n ,' pertenece a el codigo a');

T:=(p*30)/100;

T1:=p-t;

Writeln('el precio es :',p:3:2);

Writeln('el descuento fue del 30% esto es :',t:3:2);

Writeln('el precio final es :',t1:3:2);

Readln;

End

Else

If (p>=3000) and (p<5000) then

Begin

Clrscr;

Writeln('el articulo ', n ,' pertenece a el codigo "b"' );

T:=(p*20)/100;

T1:=p-t;

Writeln('pertenece al codigo b');

Writeln('',n);

Writeln('el precio es ',p:3:2);

Writeln('el descuento fue del 20% esto es :',t:3:2);

Writeln('el precio final es :',t1:3:2);

Readln;

End

Else

If (p>=2000) and (p<3000) then

Begin

Clrscr;

Writeln('el articulo ', n ,' pertenece a el codigo "c"');

T:=(p*10)/100;

T1:=p-t;

Writeln('pertenece al codigo c');

Writeln('',n);

Writeln('el precio es :',p:3:2);

Writeln('el descuento fue del 10% esto es :',t:3:2);

Writeln('el precio final es :',t1:3:2);

Readln;

End;

If (p<2000) then

Begin

Clrscr;

Writeln('el articulo ', n ,' pertenece a el codigo "d"');

T:=(p*5)/100;

T1:=p-t;

Writeln('el precio es :',p:3:2);

Writeln('el descuento fue del 5% esto es :',t:3:2);

Writeln('el precio final es :',t1:3:2);

Readln;

End;

Writeln('desea seguir capturando <s/n>');

Readln(resp);

Until (resp<>'s') and (resp<>'s');

Writeln('presione enter para salir');

Readln;

End.

End.


2.     Analizar la interrelación entre la funciones y las estructura de datos como parámetro de las funciones :

A continuación se muestran tres implementaciones de una misma función; cada una con una forma distinta para paso del argumento.

Implementación-1: Sistema clásico, paso "por valor"

int pru1(int n) {   // n entero; pasa "por valor"
  return 3 * n;
}
...
int x, i = 4;
x = pru1(i);        // ahora: x = 12, i = 4
int& ry = i;
x = pru (ry);       // ahora: x = 12, i = 4
La última sentencia no es un paso por referencia, sino por valor (a pesar de que el argumento actual sea una referencia).

Implementación-2: Sistema clásico, paso de "punteros por valor" (seudo-referencia)

void pru2(int* np) {  // np puntero-a-entero; pasa "por valor"
  *np = (*np) * 3;
}
 . . .
int x = 4;
pru2(&x);             // ahora x = 12
En este caso, pasar el valor &x (dirección de x) como argumento, es equivalente a pasar un puntero a dicha variable (que es lo exigido en la definición de pru2). Es decir, la última línea se puede sustituir por las siguientes:

int* ptr = &x         // define puntero-a-x
pru2(ptr);            // pasa el puntero como argumento
Implementación-3: Sistema C++, paso "por referencia"

void pru3(int& n) { // n tipo "referencia-a-int"; pasa "por  referencia"
  n = 3 * n;
}
 . . .
int x = 4;
pru3(x);            // ahora x = 12
En este último caso, la declaración int& n como parámetro de la función pru3, establece que este n sea declarado como "referencia-a-entero", de forma que cuando se pasa el argumento x, la función crea un valor n que es una especie de alias o espejo de x, de forma que la expresión n = 3*n tiene el mismo efecto que x = 3*x.

En la declaración de una referencia, cuando el iniciador es una constante, o un objeto de tipo diferente que el referenciado, se crea un objeto temporal para el que la referencia actúa como un alias. Esta creación de objetos temporales es lo que permite la conversión de tipos referencia-a-tipoX cuando se utilizan como parámetros de funciones y no hay concordancia entre el valor recibido y el esperado (suponiendo que exista posibilidad de conversión).


3.     Coloque un ejemplo de programación que involucren el uso de la funciones, estructurada de datos y alguna técnica de diseño modular por ejemplo HIPO.

Ejemplo diseño modular: 
/*****************************************/ 
/* Ejercicio de Traza */
 /*****************************************/
 // Var globales int a, b;
 void P(int & c); 
int main()
 {
 1) a = 1; 
2) b = 3; 
3) P(a); 
4) cout << a << b << endl; return 0;
 }
 void P(int & c)
 { 
 int b; 
5) b = 2; 
6) c = a + b + 2; return; 
}
ejemplo de estructura de datos: 



2 comentarios:

  1. los lenguajes están basados en la forma de programación que ofrecen ventajas al principio, pero el problema ocurre cuando los sistemas se vuelven complejos.

    ResponderEliminar
  2. Esto supone que podamos expresar formalmente mediante un conjunto de reglas las relaciones y operaciones posibles es lo que he entendido sobre las funciones y las programación modular.

    ResponderEliminar

Nota: solo los miembros de este blog pueden publicar comentarios.