Escrito por Jon Yoder y traducido por Andros Villanueva Blanco
Programar consiste en comunicar tus ideas al ordenador. El único problema es que los ordenadores son estúpidos. Harán exactamente lo que les digas y no lo que quieras decir, a menos que lo que quieras decir sea, de hecho, lo que les has dicho. Si no lo entiendes del todo, créeme, pronto lo entenderás.
El problema de comunicarse con el ordenador es que no habla ningún lenguaje humano. Lo único que sabe es qué hacer cuando se le da una instrucción numérica concreta. Para la gente hacer esto es muy difícil, así que escribimos en un lenguaje accesible a las personas y el ordenador lo convierte en algo que pueda entender. La traducción puede hacerse justo cuando se ejecuta el programa, lo que se denomina lenguaje interpretado, o de antemano, como se hace en los lenguajes compilados. C y C++ son lenguajes compilados, así que al escribir programas en C++, se escribe el lenguaje humano (código fuente) y el compilador lo convierte en instrucciones de ordenador (código máquina).
En C++, todas las instrucciones informáticas se agrupan en bloques denominados funciones. He aquí un ejemplo:
void miFuncion(void)
{
}
Las funciones pueden o no requerir datos para hacer su trabajo, y pueden o no devolver un resultado cuando cuando terminan. Esta, llamada miFuncion, no requiere datos y no devuelve ninguno. Tampoco hace nada, pero pero no pasa nada. Este es el formato para definir una función:
<datos de salida> <nombre de la función> (datos de entrada)
{
<instrucciones para realizar la función>
}
Los datos de entrada siempre van dentro de un par de paréntesis y todas las instrucciones de la función irán dentro de dentro del par de llaves. Para futuras referencias, todos los paréntesis y llaves deben aparecer en pares.
De todos modos, al compilador no le importa cuánto espacio hay entre todas estas cosas, así que hay mucho espacio para hacer tu código tan (i)legible como quieras. Podríamos comprimir todo esto así:
void miFuncion(void){}
Y seguiría teniendo el mismo resultado. Debido a que el espacio en blanco, como se le llama, no importa, cada instrucción (no confundir con una función) en C++ tiene un punto y coma después de ella.
Por si sirve de algo, utilizaremos un estilo de escritura de código bastante similar al que utilizan los desarrolladores de Haiku, con algunos retoques. Por ejemplo, el código colocado entre llaves siempre se sangrará un nivel con la tecla de tabulación.
Aquí está nuestra primera función que realmente hace algo:
int DosMasDos(void)
{
return 2 + 2;
}
Esta función, llamada DosMasDos, no necesita datos de entrada, pero devuelve un resultado. El resultado de esta función es siempre un número entero, cualquier número sin punto decimal. Mientras que un número es un número para la gente, C++ es es muy exigente con los tipos de datos que se pasan.
Cada programa tiene una función que necesita ser definida: main. Se puede definir (como decirle al computadora qué hacer para la función main()) de un par de maneras diferentes, pero usaremos la más simple:
int main(void)
{
return 1;
}
Este programa, cuando se compila y ejecuta, no imprime nada, pero devuelve un 1 al sistema operativo cuando termina.
Hagamos este programa. Guarda el código anterior en un archivo de código fuente llamado RetornarUno.cpp. Abre la Terminal y navega a la carpeta que lo tiene y escribe esto:
gcc -o RetornarUno RetornarUno.cpp
Esto le dice a gcc (GNU Compiler Collection) que vas a hacer un programa llamado RetornarUno (especificado por -o RetornarUno) y que vas a usar RetornarUno.cpp para hacerlo.
Las funciones también pueden llamar a otras funciones, pero sólo si el ordenador las conoce. Esto generará un error al compilar:
int main(void)
{
PulsaElBotonRojo();
return 1;
}
Guárdalo como BotonRojo.cpp y compílalo con este comando:
gcc -o BotonRojo BotonRojo.cpp
El ordenador no reconoce la función PulsaElBotonRojo(), así que no sabe qué hacer cuando la ve y se queja. Si le decimos al ordenador lo que haga, compilará el programa correctamente. Cambia BotonRojo.cpp para que se vea así y compila:
void PulsaElBotonRojo(void)
{
}
int main(void)
{
PulsaElBotonRojo();
return 1;
}
PulsaElBotonRojo no hace nada, pero al ordenador no le importa. La otra forma de llamar a otras funciones es usar bibliotecas incorporadas en el sistema. Hacemos esto enlazando una biblioteca con nuestro programa y diciéndole al compilador qué funciones están en la biblioteca. Ahora haremos algo casi útil. Guarda el siguiente código en HolaMundo.cpp:
#include
Y compila con:
gcc -o HolaMundo HolaMundo.cpp
Hay dos partes que no son familiares aquí. La línea que comienza con #include le dice a una parte del compilador llamada preprocesador que busque definiciones de funciones en la cabecera del sistema stdio.h. En este caso stdio.h es el nombre de un archivo que define un montón de funciones de entrada y salida estándar, como printf(). Los corchetes angulares &< y > indican al preprocesador que se trata de cabeceras del sistema. Podemos hacer las nuestras propias, pero llegaremos a eso en otro momento.
La otra parte desconocida aquí es la parte dentro de los paréntesis para printf(). Todo lo que está entre comillas se imprimirá en la pantalla si ejecutas este programa desde la Terminal. Este tipo de datos se llama cadena (como en cadena de caracteres).
¿Qué pasa si quitas «\n» del final de la cadena para que quede así: «¡Hola mundo!».
Escribe un programa que imprima una caja en el Terminal usando símbolos de menos y barras verticales como este:
+----------+
| |
| |
| |
| |
+----------+
¿Cómo se podría escribir un programa que dibuje dos cajas sin tener que teclear mucho?
Nuestro primer par de programas no fueron muy útiles, pero a medida que avancemos, descubrirás que puedes hacer más y más cosas con tus programas. Esta vez vamos a ver dos conceptos principales: los comentarios y las diferentes etapas por las que pasa el compilador para generar tu programa, y también aprenderemos un poco sobre la depuración de tu código.
Los comentarios son notas que se introducen en el código. Tienen muchos usos, como aclarar una determinada sección de código, proporcionar advertencias, o desactivar temporalmente una sección de código. Vea en el siguiente ejemplo cómo se pueden utilizar.
// Esto es un comentario de una línea.
// Esto también. Todo lo que hay después de las dos barras diagonales se considera parte de él.
int main(void)
{
PulsaElBotonRojo(); // Este código no funciona.
return 1;
}
Heredado de C es el comentario multilínea. Empiezan con /* y terminan con */ de forma similar a los paréntesis o las llaves, con una diferencia: no se puede poner un comentario multilínea dentro de otro.
/*-----------------------------------------------------------------------
BotonRojo.cpp
Este código es un ejemplo de cómo el compilador se quejará si usas
una función que no reconoce.
------------------------------------------------------------------------*/
// Esto es un comentario de una línea.
// Esto también. Todo lo que hay después de las dos barras se considera parte de él.
int main(void)
{
PulsaElBotonRojo(); // Este código no funciona.
return 1;
}
Ahora daremos un paso atrás y veremos el proceso por el que pasa el compilador cuando construye tu programa. Es importante entender esto porque hay diferentes tipos de errores que se pueden cometer al escribir código y saber algo sobre el proceso te ayudará a encontrarlos más fácilmente.
Cuando un programa se construye a partir del código fuente, hay cuatro herramientas que operan sobre él antes de que sea un ejecutable: el preprocesador, el compilador, el ensamblador y el enlazador.
El preprocesador acepta código fuente sin procesar como entrada y realiza un pequeño retoque antes de enviar el código al compilador. Elimina los comentarios y maneja las directivas #include insertando en el código el contenido del fichero de cabecera incluido. Hay otras directivas que maneja que se discutirán más adelante.
El compilador traduce el código C++ a lenguaje ensamblador. El ensamblador está mucho más cerca de las instrucciones que entiende el ordenador, pero sigue siendo legible para el ser humano. También es mucho más difícil escribir programas y es específico del procesador para el que está escrito.
El ensamblador crea código objeto a partir del código ensamblador creado por el compilador y los coloca en archivos objeto que tienen una extensión .o. El código objeto son las instrucciones ejecutables por el ordenador para ejecutar el programa. Sin embargo, aún no está listo para ejecutarse. En este estado, los archivos objeto utilizados para crear tu programa se parecen mucho a un conjunto de piezas de puzzle listas para ser ensambladas.
El enlazador une los archivos objeto junto con las bibliotecas que utilizan en un programa ejecutable.
Por naturaleza humana, los programadores cometen muchos errores. Escribir código y depurar van de la mano y a menudo se hacen al mismo tiempo. Por ello, aprenderemos a depurar programas al mismo tiempo que aprendemos a escribirlos.
Hay dos tipos de errores: sintácticos y semánticos. Los errores sintácticos son fáciles de encontrar porque el compilador los encuentra por nosotros. Son problemas como errores de mayúsculas, paréntesis que faltan o sobran y nombres de funciones mal escritos. Los errores semánticos suelen ser más difíciles de encontrar porque son errores en la lógica de un código perfectamente legal. Un error semántico en español sería «el censor de temperatura de mi vehículo necesita ser sustituido», la frase es perfectamente legal y está correctamente construida, pero la palabra adecuada es «sensor», no «censor». Ejemplos de errores semánticos son el punto y coma de más en determinados lugares, añadir una cantidad incorrecta a un número y hacer suposiciones sobre el valor de retorno de una función.
Veamos algunos ejemplos simplificados de errores comunes:
#include
foo.cpp:6: error: expected declaration before ‘}’ token
gcc nos ha dado un error bastante críptico, como de costumbre, pero también nos ha dado dos pistas: el nombre del archivo y el número de línea. El número de línea dado por gcc y la ubicación real del error no siempre coinciden, pero en este caso sí.
Llegados a este punto, puede que te estés preguntando: «¿Qué demonios es un token, genio?». Un token es cualquier elemento del lenguaje. Al igual que un lenguaje escrito normal tiene palabras y signos de puntuación, lo mismo ocurre con los lenguajes informáticos. Al igual que dos comas seguidas son un error de puntuación en español, tener una llave de más es un error de puntuación en C++.
/*-----------------------------------------------------------------------
BotonRojo.cpp
/*Este código es un ejemplo de cómo el compilador se quejará si usas
una función que no reconoce.*/
------------------------------------------------------------------------*/
// Esto es un comentario de una línea.
// Esto también. Todo lo que hay después de las dos barras se considera parte de él.
int main(void)
{
PulsaElBotonRojo(); // Este código no funciona.
return 1;
}
foo.cpp:7: error: expected unqualified-id before ‘--’ token
Este es un ejemplo de cómo el número de línea para el error no es el mismo lugar que el error real. La queja se refiere a los guiones al final del comentario multilínea de la parte superior. Sin embargo, la causa es el anidamiento de un comentario multilínea dentro de otro. El preprocesador elimina todos los comentarios, así que lo que el compilador ve es esto:
------------------------------------------------------------------------*/
int main(void)
{
PulsaElBotonRojo();
return 1;
}
El compilador no sabe qué hacer con la línea discontinua y se queja.
int Main(void)
{
return 1;
}
/usr/lib/gcc/i486-linux-gnu/4.4.1/../../../../lib/crt1.o: In function `_start':
/build/buildd/eglibc-2.10.1/csu/../sysdeps/i386/elf/start.S:115: undefined
reference to `main'
/tmp/ccv39Cuo.o:(.eh_frame+0x12): undefined reference to `__gxx_personality_v0'
collect2: ld returned 1 exit status
Se trata de un error de otro tipo. ¿Recuerdas que hay que definir main() en todos los programas? No lo hicimos, definimos Main(). Por lo demás, el programa es válido, así que compiló bien, pero cuando el enlazador intentó unirlo todo, no pudo encontrar la única función necesaria y lanzó un error. Cada vez que veas un error que contiene una undefined reference to, tienes un error del enlazador.
Resolver errores del enlazador por referencias indefinidas no suele ser difícil. Suele significar una de estas dos cosas: que olvidaste enlazar una biblioteca que utilizaste, o que un archivo de código fuente se omitió accidentalmente al compilar el programa.