15 Funciones II: Parámetros por valor y por referencia

Dediquemos algo más de tiempo a las funciones.

Hasta ahora siempre hemos declarado los parámetros de nuestras funciones del mismo modo. Sin embargo, éste no es el único modo que existe para pasar parámetros.

La forma en que hemos declarado y pasado los parámetros de las funciones hasta ahora es la que normalmente se conoce como "por valor". Esto quiere decir que cuando el control pasa a la función, los valores de los parámetros en la llamada se copian a "objetos" locales de la función, estos "objetos" son de hecho los propios parámetros.

Lo veremos mucho mejor con un ejemplo:

#include <iostream> 
using namespace std;
 
int funcion(int n, int m);
 
int main() { 
   int a, b; 
   a = 10; 
   b = 20;
 
   cout << "a,b ->" << a << ", " << b << endl; 
   cout << "funcion(a,b) ->" 
        << funcion(a, b) << endl;
   cout << "a,b ->" << a << ", " << b << endl; 
   cout << "funcion(10,20) ->" 
        << funcion(10, 20) << endl; 

   return 0; 
}
 
int funcion(int n, int m) { 
   n = n + 2; 
   m = m - 5; 
   return n+m; 
}

Bien, ¿qué es lo que pasa en este ejemplo?

Empezamos haciendo a = 10 y b = 20, después llamamos a la función "funcion" con las objetos a y b como parámetros. Dentro de "funcion" esos parámetros se llaman n y m, y sus valores son modificados. Sin embargo al retornar a main, a y b conservan sus valores originales. ¿Por qué?

La respuesta es que lo que pasamos no son los objetos a y b, sino que copiamos sus valores a los objetos n y m.

Piensa, por ejemplo, en lo que pasa cuando llamamos a la función con parámetros constantes, es lo que pasa en la segunda llamada a "funcion". Los valores de los parámetros no pueden cambiar al retornar de "funcion", ya que esos valores son constantes.

Si los parámetros por valor no funcionasen así, no sería posible llamar a una función con valores constantes o literales.

Referencias a objetos

Las referencias sirven para definir "alias" o nombres alternativos para un mismo objeto. Para ello se usa el operador de referencia (&).

Sintaxis:

<tipo> &<alias> = <objeto de referencia>
<tipo> &<alias>

La primera forma es la que se usa para declarar objetos que son referencias, la asignación es obligatoria ya que no pueden definirse referencias indeterminadas.

La segunda forma es la que se usa para definir parámetros por referencia en funciones, en estos casos, las asignaciones son implícitas.

Ejemplo:

#include <iostream>
using namespace std;
 
int main() { 
   int a; 
   int &r = a;
 
   a = 10; 
   cout << r << endl; 
   
   return 0; 
}

En este ejemplo los identificadores a y r se refieren al mismo objeto, cualquier cambio en una de ellos se produce en el otro, ya que son, de hecho, el mismo objeto.

El compilador mantiene una tabla en la que se hace corresponder una dirección de memoria para cada identificador de objeto. A cada nuevo objeto declarado se le reserva un espacio de memoria y se almacena su dirección. En el caso de las referencias, se omite ese paso, y se asigna la dirección de otro objeto que ya existía previamente.

De ese modo, podemos tener varios identificadores que hacen referencia al mismo objeto, pero sin usar punteros.

Pasando parámetros por referencia

Si queremos que los cambios realizados en los parámetros dentro de la función se conserven al retornar de la llamada, deberemos pasarlos por referencia. Esto se hace declarando los parámetros de la función como referencias a objetos. Por ejemplo:

#include <iostream>
using namespace std;
 
int funcion(int &n, int &m);
 
int main() { 
   int a, b;
 
   a = 10; b = 20; 
   cout << "a,b ->" << a << ", " << b << endl;
   cout << "funcion(a,b) ->" << funcion(a, b) << endl; 
   cout << "a,b ->" << a << ", " << b << endl; 
   /* cout << "funcion(10,20) ->" 
           << funcion(10, 20) << endl; // (1)
   es ilegal pasar constantes como parámetros cuando 
   estos son referencias */ 
   
   return 0; 
}
 
int funcion(int &n, int &m) {
   n = n + 2; 
   m = m - 5; 
   return n+m; 
}

En este caso, los objetos "a" y "b" tendrán valores distintos después de llamar a la función. Cualquier cambio de valor que realicemos en los parámetros dentro de la función, se hará también en los objetos referenciadas.

Esto quiere decir que no podremos llamar a la función con parámetros constantes, como se indica en (1), ya que aunque es posible definir referencias a constantes, en este ejemplo, la función tiene como parámetros referencias a objetos variables.

Y si bien es posible hacer un casting implícito de un objeto variable a uno constante, no es posible hacerlo en el sentido inverso. Un objeto constante no puede tratarse como objeto variable.

Comentarios de los usuarios (15)

Luis Fernandez
2014-12-03 13:55:38

Gracias por el curso, es el mejor

Tengo serios problemas con el paso de apuntadores a funciones, no en los simples sino en los complicados, y no encuentro la respuesta en el curso completo.

Tengo claro que cuando son variables simples se pasan por valor, si las quieres pasar por referencia haces lo de arriba, pero en cuento es un array de caracteres o dimensión todo cambia, pasa su dirección, pero no tengo claro que se usa en los parámetros de la función y que se usa para llamarla.

void funcion(int &a, char &b){
...
}
int main(){
   int c[100];
   char d[100];
   funcion (c,d);
}

Si estoy pasando la dirección en funcion (c,d); debería estar pasando por referencia, no se usan los [], ademas si son 2 dimensiones, si hay que usarlos?, ademas con valor solo en la segunda dimensión?, algo así función (c[][M],d[][G]);(si son de 2 dimeensiones

Luis Fernandez
2014-12-03 14:04:20

También tengo dudas cuando en la función usas el * para los parámetros en vez de &.

void funcion(int *a, char *b){
...
}
int main(){
   int c[100];
   char d[100];
   funcion (&c,&d);
}

acá estoy mandando la dirección de c y d

y la función recibe apuntadores, es equivalente?.

Otra pregunta, las estructuras se pasan por valor?, si es una dimensión de estructuras, pasa por referencia? varias dimensiones?, como se usan, es necesario ampliar el tema de las llamadas a funciones (como se las llama según coloques los parámetros) y sus equivalencias, y para los tipos de datos, estructuras, arrays de caracteres, dimensiones, y según lo que quieras hacer/pasar

Jesus Leyva
2015-04-29 01:00:18

Hola, necesito una ayuda, supuestamente este codigo deberia leer una cadena por consola y guardarla en un puntero de tipo char(ópto por utilizar la funcion getchar()), y pasar por referencia un entero, si compila pero no lo ejecuta como deberia ejecutarlo...añadi un par de variables mas que luego utilizare. Muchas Gracias de antemano.

#include <iostream>
#include <stdio.h>

void leerCadena(char *destino, int &tam_Destino);

int main(int argc, char **argv) {
	int tam_Automata = 0, tam_Palabra = 0;
	char *automata, *palabra;
	
	cout<<"Elija un automata : ";
	
	tam_Automata = leerCadena(automata, tam_Automata);
	
	cout<<"automata : "<<automata<<endl<<"Tamaño : "<<tam_Automata;
	
	return 0;
}

void leerCadena(char *destino, int &tam_Destino){
	while(true){
		char tmp = getchar();
		if(tmp != '\n'){
			*(destino+tam_Destino) = tmp;
			tam_Destino++;
		}
		else{
			*(destino+tam_Destino) = '\0';
			break;
		}
	}
}
Jesus Leyva
2015-04-29 03:29:00

Lo siento, el codigo anterior se me fue una asignacion, el codigo era asi :

#include <iostream>
#include <stdio.h>
using namespace std;

void leerCadena(char *destino, int &tam_Destino);

int main(int argc, char **argv) {
	int tam_Automata = 0, tam_Palabra = 0;
	char *automata, *palabra;
	
	cout<<"Elija un automata : ";
	
	leerCadena(automata, tam_Automata);
	
	cout<<"automata : "<<automata<<endl<<"Tamaño : "<<tam_Automata;
	
	return 0;
}

void leerCadena(char *destino, int &tam_Destino){
	while(true){
		char tmp = getchar();
		if(tmp != '\n'){
			*(destino+tam_Destino) = tmp;
			tam_Destino++;
		}
		else{
			*(destino+tam_Destino) = '\0';
			break;
		}
	}
}

Ahora si, una ayuda por favor con ese codigo :/

Steven R. Davidson
2015-04-29 05:46:55

Hola Jesús,

Hay varios matices a destacar con tu código fuente. Principalmente, el error que cometes es pasar 'automata' a 'leerCadena()', porque tal puntero no apunta a nada existente. Debes apuntar a memoria, previamente reservada; por ejemplo,

automata = new char[100];

leerCadena( automata, tam_Automata );

Ahora, 'automata' apunta a memoria dinámicamente adjudicada, y por tanto 'leerCadena()' puede asignar caracteres a las direcciones de memoria válidas.

Por último, no deberías usar un bucle infinito para luego interrumpirlo dentro del cuerpo en base a una condición. Para esto mismo cada bucle tiene su propia condición. Puedes reescribir tu código así,

void leerCadena( char *destino, int &tam_Destino )
{
  char tmp = getchar();

  while( tmp != '\n' )
  {
    destino[tam_Destino++] = tmp;
    tmp = getchar();
  }

  destino[tam_Destino] = 0;
}

Espero que esto te oriente.

Steven

Jesus Leyva
2015-04-30 12:37:47

Otra vez por aqui... si entiendo, tiene que darse un tamaño si o si... ahora tengo una duda con respecto a malloc... se que asigna memoria dinamica a un puntero ahora :

#include <stdio.h>

int main(int argc, char *argv[]) {
	void *tmp;
	char *cadena;
	tmp = malloc(200*sizeof(char));
	if(tmp!=NULL){
		cadena = (char*) tmp;
		printf("tamaño en bytes de cadena sera %d: ",sizeof(cadena));
	}
	else
		printf("memoria insuficiente");
	return 0;
}

supuestamente en tamaño de bytes no deberia darme 200?... en mi computadora con SO Linux me da 8, y en windows me da 4. Cuando utilizo realloc tampoco cambia el tamaño en bytes de dicho puntero, solo se mantiene en 8 o en 4... Alguna ayuda porfavor.!?

Steven R. Davidson
2015-04-30 15:38:50

Hola Jesús,

Ciertamente pides dinámicamente 200 bytes de un bloque contiguos de memoria. El problema que tienes es que preguntas (estáticamente) por el tamaño de la variable 'cadena', y por tanto el compilador consultará su tipo de dato, que es un puntero. Como un puntero representa una dirección de memoria, la cantidad de memoria dependerá del sistema operativo y del compilador que uses. En el caso de Linux de 64 bits, usará 8 bytes (64 bits), mientras que el caso de Windows, el compilador que usas es de 32 bits, por lo que creará punteros de 4 bytes (32 bits). Como puedes ver, 'sizeof' no consulta el bloque de memoria apuntado, sino la expresión o tipo de dato que des.

Si quieres saber la cantidad de bytes del bloque de memoria que adjudicas dinámicamente, entonces deberás gestionar este dato explícitamente. Es decir, crea una variable de tipo 'int' para guardar la cantidad de bytes o de elementos, según te convenga; por ejemplo,

int nElementos;
char *cadena;
...
nElementos = 200;
cadena = (char *) malloc( nElementos );
...
nElementos += 40;
cadena = (char *) realloc( cadena, nElementos );

Por último, tu código debe liberar la memoria previamente creada, invocando 'free()'.

Espero haber aclarado la duda.

Steven

Ernesto Mello
2017-03-27 04:36:54

Estimados, muy bueno el curso!!

Tengo una pregunta sobre un codigo que estoy contruyendo.

Tengo las sig. clases:

dtEmpresa

dtNacional y dtExtranjera que son derivadas de dtEmpresa

que las uso como data type

Tambien tengo las clases

empresa

nacional y extranjera que son derivadas de empresa.

tengo una funcion obligatoria así: agregarEmpresa(dtEmpresa& empr)

antes de llamar a la función defino si será dtNacional o dtExtrajera (tienen un atributo mas que dtEmpresa).

Ahora mi problema es dentro de la función cuando quiero hacer referencia a la clase dtNacional o dtExtranjera.

como el parámetro está definido por la clase Base si hago referencia a un atributo de las clases derivadas el compilador me da error.

void agregarEmpresa(dtEmpresa& empresa)

empresa * empr = new nacional(empresa.getId(), empresa.getDireccion(),empresa.getRUT()) //getRUT es un metodo de la clase nacional no de empresa.

Como hago para obtener el valor de ese atributo?

Espero haber sido claro...

esteban garcia
2017-09-09 07:51:45

Hola tengo una duda con respecto al uso y creación de una función, el posible una función que retorne un valor char pero para ello use como parámetros un entero o no es posible.

Este es el código que trato de hacer pero me da error

char punto_coma(int a)
{

    if (a) return ".";
    else return ",";
}

este es el error que me da

error: invalid conversion from 'const char*' to 'char' [-fpermissive]

Gracias a todos y excelente curso voy en el ejercicio 6.4 y quise probar si era posible esa funcion pero no pude no si no es debido o que error tengo.

Steven R. Davidson
2017-09-10 16:11:18

Hola Esteban,

En primer lugar, hay una gran diferencia entre 'char' y 'char *'. El tipo de dato, 'char', representa un carácter, mientras que 'char *' (o incluso, 'char []') representa una cadena de caracteres. Como quieres retornar una cadena de caracteres, entonces tienes que indicar el tipo de dato, 'char *'.

El problema es la validez de la memoria que guarda los caracteres de tal cadena. No podemos apuntar a una cadena de caracteres local a la función, porque todas las variables locales se destruyen al terminar tal ámbito local. La otra posibilidad es crear memoria dinámicamente, pero suele conllevar peligros, porque fuera de la función hay que procurar liberar tal memoria dinámica. Por estas razones, se recomienda pasar el puntero por parámetro, obligando al programador a crear el array y la memoria fuera de la función, y por tanto en otro ámbito. Esto es,

char * punto_coma(char *pszRetorno, int a);

Ahora bien, si sólo vas a retornar cadenas literales, entonces sí puedes hacer esto directamente usando el tipo de dato, 'const char *'; esto sería,

const char * punto_coma( int a )
{
  return a ? "." : ",";
}

Viendo tu código, podríamos retornar simplemente un solo carácter; o sea,

char punto_coma( int a )
{
  return a ? '.' : ',';
}

Espero que esto te aclare la duda.

Steven

Manuel
2018-05-17 21:54:01

puedes ayudarme con un programa que teniendo dos arreglos me muestre cual es la unión y la intersección con el uso de llenado de arreglo y muestra de arreglo

Pedro
2018-08-01 16:36:32

Buenas, tengo la duda que si uno declara una variable gloval con un valor y quiere que a paratir de cierta parte del codigo se le asigne un valor que es generado dentro de una funsión como puedo hacer que tome ese valor?, una vez creo que vi eso y era con :: pero no recuerdo la sintaxis.

Steven R. Davidson
2018-08-01 16:45:29

Hola Pedro,

En el caso más simple, sólo hay que usar su nombre. Por ejemplo,

int x = 10;

int main()
{
  x = 5;
  ...
}

Si existen declaraciones locales con el mismo nombre que la variable global, entonces sí tenemos que usar el operador de ámbito :: para indicar que queremos acceder a la variable global y no a la local. Por ejemplo,

int x = 10;

int main()
{
  int x = -3;

  ::x = 5;  // Modificamos la variable global
  ...
}

Espero haber aclarado la duda.

Steven

idipl0899
2018-11-29 04:05:53

Hola que tal, tengo una duda, como puedo hacer para poner una fecha en un arreglo de tipo short

#include<iostream>

using namespace std;

class Juguete

{

private:

string nombre;

float precio;

bool comestible;

short caducidad[3];

public:

Juguete();

void setNombre(string);

void setPrecio(float);

void setCaducidad(short[]);

string getNombre();

float getPrecio();

short getCaducidad();

bool isComestible();

void mostrarJuguete();

};

Juguete::Juguete()

{

nombre="";

precio=0.0;

comestible=false;

caducidad[3]=0;

}

void Juguete::setNombre(string nom)

{

nombre=nom;

}

void Juguete::setPrecio(float prec)

{

precio=prec;

}

void Juguete::setCaducidad(short cad[3])

{

caducidad[3]=cad[3];

}

string Juguete::getNombre()

{

return nombre;

}

float Juguete::getPrecio()

{

return precio;

}

short Juguete::getCaducidad()

{

for(int i=0;i<3;i++)

{

return caducidad[i];

}

}

bool Juguete::isComestible()

{

if(comestible)

{

return true;

}

else

{

return false;

}

}

void Juguete::mostrarJuguete()

{

if(isComestible())

{

cout<<"\nNombre: "<<nombre;

cout<<"\nPrecio: $"<<precio;

cout<<"\nCaducidad: ";

for(int i=0;i<3;i++)

{

cout<<caducidad[i]<<"/";

};

cout<<endl;

}

else

{

cout<<"\nNombre: "<<nombre;

cout<<"\nPrecio: $"<<precio;

cout<<endl;

}

}

Steven R. Davidson
2018-11-29 16:31:39

Hola idipl0899,

Analizando el código fuente, éstas son mis observaciones:

- En el constructor, deberías usar su lista inicializadora para inicializar los miembros del objeto. Esto es,

Juguete::Juguete() : nombre(""), precio(0.0f), comestible(false), caducidad{ 0 }
{ }

Si no quieres (o no puedes) usar la lista inicializadora del constructor, entonces debes corregir la asignación del array. Deberías escribir:

caducidad[0] = caducidad[1] = caducidad[2] = 0;

- Los objetos que pases por parámetro deberían ser pasados por referencia y si no tienes intención de modificar tal objeto, entonces indica que es constante. Por ejemplo,

void Juguete::setNombre( const string &nom )
{
  nombre = nom;
}

- Podríamos hacer lo mismo para aquellos objetos retornados directamente por la función. Por ejemplo, podemos usar escribir esta función como está

string Juguete::getNombre() const
{
  return nombre;
}

o podemos reescribirla así,

const string & Juguete::getNombre() const
{
  return nombre;
}

Eso sí, recuerda indicar que la función miembro no tiene intención de modificar este objeto, por lo que se indica 'const' al final de la función.

- Para asignar los elementos de un array a otro, debes hacer esto explícitamente; esto es,

void Juguete::setCaducidad( const short cad[3] )
{
  caducidad[0] = cad[0];
  caducidad[1] = cad[1];
  caducidad[2] = cad[2];
}

Como no tenemos intención de modificar el contenido de los datos en 'cad', indicamos que tales elementos son constantes.

- Para que una función retorne un array, debes retornar un puntero, ya que un array ES un puntero. Esto es,

const short * Juguete::getCaducidad() const
{
  return caducidad;
}

Nuevamente, no queremos permitir modificar el contenido del array retornado y de paso indicamos que nuestra función miembro tampoco tiene intención de modificar este objeto.

- No es necesario comprobar la veracidad del booleano si vamos a retornar su valor: simplemente retorna su valor. Esto es,

bool Juguete::isComestible() const
{
  return comestible;
}

Espero que esto te aclare las dudas.

Steven