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];
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