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:
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.
ResponderEliminarEsto 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