Buscar este blog

miércoles, 11 de mayo de 2016

Final de las lecciones

Con lo visto hasta ahora en este blog, ya tienes la suficiente soltura para programar en jC. Ahora, puedes seguir con el libro "Fundamentos de programación con jC" para ahondar en un conocimiento más formal.

Si prefieres no seguir este blog al revés, en la sección "material didáctico" encontrarás también el contenido de este blog como "lecciones de programación en formato de tutorial". Allí también encontrarás el libro en formato web.

Si tu preferencia es leer en papel, puedes encontrar el libro, que incluye el tutorial, en http://lulu.com/spotlight/baltasarq/.

martes, 26 de noviembre de 2013

Mezclando consola y gráficos

La consola (entrada y salida de texto) y la salida gráfica no son incompatibles. jC es capaz de mezclar ambas cosas, para poder realizar aplicaciones más interesantes.

En este caso, la tarea a realizar será retomar la creación de polígonos, aunque tomando los datos (distancia y lados) de la consola, de tal manera que sea el usuario el que pueda elegir qué polígono representar.

Ya conocemos la función necesaria para poder dibujar un polígono, como se vió en una entrada anterior. Ya que lo que hace que la figura a representar varíe es el número de lados y la distancia a recorrer para cada lado, esos serán los parámetros de la función:

void dibujaPoligono(int lados, int distancia)
{
 def angulo = 360 / lados;

 for(def i = 0; i < lados; ++i) {
  turtle.turnRight( angulo );
  turtle.forward( distancia );
 }
}

En realidad, conociendo esto, sólo es necesario pedir los datos (lados y distancia), y llamar a la función adecuada. El código aparece a continuación.


/** @name   Poly
  * @brief  Dibuja poligonos
  * @author jbgarcia@uvigo.es
  * @date   2013-8-10
  */

import std.io;
import std.string;
import media.gw;
import media.turtle;

void dibujaPoligono(int lados, int distancia)
{
 def angulo = 360 / lados;
 for(def i = 0; i < lados; ++i) {
  turtle.turnRight( angulo );
  turtle.forward( distancia );
 }
}

gw.setTitle( "Poly" );
def lados = strToInt( readln( "Introduzca num. de lados: " ) );
def distancia = strToInt( readln( "Introduzca distancia de lado: " ) );
dibujaPoligono( lados, distancia );

viernes, 22 de noviembre de 2013

La consola

Aunque ya la hemos estado utilizando en muchos ejercicios, vamos a estudiar un poco más en profundidad cómo funciona la consola en jC.

Además de pintar, jC también es capaz de pedir e imprimir texto. Esto se denomina "consola", pues es la forma estándar de comunicarse con un ordenador: introduciendo órdenes por el teclado y observando el resultado por la pantalla.

    Las funciones que tenemos a nuestra disposición para manejarnos con la consola son precisamente dos:
  • print( <expr> ): Permite mostrar cualquier valor por la pantalla. Hay dos variantes: print() y println(), la segunda realiza un salto de línea tras imprimir.

    Ejemplo

    print( "Tu nombre es: " );
    println( nombre );
    
  • readln( <msg> ): Devuelve la cadena de texto introducida por el usuario. El mensaje permite que el usuario conozca qué datos se le están preguntando.

    Ejemplo

    def nombre = readln( "Introduce tu nombre: " );
    print( "Hola, " );
    println( nombre );
    
    Dado que lo que devuelve readln() es una cadena, el código anterior es equivalente a:

    Ejemplo

    char[] nombre = readln( "Introduce tu nombre: " );
    print( "Hola, " );
    println( nombre );
    

No siempre querremos obtener cadenas de texto por parte del usuario. En ocasiones, querremos preguntarle datos numéricos, como por ejemplo, una edad o una altura. Aunque sólo es posible leer cadenas de texto de la consola, siempre es posible convertirlas a un número, bien sea este entero (int) o real (double). En la librería estándar std.string, tenemos a nuestra disposición las funciones strToInt() y strToDbl(), que convierten una cadena pasada por parámetro en un número, entero y real, respectivamente.
    Funciones de conversión de cadena a número:
  • strToInt( <cadena de texto> ): Esta función acepta una cadena de texto como parámetro, y devuelve el número entero que contiene.

    Ejemplo

    int edad = strToInt( cadenaDeTexto );
    println( edad );
    
  • strToDbl( <cadena de texto> ): Esta función acepta una cadena de texto como parámetro, y devuelve el número real que contiene.

    Ejemplo

    int altura = strToDbl( cadenaDeTexto );
    println( altura );
    

Realizaremos ahora un programa completo, que permita convertir grados celsius en farenheit, y viceversa. Para convertir los grados celsius (c) en farenheit (f), es necesario aplicar la fórmula:

f = ( c * 1,8 ) + 32
Para realizar la conversión inversa, de farenheit (f) a celsius (c):
c = ( f - 32 ) / 1,8

Es muy fácil escribir, por tanto dos funciones que hagan las conversiones respectivas. Dado que los grados llevan posiciones decimales, es necesario que las variables a utilizar sean de tipo número real (double).
double cnvtGradosCelsiusFarenheit(double x)
{
 return ( ( x * 1.8 ) + 32 );
}

double cnvtGradosFarenheitCelsius(double x)
{
 return ( ( x - 32 ) / 1.8 );
}

Sólo resta, por tanto, pedir una cadena de texto, y convertirla a un número real (double), esto puede hacerse de manera muy sencilla:

def grados = strToDbl( readln( "Introduce unos grados: " ) );
Una vez que se ha llamado a las funciones anteriores con la variable grados, sólo es necesario imprimir el resultado. El programa completo aparece a continuación:

/** @name   conversorGrados
  * @brief  Convierte los grados celsius a farenheit y vicecersa.
  * @author jbgarcia@uvigo.es
  * @date   2013-11-21
  */

import std.io;
import std.string;

double cnvtGradosCelsiusFarenheit(double x)
{
 return ( ( x * 1.8 ) + 32 );
}

double cnvtGradosFarenheitCelsius(double x)
{
 return ( ( x - 32 ) / 1.8 );
}

def grados = strToDbl( readln( "Introduce unos grados: " ) );

print( "Los grados celsius introducidos son grados farenheit: " );
println( cnvtGradosCelsiusFarenheit( grados ) );

print( "Los grados farenheit introducidos son grados celsius: " );
println( cnvtGradosFarenheitCelsius( grados ) );

viernes, 15 de noviembre de 2013

Funciones

Las funciones son como procedimientos, pero en lugar de no devolver nada, devuelven un valor, que se marca con return. El tipo de la devolución del valor se indica en el lugar en el que en un procedimiento se indica void.

Recordemos por tanto un procedimiento:

void imprimeConAsteriscos(char[] msg)
{
    print( "*** " );
    println( msg );
}

Una función se utiliza para realizar algún tipo de cálculo o proceso, cuyo resultado hay que devolver. Una función muy sencilla se puede ver a continuación:

double alCuadrado(double x)
{
    return ( x * x );
}

Una vez creada la función, es posible utilizarla en cualquier otra función o procedimiento, de igual forma que se utiliza un procedimiento (indicando su nombre y parámetros, estos entre paréntesis), pero con la peculiaridad de que es necesario guardar el resultado en una variable o utilizarlo en otro cálculo. No es necesario que la función a utilizar haya sido creado antes de la función que la utiliza. Por ejemplo, la hipotenusa de un triángulo rectángulo se calcula haciendo la raiz cuadrada de la suma del cuadrado de las hipotenusas. El programa completo aparece a continuación.

import std.math;
import std.io;

double calculaHipotenusa(double cateto1, double cateto2)
{
    return sqrt( alCuadrado( cateto1 ) + alCuadrado( cateto2 ) );
}

double alCuadrado(double x)
{
    return ( x * x );
}

print( "Hipotenusa para un triángulo de lados 5 y 6:" );
println( calculaHipotenusa( 5, 6 ) );

Un programa anterior transformaba un nombre, convirtiéndolo a minúsculas, a excepción de la primera letra, que siempre se pone en mayúsculas. Para hacerlo, se utilizaba un procedimiento y se aprovechaba el hecho de que los vectores se pasan por referencia.

/** @name   nombres
  * @brief  Escribe un nombre propio formateado en pantalla
  * @author jbgarcia@uvigo.es
  * @date   2013-10-28
  */

import std.io;
import std.util;
import std.charType;

void cnvtNombre(char[] s)
{
 s[ 0 ] = toUpperCase( s[ 0 ] );
 
 for(def i = 1; i < size( s ); ++i) {
  s[ i ] = toLowerCase( s[ i ] );
 }
 
 return;
}

char[] nombre = readln( "Dame tu nombre: " );
cnvtNombre( nombre );
println( nombre );

En lugar de crear un procedimiento para realizar esta tarea, es posible escribir una función que cree una copia de la cadena de texto, y devuelva una cadena nueva. Una ventaja de esto es que la cadena original no se modifica, si bien por el contrario la desventaja es que se utiliza el doble de memoria. El programa completo aparece a continuación.

/** @name   nombres
  * @brief  Escribe un nombre propio formateado en pantalla
  * @author jbgarcia@uvigo.es
  * @date   2013-10-28
  */

import std.io;
import std.util;
import std.charType;

char[] cnvtNombre(char[] s)
{
 def toret = new char[ size( s ) ];
 
 toret[ 0 ] = toUpperCase( s[ 0 ] );
 
 for(int i = 1; i < size( s ); ++i) {
  toret[ i ] = toLowerCase( s[ i ] );
 }
 
 return toret;
}

char[] nombre = readln( "Dame tu nombre: " );
println( cnvtNombre( nombre ) );

lunes, 28 de octubre de 2013

Paso de parámetros (II)

El paso de parámetros no siempre es por valor (también conocido "por copia"). El problema consiste en que los tipos de datos más complejos, como los vectores y matrices, tardan mucho en ser copiados. Así, no es lo mismo pasar un número entero, que un vector de 200 numeros enteros.

Por eso, el paso de vectores se hace por referencia. No hace falta marcar nada, simplemente sucede. Se le llama "por referencia" porque se pasa un número entero que permite localizar al vector, y referirse a él.

La implicación de todo esto es que, cuando se cambian las posiciones de un vector, los cambios realizados se mantienen después de volver de la función que lo llamó.

Veamos un ejemplo:


/** @name   nombres
  * @brief  Escribe un nombre propio formateado en pantalla
  * @author jbgarcia@uvigo.es
  * @date   2013-10-28
  */

import std.io;
import std.util;
import std.charType;

void cnvtNombre(char[] s)
{
 s[ 0 ] = toUpperCase( s[ 0 ] );
 
 for(def i = 1; i < size( s ); ++i) {
  s[ i ] = toLowerCase( s[ i ] );
 }
 
 return;
}

char[] nombre = readln( "Dame tu nombre: " );
cnvtNombre( nombre );
println( nombre );

Este programa toma un nombre, y lo convierte, de forma que la inicial pasa a estar en mayúsculas, y el resto de las letras del nombre pasado, en minúsculas. Esto se hace en la función cnvtNombre(), y dado que el vector se pasa por referencia (es el vector de caracteres s en cnvtNombre()), los cambios repercuten en el programa principal (donde el vector se llama nombre), con lo que se puede visualizar el resultado sin problema.

martes, 15 de octubre de 2013

Vectores

Los vectores son tipos complejos de datos. Esto es así porque se crean a partir de datos simples: en concreto, se trata de colecciones de datos de tipo simple, de tal forma que el tamaño de la colección siempre es fijo. El tamaño de cualquier vector se puede obtener utilizando la función size().

Los tipos de los vectores se crean simplemente añadiendo "[]" al tipo base del vector (es decir, el tipo de cada elemento del mismo). Para acceder a un elemento del vector, se indica el nombre del vector, y a continuación, entre corchetes, la posición a la que se quiere acceder. Es necesario tener en cuenta que la posición del primer elemento es 0, y que la última posición de un vector v es size( v ) - 1.

TipoExplicación
int El tipo es int[]. Se trata de un vector de números enteros.
double El tipo es double[]. Se trata de un vector de números reales.
char El tipo es char[]. Se trata de un vector de caracteres. Los vectores de caracteres tienen un significado especial: se interpretan como texto, en el que cada posición del vector es un carácter del mismo.

Aunque es posible crear un vector de booleanos, no es con diferencia un vector tan utilizado como los tres tipos anteriores.

Supongamos como ejemplo que se desea calcular la media de un vector de números reales. La creación de un vector se hace utilizando el operador new, que crea un nuevo vector. A continuación, se indica el tipo, y, entre corchetes, el número total de elementos.

double[] v = new double[ 3 ];

v[ 0 ] = 10;
v[ 1 ] = 12;
v[ 2 ] = 14;

def suma = 0.0;
for(def i = 0; i < size( v ); ++i) {
    suma += v[ i ];
}

print( "La media es: " );
println( suma / size( v ) );

Cuando conocemos de antemano los valores que va a guardar un vector, es posible crear el vector sin hacer cada una de las asignaciones a cada una de sus posiciones. Así, el caso anterior es equivalente al programa siguiente:

double[] v = new double[]{ 10, 12, 14 };
def suma = 0.0;
for(def i = 0; i < size( v ); ++i) {
    suma += v[ i ];
}

print( "La media es: " );
println( suma / size( v ) );

Los vectores de caracteres se interpretan como texto o, muy comúnmente llamados, cadenas de texto. Así, por ejemplo, no es necesario crear un bucle para visualizar un vector de caracteres, sino que ya se visualiza directamente mediante print(). También es posible asignar directamente un texto a una variable de tipo vector de caracteres: jC se encargará de crear el vector correspondiente.

import std.io;

char[] mensaje = "Hola, mundo";
println( mensaje );

Como siempre, es posible obviar char[] y sustituirlo por def en la segunda línea.

lunes, 30 de septiembre de 2013

Parámetros en procedimientos

En la última entrada, aprendimos a crear procedimientos: consiste en darle un nombre a un código, de manera que podamos invocarlo cuando queramos.

Nuestro procedimiento, en este momento, sirve para crear cuadrados.

void dibujaCuadrado()
{
 final def Lados = 4;
 final def Angulo = 90;
 final def Distancia = 50;
 
 for(def i = 0; i < Lados; ++i) {
  turtle.forward( Distancia );
  turtle.turnRight( Angulo );
 }
}

De acuerdo, pero quizás... podríamos hacer que la distancia (la longitud del lado, representada por la distancia que recorre la tortuga), pudiese cambiar. De esta manera, podríamos crear cuadrados más grandes o más pequeños. Eso se puede conseguir, precisamente, mediante parámetros. Le añadiremos parámetros al procedimiento, de forma que podamos cambiar algunos aspectos. De hecho, si pudiéramos cambiar el número de lados... ¡podríamos dibujar cualquier polígono regular, pues el ángulo se obtiene dividiendo 360 entre el número de lados!

Sintaxis de creación del procedimiento

void nombreProcedimiento(tipo1 id1 [, tipo2 id2 [,... tipon idn]])
{
    instruccion1;
    instruccion2;
    ...
    instruccionn;
}

Sintaxis de llamada al procedimiento

nombreProcedimiento( expresión1 [, expresión2 [,... expresiónn]]);
Los parámetros, tal cual aparecen en la definición del procedimiento, se denominan formales. Los que aparecen cuando llamamos al procedimiento, se denominan reales. Los parámetros reales pueden ser valores literales, variables, cálculos...

Ejemplo

void dibujaPoligono(int lados, int distancia)
{
 def angulo = 360 / lados;
 
 for(def i = 0; i < lados; ++i) {
  turtle.forward( distancia );
  turtle.turnRight( angulo );
 }
}

dibujaPoligono( 4, 50 );

En este ejemplo, estaríamos dibujando un polígono regular de 4 lados, es decir, un cuadrado cuyo lado es de longitud 50.

Ejemplo

Podemos hacer figuras aleatorias utilizando el procedimiento rand() en el módulo math. Veamos un programa completo, que crea cuatro figuras haciendo girar la tortuga cuatro veces.

/** @name   poligonos
  * @brief  Dibuja polígonos al azar
  * @author jbgarcia@uvigo.es
  * @date   2013-9-30
  */

import media.gw;
import media.turtle;
import std.math;

void dibujaPoligono(int lados, int distancia)
{
 def angulo = 360 / lados;
 
 for(def i = 0; i < lados; ++i) {
  turtle.forward( distancia );
  turtle.turnRight( angulo );
 }
}

for(def i = 0; i < 4; ++i) {
 dibujaPoligono( ( rand() * 15 ) + 3, 20 );

 turtle.setPen( false );
 turtle.forward( 60 );
 turtle.turnRight( 90 );
 turtle.setPen( true );
}