Curso C++

3. Variables

Una variable es un espacio de memoria. Pero seamos honestos, si es la primera vez que estás intentando entender qué es una variable, entonces esa definición no significa nada. Así que vamos a explicarlo de otra manera.

Una variable es como una etiqueta, como esas que se les coloca a los frascos de condimentos en la cocina. Imaginémoslo. Tomo una etiqueta, saco un marcador del cajón y anoto:

azucar

Ahora bien, esa etiqueta la tenemos que pegar en un contenedor, un frasco o un recipiente, en donde luego pondremos el azucar. Entonces, tomamos un frasco de vidrio de una medida media, digamos 1 kilo, y colocamos la etiqueta ahí.

frasco azucar

Ya casi lo tenemos. Falta el último paso: poner el azúcar en el frasco. Tomamos la bolsa que compramos por la mañana en el supermercado, la abrimos y ponemos todo dentro del frasco. Como ya tiene la etiqueta de azucar pegada, no nos vamos a confundir de frasco:

frasco azucar = 1 kilo de azucar

Listo. Ahora podemos guardar el frasco junto al resto de frascos y estar seguros de que cada vez que queramos azúcar, vamos a encontrarlo por la etiqueta.

En código

Esto que acabamos de imaginar es un mero ejemplo, pero útil para visualizar qué es una variable. Vamos a hacer unos cambios en el ejemplo para que ahora sí podamos entenderlo en código. Lo primero:

La etiqueta en la que escribimos azucar es el identificador de la variable. Es el nombre que ponemos para crearla y, posteriormente, poder hacer uso de ella.

Identificador

Lo único que debemos entender sobre el identificador es que tiene unas pocas reglas que debemos respetar:

  • Los identificadores no pueden empezar con números. Por ejemplo: 1azucar.
  • Tampoco pueden empezar con símbolos. Por ejemplo: /azucar o "azucar" (teniendo en cuenta que las comillas dobles están reservadas para los datos de tipo texto).
  • No pueden contener espacios. Por ejemplo: frasco de azucar.
  • Solamente deben iniciar con una letra (minúscula o mayúscula) o con el símbolo _, que lo vamos a evitar por el momento porque se tiende a utilizar más en Programación Orientada a Objetos.
  • Pueden contener números entre las letras. Por ejemplo: azucar1 o azu1car.
  • Pueden estar todas las letras en mayúsculas. Por ejemplo: AZUCAR.

Y un poco más, pero que no son importantes por el momento.

Entonces, la etiqueta azucar está perfecta como identificador.

Tipos de Datos

Siguiendo con el ejemplo, el contenedor o recipiente, como el frasco que utilizamos, podría tener otros tamaños, como uno más grande o más pequeño, o ser para otro tipo de elemento, como agua o aceite. En nuestro caso elegimos un frasco, lo que en código se traduce como tipo de dato.

Un tipo de dato indica qué clase de contenido vamos a guardar. ¿Un número entero?, ¿Una letra?, ¿Un texto?, ¿Un número con decimales?

  • Para números enteros -> int
  • Para números con decimales -> float o double
  • Para letras -> char
  • Para frases de texto extensas -> string o char[]
  • Para valores de verdad (1 o 0 - true o false) -> bool

Como en nuestro ejemplo, elegir el contenedor incorrecto tiene sus consecuencias. Si colocamos azúcar en un recipiente de 10 litros, entraría, pero sería demasiado grande para la función real que va a cumplir. Si lo colocamos en un recipiente de medio kilo, se desbordaría. Tiene que ser un contenedor de un kilo para que no sobre espacio, pero tampoco desborde.

Supongamos que queremos guardar un número entero. Al igual que nuestro ejemplo, comenzamos colocando el contenedor que vamos a utilizar:

int

Y seguido, colocamos el identificador:

int numero

Lo que continúa son dos posibles acciones. Si se tratase de nuestro frasco, podríamos cerrar su tapa o colocar el azúcar. Con una variable sucede lo mismo. Podemos cerrar la declaración para indicar que finalizamos la acción de “elegir el tipo de dato” y “ponerle un nombre” para guardarla en nuestra alacena y utilizarla luego.

int numero;

El punto y coma le indica al compilador que finalizamos una instrucción.

O podemos darle un valor (llenar el frasco con el contenido):

int numero = 10;

Como se nota en ambos ejemplos, las instrucciones siempre las finalizamos colocando punto y coma.

Si en lugar de querer guardar un valor entero quisiéramos guardar un texto, utilizaríamos el tipo de dato string (recordá incluir la librería <string>):

string texto = "Esto es un texto";

O si quisiéramos guardar un valor de verdad:

bool valorDeVerdad = true;

¡Listo! Construimos una variable.

Una variable, por lo tanto, es la suma de un tipo de dato, un identificador y un valor.

Ahora con toda seguridad, al haber guardado nuestro valor en un contenedor de su medida y al haberle puesto un nombre, cada vez que queramos utilizarlo vamos a poder llamarlo.

numero;

En este ejemplo estamos llamando a la variable para acceder a su valor. El compilador lee la línea de código y dice “Ok, necesitan el frasco con el nombre numero”, va hasta la alacena (Memoria RAM), lo agarra y nos lo entrega.

¿Y si queremos colocar algo distinto dentro de ese contenedor? Para eso antes tenemos que entender qué es la asignación.

Asignación

La acción de colocar un valor en una variable se llama asignación, y para realizarlo debemos utilizar el operador de asignación:

=

El símbolo de igual (a diferencia de las matemáticas) lo utilizamos para poder colocar un valor por primera vez en una variable o para cambiarlo por un nuevo valor. Esto ya lo vimos hace un rato cuando mencionamos lo de colocar el azúcar en el frasco.

int numero = 10;

Cuando colocamos el operador de asignación luego del identificador de una variable, le estamos indicando al compilador que el valor que continúa es el que queremos guardar en nuestro contenedor.

int numero;
numero = 10;

En este ejemplo, elegimos el contenedor, le ponemos nombre y cerramos la instrucción. Es decir, determinamos el tipo de dato, le ponemos un identificador y ponemos el punto y coma para indicar que finalizamos la acción. El compilador con esa simple instrucción puede reservar un espacio en la memoria RAM o, siguiendo el ejemplo, hacer espacio en la alacena en el que colocaremos el frasco. Luego, en la siguiente línea, realizamos la asignación.

Pero no solamente podemos asignar valores literales como 10 o "Esto es un texto", sino que podemos asignar el valor de un contenedor en uno nuevo.

int numero = 10;
int numero_2 = numero;

En este ejemplo, el compilador crea la variable llamada numero y le asigna el valor de 10. Luego crea una segunda variable llamada numero_2 y le asigna el valor que contiene la variable numero, es decir, el 10.

Para esto, el compilador toma un frasco, le pone su etiqueta, un contenido y lo guarda en la alacena. Luego toma otro frasco, le pone una etiqueta y, antes de cerrarlo, lee el contenido del frasco anterior para copiar ese mismo valor en el nuevo. Y acá se produce algo importante: en código, el contenido de la primera variable no “se mueve” a la segunda dejándola vacía, sino que se copia, permitiendo que ambas variables tengan el mismo valor de forma independiente. ¿No sería bueno que pasara así en la realidad? Sería como tener mi frasco con 1 kilo de azúcar y, al pasarlo a otro frasco, no estaríamos moviendo el contenido, sino duplicándolo, para tener ahora dos frascos completos con 1 kilo de azúcar cada uno. Eso mismo sucede en código. Ambas variables ahora contienen el valor 10.

Pero ¿entonces qué pasa con el siguiente código?:

int numero = 10;
numero = 20;

¿Daría error? Si seguimos estrictamente con la lógica física del frasco, sí, porque estaríamos intentando meter más contenido en un espacio que ya está lleno. Pero en código sucede algo distinto. El operador de asignación reemplaza el valor de una variable eliminando el anterior.

Se podría decir que el valor que tenía el contenedor se saca y se coloca el nuevo, pero en realidad sucede algo más destructivo. El valor anterior se sobrescribe, se pisa, se evapora como Peter Parker tras el chasquido de Thanos, para poder almacenar el nuevo valor. Esto tiene dos implicancias:

  • Es simple actualizar datos. En el ejemplo anterior, la variable inicialmente contiene 10, pero luego, cuando le asignamos el 20, pasa a contener 20. El 10 desapareció.
  • Perdemos el valor anterior. No podemos recuperarlo luego de asignar otro valor en el mismo espacio de memoria.

Veamos un error común al intentar intercambiar valores:

int a = 10;
int b = 20;

a = b;
b = a;

En este ejemplo, si lo leemos rápido, podemos pensar que estamos intercambiando los valores de una variable a otra. ¿Cierto? ¡Pero no! Lo que está sucediendo es que efectivamente creamos dos variables llamadas a y b, les asignamos 10 y 20 respectivamente. Pero a continuación, el valor original de a (el 10) se pierde de forma irreversible porque le asignamos el valor de b (el 20). Luego, y esto es lo frustrante, estamos asignando el nuevo valor de a (que ahora es 20) en la variable b. Al final, si imprimimos los valores, quedaría: a -> 20 y b -> 20.

Inevitablemente, si lo que queremos es intercambiar los valores de la variable a y la variable b, vamos a tener que utilizar un tercer contenedor temporal.

int a = 10;
int b = 20;
int aux;

aux = a;
a = b;
b = aux;

¿Qué es todo esto? A esto en programación se le llama swap (intercambio). Como la asignación directa sobrescribe el valor, creamos una variable ‘auxiliar’. Primero, antes de realizar cualquier cambio, guardamos una copia del valor de la variable a en aux, asegurando su integridad. Luego, ahora sí, podemos asignar el valor de la variable b en la variable a. Lógicamente, el valor original de la variable a se sobrescribe, pero no importa, porque tenemos la copia segura en aux. Finalmente, podemos asignar el valor de aux (que era el valor original de a) en la variable b.

¿Te perdiste? Yo también al principio. Veámoslo paso por paso para no cerrar el capítulo con un mal sabor de boca:

int a = 10;
int b = 20;
int aux;

aux = a; // La variable 'aux' guarda el valor de 10
a = b;   // La variable 'a', que tenía el valor 10, ahora toma el valor de 'b' (20)
b = aux; // A la variable 'b' le asignamos el valor de 'aux', que es 10 (el valor original de 'a')

Si pudiésemos ver los valores de a y de b al final de la ejecución, veríamos que se invirtieron. Empezaron siendo:

a -> 10
b -> 20

Y concluyeron siendo:

a -> 20
b -> 10

¿Complicado? Por supuesto que sí. Estamos comenzando a pensar de una manera diferente, donde las reglas parecen similares a la realidad física, pero su comportamiento lógico es distinto.

Repasá el capítulo. Realizá los ejemplos en tu editor. Si tenés dudas, siempre podés mandarme un mail (no tengo inconvenientes con eso) y lo vemos juntos.