sábado, 6 de mayo de 2017

Curso de C#: operadores

Los operadores sirven, para efectuar operaciones con uno o más parámetros (sumar, restar, comparar...) y retornar un resultado. Aquí se muestra una tabla con los operadores de C#.

Operadores de C#
Tomado de el Guille
Están colocados en orden de precedencia, es decir, en una expresión con varios de ellos, se ejecutan en ese orden. Los operadores primarios son operadores de expresión.

En el caso de “(expresión)”, los operadores son realmente los paréntesis. Sirven para modificar la precedencia.

En “objeto.miembro”, el operador es el punto. Sirve para especificar un miembro de una clase (sea una variable, una propiedad o un método).

En “método(argumento, argumento, ...)”, los operadores vuelven a ser los paréntesis. En este caso, sirven para especificar la lista de argumentos de un método.

En array[índice], los operadores son los corchetes. Sirven para indicar el elemento de un array o un indizador.

Los operadores de incremento (++) y decremento (--) sirven para incrementar o disminuir el valor de una variable en una unidad. Por ejemplo:

num++;

Hará que num incremente su valor en una unidad. Los operadores de incremento y decremento se pueden poner delante (preincremento ó predecremento) o bien detrás (postincremento ó postdecremento), teniendo comportamientos distintos.
Si hay un postincremento o postdecremento, primero se toma el valor de la variable y después se incrementa o decrementa. En caso contrario, si lo que hay es un preincremento o un predecremento, primero se incrementa o decrementa la variable y después se toma el valor de la misma. En una línea como la anterior esto no se ve claro, porque, además, el resultado sería el mismo que poniendo ++num. Sin embargo, veamos este otro ejemplo (num vale 10):

a = ++num;
b = a--;
Después de ejecutar la primera línea, tanto a como num valdrían 11, ya que el preincremento hace que primero se incremente num y después se tome su valor, asignándolo así a la variable a. Después de ejecutar la segunda línea, b valdrá 11, y a valdrá 10 porque el postdecremento de a hace que primero se asigne su valor actual a b y después se decremente el suyo propio.

El operador “new” sirve para instanciar objetos.

El operador “typeof” es un operador de reflexión. La reflexión es la posibilidad de recuperar información de un tipo determinado en tiempo de ejecución. typeof devuelve el tipo de un objeto.

“sizeof” devuelve el tamaño en bytes que ocupa un tipo determinado. Solamente se puede utilizar sizeof con tipos valor y en contextos de código inseguro.

“checked” y “unchecked” sirven para controlar si una expresión provoca o no desbordamiento. Por ejemplo, las variables de tipo byte pueden almacenar valores entre 0 y 255. Si escribimos:

byte i=253;
checked {i+=10;}
Console.WriteLine(i);

El programa se compila, pero al ejecutar se produce un error de desbordamiento, ya que la variable i es de tipo byte y no puede almacenar valores mayores que 255. Sin embargo, si cambiamos checked por unchecked:

byte i=253;
unchecked {i+=10;}
Console.WriteLine(i);

El programa no produciría error de desbordamiento, ya que unchecked hace que se omitan estos errores. En su lugar, i toma el valor truncado, de modo que después de esa línea valdría 7. checked y unchecked son bloques de código, como se deduce al ver que está escrito con llaves, de modo que se pueden incluir varias líneas en un bloque checked o unchecked.

Las variables numéricas tienen un rango de datos limitado, por ejemplo, una variable de tipo byte, no puede almacenar valores menores que 0 ni mayores que 255. Cuando se trunca el valor, lo que hace es colocar seguidos en una lista todos los valores que la variable acepta, empezando de nuevo por el primero cuando el rango acaba, y después se va recorriendo esta lista hasta terminar la operación de suma.

truncado de valores en C#

En la primera fila están la lista de valores que acepta la variable, a continuación del último que acepta vuelve a estar el primero. Al sumarle 10 (segunda fila) es como si se fueran contando valores posibles hacia adelante, de modo que i ahora vale 7. Otro ejemplo usando el tipo sbyte (que acepta valores entre -128 y 127):

sbyte i=126;
unchecked {i+=10;}
Console.WriteLine(i);

C# Truncado de valores

 De modo que i valdría -120 después de la suma.  

Los operadores unitarios + y - sirven para mantener o cambiar el signo de un operando. Si se desea mantener el signo de un operando sin cambios, el + se puede omitir. Por ejemplo:

int i=10;
int b=-i;
Console.WriteLine("Valor de i: {0}  Valor de b: {1}", i, b);

La variable i valdría 10, y la variable b valdría -10. Por lo tanto, la salida en la consola sería:

Valor de i: 10   Valor de b: -10

El operador unitario ! es un not lógico, invierte el valor de un dato de tipo boolean. En el siguiente ejemplo, i valdría true y b valdría false:

bool i=true;
bool b=!i;

El operador unitario ~ es de complemento a nivel de bits, o sea, que devuelve el valor complementario al operando al que afecta. Para entender esto usamos una variable de tipo int y escribimos su valor y el de su complementario en hexadecimal:

uint i=10;
Console.WriteLine("Valor de i: {0:X8}  Valor de ~i: {1:X8}", i, ~i);

La salida en la consola sería la siguiente:

Valor de i: 0000000A   Valor de ~i: FFFFFFF5

El operador ~ ha cambiado todos los bits, poniendo un 1 donde había un 0 y un 0 donde había un uno. El operador ~ solamente es aplicable a variables de tipo int, uint, long y ulong.

En el operador (conversion), lo que ha de ir entre paréntesis es el tipo al que se quiere convertir (int), (uint), (long)... Ya se explicó en el primer capítulo en el sistema de tipos.

Los operadores * y / son, respectivamente, para multiplicar y dividir.

El operador % devuelve el resto de una división. Por ejemplo, 8 % 3 devolvería 2.

Los operadores + y – (binarios) son para sumar o restar..

Los operadores << y >> efectúan un desplazamiento de bits hacia la izquierda o hacia la derecha. Ejemplo,  usando números hexadecimales:

int i=15;
int b;
int c;
Console.WriteLine("Valor de i: {0:X}", i);
b = i >> 1;
Console.WriteLine("Ejecutado b = i >> 1;");
Console.WriteLine("Valor de b: {0:X}", b);
c = i << 1;
Console.WriteLine("Ejecutado c = i << 1;");
Console.WriteLine("Valor de c: {0:X}", c);

La salida en la consola queda:

Valor de i: F
Ejecutado b = i >> 1;
Valor de b: 7
Ejecutado b = i << 1;
Vaor de b: 1E

Variable
Valor hex.
Valor binario
i
0000000F
0000 0000 0000 0000 0000 0000 0000 1111
b
00000007
0000 0000 0000 0000 0000 0000 0000 0111
c
0000001E
0000 0000 0000 0000 0000 0000 0001 1110

A la variable b se le asigna lo que vale i desplazando sus bits hacia la derecha en una unidad. El 1 que había más a la derecha se pierde. En la variable c  asigna lo que valía i desplazando sus bits hacia la izquierda también en una unidad. En la parte derecha se rellena el hueco con un cero.

Los operadores relacionales < (menor que), > (mayor que), <= (menor o igual que), >= (mayor o igual que), is, == (igual que), != (distinto de) establecen una comparación entre dos valores y devuelven como resultado un valor de tipo booleano (true o false).
  
El resultado es muy obvio cuando se trata de números (tipos valor). Sin embargo, cuando utilizamos variables de tipo referencia no es tan intuitivo.

Circunferencia c = new Circunferencia(4);
Circunferencia d = new Circunferencia(4);
Console.WriteLine("c==d devuelve: {0}", (c==d));

El resultado de comparar c==d sería False. A pesar de que ambos objetos sean idénticos el resultado es False porque una variable de un tipo referencia no retorna internamente un dato específico, sino un puntero a la dirección de memoria donde está almacenado el objeto. De este modo, al comparar, el sistema compara los punteros en lugar de los datos del objeto, y, por lo tanto, devuelve False, puesto que las variables c y d no apuntan a la misma dirección de memoria.

Para hacer esta comparación tenemos que utilizar el método Equals heredado de la clase object así:

Circunferencia c = new Circunferencia(4);
Circunferencia d = new Circunferencia(4);
Console.WriteLine("c.Equals(d) devuelve: {0}", c.Equals(d));

Ahora, el resultado sí sería True.

El operador is devuelve un valor boolean al comparar si un objeto (de un tipo referencia) es compatible con una clase. Por ejemplo:

Circunferencia c=new Circunferencia();
Console.WriteLine("El resultado de c is Circunferencia es: {0}", (c is Circunferencia));

La salida de estas dos líneas sería :

El resultado de c is Circunferencia es: True

Decir que el objeto es compatible con la clase significa que ese objeto se puede convertir a esa clase. Si el objeto contiene una referencia nula (null) o si no es compatible con la clase, el operador is retornará false.

Los operadores & (and a nivel de bits), | (or a nivel de bits) y ^ (xor -o exclusivo- a nivel de bits) hacen una comparación binaria (bit a bit) de dos números devolviendo el resultado de dicha comparación como otro número. Un ejemplo:

int i=10;
int b=7;
int res;
res = i & b;
Console.WriteLine("{0} & {1} retorna: Decimal: {2} Hexadecimal: {3:X}", i, b, res, res);
res = (i | b);
Console.WriteLine("{0} | {1} retorna: Decimal: {2} Hexadecimal: {3:X}", i, b, res, res);
res = (i ^ b);
Console.WriteLine("{0} ^ {1} retorna: Decimal: {2} Hexadecimal: {3:X}", i, b, res, res);

La salida en pantalla sería esta:

10 & 7 retorna: Decimal: 2  Hexadecimal: 2
10 | 7 retorna: Decimal: 15  Hexadecimal: F
10 ^ 7 retorna: Decimal: 13  Hexadecimal: D

En ninguno de los casos se ha hecho una suma normal de los dos números. Veamos estas tres operaciones. Las operaciones se hacen bit a bit. Marcamos en negrita los valores que provocan que el bit resultante en esa posición valga 1:

Operación: i & b
Variable
Valor dec.
Valor hex.
Valor binario
i
10
A
0000 0000 0000 0000 0000 0000 0000 1010
b
7
7
0000 0000 0000 0000 0000 0000 0000 0111
Resultado
2
2
0000 0000 0000 0000 0000 0000 0000 0010

Operación: i | b
Variable
Valor dec.
Valor hex.
Valor binario
i
10
A
0000 0000 0000 0000 0000 0000 0000 1010
b
7
7
0000 0000 0000 0000 0000 0000 0000 0111
Resultado
15
F
0000 0000 0000 0000 0000 0000 0000 1111

Operación: i ^ b
Variable
Valor dec.
Valor hex.
Valor binario
i
10
A
0000 0000 0000 0000 0000 0000 0000 1010
b
7
7
0000 0000 0000 0000 0000 0000 0000 0111
Resultado
13
D
0000 0000 0000 0000 0000 0000 0000 1101


Los operadores && (AND lógico) y || (OR lógico) comparan dos valores de tipo booleano y retornan como resultado otro valor de tipo booleano.

El operador ? : (question) evalúa una expresión como true o false y devuelve un valor que se le especifique en cada caso ( equivale más o menos a la función iif de Visual Basic). Ejemplo:

string mensaje = (num == 10) ? "El número es 10": "El número no es 10";

Delante del interrogante se pone la expresión que debe retornar un valor booleano. Si dicho valor es true, el operador retornará lo que esté detrás del interrogante, y si es false retornará lo que esté detrás de los dos puntos. No tiene por qué retornar siempre un string. Puede retornar un valor de cualquier tipo. Por lo tanto, si en este ejemplo num valiera 10, la cadena que se asignaría a mensaje sería "El número es 10", y en caso contrario se le asignaría "El número no es 10".


El operador de asignación (=) asigna lo que hay a la derecha del mismo en la variable que está a la izquierda. Por ejemplo, la expresión a = b asignaría a la variable a lo que valga la variable b. Ambas variables han de ser compatibles.  No se puede asignar un número a una cadena, y viceversa, tampoco es posible asignar una cadena a una variable de algún tipo numérico. 

int a=5;
int b=a;
b++;

Tras la ejecución de estas tres líneas, a valdría 5 y b valdría 6.  Si utilizamos variables de tipo referencia:

Circunferencia a=new Circunferencia();
a.Radio=4;
Circunferencia b=a;
b.Radio++;

Tanto a como b serán objetos de la clase circunferencia. Después de la ejecución de estas líneas, el radio de la circunferencia b valdrá 5 y el de la circunferencia a también 5. Lo que ha ocurrido aquí, es que al hacer la asignación Circunferencia b=a; no se ha reservado un nuevo espacio de memoria para la circunferencia b pues, al tratarse de una variable de tipo referencia, devuelve internamente un puntero a la dirección reservada para este objeto, es este puntero el que se ha asignado a la variable b, de modo que las variables a y b apuntan ambas al mismo espacio de memoria. Por lo tanto, cualquier modificación que se haga en el objeto usando alguna de estas variables quedará reflejado también en la otra variable, ya que, en realidad, son la misma cosa. Si quisiéramos objetos distintos, es decir, espacios de memoria distintos en la memoria, tendremos que instanciarlos por separado, y posteriormente asignar los valores a las propiedades una por una:

Circunferencia a=new Circunferencia();
a.Radio=4;
Circunferencia b=new Circunferencia();
b.Radio=a.Radio;
b.Radio++;

en este caso, el radio de la circunferencia a será 4 y el de la circunferencia b será 5. Hay que ser cuidadosos con esto,  pues si hubiéramos escrito el código de la siguiente forma:

Circunferencia a=new Circunferencia();
a.Radio=4;
Circunferencia b=new Circunferencia();
b=a;
b.Radio++;

Hubiera ocurrido algo parecido al primer caso. A pesar de haber instanciado el objeto b por su lado (reservando así un espacio de memoria para b distinto del de a), al asignar b=a, la referencia de b se destruye, asignándosele de nuevo la de a, de modo que ambas variables volverían a apuntar al mismo objeto dando el mismo resultado que en el primer caso, es decir, que el radio de ambas circunferencias sería 5.

El resto de operadores de asignación son operadores compuestos a partir de otro operador y el operador de asignación. Los operadores *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=

num *= 10; // Equivale a num = num * 10
num /= 10; // Equivale a num = num / 10
num %= 10; // Equivale a num = num % 10
num += 10; // Equivale a num = num + 10
num -= 10; // Equivale a num = num - 10
num <<= 10; // Equivale a num = num << 10
num >>= 10; // Equivale a num = num >> 10
num &= 10; // Equivale a num = num & 10
num ^= 10; // Equivale a num = num ^ 10
num |= 10; // Equivale a num = num | 10

La precedencia de operadores determina la prioridad con la que se ejecutan cuando hay varios de ellos en una misma expresión. Pues el resultado puede ser distinto en función del orden en que se ejecuten. Por ejemplo. La expresión 4 + 3 * 6 - 8 devolvería 14, ya que primero se hace la multiplicación y después las sumas y las restas. Si queremos modificar dicha precedencia hay que utilizar paréntesis: (4+3)*6-8 lo que devuelve 34, pues primero se ejecuta lo que hay dentro del paréntesis, después la multiplicación y, por último, la resta.

No hay comentarios:

Publicar un comentario