Dá para entender melhor a diferença entre referências e cópias pensando na versão mais “raiz” das referências, os ponteiros. Os ponteiros são basicamente variáveis contendo o local de memória onde uma variável está, e o tamanho deles é (simplificando um pouco) a quantidade de bits da máquina.
A principal consequência tanto de ponteiros quando de referências é que é possível uma função editar variáveis de outra:
#include <stdio.h>
int somarUm(int *numero) {
// podemos alterar o conteúdo da memória na localização apontada por numero
// usando o operador *.
// OBS: usando int& no C++, podemos omitir o *.
(*numero)++;
}
int main(void) {
int numero = 2;
somarUm(&numero);
printf("Número: %d\n", numero);
}
Numa cópia (só int numero
em vez de int *numero
ou int &numero
), a alteração só se refletiria na própria função. Por isso, bibliotecas no geral pedem ponteiros/referências.
Como o @Melk citou, outra propriedade é que referências e ponteiros consomem muito menos memória. Não é tão importante assim para int
(na verdade, teoricamente compensaria fazer a cópia, pois na arquitetura x86_64 o int
tem 32 bits e ponteiros têm 64 bits), mas para arrays ou estruturas de dados mais compridas, compensa muito não ter que ler os dados de um local, transcrevê-los para outro, para ler uma ou outra coisinha e descartá-los.
Antecipando: a diferença entre ponteiros e referências é que referências possuem salvaguardas adicionais. Ponteiros “brutos” podem mudar para onde apontam, e podem inclusive apontar para variáveis que não existem, sendo esse último item a causa clássica dos erros “Segmentation Fault” (no Linux) ou “Access Violation” (no Windows). Já referências devem apontar para uma variável que exista, e dependendo da linguagem, não podem mudar de variável.
Em linguagens que não têm esses conceitos de cópia e ponteiro/referência explícitos, como Python, precisamos ver a documentação. Por exemplo, sabendo que em Python listas são sempre referências, e que ao usar [x,y,z]
você na verdade está criando uma lista do nada e puxando a referência dela, o fato do código a seguir soltar [1, 2, 3, 4]
está explicado:
super_lista = [1,2,3]
def coisa(l):
# É uma referência, vai se refletir na super_lista de fora da função.
l.append(4)
# Mudei a referência, agora l aponta para essa lista que eu criei agora.
l = [1, 2]
# Não vai afetar a super_lista.
l.append(44)
coisa(super_lista)
print(super_lista)