sábado, 9 de agosto de 2014

C++, Operadores, const, volatile, funciones inline

Declaración de Constantes, const y volatile.Funciones inline.Operadores New y Delete.

Declaración de constantes

Una constante es como una variable pero como su nombre indica, no varía si no que es constante. Se usa fundamentalmente para definir valores que serán constantes a lo largo de todo el programa pero en vez de poner el mismo valor una y otra vez a lo largo del código, lo definimos sólo una vez al principio, de este modo si hubiera que cambiar dicho valor no es necesario rastrear todo el código buscando donde cambiarlo, basta con cambiar el valor de la constante definida al principio. Esto es válido para cualquier lenguaje aunque los ejemplos se pongan para C++. 

C++, Operadores, const, volatile, funciones inline

Calificador const

Se puede anteponer el especificador const a la declaración de cualquier objeto o tipo de datos, con el fin de hacer que el objeto sea en lugar de una variable, una constante.
const int k = 12;
const int v[v] = { 1,2,3,4 };
A un objeto declarado como constante no se le puede asignar un valor más que al principio en su declaración. Al declararlo si una constante k ha sido declarada como constante  las siguientes sentencias darían error:
k = 100; //   error
k++;      //  error
Si declaramos un puntero precedido por const, esto hace que el objeto apuntado sea una constante.  Pero no pasa  lo mismo con el puntero.
const char *pc = "abcd";
pc[0] = 'z' ;  // error
pc = "efg";  // correcto
Si en lugar de esto queremos declarar un puntero como una constante, procederíamos así.
char *const pc = "abcd";
pc[0] = 'z';  // correcto
pc = " efg";  // error
Para hacer que tanto el puntero como el objeto apuntado sean constantes, procederemos como se indica a continuación:
const char *const pc = "abcd";
pc[0] = 'z';  //error
pc = "efg"; // error

Una variable global declarada como const se considera static.

Calificador volatile

Si anteponemos el calificador volatile a la declaración de un objeto, hacemos que dicho objeto pueda ser modificado por otros procesos diferentes al programa actual. Una utilidad del calificador volatile es proveer acceso a las posiciones de memoria  utilizadas por procesos asíncronos, tal como manejadores de interrupciones. Los calificadores const y volatile pueden utilizarse conjuntamente o individualmente.
volatile int v;
Los objetos declarados volátiles no se pueden utilizar en optimizaciones porque sus valores pueden cambiar en cualquier momento. Cada vez que accedemos a un objeto volatile, el sistema lee su valor actual. Además en una asignación, su valor es escrito inmediatamente. Cuando se declara explícitamente un objeto volatile, es un error que la función miembro invocada por este objeto no sea también volatile.

Funciones en línea

C++ provee el especificador de función llamado inline que cuando precede al nombre de la función hace que el compilador reemplace cualquier llamada a la función por el cuerpo actual de la función. Para utilizar esta funcionalidad, la función debe estar definida antes de ser invocada, en caso contrario, el compilador no la expandirá. Debido a esta restricción, las funciones inline son normalmente definidas en ficheros de cabecera .h.
Una función como inline solamente es una recomendación para el compilador. Es decir, el compilador puede tomar la iniciativa de expandir o no la función, por ejemplo, por ser demasiado larga.
Las funciones inline son similares a las macros declaradas con #define. Pero es mejor utilizar funciones inline que macros, ya que sus parámetros son chequeados automáticamente y no presentan los problemas de las macros parametrizadas.
//Comparación de una función en línea con una macro.
#include <iostream.h>
#define MENOR (X, Y) ((X) < (Y)) ? (X) : (Y)
inline int menor ( int x, int y)
{
return ((x < y) ? x : y);
}
void main()
{
int m, a = 20, b = 30;
m = MENOR ( a--, b--); // efecto colateral. El valor menor     //se decrementa dos veces
cout << "menor = " << m;
cout << " a = " << a << " b = " << b << endl;
a = 20; b = 30;
m = menor(a--, b--);
cout << " menor = " << m;
cout << "a = " << a << " b = " << b << endl;
}
Este ejemplo da lugar al siguiente resultado:
menor = 9    a = 8    b = 19
menor = 10   a = 9    b = 19
Las funciones inline se ejecutan más rápidamente, ya que se evitan las llamadas a cada una de estas funciones. Sin embargo, el abuso de inline en ocasiones puede no ser bueno. Ya que la modificación de una función inline obligaría a recompilar todos los módulos en los que ésta apareciera. Además el tamaño del código puede aumentar extraordinariamente. Se recomienda utilizar solamente el especificador inline cuando la función es muy pequeña o si se llama desde pocos lugares.

Operadores new y delete

Para manejar la memoria libre, C++ dispone de los operadores new y delete.

Operador new

El operador new asigna recursos de memoria para un objeto del tipo y tamaño especificados. En el caso de arrays, el tamaño se especifica explícitamente. En otros casos el tamaño viene definido por el propio tipo. El operador new devuelve un puntero a un objeto del tipo especificado que referencia el espacio reservado. Si no hay espacio para crear un objeto del tipo especificado, el operador new devuelve un puntero nulo ( 0 o NULL ).
int n_elementos;
cin >> n_elementos;
int *a = new int [n_elementos]; // creación //dinámica del array a
long *pl = new long; // asignación para un //long,  notación sin paréntesis
float *pf = new(float); //asignación para //un float, notación funcional
struct complejo
{
float re, im;
};
complejo *pc = new complejo; // asignación //para una  estructura
Si el tipo especificado es un array, el operador new devolverá un puntero al primer elemento de dicho array. Las dimensiones para crear un array pueden ser expresiones.
int cols = 10;
int  filas = 5;
double **puntArr = new double[filas * 2][cols];
En este ejemplo declara un puntero puntArr a un array de  dos dimensiones dadas por las por las expresiones filas * 2 y cols.

Cuando en la expresión que sigue al operador new para especificar el tipo aparece entre paréntesis, es obligatorio utilizar la versión con paréntesis del operador new (notación funcional).
int  (**puntArr fn)( );
puntArr fn = new(int (*[20] )( ));
En este ejemplo, new asigna memoria para un array de punteros de 20 elementos a funciones que no requieren argumentos y que devuelven un entero.
Si en la anterior instrucción no se utiliza la notación funcional, el compilador muestra un error. Es decir.
new int (*[30] )( ); // error
Es un error que se analiza como:    
( new int )(*[30] )( ); // error
El operador global ::new aparece originalmente declarado como void *operador new ( size_t tamaño_en_bytes_del_objeto);
Según esto en los ejemplos anteriores no se ha declarado explícitamente el tamaño en bytes. Esto se debe a que es el sistema el que realiza automáticamente el cálculo a partir del tipo definido.
El operador new asigna memoria de un área de memoria del  programa conocida como "free store".

Sobrecargando new

Una clase también puede gestionar por si misma el espacio de memoria libre que se necesita para la creación de objetos. Para ello es necesario definir los operadores new y delete como funciones miembro de la clase. El operador new llamará a  la  función operator new, y el operador delete llamará a la función operator delete. Cuando  se sobrecarguen estos operadores, la definición de estas funciones necesitan unos requerimientos que se comentan a continuación.
Asignar memoria: operator new
Si en un programa encontramos una instrucción como la siguiente, se invoca la función operator new para asignar un espacio del mismo tamaño especificado en bytes. Una llamada al operador new invoca una llamada al constructor de la clase.
int *pint = new int [TMAX];
Si no hubiera suficiente espacio de memoria para la asignación requerida, esta función  devolverá NULL.
Cuando el operador new se utiliza para asignar memoria para un objeto de una clase que no tiene definido el operador new, o para un array de cualquier tipo se invoca a la función global ::new. Este operador se define de la forma.
void *operator new (new_t  tamaño);
En el caso de que operador new se utilice para asignar memoria de un objeto de una clase con un operador new definido, entonces se invoca a la función operator new de esa clase:
nombre_clase::operador new;
La función operator new definida en una clase es necesario que sea una función miembro estática por lo tanto, no puede ser virtual, pues debe devolver void* y tener un primer argumento del tipo size_t. Este argumento recibirá automáticamente un valor en bytes igual al tamaño a asignar. El tipo size_t se encuentra definido en el fichero de cabecera stddef.h.
Al ejecutar new, el compilador busca una definición para este operador en la clase del objeto a crear; si no la encuentra ejecuta el operador global ::new

Operador delete

El operador delete destruye un objeto previamente creado por el operador new, de este modo libera la memoria que ocupaba dicho objeto. El operador delete sólo se puede aplicar a punteros si estos han sido retornados a través del operador new. Un puntero a una constante no se puede borrar. Sin embargo, si se puede aplicar delete a un puntero nulo ( un puntero con valor cero ).
int *p, *parray;
...
p = new int [n];
parray = new int [n];
...
delete p;
delete [ ] parray;

En las líneas de arriba, la primera intrucción delete libera la memoria apuntada por p y asignada a un entero. La segunda línea delete libera la memoria apuntada por parray y asignada a un array.
La sintaxis varía en el caso de tener que liberar la memoria ocupada por un array. En este caso, el compilador deberá invocar al destructor apropiado para cada elemento del array, desde parray[0] a parray[n-1].
El operador global ::delete aparece originalmente declarado de la forma siguiente:
void *operator delete(void *puntero_al_objeto);
void *operator delete(void *puntero_al_objeto, size_t  tamaño);
Se puede ver que no se ha declarado explícitamente el tamaño en bytes. Esto es así porque es el sistema el que realiza automáticamente el cálculo.

Liberar memoria: operator delete

Cuando en un programa se encuentra una sentencia como alguna de las siguientes, la función operator delete se invoca para liberar la memoria asignada por new. Una llamada al operator delete ejecuta una llamada del destructor de la clase.
delete c1;
delete []c3;
Se puede escribir la función miembro operator delete de dos formas:
void operator delete (void *);
void operator delete (void *, size_t );

La función operator delete puede ser de ámbito global, o de la clase. La función global, en caso de ser definida, toma un único argumento de tipo void*. La función operator delete con dos parámetros es muy útil cuando la utilizamos desde su clase base para eliminar un objeto de una clase derivada. Es necesario que una función operator delete definida en una clase tiene que ser una función miembro estática ( por lo tanto, no puede ser virtual ), esta debe tener como tipo del resultado void y un primer argumento de tipo void*. También se puede añadir un segundo argumento de tipo size_t, el cual debemos inicializar con el compilador con el tamaño en bytes del objeto direccionado por el primer argumento. El tipo size_t se encuentra definido en el archivo de cabecera stddef.h.


Este cursillo continúa aqui:




No hay comentarios:

Publicar un comentario