referencias - referências em c++




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

Eu sei que 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?

Resumo das respostas e links abaixo:

  1. Um ponteiro pode ser reatribuído qualquer número de vezes, enquanto uma referência não pode ser reatribuída após a ligação.
  2. Ponteiros podem apontar para lugar nenhum ( NULL ), enquanto uma referência sempre se refere a um objeto.
  3. Você não pode pegar o endereço de uma referência como você pode com ponteiros.
  4. Não há "aritmética de referência" (mas você pode pegar o endereço de um objeto apontado por uma referência e fazer uma aritmética de ponteiro como em &obj + 5 ).

Para esclarecer um equívoco:

O padrão C ++ é muito cuidadoso para evitar ditar como um compilador pode implementar referências, mas todo compilador C ++ implementa referências como ponteiros. Isto é, uma declaração como:

int &ri = i;

se não for totalmente otimizado , aloca a mesma quantidade de armazenamento que um ponteiro e coloca o endereço de i nesse armazenamento.

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

Como uma regra geral,

  • Use referências em parâmetros de função e tipos de retorno para fornecer interfaces úteis e de auto-documentação.
  • Use ponteiros para implementar algoritmos e estruturas de dados.

Interessante ler:


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

Uma referência pode ser considerada como um ponteiro constante (não confundir com um ponteiro para um valor constante!) Com indireto automático, isto é, 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 em referências.

Os programadores C podem não gostar de referências C ++, uma vez que deixará de ser óbvio quando ocorrer uma inflexão ou se um argumento for passado por valor ou por ponteiro sem olhar para as assinaturas de função.

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 do que os ponteiros constantes, exceto nos casos mais triviais - não têm a conveniência da indireção automática e têm uma conotação semântica diferente.

Considere a seguinte declaração da FAQ do C ++ :

Mesmo que uma referência seja frequentemente implementada usando um endereço na linguagem de montagem 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. É o objeto.

Mas se uma referência realmente fosse o objeto, como poderia haver referências pendentes? Em idiomas não gerenciados, é impossível que as referências sejam mais "seguras" do que os ponteiros - geralmente não há uma forma de alias confiáveis ​​nos limites do escopo!

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

Vindo de um segundo plano C, as referências em C ++ podem parecer um pouco tolas, mas ainda assim é necessário usá-las em vez de ponteiros: A indireção automática é conveniente, e as referências tornam-se especialmente úteis quando se lida com RAII - mas não por qualquer segurança percebida vantagem, mas sim porque eles fazem escrever código idiomático menos complicado.

O RAII é um dos conceitos centrais do C ++, mas interage de forma não trivial com a semântica de cópia. Passar objetos por referência evita esses problemas, pois não há cópias envolvidas. Se as referências não estivessem presentes no idioma, você teria que usar ponteiros, que são mais complicados de usar, violando assim o princípio do design de linguagem de que a solução de melhores práticas deveria ser mais fácil que as alternativas.


Diferença entre ponteiro e referência

Um ponteiro pode ser inicializado para 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émint& p=5 ; .

De fato, para fazê-lo corretamente, devemos ter declarado e definido um objeto no primeiro, então podemos fazer uma referência a esse objeto, então a implementação correta do código anterior será:

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

Outro ponto importante é que é possível fazer a declaração do ponteiro sem inicialização, porém isso não pode ser feito em caso de referência, que deve sempre fazer referência a variável ou objeto. No entanto, tal uso de um ponteiro é arriscado, então geralmente nós verificamos se o ponteiro está realmente apontando para algo ou não. No caso de uma referência, tal verificação é necessária, porque 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, mas a referência é sempre fazer referência ao mesmo objeto, vamos pegar 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 de classe sempre retornará uma referência, não um ponteiro, para facilitar a leitura ou atribuir um novo valor usando operator []:

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 "="

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

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

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

O alvo de uma referência não pode ser substituído de forma alguma a partir do UB.

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


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

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

Concedido, é muito mais difícil de fazer com uma referência - mas se você conseguir, você vai arrancar o cabelo para fora tentando encontrá-lo. Referências não são inerentemente seguras em C ++!

Tecnicamente, esta é uma referência inválida , não uma referência nula. C ++ não suporta referências nulas como um 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 , da mesma forma que usar um ponteiro inválido.

O erro real está na desreferência do ponteiro NULL, antes da atribuição a uma referência. Mas eu não estou ciente de nenhum compilador que irá gerar erros nessa condição - o erro se propaga para um ponto mais adiante no código. Isso é o que torna esse problema tão insidioso. Na maioria das vezes, se você desreferenciar um ponteiro NULL, você cai exatamente naquele ponto e não é preciso muita depuração para descobrir isso.

Meu exemplo acima é curto e planejado. 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 do código malformado e, uma vez obtido, você está obtendo um comportamento indefinido. Nunca faz sentido verificar uma referência nula; por exemplo, você pode tentar if(&bar==NULL)... mas o compilador pode otimizar a declaração para fora da existência! Uma referência válida nunca pode ser NULL, portanto, a partir da visão do compilador, a comparação é sempre falsa, e é livre para eliminar a cláusula if como código morto - essa é a essência do comportamento indefinido.

A maneira correta de evitar problemas é evitar a referência a um ponteiro NULL para criar uma referência. Aqui está uma maneira automatizada de conseguir 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 um exame mais antigo desse problema por 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 Expor comportamento indefinido ao tentar portar código para outra plataforma por Raymond Chen.


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

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

Quando você cria uma referência, você apenas informa ao compilador que você atribui outro nome à variável do 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 tem apenas uma variável que está referenciando.

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

int a = 0;
int& b = a;

Aqui você não está criando outra variável que aponte para a ; você está apenas adicionando outro nome ao conteúdo da memória que contém o valor de a . Esta memória agora tem dois nomes, b , e pode ser endereçada usando qualquer 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 da 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 manipulará diretamente uma variável declarada no escopo de chamada, mas lembre-se de que, ao executar o código compilado, não há mais escopo; há apenas memória simples, e seu código de função pode manipular quaisquer variáveis.

Agora pode haver alguns casos em que seu compilador pode não ser capaz de saber 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 lhe dei, provavelmente não será implementado com um ponteiro.


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: estenda a vida útil de um objeto temporário. Em 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 que é resultado da concatenação. Considerando s3_reference em essência torna-se o objeto temporário. É realmente uma referência a um objeto temporário que agora tem o mesmo tempo de vida da referência.

Se você tentar isso sem o const ele deve 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.


Você esqueceu a parte mais importante:

Acesso de membro com ponteiros usa ->
acesso de membros com usos de referências .

foo.bar é claramente superior a foo->bar da mesma forma que o vi é claramente superior ao Emacs :-)


Além disso, uma referência que é um parâmetro para uma função que está embutida pode ser tratada 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 inlining a versão do ponteiro 1 forçarão realmente uma gravação na memória (nós estamos tomando o endereço explicitamente). No entanto, eles deixarão a referência em um registro que seja 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 eles não forem modificados e retornados pela função.


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

Ao contrário dos ponteiros, as referências são sintaticamente equivalentes ao objeto ao qual 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 é crucial para vários recursos do C ++, por exemplo:

  • Templates . Como os parâmetros do modelo são tipificados com duck, as propriedades sintáticas de um tipo são tudo o que importa, portanto, o mesmo modelo pode ser usado com ambos Te T&.
    (ou std::reference_wrapper<T>que ainda depende de um elenco implícito para T&)
    Modelos que cobrem ambos T&e T&&são ainda mais comuns.

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

  • Copiar construtores . Sintaticamente, faz sentido passar objetos para copiar construtores e não ponteiros para objetos. Mas não há maneira de um construtor de cópia obter 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 as referências, é possível introduzir indireção a uma chamada de operador - digamos, operator+(const T& a, const T& b)mantendo a mesma notação de infixação. Isso também funciona para funções sobrecarregadas regulares.

Esses pontos capacitam uma parte considerável do C ++ e da biblioteca padrão, de modo que essa é uma grande propriedade das referências.


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

  • Os ponteiros nulos podem ser usados ​​como um valor sentinela, geralmente um modo barato de evitar a sobrecarga de funções ou o uso de um bool.

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


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 acadêmica.

No nível mais alto, a ideia de referências é que elas são "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 do que os ponteiros, então seu compilador pode avisar com mais segurança quando você está prestes a criar uma referência pendente, do que quando você está prestes a criar um ponteiro pendente.

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 "refazer" referências, ter referências a nada ou ter ponteiros para referências.


Não importa quanto espaço ele ocupe, já que você não pode realmente ver nenhum efeito colateral (sem executar código) de qualquer espaço que ocuparia.

Por outro lado, uma diferença importante entre referências e ponteiros é que os temporários atribuídos às referências constantes residem até que a referência const saia 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");
}

vai imprimir:

in scope
scope_test done!

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


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 aspecto 'vincular referência const a um temporário' de referências.


A diferença é que a variável de ponteiro não constante (que não deve ser confundida com um ponteiro para constante) pode ser alterada em algum momento durante a execução do programa, requer semântica de ponteiro (&, *), enquanto referências podem ser definidas na inicialização somente (é por isso que você pode defini-los apenas na lista de inicializadores de construtor, mas não de outra forma) e usar a semântica de acesso a valores comuns. Basicamente, referências foram introduzidas para permitir o suporte para sobrecarga de operadores, como eu li em algum livro muito antigo. Como alguém afirmou neste thread - ponteiro pode ser definido como 0 ou qualquer valor que você deseja. 0 (NULL, nullptr) significa que o ponteiro é inicializado com nada. É um erro excluir a referência do ponteiro nulo. Mas, na verdade, o ponteiro pode conter um valor que não aponta para algum local de memória correto.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 o valor correto do tipo para ele. Embora existam muitas maneiras de fazer com que a variável de referência seja inicializada em um local incorreto da memória - é melhor que você não consiga aprofundá-la nos detalhes. No nível da máquina, tanto o ponteiro quanto a referência funcionam uniformemente - por meio de ponteiros. Digamos que em referências essenciais o açúcar sintático. As referências de valor são diferentes disso - elas são naturalmente objetos de pilha / heap.Embora existam muitas maneiras de fazer com que a variável de referência seja inicializada em um local incorreto da memória - é melhor que você não consiga aprofundá-la nos detalhes. No nível da máquina, tanto o ponteiro quanto a referência funcionam uniformemente - por meio de ponteiros. Digamos que em referências essenciais o açúcar sintático. As referências de valor são diferentes disso - elas são naturalmente objetos de pilha / heap.Embora existam muitas maneiras de fazer com que a variável de referência seja inicializada em um local incorreto da memória - é melhor que você não consiga aprofundá-la nos detalhes. No nível da máquina, tanto o ponteiro quanto a referência funcionam uniformemente - por meio de ponteiros. Digamos que em referências essenciais o açúcar sintático. As referências de valor são diferentes disso - elas são naturalmente objetos de pilha / heap.


Embora as referências e os ponteiros sejam 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: a atribuição a uma referência altera o objeto ao qual a referência está vinculada; ele não liga novamente a referência a outro objeto. Uma vez inicializado, uma referência sempre se refere ao mesmo objeto subjacente.

Considere estes dois fragmentos de programa. No primeiro, atribuímos um ponteiro a 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 atribua duas referências:

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

Esta 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 a seus objetos originais, e o valor desses objetos é agora o mesmo também.


Este programa pode ajudar na compreensão da 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, poupe 10 minutos e assista a este vídeo: https://www.youtube.com/watch?v=rlJrrGV0iOg


Há uma diferença fundamental entre ponteiros e referências que eu não vi ninguém ter mencionado: referências permitem semântica de passagem por referência em argumentos de função. Ponteiros, embora não seja visível no início não: eles fornecem apenas semântica de passagem por valor. Isso foi muito bem descrito neste artigo .

Atenciosamente, & rzej


Isso é baseado no tutorial . O que está escrito deixa 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 disso,

>>> 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 do mais, como podemos nos referir a quase todos os tutoriais de ponteiro, um ponteiro é um objeto que é suportado pela aritmética de ponteiros, o que torna o ponteiro semelhante a um array.

Olhe a seguinte declaração,

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

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


Uma referência é um alias para outra variável, enquanto um ponteiro contém o endereço de memória de uma variável. 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. 

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ê tem que usar ponteiro para ponteiro (múltipla indireta), e isso leva a 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 a referência ao ponteiro:

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

#include <iostream>
using namespace std;

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




c++-faq