valores tipos salida retornan referencias referencia que por parametros funciones entrada con c++ reference-parameters

tipos - ¿Cómo uso los parámetros de referencia en C++?



referencias en c++ (8)

¿Qué hay de metáfora: decir que su función cuenta frijoles en un frasco. Necesita el frasco de frijoles y necesita conocer el resultado que no puede ser el valor de retorno (por varias razones). Podría enviarle el jar y el valor de la variable, pero nunca sabrá si cambia el valor o a qué le cambia. En su lugar, debe enviar esa variable a través de un sobre dirigido a devolver, para que pueda poner el valor en eso y saber que está escrito el resultado con el valor en dicha dirección.

Estoy tratando de entender cómo usar los parámetros de referencia. Hay varios ejemplos en mi texto, sin embargo, son demasiado complicados para que entienda por qué y cómo usarlos.

¿Cómo y por qué querrías usar una referencia? ¿Qué pasaría si no convirtieras el parámetro en una referencia, sino que abandonaras el & off?

Por ejemplo, ¿cuál es la diferencia entre estas funciones?

int doSomething(int& a, int& b); int doSomething(int a, int b);

Entiendo que las variables de referencia se utilizan para cambiar una referencia formal->, que luego permite un intercambio bidireccional de parámetros. Sin embargo, ese es el grado de mi conocimiento, y un ejemplo más concreto sería de mucha ayuda.


Corrígeme si me equivoco, pero una referencia es solo un puntero desreferenciado, o?

La diferencia con un puntero es que no puede asignar fácilmente un NULL.


La respuesta de GMan le da la verdad sobre las referencias. Solo quería mostrarte una función muy básica que debe usar referencias: swap , que intercambia dos variables. Aquí está para int s (como lo solicitó):

// changes to a & b hold when the function exits void swap(int& a, int& b) { int tmp = a; a = b; b = tmp; } // changes to a & b are local to swap_noref and will go away when the function exits void swap_noref(int a, int b) { int tmp = a; a = b; b = tmp; } // changes swap_ptr makes to the variables pointed to by pa & pb // are visible outside swap_ptr, but changes to pa and pb won''t be visible void swap_ptr(int *pa, int *pb) { int tmp = *pa; *pa = *pb; *pb = tmp; } int main() { int x = 17; int y = 42; // next line will print "x: 17; y: 42" std::cout << "x: " << x << "; y: " << y << std::endl // swap can alter x & y swap(x,y); // next line will print "x: 42; y: 17" std::cout << "x: " << x << "; y: " << y << std::endl // swap_noref can''t alter x or y swap_noref(x,y); // next line will print "x: 42; y: 17" std::cout << "x: " << x << "; y: " << y << std::endl // swap_ptr can alter x & y swap_ptr(&x,&y); // next line will print "x: 17; y: 42" std::cout << "x: " << x << "; y: " << y << std::endl }

Hay una implementación más inteligente de swap para int s que no necesita un temporal. Sin embargo, aquí me importa más claro que inteligente.

Sin referencias (o punteros), swap_noref no puede alterar las variables que se le pasan, lo que significa que simplemente no puede funcionar. swap_ptr puede alterar variables, pero utiliza punteros, que son desordenados (cuando las referencias no lo cortan del todo, sin embargo, los punteros pueden hacer el trabajo). swap es el más simple en general.

En punteros

Los punteros le permiten hacer algunas de las mismas cosas que las referencias. Sin embargo, los punteros ponen más responsabilidad en el programador para administrarlos y la memoria que señalan (un tema llamado " gestión de memoria ", pero no se preocupe por el momento). Como consecuencia, las referencias deberían ser su herramienta preferida por ahora.

Piense en variables como nombres vinculados a cuadros que almacenan un valor. Las constantes son nombres vinculados directamente a los valores. Ambos asignan nombres a valores, pero el valor de las constantes no se puede cambiar. Si bien el valor contenido en un recuadro puede cambiar, el enlace del nombre al recuadro no puede, por lo que no se puede cambiar una referencia para hacer referencia a una variable diferente.

Dos operaciones básicas sobre variables son obtener el valor actual (hecho simplemente usando el nombre de la variable) y asignar un nuevo valor (el operador de asignación, ''=''). Los valores se almacenan en la memoria (el cuadro que contiene un valor es simplemente una región contigua de la memoria). Por ejemplo,

int a = 17;

resultados en algo así como (nota: en el siguiente, "foo @ 0xDEADBEEF" significa una variable con el nombre "foo" almacenado en la dirección "0xDEADBEEF". Las direcciones de memoria se han formado):

____ a @ 0x1000: | 17 | ----

Todo lo almacenado en la memoria tiene una dirección de inicio, por lo que hay una operación más: obtener la dirección del valor ("&" es el operador de dirección de). Un puntero es una variable que almacena una dirección.

int *pa = &a;

resultados en:

______ ____ pa @ 0x10A0: |0x1000| ------> @ 0x1000: | 17 | ------ ----

Tenga en cuenta que un puntero simplemente almacena una dirección de memoria, por lo que no tiene acceso al nombre al que apunta. De hecho, los punteros pueden señalar cosas sin nombres, pero ese es un tema para otro día.

Hay algunas operaciones en punteros. Puede desviar un puntero (el operador "*"), que le proporciona los datos a los que apunta el puntero. La desreferencia es lo opuesto a obtener la dirección: *&a es el mismo cuadro que a , &*pa tiene el mismo valor que pa , y *pa es el mismo cuadro que a . En particular, pa en el ejemplo contiene 0x1000; * pa significa "int en la memoria en la ubicación pa", o "int en la memoria en la ubicación 0x1000". "a" también es "el int en la ubicación de la memoria 0x1000". Otra operación en punteros es la suma y la resta, pero ese también es un tema para otro día.


No sé si esto es lo más básico, pero aquí va ...

typedef int Element; typedef std::list<Element> ElementList; // Defined elsewhere. bool CanReadElement(void); Element ReadSingleElement(void); int ReadElementsIntoList(int count, ElementList& elems) { int elemsRead = 0; while(elemsRead < count && CanReadElement()) elems.push_back(ReadSingleElement()); return count; }

Aquí usamos una referencia para pasar nuestra lista de elementos a ReadElementsIntoList() . De esta forma, la función carga los elementos directamente en la lista. Si no usamos una referencia, entonces elems sería una copia de la lista pasada, que tendría los elementos agregados a ella, pero luego los elems se descartarían cuando la función retorna.

Esto funciona en ambos sentidos. En el caso del count , no lo convertimos en una referencia, porque no queremos modificar el recuento pasado, sino que devolvemos la cantidad de elementos leídos. Esto permite que el código de llamada compare el número de elementos realmente leídos con el número solicitado; si no coinciden, entonces CanReadElement() debe haber devuelto false , e inmediatamente tratar de leer algunos más probablemente fallará. Si coinciden, entonces tal vez el count era menor que la cantidad de elementos disponibles, y una lectura adicional sería apropiada. Finalmente, si ReadElementsIntoList() necesitaba modificar el count internamente, podría hacerlo sin molestar a la persona que llama.


Piense en una referencia como un alias . Cuando invocas algo en una referencia, lo estás invocando realmente en el objeto al que se refiere la referencia.

int i; int& j = i; // j is an alias to i j = 5; // same as i = 5

Cuando se trata de funciones, considere:

void foo(int i) { i = 5; }

Arriba, int i es un valor y el argumento pasado se pasa por valor . Eso significa que si decimos:

int x = 2; foo(x);

seré una copia de x . Por lo tanto, establecer i a 5 no tiene efecto sobre x , porque es la copia de x está cambiando. Sin embargo, si hacemos una referencia:

void foo(int& i) // i is an alias for a variable { i = 5; }

Luego, diciendo foo(x) ya no hace una copia de x ; i soy x Entonces, si decimos foo(x) , dentro de la función i = 5; es exactamente lo mismo que x = 5; y x cambios.

Esperemos que eso aclare un poco.

¿Porque es esto importante? Cuando programa, nunca desea copiar y pegar el código. Desea hacer una función que realice una tarea y lo haga bien. Siempre que deba realizarse esa tarea, usa esa función.

Entonces digamos que queremos cambiar dos variables. Eso se ve así:

int x, y; // swap: int temp = x; // store the value of x x = y; // make x equal to y y = temp; // make y equal to the old value of x

Bien, excelente. Queremos hacer de esto una función, porque: swap(x, y); es mucho más fácil de leer Entonces, probemos esto:

void swap(int x, int y) { int temp = x; x = y; y = temp; }

¡Esto no funcionará! El problema es que esto es intercambiar copias de dos variables. Es decir:

int a, b; swap(a, b); // hm, x and y are copies of a and b...a and b remain unchanged

En C, donde las referencias no existen, la solución fue pasar la dirección de estas variables; es decir, use punteros *:

void swap(int* x, int* y) { int temp = *x; *x = *y; *y = temp; } int a, b; swap(&a, &b);

Esto funciona bien Sin embargo, es un poco torpe de usar, y en realidad un poco inseguro. swap(nullptr, nullptr) , intercambia dos nada y elimina referencias punteros nulos ... ¡comportamiento indefinido! Solucionable con algunos controles:

void swap(int* x, int* y) { if (x == nullptr || y == nullptr) return; // one is null; this is a meaningless operation int temp = *x; *x = *y; *y = temp; }

Pero mira lo torpe que nuestro código ha llegado. C ++ introduce referencias para resolver este problema. Si podemos alias una variable, obtenemos el código que estábamos buscando:

void swap(int& x, int& y) { int temp = x; x = y; y = temp; } int a, b; swap(a, b); // inside, x and y are really a and b

Fácil de usar y seguro. (No podemos pasar accidentalmente un nulo, no hay referencias nulas.) Esto funciona porque el intercambio que ocurre dentro de la función realmente está ocurriendo en las variables que se alias fuera de la función.

(Nota, nunca escriba una función de swap . :) Ya existe uno en el encabezado <algorithm> , y está diseñado para trabajar con cualquier tipo.)

Otro uso es eliminar esa copia que ocurre cuando llamas a una función. Considere que tenemos un tipo de datos que es muy grande. Copiar este objeto lleva mucho tiempo, y nos gustaría evitar eso:

struct big_data { char data[9999999]; }; // big! void do_something(big_data data); big_data d; do_something(d); // ouch, making a copy of all that data :<

Sin embargo, todo lo que realmente necesitamos es un alias para la variable, así que indiquemos eso. (Nuevamente, en C pasamos la dirección de nuestro tipo de Big Data, resolviendo el problema de la copia pero introduciendo torpeza):

void do_something(big_data& data); big_data d; do_something(d); // no copies at all! data aliases d within the function

Es por eso que lo oirás decir que debes pasar cosas por referencia todo el tiempo, a menos que sean tipos primitivos. (Porque internamente, pasar un alias probablemente se hace con un puntero, como en C. Para objetos pequeños, es más rápido hacer la copia y luego preocuparse por los punteros).

Tenga en cuenta que debe ser const-correcto. Esto significa que si su función no modifica el parámetro, márquelo como const . Si do_something arriba solo miraba pero no cambiaba los data , lo marcaríamos como const :

void do_something(const big_data& data); // alias a big_data, and don''t change it

Evitamos la copia y decimos "hey, no modificaremos esto". Esto tiene otros efectos secundarios (con cosas como variables temporales), pero no debes preocuparte por eso ahora.

Por el contrario, nuestra función de swap no puede ser const , porque estamos modificando los alias.

Espero que esto aclare un poco más.

* Tutorial de punteros ásperos:

Un puntero es una variable que contiene la dirección de otra variable. Por ejemplo:

int i; // normal int int* p; // points to an integer (is not an integer!) p = &i; // &i means "address of i". p is pointing to i *p = 2; // *p means "dereference p". that is, this goes to the int // pointed to by p (i), and sets it to 2.

Entonces, si ha visto la función de intercambio de la versión del puntero, pasamos la dirección de las variables que queremos intercambiar, y luego hacemos el intercambio, desreferenciando para obtener y establecer valores.


Tomemos un ejemplo simple de una función llamada increment que incrementa su argumento. Considerar:

void increment(int input) { input++; }

que no funcionará ya que el cambio tiene lugar en la copia del argumento pasado a la función en el parámetro real. Asi que

int i = 1; std::cout<<i<<" "; increment(i); std::cout<<i<<" ";

producirá 1 1 como salida.

Para que la función funcione en el parámetro real pasado pasamos su reference a la función como:

void increment(int &input) { // note the & input++; }

el cambio realizado en la input dentro de la función en realidad se está realizando en el parámetro real. Esto producirá el resultado esperado de 1 2


Un simple par de ejemplos que puedes ejecutar en línea.

El primero usa una función normal, y el segundo usa referencias:

Editar: aquí está el código fuente en caso de que no te gusten los enlaces:

Ejemplo 1

using namespace std; void foo(int y){ y=2; } int main(){ int x=1; foo(x); cout<<x;//outputs 1 }


Ejemplo 2

using namespace std; void foo(int & y){ y=2; } int main(){ int x=1; foo(x); cout<<x;//outputs 2 }


// Passes in mutable references of a and b. int doSomething(int& a, int& b) { a = 5; cout << "1: " << a << b; // prints 1: 5,6 } a = 0; b = 6; doSomething(a, b); cout << "2: " << a << ", " << b; // prints 2: 5,6

Alternativamente,

// Passes in copied values of a and b. int doSomething(int a, int b) { a = 5; cout << "1: " << a << b; // prints 1: 5,6 } a = 0; b = 6; doSomething(a, b); cout << "2: " << a << ", " << b; // prints 2: 0,6

O la versión const:

// Passes in const references a and b. int doSomething(const int &a, const int &b) { a = 5; // COMPILE ERROR, cannot assign to const reference. cout << "1: " << b; // prints 1: 6 } a = 0; b = 6; doSomething(a, b);

Las referencias se utilizan para pasar ubicaciones de variables, por lo que no es necesario copiarlas en la pila a la nueva función.