Quais são as diferenças entre uma variável de ponteiro e uma variável de referência em C ++?

3263

Eu sei que as referências são açúcar sintático, então o código é mais fácil de ler e escrever.

Mas quais são as diferenças?

prakash
fonte
100
Acho que o ponto 2 deve ser "Um ponteiro pode ser NULL, mas uma referência não. Somente código malformado pode criar uma referência NULL e seu comportamento é indefinido".
Mark Ransom
19
Os ponteiros são apenas outro tipo de objeto e, como qualquer objeto em C ++, podem ser uma variável. As referências, por outro lado, nunca são objetos, apenas variáveis.
Kerrek SB
19
Isso compila sem avisos: int &x = *(int*)0;no gcc. Referência pode realmente apontar para NULL.
Calmarius 13/08/2012
20
reference é um alias variável
Khaled.K 23/12/13
20
Eu gosto de como a primeira frase é uma falácia total. As referências têm sua própria semântica.
Lightness Races in Orbit

Respostas:

1708
  1. Um ponteiro pode ser redesignado:

    int x = 5;
    int y = 6;
    int *p;
    p = &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);

    Uma referência não pode e deve ser atribuída na inicialização:

    int x = 5;
    int y = 6;
    int &r = x;
  2. Um ponteiro possui seu próprio endereço de memória e tamanho na pilha (4 bytes em x86), enquanto uma referência compartilha o mesmo endereço de memória (com a variável original), mas também ocupa algum espaço na pilha. Como uma referência tem o mesmo endereço da variável original, é seguro pensar em uma referência como outro nome para a mesma variável. Nota: O que um ponteiro aponta para pode estar na pilha ou na pilha. Idem uma referência. Minha afirmação nesta declaração não é que um ponteiro deva apontar para a pilha. Um ponteiro é apenas uma variável que contém um endereço de memória. Esta variável está na pilha. Como uma referência tem seu próprio espaço na pilha e como o endereço é o mesmo que a variável que ela faz referência. Mais sobre stack vs heap. Isso implica que existe um endereço real de uma referência que o compilador não informará.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    assert(p == p2);
  3. Você pode ter ponteiros para ponteiros, oferecendo níveis extras de indireção. Enquanto as referências oferecem apenas um nível de indireção.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    pp = &q;//*pp = q
    **pp = 4;
    assert(y == 4);
    assert(x == 0);
  4. Um ponteiro pode ser atribuído nullptrdiretamente, enquanto a referência não. Se você se esforçar o suficiente, e você souber como, poderá fazer o endereço de uma referência nullptr. Da mesma forma, se você se esforçar o suficiente, poderá ter uma referência a um ponteiro e essa referência poderá conter nullptr.

    int *p = nullptr;
    int &r = nullptr; <--- compiling error
    int &r = *p;  <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
  5. Os ponteiros podem iterar sobre uma matriz; você pode usar ++para ir para o próximo item que um ponteiro está apontando e + 4para o quinto elemento. Não importa qual o tamanho do objeto para o qual o ponteiro aponta.

  6. Um ponteiro precisa ser desreferenciado *para acessar o local da memória para o qual aponta, enquanto uma referência pode ser usada diretamente. Um ponteiro para uma classe / estrutura usa ->para acessar seus membros, enquanto uma referência usa a ..

  7. As referências não podem ser inseridas em uma matriz, enquanto os ponteiros podem ser (Mencionado pelo usuário @litb)

  8. As referências Const podem ser vinculadas a temporários. Os ponteiros não podem (não sem algum indireção):

    const int &x = int(12); //legal C++
    int *y = &int(12); //illegal to dereference a temporary.

    Isso torna const&mais seguro o uso em listas de argumentos e assim por diante.

Brian R. Bondy
fonte
23
... mas a exclusão da referência NULL é indefinida. Por exemplo, você não pode testar se uma referência é NULL (por exemplo, & ref == NULL).
Pat Notz 11/09/08
69
O número 2 não é verdadeiro. Uma referência não é simplesmente "outro nome para a mesma variável". As referências podem ser passadas para funções, armazenadas em classes, etc. de maneira muito semelhante aos ponteiros. Eles existem independentemente das variáveis ​​para as quais apontam.
Derek Park
31
Brian, a pilha não é relevante. Referências e ponteiros não precisam ocupar espaço na pilha. Ambos podem ser alocados no heap.
Derek Park
22
Brian, o fato de uma variável (neste caso, um ponteiro ou referência) exigir espaço, não significa que requer espaço na pilha. Ponteiros e referências podem não apenas apontar para o heap, mas também podem ser alocados no heap.
Derek Park
38
outra diff importante: referências não podem ser recheadas em uma matriz
Johannes Schaub - litb
384

O que é uma referência C ++ ( para programadores C )

Uma referência pode ser pensada como um ponteiro constante (não deve ser confundido com um ponteiro para um valor constante!) Com indireção automática, ou seja, o compilador aplicará o *operador para você.

Todas as referências devem ser inicializadas com um valor não nulo ou a compilação falhará. Não é possível obter o endereço de uma referência - o operador de endereço retornará o endereço do valor referenciado - nem é possível fazer aritmética nas referências.

Os programadores de C podem não gostar das referências do C ++, pois não será mais óbvio quando a indireção acontecer ou se um argumento for passado por valor ou por ponteiro sem observar as assinaturas das funções.

Os programadores de C ++ podem não gostar de usar ponteiros, pois são considerados inseguros - embora as referências não sejam realmente mais seguras que os ponteiros constantes, exceto nos casos mais triviais - não têm a conveniência do indireto automático e carregam uma conotação semântica diferente.

Considere a seguinte declaração nas Perguntas frequentes sobre C ++ :

Mesmo que uma referência seja frequentemente implementada usando um endereço na linguagem assembly subjacente, não pense em uma referência como um ponteiro de aparência engraçada para um objeto. Uma referência é o objeto. Não é um ponteiro para o objeto, nem uma cópia do objeto. Ele é o objeto.

Mas se uma referência realmente fosse o objeto, como poderia haver referências pendentes? Em linguagens não gerenciadas, é impossível que as referências sejam mais "seguras" que os ponteiros - geralmente não existe uma maneira de aliasizar valores de maneira confiável através dos limites do escopo!

Por que considero úteis as referências C ++

Vindo de um background C, as referências C ++ podem parecer um conceito um pouco tolo, mas ainda é possível usá-las em vez de ponteiros, sempre que possível: Indirecionamento automático é conveniente, e as referências se tornam especialmente úteis ao lidar com RAII - mas não por causa de qualquer segurança percebida vantagem, mas porque eles tornam a escrita de código idiomático menos incômoda.

RAII é um dos conceitos centrais do C ++, mas interage de maneira não trivial com a semântica de cópia. A passagem de objetos por referência evita esses problemas, pois não há cópia envolvida. Se não houvesse referências no idioma, seria necessário usar ponteiros, que são mais difíceis de usar, violando o princípio de design do idioma de que a solução de melhores práticas deve ser mais fácil do que as alternativas.

Christoph
fonte
17
@kriss: Não, você também pode obter uma referência pendente retornando uma variável automática por referência.
Ben Voigt
12
@kriss: É praticamente impossível para um compilador detectar no caso geral. Considere uma função de membro que retorna uma referência a uma variável de membro de classe: isso é seguro e não deve ser proibido pelo compilador. Em seguida, um chamador que possui uma instância automática dessa classe, chama essa função de membro e retorna a referência. Presto: referência pendente. E sim, isso vai causar problemas, @kriss: esse é o meu ponto. Muitas pessoas afirmam que uma vantagem das referências sobre os ponteiros é que as referências são sempre válidas, mas simplesmente não são.
quer
4
@kriss: Não, uma referência a um objeto com duração de armazenamento automático é muito diferente de um objeto temporário. Enfim, eu estava apenas fornecendo um contra-exemplo à sua afirmação de que você só pode obter uma referência inválida desreferenciando um ponteiro inválido. Christoph está correto - as referências não são mais seguras que os ponteiros, um programa que usa referências exclusivamente ainda pode prejudicar a segurança do tipo.
Ben Voigt
7
As referências não são um tipo de ponteiro. Eles são um novo nome para um objeto existente.
catphive
18
@catphive: true se você usa a semântica da linguagem, não é verdade se você realmente observar a implementação; C ++ é um 'mágico' linguagem muito mais que C, e se você remover a magia a partir de referências, você acaba com um ponteiro
Christoph
191

Se você quer ser realmente pedante, há uma coisa que você pode fazer com uma referência que você não pode fazer com um ponteiro: prolongar a vida útil de um objeto temporário. No C ++, se você vincular uma referência const a um objeto temporário, a vida útil desse objeto se tornará a vida útil da referência.

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

Neste exemplo, s3_copy copia o objeto temporário resultante da concatenação. Enquanto s3_reference em essência se torna o objeto temporário. É realmente uma referência a um objeto temporário que agora tem a mesma vida útil que a referência.

Se você tentar isso sem o, constele deverá falhar na compilação. Você não pode vincular uma referência não-const a um objeto temporário, nem pode levar seu endereço para esse assunto.

Matt Price
fonte
5
mas qual é o caso de uso para isso?
Ahmad Mushtaq
20
Bem, s3_copy criará um temporário e depois copiará o construto para s3_copy, enquanto s3_reference usa diretamente o temporário. Para ser realmente pedante, você precisa observar a Otimização do valor de retorno, na qual o compilador pode excluir a construção da cópia no primeiro caso.
Matt Price
6
@digitalSurgeon: A mágica é bastante poderosa. A vida útil do objeto é estendida pelo fato da const &ligação e somente quando a referência fica fora do escopo é chamado o destruidor do tipo real referenciado (em comparação com o tipo de referência, que pode ser uma base). Como é uma referência, nenhuma fatia ocorrerá no meio.
David Rodríguez - dribeas
9
Atualização para o C ++ 11: última frase deve ler "Você não pode vincular uma referência lvalue não-const para um temporário" porque você pode ligar um não-const rvalue referência a um temporária, e tem o mesmo comportamento de prolongamento da vida útil.
Oktalist 10/11/13
4
@AhmadMushtaq: O principal uso disso são as classes derivadas . Se não houver herança envolvida, você também pode usar a semântica de valores, que será barata ou gratuita devido à construção do RVO / move. Mas se você tiver, Animal x = fast ? getHare() : getTortoise()então xenfrentará o problema clássico de fatia, enquanto Animal& x = ...funcionará corretamente.
Arthur Tacca
128

Além do açúcar sintático, uma referência é um constponteiro ( não um ponteiro para a const). Você deve estabelecer a que se refere quando declara a variável de referência e não pode alterá-la posteriormente.

Atualização: agora que penso um pouco mais, há uma diferença importante.

O alvo de um ponteiro const pode ser substituído pegando seu endereço e usando uma conversão constante.

O destino de uma referência não pode ser substituído de forma alguma abaixo do UB.

Isso deve permitir que o compilador faça mais otimização em uma referência.


fonte
8
Eu acho que esta é a melhor resposta de longe. Outros falam sobre referências e indicadores como se fossem bestas diferentes e depois descrevem como diferem em comportamento. Não torna as coisas mais fáceis. Sempre entendi as referências como sendo um T* constaçúcar sintático diferente (que acaba eliminando muito * e & do seu código).
Carlo Wood
2
"O alvo de um ponteiro const pode ser substituído pegando seu endereço e usando um elenco constante." Fazer isso é um comportamento indefinido. Consulte stackoverflow.com/questions/25209838/… para obter detalhes.
dgnuff
1
Tentar alterar o referente de uma referência ou o valor de um ponteiro const (ou qualquer escalar const) é uma igualdade ilegal. O que você pode fazer: remover uma qualificação const que foi adicionada por conversão implícita: int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);está OK.
Curiousguy
1
A diferença aqui é UB versus literalmente impossível. Não há sintaxe no C ++ que permita alterar em quais pontos de referência.
Não é impossível, mais difícil: você pode simplesmente acessar a área de memória do ponteiro que está modelando essa referência e alterando seu conteúdo. Isso certamente pode ser feito.
Nicolas Bousquet
126

Ao contrário da opinião popular, é possível ter uma referência que seja NULL.

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

É verdade que é muito mais difícil fazer uma referência - mas, se você conseguir, vai arrancar o cabelo tentando encontrá-lo. As referências não são inerentemente seguras no C ++!

Tecnicamente, essa é uma referência inválida , não uma referência nula. O C ++ não suporta referências nulas como conceito, como você pode encontrar em outros idiomas. Existem outros tipos de referências inválidas também. Qualquer referência inválida aumenta o espectro de comportamento indefinido , assim como o faria com um ponteiro inválido.

O erro real está na desreferenciação do ponteiro NULL, antes da atribuição a uma referência. Mas não conheço nenhum compilador que gere erros nessa condição - o erro se propaga para um ponto mais adiante no código. É isso que torna esse problema tão insidioso. Na maioria das vezes, se você derereferenciar um ponteiro NULL, você trava naquele local e não é preciso muita depuração para descobrir isso.

Meu exemplo acima é curto e artificial. Aqui está um exemplo mais real.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

Quero reiterar que a única maneira de obter uma referência nula é através de código malformado e, quando você o obtém, está obtendo um comportamento indefinido. Ele não faz sentido para verificar se há uma referência nula; por exemplo, você pode tentar, if(&bar==NULL)...mas o compilador pode otimizar a declaração para não existir! Uma referência válida nunca pode ser NULL; portanto, na visão do compilador, a comparação é sempre falsa e é livre para eliminar oif cláusula como código morto - essa é a essência do comportamento indefinido.

A maneira correta de evitar problemas é evitar desreferenciar um ponteiro NULL para criar uma referência. Aqui está uma maneira automatizada de fazer isso.

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

Para uma análise mais antiga desse problema de alguém com melhores habilidades de escrita, consulte Referências nulas de Jim Hyslop e Herb Sutter.

Para outro exemplo dos perigos de desreferenciar um ponteiro nulo, consulte Expondo comportamento indefinido ao tentar portar código para outra plataforma por Raymond Chen.

Mark Ransom
fonte
63
O código em questão contém um comportamento indefinido. Tecnicamente, você não pode fazer nada com um ponteiro nulo, exceto configurá-lo e compará-lo. Depois que seu programa chama um comportamento indefinido, ele pode fazer qualquer coisa, inclusive parecer funcionar corretamente até você dar uma demonstração ao grande chefe.
KeithB
9
A marca possui um argumento válido. o argumento de que um ponteiro pode ser NULL e você deve verificar também não é real: se você diz que uma função requer não-NULL, o chamador deve fazer isso. portanto, se o chamador não estiver, ele está invocando um comportamento indefinido. como marca fez com o mau referência
Johannes Schaub - litb
13
A descrição está incorreta. Esse código pode ou não criar uma referência que seja NULL. Seu comportamento é indefinido. Pode criar uma referência perfeitamente válida. Pode não conseguir criar nenhuma referência.
David Schwartz
7
@ David Schwartz, se eu estivesse falando sobre como as coisas tinham que funcionar de acordo com o padrão, você estaria correto. Mas não é disso que estou falando - estou falando sobre o comportamento real observado com um compilador muito popular e extrapolando com base no meu conhecimento de compiladores típicos e arquiteturas de CPU para o que provavelmente acontecerá. Se você acredita que as referências são superiores aos ponteiros, porque são mais seguras e não considera que as referências possam ser ruins, você será surpreendido por um problema simples algum dia, exatamente como eu.
Mark Ransom
6
Desreferenciar um ponteiro nulo está errado. Qualquer programa que faça isso, mesmo para inicializar uma referência, está errado. Se você estiver inicializando uma referência a partir de um ponteiro, verifique sempre se o ponteiro é válido. Mesmo que isso tenha êxito, o objeto subjacente pode ser excluído a qualquer momento, deixando a referência para se referir a um objeto inexistente, certo? O que você está dizendo é uma coisa boa. Eu acho que o problema real aqui é que a referência NÃO precisa ser verificada para "nulidade" quando você vê uma e o ponteiro deve ser, no mínimo, afirmado.
t0rakka
115

Você esqueceu a parte mais importante:

acesso por membro com ponteiros usa ->
acesso por membro com referências.

foo.baré claramente superior ao foo->barda mesma maneira que vi é claramente superior ao Emacs :-)

Orion Edwards
fonte
4
@Orion Edwards> acesso de membro com ponteiros usa ->> acesso de membro com usos de referências. Isso não é 100% verdade. Você pode ter uma referência a um ponteiro. Nesse caso, você acessaria membros do ponteiro não referenciado usando -> struct Node {Node * next; }; Nó * primeiro; // p é uma referência a um ponteiro void foo (Nó * & p) {p-> next = first; } Nó * bar = novo Nó; foo (barra); - OP: Você está familiarizado com os conceitos de rvalues ​​e lvalues?
3
Ponteiros inteligentes têm ambos. (métodos na classe de ponteiro inteligente) e -> (métodos no tipo subjacente).
precisa saber é o seguinte
1
@ user6105 A declaração da Orion Edwards é realmente 100% verdadeira. "acessar membros do [o] ponteiro não referenciado" Um ponteiro não possui nenhum membro. O objeto ao qual o ponteiro se refere possui membros, e o acesso a esses é exatamente o que ->fornece referências a ponteiros, assim como o próprio ponteiro.
precisa saber é o seguinte
1
por isso é que .e ->tem algo a ver com o vi e emacs :)
artm
10
@artM - era uma piada e provavelmente não faz sentido para falantes de inglês não nativos. Me desculpe. Para explicar, se o vi é melhor que o emacs é inteiramente subjetivo. Algumas pessoas pensam que o vi é muito superior e outras pensam exatamente o oposto. Da mesma forma, eu acho que usando .é melhor do que usar ->, mas como vi vs Emacs, é inteiramente subjetiva e você não pode provar nada
Orion Edwards
74

As referências são muito semelhantes aos ponteiros, mas são criadas especificamente para ajudar na otimização de compiladores.

  • As referências são projetadas de modo que seja substancialmente mais fácil para o compilador rastrear quais aliases de referência e quais variáveis. Duas características principais são muito importantes: nenhuma "aritmética de referência" e nenhuma reatribuição de referências. Isso permite que o compilador descubra quais referências fazem alias a quais variáveis ​​no tempo de compilação.
  • As referências podem se referir a variáveis ​​que não possuem endereços de memória, como aquelas que o compilador escolhe colocar nos registradores. Se você pegar o endereço de uma variável local, é muito difícil para o compilador colocá-lo em um registro.

Como um exemplo:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

Um compilador otimizador pode perceber que estamos acessando um monte de [0] e [1]. Adoraria otimizar o algoritmo para:

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

Para fazer essa otimização, é necessário provar que nada pode mudar de matriz [1] durante a chamada. Isso é bastante fácil de fazer. i nunca é menor que 2, portanto, o array [i] nunca pode se referir ao array [1]. maybeModify () recebe a0 como referência (aliasing array [0]). Como não há aritmética de "referência", o compilador apenas precisa provar que talvezModify nunca obtém o endereço de x, e provou que nada muda de matriz [1].

Ele também tem que provar que não há como uma chamada futura ler / gravar um [0] enquanto tivermos uma cópia temporária do registro em a0. Isso costuma ser trivial para provar, porque em muitos casos é óbvio que a referência nunca é armazenada em uma estrutura permanente como uma instância de classe.

Agora faça o mesmo com ponteiros

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

O comportamento é o mesmo; só que agora é muito mais difícil provar que o talvezModify nunca modifica o array [1], porque já o indicamos; O gato está fora da bolsa. Agora ele tem que fazer a prova muito mais difícil: uma análise estática do maybeModify para provar que nunca grava em & x + 1. Ele também tem que provar que nunca salva um ponteiro que possa se referir ao array [0], que é apenas tão complicado.

Os compiladores modernos estão cada vez melhores na análise estática, mas é sempre bom ajudá-los e usar referências.

É claro que, exceto por otimizações inteligentes, os compiladores realmente transformarão referências em indicadores quando necessário.

EDIT: Cinco anos após postar esta resposta, encontrei uma diferença técnica real em que as referências são diferentes do que apenas uma maneira diferente de ver o mesmo conceito de endereçamento. As referências podem modificar a vida útil dos objetos temporários de uma maneira que os ponteiros não podem.

F createF(int argument);

void extending()
{
    const F& ref = createF(5);
    std::cout << ref.getArgument() << std::endl;
};

Objetos normalmente temporários, como o criado pela chamada para, createF(5)são destruídos no final da expressão. No entanto, vinculando esse objeto a uma referência, o refC ++ estenderá a vida útil desse objeto temporário até refficar fora do escopo.

Cort Ammon
fonte
É verdade que o corpo precisa ser visível. No entanto, determinar que maybeModifynão leva o endereço de qualquer coisa relacionada xé substancialmente mais fácil do que provar que um monte de aritmética de ponteiro não ocorre.
Cort Ammon
Acredito que o otimizador já faz que "um monte de indicadores aritméticos não ocorra" verifique por vários outros motivos.
precisa saber é o seguinte
"As referências são muito semelhantes aos ponteiros" - semanticamente, em contextos apropriados - mas em termos de código gerado, apenas em algumas implementações e não através de qualquer definição / requisito. Sei que você apontou isso e não discordo de nenhuma das suas postagens em termos práticos, mas já temos muitos problemas com pessoas lendo muito em descrições abreviadas como 'referências são como / geralmente implementadas como ponteiros' .
Sublinhado #
Tenho a sensação de que alguém erroneamente marcada como comentário obsoleto um longo das linhas de void maybeModify(int& x) { 1[&x]++; }que os outros comentários acima estão discutindo
Ben Voigt
69

Na verdade, uma referência não é realmente como um ponteiro.

Um compilador mantém "referências" a variáveis, associando um nome a um endereço de memória; esse é o trabalho de traduzir qualquer nome de variável para um endereço de memória durante a compilação.

Ao criar uma referência, você só diz ao compilador que atribui outro nome à variável de ponteiro; é por isso que as referências não podem "apontar para nulo", porque uma variável não pode ser e não ser.

Ponteiros são variáveis; eles contêm o endereço de alguma outra variável ou podem ser nulos. O importante é que um ponteiro tenha um valor, enquanto uma referência possui apenas uma variável à qual está fazendo referência.

Agora, alguma explicação do código real:

int a = 0;
int& b = a;

Aqui você não está criando outra variável que aponta para a; você está apenas adicionando outro nome ao conteúdo da memória que contém o valor de a. Esta memória tem agora dois nomes, ae b, e que pode ser dirigida usando o nome.

void increment(int& n)
{
    n = n + 1;
}

int a;
increment(a);

Ao chamar uma função, o compilador geralmente gera espaços de memória para os argumentos a serem copiados. A assinatura da função define os espaços que devem ser criados e fornece o nome que deve ser usado para esses espaços. Declarar um parâmetro como referência apenas informa ao compilador para usar o espaço de memória variável de entrada em vez de alocar um novo espaço de memória durante a chamada do método. Pode parecer estranho dizer que sua função estará manipulando diretamente uma variável declarada no escopo de chamada, mas lembre-se de que, ao executar código compilado, não há mais escopo; há apenas memória simples e seu código de função pode manipular qualquer variável.

Agora, pode haver alguns casos em que seu compilador pode não ser capaz de conhecer a referência ao compilar, como ao usar uma variável externa. Portanto, uma referência pode ou não ser implementada como um ponteiro no código subjacente. Mas nos exemplos que eu lhe dei, provavelmente não será implementado com um ponteiro.

Vincent Robert
fonte
2
Uma referência é uma referência ao valor l, não necessariamente a uma variável. Por isso, é muito mais próximo de um ponteiro do que de um alias real (uma construção em tempo de compilação). Os exemplos de expressões que podem ser mencionados são * p * p ou mesmo ++
5
Certo, eu estava apenas apontando o fato de que uma referência nem sempre empurra uma nova variável na pilha da maneira que um novo ponteiro faz.
Vincent Robert
1
@VincentRobert: Ele funcionará da mesma forma que um ponteiro ... se a função estiver embutida, a referência e o ponteiro serão otimizados. Se houver uma chamada de função, o endereço do objeto precisará ser passado para a função.
Ben Voigt
1
int * p = NULL; int & r = * p; referência apontando para NULL; if (r) {} -> boOm;)
sree
2
Esse foco no estágio de compilação parece bom, até você lembrar que as referências podem ser passadas em tempo de execução, quando o alias estático sai da janela. (E, em seguida, as referências são geralmente implementadas como ponteiros, mas o padrão não requer este método.)
underscore_d
45

Uma referência nunca pode ser NULL.

Cole Johnson
fonte
10
Veja a resposta de Mark Ransom para um contra-exemplo. Este é o mito mais frequentemente afirmado sobre referências, mas é um mito. A única garantia que você tem pelo padrão é que você tenha UB imediatamente quando tiver uma referência NULL. Mas isso é semelhante a dizer "Este carro é seguro, nunca pode sair da estrada. (Nós não assumimos nenhuma responsabilidade pelo que pode acontecer se você o desviar da estrada de qualquer maneira. Ele pode explodir.)"
cmaster Reintegrar monica
17
@ master: Em um programa válido , uma referência não pode ser nula. Mas um ponteiro pode. Isso não é um mito, isso é um fato.
user541686
8
@Mehrdad Sim, programas válidos permanecem na estrada. Mas não há barreira de tráfego para impor o que seu programa realmente faz. Grandes partes da estrada estão realmente faltando marcações. Portanto, é extremamente fácil sair da estrada à noite. E é crucial para depurar esses erros que você sabe que isso pode acontecer: a referência nula pode se propagar antes de travar seu programa, assim como um ponteiro nulo. E quando você tem um código como void Foo::bar() { virtual_baz(); }esse segfaults. Se você não estiver ciente de que as referências podem ser nulas, não será possível rastrear o nulo até sua origem.
cmaster - reinstate monica
4
int * p = NULL; int & r = * p; referência apontando para NULL; if (r) {} -> boOm;) -
sree
10
@sree int &r=*p;é um comportamento indefinido. Nesse ponto, você não tem um "apontar referência a NULL," você tem um programa que já não pode ser fundamentado sobre a todos .
Cdhowie 28/03
35

Enquanto referências e ponteiros são usados ​​para acessar indiretamente outro valor, há duas diferenças importantes entre referências e ponteiros. A primeira é que uma referência sempre se refere a um objeto: é um erro definir uma referência sem inicializá-la. O comportamento da atribuição é a segunda diferença importante: atribuir a uma referência altera o objeto ao qual a referência está vinculada; não religa a referência a outro objeto. Uma vez inicializada, uma referência sempre se refere ao mesmo objeto subjacente.

Considere esses dois fragmentos de programa. No primeiro, atribuímos um ponteiro para outro:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

Após a atribuição, ival, o objeto endereçado por pi permanece inalterado. A atribuição altera o valor de pi, fazendo com que aponte para um objeto diferente. Agora considere um programa semelhante que atribui duas referências:

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

Essa atribuição altera ival, o valor referenciado por ri, e não a própria referência. Após a atribuição, as duas referências ainda se referem aos seus objetos originais, e o valor desses objetos agora também é o mesmo.

Kunal Vyas
fonte
"uma referência sempre se refere a um objeto" é apenas completamente falso
Ben Voigt
32

Há uma diferença semântica que pode parecer esotérica se você não estiver familiarizado com o estudo de linguagens de computador de maneira abstrata ou mesmo acadêmica.

No nível mais alto, a idéia das referências é que elas sejam "aliases" transparentes. Seu computador pode usar um endereço para fazê-los funcionar, mas você não deve se preocupar com isso: você deve pensar neles como "apenas outro nome" para um objeto existente e a sintaxe reflete isso. Eles são mais rígidos que os ponteiros, de modo que seu compilador poderá avisá-lo com mais segurança quando você estiver prestes a criar uma referência de oscilação, do que quando estiver prestes a criar um ponteiro de oscilação.

Além disso, existem algumas diferenças práticas entre ponteiros e referências. A sintaxe para usá-los é obviamente diferente e você não pode "recolocar" as referências, ter referências ao nada ou ter ponteiros para as referências.

Raças de leveza em órbita
fonte
27

Uma referência é um alias para outra variável, enquanto um ponteiro mantém o endereço de memória de uma variável. As referências são geralmente usadas como parâmetros de função, para que o objeto passado não seja a cópia, mas o próprio objeto.

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 
fatma.ekici
fonte
20

Não importa quanto espaço consuma, pois você não pode ver nenhum efeito colateral (sem executar o código) do espaço que ocuparia.

Por outro lado, uma grande diferença entre referências e ponteiros é que os temporários atribuídos às referências const permanecem vivos até que a referência const fique fora do escopo.

Por exemplo:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

irá imprimir:

in scope
scope_test done!

Esse é o mecanismo de linguagem que permite que o ScopeGuard funcione.

MSN
fonte
1
Você não pode usar o endereço de uma referência, mas isso não significa que eles não ocupam espaço fisicamente. Exceto otimizações, eles certamente podem.
Lightness Races in Orbit
2
Não obstante o impacto, "Uma referência na pilha não ocupa nenhum espaço" é evidentemente falsa.
Lightness Races in Orbit
1
@ Tomlak, bem, isso depende também do compilador. Mas sim, dizer que é um pouco confuso. Suponho que seria menos confuso apenas remover isso.
26411 MSN
1
Em qualquer caso específico, pode ou não. Então "não", pois uma afirmação categórica está errada. É isso que eu estou dizendo. :) [Não me lembro do que o padrão diz sobre o assunto; as regras dos membros de referência podem transmitir uma regra geral de "referências podem ocupar espaço", mas não tenho minha cópia do padrão comigo aqui na praia: D]
Leveza raças em órbita
20

Isso é baseado no tutorial . O que está escrito torna mais claro:

>>> The address that locates a variable within memory is
    what we call a reference to that variable. (5th paragraph at page 63)

>>> The variable that stores the reference to another
    variable is what we call a pointer. (3rd paragraph at page 64)

Simplesmente lembrar que,

>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)

Além disso, como podemos nos referir a quase qualquer tutorial de ponteiro, um ponteiro é um objeto suportado pela aritmética de ponteiros, o que torna o ponteiro semelhante a uma matriz.

Veja a seguinte declaração,

int Tom(0);
int & alias_Tom = Tom;

alias_Tompode ser entendido como um alias of a variable(diferente de typedef, o que é alias of a type) Tom. Também é bom esquecer que a terminologia de tal afirmação é criar uma referência de Tom.

Vida
fonte
1
E se uma classe tiver uma variável de referência, ela deverá ser inicializada com um nullptr ou um objeto válido na lista de inicialização.
Misgevolution
1
A redação desta resposta é muito confusa para que seja de uso real. Além disso, @Misgevolution, você está recomendando seriamente aos leitores que inicializem uma referência com a nullptr? Você já leu alguma outra parte deste tópico ou ...?
Sublinhado #
1
Desculpe, desculpe pela coisa estúpida que eu disse. Eu devo ter sido privado de sono a essa hora. 'inicializar com nullptr' está totalmente errado.
Misgevolution
19

Uma referência não é outro nome dado a alguma memória. É um ponteiro imutável que é automaticamente referenciado no uso. Basicamente, tudo se resume a:

int& j = i;

Torna-se internamente

int* const j = &i;
alam do tanweer
fonte
13
Não é isso que o Padrão C ++ diz, e não é necessário que o compilador implemente referências da maneira descrita por sua resposta.
jogojapan
@jogojapan: Qualquer maneira que seja válida para um compilador C ++ implementar uma referência também é uma maneira válida de implementar um constponteiro. Essa flexibilidade não prova que há uma diferença entre uma referência e um ponteiro.
Ben Voigt
2
@BenVoigt Pode ser verdade que qualquer implementação válida de uma também seja válida, mas isso não segue de maneira óbvia as definições desses dois conceitos. Uma boa resposta teria começado com as definições e demonstrado por que a afirmação de que os dois são basicamente os mesmos é verdadeira. Essa resposta parece ser algum tipo de comentário sobre algumas das outras respostas.
jogojapan
Uma referência é outro nome dado a um objeto. O compilador tem permissão para ter qualquer tipo de implementação, desde que você não saiba a diferença, isso é conhecido como regra "como se". A parte importante aqui é que você não pode dizer a diferença. Se você descobrir que um ponteiro não possui armazenamento, o compilador está com erro. Se você descobrir que uma referência não possui armazenamento, o compilador ainda está em conformidade.
Sp2danny 06/09
18

A resposta direta

O que é uma referência em C ++? Alguma instância específica do tipo que não é um tipo de objeto .

O que é um ponteiro em C ++? Alguma instância específica do tipo que é um tipo de objeto .

Na definição ISO C ++ do tipo de objeto :

Um objecto tipo é um (possivelmente cv -qualified) tipo que não é um tipo de função e não um tipo de referência, e não cv vazio.

Pode ser importante saber, o tipo de objeto é uma categoria de nível superior do universo de tipos em C ++. A referência também é uma categoria de nível superior. Mas ponteiro não é.

Ponteiros e referências são mencionados juntos no contexto do tipo composto . Isso se deve basicamente à natureza da sintaxe do declarador herdada de (e estendida) C, que não possui referências. (Além disso, há mais de um tipo de declarador de referências desde C ++ 11, enquanto os ponteiros ainda são "unificados": &+ &&vs *..) Portanto, esboçar uma linguagem específica por "extensão" com estilo semelhante de C nesse contexto é algo razoável . (Eu ainda argumentam que a sintaxe de declarators resíduos a expressividade sintática muito , faz com que ambos os usuários humanos e implementações frustrante. Assim, todos eles não são qualificados para ser built-in em um novo design linguagem. Este é um tema totalmente diferente sobre o design do PL, no entanto.)

Caso contrário, é insignificante que os ponteiros possam ser qualificados como tipos específicos de tipos com referências juntos. Eles simplesmente compartilham poucas propriedades comuns além da semelhança de sintaxe, portanto, não há necessidade de reuni-las na maioria dos casos.

Observe que as instruções acima mencionam apenas "ponteiros" e "referências" como tipos. Há algumas perguntas interessadas sobre suas instâncias (como variáveis). Também existem muitos equívocos.

As diferenças das categorias de nível superior já podem revelar muitas diferenças concretas não vinculadas diretamente aos ponteiros:

  • Os tipos de objetos podem ter cvqualificadores de nível superior . Referências não podem.
  • Variáveis ​​de tipos de objetos ocupam armazenamento de acordo com a semântica abstrata da máquina . As referências não ocupam espaço de armazenamento (consulte a seção sobre conceitos errôneos abaixo para obter detalhes).
  • ...

Mais algumas regras especiais sobre referências:

  • Declaradores compostos são mais restritivos em referências.
  • As referências podem entrar em colapso .
    • Regras especiais sobre &&parâmetros (como as "referências de encaminhamento") baseadas no recolhimento de referência durante a dedução de parâmetro do modelo permitem o "encaminhamento perfeito" dos parâmetros.
  • As referências possuem regras especiais na inicialização. O tempo de vida da variável declarada como um tipo de referência pode ser diferente dos objetos comuns via extensão.
    • BTW, alguns outros contextos, como a inicialização envolvendo, std::initializer_listseguem algumas regras semelhantes de extensão da vida útil de referência. É outra lata de vermes.
  • ...

Os equívocos

Açúcar sintático

Eu sei que as referências são açúcar sintático, então o código é mais fácil de ler e escrever.

Tecnicamente, isso está totalmente errado. As referências não são um açúcar sintático de nenhum outro recurso no C ++, porque não podem ser substituídas exatamente por outros recursos sem diferenças semânticas.

(Da mesma forma, as expressões lambda s não são um açúcar sintático de nenhum outro recurso no C ++ porque não pode ser simulado com propriedades "não especificadas", como a ordem de declaração das variáveis ​​capturadas , o que pode ser importante porque a ordem de inicialização dessas variáveis ​​pode ser significativo.)

O C ++ possui apenas alguns tipos de açúcares sintáticos nesse sentido estrito. Uma instância é (herdada de C) o operador interno (sem sobrecarga) [], que é definido exatamente com as mesmas propriedades semânticas de formas específicas de combinação sobre o operador interno unário *e binário+ .

Armazenamento

Portanto, um ponteiro e uma referência usam a mesma quantidade de memória.

A afirmação acima está simplesmente errada. Para evitar tais conceitos errôneos, observe as regras ISO C ++:

De [intro.object] / 1 :

... Um objeto ocupa uma região de armazenamento em seu período de construção, durante sua vida útil e em seu período de destruição. ...

De [dcl.ref] / 4 :

Não é especificado se uma referência requer ou não armazenamento.

Observe que essas são propriedades semânticas .

Pragmáticos

Mesmo que os ponteiros não sejam qualificados o suficiente para serem reunidos com referências no sentido do design da linguagem, ainda existem alguns argumentos que tornam discutível a escolha entre eles em outros contextos, por exemplo, ao fazer escolhas nos tipos de parâmetros.

Mas essa não é a história toda. Quero dizer, há mais coisas do que indicadores versus referências que você deve considerar.

Se você não precisa se ater a essas escolhas superespecíficas, na maioria dos casos a resposta é curta: você não precisa usar ponteiros, portanto não precisa . Os ponteiros geralmente são ruins o suficiente porque implicam muitas coisas que você não espera e dependerão de muitas suposições implícitas que comprometem a capacidade de manutenção e (até) a portabilidade do código. Contar desnecessariamente com ponteiros é definitivamente um estilo ruim e deve ser evitado no sentido de C ++ moderno. Reconsidere seu objetivo e você finalmente descobrirá que o ponteiro é o recurso dos últimos tipos na maioria dos casos.

  • Às vezes, as regras de idioma exigem explicitamente tipos específicos para serem usados. Se você deseja usar esses recursos, siga as regras.
    • Construtores de cópia requerem tipos específicos de cv - &tipo de referência como o 1º tipo de parâmetro. (E geralmente deve ser constqualificado.)
    • Construtores movimento exigem tipos específicos de cv - &&tipo de referência como o 1º tipo de parâmetro. (E geralmente não deve haver qualificadores.)
    • Sobrecargas específicas de operadores requerem tipos de referência ou não. Por exemplo:
      • Sobrecarregado operator=como funções membro especiais requer tipos de referência semelhantes ao 1º parâmetro dos construtores de copiar / mover.
      • O Postfix ++requer fictício int.
      • ...
  • Se você souber que a passagem por valor (ou seja, o uso de tipos não de referência) é suficiente, use-a diretamente, principalmente ao usar uma implementação que suporte elision de cópia obrigatória do C ++ 17. ( Aviso : no entanto, raciocinar exaustivamente sobre a necessidade pode ser muito complicado .)
  • Se você deseja operar algumas alças com propriedade, use ponteiros inteligentes como unique_ptre shared_ptr(ou mesmo os de homebrew, se você precisar que sejam opacos ), em vez de ponteiros brutos.
  • Se você estiver fazendo algumas iterações em um intervalo, use iteradores (ou alguns intervalos que ainda não foram fornecidos pela biblioteca padrão), em vez de ponteiros brutos, a menos que você esteja convencido de que os ponteiros brutos farão melhor (por exemplo, para menos dependências de cabeçalho) em situações muito específicas. casos.
  • Se você sabe que a passagem por valor é suficiente e deseja alguma semântica nula explícita, use wrapper like std::optional, em vez de ponteiros brutos.
  • Se você sabe que a passagem por valor não é ideal pelos motivos acima, e não deseja semântica nula, use as referências {lvalue, rvalue, forwarding}.
  • Mesmo quando você deseja semântica como ponteiro tradicional, geralmente há algo mais apropriado, como observer_ptrno TS Fundamental da Biblioteca.

As únicas exceções não podem ser contornadas no idioma atual:

  • Ao implementar ponteiros inteligentes acima, pode ser necessário lidar com ponteiros brutos.
  • Rotinas específicas de interoperação de linguagem requerem ponteiros, como operator new. (No entanto, cv - void*ainda é bem diferente e mais seguro em comparação com os ponteiros de objetos comuns, porque exclui aritmética inesperada de ponteiros, a menos que você esteja confiando em alguma extensão não conforme, void*como a GNU.
  • Os ponteiros de função podem ser convertidos a partir de expressões lambda sem capturas, enquanto as referências de função não podem. Você precisa usar ponteiros de função no código não genérico para esses casos, mesmo que você não queira deliberadamente valores nulos.

Portanto, na prática, a resposta é tão óbvia: em caso de dúvida, evite apontadores . Você precisa usar ponteiros apenas quando houver motivos muito explícitos de que nada mais é mais apropriado. Exceto alguns casos excepcionais mencionados acima, essas opções quase sempre não são puramente específicas de C ++ (mas provavelmente específicas de implementação de linguagem). Tais instâncias podem ser:

  • Você precisa servir para APIs de estilo antigo (C).
  • Você precisa atender aos requisitos da ABI de implementações específicas do C ++.
  • É necessário interoperar em tempo de execução com diferentes implementações de idioma (incluindo vários assemblies, tempo de execução de idioma e FFI de algumas linguagens de cliente de alto nível) com base em suposições de implementações específicas.
  • Você precisa melhorar a eficiência da tradução (compilação e vinculação) em alguns casos extremos.
  • Você deve evitar o inchaço dos símbolos em alguns casos extremos.

Advertências de neutralidade de idioma

Se você vir a pergunta por meio de algum resultado de pesquisa do Google (não específico para C ++) , é muito provável que este seja o lugar errado.

Referências em C ++ é muito "estranho", pois não é essencialmente de primeira classe: eles serão tratados como os objetos ou as funções a ser referido para que eles não têm chance de apoiar algumas operações de primeira classe como estar operando esquerdo do operador de acesso de membro independentemente ao tipo do objeto referido. Outros idiomas podem ou não ter restrições semelhantes em suas referências.

As referências em C ++ provavelmente não preservarão o significado em diferentes idiomas. Por exemplo, as referências em geral não implicam propriedades não nulas em valores como eles em C ++, portanto, essas suposições podem não funcionar em outras linguagens (e você encontrará contra-exemplos com bastante facilidade, por exemplo, Java, C #, ...).

Ainda pode haver algumas propriedades comuns entre referências em diferentes linguagens de programação em geral, mas vamos deixar para outras questões no SO.

(Uma observação lateral: a questão pode ser significativa mais cedo do que qualquer linguagem "C", como ALGOL 68 vs. PL / I. )

FrankHB
fonte
17

Uma referência a um ponteiro é possível em C ++, mas o inverso não é possível significa que um ponteiro para uma referência não é possível. Uma referência a um ponteiro fornece uma sintaxe mais limpa para modificar o ponteiro. Veja este exemplo:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

E considere a versão C do programa acima. Em C, você precisa usar ponteiro para ponteiro (indireto múltiplo), e isso gera confusão e o programa pode parecer complicado.

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

Visite o seguinte para obter mais informações sobre referência ao ponteiro:

Como eu disse, um ponteiro para uma referência não é possível. Tente o seguinte programa:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}
Destruidor
fonte
16

Eu uso referências, a menos que eu precise de um destes:

  • Ponteiros nulos podem ser usados ​​como um valor sentinela, geralmente uma maneira barata de evitar sobrecarga de função ou uso de um bool.

  • Você pode fazer aritmética em um ponteiro. Por exemplo,p += offset;

Aardvark
fonte
5
Você pode escrever &r + offsetonde rfoi declarado como referência
MM
15

Há uma diferença fundamental entre ponteiros e referências que eu não vi ninguém mencionar: as referências ativam a semântica de passagem por referência nos argumentos das funções. Os ponteiros, embora inicialmente não sejam visíveis, não fornecem: eles fornecem apenas semânticas de passagem por valor. Isso foi muito bem descrito neste artigo .

Atenciosamente, & rzej

Andrzej
fonte
1
Referências e ponteiros são alças. Ambos fornecem a semântica em que seu objeto é passado por referência, mas o identificador é copiado. Não faz diferença. (Há outras maneiras de ter alças também, como uma chave para a pesquisa em um dicionário)
Ben Voigt
Eu também costumava pensar assim. Mas veja o artigo vinculado descrevendo por que não é assim.
Andrzej
2
@Andrzj: Essa é apenas uma versão muito longa da frase única no meu comentário: O identificador é copiado.
precisa
Preciso de mais explicações sobre "O identificador é copiado". Eu entendo uma idéia básica, mas acho que fisicamente a referência e o ponteiro apontam o local da memória da variável. É como alias armazena a variável value e atualiza-a como o valor da variável é change ou algo mais? Sou novato e, por favor, não o sinalize como uma pergunta estúpida.
Asim
1
@Andrzej False. Nos dois casos, a passagem por valor está ocorrendo. A referência é passada por valor e o ponteiro é passado por valor. Dizer o contrário confunde novatos.
Route de milhas
14

Correndo o risco de aumentar a confusão, quero incluir alguma entrada, tenho certeza de que depende principalmente de como o compilador implementa as referências, mas no caso do gcc, a ideia de que uma referência pode apontar apenas para uma variável na pilha não está realmente correto, veja isto por exemplo:

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

O que gera isso:

THIS IS A STRING
0xbb2070 : 0xbb2070

Se você notar que mesmo os endereços de memória são exatamente os mesmos, significa que a referência está apontando com êxito para uma variável na pilha! Agora, se você realmente quer ficar esquisito, isso também funciona:

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

O que gera isso:

THIS IS A STRING

Portanto, uma referência é um ponteiro sob o capô, ambos estão apenas armazenando um endereço de memória, onde o endereço está apontando é irrelevante, o que você acha que aconteceria se eu chamasse std :: cout << str_ref; DEPOIS de chamar delete & str_ref? Bem, obviamente ele compila bem, mas causa uma falha de segmentação no tempo de execução porque não está mais apontando para uma variável válida, temos essencialmente uma referência quebrada que ainda existe (até cair fora do escopo), mas é inútil.

Em outras palavras, uma referência não passa de um ponteiro que abstrai a mecânica do ponteiro, tornando-o mais seguro e fácil de usar (sem matemática acidental do ponteiro, sem misturar '.' E '->' etc.), supondo que você não tente nenhuma bobagem como os meus exemplos acima;)

Agora, independentemente de como um compilador lida com referências, ele sempre terá algum tipo de ponteiro sob o capô, porque uma referência deve se referir a uma variável específica em um endereço de memória específico para que funcione conforme o esperado, não há como contornar isso (portanto, o termo 'referência').

A única regra importante que é importante lembrar com referências é que elas devem ser definidas no momento da declaração (com exceção de uma referência em um cabeçalho, nesse caso, ela deve ser definida no construtor, depois que o objeto em que estiver contido for construído é tarde demais para defini-lo).

Lembre-se, meus exemplos acima são apenas isso, exemplos demonstrando o que é uma referência, você nunca iria querer usar uma referência dessa maneira! Para o uso adequado de uma referência, já existem muitas respostas aqui que atingiram a unha na cabeça

Tory
fonte
14

Outra diferença é que você pode ter ponteiros para um tipo de void (e isso significa ponteiro para qualquer coisa), mas as referências a void são proibidas.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

Não posso dizer que estou realmente feliz com essa diferença em particular. Eu preferiria que isso fosse permitido com a referência de significado para qualquer coisa com um endereço e, caso contrário, o mesmo comportamento para referências. Isso permitiria definir alguns equivalentes de funções da biblioteca C, como memcpy, usando referências.

kriss
fonte
13

Além disso, uma referência que é um parâmetro para uma função embutida pode ser manipulada de maneira diferente de um ponteiro.

void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
    int testptr=0;
    increment(&testptr);
}
void increftest()
{
    int testref=0;
    increment(testref);
}

Muitos compiladores ao alinhar a versão do ponteiro forçarão uma gravação na memória (estamos pegando o endereço explicitamente). No entanto, eles deixarão a referência em um registro mais ideal.

Obviamente, para funções que não estão embutidas, o ponteiro e a referência geram o mesmo código e é sempre melhor passar intrínsecos por valor do que por referência, se não forem modificados e retornados pela função.

Adisak
fonte
11

Outro uso interessante de referências é fornecer um argumento padrão de um tipo definido pelo usuário:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

O sabor padrão usa o 'bind const reference a um temporário' aspecto das referências.

Don Wakefield
fonte
11

Este programa pode ajudar a compreender a resposta da pergunta. Este é um programa simples de uma referência "j" e um ponteiro "ptr" apontando para a variável "x".

#include<iostream>

using namespace std;

int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"

cout << "x=" << x << endl;

cout << "&x=" << &x << endl;

cout << "j=" << j << endl;

cout << "&j=" << &j << endl;

cout << "*ptr=" << *ptr << endl;

cout << "ptr=" << ptr << endl;

cout << "&ptr=" << &ptr << endl;
    getch();
}

Execute o programa e dê uma olhada na saída e você entenderá.

Além disso, reserve 10 minutos e assista a este vídeo: https://www.youtube.com/watch?v=rlJrrGV0iOg

Arlene Batada
fonte
11

Sinto que ainda há outro ponto que não foi abordado aqui.

Diferentemente dos ponteiros, as referências são sintaticamente equivalentes ao objeto a que se referem, ou seja, qualquer operação que possa ser aplicada a um objeto funciona para uma referência e com a mesma sintaxe exata (a exceção é, obviamente, a inicialização).

Embora isso possa parecer superficial, acredito que essa propriedade seja crucial para vários recursos do C ++, por exemplo:

  • Templates . Desde parâmetros do modelo são digitados-pato, propriedades sintáticas de um tipo é tudo o que importa, por isso, muitas vezes o mesmo modelo pode ser usado tanto com Te T&.
    (ou std::reference_wrapper<T>que ainda depende de uma conversão implícita para T&)
    Modelos que abrangem ambos T&e T&&são ainda mais comuns.

  • Valores . Considere a declaração str[0] = 'X';Sem referências, isso só funcionaria para c-strings ( char* str). Retornar o caractere por referência permite que as classes definidas pelo usuário tenham a mesma notação.

  • Copie construtores . Sintaticamente, faz sentido passar objetos para copiar construtores, e não ponteiros para objetos. Mas simplesmente não há como um construtor de cópia pegar um objeto por valor - isso resultaria em uma chamada recursiva para o mesmo construtor de cópia. Isso deixa as referências como a única opção aqui.

  • Sobrecargas do operador . Com referências, é possível introduzir indiretamente uma chamada de operador - por exemplo, operator+(const T& a, const T& b)mantendo a mesma notação de infixo. Isso também funciona para funções sobrecarregadas regulares.

Esses pontos fortalecem uma parte considerável do C ++ e da biblioteca padrão, portanto, essa é uma propriedade bastante importante das referências.

Ap31
fonte
" elenco implícito " um elenco é uma construção de sintaxe, existe na gramática; um elenco é sempre explícita
curiousguy
9

Há uma diferença não técnica muito importante entre ponteiros e referências: um argumento passado para uma função por ponteiro é muito mais visível do que um argumento passado para uma função por referência não const. Por exemplo:

void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);

void bar() {
    std::string x;
    fn1(x);  // Cannot modify x
    fn2(x);  // Cannot modify x (without const_cast)
    fn3(x);  // CAN modify x!
    fn4(&x); // Can modify x (but is obvious about it)
}

De volta a C, uma chamada que parece fn(x)apenas pode ser transmitida por valor; portanto, definitivamente não pode ser modificada x; Para modificar um argumento, você precisaria passar um ponteiro fn(&x). Portanto, se um argumento não fosse precedido por um, &você sabia que não seria modificado. (O inverso, &significa que modificado, não era verdadeiro porque às vezes você precisava passar grandes estruturas somente leitura por constponteiro.)

Alguns argumentam que esse é um recurso tão útil na leitura de código, que os parâmetros do ponteiro sempre devem ser usados ​​para parâmetros modificáveis, em vez de não constreferências, mesmo que a função nunca espere a nullptr. Ou seja, essas pessoas argumentam que assinaturas de funções como fn3()acima não devem ser permitidas. As diretrizes de estilo C ++ do Google são um exemplo disso.

Arthur Tacca
fonte
8

Talvez algumas metáforas ajudem; No contexto do espaço na tela da sua área de trabalho -

  • Uma referência requer que você especifique uma janela real.
  • Um ponteiro requer a localização de um pedaço de espaço na tela para garantir que ele contenha zero ou mais instâncias desse tipo de janela.
George R
fonte
6

Diferença entre ponteiro e referência

Um ponteiro pode ser inicializado como 0 e uma referência não. De fato, uma referência também deve se referir a um objeto, mas um ponteiro pode ser o ponteiro nulo:

int* p = 0;

Mas não podemos ter int& p = 0;e também int& p=5 ;.

De fato, para fazê-lo corretamente, devemos ter declarado e definido um objeto no início, para que possamos fazer uma referência a esse objeto, para que a implementação correta do código anterior seja:

Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;

Outro ponto importante é que podemos fazer a declaração do ponteiro sem inicialização, no entanto, nada pode ser feito no caso de referência, que deve sempre fazer referência a variável ou objeto. No entanto, esse uso de um ponteiro é arriscado; portanto, geralmente verificamos se o ponteiro está realmente apontando para algo ou não. No caso de uma referência, essa verificação não é necessária, porque já sabemos que a referência a um objeto durante a declaração é obrigatória.

Outra diferença é que o ponteiro pode apontar para outro objeto, no entanto, a referência sempre faz referência ao mesmo objeto, vamos dar este exemplo:

Int a = 6, b = 5;
Int& rf = a;

Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.

rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased

Outro ponto: quando temos um modelo como um modelo STL, esse tipo de modelo sempre retorna uma referência, não um ponteiro, para facilitar a leitura ou a atribuição de novo valor usando o operador []:

Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="
dhokar.w
fonte
1
Nós ainda podemos ter const int& i = 0.
Revolver_Ocelot
1
Nesse caso, a referência será usada apenas na leitura. Não podemos modificar essa referência const, mesmo usando "const_cast" porque "const_cast" aceita apenas ponteiro e não referência.
precisa saber é o seguinte
1
const_cast trabalha com referências muito bem: coliru.stacked-crooked.com/a/eebb454ab2cfd570
Revolver_Ocelot
1
você está fazendo uma conversão para referência e não fazendo referência, tente isso; const int & i =; const_cast <int> (i); tento jogar fora a constância da referência para possibilitar a gravação e a atribuição de novo valor à referência, mas isso não é possível. por favor foque !!
precisa saber é o seguinte
5

A diferença é que a variável de ponteiro não constante (que não deve ser confundida com um ponteiro em constante) pode ser alterada em algum momento durante a execução do programa, requer que a semântica de ponteiros seja usada (&, *), enquanto as referências podem ser definidas na inicialização somente (é por isso que você pode defini-los apenas na lista de inicializadores de construtores, mas não de outra forma) e usar a semântica de acesso a valores comuns. Basicamente, foram introduzidas referências para permitir o suporte a sobrecarga de operadores, como eu havia lido em algum livro muito antigo. Como alguém afirmou neste tópico - o ponteiro pode ser definido como 0 ou qualquer valor que você desejar. 0 (NULL, nullptr) significa que o ponteiro é inicializado com nada. É um erro desreferenciar o ponteiro nulo. Mas, na verdade, o ponteiro pode conter um valor que não aponte para algum local correto da memória. As referências, por sua vez, tentam não permitir que um usuário inicialize uma referência a algo que não pode ser referenciado devido ao fato de você sempre fornecer um valor de tipo correto a ele. Embora existam várias maneiras de fazer com que a variável de referência seja inicializada em um local de memória errado - é melhor não aprofundar isso em detalhes. No nível da máquina, o ponteiro e a referência funcionam de maneira uniforme - por meio de ponteiros. Digamos que em referências essenciais há açúcar sintático. As referências rvalue são diferentes disso - são naturalmente objetos de pilha / pilha. Embora existam várias maneiras de fazer com que a variável de referência seja inicializada em um local de memória errado - é melhor não aprofundar isso em detalhes. No nível da máquina, o ponteiro e a referência funcionam de maneira uniforme - por meio de ponteiros. Digamos que em referências essenciais há açúcar sintático. As referências rvalue são diferentes disso - são naturalmente objetos de pilha / pilha. Embora existam várias maneiras de fazer com que a variável de referência seja inicializada em um local de memória errado - é melhor não aprofundar isso em detalhes. No nível da máquina, o ponteiro e a referência funcionam de maneira uniforme - por meio de ponteiros. Digamos que em referências essenciais há açúcar sintático. As referências rvalue são diferentes disso - são naturalmente objetos de pilha / pilha.

Zorgiev
fonte
4

em palavras simples, podemos dizer que uma referência é um nome alternativo para uma variável, enquanto um ponteiro é uma variável que contém o endereço de outra variável. por exemplo

int a = 20;
int &r = a;
r = 40;  /* now the value of a is changed to 40 */

int b =20;
int *ptr;
ptr = &b;  /*assigns address of b to ptr not the value */
Sadhana Singh
fonte