I/O: Input / Output
Tabla de Contenidos
En esta sección vamos a tratar sobre cómo se transfieren datos, de entrada y salida, a tres entornos distintos: consola, archivos de texto y bases de datos.
Consola
La clase FileStream, que también veremos al operar con archivos, en consola se instancia con los objetos stdin and stdout, mientras que gets() es un método de la clase FileStream.
Recuerda que stdin y stdout significan 'standard input' y 'standard output' respectivamente.
Varias posibilidades:
init // creamos una matriz de caracteres var a = new array of char[64] stdout.printf( "¿Cómo te llamas? " ) // stdin.gets(a) lee una línea del teclado // y la almacena en la matriz de caracteres. stdin.gets(a) // 2 formas distintas para extraer la información del array: // 1. convertimos los caracteres en un string s:string = (string)a stdout.printf( "Hola %s", s ) // 2. extraemos los caracteres con un bucle stdout.printf( "Hola " ) for c in a stdout.printf ("%c", c) // más fácil todavía (al revés que en el circo) stdout.printf( "Tu nombre, por favor: ") // lee la linea nombre:string = stdin.read_line() print "Hola " + nombre + ", gracias por visitar Genie Doc."
Respecto a stdout, es importante conocer los formatos admitidos para cada tipo de datos.
Se utiliza el prefijo % seguido del carácter o caracteres correspondientes al tipo de dato utilizado.
init texto:string = "texto" caracter:char = 'a' caracter_ASC:uchar = 'a' caracter_no_ASC:unichar = 'á' entero:int = -10 entero_positivo:uint = 10 largo:long = -87654454 largo_positivo:ulong = 87654454 decimales:float = 0.1234f decimales2: double = 23.43546544 exponencial:double = 453 // entrada-salida de distintos tipos de datos y con distintos formatos //texto y caracteres stdout.printf( "entrada: string | salida: %%s | texto = %s", texto ) // texto print "" stdout.printf( "entrada: char | salida: %%c | a = %c", caracter ) // carácter print "" stdout.printf( "entrada: uchar | salida: %%huu | a = %huu", caracter_ASC ) //posición decimal ASCII print "" stdout.printf( "entrada: unichar | salida: %%uc | á = %uc", caracter_no_ASC ) // posición códigos no ASCII print "" // número enteros stdout.printf( "entrada: int | salida: %%i | -10 = %i", entero ) // entero print "" stdout.printf( "entrada: entero | salida: %%d | -10 = %d", entero ) // entero print "" stdout.printf( "entrada: uint | salida: %%u | 10 = %u", entero_positivo ) // entero positivo print "" stdout.printf( "entrada: long | salida: %%li | 87654454 = %li", largo ) // números grandes (32 bits) print "" stdout.printf( "entrada: ulong | salida: %%lu | 87654454 = %lu", largo_positivo ) // números grandes positivos print "" // decimales y exponenciales stdout.printf( "entrada: float | salida: %%f | 0.1234f = %f", decimales ) //números con 6 decimales print "" stdout.printf( "entrada: float | salida: %%.2f | 0.1234f = %.2f", decimales ) // números con 2 decimales print "" stdout.printf( "entrada: double | salida: %%g | 23.43546544 = %g", decimales2 ) // número con 4 decimales print "" stdout.printf( "entrada: double | salida: %%e | 453 = %e", exponencial ) // exponenciales
Este código devuelve:
entrada: string | salida: %s | texto = texto entrada: char | salida: %c | a = a entrada: uchar | salida: %huu | a = 97u entrada: unichar | salida: %uc | á = 225c entrada: int | salida: %i | -10 = -10 entrada: entero | salida: %d | -10 = -10 entrada: uint | salida: %u | 10 = 10 entrada: long | salida: %li | 87654454 = -87654454 entrada: ulong | salida: %lu | 87654454 = 87654454 entrada: float | salida: %f | 0.1234f = 0.123400 entrada: float | salida: %.2f | 0.1234f = 0.12 entrada: double | salida: %g | 23.43546544 = 23.4355 entrada: double | salida: %e | 453 = 4.530000e+02
En el código anterior se observa que para poder imprimir el carácter % en stdout he tenido que escribir dos %. Se trata de una secuencia de escape, como éstas:
init // con \n hacemos un salto de línea stdout.printf( "Aprendiendo a programar\n" ) stdout.printf( "con Genie.\n" ) // con \t escribimos el espacio de un tabulador stdout.printf( "\tAprendiendo a programar " ) //observa que aquí no hay salto de línea stdout.printf( "con Genie.\n" ) // con \r hacemos retroceso hasta el inicio stdout.printf( "Reprendiendo\rAprendiendo a programar.\n" ) // con \b hacemos un retroceso stdout.printf( "Aprendiendof\b a programar.\n" ) // con \\ podemos imprimir \ stdout.printf( "blanco \\ negro\n" ) // con %% podemos imprimir % stdout.printf( "blanco %% negro" )
Archivos
Genie ofrece una serie de utilidades para operar con archivos, básicamente se trata de dos clases de funciones: FileUtils y FileStream con sus correspondientes métodos.
FileUtils
La clase FileUtils es un grupo de funciones que incluyen los siguientes métodos:
- get_contents
- set_contents
- test
- open_tmp
- read_link
- mkstemp
- rename
- unlink
- chmod
- symlink
Un ejemplo para cambiar el nombre a un archivo:
init var nombre_nuevo = "prueba.txt" FileUtils.rename("/home/jcv/Programacion/Genie/data.txt" , nombre_nuevo)
Un ejemplo para leer un archivo:
init s:string len:ulong FileUtils.get_contents("/home/jcv/Programacion/Genie/data.txt",out s, out len) print s print "%lu", len
Con este código extraemos el contenido del archivo 'data.txt' y el número de caracteres que ocupa. El tipo de datos ulong se utiliza para número enteros muy grandes (64-bit).
Y para escribir un archivo:
init texto:string = "Escrito con Genie." FileUtils.set_contents("/home/jcv/Programacion/Genie/data.txt", texto)
FileStream
Con la clase FileStream también podemos manejar archivos.
init var archivo = FileStream.open("/home/jcv/Programacion/Genie/data.txt","r") var caracter = new array of char[100] while archivo.gets(caracter) != null for x in caracter stdout.printf ("%c", x)
Ahí estamos leyendo el archivo por caracteres, pero también podemos leerlo por líneas:
init var archivo = FileStream.open("/home/jcv/Programacion/Genie/data.txt","r") var linea = archivo.read_line() while linea != null print linea linea = archivo.read_line()
En los ejemplos anteriores, abrimos el archivo en modo lectura (“r”), pero también podemos abrirlos en modo escritura (“w”).
init // abrimos el archivo // si no existe, lo crea var archivo = FileStream.open("/home/jcv/Programacion/Genie/data.txt","w") archivo.rewind () // manda el cursor al inicio archivo.puts ("Hola mundo.") archivo.rewind () // volvemos al principio archivo.puts ("Escribiendo con Genie.") archivo.puts (" Seguimos escribiendo en la misma línea.") archivo.puts ("\n\nAhora escribimos dos líneas más abajo.")
Bases de datos
SQLite es una biblioteca de C que implementa un motor de base de datos SQL, lo que nos proporciona una manera ordenada de almacenar datos y de rápida consulta.
Para operar en una base de datos utilizamos comandos SQL estándar, entre los que podemos distinguir tres tipos:
- Comandos de definición. Proporcionan la estructura y métodos de almacenamiento en la base de datos: CREATE, ALTER, DROP.
- Comandos de manipulación. Permiten añadir, modificar y eliminar los datos: INSERT, UPDATE, DELETE.
- Comandos de consulta. Permiten recuperar datos específicos de la base de datos: SELECT.
En C estos comandos SQL se definen según métodos específicos (ver C-language Interface Specification for SQLite). Algunos de los métodos esenciales usados en C para operar con SQLite son: sqlite3_open(), sqlite3_prepare(), sqlite3_bind(), sqlite3_step(), sqlite3_column(), sqlite3_finalize(), sqlite3_close(), qlite3_exec() (ver lista completa).
Veremos cómo usar algunos de esos métodos en Genie para crear y operar con SQLite.
No olvides compilar con:
valac --pkg sqlite3 archivo.gs
Y para comprobar los resultados en la base de datos puede venir bien alguna aplicación como SQLiteBrowser, disponible para Windows, Mac y Linux (en la mayoría de repositorios).
Crear base de datos
init // Creamos una base de datos vacía (si no existe) // sin estructura ni datos db : Sqlite.Database Sqlite.Database.open ("agenda.db3", out db) // especifica nombre y tipo de archivo
Crear estructura
init db : Sqlite.Database Sqlite.Database.open ("agenda.db3", out db) // creamos la tabla contactos // con 3 columnas: id, nombre y phone // cada campo se definde por un nombre y un tipo de datos db.exec ("CREATE TABLE Contactos (iD INTEGER PRIMARY KEY, nombre TEXT, phone INTEGER)")
Sobre los tipos de datos se puede leer Datatypes In SQLite Version 3.
Introducir datos
init db : Sqlite.Database Sqlite.Database.open ("agenda.db3", out db) db.exec ("CREATE TABLE Contactos (iD INTEGER PRIMARY KEY, nombre TEXT, phone INTEGER)") // introducimos datos db.exec ("""INSERT INTO Contactos (nombre, phone) VALUES ("Pepe", 915456456)""") db.exec ("""INSERT INTO Contactos (nombre, phone) VALUES ("Alicia", 967485987)""")
Otra manera de introducir datos, a través de consola:
init db : Sqlite.Database Sqlite.Database.open ("agenda.db3", out db) db.exec ("CREATE TABLE Contactos (iD INTEGER PRIMARY KEY, nombre TEXT, phone INTEGER)") stdout.printf( "Nuevo contacto: " ) contacto_nombre:string = stdin.read_line() stdout.printf( "Teléfono: " ) contacto_phone:string = stdin.read_line() introducir:string = "INSERT INTO Contactos (nombre, phone) VALUES ('Anonimo', 000000000)" introducir = introducir.replace("Anonimo", contacto_nombre) introducir = introducir.replace("000000000", contacto_phone) db.exec (introducir)
Mejorando el código:
init db : Sqlite.Database Sqlite.Database.open ("agenda.db3", out db) db.exec ("CREATE TABLE Contactos (iD INTEGER PRIMARY KEY, nombre TEXT, phone INTEGER)") stdout.printf( "Nuevo contacto: " ) contacto_nombre:string = stdin.read_line() stdout.printf( "Teléfono: " ) contacto_phone:string = stdin.read_line() introducir:string = @"INSERT INTO Contactos (nombre, phone) VALUES ('$contacto_nombre', $contacto_phone)" db.exec (introducir)
Vemos que todas estas maneras de introducir datos funcionan, pero ninguna de ellas comprueba si ya existen los mismos valores, y los puede duplicar.
Para solucionarlo podemos añadir el parámetro 'UNIQUE' al campo nombre cuando creamos la tabla:
db.exec ("CREATE TABLE Contactos (iD INTEGER PRIMARY KEY, nombre TEXT UNIQUE, phone INTEGER)")
Antes de ejecutar nuevamente el código, eliminamos la base de datos y entonces comprobamos que ahora la base de datos no permite introducir dos nombres iguales (se queda con el primero).
Hacer consulta
Pero estaría bien que avisara de que el valor ya existe. Una solución puede ser:
// compilar con valac --pkg sqlite3 --pkg gee-0.8 archivo.gs uses Sqlite Gee init db : Sqlite.Database Sqlite.Database.open ("agenda.db3", out db) db.exec ("CREATE TABLE Contactos (iD INTEGER PRIMARY KEY, nombre TEXT UNIQUE, phone INTEGER)") stdout.printf( "Nuevo contacto: " ) contacto_nombre:string = stdin.read_line() // hacemos una consulta statement:Statement db.prepare_v2("SELECT nombre FROM Contactos", -1, out statement) cols:int = statement.column_count () var row = new dict of string, string item:int = 1 // creamos una lista para guardar los resultados de la consulta var lista = new list of string // recorremos los valores de 'nombre' // y los añadimos a la lista while statement.step() == ROW for i:int = 0 to (cols - 1) row[ statement.column_name( i ) ] = statement.column_text( i ) lista.add(row[ "nombre" ]) item++ // comprobamos si el nombre introducido está en la lista if lista.contains(contacto_nombre) == true stdout.printf("%s ya está en la Agenda.\n", contacto_nombre) else stdout.printf( "Teléfono: " ) contacto_phone:string = stdin.read_line() enter:string = @"INSERT INTO Contactos (nombre, phone) VALUES ('$contacto_nombre', $contacto_phone)" db.exec (enter)
Mejorando el código:
// compilar con valac --pkg sqlite3 --pkg gee-0.8 archivo.gs uses Sqlite Gee init db : Sqlite.Database Sqlite.Database.open ("agenda.db3", out db) db.exec ("CREATE TABLE Contactos (iD INTEGER PRIMARY KEY, nombre TEXT UNIQUE, phone INTEGER)") stdout.printf( "Nuevo contacto: " ) contacto_nombre:string = stdin.read_line() // ahora en la consulta incorporamos ambos campos statement:Statement db.prepare_v2("SELECT nombre, phone FROM Contactos", -1, out statement) cols:int = statement.column_count () var row = new dict of string, string item:int = 1 // creamos un diccionario var agenda = new dict of string,string while statement.step() == ROW for i:int = 0 to (cols - 1) row[ statement.column_name( i ) ] = statement.column_text( i ) agenda[row[ "nombre" ]] = row[ "phone" ] item++ //if agenda.contains(contacto_nombre) == true // OBSOLETO if agenda.has_key(contacto_nombre) == true stdout.printf("%s ya está en la Agenda.\n", contacto_nombre) stdout.printf("Su teléfono es %s\n", agenda[contacto_nombre]) else stdout.printf( "Teléfono: " ) contacto_phone:string = stdin.read_line() enter:string = @"INSERT INTO Contactos (nombre, phone) VALUES ('$contacto_nombre', $contacto_phone)" db.exec (enter)
Modificar datos
Pero además de avisar de que existe un valor, sería mejor si nos diera la oportunidad de editar datos.
// compilar con valac --pkg sqlite3 --pkg gee-0.8 archivo.gs uses Sqlite Gee init db : Sqlite.Database Sqlite.Database.open ("agenda.db3", out db) db.exec ("CREATE TABLE Contactos (iD INTEGER PRIMARY KEY, nombre TEXT UNIQUE, phone INTEGER)") stdout.printf( "Nuevo contacto: " ) contacto_nombre:string = stdin.read_line() statement:Statement db.prepare_v2("SELECT nombre, phone FROM Contactos", -1, out statement) cols:int = statement.column_count () var row = new dict of string, string item:int = 1 var agenda = new dict of string,string while statement.step() == ROW for i:int = 0 to (cols - 1) row[ statement.column_name( i ) ] = statement.column_text( i ) agenda[row[ "nombre" ]] = row[ "phone" ] item++ if agenda.has_key(contacto_nombre) == true stdout.printf("%s ya está en la Agenda.\n", contacto_nombre) stdout.printf("Su teléfono es %s\n", agenda[contacto_nombre]) stdout.printf( "¿Quieres cambiar el número? (s/n) " ) respuesta:string = stdin.read_line() if respuesta == "s" stdout.printf( "Nuevo teléfono: " ) contacto_phone:string = stdin.read_line() // actualizamos con un nuevo valor enter:string = @"UPDATE Contactos SET phone = $contacto_phone WHERE nombre = '$contacto_nombre'" db.exec (enter) else stdout.printf( "Teléfono: " ) contacto_phone:string = stdin.read_line() enter:string = @"INSERT INTO Contactos (nombre, phone) VALUES ('$contacto_nombre', $contacto_phone)" db.exec (enter)