Translate

20 dic 2013

Cómo proteger tu conexión a Internet

Son muchas las ocasiones en las que la gente habla de dispositivos que permiten coger la señal de un router, ¿pero es posible evitar esto? Es injusto que tú estés pagando x€ mensuales por una conexión que te roba el vecino; por esta razón he decidido publicar esta entrada, con la finalidad de que la gente que paga por su conexión se asegure el tenerla totalmente para sí misma.

Muy bien, tenemos entonces que entender el concepto de IP (Internet Protocol), ¿qué es una dirección IP? Una dirección IP es un conjunto de números especiales que el ordenador interpreta para identificarse. Cada ordenador o dispositivo electrónico (un móvil, un ordenador portátil, una consola de sobremesa como la PS3, la X-Box...) tiene asociada una IP, y si contratamos a una compañía conexión a Internet, tendremos también en casa un dispositivo más denominado "Router", éste tendrá también una IP asociada. En definitiva, una IP es como el DNI de cada aparato electrónico que puede conectarse a Internet.

Ahora bien, el router que nos entrega la compañía ya tiene una configuración por defecto, y esta es fácil de romper o saltar mediante dispositivos o programas especiales (que no tengo ni idea ahora mismo de cuales son), en caso de que esto último suceda, si hemos contratado por ejemplo, una conexión de 30GB, la persona que ahora nos roba conexión tendrá acceso a los 30GB, y a nosotros nos irá más lento, a parte de estar pagando a alguien el Internet.

¿Cómo se evita esto? Es muy sencillo, primero tenemos que averiguar la IP de nuestro router, pues es el que nos otorga conexión. Para esto vamos a Inicio y en "Buscar", escribimos "cmd". A continuación nos saldrá una pantallita color negro en la que podemos escribir, tecleamos la siguiente palabra y presionamos Enter o salto de línea: ipconfig

Este comando nos mostrará los datos relacionados con las IP de nuestro ordenador, incluyendo la IP del router. Por lo general, hay una sección en esa maraña de datos incomprensibles para la mayoría de la gente que siempre es igual, la cual es así:

Dirección IPv4: "IP de nuestro ordenador"

Máscara de Subred: 255.255.255.0 (U otra combinación de números, la máscara realmente no importa en esto)

Puerta de enlace predeterminada: "IP del router que hemos contratado"

Muy bien, pues una vez hayamos localizado esta parte, ya tenemos la IP de nuestro router y podremos acceder a su configuración. Basta con abrir un navegador (Google Chrome, Mozilla Firefox, Opera, Safari, Internet Explorer...) e ir a la barra superior de búsqueda y escribir ahí la dirección de nuestro router, que será la puerta de enlace mostrada en la pantalla negra. Por lo general (o siempre) nos pedirá un ID y una contraseña, estos datos vienen en el mismo router sobre unas pegatinas. Insertamos los datos y entramos a la configuración del router.

Le ruego a las personas que no tengan conocimientos de redes o informática que se abstengan a presionar todos los botones que encuentren para evitar problemas.

Como primer comentario sobre la configuración, he de decir que podemos acceder en modo Básico a los dispositivos vinculados, es decir, podremos ver aquellos dispositivos que está usando en ese momento nuestro router, solo es necesario tener en cuenta cuántas personas usan Internet en casa y con qué dispositivos, además de comprobar si está ese mismo número de dispositivos conectados; si hay de más, es que posiblemente algún vecino se haya colado. Podemos ver también cuáles se conectan por cable al router y cuáles de manera inalámbrica. En el siguiente apartado de Conexión Inalámbrica podemos modificar el nombre de nuestra conexión, así como la contraseña, la cual nos han roto para acceder a nuestra conexión (si es que eso ha pasado). Si permanecemos más de cinco minutos sin hacer nada en la configuración del router, éste cortará la señal y tendremos que volver a entrar.

Como último consejo, recomiendo cambiar la contraseña que viene por defecto cada cierto tiempo por una que contenga caracteres especiales (! > # @ Ç [ ' ¿ ...), números (0 4 1 2 ...) y letras mayúsculas y minúsculas, de este modo nos aseguraremos de que absolutamente nadie nos robe conexión.

Espero que haya sido de su agrado =)

27 nov 2013

Cómo compilar programas de C con NetBeans en Windows XP o Windows 7

Cómo instalar el NetBeans y las herramientas para compilar programas en C/C++ en Windows XP o Windows 7:

Por defecto el NetBeans no traerá los compiladores y herramientas necesarias para ejecutarlas en C,
a diferencia de otros lenguajes como Java. Por esta razón, aquí elaboro una serie de pasos para
obtener dichas herramientas y que no de error.

Descargar el NetBeans: https://netbeans.org/downloads/

Una vez descargado el entorno de desarrollo, pasaremos a descargar las herramientas necesarias
para compilar un programa de C/C++.

Para aquellas personas que se manejen con el inglés, aquí les dejo dos guías muy útiles (se
necesitan conocimientos previos acerca de cómo manejar un poco el ordenador).

Guía para descargar las herramientas (inglés): https://netbeans.org/community/releases/73/cpp-setup-instructions.html#cygwin

Guía para verificar que todo está bien (inglés): https://netbeans.org/community/releases/73/cpp-setup-instructions.html#verifying

Aquellas personas que no sepan por dónde van los tiros o estén más perdidas, no os preocupéis, aquí
dejo paso a paso cómo instalarlo todo correctamente:

Primer paso, crear una carpeta en el Escritorio llamada "Compilador CC++" (o a vuestro gusto el nombre, pero
que indique dónde van a estar los compiladores de estos lenguajes).

A continuación, descargar el Cygwin Setup, por lo general será la versión setup-x86.exe ya que la inmensa mayoría de los
procesadores son de este tipo, de lo contrario será la versión setup-x86_64.exe:
http://cygwin.com/cygwin-ug-net/setup-net.html#internet-setup

Una vez descargado el Cygwin Setup y tenemos su acceso directo (o icono, el cual deberemos haber metido o incluido
en la carpeta del escritorio que hicimos antes), lo abrimos y nos saldrá una pantalla negra
con letras verdes denominada "bash". En esta ventanita escribiremos los siguientes comandos, presionando "Enter" o
salto de línea en cuanto terminemos de escribir uno. Debemos ejecutarlos todos:

cygcheck -c cygwin
gcc --version
g++ --version
make --version
gdb --version

Lo más probable es que nos de alguna clase de error en todos ellos, como que no se hallan estas herramientas en el
sistema (ordenador), para obtenerlas es necesario descargar otras dos cosas más...

El gnuwin32 (el make), pincharemos en el primer enlace que veamos llamado "Setup Program": http://gnuwin32.sourceforge.net/packages/make.htm

Una vez tengamos el icono o acceso directo, lo incorporaremos en la carpeta del escritorio que hemos creado antes.

Muy bien, lo siguiente es descargar el MinGW: http://sourceforge.net/projects/mingw/

Nada más entrar pinchamos en el recuadro verde que dice "Download" y listo, comenzará la descarga. Una vez lo tengamos
dentro le daremos a siguiente, siguiente, siguiente (cuantas veces sean necesarias), hasta alcanzar una ventana con
algunas líneas y un recuadrito al comienzo de estas, pinchamos en ellas y decimos "Mark for upgrade" en todas,
para luego ir a la parte superior izquierda de la ventana y entrar en "Installation", allí hacemos "Apply changes" y
comenzaremos a descargar los paquetes de gcc, g++, etc...

Una vez hecho todo lo anterior, volvemos al Bash (ventanita negra con letras verdes), y volvemos a ejecutar todos
los comandos, ya deberíamos tenerlos dentro del sistema e incluso nos dirá la versión.

Si todo ha ido bien, las versiones de cada una de las herramientas debería ser:

cygwin version 1.7.25-1
gcc version 4.8.1
g++ version 4.8.1
GNU make version 3.81
gdb version 7.6.50

Si no son las versiones exactas, tampoco pasa nada, lo importante es que estén dentro del sistema. Tendremos que
incluir en la carpeta del escritorio el MinGW Installer, para tenerlo todo a mano.

Si en este punto volvemos al NetBeans, veremos que incluso teniendo las herramientas no nos deja compilar un programa en C o C++,
todo nos dará error con líneas rojas por todas partes, para remediarlo, tenemos que establecer una ruta en las variables del
sistema... Espera, ¡¿variables de qué?!

No es muy complicado, simplemente debemos ir en Windows XP a "Mi PC", y hacer click derecho sobre su acceso directo, entremos
en Propiedades. Luego iremos a "Opciones Avanzadas" y seleccionaremos "Variables de Entorno". Una vez aquí tendremos dos
cuadritos con variables de entorno, en variables del sistema (el cuadrito inferior), debemos localizar la variable "Path" y
modificarla o editarla. Una vez podemos ver su contenido, sin tocar absolutamente nada y situándonos al final de esa línea de
cosas incomprensibles, escribimos lo siguiente sin espacio alguno:

;C:\cygwin\bin

Y aplicamos todos los cambios. En Windows 7 debemos ir a Inicio, y sobre Equipo click derecho y Propiedades, opciones avanzadas,
variables de entorno, y en variables del sistema localizar la variable Path. La editamos/modificamos y una vez tengamos su contenido
a la vista, una línea de palabras incomprensibles, sin tocar ni borrar nada, ni dejar espacio alguno, nos situamos al final y
escribimos lo siguiente:

;C:\MinGW\bin;C:\MinGW\MSYS\1.0\bin

Aplicamos cambios y listo. Ahora volvamos al NetBeans, creemos un proyecto, de lenguaje C y a vuestro gusto dejo el nombre de
la carpeta, yo la he llamado "Introduccion". Una vez aquí, veremos que todo sigue en rojo y dando errores (o debería, ¡¡¡sino es que
ya puedes programar sin problemas en C!!! ^w^), y en la parte superior del NetBeans, en Tools, vamos a Options. Seleccionamos
la sección de C/C++ y restauramos los valores por defecto (Restore Defaults), a continuación, a la izquierda, saldrá un cuadro o
ventana llamado "Tool Collection", ahí elegimos MinGW_1 y listo, aplicamos cambios.

Ahora todo lo que tenemos que hacer es presionar el botón "F6" para compilar lo poco que tenemos, y si ha ido todo bien,
nos dirá que se ha ejecutado correctamente (RUN SUCCESSFULL).


¡Felicidades si has llegado hasta aquí! =)

20 may 2013

Final Fantasy XIII-2: ¿Bug o error en el diseño del juego?

Un día como otro cualquiera me encontraba jugando al mencionado juego, Final Fantasy XIII-2 de Square-Enix en la PS3. Lo curioso es que me terminé topando con un fallo de los gordos y no entiendo si los propios programadores no vieron esa posibilidad, o si simplemente se dejó así aun sabiendo las consecuencias.

El fallo es muy difícil que suceda en la primera partida de una persona, pero es posible que se de, creo que ese es el motivo por el cual lo ignoraron. Se trata nada más y nada menos que abrir un portal cuando en teoría no debería poder abrirse, y eso trastoca algunas cosas del juego como el propio argumento.

En esta ocasión me encontraba en la era de Oerba 200. D.H., la primera vez que la visito durante la historia y debo enfrentarme a Caius. Lo común es que la gente siga el "nivel" o la "fase" hasta el final y no se percate de cierta cosa al comienzo, nada más entrar en la era hay un artefacto maestro transparente y necesitamos la ayuda de Mog para hacerlo visible. El problema viene cuando lo obtenemos, finalizamos la era de Oerba en el 200 D.H. y volvemos a los Montes Yaschas, esta vez en la historia paralela del 1x D.H..

Tras terminar todo lo importante en los montes, vemos como encima de la caseta de Hope hay otro artefacto, y este sirve para abrir el portal correcto, pues hay dos portales en total en el 1x. D.H.. Según el objetivo tenía que abrir el portal de la zona inferior del mapa con el nuevo artefacto, pero por mera casualidad, montando sobre un chocobo (La canción de Groovy Chocobo totalmente recomendada) me terminé topando con otro portal, y obviamente, la curiosidad me mató. Era mi segunda partida por lo que me sabía un poco soso seguir el argumento tal cual, cual línea, así que decidí abrir el portal que no era, ¿pero cómo? Pues con el artefacto maestro de Oerba, que en teoría no debería tenerlo (Yo en mi primera partida no lo encontré porque era invisible y estaba bien escondido sobre el polvo del Desierto Albo y no pude abrir el portal), pero en esta ocasión sí lo tenía.

Me pregunté por dentro si era buena idea abrir el portal que no era con el artefacto que no debía tener, pero la curiosidad ganó, y como se suele decir, también mató al gato. Tras abrir el portal se produjo un pequeño fallo en el juego pues desbloqueé la era de Oerba 200 D.H. cuando ya la tenía desbloqueada (Un error en el código del juego, sin duda, porque a esas alturas no es posible abrir tal portal), y también desbloqueé la torre augustia en el 300 D.H.. Serah y Noel al entrar en la torre saben sobre lo sucedido en Academia 400 D.H. y saben sobre el Fal`Cie que mató a Hope.

Tras este pequeño fallo en el juego, que desconozco si lo evitaron corregir porque en la primera partida nadie se da cuenta del artefacto o si simplemente no se dieron cuenta, es imposible que fuese un error de programación, pero esa falta sí provocó un fallo en el código al desbloquear una era que ya era accesible.

En fin, evité seguir en la torre porque ya tuve suficiente de mi curiosidad y abrí el portal que debía haber sido abierto, espero que ese pequeño fallo de código, el que desbloqueó la era de Oerba que ya tenía desbloqueada, no me cause problemas en el futuro.

Y hasta aquí hasta dónde llega el poder de la Curiosidad.

27 mar 2013

Programación de un menú con punteros.

Programación de un menú mediante memoria dinámica.

El menú se centrará en controlar el stock de productos de una tienda de móviles, no me centraré en el aspecto gráfico.

Lo primero a realizar serán las cabeceras y la biblioteca:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

Le he añadido el string.h porque en principio puedo imaginar que manejaré cadenas de caracteres, el stdio.h y el lib.h han venido por defecto así que no los tocaré.

El siguiente paso será pensar qué vamos a tratar con el programa: Pues unos móviles o celulares, así que tendré que implementar la estructura que tendrán.


struct estructura {
    int id;
    char marca[25];
    char modelo[25];
    char color[25];
    float precio;
    struct estructura *sig;
};
typedef struct estructura tMovil;

char password[7]="Entrar";

Cada unidad dispondrá de un id, de una marca, de un modelo, de un color y de un precio. El tipo de dato tMovil servirá principalmente para referirnos a la estructura, una manera de abreviar el "struct estructura". El puntero interno siguiente servirá para enlazar desde la memoria incontables móviles, controlarlos y realizar operaciones con ellos.

También he declarado una cadena de caracteres predefinida, será la contraseña para poder acceder al menú. Cuando un empleado desee acceder a la aplicación deberá insertar una contraseña, solamente la he definido como una global, influirá en todo el código. También está la opción de que el usuario introduzca una contraseña manualmente y se guarde en la variable, pero he preferido este método ya que el nuevo empleado no tendrá por qué introducir la contraseña que le parezca mejor, sino que la empresa ya disponga de una para el programa en sí. En esta ocasión la contraseña será la palabra "Entrar".

Entonces vamos a ir creando ya la inserción de la contraseña, que es lo primero que debe suceder en el programa:


void acceder() {
    char password2[7];
    printf("Introduzca la contraseña: ");
    scanf("%s", password2);
    if(strcmp(password, password2)==0) {
mostrarMenu();
    }
    else {
system("clear");
printf("\nLa contraseña no es válida.");
    }
}

La función "acceder" no recibirá parámetros, he declarado el vector de caracteres "password2", que será la destinada a teclear por teclado, en otras palabras, se le pedirá al usuario que introduzca una contraseña y ese valor se almacenará en la variable, ipso facto se comprobará si esa contraseña introducida es igual a la que está predefinida por la empresa (supuestamente), se compararía con la password global, si son iguales ejecutará la función de mostrar menú y ahí empezará la aplicación, sino coincide (se ha introducido una falsa o errónea), se limpiará la pantalla y dirá que la contraseña introducida no es válida. Y la siguiente pregunta es: ¿Se termina el programa con esto? Cierto es que no hay manera de continuar si se introduce una contraseña errónea, por lo que para mejorar la eficiencia del programa (pensemos que el usuario o empleado llevaba algo de prisa y tecleó mal una letra, ¿tendría que volver a iniciar el programa entero? Sería muy tedioso), crearé un bucle eterno para que siempre pida la contraseña.

Por tanto bajo la variable global de la contraseña declararé una variable numérica con un valor:


char password[7]="Entrar";
int bucle=1;

El valor del bucle será el número uno. Ahora volveré al fragmento de código que pedía la contraseña y añadiré las modificaciones que crea necesarias para que todo funcione:


int main(int argc, char** argv) {
    
    if(!acceder()) {
printf("\nAlgo ha ido mal.\n");
    }
    else {
printf("\nHa salido del programa con éxito.\n");
    }
    return (EXIT_SUCCESS);
}

int acceder() {
    char password2[7];
    int num;
    
    while(bucle!=0) {
printf("Introduzca la contraseña: ");
scanf("%s", password2);
if(strcmp(password, password2)==0) {
   mostrarMenu();
}
else {
   system("clear");
   printf("\nLa contraseña no es válida.\n"
   "¿Desea continuar?: --->[0=No - 1=Sí]<---\n");
   scanf("%d", &num);
   if(num==0) {
bucle=0;
return(1);
   }
   else {
system("clear");
   }
}
    }
}

int mostrarMenu() {
    printf("\nHola k ase.\n");
    return(1);
}




Después del susto, vamos a analizar esto, la función "main" es el programa en sí, cuando se abre un programa desde el escritorio o cualquier parte con un doble click se ejecuta lo que contiene la función "main". En esta se llama a la función acceder, el problema del C es que no tiene los valores "True" o "False", por tanto habrá que hacerlo todo con números, interpretaré que el 1 es true, y el 0 es false:


int main(int argc, char** argv) {
    
    if(!acceder()) {
printf("\nAlgo ha ido mal.\n");
    }
    else {
printf("\nHa salido del programa con éxito.\n");
    }
    return (EXIT_SUCCESS);
}


Si la función "acceder" no devuelve un true (nótese el signo de negación "!" al comienzo del if), dirá que algo ha ido mal, ha retornado un False (es decir, un 0), si sucede esto es que hay un problema gordote en el programa y la máxima prioridad será resolverlo. De lo contrario no andará por mucho que implemente o codifique. En caso contrario, si devuelve un True (un 1), saldrá en pantalla que se ha salido del programa con éxito y ahí finalizará todo.

En la función "acceder"...


int acceder() {
    char password2[7];
    int num;
    
    while(bucle!=0) {
printf("Introduzca la contraseña: ");
scanf("%s", password2);
if(strcmp(password, password2)==0) {
    mostrarMenu();
}
else {
    system("clear");
    printf("\nLa contraseña no es válida.\n"
    "¿Desea continuar?: --->[0=No - 1=Sí]<---\n");
    scanf("%d", &num);
    if(num==0) {
bucle=0;
return(1);
    }
    else {
system("clear");
    }
}
    }
}


He cambiado el valor de "void" por el de "int", es decir, devolverá un número en algún punto del bloque de código. He declarado un número tras el vector de caracteres y luego genero el bucle eterno. Mientras la variable bucle sea distinta de cero (le he asignado arriba el valor 1, por lo que siempre se repetirá) le pedirá al usuario por pantalla que introduzca la contraseña, esta se guardará en el vector de password2 y comparará mediante el strcmp lo introducido con la contraseña ya establecida arriba, el password global. Si son iguales (si el strcmp de ambos vectores es igual a cero, esto funciona mediante una resta de caracteres, cada uno de ellos en el código ASCII es un número, si la resta o el strcmp es positivo o negativo, es que los vectores o cadenas son diferentes, si es igual a cero son iguales), repito, si son iguales y el strcmp da cero se llamará a la función "mostrarMenú", en caso contrario se limpiará la pantalla y dirá que la contraseña no es válida.

Ahí no termina la cosa, preguntará también si se desea continuar o no, indicando que el 0 equivale al no y el 1 al sí, de ahí a que declarara arriba un número, que vendría a ser la variable protagonista en esta parte. Si se introduce un cero la variable bucle será cero y el bucle eterno del comienzo de la función se romperá, además retornará el valor 1, es decir, un True. Y si en el main esa función devuelve un True, saldrá el mensaje de que se ha salido con éxito del programa.

En caso de que no se introduzca un 0, sino un 1, se limpiará la pantalla y volverá a pedir contraseña, ojo a lo siguiente, en caso de ser un 1 el valor introducido funcionará perfectamente, pero en caso de ser una letra o cualquier otra cosa, podrían pasar cosas extrañas en el programa. En mi caso se salta la pregunta de contraseña y vuelve a pedir si se desea continuar o no. Ya que la condición del if solo valúa el 1, no el resto de valores (números o caracteres), y como no me quiero complicar más la existencia, lo dejaré así, que de momento funciona.

Respecto a este fragmento:


int mostrarMenu() {
    printf("\nHola k ase.\n");
    return(1);
}


Simplemente era por poner algo, para ver que se ejecutaba la función del menú correctamente aunque no hiciese nada útil. Todo hasta aquí funciona perfectamente (excepto la decisión de si se desea continuar o no, que funciona a medias, cumple con su finalidad pero no es perfecta), por lo que pasaré a desarrollar el menú:


int mostrarMenu() {
    int opcion;
    
    while(bucle!=0) {
printf("1. Agregar un nuevo artículo.\n2. Listar los artículos "
"disponibles.\n3. Buscar artículos por marca.\n4. Eliminar "
"artículo de la lista.\n5. Calcular el precio medio de una "
"marca.\n6. Crear oferta.\n0. Salir.\n\t¿Opción?: ");
scanf("%d", &opcion);
switch(opcion) {
   case 0:
system("clear");
printf("\nHa salido con éxito del menú.");
bucle=0;
break;
return(1);
   case 1:
system("clear");
printf("\nOpción uno válida.");
bucle=0;
break;
return(1);
   case 2:
system("clear");
printf("\nOpción dos válida.");
bucle=0;
break;
return(1);
   case 3:
system("clear");
printf("\nOpción tres válida.");
bucle=0;
break;
return(1);
   case 4:
system("clear");
printf("\nOpción cuatro válida.");
bucle=0;
break;
return(1);
   case 5:
system("clear");
printf("\nOpción cinco válida.");
bucle=0;
break;
return(1);
   case 6:
system("clear");
printf("\nOpción seis válida.");
bucle=0;
break;
return(1);
   default:
system("clear");
printf("\n¡Esa opción no es válida!\n");
}
    }
}


En el menú declaro un número cualquiera sin valor y luego generaré otro bucle eterno gracias a la variable bucle, que vale 1, mientras sea distinta de 0 (que lo es) seguirá repitiendo eso. La función mostrará en pantalla las distintas opciones del menú y pedirá una opción a elegir, quedando esto en pantalla, así tal cual:

1. Agregar un nuevo artículo.
2. Listar los artículos disponibles.
3. Buscar artículos por marca.
4. Eliminar artículo de la lista.
5. Calcular el precio medio de una marca.
6. Crear oferta.
0. Salir.
¿Opción?:

Voy a ejecutar el programa desde el comienzo, me pide la contraseña (debe ser la palabra "Entrar"), escribo un carácter cualquiera y me dice que es incorrecta, que si deseo continuar, introduzco un 0 y se cierra con éxito. Vuelvo a iniciar y marco otra contraseña incorrecta, en este caso cuando me pida si deseo continuar le daré el valor 1 (el sí deseo continuar) y me volverá a pedir contraseña, esta vez le introduzco la palabra correcta, "Entrar", y me mostrará en una nueva pantalla el menú con sus opciones. En el código del menú, tras el printf (la impresión en pantalla de todas las opciones) hay un switch case de la opción, habrá un total de 8 casos. Si se introduce como opción un número del 1 al 6 dirá que la opción es válida y saldrá del programa correctamente (aunque en el main diga que "algo ha ido mal", esto se debe a que la función "acceder" no devuelve un True pero se inicia y finaliza correctamente pues en la ventana OutPut dice "RUN SUCCESSFUL", y esto equivale a que nada ha ido mal internamente.), si se introduce un 0 (el case del 0), dirá que se sale correctamente del menú y repetirá el "algo ha ido mal" del main por la función "acceder". Si se introduce un número distinto del rango de 0 a 6, dirá que esa opción no es válida y repetirá el menú (esto vendría a ser el default del switch, la opción por defecto que no abarca las seleccionadas en los Case o casos).

Cabe aclarar también que en cada Case la variable bucle valdrá cero, el bucle eterno se romperá, hará un break y la función retornará un True (un 1).

Ahora toca hacer modificaciones al main, quedando así:


int main(int argc, char** argv) {
    
    acceder();
    return (EXIT_SUCCESS);
}


Simplemente se llamará a la función "acceder" y ya lo hará todo el menú. Si introducimos la opción 0 nos dirá que hemos salido correctamente del menú y todo finalizará. Esto lo he hecho así para evitar problemas al comienzo con la función "acceder", ahora que va todo bien no hay por qué comprobar algo que nos da más problemas que alegrías (problemas por el "Algo ha ido mal" cuando se supone que va bien).

Por tanto el programa ya pide una contraseña, verifica que es correcta o no, da a elegir si se desea continuar y en caso de ser correcta muestra el menú con sus opciones, donde la opción 0 cierra con éxito todo, la opción por defecto vuelve a mostrar el menú y las opciones del 1 al 6 todavía se deben implementar.

En primer lugar, ahora que tengo el esqueleto del menú hecho, voy a "rematar" (por decirlo de alguna manera) la estética del menú para que quede lo más cómodo posible. Antes de nada iré a la función de acceder y la modificaré tal que:


int acceder() {
    char password2[7];
    int num;
    
    while(bucle!=0) {
printf("Introduzca la contraseña: ");
scanf("%s", password2);
if(strcmp(password, password2)==0) {
   system("clear");
   if(mostrarMenu()) {
   }
}
else {
   system("clear");
   printf("\nLa contraseña no es válida.\n"
   "¿Desea continuar?: --->[0=No - 1=Sí]<---\n");
   scanf("%d", &num);
   if(num==0) {
bucle=0;
system("clear");
printf("\nHa salido con éxito del programa.");
return(1);
   }
   else {
system("clear");
   }
}
    }
}


Como al finalizar el programa ya no dice nada, pues he decidido modificar la parte en la que se selecciona mal la contraseña y no se desea continuar (se inserta un 0). Ahora cuando inserte el cero me dirá que he salido con éxito del programa. Lo siguiente es la "estética" del menú, el cual lo he modificado para que quede algo así:


int mostrarMenu() {
    int opcion;
    
    while(bucle!=0) {
printf("1. Agregar un nuevo artículo.\n2. Listar los artículos "
"disponibles.\n3. Buscar artículos por marca.\n4. Eliminar "
"artículo de la lista.\n5. Calcular el precio medio de una "
"marca.\n6. Crear oferta.\n0. Salir.\n\t¿Opción?: ");
scanf("%d", &opcion);
switch(opcion) {
   case 0:
system("clear");
printf("\nHa salido con éxito del menú.");
bucle=0;
break;
return(1);
   case 1:
printf("\nOpción uno válida.\n");
getchar(); getchar();
system("clear");
break;
   case 2:
printf("\nOpción dos válida.\n");
getchar(); getchar();
system("clear");
break;
   case 3:
printf("\nOpción tres válida.\n");
getchar(); getchar();
system("clear");
break;
   case 4:
printf("\nOpción cuatro válida.\n");
getchar(); getchar();
system("clear");
break;
   case 5:
printf("\nOpción cinco válida.\n");
getchar(); getchar();
system("clear");
break;
   case 6:
printf("\nOpción seis válida.\n");
getchar(); getchar();
system("clear");
break;
   default:
printf("\n¡Esa opción no es válida!\n\n");
getchar(); getchar();
system("clear");
}
    }
}


En esta ocasión, si se selecciona la opción 0 finalizará el programa y dirá que se ha salido con éxito del menú. En el resto de opciones, he logrado que cada una de ellas sea independiente y funcione a su manera, vamos a explicarlo bien... Si introduzco un 1, un 2, o cualquier otra opción disponible se activará un doble "getchar()", esto sirve para que en pantalla salga lo siguiente:

"Opción x válida."

Y espere a que se presione una tecla ("enter" generalmente, pero cualquiera es válida) para que se borre la pantalla mediante el "system("clear")" y se vuelva a reescribir el menú en vez de pasar a la siguiente opción, ya que el "break" de cada "Case" rompe el "Switch" de manera definitiva. Si introduzco una opción que en teoría no es válida sucederá lo mismo pero con el mensajito de se ha seleccionado algo que no es válido.

Ahora que el menú funciona perfectamente y es cómodo para el usuario, pasaré a la primera opción, "Agregar un nuevo artículo".

En esta tarea voy a tener que declarar un puntero en el propio menú junto a la variable numérica de la opción, debería quedar así al comienzo de la función "mostrarMenu()":


int mostrarMenu() {
    int opcion;
    tMovil *inicioLista=NULL;

.......


Un puntero llamado "inicioLista" que comenzará siendo nulo, no tendrá ningún valor, esto lo hago para evitar que se acumule basura desde la declaración y es algo muy importante en los punteros. El inicioLista estará apuntando o marcando una dirección de memoria en concreto que es donde comenzará la lista de artículos (está apuntando a un tipo de dato "tMovil", un tipo de dato para móviles). Por ahora no lo voy a utilizar pero esa sería su finalidad. Ahora tendré que declarar una función para pedir los datos, otra que cree un artículo y una que los vaya insertando todos de manera ordenada.

Vamos a la fundamental, la de pedir datos:


void pedirDatos(tMovil **inicioLista) {
    int numArticulos, i;
    tMovil datosMovil;
    
    system("clear");
    printf("\n¿Cuántos artículos desea añadir?:\n");
    scanf("%d", &numArticulos);
    for(i=1; i<=numArticulos; i++) {
system("clear");
idActualizado++;
datosMovil.id=idActualizado;
printf("\nArtículo nº: %d", i);
printf("\n¿Marca?:\n");
scanf("%s", datosMovil.marca);
printf("\n¿Modelo?:\n");
scanf("%s", datosMovil.modelo);
printf("\n¿Color?:\n");
scanf("%s", datosMovil.color);
printf("\n¿Precio?:\n");
scanf("%f", &datosMovil.precio);
insercionFinal(inicioLista, datosMovil);
    }
}


Esta función no devolverá ningún valor y recibirá como parámetro el doble puntero del inicio de la lista, ¿por qué un doble puntero? Pues porque se va a modificar la dirección de memoria a la que apuntará cuando la función finalice, es la dirección de una "cosa" (un puntero) que tiene marcada la dirección de memoria de un supuesto artículo que vaya a crear. Luego declaro el número de artículos, la variable "i", y una variable de tipo "tMovil" que tendrá acceso directo a todos los atributos o características de un móvil.

Primero se le preguntará al empleado cuántos artículos desea introducir, y a cada artículo que introduzca se irán pidiendo los datos. Antes de nada, debo aclarar que el Id de cada artículo se irá actualizando de manera automática, por tanto me he decidido por una variable numérica global que empiece en cero, vamos de nuevo a la zona superior del código fuente donde tengo declaradas las variables globales:


char password[7]="Entrar";
int bucle=1, idActualizado=0;


Ahí he añadido la variable o elemento "idActualizado" y es cero desde el comienzo. Ahora, en la función de pedir los datos al empleado nada más insertar el número de artículos la variable global se incrementará en uno y ese será el Id del artículo actual. Todos los datos insertados por teclado se almacenarán en la variable "datosMovil", y al final de cada artículo se llamará a la función "inserciónFinal" con el puntero y los datos introducidos, esa función la implementaré posteriormente.

Antes de ir a la función de insertar por el final, tengo que codificar la función de crear un artículo y aislarlo en la memoria RAM, vendría a ser la siguiente:


tMovil* crearArticulo(tMovil datosMovil) {
    tMovil *pAux=NULL;
    pAux=(tMovil*)malloc(sizeof(tMovil));
    
    strcpy(pAux->marca, datosMovil.marca);
    strcpy(pAux->modelo, datosMovil.modelo);
    strcpy(pAux->color, datosMovil.color);
    pAux->precio=datosMovil.precio;
    pAux->sig=NULL;
    
    return(pAux);
}


Esta función devolverá un puntero a un tipo de dato "tMovil", es decir, la dirección de memoria en la que se aloja un artículo, devuelve un número. Recibirá por parámetro los datos introducidos por teclado y declararé  un puntero auxiliar, con el que reservaré memoria mediante el malloc (memory allocate). Esta última operación es sencilla:

pAux [el puntero] = [será igual a..] (tMovil*) [un puntero de tipo tMovil] malloc [y reserva memoria] (sizeof(tMovil)) [de un elemento con el tamaño justo de una estructura tMovil, un struct estructura]

Por tanto, lo que queda es igualar los datos del parámetro (los introducidos por teclado) a los del elemento creado que tiene una memoria en la RAM reservada y es algo existente. Las cadenas de caracteres se copian con el comando "=" de los números, sino con el strcpy (string copy), copiaré los atributos que sean vectores de caracteres mediante el strcpy, del datosMovil al puntero pAux, y el precio al ser un float, un número muy grande, bastará con el "=". El puntero interno "siguiente" será nulo, por lo que el elemento o artículo quedará aislado en la memoria, localizado únicamente por el pAux. Y finalmente devuelvo el propio pAux, el número, la dirección de memoria en la que está localizado el artículo que acabo de crear.

Y ahora sí, la famosa función de insertar por el final que tampoco tiene mucho misterio:


void insercionFinal(tMovil **inicioLista, tMovil datosMovil) {
    tMovil *pAux=NULL;
    
    if(*inicioLista==NULL) {
*inicioLista=crearArticulo(datosMovil);
    }
    else {
pAux=*inicioLista;
while(pAux->sig!=NULL) {
   pAux=pAux->sig;
}
pAux->sig=crearArticulo(datosMovil);
    }
}


La inserción por el final no devolverá nada, recibirá como parámetros el puntero que marca el inicio de la lista y los datos del artículo introducido por teclado. Declaro un puntero auxiliar que me ayude a "moverme" por la lista de artículos.

Primero compruebo si en la lista hay algo o no, si no hay nada (que será el primer caso sí o sí) el puntero de inicio marcará a la dirección de memoria que ha devuelto la función "crearArticulo" mediante los datos del teclado y al finalizar la función, al ser un doble puntero el inicio de la lista queda con los cambios registrados de manera definitiva. Si ya hay al menos un elemento en la lista, el puntero auxiliar marcará la misma dirección de memoria que marca la del inicio de la lista, he necesitado de un puntero auxiliar porque de lo contrario, si muevo el inicio de la lista por la propia lista (valga la redundancia) se me quedará modificado y entonces no sería un inicio de nada.

Por tanto, mientras el puntero interno "siguiente" del primer elemento de la lista (en la primera "vuelta" del bucle while) sea distinto de nulo (es decir, apunta a una dirección de memoria en la que hay otro artículo), el puntero auxiliar apuntará a la dirección de memoria a la que apunta o marca el puntero interno, pasando así por todos los artículos de la lista hasta llegar al último.

Cuando el bucle se rompe significa que ha llegado al último, por lo que en esa situación el puntero "siguiente" del último elemento de la lista apuntará a la dirección de memoria a la que apunta el puntero devuelto por la función de crear a partir de los datos (¡aaaaaaaaaaaahh pedazo traba lenguas!).


Y por último y más importante de la primera opción del menú, modificar esto en la función de mostrarMenu():


   case 1:
pedirDatos(&inicioLista);
getchar(); getchar();
system("clear");
break;


En el caso de que se presione la opción 1 se llamará a la función "pedirDatos" y se le pasará la dirección de memoria del puntero inicioLista (ya que la función de pedir datos pide un doble puntero y esto equivale a eso). Tras introducir todos los datos tendremos en la memoria RAM una lista de elementos enlazados mediante direcciones de memoria y el puntero de inicioLista marcará al primero de todos ellos, al finalizar nos pedirá que presiones cualquier tecla mediante el doble "getchar()", la pantalla se limpiará y volverá a mostrar el menú.

En cuanto pueda paso a la segunda opción, paciencia please ^^

[[[PENDIENTE DE ACTUALIZAR]]]

24 mar 2013

Programación de listas con punteros.

Hoy voy a crear esta entrada con el objetivo de repasar programación avanzada en C, mediante punteros y direcciones de memoria, más que nada para entender el funcionamiento de todo esto y de cómo se opera con punteros.

Lo primero que hay que hacer es, obviamente, elaborar un nuevo proyecto en C, yo lo he llamado "ListasPunteros" con una única carpeta de código llamada "Main".

Lo primero a incluir serán las cabeceras (librería), por defecto ya me vienen stdio y stdlib, así que nada que añadir o modificar por el momento:

#include <stdio.h>  {Libre    }
#include <stdlib.h> {        ía}

Lo siguiente a realizar es una lista, debo crear primero la estructura de una lista, o elemento (por elemento se entiende que es una lista), la lista en principio solo contendrá un número, puesto que lo me interesa es practicar el uso y manejo de punteros y memoria dinámica, no de modificar los datos de la lista. Y obviamente, para que las listas se enlacen entre sí, en su propia estructura deben tener un puntero que "apunte" a la propia estructura, un puntero interno, éste será el encargado de enlazar listas. Esto queda así:

struct lista {
        int num;
        struct lista *sig;
};

Obviamente, eso de escribir "struct lista" cada vez que declaro un puntero es un coñazo, y algo tedioso, por lo que procederé a crear un tipo de dato que me simplifique trabajo:

typedef struct lista tLista;

O bien:

typedef struct lista {
       int num;
       struct lista *sig;
} tLista;

Lo siguiente que me atreveré a realizar es un puntero que marcará siempre el inicio de la lista, lo declararé en el main (función main) como: tLista *inicioLista=NULL;
He procurado en dejar el puntero inicializado a nulo para que no almacene basura o parásitos. Lo siguiente a llevar a cabo, es crear una lista, así de simple:

tLista* crearLista(int valor)
{
        tLista *p=NULL;
        p=(tLista*)malloc(sizeof(tLista));
        if(valor==NULL) {
                   printf("No ha introducido ningún valor, no se puede crear la lista.");
        else {
                   p->num=valor;
                   p->sig=NULL;
                   return(p);
}

¿Simple, verdad? Pues sí, bastante, la función "crearLista" me creará una sola lista cada vez que la llame, será la encargada de crear, nada más. Devolverá un puntero de tipo "tLista", es decir, una dirección de memoria en la que se aloja la estructura de una lista (un número y un puntero interno), dicha dirección de memoria que devolverá obviamente será la lista creada. La función recibirá como parámetro un número (int valor). Posteriormente declaro otro puntero (el destinado a apuntar la dirección de memoria creada), llamado "p" e igualado a nulo por las razones explicadas arriba con "*inicioLista", ese mismo puntero, p, reservará memoria para una estructura mediante el "malloc", y después la función comprobará si el valor introducido por parámetro será nulo o no, si es nulo (es decir, no tiene ningún valor), se mostrará en pantalla un aviso que dirá lo siguiente: "No ha introducido ningún valor, no se puede crear la lista.". Si no se cumple ese caso y el parámetro sí tiene valor numérico, el puntero "p" asignará al número de la lista reservada previamente (malloc), el valor del parámetro (p->num=valor), y asignará al puntero interno "sig" (siguiente) un valor nulo, ya que prefiero que primero la lista creada quede aislada. Finalmente devolverá la dirección de memoria en la que se aloja el puntero "p", con los datos de la lista.

Lo siguiente a realizar es la concatenación de listas, unir todas las listas que vaya a crear en un futuro, primero lo haré de manera "inicial", es decir, cada lista que cree se unirá a las que ya están creadas para formar así una especie de "fila" de listas unidas. Para eso voy a crear una función que se encargue de hacer dicha tarea, basada obviamente en la función previa "crearLista":


void insercionInicial(tLista **inicioLista, int valor)
{
    tLista *p=NULL;
    if(*inicioLista==NULL) {
p=crearLista(valor);
*inicioLista=p;
    }
    else {
p=crearLista(valor);
p->sig=*inicioLista;
*inicioLista=p;
    }
}

La función "inserciónInicial" simplemente no devolverá ningún valor, recibirá un doble puntero que esté marcando al inicio de la lista (doble puntero es la dirección de la dirección de memoria, por lo que el inicio de la lista quedará modificado cuando la función finalice), y un valor numérico. En primer lugar declararé un puntero a nulo, posteriormente preguntaré si el inicio de la lista apunta a nulo (la lista está vacía), si se da el caso, p apuntará a la dirección de memoria que se ha creado mediante el parámetro numérico y la función de crear, e inicioLista apuntará a ese elemento creado que es apuntado por el puntero local "p", creando así una lista de un solo elemento, este es el caso en el que no haya nada creado. Si no se da, crearé una estructura y p apuntará a su dirección de memoria, y gracias al puntero interno siguiente, apuntará a la dirección de memoria a la que apunta el puntero encargado de marcar el inicio de la lista, por tanto el elemento creado se ha insertado antes (va "detrás") del elemento inicial de la lista, y por último, el puntero que marca el inicio apuntará al nuevo elemento creado, siendo este el nuevo y primer elemento de la lista entera.

Para comprobar que todo ande como es debido, realizaré una función que me imprima en pantalla los elementos de las listas:


void imprimirLista(tLista *inicioLista)
{
    if(inicioLista==NULL) {
printf("La lista está vacía.");
    }
    else {
while(inicioLista!=NULL){
   printf("%d", inicioLista->num);
   if(inicioLista->sig!=NULL) {
printf("-->");
   }
   else {
printf("--//");
   }
   inicioLista=inicioLista->sig;
}
    }
}

Esta función se encargará de imprimirme una lista en pantalla. Recibirá como parámetro el inicio de la lista (el primer elemento), y comprobará si apunta a una dirección de memoria nula (donde no hay nada), o si en contraposición apuntará a una dirección de memoria en la que exista una estructura creada. Si apunta a la nada, dirá que la lista está vacía, si no, creará un bucle que se repetirá mientras la lista no finalice, esto se debe al algoritmo que he ideado en su interior:
Primero imprimirá el número de la estructura a la que apunta el inicio de la lista, si el puntero interno siguiente tiene una dirección de memoria (existe otro elemento, el segundo), mostrará en pantalla "-->" (esto), si no hay más elementos en la lista, mostrará esto "--//". Y por último, el puntero que apunta al inicio de la lista pasará a apuntar la dirección de memoria a la que apunta el puntero interno "siguiente", pasando de esta manera el segundo elemento y volviendo a repetir el bucle.

Otra manera de insertar elementos en la lista, será insertarlos en el final y no en el comienzo, para ello he ideado esta función:


void insercionFinal(tLista **inicioLista, int valor)
{
    tLista *p=NULL, *pAux=NULL;
    if(*inicioLista==NULL) {
p=crearLista(valor);
*inicioLista=p;
    }
    else {
pAux=*inicioLista;
p=crearLista(valor);
while((*inicioLista)->sig!=NULL) {
   *inicioLista=(*inicioLista)->sig;
}
(*inicioLista)->sig=p;
*inicioLista=pAux;
    }
}

La inserción Final (qNombre más chulo(?)). Recibirá como doble puntero el inicio de la lista, aunque realmente no sea estrictamente necesario hacerlo así, pero yo sí lo haré por comodidad, también se le pasará el valor de la nueva estructura a crear e insertar. Declararé dos punteros que apuntan a nulo y luego comprobaré si la lista está vacía o no, si lo está, creará una nueva estructura y esta pasará a ser el primer elemento de la lista, que no está vacía, pues el puntero Auxiliar (pAux), pasará a apuntar la dirección de memoria a la que apunta el inicio de la lista, luego p apuntará a un nuevo elemento creado y generaré un bucle, mientras el puntero interno "siguiente" no apunte a la nada (es decir, debe apuntar a otra dirección de memoria para que se repita el algoritmo), el puntero que marcaba al primer elemento pasará a apuntar al elemento al que apunta su puntero interno "siguiente", logrando con esto "moverse" a través de la fila de elementos hasta alcanzar el último. Una vez lo alcance, ese último elemento apuntará mediante el "siguiente" al elemento creado que hasta ahora estaba aislado, y el puntero destinado al inicio volverá a apuntar al primer elemento de la fila gracias al puntero Auxiliar que estaba allí.

De esta manera insertamos elementos al final de la lista.

Ahora voy a comprobar que todo lo que he hecho va perfectamente, así que haré varias modificaciones en la función "main", quedando esta así:


int main(int argc, char** argv)
{
    int array[]={5, 12, 1, 10, 32, 40}, i;
    const int MAXARRAY=6;
    tLista *inicioLista=NULL;
    for(i=0; i<MAXARRAY; i++) {
insercionInicial(&inicioLista, array[i]);
    }
    imprimirLista(inicioLista);
    printf("\n");
    insercionInicial(&inicioLista, 6);
    printf("\n");
    insercionFinal(&inicioLista, 100);
    imprimirLista(inicioLista);
 
    return (EXIT_SUCCESS);
}

Cuando se supera el susto inicial, uno se da cuenta de que no es para tanto, he creado una variable numérica   que hará de array, almacenando hasta 6 números que he puesto al azar. Luego declaro una constante que valdrá el mismo número de elementos que posee el array numérico, y además tengo el puntero que marca al inicio de la lista. Mediante un "for" llevaré a cabo un bucle hasta seis veces, y en cada pasada llamaré a la función de insertar por el inicio, utilizando el array numérico y el puntero de inicio. Luego llamo a la función de imprimir la lista creada (ha insertado los elementos en el bucle "for" previamente), vuelvo a insertar un número, me ha dado por insertar el 6, y luego llamo a la inserción final para insertar el número 100 por el final.

El resultado, lo que se debería mostrar en pantalla, vendría a ser algo así:



40-->32-->10-->1-->12-->5--//

6-->40-->32-->10-->1-->12-->5-->100--//

Primero muestro la lista de elementos insertados al principio, (array[]={5, 12, 1, 10, 32, 40}), la inserción inicial me insertó primero el 5, luego me insertó antes del cinco el 12, y así hasta el 40, siendo éste último el primer elemento de la lista. Inserto de nuevo al comienzo un seis, y luego al final inserto el número 100.