Ir al contenido principal

Como ocupar C++

El lenguaje C++

C++ es un ejemplo de lenguaje de programación compiladomultiparadigma, principalmente de tipo imperativo y orientado a objetos, incluyendo también programación genérica y funcional, características estas últimas que comentaremos más adelante en el curso.

Un programa escrito en un lenguaje imperativo es un conjunto de instrucciones que indican al computador cómo realizar una tarea.

En un lenguaje imperativo:

  • se conoce en cada paso del programa su estado, determinado por el valor que toman las variables utilizadas.

  • se altera el estado del programa a través de sentencias.

La implementación hardware de la mayoría de computadores está diseñada para ejecutar código máquina escrito en forma imperativa:

  • las variables son posiciones de memoria

  • las sentencias son instrucciones

En este curso nos centraremos principalmente en la parte imperativa del lenguaje C++, que es básicamente la misma que la del lenguaje C.

Historia

C++ es un lenguaje compilado que fue desarrollado en 1980 por Bjarne Stroustroup en los laboratorios At&T como una extensión orientada a objetos del lenguaje C.

Nota

C++ significaría incremento de C, aprovechando que el lenguaje C tiene el operador ++ con ese nombre.

Por ello, código fuente escrito en C puede compilarse como C++. Esto fue una virtud en los primeros años, pero a la larga también ha sido una debilidad pues, para garantizar la compatibilidad, ha mantenido algunos de los graves inconvenientes del lenguaje C.

El lenguaje C fue creado por Dennis Ritchie entre los años 1970-73. D. Ritchie fue cocreador junto a Ken Thompson del sistema operativo UNIX.

Durante el curso usaremos principalmente C++11, que es la cuarta revisión del Lenguaje de Programación C++ aprobada por la Organización Internacional para la Estandarización (ISO) en 2011.

La versión estándar más actual es la C++20. Eso no significa que vayamos a estudiar unas características del lenguaje desfasadas. Las novedades que se han ido incorporando en los últimos años respecto a la versión C++11 están enfocadas a programas/programadores de muy alto nivel.

Ejemplos de programas escritos en C/C++ son los sistemas operativos Windows, Mac OS X y Linux, el navegador/buscador Google Chrome, Adobe Acrobat, la página de Amazon, Autodesk, Facebook, Microsoft Office, la suite de programas Mozilla, FIFA EA Sports, Fortnite, etc.

La biblioteca iostream

Por regla general, un programa tiene utilidad práctica si:

  • acepta datos de entrada, para su posterior procesamiento

  • proporciona una salida de resultados, para ser reutilizados por el usuario u otro programa

La forma más elemental de proporcionar datos es vía teclado y, de mostrarlos, es utilizando una terminal o consola, interfaz gráfica que muestra únicamente caracteres alfanuméricos.

C++ proporciona estos recursos básicos a través de la biblioteca de entrada/salida iostream. Esta biblioteca forma parte de la biblioteca estándar de C++.

Para informar al compilador de que deseamos utilizar alguna de las funcionalidades de la biblioteca iostream, es necesario incluir su archivo de cabecera (header file) asociado, típicamente al principio del archivo:

#include <iostream>

La biblioteca estándar y el espacio de nombres std

Todas las funcionalidades de la biblioteca estándar y, por tanto, las de iostream, se encuentran englobadas dentro de un espacio de nombres (namespace) denominado std.

Los espacios de nombres proporcionan un ámbito único a un grupo de identificadores.

  • permiten organizar el código en grupos lógicos.

  • evitan conflictos de nombres que pueden producirse cuando el programa utiliza varias bibliotecas.

Las colisiones entre identificadores es un grave problema que surge cuando los programas empiezan a tener un considerable número de líneas de código y se utilizan ficheros creados por diferentes programadores y/o procedentes de diversas bibliotecas.

Para poder usar cualquiera de los identificadores perteneciente a un espacio de nombres debemos utilizar el espacio de nombres seguido del operador de resolución de ámbito, ::.

Nota

El concepto de espacio de nombres como mecanismo de organización y, de paso, de evitación de colisiones, está presente en todos los ámbitos, no sólo en el de la informática.

  • Podemos tener dos ficheros con el mismo nombre en el ordenador si están dentro de espacios de nombres (carpetas o directorios) diferentes.

  • Podemos enviar una carta a la dirección Cervantes 7, 3ºE porque el espacio de nombres (ciudad, nación) es diferente.


La salida estándar std::cout

Concepto de flujo (stream)

Un flujo (stream) es un canal a través del cual fluyen en serie bytes desde un origen a un destino. Un flujo se implementa internamente con el concurso de un búfer (buffer), una zona de memoria donde se almacenan temporalmente los datos siguiendo una estructura tipo cola (queue), es decir, primero en llegar, primero en salir.

La biblioteca iostream tiene una variable predefinida para un flujo de caracteres (bytes) de salida llamada cout (character output), lo que permite enviar datos a la consola para ser imprimidos como texto.

#include <iostream>

int main()
{
   std::cout << "Hola mundo.";
}

Edita, compila y ejecuta el código

Analicemos este programa:

  1. #include <iostream>

    informamos al compilador nuestro deseo de utilizar funciones de E/S.

  2. std::cout

    la variable cout pertenece al espacio de nombres std.

  3. <<

    operador de inserción, que permite insertar caracteres al flujo de salida cout.

  4. "Hola mundo."

    es una constante literal cadena de caracteres.

El efecto del operador de inserción pude verse en el siguiente ejemplo, cuya salida es idéntica a la del programa anterior. Así, el operador << concatena de izquierda a derecha al flujo de salida cout la cadena "Hola " y luego la cadena "mundo.".

#include <iostream>

int main()
{
   std::cout << "Hola " << "mundo.";
}

Edita, compila y ejecuta el código

¿Qué ocurre si suprimimos la línea #include <iostream>?

//#include <iostream>

int main()
{
   std::cout << "Hola " << "mundo.";
}

Edita, compila y ejecuta el código

Al compilar tendremos un error similar a error: 'cout' is not a member of 'std' o error: use of undeclared identifier 'std'.

El error se debe a que el compilador no encuentra la declaración de cout al no haber incluido la cabecera de la biblioteca iostream o no encuentra ni siquiera el espacio de nombres std.

Nota

Los mensajes de error y la información asociada varían dependiendo del compilador que utilicemos.

¿Qué ocurre si no usamos la resolución de ámbito std::?

#include <iostream>

int main()
{
   cout << "Hola " << "mundo.";
}

Edita, compila y ejecuta el código

Al compilar tendremos un error similar a error: 'cout' was not declared in this scope.

En este caso, el compilador no sabe a qué nos referimos con el identificador cout, es incapaz de relacionarlo con el identificador std::cout que sí está presente en iostream.

Alternativas al uso de la resolución de ámbito ::

Usar la resolución de ámbito con el operador :: es la fórmula recomendada y profesional. Es cierto que este operador recarga el código, haciéndolo prolijo visualmente y tedioso al escribirlo.

Como alternativa y, en particular, para un curso introductorio, puede optarse por utilizar la directiva using de la siguiente forma:

#include <iostream>

using namespace std;

int main()
{
   cout << "Hola " << "mundo.";
}

En el ejemplo, la línea using namespace std; informa al compilador de que, si algún identificador no está declarado, entonces debe buscarlo en el espacio de nombres std.

Transformando representaciones numéricas a cadenas de caracteres

Una de las funcionalidades de la biblioteca iostream es que permite insertar en el flujo de caracteres de salida cout datos numéricos y transformarlos, de forma transparente al usuario, a una representación como cadena de caracteres.

#include <iostream>

int main()
{
   int x = 3;
   double y = 8.13;
   std::cout << "x=" << x << ", y=" << y;
}

Edita, compila y ejecuta el código

Nótese que el efecto final ha sido crear el flujo de caracteres de salida x=3, y=8.13.

Salida en múltiples líneas

En ocasiones se desea imprimir por la consola datos en diferentes líneas. Veamos qué ocurre en el siguiente ejemplo.

#include <iostream>

int main()
{
   int x = 3;
   double y = 8.13;
   std::cout << "x=" << x;
   std::cout << "y=" << y;
}

Edita, compila y ejecuta el código

La salida mostrada es x=3y=8.13. Es algo normal, pues en ningún caso hemos introducido en el flujo de salida el carácter nueva línea'\n'.

Dependiendo del entorno de ejecución del programa, también se observará que el prompt de la consola (símbolo $ o similar) aparece a continuación de la salida del programa.

Una forma de forzar una nueva línea es mediante std::endl cuyo objetivo es:

  • insertar en el flujo de salida ese carácter

  • vaciar (flush) el flujo de caracteres, forzándolo a ser mostrado en la consola

#include <iostream>

int main()
{
   int x = 3;
   double y = 8.13;
   std::cout << "x=" << x << std::endl;
   std::cout << "y=" << y << std::endl;
}

Edita, compila y ejecuta el código

Una alternativa habitual al uso de std::endl es utilizar directamente el carácter nueva línea '\n'.

#include <iostream>

int main()
{
   int x = 3;
   double y = 8.13;
   std::cout << "x=" << x << "\ny=" << y << '\n';
} 

Edita, compila y ejecuta el código

 


La entrada estándar std::cin

En los apartados anteriores se ha aprendido a mostrar texto por pantalla. Sin embargo, eso no permite interaccionar a un usuario con un programa.

Una forma básica de interacción es aceptar datos en forma de texto vía teclado. La biblioteca iostream tiene una variable predefinida para un flujo (stream) de caracteres de entrada llamada cin (character input), lo que permite transformar secuencias de caracteres a la representación binaria de datos en memoria.

Nótese que es el proceso inverso al de cout, donde se transforma la representación binaria de un dato a una secuencia de caracteres imprimibles.

#include <iostream>

int main()
{
   std::cout << "Introduce un entero: ";
   int x;
   std::cin >> x;
   std::cout << "x=" << x << '\n';
}

Edita, compila y ejecuta el código

Analicemos este programa:

  1. #include <iostream>

    informamos al compilador nuestro deseo de utilizar funciones de E/S.

  2. int x

    definimos la variable donde deseamos almacenar el valor introducido por teclado.

  3. std::cin

    la variable cin pertenece al espacio de nombres std.

  4. >>

    operador de extracción, que permite extraer caracteres del flujo de entrada cin y transformarlos a su representación binaria interna.

Nota

Regla nemotécnica

Los operadores de extracción e inserción indican el sentido del flujo de datos. Así, sabiendo que con cin utilizamos el teclado y con cout mostramos en la consola:

  • cin >>: los caracteres fluyen desde el teclado

  • cout <<: los caracteres fluyen hacia la consola

La operación de extracción

La introducción de caracteres desde el teclado por parte del usuario es libre y, por tanto, sujeta a errores.

De cara a entender algunos de los errores que pueden producirse es importante conocer cómo se produce el proceso de extracción mediante el operador >>.

Podemos encontrarnos en las siguientes dos situaciones:

  1. El búfer asociado a cin está vacío. Es la situación más habitual.

    • El operador >> mantiene la ejecución del programa a la espera de la introducción de datos por parte del usuario.

    • Cuando el usuario introduce el carácter '\n', el operador >> comienza a transformar los caracteres introducidos a la representación binaria interna de la variable.

    • Todos los espacios en blanco (whitespace characters) al inicio del búfer son ignorados, incluidos tabuladores y retorno de línea.

    • Se transforman los caracteres hasta el siguiente espacio en blanco.

    • El resto de caracteres permanece en el búfer de cin hasta una nueva operación de extracción.

  2. El búfer asociado a cin tiene datos. En este caso, el operador >> comienza de forma inmediata la operación de extracción siguiendo las pautas descritas anteriormente respecto a la gestión de los espacios en blanco.

Veamos qué ocurre con el siguiente programa con distintos escenarios de introducción de datos. Supondremos que el usuario decide introducir los valores 3 y 5 para las variables x e y respectivamente.

#include <iostream>

int main()
{
   std::cout << "Introduce un entero: ";
   int x;
   std::cin >> x;
   std::cout << "Introduce otro entero: ";
   int y;
   std::cin >> y;
   std::cout << "x=" << x << "\ny=" << y << '\n';
}

Edita, compila y ejecuta el código

Caso 1

  1. Se muestra el mensaje "Introduce un entero: "

  2. El usuario pulsa 3 + '\n'

  3. Se muestra el mensaje "Introduce otro entero: "

  4. El usuario pulsa 5 + '\n'

  5. Se muestran por pantalla correctamente los valores de las variables x e y

Es el habitual y todo sucede como es de esperar.

Caso 2

  1. Se muestra el mensaje "Introduce un entero: "

  2. El usuario pulsa '\t' + '\t' + 3 + '\n'

  3. Se muestra el mensaje "Introduce otro entero: "

  4. El usuario pulsa '\n' + 5 + '\n'

  5. Se muestran por pantalla correctamente los valores de las variables x e y

El usuario ha introducido espacios en blanco al principio de cada una de las dos operaciones de extracción. Aunque es algo inusual, no hay ningún error pues el operador >> extrae estos caracteres del búfer ignorándolos.

Caso 3

  1. Se muestra el mensaje "Introduce un entero: "

  2. El usuario pulsa 3 + ' ' + 5 + '\n'

  3. Se muestra el mensaje "Introduce otro entero: x=3"

  4. Se muestra por pantalla "y=5"

En este caso, el usuario se ha precipitado y ha introducido inicialmente los dos valores separados por un espacio, 3 5'\n'. ¡Ese es el contenido del búfer en ese momento! Lo que ocurre es lo siguiente:

  1. El operador de extracción, tras introducir el carácter '\n' comienza el proceso de transformación, que se ve interrumpido por la presencia del espacio en blanco ' '.

  2. A continuación, se muestra el mensaje "Introduce otro entero: ".

  3. Comienza el proceso de extracción para la variable y, pero ahora el búfer de cin no está vacío, está formado por la cadena "5'\n'".

  4. Se extrae correctamente el valor 5 hacia la variable y.

  5. Comienza el proceso de inserción del búfer de cout. Nótese que, como no se ha introducido ningún carácter nueva línea, la cadena "x=3" aparece a continuación de la cadena "Introduce otro entero: ".

En definitiva, las variables x e y tienen los valores deseados, aunque el proceso ha sido atípico y los datos de salida en la consola muestran un aspecto desordenado.

Caso 4

  1. Se muestra el mensaje "Introduce un entero: "

  2. El usuario pulsa a + 3 + '\n'

  3. Se muestra el mensaje "Introduce otro entero: x=0"

  4. Se muestra el mensaje "y=0"

En este caso, el usuario se ha equivocado y ha introducido un carácter, a, que el operador de extracción es incapaz de transformar a entero. A partir de este momento, cin pasa a modo fallo (failure) y cualquier proceso de extracción posterior es obviado. Las variables oportunas se inicializan a 0 por defecto.

Es una situación de error en el programa y el funcionamiento de este será normalmente indefinido, con resultados espurios.

Caso 5

  1. Se muestra el mensaje "Introduce un entero: "

  2. El usuario pulsa 3 + a + '\n'

  3. Se muestra el mensaje "Introduce otro entero: x=3"

  4. Se muestra el mensaje "y=0"

Es un caso similar al anterior. En este caso, el operador de extracción es capaz de transformar el 3, pero a partir de este momento entra en modo fallo.

Atención

La gestión de este tipo de errores debidos a la incorrecta introducción de datos por parte del usuario es fundamental en cualquier programa comercial.

Ejemplos típicos los vemos en el relleno de formularios en páginas web, donde el programa se asegura que un número de cuenta, un DNI, etc. tiene el formato correcto.

Detectar caracteres o formatos erróneos, es una tarea relativamente compleja y sale del ámbito de la asignatura. Por ello, no lo abordaremos en este curso.

Sin embargo, conocer el proceso de extracción del operador >> puede arrojar luz para entender el porqué de resultados absurdos cuando se ejecuta un programa.

Introducción de más de un valor en la misma línea

El operador de extracción puede aparecer concatenado, evaluándose de izquierda a derecha.

#include <iostream>

int main()
{
   std::cout << "Introduce dos enteros: ";
   int x, y;
   std::cin >> x >> y;
   std::cout << "x=" << x << "\ny=" << y << '\n';
}

Fuente de información:

https://www2.eii.uva.es/fund_inf/cpp/temas/8_funciones/ejemplos.html

Comentarios