INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. SUBPROGRAMAS EN PASCAL. Llamaremos subprograma a un código compacto, contenido dentro de un programa, y que realiza una tarea específica como validar datos de entrada, generar recuadros, ordenar arreglos, realizar cálculos matemáticos, etc. Ud. sin saberlo ha estado utilizando estos subprogramas en forma totalmente transparente. Cuando calculaba una raíz cuadrada mediante la expresión: Raiz:=sqrt(x) estuvo haciendo uso de la función sqrt( ) que en su esencia íntima no es otra cosa que un código ejecutable, pero con la particularidad que el mismo ya fue escrito por Borland en lugar de Ud. El valor encerrado entre paréntesis se denominaba un argumento o parámetro pasado a la función, y el prototipo de la misma (solicitado a través del help) indicaba las características de este parámetro y el valor devuelto por la función: function Sqrt( x : real): real; Esto significaba que cualquier magnitud numérica asignada a x es convertida en un real, realiza sus cálculos y retorna en el nombre en sí de la función, un resultado (el valor de la raíz) de tipo real. Un subprograma posee una estructura similar a la de un programa básico en Pascal: Cabecera; Bloque declarativo; begin instrucciones ejecutables; end; y este trozo de códigos debe ubicarse antes del (* main *) y en general de cualquier invocación al mismo, entendiendo por invocación o llamado, al hecho en sí de utilizar la función. En el caso anterior: Raiz:=sqrt(x) se dice que hemos efectuado una invocación o llamada a la función sqrt( ). Cabecera. Comienza con la palabra clave function seguida a continuación de un nombre que responda a las reglas ya vistas sobre sintaxis de identificadores. Si la función va a trabajar sobre valores que le son pasados desde el punto de invocación (parámetros), entonces el nombre va acompañado de paréntesis dentro de los cuales se enumerarán los valores que recibirá, especificando su nombre y tipo. Clase Teórica Nro 9 Pág. 1/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. El final de esta declarativa van dos puntos (:) y el tipo que retornará la función. Veamos un ejemplo: function ModuloZ( Re:double; Im:double):double; La función se denomina ModuloZ (determina el módulo de un complejo Z cuyos componentes real e imaginario se conocen) y recibe dos argumentos: Re e Im de tipo double. El valor regresado por esta función será de tipo double. Muy importante: Los nombres de estos parámetros son totalmente genéricos lo cual significa que desde el punto de invocación la función puede ser llamada con cualquier otro identificador, incluso valores escritos manualmente: Modulo:=ModuloZ(3,4) Tal vez desde otro lugar remoto del programa vuelva a requerir los servicios de esta función sólo que con otros argumentos. Piense lo siguiente: Ud. dispone de 2 vectores correspondientes a la parte real y a la parte imaginaria de una cierta cantidad de complejos y desea determinar sus magnitudes: var VectRe : array[1..DIM]of real; VectIm : array[1..DIM]of real; ModZ : array[1..DIM]of real; i : byte; begin (* main *) ...................... for i:=1 to DIM do ModZ[i]:=ModuloZ(VectR[i],VectIm[i]); end; donde hemos invocado DIM veces a la función ModuloZ( ) pero con distintos argumentos. Sin embargo localmente siempre han sido recibidos con los mismos nombres Re e Im. Estos parámetros en la cabecera de la función se denominan parámetros formales y los que figuran en el punto de invocación se denominan parámetros locales. Los parámetros formales se denominan así porque establecen la forma en que dichos elementos serán recibidos, teniendo además el sentido de un verdadero bloque declarativo. Si la cantidad de estos parámetros es numerosa, puede recurrirse a la sintaxis clásica de un bloque declarativo: function Nombre ( Parámetro1 : tipo1; Parámetro2 : tipo2; .............................. Parámetron : tipo_n ):tipo_devuelto; Estos argumentos pueden ser utilizados por el código operativo de la función como si ellos hubiesen sido declarados en bloque propio de la función. Clase Teórica Nro 9 Pág. 2/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. Muy importante: los parámetros locales (desde la invocación) deben corresponderse: En cantidad. En tipo En orden con aquellos que son esperados en la declarativa formal del subprograma. Dicho en otras palabras: si la function está esperando 3 parámetros, en la invocación o llamada a la función deben enviarse 3 parámetros. Si los dos primeros deben ser enteros y el último de tipo double, deben corresponderse en ese orden. A continuación de la cabecera viene el bloque de identificadores propios que requiera el subprograma para su funcionamiento. Pueden incluir constantes y variables como cualquier programa básico: const ............. var ............. con tantos elementos como sean necesarios. Todos estos identificadores: los parámetros formales y los del bloque declarativo, se denominan identificadores locales y tienen vida mientras la función se halle activa (o sea operando). Una vez que la función se ha extinguido porque su ejecución finalizó, todos estos identificadores se pierden, y su explicación es muy sencilla: Cada vez que una función es invocada, el compilador le asigna un espacio de memoria RAM para que la misma opere. En este espacio se almacenan tanto los parámetros formales como los identificadores del bloque declarativo, y de allí son tomados por los códigos ejecutables para las operaciones de procesamiento. Una vez finalizada la ejecución de la función esta zona de memoria es liberada perdiéndose todo lo que había en ella. Tanto los parámetros formales como los identificadores locales sólo pueden ser accesados por la propia función. A continuación del bloque declarativo se escribirán los códigos ejecutables de la función que pueden ser tan complejos como se requiera. Ahora bien, para que la función pueda retornar un valor en su propio nombre es necesario que en algún momento (generalmente al final, aunque no es obligatorio) debe existir una instrucción en la cual figure de izquierda a derecha: NombreDeLaFunción:=Expresión; En el ejemplo del módulo: ModuloZ:=sqrt(Re*Re+Im*Im); Caso contrario la función no retornará nada. Clase Teórica Nro 9 Pág. 3/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. IMPORTANTE: Debe tomarse especial cuidado en no colocar el nombre de la función en el lado derecho, puesto que ello implicaría un nuevo llamado a la función en sí, creando lo que se denomina recursividad. Este es un concepto muy potente que va más allá del alcance de este curso. Cuando estudiemos otro tipo de subprogramas denominados procedimientos, analizaremos dos modalidades adicionales muy importantes en la forma de recibir los parámetros formales, por ahora no complicaremos las cosas. Veamos algunos ejemplos para afianzar todo lo dicho hasta ahora. program Raiz_n; { ------------------------------------------------------------Implementar una función que determine cualquier orden de raíz sobre cualquier base. ------------------------------------------------------------- } uses crt; var n : double; IndRaiz : double; { ------------------------------------------------------------ } function Raiz(N:double; IndRaiz:double):double; begin Raiz:=exp(ln(N)/IndRaiz); end; { ------------------------------------------------------------ } begin (* main *) clrscr; highvideo; n:=2; IndRaiz:=2; writeln('Raiz cuadrada de 2 = ',Raiz(2,2):2:8); readkey; end. Note la sencillez de esta función que está definida en una sola línea de códigos. Al tratarse de una sola línea es obvio que deberá contener el nombre de la función a fin de que ésta pueda retornar algún valor. Desde el punto de invocación (argumento de una instrucción writeln, la misma es llamada como cualquier otra función estándar propias de Pascal. Observación importante. El hecho de poder implementar tantas funciones como se nos ocurra, y de que su invocación posee la sintaxis clásica de cualquier comando Pascal, implica que lo que en realidad estamos haciendo es crear nuevas instrucciones enriqueciendo las que ya traía el lenguaje de por sí. Esto le da al lenguaje una potencialidad asombrosa. He aquí más ejemplos: Clase Teórica Nro 9 Pág. 4/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. program ComplejoDeBinario_Exponencial; uses crt; var Re,Im : double; Modulo : double; Fi : double; { ------------------------------------------------------------} function ModuloZ(Re:double; Im:double):double; begin ModuloZ:= sqrt(Re*Re+Im*Im); end; { ------------------------------------------------------------} function AnguloZ(Re:double; Im:double):double; const Kgrad = 180/PI; var Modu : double; Seno : double; begin Modu:= sqrt(Re*Re+Im*Im); if(Modu>0)then begin Seno:= Im/Modu; if(Seno>0)then if(Re=0)then Fi:=90 else if(Re<0)then Fi:=180-Kgrad*arctan(abs(Im/Re)) else Fi:=Kgrad*arctan(abs(Im/Re)) else if(Re=0)then Fi:=270 else if(Re<0)then Fi:=180+Kgrad*arctan(abs(Im/Re)) else Fi:=270+Kgrad*arctan(abs(Im/Re)); end else Fi:=0; AnguloZ:=Fi; end; { ------------------------------------------------------------} begin (* main *) clrscr; highvideo; Re:=3; Im:=4; Modulo:=ModuloZ(Re,Im); Fi:=AnguloZ(Re,Im); writeln(' writeln(' ',Re:2:2,'+j',Im:2:2); Mod=',Modulo:2:2,' Fi=',Fi:2:2,' grados'); readkey; end. Clase Teórica Nro 9 Pág. 5/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. Aquí hemos completado el pasaje de un complejo de la forma binaria a la forma exponencial o de Euler. program CambioDeBaseLogaritmica; { --------------------------------------------------Desarrollar las siguientes funciones: Log10(x) que determine el log decimal de x. LogB(x) que determine el log de x en cualquier base. ---------------------------------------------------- } uses crt; var N : double; { --------------------------------------------------- } function Log10(N:double):double; begin Log10:=Ln(N)/Ln(10); end; { --------------------------------------------------- } function LogB(N:double; B:double):double; begin LogB:=Ln(N)/Ln(B); end; { --------------------------------------------------- } begin (* main *) clrscr; highvideo; N:=14; writeln(' writeln(' Log10(',N:2:2,')=',Log10(N):2:4); Log10(',N:2:2,')=',LogB(N,10):2:4); readkey; end. Este par de funciones es interesante puesto que amplía la capacidad de cálculo de Pascal ya que permite determinar el logaritmo decimal y en general el logaritmo en cualquier base, de un número “x” dado. Otro tipo de subprograma: los procedimientos. Los procedimientos, a diferencia de las funciones, no retornan ningún valor en su propio nombre, sino a través de sus parámetros. Su estructura es la siguiente: procedure Nombre_del_procedimiento(lista de parámetros); Bloque declarativo. begin instrucciones ejecutables; end; Clase Teórica Nro 9 Pág. 6/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. en vez de function va la palabra clave procedure. Luego todo es idéntico, excepto que tampoco van los dos puntos verticales ni el tipo devuelto (porque no lo hay). ¿Entonces cómo puede devolver algún elemento que haya procesado? Bien, ha llegado el momento de mejorar nuestro concepto acerca de los parámetros. Parámetros por valor. Son todos los vistos cuando estudiamos las funciones. Se denominan también parámetros de entrada, puesto que actúan como una copia de aquél que es enviado desde el punto de invocación. Un ejemplo sencillo de la vida real aclarará este punto: imagine que Ud. requiere de los servicios de impresión de un taller gráfico. Se acerca al mostrador, pero en lugar de entregar el archivo original de su trabajo, le entrega una copia del mismo. El empleado hace su tarea de impresión y luego tira a la basura el CD, pero como se trataba de una copia, su original continúa a salvo. Esa es la idea. Parámetros por referencia. Aquí la cosa cambia, porque lo que se entrega en el mostrador no es una copia sino el original. Esto significa que todo lo que el empleado haga sobre él permanecerá cuando finalice con su trabajo. En la sintaxis el identificador va acompañado de la partícula var. procedure CargarMatriz ( var M : TMat); Aprovechamos y hacemos otro anuncio: en la declarativa de parámetros formales (ya sea en procedimientos o en funciones) Pascal no permite declaraciones estructuradas, pero sí un tipo definido por el usuario en una línea type y que haga alusión a dicha estructura: const DIM1 = 5; DIM2 = 7; type TMat = array[1..DIM1, 1..DIM2]of integer; La partícula var significa que al invocar al procedimiento CargarMatriz, el punto de invocación le está pasando la dirección donde reside el arreglo original, o sea que todo lo que el subprograma haga sobre el parámetro M, en realidad lo estará haciendo sobre el original. Esto podemos querer hacerlo por dos razones: Porque nos interesa conservar las modificaciones realizadas. Porque el dato (un arreglo) es demasiado grande y resultaría costoso en memoria pasarlo por valor y que se haga una copia local en el procedimiento. Siempre un ejemplo es lo mejor para aclarar dudas: Clase Teórica Nro 9 Pág. 7/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. program SubprogramaQueCargaMatriz; { -----------------------------------------------------------Desarrollar un procedimiento llamado CargarMatriz( ) cuya finalidad será cargar aleatoriamente una matriz de enteros de DIM1 x DIM2, que recibirá como único parámetro. Los valores aleatorios estarán comprendidos entre 50 y 100. En el main declarar dos matrices: Mat1[ ] y Mat2[ ] que serán cargadas por este procedimiento. También en el main mostrar por pantalla, y en forma matricial, el contenido asignado a ambos arreglos. ----------------------------------------------------------- } uses crt; const DIM1 = 5; DIM2 = 7; type TMat = array[1..DIM1,1..DIM2]of integer; var Mat1 Mat2 i,j ColM1 FilM1 ColM2 FilM2 : : : : : : : TMat; TMat; byte; byte; byte; byte; byte; { ------------------------------------------------------------ } procedure CargarMatriz(var M : TMat); var i,j : byte; begin for i:=1 to DIM1 do for j:=1 to DIM2 do M[i,j]:=50+random(51); end; { ------------------------------------------------------------ } begin (* main *) clrscr; highvideo; randomize; ColM1:=4; FilM1:=4; ColM2:=40; FilM2:=4; CargarMatriz(Mat1); CargarMatriz(Mat2); for i:=1 to DIM1 do begin for j:=1 to DIM2 do begin gotoxy(ColM1+(j-1)*4,FilM1); write(Mat1[i,j]); gotoxy(ColM2+(j-1)*4,FilM2); write(Mat2[i,j]); Clase Teórica Nro 9 Pág. 8/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. end; FilM1:=FilM1+3; FilM2:=FilM2+3; end; readkey; end. Para notar la diferencia entre un parámetro formal por referencia y por valor, haga la siguiente prueba: modifique en la cabecera del procedimiento la declarativa de M eliminando la partícula var. Vea qué pasa cuando intenta mostrar por pantalla el arreglo supuestamente cargado por el subprograma (que sabemos que al ser ahora un parámetro de entrada, los datos se pierden al finalizar su invocación). Veamos otro ejemplo: program MaxMinEnUnaMatriz; { -----------------------------------------------------Desarrollar dos procedimientos: CargarMatriz( ); MatrizMaxMin( ); cuyas finalidades son: La primera: Cargar una matriz de enteros de DIM x DIM elementos que será recibida como parámetro por referencia. Al mismo tiempo irá mostrándola por pantalla en forma matricial. La segunda: Determinar el Máximo y el Mínimo elementos almacenados. Mostrar en pantalla el Máximo y el Mínimo devueltos por el segundo procedimiento. ------------------------------------------------------ } uses crt; const DIM1 = 5; DIM2 = 7; type TMat = array[1..DIM1,1..DIM2]of integer; var Mat : TMat; Max : integer; Min : integer; { ----------------------------------------------------- } Clase Teórica Nro 9 Pág. 9/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. procedure CargarMatriz(var M:TMat); var i,j : byte; begin for i:=1 to DIM1 do begin for j:=1 to DIM2 do begin M[i,j]:=random(101); write(M[i,j]:4); end; writeln; end; end; { ----------------------------------------------------- } procedure MatrizMaxMin ( var M : TMat; var Max : integer; var Min : integer ); var i,j : byte; begin Max:=-32768; Min:=32767; for i:=1 to DIM1 do for j:=1 to DIM2 do begin if(M[i,j]>Max)then Max:=M[i,j]; if(M[i,j]<Min)then Min:=M[i,j]; end; end; { ----------------------------------------------------- } begin (* main *) clrscr; highvideo; randomize; CargarMatriz(Mat); MatrizMaxMin(Mat,Max,Min); writeln; writeln(' Max=',Max,' Min=',Min); readkey; end. Alcance de los identificadores. Todo identificador (variable o constante) declarados en el bloque del main, puede ser accesado desde cualquier ámbito del programa, ya sea desde los propios códigos del programa principal o desde cualquier subprograma. Se dice entonces que dicho identificador posee un alcance global, o más brevemente se trata de un identificador global. Clase Teórica Nro 9 Pág. 10/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. En cambio los identificadores declarados dentro de un subprograma sólo pueden ser accesados dentro del subprograma que le da vida, y en tanto el mismo se halle activo. Se dice entonces que dichos identificadores son de alcance local o identificadores locales. Modularidad. Ya habrá captado que los subprogramas permiten aplicar la técnica de refinamientos sucesivos al hacer posible la subdivisión de un programa complejo en muchos subprogramas de menor complejidad. He aquí las ventajas: Que muchos programadores puedan trabajar simultáneamente sobre distintos aspectos de un mismo problema. Ahorrar tiempo y esfuerzo al generar módulos que puedan ser llamados desde distintos puntos del programa. Permite reusabilidad de módulos que por su generalidad puedan ser utilizados por distintos programas: sólo hay que incluirlos en su segmento de códigos. Permiten generar nuevas instrucciones enriqueciendo la potencia y alcance del lenguaje. He aquí un ejemplo de este último punto: program ArcoSeno; { ----------------------------------------------------------Implementar una función que permita determinar el ArcSen(x) basándose en la Serie de Taylor: ArcSen(x)=x+1/2*x3/3+1/2*3/4*x5/5+1/2*3/4*5/6*x7/7+.... calculado con no menos de 3000 términos. ---------------------------------------------------------- } uses crt; const Kgrad = 180/PI; var x : double; Arco : double; { ----------------------------------------------------------- } function ArcSen(x:double):double; var i : word; n : word; Sum : double; { acumulada de términos } Pot_x : double; { valor anterior de x } TermAct : double; begin Sum:=x; Pot_x:=x; i:=3; Clase Teórica Nro 9 Pág. 11/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. repeat n :=i; Pot_x :=Pot_x*x*x; TermAct:=Pot_x/n; repeat n:=n-2; TermAct:=TermAct*n/(n+1); until(n=1); Sum:=Sum+TermAct; i :=i+2; until(i>10000); ArcSen:=Sum; end; { ----------------------------------------------------------- } begin (* main *) clrscr; highvideo; Arco:=0.422618261; (* 25 grados *) writeln('ArcSen(',Arco:1:9,')=',Kgrad*ArcSen(Arco):2:6); readkey; end. program FuncionesLogicas; { ------------------------------------------------------Implementar una función lógica de tal suerte que reciba como único parámetro un caracter y determine si se trata o no de una vocal. ------------------------------------------------------- } uses crt; { ----------------------------------------------------------- } function EsVocal(c:char):boolean; begin EsVocal:=false; case c of 'a','e','i','o','u' : EsVocal:=true; 'A','E','I','O','U' : EsVocal:=true; end; end; { ----------------------------------------------------------- } var Letra : char; Texto : string; i : byte; Voc : byte; { contador de vocales en la cadena } NoVoc : byte; Clase Teórica Nro 9 Pág. 12/13 INFORMATICA – C.B.I. (Ciclo Básico de Ingeniería) - 2012 Departamento de Ciencias de la Computación – FACET / UNT Grupo II – Dictado: Ing. Juan Manuel Conti. begin (* main *) clrscr; highvideo; Voc:=0; NoVoc:=0; Texto:='Turbo Pascal es un lenguaje muy estructurado'; writeln(' ',Texto); for i:=1 to length(Texto) do if(EsVocal(Texto[i]))then Voc:=Voc+1 else NoVoc:=NoVoc+1; writeln; writeln(' writeln(' writeln(' writeln(' Longitud de la cad=',length(Texto)); Total de vocales =',Voc); Total de otras =',NoVoc); Vocales+Novocales =',Voc+NoVoc); readkey; end. program ParametrosPorValor; { ---------------------------------------------------Implementar un procedure llamado CargarValor( ) que recibirá como parámetros, una variable "x" y un valor "k" que deberá asignárselo a x. Probar de recibir "x" por valor y ver que se imprime en el main luego de la invocación al procedimiento. Cambiar luego la modalidad a var y volver a verificar. ----------------------------------------------------- } uses crt; { ---------------------------------------------------- } procedure CargarValor(x:integer; n:integer); begin x:=n; end; { ---------------------------------------------------- } var a : integer; k : integer; begin (* main *) clrscr; highvideo; a:=-12; k:=5; CargarValor(a,k); writeln(' a=',a); readkey; end. Clase Teórica Nro 9 Pág. 13/13