Mostrando entradas con la etiqueta Pascal. Mostrar todas las entradas
Mostrando entradas con la etiqueta Pascal. Mostrar todas las entradas

24 septiembre 2014

Pascal - 6d: Parámetros por referencia

6.4. Modificación de parámetros

Ya habíamos visto qué son los parámetros: una serie de datos extra, que indicábamos entre paréntesis en la cabecera de un procedimiento o función.
Es algo que estamos usando, casi sin darnos cuenta, desde el primer apartado del curso, cuando empezamos a usar "WriteLn":
writeLn( 'Hola' );
Esta línea es una llamada al procedimiento "WriteLn", y como parámetros le estamos indicando lo que queremos que escriba, en este caso, el texto "Hola".
Pero vamos a ver qué ocurre si modificamos un parámetro:
(* PROCMOD1.PAS, Procedimiento que modifica parametros *)
(* Primer intento: paso por valor                      *)
(* Parte de CUPAS5, por Nacho Cabanes                  *)
 
program ModificarParametros1;
 
var dato: integer;
 
procedure modifica( variable : integer);
begin
    variable := 3 ;
    writeLn( 'Dentro: ', variable );
end;
 
begin
    dato := 2;
    writeLn( 'Antes: ', dato );
    modifica( dato );
    writeLn( 'Después: ', dato );
end. 
 
¿Qué podemos esperar que pase? Vamos a ir siguiendo cada instrucción:
  • Declaramos el nombre del programa
  • Usaremos la variable "dato", de tipo entero.
  • El procedimiento "modifica" toma una variable de tipo entero, le asigna el valor 3 y la escribe. Lógicamente, siempre escribirá 3.
  • Empieza el cuerpo del programa: damos el valor 2 a "dato".
  • Escribimos el valor de "dato". Claramente, será 2.
  • Llamamos al procedimiento "modifica", que asigna el valor 3 a "dato" y lo escribe.
  • Finalmente volvemos a escribir el valor de "dato"... ¿3?
Pues no es eso lo que ocurre: escribe un 2. Las modificaciones que hagamos a "dato" dentro del procedimiento "modifica" sólo son válidas mientras estemos dentro de ese procedimiento (lo que realmente ocurre es que el procedimiento está recibiendo una copia de la variable, de modo que si hacemos algún cambio, éstos no se cambios no afectan a la variable original, que existe fuera del procedimiento).
Eso es pasar un parámetro por valor: podemos leer su valor, pero no podemos alterarlo (los cambios se pierden al salir).
La alternativa es "pasar parámetros por referencia, añadiendo la palabra "var" delante de cada parámetro que sea modificable dentro del procedimiento (o función):
(* PROCMOD2.PAS, Procedimiento que modifica parametros *)
(* Segundo intento: paso por referencia                *)
(* Parte de CUPAS5, por Nacho Cabanes                  *)
 
program ModificarParametros2;
 
var dato: integer;
 
procedure modifica( var variable : integer);
begin
    variable := 3 ;
    writeLn( 'Dentro: ', variable );
end;
 
begin
    dato := 2;
    writeLn( 'Antes: ', dato );
    modifica( dato );
    writeLn( 'Después: ', dato );
end.
 
Esta vez la última línea del programa sí que escribe un 3 y no un 2, porque hemos permitido que los cambios hechos a la variable salgan del procedimiento (para eso hemos añadido la palabra "var").
El nombre "referencia" alude a que no se pasa realmente al procedimiento o función el valor de la variable, sino la dirección de memoria en la que se encuentra, algo que más adelante llamaremos un "puntero".
Una de las aplicaciones más habituales de pasar parámetros por referencia es cuando debemos devolver más de un valor: habíamos visto que una función era muy parecida a un procedimiento, pero además devolvía un valor (pero sólo uno); cuando necesitamos obtener más de un valor de salida, la forma habitual de conseguirlo es pasándolos como parámetros por referencia, (precedidos por la palabra "var"). Existe alguna otra alternativa menos elegante, como devolver un array, cosas que no tiene mucho sentido si no se trata de datos relacionados..
Otra aplicación puede ser optimizar (ligeramente) velocidad: si pasamos datos de gran tamaño, será algo más lento pasarlos por valor (hay que duplicar toda su estructura) que pasarlos por referencia (sólo se pasa su dirección), pero este tipo de consideraciones hay que tenerlas más adelante, no en la primera implementación del programa, porque tratar de optimizar velocidad antes de tener la certeza de que todo funciona correctamente puede dar lugar a errores difíciles de detectar.
¿Y si la variable tiene el mismo nombre en la función y en el cuerpo del programa? ¿Conseguiremos "engañar" al compilador y modificar su valor sin necesidad de usar "var"?
(* PROCMOD3.PAS, Procedimiento que modifica parametros *)
(* Tercer intento: paso por valor de variables con el  *)
(*     el mismo nombre                                 *)
(* Parte de CUPAS5, por Nacho Cabanes                  *)
 
program ModificarParametros3;
 
var dato: integer;
 
procedure modifica( dato : integer);
begin
    dato := 3 ;
    writeLn( 'Dentro: ', dato );
end;
 
begin
    dato := 2;
    writeLn( 'Antes: ', dato );
    modifica( dato );
    writeLn( 'Después: ', dato );
end.
 
 
Como se ve en este ejemplo, da igual el nombre que tengan las variables. El comportamiento será el mismo: ambas variables son distintas, aunque el nombre parezca indicar lo contrario.

(Como siempre, tienes más detalles y ejercicios propuestos en la versión oficial del curso).

23 septiembre 2014

Pascal - 6c: Funciones

6.3. Funciones

En muchas ocasiones querremos obtener un valor como resultado de esa "subrutina" que hemos creado. Llamaremos "función" a un procedimiento capaz de devolver un valor. Por ejemplo, podríamos crear una función sencilla, que calcule (y devuelva) el doble del número que se le pase como parámetro, así:
(* DOBLE.PAS, Primer ejemplo de funcion *)
(* Parte de CUPAS5, por Nacho Cabanes   *)
 
program FuncionDoble;
 
function doble( numero: integer): integer;
begin
    doble := numero * 2;
end;
 
(* Cuerpo del programa *)
var
    n: integer;
 
begin
    write('Introduce un numero entero: ');
    readLn( n );
    writeLn('Su doble es: ', doble(n) );
    writeLn('Y el doble de 5 es: ', doble(5) );
end. 
 
(* 
Ejemplo de ejecucion:
Introduce un numero entero: 3
Su doble es: 6
Y el doble de 5 es: 10
*)
 
Aquí hay varios detalles que merece la pena comentar, algunos de ellos nuevos:
  • Esta función se llama "doble".
  • Tiene un parámetro llamado "numero", que es un número entero.
  • El resultado va a ser también un número entero.
  • Antes de salir de la función, le asignamos el que será su valor definitivo.
  • Desde el cuerpo del programa usamos la función 2 veces: la primera para calcular el doble del número "n", que ha introducido el usuario, y la segunda para hallar el doble de 5.

Una función puede recibir más de un parámetro, y puede realizar operaciones más complejas. Por ejemplo, podríamos calcular una potencia (un número elevado a otro) mediante multiplicaciones repetitivas, así:
(* POTENCIA.PAS, Segundo ejemplo de funcion *)
(* Parte de CUPAS5, por Nacho Cabanes       *)
 
program PruebaDePotencia;
 
 
function potencia(a,b: integer): integer;   (* Definimos la funcion *)
var
    i: integer;                      (* Locales: para bucles *)
    temporal: integer;               (* y para el valor temporal *)
begin
    temporal := 1;                    (* Inicializacion *)
    for i := 1 to b do
        temporal := temporal * a;     (* multiplicamos "b" veces por "a" *)
    potencia := temporal;             (* y finalmente devolvemos el valor *)
end;
 
 
var
    numero1, numero2: integer;
 
begin                                (* Cuerpo del programa *)
    writeLn('Potencia de un numero entero');
    writeLn;
    writeLn('Introduce el primer numero');
    readLn( numero1 );
    writeLn('Introduce el segundo numero');
    readLn( numero2 );
    writeLn( numero1 ,' elevado a ', numero2 ,' vale ',
        potencia (numero1, numero2) )
end. 
 

Por supuesto, una función puede devolver valores de otros tipos, no solo numéricos. Así, una función que nos devolviera la inicial de una palabra podría ser:
(* INICIAL.PAS, Funcion que devuelve char *)
(* Parte de CUPAS5, por Nacho Cabanes     *)
 
program FuncionInicial;
 
function inicial( texto: string): char;
begin
    inicial := texto[1];
end;
 
(* Cuerpo del programa *)
var
    frase: string;
 
begin
    write('Introduce una frase: ');
    readLn( frase );
    writeLn('Su primera letra es: ', inicial(frase) );
end. 
 
(* 
Ejemplo de ejecucion:
Introduce una frase: hola, que tal
Su primera letra es: h
*)
 
O bien podríamos recorrer una frase formada por varias palabras, para así construir y devolver una cadena que contenga las iniciales de todas ellas:
(* INICIALES.PAS, Funcion que devuelve un string *)
(* Parte de CUPAS5, por Nacho Cabanes            *)
 
program FuncionIniciales;
 
function iniciales( texto: string): string;
var
    temporal: string;
    i: byte;
begin
    temporal := '';
    temporal := temporal + texto[1];
    for i := 2 to length(texto) do
        if texto[i-1] = ' ' then
            temporal := temporal + texto[i];
    iniciales := temporal;
end;
 
(* Cuerpo del programa *)
var
    frase: string;
 
begin
    write('Introduce una frase con varias palabras: ');
    readLn( frase );
    writeLn('Sus iniciales son: ', iniciales(frase) );
end. 
 
(* 
Ejemplo de ejecucion:
Introduce una frase con varias palabras: hola, que tal
Sus iniciales son: hqt
*)
 

22 septiembre 2014

Pascal - 6b: Procedimientos con parámetros

6.2. Procedimientos con parámetros.

La verdadera potencia de los procedimientos está en que no tienen por qué trabajar siempre con los mismos datos, sino que podemos indicarles unos datos adicionales, que llamaremos parámetros. Por ejemplo, podemos mejorar el procedimiento que escribía asteriscos, si le indicamos la cantidad de asteriscos que debe mostrar en pantalla, así:
(* PROC03.PAS, procedimientos con parametros *)
(* Parte de CUPAS5, por Nacho Cabanes        *)
 
program Proc03;
 
procedure EscribirAsteriscos( cantidad: integer );
var
    i: byte;
begin
    { Escribimos N asteriscos y avanzamos de linea }
    for i := 1 to cantidad do
        write('*');
    writeLn;
end;
 
begin
    writeLn( 'Primer texto' );
    EscribirAsteriscos(12);
    writeLn( 'Y un segundo texto' );
    EscribirAsteriscos(18);
    writeLn( 'Y texto de despedida' );
    EscribirAsteriscos(20);
end.  
 
(* 
Resultado:
Primer texto
************
Y un segundo texto
******************
Y texto de despedida
********************
*)
 

¿Quieres ejercicicios propuestos? Ya sabes: la versión oficial del curso.

20 septiembre 2014

Pascal - 6a: Procedimientos

6. Procedimientos y funciones

6.1. Procedimientos.

Hemos visto cómo hacer que se repita un bloque de programa, usando estructuras como "for", "while" o "repeat". Pero cuando un fragmento de programa se debe repetir en varias partes de nuestro fuente, el resultado puede seguir siendo redundante. Por ejemplo, podemos escribir varias frases "subrayadas" con asterisco así:
(* PROC01.PAS, procedimientos: acercamiento *)
(* Parte de CUPAS5, por Nacho Cabanes       *)
 
program Proc01;
 
var
    i: byte;
 
begin
    { Primer texto }
    writeLn( 'Primer texto' );
 
    { Escribimos 20 asteriscos y avanzamos de linea }
    for i := 1 to 20 do
        write('*');
    writeLn;
 
 
    { Segundo texto }
    writeLn( 'Y un segundo texto' );
 
    { Escribimos 20 asteriscos y avanzamos de linea }
    for i := 1 to 20 do
        write('*');
    writeLn;
 
 
    { Tercer texto }
    writeLn( 'Texto de despedida' );
 
    { Escribimos 20 asteriscos y avanzamos de linea }
    for i := 1 to 20 do
        write('*');
    writeLn;
end.  
 
(* 
Resultado:
Primer texto
********************
Y un segundo texto
********************
Texto de despedida
********************
*)
 
Esto sigue siendo claramente repetitivo, a pesar de utilizar "for". Para mejorarlo, podemos crear un "bloque" llamado "Escribir20asteriscos" y que sirva como una "abreviatura" que nos permita no tener que teclear tantos código repetitivo. Además, el repetir menos hace que el programa sea menos propenso a errores, ya sea en el momento de teclear o cuando vayamos a ampliarlo o corregirlo (por ejemplo, si queremos cambiar los 20 asteriscos por 18, corremos el riesgo de olvidar hacerlo en alguno de los bloques). Quedaría así:
(* PROC02.PAS, procedimientos: uso real *)
(* Parte de CUPAS5, por Nacho Cabanes   *)
 
program Proc02;
 
procedure Escribir20asteriscos;
var
    i: byte;
begin
    { Escribimos 20 asteriscos y avanzamos de linea }
    for i := 1 to 20 do
        write('*');
    writeLn;
end;
 
begin
    writeLn( 'Primer texto' );
    Escribir20asteriscos;
    writeLn( 'Y un segundo texto' );
    Escribir20asteriscos;
    writeLn( 'Texto de despedida' );
    Escribir20asteriscos;
end.  
 
(* 
Resultado:
Primer texto
********************
Y un segundo texto
********************
Texto de despedida
********************
*)
 
 
Estos bloques, que llamaremos "procedimientos" (y que en otros lenguajes de programación se conocen como "subrutinas") ayudarán a que nuestro programa sea menos repetitivo y resulte más fácil de leer.
En este programa, "i" es una variable local: se declara dentro del procedimiento "Escribir20asteriscos" y sólo es accesible dentro de él. Si en el cuerpo del programa intentamos hacer algo como "i:=2;" el programa no compilará, porque la variable no existe en esa zona. Debemos buscar conseguir que tantas variables como podamos sean locales. Sólo deberán ser "globales" (compartidas por todo el programa, y declaradas fuera de procedimientos) las variables que realmente se vayan a utilizar desde varias zonas distintas.

19 septiembre 2014

Pascal - 5b: Definición de tipos

5.2. Definición de tipos.

El "tipo" de una variable es lo que determina qué clase de valores podremos guardar en ella. En los programas que hemos creado hasta ahora, el "tipo" es lo que indicamos junto al nombre de una variable cuando la declaramos. Por ejemplo,
var PrimerNumero: real;
indica que vamos a usar una variable que se va a llamar PrimerNumero y que almacenará números reales (con decimales), mientras que
const intentos: integer = 3;
indica que vamos a usar una variable llamada intentos, que guardará números enteros y que tendrá como valor inicial 3.
También podemos crear nuestros tipos de datos, a partir de los tipos básicos que permite el lenguaje, usando la palabra "type". Así, por ejemplo, podemos crear una nueva versión del ejemplo de "array de records" del apartado 4.3.3, definiendo
(* ARR_REC2.PAS, "array" de varios "record" *)
(* Segunda version, con "const" y "type"    *)
(* Parte de CUPAS5, por Nacho Cabanes       *)
 
program RecordArray2;
 
const
    maxDatos = 10;
 
type
    ficha = record
        nombre: string[30];
        edad: byte;
    end;
 
var
    datos: array [1..maxDatos] of ficha;
 
begin
    datos[1].nombre := 'Ignacio';
    datos[1].edad := 23;
    write('El primer nombre es ', datos[1].nombre );
    write(' y la primera edad ', datos[1].edad);
end.
 
 
(* Resultado:
El primer nombre es Ignacio y la primera edad 23
*)
 
Esto añade legibilidad, pero no es imprescindible. Entonces... ¿no será necesario definir tipos? Sí, en algunas circunstancias concretas sí será necesario. Un primer ejemplo en cuando tenemos dos variables declaradas de igual forma en distintas partes del programa, como en este ejemplo:
(* TIPOS1.PAS, asignacion INCORRECTA de tipos *)
(*            No compila !!!                  *)
(* Parte de CUPAS5, por Nacho Cabanes         *)
 
program Tipos1;
 
var
    ficha1: record
        nombre: string;
        email: string;
        anyoNacimiento: integer;
    end;
 
    ficha2: record
        nombre: string;
        email: string;
        anyoNacimiento: integer;
    end;
 
begin
    ficha1.nombre := 'Pepe';
    ficha1.email := 'p@p.com';
    ficha1.anyoNacimiento := 1990;
 
    ficha2 := ficha1;
    writeLn( ficha2.nombre );
end.  
 
Aunque a nuestros ojos parezca que ambas variables son del mismo tipo, para el compilador no es así, de modo que el programa anterior no compilar correctamente.
Podríamos declarar ambas a la vez, de la siguiente forma:
var
    ficha1, ficha2: record
        nombre: string;
        email: string;
        anyoNacimiento: integer;
    end;
pero eso no servirá si las variables están en zonas separadas del programa (algo que nos empezará a ocurrir pronto). En ese caso, no habrá más remedio que crear un nuevo tipo de datos, y declarar ambas variables como pertenecientes a dicho tipo:
(* TIPOS2.PAS, asignacion correcta de tipos *)
(* Parte de CUPAS5, por Nacho Cabanes       *)
 
program Tipos2;
 
type TipoFicha = record 
        nombre: string;
        email: string;
        anyoNacimiento: integer;
    end;
 
var
    ficha1: TipoFicha;
    ficha2: TipoFicha;
 
begin
    ficha1.nombre := 'Pepe';
    ficha1.email := 'p@p.com';
    ficha1.anyoNacimiento := 1990;
 
    ficha2 := ficha1;
    writeLn( ficha2.nombre );
end.  
 
Y habrá más casos en los que sea imprescindible declarar tipos, como cuando vayamos a volcar nuestros "record" a fichero, pero eso lo veremos más adelante.

18 septiembre 2014

Pascal - 5a: Constantes

5.1. Definición de constantes

5.1.1. Valores que no cambian

Cuando desarrollamos un programa, nos podemos encontrar con que hay variables que realmente "no varían" a lo largo de la ejecución de un programa, sino que su valor es constante.
Hay una manera especial de definirlas: con el especificador "const", que tiene el formato
const Nombre = Valor;
Cuando las declaramos, no será necesario indicar el tipo de datos, porque el compilador lo puede deducir:
const MiNombre = 'Nacho Cabanes'; 
const PI = 3.1415926535; 
const LongitudMaxima = 128; 
Ya en el cuerpo del programa, estas constantes se manejan igual que variables como las que habíamos visto hasta hora, con la diferencia de que no se nos permitirá cambiar su valor. Por tanto, es válido hacer
WriteLn(MiNombre); 
if Longitud > LongitudMaxima then ... 
OtraVariable := MiNombre; 
LongitudCircunferencia := 2 * PI * radio; 
pero no podríamos hacer
PI := 3.14; 
MiNombre := 'Nacho'; 
LongitudMaxima := LongitudMaxima + 10;
Con los pocos conocimientos que llevamos hasta ahora, las constantes nos resultarán especialmente útiles para indicar el tamaño máximo de un array, de modo que los cambios en el programa sean mínimos en caso de que decidamos usar un tamaño de array mayor o menor, como en este ejemplo:
(* CONST01.PAS, Const y arrays (01)   *)
(* Parte de CUPAS5, por Nacho Cabanes *)
 
Program Const01;
 
const
    MAXIMO = 5;
 
var
    datos: array[1..MAXIMO] of integer;
    i: integer;
 
begin
    for i := 1 to MAXIMO do
    begin
        write('Deme el dato ',i,': ');
        readLn(datos[i]);
    end;
 
    write('Los datos al reves son: ');
    for i := MAXIMO downto 1 do
        write(datos[i], ' ');
 
    writeLn;
end. 
 
Si decidimos que queremos trabajar con 10 datos, el único cambio sería el valor de la constante; el resto de programa se comportaría correctamente en cuanto recompilásemos.
Como se ve en este ejemplo, las declaraciones de constantes se hacen antes del cuerpo del programa principal, y generalmente antes de las declaraciones de variables.

5.1.2. Inicialización: Constantes "con tipo"

En Turbo Pascal (y Free Pascal), el identificador "const" tiene también otro uso menos habitual: definir lo que se suele llamar "constantes con tipo", que no son más que variables normales, pero a las que damos un valor inicial antes de que comience a ejecutarse el programa. Se usa
const variable: tipo = valor;
Un ejemplo más detallado sería:
(* CONSTTIPO.PAS, Constantes con tipo *)
(* Parte de CUPAS5, por Nacho Cabanes *)
 
Program ConstTipo;
 
const
    x: integer = 5;
    y: byte = 2;
 
begin
    writeLn('La suma de X e Y es: ', x+y);
 
    write('Nuevo valor para X? ');
    readLn(x);
 
    write('Nuevo valor para Y? ');
    readLn(y);
 
    writeLn('La nueva suma de X e Y es: ', x+y);
end. 
 
(* Ejemplo de ejecucion:
La suma de X e Y es: 7
Nuevo valor para X? 3
Nuevo valor para Y? 6
La nueva suma de X e Y es: 9
*)
 
La única ventaja real sobre el uso "habitual" de una variable es la legibilidad: en el mismo punto en el que se declara la variable, queda indicado cuál queremos que sea su valor inicial.
Ejercicio propuesto 5.1.2.1: Crea un programa que pida al usuario 10 números enteros y vaya calculando y mostrando su suma. No debes almacenar los datos en un array, y la suma debe guardarse en una variable que se haya inicializado a 0, usando una "constante con tipo".
Ejercicio propuesto 5.1.2.2: Crea un programa que pida al usuario 5 números enteros y vaya calculando y mostrando su producto. No debes almacenar los datos en un array, y el producto se debe guardar en una variable que se haya inicializado a 1, empleando una "constante con tipo".

5.1.3. Inicialización de arrays

Las "constantes con tipo" son especialmente útiles para dar valores iniciales a todo un array, indicando todos sus valores entre paréntesis y separados por comas, así:
(* CONSTTIPOARR.PAS, Constantes con tipo          *)
(* como forma de dar valores iniciales a un array *)
(* Parte de CUPAS5, por Nacho Cabanes             *)
 
program ConstTipoArr;
 
const
    diasMes : array[1..12] of byte =
        (31,28,31,30,31,30,  { Enero a Junio }
         31,31,30,31,30,31); { Julio a Diciembre }
 
var
    mes: byte;
 
begin
    write('Dime un numero de mes: ');
    readLn(mes);
 
    writeLn('Ese mes tiene ', diasMes[ mes ], ' dias');
end. 
 
(* Ejemplo de ejecucion:
Dime un numero de mes: 7
Ese mes tiene 31 dias
*)
 
No sólo se pueden emplear valores numéricos. Por ejemplo, también podemos crear un array de cadenas de texto, si delimitamos los valores entre comillas simples, de la siguiente manera:
(* CONSTTIPOARR2.PAS, Constantes con tipo         *)
(* como forma de dar valores iniciales a un array *)
(* Version con cadenas de texto                   *)
(* Parte de CUPAS5, por Nacho Cabanes             *)
 
Program ConstTipoArr2;
 
const
    nombreMes : array[1..12] of string =
        ('enero', 'febrero', 'marzo', 'abril',
         'mayo', 'junio', 'julio', 'agosto',
         'septiembre', 'octubre', 'noviembre', 'diciembre');
 
var
    mes: byte;
 
begin
    write('Dime un numero de mes: ');
    readLn(mes);
 
    writeLn('Ese mes se llama ', nombreMes[ mes ]);
end. 
 
(* Ejemplo de ejecucion:
Dime un numero de mes: 4
Ese mes se llama abril
*)
 
(Como siempre, más detalles y ejercicios propuestos en la página oficial del curso).