lunes, 25 de marzo de 2024
funciones sobrecargadas, referencias
/*Este texto no es mío. Viene de analisisyprogramacionoop.blogspot.com*/
Funciones sobrecargadasPaso de parámetros por referenciaReferencia como valor retornadoClases con miembros que son punterosArrays de objetos y de punteros a objetosPunteros a miembros de una clasePunteros como argumentos de funciones
Funciones sobrecargadas
Se dice que una función está sobrecargada cuando se declara una función previamente declarada con distinto número y/o tipo de parámetros pero con el mismo nombre. Es un concepto de programación orientada a objetos llamado polimorfismo. (Muchas declaraciones de una misma función).
Cada función suele tener un nombre que la distingue de las demás. Pero se pueden presentar casos en los que varias funciones ejecuten la misma tarea sobre objetos de diferentes tipos, y puede ser interesante que dichas funciones tengan el mismo nombre. Por ejemplo podemos definir una función Mover para cada tipo de movimiento de una pieza distinta de ajedrez y llamar a todas las funciones con el nombre mover pero distinguirlas por el número y/o el tipo de los parámetros sean diferentes.
Otro ejemplo: una función pot que calcule xy siendo x e y enteros o reales podemos hacer.
int pot (int, int);
double pot (double, double);
double pot (int, double);
double pot (double, int);
El compilador buscará la función adecuada en función del tipo de parámetros que le enviamos, en caso de no encontrar una función con los mismos tipos de argumentos, realizaría las conversiones permitidas sobre los parámetros actuales, buscando así una función. No se pueden declarar dos funciones que difieran solamente en el resultado de salida.
En el siguiente código se utilizan una u otra de estas funciones para calcular xy en función de los argumentos pasados en la llamada.
#include
#include "pot.h"
void main( )
{
double ad = 2.5 , bd = 1.5;
int ai = 3, bi = 2;
cout << pot ( ai, bi ) << endl;
cout << pot ( ad, bd ) << endl;
cout << pot (ai, bd ) << endl;
cout << pot (ad, bi) << endl;
}
Paso de parámetros por referencia
Cuando pasamos parámetros por valor se hace una copia de los parámetros en sus correspondientes parámetros formales. Esta operación se hace automáticamente cuando se llama a una función, esto impide que se modifiquen los parámetros actuales.
Pasar parámetros por referencia, significa que no se transfieren los valores sino las direcciones de las variables donde están contenidos esos valores, de este modo los parámetros actuales se verán modificados en el mismo valor que lo hagan sus correspondientes parámetros formales.
Cuando se llama a una función, los argumentos especificados en la llamada son pasados por valor, excepto si se trata de arrays, donde se pasan por referencia, ya que el nombre del array es un puntero a dicho array.
Por valor se pueden traspasar constantes, variables y expresiones, y utilizando la forma de pasar parámetros por referencia solamente se permite transferir las direcciones de variables de cualquier tipo, arrays y funciones.
Para pasar una variable por referencia, se pueden utilizar las dos formas explicadas a continuación:
1. Pasar la dirección del parámetro actual a su correspondiente parámetro el cual necesariamente tiene que ser un puntero.
void permutar(int *, int *);
void main( )
{
int a = 10, b = 15;
permutar(&a, &b);// se pasan las direcciones de //a y b
cout << "a = "<< a << "b = " << b << endl;
}
// utilizando punteros
void permutar( int *x, int *y)
{
int z = *x;
*x = *y;
*y = z;
}
2. Definir el parámetro como una referencia al parámetro actual que se quiere pasar por referencia. Para ello, es necesario anteponer el operador & al nombre del parámetro.
#include
void permutar (int &, int &);
void main( )
{
int a = 10, b = 15;
permutar (a, b);
cout << " a = " << a << " b= " << b << endl;
}
// Utilizando referencias x e y son referencias a sus
// correspondientes parámetros actuales a y b.
void permutar(int &x, int &y)
{
int z = x;
x = y;
y = z;
}
Los resultados son los mismos que en el programa anterior. En el programa con punteros, cualquier asignación que se haga a *x afecta a la variable a. En el programa con referencias, la referencia x también tiene esa propiedad, pero sin necesitar el operador de indirección *.
Hay que tener en cuenta que las referencias no pueden manipularse igual que los punteros. Con un puntero se puede distinguir el puntero de la variable apuntada utilizando el operador de indirección *. Es decir, x describe el puntero, mientras que *x describe el dato apuntado. Pero con una referencia sólo podemos referirnos al dato. Cualquier operación sobre la propia referencia se realizará sobre el dato referenciado pero nunca sobre la referencia.
Referencia como valor retornado
También es posible definir una función en la que el valor devuelto venga dado por una referencia a ese valor.
#include
int &fnx( );
int n;
void main( )
{
int c;
fnx( ) = 25; // n = 15
c = fnx( ); // c = 15
fnx( )++ ; //n = 16
fnx( ) = fnx( ) + 3; // n =19
}
//El tipo del resultado es una referencia a un int
int &fnx( )
{
// otro código
return n;
}
En este código, el valor retornado por la función fnx es una referencia inicializada con la variable global n. Lo que sucede es que fnx actúa como un nombre alternativo para n. Por tanto una llamada a la función puede aparecer a la izquierda o a la derecha de un operador de asignación.
Clases con miembros que son punteros
Los operadores new y delete se pueden utilizar en la definición de las funciones miembro de una clase. En el código mostrado debajo, nuestro objetivo es crear un array de enteros con un número cualquiera de elementos. En este caso es inapropiado definir como miembro privado de la clase CVector un array con un número fijo de elementos. Para ello es mejor definir un puntero, pVector, que apunte a un entero para después reservar dinámicamente la cantidad de memoria necesaria para el array.
class CVector
{
public:
CVector( ); //crea un array con un nº de elementos //por defecto
CVector(int ne); // crea un array de ne elementos
CVector(CVector &); // inicialización con un //vector v
CVector(int [ ], int ); // inicialización con un //array a
// otras funciones miembro
private:
int *pVector; // puntero base
int n_elementos; // numero de elementos
};
Una clase cuyos miembros sean de tipo puntero como es CVector puede tener problemas. En el caso de que en el programa anterior no se defina un constructor copia y que la función main sea como esta:
void main( )
{
CVector vector1(x,7);
fnEscribir(vector1); // escribe 1 2 3 4 5 6 7
// el siguiente bloque define vector2
{
CVector vector2 = vector1;
fnEscribir(vector2); //escribe1 2 3 4 5 6 7
}
// vector2 ha sido destruido
int *pi = new int[5];
for (int i = 0; i < 5; i++) pi[i] = i * 10;
// pi apunta a un array de valores 0 10 20 30 40
fnEscribir(vector1); //escribe 0 10 20 30 40 50
}
En el caso de que en el programa anterior no definamos un constructor copia, el constructor copia definido por defecto será de la forma.
CVector::CVector(CVector &v) // constructor copia
{
n_elementos = v.n_elementos;
pVector = v.pVector;
}
Ahora v.pVector es un puntero. El resultado de esta asignación es que pVector y v.pVector apuntan a la misma dirección de memoria. Por tanto cuando ejecutemos el código:
CVector vector2 = vector1; // llama al constructor copia
El miembro pVector de vector1 y el miembro pVector de vector2 apuntarán al mismo array de enteros. Por lo que cualquier modificación en uno de los objetos afecta a ambos.
Si salimos fuera del ámbito de vector2 se llama al destructor de la clase CVector y se liberan los dos bloques de memoria correspondientes a ese objeto ( su estructura interna y el array de objetos ). De este modo cualquier operación posterior con el objeto vector1 podría dar resultados inesperados, ya que pVector de vector1 apuntaba a la misma localización de memoria y ésta ha sido liberada. Por otra parte cuando se sale fuera del ámbito de vector1 se invoca de nuevo al destructor, y este intentará liberar de nuevo el bloque de memoria ocupado por el array de enteros, esto provocará resultados inesperados.
En la función main anterior al destruir el vector2, la siguiente instrucción asigna memoria para un array de enteros irreferenciado por pi. Se da la circunstancia de que la memoria asignada ha coincidido con la liberada. Pero en el vector1, además de no tener memoria asignada para su array de enteros, tampoco devuelve los resultados esperados. Esto pone de manifiesto que la memoria se libera, pero los punteros conservan sus valores.
Si nuestra intención no fuera esta, sino crear un nuevo array para cada objeto, el constructor debería ser como el que sigue a continuación.
CVector::CVector(CVector &v) //constructor copia
{
n_elementos = v.n_elementos;
pVector = new int [n_elementos];
for ( int i = 0; i*.
El operador .* liga su segundo operando a su primer operando, el cual debe ser un puntero a un miembro, el cual que debe ser un objeto. En este caso tenemos:
objeto.*puntero_a_miembro
Es decir.
CCalificacion Alumno;
float n = Alumno.*pmCalificacion;
Si el primer operando es un puntero a un objeto, entonces utilizaremos el operador ->*. La sintaxis será:
puntero_a_objeto ->* puntero_a_miembro
De este modo temenos.
CCalificacion *pAlumno;
//...
(pAlumno ->*pmfnSetCalificacion)(n);
La precedencia de los paréntesis ( ) es mayor que la de .* y ->*, por eso son necesarios.
No es posible definir un puntero a un miembro declarado como static, ya que este pertenece a la clase misma y no a un objeto en particular de la clase.
Punteros como argumentos de funciones
Un buen motivo para pasar un puntero a una función en vez de pasarle una copia del valor de la variable es ahorrar tiempo y espacio. Al pasar una copia de una variable grande, como un array o una estructura de datos grande, requiere una gran cantidad de espacio en la pila y de tiempo. Se puede mejorar el rendimiento de un programa pasando la dirección de esa variable y permitir que la función acceda a sus elementos a través de un puntero.
Otro motivo para usar punteros como parámetros es para que una función devuelva más de un sólo valor. Pues una función sólo puede devolver un valor por medio de su sentencia return. Pero si se utilizan parámetros de tipo puntero, se puede dar a la función acceso (y de este modo poder cambiar) los valores de una variable. Para ello, la función llamada tendrá que parar argumentos como una dirección en lugar de como un valor.
Suscribirse a:
Comentarios de la entrada (Atom)
No hay comentarios.:
Publicar un comentario