Por que C ++ e Java usam a noção de "referência", mas não no mesmo sentido?


26

No C ++, um argumento de referência para uma função permite que a função faça a referência se referir a outra coisa:

int replacement = 23;

void changeNumberReference(int& reference) {
    reference = replacement;
}

int main() {
    int i = 1;
    std::cout << "i=" << i << "\n"; // i = 1;
    changeNumberReference(i);
    std::cout << "i=" << i << "\n"; // i = 23;
}

Analogamente, um argumento de referência constante para uma função gerará um erro de tempo de compilação se tentarmos alterar a referência:

void changeNumberReference(const int& reference) {
    reference = replacement; // compile-time error: assignment of read-only reference 'reference'
}

Agora, com Java, os documentos dizem que argumentos de funções de tipos não primitivos são referências. Exemplo dos documentos oficiais:

public void moveCircle(Circle circle, int deltaX, int deltaY) {
    // code to move origin of circle to x+deltaX, y+deltaY
    circle.setX(circle.getX() + deltaX);
    circle.setY(circle.getY() + deltaY);

    // code to assign a new reference to circle
    circle = new Circle(0, 0);
}

No círculo, é atribuída uma referência a um novo objeto Circle com x = y = 0. Essa reatribuição não tem permanência, no entanto, porque a referência foi passada por valor e não pode ser alterada.

Para mim, isso não parece nada com referências de C ++. Ele não se assemelha a referências regulares em C ++ porque você não pode fazer referência a outra coisa, e não se assemelha a referências const C ++ porque em Java, o código que mudaria (mas realmente não) a referência não gera uma compilação erro de tempo.

Esse comportamento é mais semelhante aos ponteiros C ++. Você pode usá-lo para alterar os valores dos objetos apontados, mas não pode alterar o valor do ponteiro em uma função. Além disso, como nos ponteiros C ++ (mas não nas referências C ++), em Java, você pode passar "null" como valor para esse argumento.

Então, minha pergunta é: Por que o Java usa a noção de "referência"? É preciso entender que eles não se parecem com referências de C ++? Ou eles realmente se parecem com referências de C ++ e estou perdendo alguma coisa?


10
Observe que no seu primeiro exemplo de C ++, você não está fazendo a referência "apontar para outra coisa". Em vez disso, você está alterando o valor da variável para a qual a referência aponta. Esta é conceitualmente similar à mutação do estado do objeto que é referido com referência Java,
Xion

@ Xion sim, agora que li o seu comentário, entendo o que você quer dizer e concordo com 98% do que você diz :) O problema em Java é que você deve ter acesso a todo e qualquer estado do objeto para conceitualmente consiga o que o C ++ simplesmente faz, permitindo que você defina a referência para o valor de alguma outra referência.
Shivan Dragão

Respostas:


48

Por quê? Porque, embora uma terminologia consistente seja geralmente boa para toda a profissão, os designers de idiomas nem sempre respeitam o uso de outros designers de idiomas, principalmente se esses outros idiomas são percebidos como concorrentes.

Mas, na verdade, nem o uso de 'referência' foi uma escolha muito boa. "Referências" em C ++ são simplesmente uma construção de linguagem para introduzir aliases (nomes alternativos para exatamente a mesma entidade) explicitamente. As coisas teriam ficado muito mais claras se eles simplesmente tivessem chamado o novo recurso de "apelidos" em primeiro lugar. No entanto, naquela época, a grande dificuldade era fazer com que todos entendessem a diferença entre ponteiros (que exigem desreferenciamento) e referências (que não), então o importante era que isso fosse chamado de algo que não seja "ponteiro", e não muito especificamente qual termo usar.

Java não tem ponteiros e se orgulha disso, portanto, usar "ponteiro" como termo não era uma opção. No entanto, as "referências" que ele tem se comportam bastante como os ponteiros do C ++ quando você as repassa - a grande diferença é que você não pode executar as operações mais desagradáveis ​​de baixo nível (conversão, adição ...) nelas , mas eles resultam exatamente na mesma semântica quando você passa identificadores para entidades idênticas vs. entidades que simplesmente são iguais. Infelizmente, o termo "ponteiro" carrega tantas associações negativas de baixo nível que é improvável que seja aceito pela comunidade Java.

O resultado é que ambos os idiomas usam o mesmo termo vago para duas coisas bastante diferentes, ambas as quais podem se beneficiar de um nome mais específico, mas nenhuma das quais provavelmente será substituída em breve. A linguagem natural também pode ser frustrante às vezes!


7
Obrigado, pensar em referências C ++ como "aliases" (para o mesmo valor / instância) e referências Java como "ponteiros sem as operações mais desagradáveis ​​de baixo nível (conversão, adição ...)" realmente esclarecem as coisas para mim.
Shivan Dragon

14
Java não tem ponteiros e se orgulha disso, portanto, usar "ponteiro" como termo não era uma opção. O que dizer NullPointerExceptionou o fato de o JLS usar o termo "ponteiro"?
Tobias Brandt

7
@TobiasBrandt: NullPointerExceptionnão é um ponteiro, mas é uma exceção que indica que, em um nível inferior, um ponteiro NULL foi passado / desreferenciado. Usar Pointercomo parte de uma exceção, se é que acrescenta alguma coisa, aumenta a imagem desagradável que eles têm. O JLS precisa mencionar ponteiros, porque a VM do Java foi escrita principalmente em uma linguagem que os utiliza. Você não pode descrever com precisão as especificações de linguagem sem descrever como os internos trabalhar
Elias Van Ootegem

3
@TobiasBrandt: Java usa ponteiros em todo o lugar; objetos heap são referenciados através de algo que, para todos os fins práticos, é um ponteiro. É que o Java não expõe nenhuma operação nos tipos de ponteiros para o programador.
John Bode

2
Prefiro o termo "ID do objeto" para referências Java / .NET. No nível de bit, essas coisas geralmente podem ser implementadas como algo semelhante a um ponteiro, mas nada exige que elas sejam. O importante é que um ID de objeto contenha qualquer tipo de informação que o framework / vm precise para identificar um objeto.
supercat

9

Uma referência é uma coisa que se refere a outra coisa. Várias línguas atribuíram um significado mais específico à palavra "referência", geralmente a "algo como um ponteiro sem todos os aspectos ruins". C ++ atribui um significado específico, assim como Java ou Perl.

No C ++, as referências são mais parecidas com aliases (que podem ser implementadas por meio de um ponteiro). Isso permite passar por referência ou argumentos de saída .

Em Java, referências são ponteiros, exceto que este não é um conceito reificado da linguagem: todos os objetos são referências, tipos primitivos como números não. Eles não querem dizer "ponteiro" porque não há aritmética de ponteiro e não há ponteiros reificados em Java, mas querem deixar claro que quando você passa um Objectcomo argumento, o objeto não é copiado . Isso também não é passado por referência , mas algo mais como passar pelo compartilhamento .


6

Em grande parte remonta a Algol 68 e, em parte, a uma reação contra a maneira como C define ponteiros.

Algol 68 definiu um conceito chamado referência. Era praticamente o mesmo que (por exemplo, um ponteiro em Pascal). Era uma célula que continha NIL ou o endereço de outra célula de algum tipo especificado. Você pode atribuir a uma referência, para que uma referência possa se referir a uma célula por vez e a uma célula diferente após ser reatribuída. No entanto, ele não suportava nada análogo à aritmética do ponteiro C ou C ++.

Pelo menos como Algol 68 definiu as coisas, no entanto, as referências eram um conceito bastante limpo que era bastante desajeitado para usar na prática. A maioria das definições de variáveis ​​eram, na verdade, referências, por isso definiram uma notação abreviada para evitar que ela ficasse completamente fora de controle, mas qualquer uso que não fosse trivial poderia ficar desajeitado rapidamente, de qualquer maneira.

Por exemplo, uma declaração como INT j := 7;foi realmente tratada pelo compilador como uma declaração como REF INT j = NEW LOC INT := 7. Portanto, o que você declarou no Algol 68 era normalmente uma referência, que foi inicializada para se referir a algo que foi alocado no heap e que foi (opcionalmente) inicializado para conter algum valor especificado. Ao contrário do Java, no entanto, eles pelo menos tentaram permitir que você mantivesse uma sintaxe sadia para isso, em vez de constantemente ter coisas como foo bar = new foo();ou tentar contar mentiras sobre "nossos indicadores não são indicadores".

Pascal e a maioria de seus descendentes (diretos e ... espirituais) renomearam o conceito de referência do Algol 68 como "ponteiro", mas mantiveram o conceito essencialmente o mesmo: um ponteiro era uma variável que continha um nilendereço ou algo que você alocou na pilha (ou seja, pelo menos como originalmente definido por Jensen e Wirth, não havia um operador "endereço de", portanto não havia como um ponteiro se referir a uma variável normalmente definida). Apesar de serem "ponteiros", nenhuma aritmética de ponteiros era suportada.

C e C ++ adicionaram algumas reviravoltas a isso. Primeiro, eles fazem tem um endereço-de operador, assim que um ponteiro pode se referir não só a algo alocada na pilha, mas a qualquer variável, independentemente da forma como está alocado. Segundo, eles definem aritmética em ponteiros - e definem subscritos de matriz como basicamente apenas uma notação abreviada de aritmética de ponteiro; portanto, a aritmética de ponteiro é onipresente (quase inevitável) na maioria dos C e C ++.

Quando o Java estava sendo inventado, a Sun aparentemente pensou que "o Java não tem ponteiros" era uma mensagem de marketing mais simples e limpa do que: "quase tudo em Java é um ponteiro, mas esses ponteiros são principalmente do Pascal em vez de C". Como eles decidiram que "ponteiro" não era um termo aceitável, precisavam de outra coisa e, em vez disso, trouxeram "referência", embora suas referências fossem sutilmente (e em alguns casos não tão sutilmente) diferentes das referências a Algol 68.

Embora tenha saído um pouco diferente, o C ++ ficou com o mesmo problema: a palavra "ponteiro" já era conhecida e entendida; portanto, eles precisavam de uma palavra diferente para essa coisa diferente que estavam adicionando que se referia a outra coisa, mas, de outra forma, era bastante um pouco diferente do que as pessoas entendiam "ponteiro". Portanto, embora também seja visivelmente diferente de uma referência do Algol 68, eles reutilizaram o termo "referência" também.


O que torna a referência em Algol 68 particularmente dolorosa é a desreferenciação automática. Isso é algo conveniente, desde que limitado a um nível. Mas o fato de poder ser feito várias vezes combinado com o açúcar sintático que escondia algumas das referências aos olhos inconscientes a deixava confusa.
AProgrammer

0

A noção de 'tipo de referência' é genérica; pode expressar um ponteiro e uma referência (em termos de C ++), em oposição a 'tipo de valor'. As referências de Java são realmente ponteiros, sem sobrecarga sintática de ->uma *desreferenciação. Se você examinar a implementação da JVM em C ++, eles são realmente indicadores simples. Além disso, o C # possui uma noção de referências semelhantes às do Java, mas também possui ponteiros e qualificadores 'ref' nos parâmetros de função, permitindo passar tipos de valor por referência e evitar a cópia, como &no C ++.


Como esta resposta é diferente / melhor das respostas anteriores aqui?
mosquito
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.