5.5. Variables locales y variables globales
Hasta ahora, hemos declarado las variables dentro de "Main". Ahora nuestros programas tienen varios "bloques", así que se comportarán de forma distinta según donde declaremos las variables.
Las variables se pueden declarar dentro de un bloque (una función), y entonces sólo ese bloque las conocerá, no se podrán usar desde ningún otro bloque del programa. Es lo que llamaremos "variables locales".
Por el contrario, si declaramos una variable al comienzo del programa, fuera de todos los "bloques" de programa, será una "variable global", a la que se podrá acceder desde cualquier parte.
Vamos a verlo con un ejemplo. Crearemos una función que calcule la potencia de un número entero (un número elevado a otro), y el cuerpo del programa que la use.
La forma de conseguir elevar un número a otro será a base de multiplicaciones, es decir:
3 elevado a 5 = 3 • 3 • 3 • 3 • 3
(multiplicamos 5 veces el 3 por sí mismo). En general, como nos pueden pedir cosas como "6 elevado a 100" (o en general números que pueden ser grandes), usaremos la orden "for" para multiplicar tantas veces como haga falta:
/*---------------------------*/ /* Ejemplo en C# nº 50: */ /* ejemplo50.cs */ /* */ /* Ejemplo de función con */ /* variables locales */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo50 { public static int potencia(int nBase, int nExponente) { int temporal = 1; /* Valor que voy hallando */ int i; /* Para bucles */ for(i=1; i<=nExponente; i++) /* Multiplico "n" veces */ temporal *= nBase; /* Y calculo el valor temporal */ return temporal; /* Tras las multiplicaciones, */ } /* obtengo el valor que buscaba */ public static void Main() { int num1, num2; Console.WriteLine("Introduzca la base: "); num1 = Convert.ToInt32( Console.ReadLine() ); Console.WriteLine("Introduzca el exponente: "); num2 = Convert.ToInt32( Console.ReadLine() ); Console.WriteLine("{0} elevado a {1} vale {2}", num1, num2, potencia(num1,num2)); } }
En este caso, las variables "temporal" e "i" son locales a la función "potencia": para "Main" no existen. Si en "Main" intentáramos hacer i=5; obtendríamos un mensaje de error.
De igual modo, "num1" y "num2" son locales para "main": desde la función "potencia" no podemos acceder a su valor (ni para leerlo ni para modificarlo), sólo desde "main".
En general, deberemos intentar que la mayor cantidad de variables posible sean locales (lo ideal sería que todas lo fueran). Así hacemos que cada parte del programa trabaje con sus propios datos, y ayudamos a evitar que un error en un trozo de programa pueda afectar al resto. La forma correcta de pasar datos entre distintos trozos de programa es usando los parámetros de cada función, como en el anterior ejemplo.
Ejercicios propuestos:
- Crear una función "pedirEntero", que reciba como parámetros el texto que se debe mostrar en pantalla, el valor mínimo aceptable y el valor máximo aceptable. Deberá pedir al usuario que introduzca el valor tantas veces como sea necesario, volvérselo a pedir en caso de error, y devolver un valor correcto. Probarlo con un programa que pida al usuario un año entre 1800 y 2100.
- Crear una función "escribirTablaMultiplicar", que reciba como parámetro un número entero, y escriba la tabla de multiplicar de ese número (por ejemplo, para el 3 deberá llegar desde 3x0=0 hasta 3x10=30).
- Crear una función "esPrimo", que reciba un número y devuelva el valor booleano "true" si es un número primo o "false" en caso contrario.
- Crear una función que reciba una cadena y una letra, y devuelva la cantidad de veces que dicha letra aparece en la cadena. Por ejemplo, si la cadena es "Barcelona" y la letra es 'a', debería devolver 2 (aparece 2 veces).
- Crear una función que reciba un numero cualquiera y que devuelva como resultado la suma de sus dígitos. Por ejemplo, si el número fuera 123 la suma sería 6.
- Crear una función que reciba una letra y un número, y escriba un "triángulo" formado por esa letra, que tenga como anchura inicial la que se ha indicado. Por ejemplo, si la letra es * y la anchura es 4, debería escribir
****
***
**
*
5.6. Los conflictos de nombres en las variables
¿Qué ocurre si damos el mismo nombre a dos variables locales? Vamos a comprobarlo con un ejemplo:
/*---------------------------*/ /* Ejemplo en C# nº 51: */ /* ejemplo51.cs */ /* */ /* Dos variables locales */ /* con el mismo nombre */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo51 { public static void cambiaN() { int n = 7; n ++; } public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); cambiaN(); Console.WriteLine("Ahora n vale {0}", n); } }
El resultado de este programa es:
n vale 5
Ahora n vale 5
¿Por qué? Sencillo: tenemos una variable local dentro de "duplica" y otra dentro de "main". El hecho de que las dos tengan el mismo nombre no afecta al funcionamiento del programa, siguen siendo distintas.
Si la variable es "global", declarada fuera de estas funciones, sí será accesible por todas ellas:
/*---------------------------*/ /* Ejemplo en C# nº 52: */ /* ejemplo52.cs */ /* */ /* Una variable global */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo52 { static int n = 7; public static void cambiaN() { n ++; } public static void Main() { Console.WriteLine("n vale {0}", n); cambiaN(); Console.WriteLine("Ahora n vale {0}", n); } }
Dentro de poco, hablaremos de por qué cada uno de los bloques de nuestro programa, e incluso las "variables globales", tienen delante la palabra "static". Será cuando tratemos la "Programación Orientada a Objetos", en el próximo tema.
5.7. Modificando parámetros
Podemos modificar el valor de un dato que recibamos como parámetro, pero posiblemente el resultado no será el que esperamos. Vamos a verlo con un ejemplo:
/*---------------------------*/ /* Ejemplo en C# nº 53: */ /* ejemplo53.cs */ /* */ /* Modificar una variable */ /* recibida como parámetro */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo53 { public static void duplica(int x) { Console.WriteLine(" El valor recibido vale {0}", x); x = x * 2; Console.WriteLine(" y ahora vale {0}", x); } public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); duplica(n); Console.WriteLine("Ahora n vale {0}", n); } }
El resultado de este programa será:
n vale 5
El valor recibido vale 5
y ahora vale 10
Ahora n vale 5
Vemos que al salir de la función, los cambios que hagamos a esa variable que se ha recibido como parámetro no se conservan.
Esto se debe a que, si no indicamos otra cosa, los parámetros "se pasan por valor", es decir, la función no recibe los datos originales, sino una copia de ellos. Si modificamos algo, estamos cambiando una copia de los datos originales, no dichos datos.
Si queremos que los cambios se conserven, basta con hacer un pequeño cambio: indicar que la variable se va a pasar "por referencia", lo que se indica usando la palabra "ref", tanto en la declaración de la función como en la llamada, así:
/*---------------------------*/ /* Ejemplo en C# nº 54: */ /* ejemplo54.cs */ /* */ /* Modificar una variable */ /* recibida como parámetro */ /* */ /* Introduccion a C#, */ /* Nacho Cabanes */ /*---------------------------*/ using System; public class Ejemplo54 { public static void duplica(ref int x) { Console.WriteLine(" El valor recibido vale {0}", x); x = x * 2; Console.WriteLine(" y ahora vale {0}", x); } public static void Main() { int n = 5; Console.WriteLine("n vale {0}", n); duplica(ref n); Console.WriteLine("Ahora n vale {0}", n); } }
En este caso sí se modifica la variable n:
n vale 5
El valor recibido vale 5
y ahora vale 10
Ahora n vale 10
El hecho de poder modificar valores que se reciban como parámetros abre una posibilidad que no se podría conseguir de otra forma: con "return" sólo se puede devolver un valor de una función, pero con parámetros pasados por referencia podríamos devolver más de un dato. Por ejemplo, podríamos crear una función que intercambiara los valores de dos variables:
public static void intercambia(ref int x, ref int y)
La posibilidad de pasar parámetros por valor y por referencia existe en la mayoría de lenguajes de programación. En el caso de C# existe alguna posibilidad adicional que no existe en otros lenguajes, como los "parámetros de salida". Las veremos más adelante.
Ejercicios propuestos:
- Crear una función "intercambia", que intercambie el valor de los dos números enteros que se le indiquen como parámetro.
- Crear una función "iniciales", que reciba una cadena como "Nacho Cabanes" y devuelva las letras N y C (primera letra, y letra situada tras el primer espacio), usando parámetros por referencia.