LICENCIATURA EN CIENCIA DE LA
COMPUTACIÓN
Lenguajes de Programación
Subprogramas
BLOQUES
Los bloques son regiones textuales del programa
permiten agrupar secuencias de instrucciones junto
con declaraciones de objetos de datos, usando
delimitadores apropiados de comienzo y fin del bloque.
Un bloque puede ser usado el cualquier contexto en el
que se espere una estructura de control simple, en
particular dentro de otro bloque. La estructura en
bloques permite crear memoria para las variables justo
en el momento que se necesiten.
2
ESTRUCTURA GENERAL DE UN BLOQUE
Encabezamiento
Declaraciones
Instrucciones
Finalización
3
CASOS DE ESTRUCTURA DE BLOQUES
Bloque corriente o inline: Es el bloque que no
corresponde a la definición de un subprograma y puede
aparecer en cualquier posición en que se espera una
instrucción.
Bloques asociados a subprogramas: Se corresponde
textualmente con el cuerpo del subprograma, extendido
con las declaraciones de los parámetros formales.
4
BLOQUE CORRIENTE
Un bloque se considera una instrucción más del programa. A
continuación se muestran ejemplos de estructuras de bloques.
Ejemplo
{ int a,b,h; // B1
.........
{ int b,x,y; // B2
.........
}
.........
}
Esta estructuración en bloques aparece, entre otros, en lenguajes
como Algol-60 y basados en C. Pueden estructurarse bloques en
paralelo o anidados, tal como se muestra en el ejemplo. Hay
lenguajes en que la secuencia de instrucciones del bloque no
puede contener a su vez otros bloques, solamente otras
instrucciones del lenguaje.
5
SUBPROGRAMA
Entidad compuesta de declaraciones de datos e instrucciones, conformando
una unidad de cómputo, que puede ser invocado (llamado) desde diferentes
partes del programa, constituyendo una forma primaria de reuso de código.
Cada método de una clase se representa mediante un subprograma.
Cada subprograma tiene un único punto de entrada.
Cuando un subprograma es llamado se le pasa un conjunto de argumentos
que son usados para modificar la ejecución del subprograma, enviar datos al
subprograma o recibir los resultados del cómputo realizado por el
subprograma.
La unidad de programa invocante queda suspendida durante la ejecución
del subprograma llamado, por lo que solo un subprograma está en ejecución
en un momento dado.
El control retorna al invocante cuando termina la ejecución del
6subprograma llamado. 6
FUNCIONES EN C++
double f (double x) {
return x / (1 + x*x) ;
}
void intercambiar (double& x, double& y) {
double t ;
t = x ;
x = y ;
y = t ;
} // intercambiar 7
LLAMADA DE UNA FUNCIÓN
void intercambiar (double& x, double& y) {
...........
}
Correspondencia posicional entre
parámetros
intercambiar (a , b ) ; / /llamada a la función
8
MECANISMOS DE INTERCAMBIO DE INFORMACIÓN DEL
SUBPROGRAMA
Parámetros: Se distinguen entre parámetros formales,
que aparecen en la definición del subprograma (en la parte de
encabezamiento) y parámetros o argumentos actuales que
aparecen en la llamada al subprograma. Los parámetros
formales son denotados por nombres y se comportan como
declaraciones locales al subprograma.
Valor de retorno: Algunas funciones intercambian
información con el resto del programa retornando un valor
como resultado de la propia función.
Ambiente no local: Constituye el mecanismo más atrasado
de intercambio de información entre el subprograma y el resto
del programa. Si el cuerpo de la función modifica una variable
no local a él, esta modificación afecta a las partes del 9
programa donde dicha variable es visible.
EJEMPLO DE FUNCIÓN Y LLAMADAS A FUNCIÓN EN
PYTHON
def cuadrado (x):
return x ** 2
print(cuadrado(2))
a = 1 + cuadrado(3)
print(cuadrado(a * 3)
10
ESTRUCTURA DE UN SUBPROGRAMA
Encabezamiento que contiene el nombre del subprograma y los
parámetros formales del subprograma y opcionalmente un tipo
para el resultado.
Cuerpo que contiene las declaraciones propias del subprograma y
el conjunto de instrucciones.
Existen lenguajes que permiten subprogramas anidados, es
decir, que permiten declarar subprogramas dentro de las
declaraciones propias de un subprograma. Tal es el caso Pascal,
Python o R. Otros lenguajes no lo permiten, como el C.
11
MECANISMOS DE TRASPASO DE PARÁMETROS EN
SUBPROGRAMAS
Es la acción de control asociada a los subprogramas y que ejecuta
los siguientes pasos:
Establecer el conjunto de variables no locales al subprograma.
Ejecutar el mecanismo de transferencia entre los parámetros
actuales (de la llamada) y los formales (que aparecen en el
encabezamiento de su definición).
Reservar memoria para las variables locales al subprograma.
Ejecutar su conjunto de instrucciones.
Restablecer las condiciones previas (el ambiente de referencia al
momento de la invocación)
12
MODELOS SEMÁNTICOS DE TRASPASO DE PARÁMETROS
Modo entrada
Modo salida
Modo entrada/salida
X usa modo
entrada;
Y usa modo
salida;
Z usa modo 13
entrada/salida
LIGADURA (CORRESPONDENCIA) ENTRE PARÁMETROS
FORMALES Y ARGUMENTOS ACTUALES
Posicional: primer argumento actual con primer parámetro
formal y así sucesivamente. Es la forma más segura y eficiente
para listas de parámetros pequeñas.
Nominal: para listas largas de parámetros. En este caso los
parámetros actuales se asignan a nombres que corresponden a los
de los parámetros formales, y la correspondencia en la invocación
se establece entre argumentos actuales y formales del mismo
nombre.
14
EJEMPLO EN R DE CORRESPONDENCIA ENTRE
PARÁMETROS FORMALES Y ARGUMENTOS ACTUALES
Subprograma para calcular el área de un trapecio:
[Link] <- function(a,c,h) {
area <- ((a+c)/2)*h
area
}
Llamada con correspondencia posicional: [Link] (2,5,4). La salida
es 14.
Llamada con correspondencia nominal: [Link](a=2, h=4, c=5).
También la salida es 14.
La ventaja de la correspondencia nominal es que los parámetros formales
pueden aparecer en cualquier orden en la lista de parámetros actuales.
R y Python son ejemplos de LP que admiten ambas formas de 15
correspondencia.
FORMAS FUNDAMENTALES DE MECANISMOS DE
TRANSFERENCIA DE PARÁMETROS
Transferencia por valor
Transferencia por referencia
Transferencia por valor constante
Transferencia por resultado
Transferencia por valor-resultado
Transferencia por nombre
Transferencia de subprogramas.
16
TRANSFERENCIA POR VALOR
Modelo de implementación para parámetros de modo entrada.
Dirección Valor Valor Dirección
156 156
XXXX YYYY
Traspaso por
valor
Argumento actual Parámetro formal
Ventaja: Seguridad; rapidez, cuando se pasa un valor escalar
Desventajas: Alto consumo de tiempo y espacio si se traspasan valores que 17
ocupan mucho espacio.
EJEMPLO TRASPASO POR VALOR
void nocambiar (int z) {
z =50 ;
} // fin de cambiar
int main() {
int x=100;
cout <<"Valor antes de ejecutar el procedimiento: “
<<x <<endl;
nocambiar(x);
cout <<"Valor después de ejecutar el procedimiento: “
<<x <<endl; 18
}
EJEMPLO TRANSFERENCIA POR VALOR
void intercambiar(int x, int y) { X = 3,
6, Y = 6
3
int temp = x;
x = y;
y = temp;
}
//función principal
int main() {
int A[10]; I=3
int I = 3; A[I] = 6; A[3] = 6
intercambiar(I, A[I]);
cout << I << A[I]);
Salida: 3 6
} 19
TRASPASO POR REFERENCIA
Modelo de implementación para parámetros de modo entrada/salida.
Argumento actual Parámetro formal
Dirección Valor Dirección
Valor
Referencia a
XXXX 156 XXXX YYYY
Traspaso por
referencia
Algunos lenguajes exigen que el argumento actual sea un l-valor, aunque se han
20
incorporados formas de semántica de movimiento que admiten r-valores.
En otros puede ser un valor cualquiera (FORTRAN).
EJEMPLO TRASPASO POR REFERENCIA
void cambiar (int& z) {
z = 50 ;
} // fin de cambiar
int main() {
int x = 100;
cout <<"Valor antes de ejecutar el subprograma: " <<x <<endl;
cambiar(x);
cout <<"Valor después de ejecutar el subprograma: " <<x <<endl;
}
21
EJEMPLO TRANSFERENCIA POR REFERENCIA (SEUDO
C++)
intercambiar(int x, int y) X = 3, Y = 6
{ int temp = x;
x = y;
y = temp; X = 6, Y = 3
}
//función principal
int main() {
int A[10];
int I = 3; A[I] = 6; I=3 A[3]=6
intercambiar(I, A[I]); I=6 A[3]=3
cout << I << A[I]); Salida: 6 3
} 22
VENTAJAS-DESVENTAJAS TRASPASO POR REFERENCIA
Proceso de traspaso eficiente: no copias, ni espacio duplicado.
Desventajas:
Acceso a parámetros formales probablemente más lento debido a
direccionado indirecto.
Modificación no deseada de un parámetro actual.
Pueden aparecer alias:
Sea void fun(int@ w, int@ z)
Con la llamada fun(a,a) son alias w y z
Con fun(x[i], x[j]), i=j alias w y z
23
TRANSFERENCIA POR VALOR-CONSTANTE
La llamada por valor es muy costosa para parámetros de gran
tamaño, tanto en espacio a reservar para el parámetro formal como
tiempo de copia byte-byte del argumento actual al parámetro
formal.
Una posible solución: para parámetros de entrada muy grandes es
emplear para ellos la transferencia por referencia, manteniendo la
semántica de paso por valor.
Esto constituye la transferencia de parámetro de sólo copia o por
valor constante.
Por ejemplo, en C++, declarando el parámetro formal
const tipo &
24
TRANSFERENCIA POR RESULTADO (1/2)
Modelo de implementación para parámetros de modo entrada, vía
de comunicación del subprograma hacia la rutina que lo invocó,
para transmitirle un valor de salida.
Cuando el subprograma es llamado, se salva la dirección del
parámetro actual que se corresponde con uno formal por resultado.
Cuando concluye la ejecución del subprograma, se copia el valor del
parámetro formal en la dirección salvada de su correspondiente
parámetro actual, que debe ser una variable.
Ada y C# implementan esta transferencia a través de los
parámetros out (copia de salida).
25
TRANSFERENCIA POR RESULTADO (2/2)
Surgen problemas si hay colisión de nombres en los parámetros
actuales. Ejemplo (C#):
void Fixer(out int x, out int y) {
x = 17;
y = 35;
}
. . .
[Link](out a, out a);
El parámetro a toma valor 35, pero si se invierten las
instrucciones de Fixer toma valor17.
26
EJEMPLO TRANSFERENCIA POR RESULTADO SEUDO-C++
intercambiar(int x, int y) {
int temp = x; Error de indefinición: X
x = y; e Y sólo envían valores de
y = temp; resultado, pero no están
inicializadas a algún valor
}
//función principal
principal() {
int A[10];
int I = 3; A[I] = 6; I=3 A[3]=6
intercambiar(I, A[I]); Error
cout << I << A[I]);
}
27
TRANSFERENCIA POR VALOR-RESULTADO
Combina las transferencias por valor y resultado.
Tiene un efecto similar al mecanismo por referencia, aunque
reservando memoria al parámetro formal que se inicializa con el valor
del parámetro actual. La dirección del parámetro actual es salvada.
Al concluir la ejecución del subprograma y antes de destruir el
ambiente local se copia el valor del parámetro formal en la dirección
salvada del parámetro actual.
Si los parámetros del subprograma usan traspaso por valor resultado
la salida sería la misma que en el caso que se usó transferencia por
referencia, excepto cuando se tienen en el subprograma expresiones
diferentes para un mismo valor (p.e., por aliasing).
El lenguaje Ada usa esta forma de transferencia a través de sus
parámetros in out. Sin embargo, el peligro de aliasing no existe, ya
que el lenguaje lo da como error. 28
TRANSFERENCIA POR VALOR-RESULTADO: SALIDAS
DIFERENTES AL MECANISMO POR REFERENCIA (1/2)
int I;
int A[10];
void Nada( int X, int Y) {
I = Y
}
int main {
I = 3; A[I] = 6;
Nada(I, A[I]);
cout << "I =" <<I << " A[3]= " <<A[3];
}
Como I se pasa como parámetro actual en la invocación Nada(I,A(I)), entonces X se
convierte en un alias de I. Con la invocación Nada(I,A(I)) no sufren cambios ni I ni
A(I), pues cuando acaba la ejecución del subprograma Nada se restablecen los
valores de la copia de entrada X y Y, que no han sido modificados dentro del
subprograma. Entonces la salida sería
I = 3 A[3] = 6.
Si la transmisión se hubiese realizado por referencia, entonces la salida hubiese sido 29
I = 6 A[3] = 6, debido a que I y X comparten la misma dirección de memoria.
TRANSFERENCIA POR VALOR-RESULTADO: SALIDAS
DIFERENTES AL MECANISMO POR REFERENCIA (2/2)
int I;
int A[10];
void Nada( int X, int Y) { Como I se pasa como parámetro actual
I = Y en la invocación Nada(I,A(I)), entonces
} X se convierte en alias de I. Con la
int main { invocación Nada(I,A(I)) no sufren
I = 3; A[I] = 6; cambios ni I ni A(I).
Nada(I, A[I]);
cout << "I =" <<I << " A[3]= " <<A[3];
}
Como I se pasa como parámetro actual en la invocación Nada(I,A(I)), entonces X se
convierte en un alias de I. Con la invocación Nada(I,A(I)) no sufren cambios ni I ni
A(I), pues cuando acaba la ejecución del subprograma Nada se restablecen los
valores de la copia de entrada X y Y, que no han sido modificados dentro del
subprograma. Entonces la salida sería
I = 3 A[3] = 6.
Si la transmisión se hubiese realizado por referencia, entonces la salida hubiese sido 30
I = 6 A[3] = 6, debido a que I y X comparten la misma dirección de memoria.
TRANSFERENCIA POR NOMBRE
Modelo de implementación para parámetros de modo
entrada/salida.
Similar a la transferencia por referencia, pero el cálculo de la
dirección del argumento actual no sólo ocurre al momento de la
invocación, sino cada vez que se use el formal correspondiente,
pues el texto literal del parámetro actual remplaza cada ocurrencia
de su correspondiente parámetro actual. Las variables que
aparezcan en tales expresiones se consideran globales al
subprograma llamado.
Si los argumentos actuales son variables simples, esta forma de
transferencia es equivalente a emplear el mecanismo por
referencia.
La transferencia por nombre resulta un mecanismo flexible,
poderoso, pero poco eficiente en virtud de tantos cálculos de
direcciones, que puede constituir fuente de errores y puede
producir cálculos impredecibles. Fue implementada en ALGOL-60 31
y usada luego en Simula-67. Está en desuso.
EJEMPLO TRANSFERENCIA POR NOMBRE
intercambiar(int x, int y) { X = 3, Y = 6
int temp = x;
x = y; X = 6,
y = temp; Y = 3 A[6]=3
}
//función principal
principal() {
int A[10];
int I = 3; A[I] = 6; I=3 A[3]=6
intercambiar(I, A[I]); I=6 A[6]=3
cout << I << A[I]);
Salida: 6 3 (a[6])
} 32
FUNCIONAL
Una función de orden superior o funcional es una función que
o bien acepta una o más funciones como parámetros o devuelve una
función como resultado, o hace ambas cosas. Lo primero es
bastante común, lo segundo es menos común, pero es un
mecanismo fundamental de los lenguajes de programación
funcional.
Una forma común de funcional es mediante la composición de
funciones.
Por ejemplo, si f(x) = x+2 y g(x) = 3*x
h(x) = f ° g(x) = f(g(x)) = f(3*x) = 3*x+2
33
TRANSFERENCIA DE SUBPROGRAMAS
El subprograma es un funcional que Con int g(int h(int b)) {...}
acepta como parámetro otro denotamos la declaración de una
subprograma. función g con un parámetro h que
Ejemplo en seudocódigo C. a su vez retorna un valor entero.
int x=1; Dos puntos clave en el ejemplo
int f(int y) son:
{ return x+y; • f es argumento actual en la
} llamada a g y luego es llamado a
int g(int h (int b)) través del parámetro formal h.
{ int x = 2; • El nombre x es usado más de
return h(3) + x; // 7 una vez, por lo que es necesario
} establecer cuál es el ambiente
no local en el cual f será
...
evaluada.
{ int x = 4;
Clausura de fun =
int z = g(f); // 11
<fun, AmbRef(fun)> 34
} Ejemplo: Clausura(f)=<f,{x/1}>
TRANSFERENCIA DE SUBPROGRAMAS…
En C++ (con algunos adornos).
Ejemplo en seudocódigo C.
int x=1; typedef int TFuncion(int);
int f(int y) int x = 1;
int f(int y)
{ return x+y; { return x+y;
} }
int g (TFuncion& h)
int g (int h (int b)) { int x = 2;
{ int x = 2; cout <<h(3) <<endl;
return h(3)+x; // 7 return h(3) + x;
}
} int main()
... { int x = 4;
int z = g(f);
{ int x = 4; cout <<"X=" <<x <<“
int z = g(f); // 11 Z=" <<z <<endl;
} } 35
TRANSFERENCIA DE SUBPROGRAMAS…
Ejemplo en seudocódigo C. Posibilidades de seleccionar el ambiente no local a usar
cuando se ejecute f invocado usando el parámetro
int x=1; formal h:
int f(int y)
{ return x+y; Política de enlace superficial: Enlazar el
} ambiente de referencia del subprograma
cuando efectivamente es llamado usar el
int g (int h (int b)) ambiente que está activo cuando la llamada a f
{ int x = 2; ocurre usando h (comentario // 7).
return h(3)+x; // 7
}
Política de enlace profundo: Cuando un
...
subprograma se pasa como parámetro, se pasa
{ int x = 4; su clausura usar el ambiente activo en el
int z = g(f); // 11 momento en que se crea el enlace entre h y f
(comentario // 11). 36
}
TRANSFERENCIA DE SUBPROGRAMAS…
Los lenguajes más conocidos que usan alcance estático emplean
enlace profundo.
Esto no queda claro para lenguajes con alcance dinámico, ya que
hay lenguajes que emplean enlace profundo o superficial.
37
TRANSFERENCIA DE SUBPROGRAMAS…
Siguiendo el ejemplo anterior las
diferentes formas de alcance y políticas int x=1;
de enlace derivan en los siguientes casos: int f(int y)
Alcance estático y enlace profundo: la { return x+y;
llamada h(3) retorna 4 y g retorna 6. }
La x en el cuerpo de f cuando es int g (int h (int b))
llamada usando h es la del bloque más { int x = 2;
externo. return h(3) + x;
Alcance dinámico y enlace profundo: la }
llamada h(3) retorna 7 y g retorna 9. ...
La x en el cuerpo de f cuando es { int x = 4;
llamada usando h es la local al bloque int z = g(f);
en el cual g(f) ocurre. }
Alcance dinámico y enlace superficial:
la llamada h(3) retorna 5 y g retorna 7.
38
La x en el cuerpo de f al momento de
su llamada a través de h es la local a g.
FUNCIONAL DEL TIPO APPLY-TO-ALL
Apply-to-all (o simplemente funcional apply) es una forma funcional
que toma una sola función como parámetro y de poseer una lista de
parámetros, aplica el parámetro función a cada elemento de la lista y
recoge el resultado en una lista o secuencia.
Denotemos por α un funcional apply. Sea h(x) = x*x, entonces
α(h,(2,3,4)) retorna (4, 9, 16).
Ejemplo en R:
sapply(x, f, ..., simplify = TRUE, …)
x: es una lista o vector.
f: es una función a aplicar a cada elemento en x.
Devuelve una lista, vector o matriz de igual longitud que x, en que cada
elemento es el resultado de aplicar f al correspondiente elemento de x,
llevando el resultado de salida a un vector o una matriz, siempre que sea
posible (simplify = TRUE). 39
EJEMPLO DE EMPLEO DE SAPPLY
df <- [Link](first=5:9, second=(0:4)^2, third=-1:3)
df
Prepara y muestra una matriz compuesta por tres vectores columna:
first second third
1 5 0 -1
2 6 1 0
3 7 4 1
4 8 9 2
5 9 16 3
Con:
sapply(df, function(x) sqrt(sum(x^2)), simplify = TRUE)
se aplica el cálculo de la raiz de la suma de los cuadrados de las
components de cada vector columna, resultando:
first second third
15.968719 18.814888 3.872983 40
EJEMPLOS C++ DE FUNCIONAL DEL TIPO APPLY-TO-ALL
Se aplican usando objetos funciones (function object), que son objetos que se
comportan como funciones.
A nivel de implementación son objetos de una clase que implementan el
operador () - operator().
template <typename T>
class Imprimir
{ public:
void operator() (const T& elem)
{ cout <<elem <<'\t';
}
};
int main()
{ vector <int> v{11,22,33,44,55};
cout <<"Impresion del vector usando el objeto funcion Imprimir \n";
for_each([Link](), [Link](), Imprimir<int>());
...... Se aplica el operador() de la 41
} clase Imprimir a cada
elemento del vector v
EFECTOS COLATERALES EN LLAMADAS DE
FUNCIONES
Un efecto colateral es una modificación que se efectúa
dentro de una función a elementos (por ejemplo, variables) que
le son globales o no locales.
En el caso de una función void, o sea, que no retorna valor, las
únicas formas de dar valores de salida es a través de los
parámetros que se transfieren por referencia. Cualquier otra
modificación que se realice sobre variables globales o no
locales a la función constituye un efecto colateral.
En el caso de una función con tipo, la única forma previsible
en que se devuelve valor de la función es a través de su
nombre, por lo que cualquier modificación a variables globales,
incluso por la vía de parámetros por referencia (a menos que
se use el mecanismo por referencia para ahorrar espacio y 42
tiempo en la transferencia) son considerados efectos
colaterales.
EJEMPLO DE EFECTOS COLATERALES EN
LLAMADAS DE FUNCIONES
double x, y; // variables declaradas globales
double f(double a)
{ return (x=a+1); /* Introduce como efecto colateral
la modificación de la variable x */
}
int main()
{ x=0;
y = f(x) - f(x); // ¿Cero?
cout <<"El valor de y es: " << y << '\n';
}
El valor que se imprime no es cero, ya que la primera llamada de f(x) corresponde a la
evaluación f(0), pero la segunda llamada de f(x) corresponde a la evaluación f(1), puesto
que en la anterior llamada el valor de x se había incrementado de forma colateral.
Los efectos colaterales son considerados, en términos generales, una mala práctica de
programación, ya que complican la comprensión del programa y le resta independencia 43
a la función.
ALIASING
Consideremos el siguiente subprograma en C++ con tres parámetros
por referencia:
void pour(int& v1, int& v2, int& v) {
v1 -= v;
v2 += v;
}
Sean las variables x, y, z con valores 4, 6 y 1, respectivamente.
Entonces la llamada:
pour(x, y, z);
actualiza x ← 3 y ← 7 (lo esperado).
Si en vez de la anterior llamada se hace:
pour(x, y, x);
Se espera ocurra que x ← 0 ; y ← 7, pero y recibe valor 6.
Note en el último llamado que tanto v1 como v son alias de x, y por
tanto son también alias entre si v1 y v. 44
ARGUMENTOS POR DEFECTO (1/3)
Son valores que deben asignarse automáticamente a los parámetros formales en caso que
no sean especificados para ellos valores de argumentos actuales en la llamada de la
función.
Los parámetros por defecto se especifican cuando se declara la función, asignándoles un
valor dado.
void print(int valor, int base = 10) {
cout <<valor <<base ;
} Parámetro con
valor por defecto 10
Los llamados:
print(31)
print(31,10)
ejecutarían las mismas acciones, imprimir los valores 31 y 10.
45
ARGUMENTOS POR DEFECTO (2/3)
En Python, R, Ruby, C++, PHP y algunos otros LP, los parámetros
formales pueden tener valores por defecto.
Muchos lenguajes que admiten parámetros por defecto exigen que
estos deben ser los últimos argumentos en la lista de
parámetros. Si alguno de los parámetros no tiene un valor por
defecto, ningún parámetro previo puede tener un valor por
defecto.
Este no es el caso del lenguaje R.
46
ARGUMENTOS POR DEFECTO (3/3) CASO R
[Link] <- function(a,c=5,h) {
area <- ((a+c)/2)*h
area
}
Con [Link](2,2,4) sale 8, resultado de (2x2)/2 x 4.
Con [Link](2,h=4) sale 14, resultado de (2x5)/2 x 4.
47
SOBRECARGA DE FUNCIONES
Uso del mismo nombre de función para acciones con similar valor
semántico, aunque implementadas de forma diferente, sobre
parámetros de tipos o cantidades diferentes.
48
EJEMPLO DE SOBRECARGA DE FUNCIONES
// Definición de Suma para dos parámetros
double Suma(double a, double b) {
return a+b;
}
// Definición de Suma para tres parámetros
double Suma(double a, double b, double c) {
return a+b+c;
}
int main() {
double f=50, g=60, h=100;
// Invocación de Suma con dos parámetros.
cout <<"Valor de la suma (dos valores a sumar): " <<Suma(f,g) <<endl;
// Invocación de Suma con tres parámetros.
cout <<"Valor de la suma (tres valores a sumar): " <<Suma(f,g,h) <<endl;
}
Salida
49
Valor de la suma (dos valores a sumar): 110
Valor de la suma (tres valores a sumar): 210
TIPO DE VALOR RETORNADO POR UNA FUNCIÓN
La mayoría de los lenguajes imperativos restringen los tipos de valores que
pueden ser retornados por una función.
C permite retornar valores de cualquier tipo excepto arreglos y funciones.
C++ es parecido a C, permitiendo además retornar valores de tipos definidos por
el programador o clases.
Ada, Python, Ruby y Lua son los únicos lenguajes imperativos cuyas funciones
y/o métodos pueden retornar valores de cualquier tipo.
En el caso de Ada, como no existen tipos función, no pueden retornarse valores
función (aunque si punteros a funciones).
En algunos LP las funciones son objetos de primera clase, por lo que pueden ser
pasadas como parámetros y retornadas desde una función. Los métodos son
objetos de primera clase en lenguajes imperativos como Python, Ruby y Lua;
esto se cumple también en la mayoría de los lenguajes funcionales.
Ni Java ni C# tiene funciones, aunque si métodos. Como los métodos no son 50
tipos, no pueden ser retornados.
NOTACIÓN LAMBDA PARA FUNCIONES
Proporciona un método para definir funciones anónimas.
Una expresión lambda produce una función directamente, sin tener que
declararla.
Una -expresión especifica los parámetros y la expresión de una función. Es la
función en sí misma, pero sin nombre.
Por ejemplo, considere la siguiente -expresión: λx [x * x * x].
Una aplicación de esta - expresión es: λx [x * x * x](2), que resulta en el valor 8.
Una expresión lambda es útil en contextos en que forman parte de otra
operación y ellas en sí ocupan poco espacio. Son bastante usadas para especificar
funciones sencillas que son referenciadas solo una vez, muchas veces un objeto
función que es argumento de otra función.
El cálculo lambda fue ideado por Alonzo Church (1941) quien expresó que este
cálculo incorporaba una definición precisa de la noción de "computabilidad".
Constituye una de las bases teóricas de la programación funcional.
51
FUNCIONES ANÓNIMAS – EJEMPLO EN R
Ejemplo 1:
sapply(1:10, function(x) if(x < 5) x^2 else -x^2)
Salida: [1] 1 4 9 16 -25 -36 -49 -64 -81 -100
que es un vector de 10 valores.
Ejemplo 2, se desea encontrar el primer valor par de un vector de números
enteros. Una posible solución es:
es_par <- function(n) if (n%%2==0) TRUE else FALSE
vp <- c(1,3,2,5,4)
Find(es_par, vp)
Salida: [1] 2
Una solución similar (con idéntico resultado) usando funciones anónimas
es:
vp <- c(1,3,2,5,4)
Find(function(n) if (n%%2==0) TRUE else FALSE, vp) 52
FUNCIONES ANÓNIMAS – -EXPRESIÓN EN (C++11)
Sintaxis:
JRDS-UO
[Var1,Var2, …] : lista de captura de variables del alcance externo. Puede estar
vacía [].
(Tipo1 param1, … ): parámetros formales de la expresión lambda. Tipo1 es el
tipo del parámetro (se puede acompañar con const, &) y param1 es el
identificador del parámetro.
-> tipo: Tipo de valor retornado por la función. No es necesario especificar ->.
{Cuerpo de la función}: Instrucciones que explican la acción que realiza la 53
expresión con sus variables de entorno y parámetros.
FUNCIONES ANÓNIMAS – EXPRESIONES LAMBDA
Ejemplo C++11:
auto sum = [](int x, int y) -> int
{ return x + y;
JRDS-UO
}
Llamada: cout << sum(2, 3); // Imprime "5"
Es opcional incluir el tipo de retorno, cuando el compilador pueda deducir
el tipo de valor retornado por la expresión lambda:
auto sum = [](int x, int y) { return x + y; };
Una expresión lambda normalmente no tiene acceso a las variables definidas
en el ambiente que la contiene. Si se desea modificar variables del ambiente
externo, deben ser colocadas dentro del bloque de captura [].
int x = 2;
auto sum1 = [x](int y) { return x + y; };
Llamada: cout << sum(3); // Imprime "5" también 54
EJEMPLO: SE REMPLAZA CADA ELEMENTO PAR DE
UNA LISTA POR SU CUADRADO (FRAGMENTO)
......
list<int> v={0,1,2,3,4,5,6};
JRDS-UO
......
// Define un predicado (usando expresión lambda)
auto esPar = [](int elem) { return elem%2==0; };
// Uso de la expression para remplazar pares en v por sus cuadrados
for (auto& elem:v) {
if (esPar(elem)) {
elem = elem*elem;
}
}
...... 55
BIBLIOGRAFÍA
Robert Sebesta (2016). Concepts of Programming Languages,
12th Edition. Pearson Education. Capítulos 5 y 9.
Ray Toal, Rafael Rivera, Alexander Schneider and Eileen Choe
(2017). Programming Languages Explorations. CRC Press.
Terrence Pratt and Malvin Zelkowitz (2001). Programming
Language Design and Implementation (4th Edition). Prentice
Hall. Capítulo 1, 2 y 5 (5.3.1).
56