sábado, 4 de octubre de 2014

C++, punteros, templates (plantillas)

Operaciones con punteros.
Tipos genéricos. Plantillas. ( Templates ).
Typedef.
El operador sizeof.
Constructores. Objetos de la clase.

Operaciones con punteros


El lenguaje C++ ofrece cinco operaciones básicas con punteros.
1- Asignación: Consiste en asignar una dirección a un puntero. Normalmente se empleará el nombre de un array o con el operador dirección (&). En el siguiente ejemplo, se asigna a punt1 la dirección del inicio de un array llamado arr. En la variable punt2 colocamos la dirección del tercer y último elemento, arr[2].
static int arr[] = { 10,20,30};
int *punt1, *punt2;
punt1 = arr// asigna una dirección al puntero

punt2 = & arr[2];



Punteros en C

2- Valor guardado en una dirección: El operador * nos da el valor almacenado en la posición de memoria apuntada.
*punt1 =arr[1]; //obtenemos en *punt1 el valor que hay en arr[1].

3- Dirección de un puntero: Como cualquier variable, los punteros tienen una dirección y un valor. El operador & nos dice dónde está almacenado el puntero.
int direcc;
direcc = &punt1; // en direc metemos la dirección en la que se encuentra punt1.

4- Incremento de un puntero: Es posible realizar esta tarea como una suma normal o a través del operador de incremento. Si incrementamos un puntero, éste apuntará al siguiente elemento del array. De este modo, punt1++ incrementa el valor numérico de punt1 en 2 ( un int ocupa 2 bytes ) y hace que punt1 señale a arr[1]. Hay que tener en cuenta que la dirección de punt1 sigue siendo la misma. Pues una variable no se mueve de su sitio sólo por el hecho de cambiar su valor.
También es posible decrementar el puntero pero hay que tener en cuenta algunos puntos peligrosos. El ordenador no le sigue la pista a un puntero, por tanto no puede saber si está apuntando dentro de un array o producirá un error. La operación ++punt1 hace que punt1 se mueva otros dos bytes.
Otra cuestión a tener en cuenta es que sólo se pueden incrementar variables, y no constantes, así que una constante que sea puntero no puede cambiar su valor ( esto puede parecer una perogrullada, pero ++arr es una expresión muy atractiva).  Sin embargo, sí puede utilizarse en esta suma normal.
Válido                                               No válido
punt1++;               arr++;
x++;                   3++;
punt2 = punt1 + 4;     punt2 = arr++;
punt2 = arr + 1;       x = y + 3++;

5- Resta: Dos punteros pueden restarse. Se utilizará con dos punteros que señalen elementos dentro de un mismo array, para saber cuántos elementos les separan. El resultado no son bytes, sino las mismas unidades del tamaño del array.

Punteros y arrays


En el siguiente ejemplo veremos cómo se relacionan los puntero y los arrays, vamos a ver primero la notación de un array y luego la notación de un puntero.
Versión de array:
// imprime los valores del array
main( )
{
static int nums[ ] = { 90, 70, 69, 58 };
int indice;
for (indice = 0; indice < 4; indice ++)
cout << nums[indice];
}
Esta forma de programar  es más directa, pues utiliza la notación de array para acceder a los elementos del array mediante la expresión nums[indice]. 
          Versión de punteros:
// utiliza punteros para imprimir los valores del array
main( )
{
static int nums[ ] =  { 90, 70, 69, 58 };
int indice;
for (indice = 0; indice < 5; indice ++)
cout << *(nums + indice);
}
Esta versión es idéntica a la primera, excepto por la expresión *(nums + indice). Cuyo  efecto es exactamente el mismo que el de nums[indice] en el programa anterior, es decir:  *( array + indice) es lo mismo que array[indice]
Una tira de caracteres es una cadena de bytes, finalizada por el carácter especial '\0';
En este caso para inicializar la cadena, emplearemos:
char *saludo = "Hola ";
En vez de
static char saludo[ ] = "Hola ";
Estos dos formatos parecen que tienen el mismo efecto en el programa. Pero hay una pequeña diferencia. La segunda línea reserva espacio para un array con el suficiente número de bytes ( En este caso 10 ) para almacenar la palabra más un byte correspondiente al carácter '\0' ( nulo ). La dirección del primer carácter del array se obtiene dando el nombre del array, saludo. En la versión puntero (la primera línea)  se reserva espacio para el array de la misma forma, pero también se reserva espacio para una variable de puntero,  este puntero es el que recibe el nombre saludo.

Tipos genéricos. Plantillas. ( Templates )


Los tipos genéricos o tipos parametrizados, también denominados plantillas, permiten construir una familia de funciones o clases relacionadas. Las funciones o clases se diseñan para trabajar con un tipo específico de datos. Esta característica tiene poco interés para el diseñador de la función o de la clase, pero tiene importancia para el usuario de la función o clase, ya que le permitiría elegir el tipo de datos que necesite en cada momento.
Las funciones genéricas o plantillas de función admiten argumentos de cualquier tipo. Es decir podemos definir una plantilla que admita el tipo como un parámetro más.
#include <iostream.h>
template < class Plantilla >
Plantilla min(Plantilla a, Plantilla b)
{
return ( a < b ) ? a : b;
}
void main( )
{
int m = 11, n = 28;
float x = 17.5, y = 36.2;
cout << min ( m, n) << endl;
cout << min (x, y) << endl;
}
Una plantilla no es una definición de función, sino un patrón a partir del cual el compilador puede generar una función. El prefijo template < class Plantilla > indicará al compilador que Plantilla se proporcionará en el momento de la expansión de la plantilla a una definición real de la función, en ese momento un tipo definido sustituirá a Plantilla.
Si necesitamos que a sea de un tipo cualquiera y b de otro tipo cualquiera. Entonces escribimos:
#include < iostream.h>
template < class Plantilla1, class Plantilla2 >
Plantilla2 min (Plantilla 2 a, Plantilla1 b)
{
return ( a < b ) ? a : b;
}
void main( )
{
int m = 11, n = 28;
float x = 17.5, y = 36.2;
cout << min ( m, n) << endl;
cout << min ( x, n) << endl;
cout << min ( m, y) << endl;
cout << min ( x, y) << endl;
}
El prefijo template < class Plantilla1, class Plantilla2 > indica que la plantilla en el momento de la expansión sustituirá Plantilla1 y Plantilla2 por los argumentos  correspondientes a los tipos en la llamada a min, El compilador expandirá la plantilla automáticamente cuando sea necesario. Por  ejemplo, si detecta una llamada min con dos argumentos enteros, automáticamente amplía una versión int de la plantilla.

Clases genéricas


Aunque las plantillas son útiles, son aún más importantes las plantillas de clase. Las clases genéricas o plantillas de clase permitirán definir un patrón para definiciones de clases. Las clases contenedor o clases de contenido; esto es, una clase que contiene objetos del mismo tipo (listas, arrays, etc). Son buenos ejemplos de plantillas. Si tenemos un vector de enteros o de cualquier tipo, las operaciones básicas que podremos realizar con él, serán las mismas (insertar, borrar, leer, etc.).  Lo que permitirá al usuario elegir el tipo de elementos del array, para ellos, definiremos una clase genérica.
Una plantilla de clase se diferencia de una plantilla de función por que se expande, dando lugar a una definición completa de clase, cuyos tipos internos son proporcionados por el usuario.
Las funciones que son miembros de la plantilla de clase, también son plantillas de función, y por tanto, también tendrán que expandirse cada vez que se expanda la clase asociada.
La solución a este problema, y por tanto la forma correcta de utilizar las plantillas, consiste en reunir en una sola clase base común todo el código que podría ser idéntico en las diversas ampliaciones de la plantilla, y derivar de ella la plantilla de clase.

Typedef


La palabra clave typedef crea un tipo al que se le asigna un nombre arbitrario dado por el programador. Se parece a #define, pero tiene tres diferencias:

1. typedef está limitado a asignar nombres simbólicos a tipos de datos únicamente, al contrario de como hace #define
2. La función typedef se ejecuta desde el compilador y desde el  preprocesador.
3. Dentro de sus límites, typedef es más flexible que #define.
typedef float real;  ó typedef float REAL;
permite declaraciones del tipo:
real x;  ó  REAL *numero;
También es posible el uso de typedef con estructuras:
typedef struct DAT_PER{
char nombre[25];
union datos_conf{
char matricula[20];
int cuenta;
char nif;
} conf;
DAT_PER *sig;
}personal[MAX_NUM_PERSONAS];
lo cual permite declaraciones más intelegibles:
DAT_PER *sig;  o DAT_PER personal[MAX_NUM_PERSONAS]

Operador sizeof


El operador sizeof no se representa mediante un símbolo como el resto de operadores. Es un operador unario, no una función. Devuelve el tamaño en bytes de un objeto determinado. La forma de  utilizar sizeof es:
sizeof expresión;
Donde expresión es el nombre de una variable o de un tipo de datos.
El objetivo de sizeof es determinar el tamaño de varios objetos dependientes de la máquina. Es muy útil para escribir programas que puedan ejecutarse en varios tipos de ordenadores. Pues estos pueden tener tamaños de almacenamiento diferentes.
//muestra el tamaño en bytes de varios tipos de datos
main( )
{
cout << sizeof(char);
cout << sizeof(short);
cout << sizeof(int);
cout << sizeof(long);
cout << sizeof(float);
cout << sizeof(double);
}
Este ejemplo devuelve la memoria se se usa para cada dato de tipo primario.
Si se ejecuta en un sistema de 16 bits. El programa le dirá que un entero ocupa 4 bytes. En uno de 64 bits le dirá que ocupa 8 bytes. Un programa que necesite este tipo de información  se puede obtener fácilmente utilizando sizeof.

Constructores. Objetos de la clase


En primer lugar definimos una clase:
class Punto{
int x; // miembros dato
int y;
char ch;
public:
void mostrar( );
void ocultar( );
};
void Punto::mostrar( )
{
gotoxy( x, y);
cout << ch;
}
void Punto::ocultar( )
{
gotoxy( x, y);
cout << " ";
}
Una vez definida la clase, para usarla se ha de definir un objeto. Se define una variable de la clase Punto, exactamente igual que se define una variable de un tipo predefinido ( int, float, etc,) o cualquier otro tipo definido por el usuario.
Los objetos usan la misma notación que cualquier tipo de variable, y su alcance se extiende desde la línea donde se ha declarado hasta el final del bloque.
Para crear un objeto de una clase se llama a una función denominada constructor. El constructor se llama de forma automática cuando se crea un objeto. El constructor tiene el mismo nombre que la clase. Lo específico del constructor es que no tiene tipo de retorno.
                        Punto::Punto(argumentos)
-Declaración del constructor


class Punto {
//...
public:
Punto ( char ch1, int x1, int y1);
      };
- El constructor inicializa los miembros dato en la definición del constructor.
Punto::Punto ( char ch1, int x1, int y1)
{
ch = ch1;
x = x1;
y = y1;
}
-Llamamos al constructor. Para crear un objeto pt1 de la clase Punto.
Punto pt1('*',20,33);
En el objeto, el miembro dato ch guardará el carácter *, el miembro dato x, el número entero 20, y el miembro y, el entero 33.
-Se llama a las funciones miembro desde el objeto pt1
pt1.mostrar( );
pt1.ocultar( );
Es posible tener más de un constructor, A continuación definimos  uno que fije el carácter pero que permita cambiar las coordenadas del punto.
-Declaración de constructor
class Punto{
//...
public:
Punto( int x1, int y1);
//...
};
-Definición del constructor: se fija el carácter, y se le pasan las coordenadas del punto
Punto::Punto( int x1, int y1 )
{
x = x1;
y = y1;
ch = '*';
}

Se llama al constructor para crear un objeto pt2
Punto pt2( 20, 33 );
En el objeto, el miembro dato ch almacenará el carácter *, el miembro dato x, el número entero 20, y el miembro y el entero 33.
-Se llama a las funciones miembro desde el objeto pt2
pt2.mostrar( );
pt2.ocultar( );
Una clase puede tener más de un constructor. Si un constructor no tiene argumentos, es el constructor por defecto.
-Declaración del constructor por defecto de la clase

class Punto{
//...
public:
Punto( );
//...
};
-Definición del constructor por defecto: los miembros dato se inicializan en el bloque de dicho constructor
Punto::Punto( )
{
ch = '*';
x = 20;
y = 33;
}
Para llamar al constructor por defecto escribimos:
Punto def;
Si escribimos
Punto def( ); // error
Da un error.
Para llamar a las funciones miembro desde el objeto def.
def.mostrar( );
def.ocultar( );

No hay comentarios:

Publicar un comentario