Filminas 03

Filmina 01

 

 

Conversión de tipos

Quizás te hayas preguntado qué pasa cuando escribimos expresiones numéricas en las que todos los operandos no son del mismo tipo. Por ejemplo:

char n;  
int a, b, c, d;   
float r, s, t;   
...   
a = 10;  
b = 100;   
r = 1000;   
c = a + b;   
s = r + a;   
d = r + b;   
d = n + a + r;   
t = r + a - s + c; 
... 

En estos casos, cuando los operandos de cada operación binaria asociados a un operador son de distinto tipo, se convierten a un tipo común. Existen reglas que rigen estas conversiones, y aunque pueden cambiar ligeramente de un compilador a otro, en general serán más o menos así:

  1. Cualquier tipo entero pequeño como char o short es convertido a int o unsigned int. En este punto cualquier pareja de operandos será int (con o sin signo), long, long long, double, float o long double.
  2. Si algún operando es de tipo long double, el otro se convertirá a long double.
  3. Si algún operando es de tipo double, el otro se convertirá a double.
  4. Si algún operando es de tipo float, el otro se convertirá a float.
  5. Si algún operando es de tipo unsigned long long, el otro se convertirá a unsigned long long.
  6. Si algún operando es de tipo long long, el otro se convertirá a long long.
  7. Si algún operando es de tipo unsigned long, el otro se convertirá a unsigned long.
  8. Si algún operando es de tipo long, el otro se convertirá a long.
  9. Si algún operando es de tipo unsigned int, el otro se convertirá a unsigned int.
  10. En este caso ambos operandos son int.

 

Filmina 02

 

 

"Casting", conversiones explícitas de tipo:

Para eludir estos avisos del compilador se usa el "casting", o conversión explícita.

En general, el uso de "casting" es obligatorio cuando se hacen asignaciones, o cuando se pasan argumentos a funciones con pérdida de precisión. En el caso de los argumentos pasados a funciones es también muy recomendable aunque no haya pérdida de precisión. Eliminar los avisos del compilador demostrará que sabemos lo que hacemos con nuestras variables, aún cuando estemos haciendo conversiones de tipo extrañas.

En C++ hay varios tipos diferentes de "casting", pero de momento veremos sólo el que existe también en C.

Un "casting" tiene una de las siguientes formas:

(<nombre de tipo>)<expresión> 

ó

<nombre de tipo>(<expresión>) 

Filmina 03

 

 

Algoritmos de ordenación, método de la burbuja:

Una operación que se hace muy a menudo con los arrays, sobre todo con los de una dimensión, es ordenar sus elementos.

Dedicaremos más capítulos a algoritmos de ordenación, pero ahora veremos uno de los más usados, aunque no de los más eficaces, se trata del método de la burbuja.

Consiste en recorrer la lista de valores a ordenar y compararlos dos a dos. Si los elementos están bien ordenados, pasamos al siguiente par, si no lo están los intercambiamos, y pasamos al siguiente, hasta llegar al final de la lista. El proceso completo se repite hasta que la lista está ordenada.

Lo veremos mejor con un ejemplo:

Ordenar la siguiente lista de menor a mayor:

15, 3, 8, 6, 18, 1.

Empezamos comparando 15 y 3. Como están mal ordenados los intercambiamos, la lista quedará:

3, 15, 8, 6, 18, 1

Tomamos el siguiente par de valores: 15 y 8, y volvemos a intercambiarlos, y seguimos el proceso...

Cuando lleguemos al final la lista estará así:

3, 8, 6, 15, 1, 18

Empezamos la segunda pasada, pero ahora no es necesario recorrer toda la lista. Si observas verás que el último elemento está bien ordenado, siempre será el mayor, por lo tanto no será necesario incluirlo en la segunda pasada. Después de la segunda pasada la lista quedará:

3, 6, 8, 1, 15, 18

Ahora es el 15 el que ocupa su posición final, la penúltima, por lo tanto no será necesario que entre en las comparaciones para la siguiente pasada. Las sucesivas pasadas dejarán la lista así:

3ª 3, 6, 1, 8, 15, 18

4ª 3, 1, 6, 8, 15, 18

5ª 1, 3, 6, 8, 15, 18

Filmina 04

 

 

Punteros 1

Los punteros proporcionan la mayor parte de la potencia al C y C++, y marcan la principal diferencia con otros lenguajes de programación.

Una buena comprensión y un buen dominio de los punteros pondrá en tus manos una herramienta de gran potencia. Un conocimiento mediocre o incompleto te impedirá desarrollar programas eficaces.

Por eso le dedicaremos mucha atención y mucho espacio a los punteros. Es muy importante comprender bien cómo funcionan y cómo se usan.

Para entender qué es un puntero veremos primero cómo se almacenan los datos en un ordenador.

La memoria de un ordenador está compuesta por unidades básicas llamadas bits. Cada bit sólo puede tomar dos valores, normalmente denominados alto y bajo, ó 1 y 0. Pero trabajar con bits no es práctico, y por eso se agrupan.

Cada grupo de 8 bits forma un byte u octeto. En realidad el microprocesador, y por lo tanto nuestro programa, sólo puede manejar directamente bytes o grupos de dos o cuatro bytes. Para acceder a los bits hay que acceder antes a los bytes. Y aquí llegamos al quid, cada byte tiene una dirección, llamada normalmente dirección de memoria.

La unidad de información básica es la palabra, dependiendo del tipo de microprocesador una palabra puede estar compuesta por dos, cuatro, ocho o dieciséis bytes. Hablaremos en estos casos de plataformas de 16, 32, 64 ó 128 bits. Se habla indistintamente de direcciones de memoria, aunque las palabras sean de distinta longitud. Cada dirección de memoria contiene siempre un byte. Lo que sucederá cuando las palabras sean de 32 bits es que accederemos a posiciones de memoria que serán múltiplos de 4.

Todo esto sucede en el interior de la máquina, y nos importa más bien poco. Podemos saber qué tipo de plataforma estamos usando averiguando el tamaño del tipo int, y para ello hay que usar el operador "sizeof()", por ejemplo:

cout << "Plataforma de " << 8*sizeof(int) << " bits";

Ahora veremos cómo funcionan los punteros. Un puntero es un tipo especial de variable que contiene, ni más ni menos que, una dirección de memoria. Por supuesto, a partir de esa dirección de memoria puede haber cualquier tipo de objeto: un char, un int, un float, un array, una estructura, una función u otro puntero. Seremos nosotros los responsables de decidir ese contenido.

Intentemos ver con mayor claridad el funcionamiento de los punteros. Podemos considerar la memoria del ordenador como un gran array, de modo que podemos acceder a cada celda de memoria a través de un índice. Podemos considerar que la primera posición del array es la 0 celda[0].

Si usamos una variable para almacenar el índice, por ejemplo, indice=0, entonces celda[0] == celda[indice]. Prescindiendo de la notación de los arrays, el índice se comporta exactamente igual que un puntero.

Filmina 05

 

 

Declaración de punteros:

Los punteros se declaran igual que el resto de las variables, pero precediendo el identificador con el operador de indirección, (*), que leeremos como "puntero a".

Sintaxis:

<tipo> *<identificador>; 

Ejemplos:

int *entero;  char *carácter;  struct stPunto *punto;

Los punteros siempre apuntan a un objeto de un tipo determinado, en el ejemplo, "entero" siempre apuntará a un objeto de tipo "int".

La forma:

<tipo>* <identificador>; 

con el (*) junto al tipo, en lugar de junto al identificador de variable, también está permitida.

Veamos algunos matices. Tomemos el primer ejemplo:

int *entero;

equivale a:

int* entero;

Debes tener muy claro que "entero" es una variable del tipo "puntero a int", que "*entero" NO es una variable de tipo "int".

Como pasa con todas las variables en C++, cuando se declaran sólo se reserva espacio para almacenarlas, pero no se asigna ningún valor inicial, el contenido de la variable permanecerá sin cambios, de modo que el valor inicial del puntero será aleatorio e indeterminado. Debemos suponer que contiene una dirección no válida.

Si "entero" apunta a una variable de tipo "int", "*entero" será el contenido de esa variable, pero no olvides que "*entero" es un operador aplicado a una variable de tipo "puntero a int", es decir "*entero" es una expresión, no una variable.

Filmina 06

 

 

Obtener punteros a variables:

Para averiguar la dirección de memoria de cualquier variable usaremos el operador de dirección (&), que leeremos como "dirección de".

Por supuesto, los tipos tienen que ser "compatibles", no podemos almacenar la dirección de una variable de tipo "char" en un puntero de tipo "int".

Por ejemplo:

int A;  int *pA;    pA = &A;

Según este ejemplo, pA es un puntero a int que apunta a la dirección donde se almacena el valor del entero A.

Filmina 07

 

 

Diferencia entre punteros y variables: 

Declarar un puntero no creará un objeto. Por ejemplo: int *entero; no crea un objeto de tipo "int" en memoria, sólo crea una variable que puede contener una dirección de memoria. Se puede decir que existe físicamente la variable "entero", y también que esta variable puede contener la dirección de un objeto de tipo "int". Lo veremos mejor con otro ejemplo:

int A, B;   
int *entero;   
...   
B = 213; /* B vale 213 */   
entero = &A;   /* entero apunta a la          
dirección de la variable A */   
*entero = 103; /* equivale a la línea A = 103; */   
B = *entero;   /* equivale a B = A; */  
 ...

En este ejemplo vemos que "entero" puede apuntar a cualquier variable de tipo "int", y que podemos hacer referencia al contenido de dichas variables usando el operador de indirección (*).

Como todas las variables, los punteros también contienen "basura" cuando son declaradas. Es costumbre dar valores iniciales nulos a los punteros que no apuntan a ningún sitio concreto:

entero = NULL;   caracter = NULL; 

NULL es una constante, que está definida como cero en varios ficheros de cabecera, como "cstdio" o "iostream", y normalmente vale 0L.

Filmina 08

 

 

Correspondencia entre arrays y punteros:

Existe una equivalencia casi total entre arrays y punteros. Cuando declaramos un array estamos haciendo varias cosas a la vez:

  • Declaramos un puntero del mismo tipo que los elementos del array, y que apunta al primer elemento del array.
  • Reservamos memoria para todos los elementos del array. Los elementos de un array se almacenan internamente en el ordenador en posiciones consecutivas de la memoria.

La principal diferencia entre un array y un puntero es que el nombre de un array es un puntero constante, no podemos hacer que apunte a otra dirección de memoria. Además, el compilador asocia una zona de memoria para los elementos del array, cosa que no hace para los elementos apuntados por un puntero auténtico.

Ejemplo:

int vector[10];   
int *puntero;     
puntero = vector; /* Equivale a puntero = &vector[0];     
esto se lee como "dirección del primer de vector" */   
*puntero++; /* Equivale a vector[0]++; */   
puntero++; /* puntero equivale a &vector[1] */ 

¿Qué hace cada una de estas instrucciones?:

La primera incrementa el contenido de la memoria apuntada por "puntero", que es vector[0].

La segunda incrementa el puntero, esto significa que apuntará a la posición de memoria del siguiente "int", pero no a la siguiente posición de memoria. El puntero no se incrementará en una unidad, como tal vez sería lógico esperar, sino en la longitud de un "int".

Análogamente la operación:

puntero = puntero + 7; 

No incrementará la dirección de memoria almacenada en "puntero" en siete posiciones, sino en 7*sizeof(int).

Otro ejemplo:

struct stComplejo {      
float real, imaginario;   
} Complejo[10];      


stComplejo *p;   
p = Complejo; /* Equivale a p = &Complejo[0]; */   
p++; /* p  == &Complejo[1] */ 

En este caso, al incrementar p avanzaremos las posiciones de memoria necesarias para apuntar al siguiente complejo del array "Complejo". Es decir avanzaremos sizeof(stComplejo) bytes.

 

 

 

 

 

 

Hosted by www.Geocities.ws

1