Taxonomia da categoria de expressão em C ++

Taxonomia da categoria de expressão em C ++
Um cálculo é qualquer tipo de cálculo que segue um algoritmo bem definido. Uma expressão é uma sequência de operadores e operando que especifica um cálculo. Em outras palavras, uma expressão é um identificador ou um literal, ou uma sequência de ambos, unida por operadores.Na programação, uma expressão pode resultar em um valor e/ou fazer com que alguns aconteçam. Quando resulta em um valor, a expressão é um GlValue, Rvalue, LValue, XValue ou Prvalue. Cada uma dessas categorias é um conjunto de expressões. Cada conjunto tem uma definição e situações específicas em que seu significado prevalece, diferenciando -o de outro conjunto. Cada conjunto é chamado de categoria de valor.

Observação: Um valor ou literal ainda é uma expressão; portanto, esses termos classificam expressões e não são realmente valores.

GlValue e RValue são os dois subconjuntos da expressão de Big Set. GlValue existe em mais dois subconjuntos: LValue e XValue. RValue, o outro subconjunto de expressão, também existe em dois subconjuntos: XValue e Prvvalue. Portanto, XValue é um subconjunto de GlValue e Rvalue: isto é, XValue é a interseção de GlValue e Rvalue. O seguinte diagrama de taxonomia, retirado da especificação C ++, ilustra o relacionamento de todos os conjuntos:

Prvvalue, XValue e LValue são os valores da categoria primária. GlValue é a união de lvalues ​​e xvalues, enquanto os rvalues ​​são a união de Xvalues ​​e Prvalues.

Você precisa de conhecimento básico em C ++ para entender este artigo; Você também precisa de conhecimento de escopo em C++.

Conteúdo do artigo

  • Fundamentos
  • lvalue
  • Prvvalue
  • xvalue
  • Conjunto de taxonomia da categoria de expressão
  • Conclusão

Fundamentos

Para realmente entender a taxonomia da categoria de expressão, você precisa recordar ou conhecer os seguintes recursos básicos primeiro: localização e objeto, armazenamento e recurso, inicialização, identificador e referência, referências de LValue e Rvalue, ponteiro, armazenamento gratuito e reutilização de um recurso.

Localização e objeto

Considere a seguinte declaração:

int ident;

Esta é uma declaração que identifica um local na memória. Um local é um conjunto específico de bytes consecutivos na memória. Um local pode consistir em um byte, dois bytes, quatro bytes, sessenta e quatro bytes, etc. A localização para um número inteiro para uma máquina de 32 bits é de quatro bytes. Além disso, o local pode ser identificado por um identificador.

Na declaração acima, o local não possui nenhum conteúdo. Isso significa que não tem nenhum valor, pois o conteúdo é o valor. Portanto, um identificador identifica um local (pequeno espaço contínuo). Quando o local recebe um conteúdo específico, o identificador identifica o local e o conteúdo; ou seja, o identificador identifica então o local e o valor.

Considere as seguintes declarações:

int identd1 = 5;
int identd2 = 100;

Cada uma dessas declarações é uma declaração e uma definição. O primeiro identificador tem o valor (conteúdo) 5, e o segundo identificador tem o valor 100. Em uma máquina de 32 bits, cada um desses locais tem quatro bytes. O primeiro identificador identifica um local e um valor. O segundo identificador também identifica os dois.

Um objeto é uma região de armazenamento nomeada na memória. Portanto, um objeto é um local sem um valor ou um local com um valor.

Armazenamento e recurso de objetos

O local para um objeto também é chamado de armazenamento ou recurso do objeto.

Inicialização

Considere o seguinte segmento de código:

int ident;
identificação = 8;

A primeira linha declara um identificador. Esta declaração fornece um local (armazenamento ou recurso) para um objeto inteiro, identificando -o com o nome, identificação. A próxima linha coloca o valor 8 (em bits) no local identificado por identificação. A colocação desse valor é inicialização.

A declaração a seguir define um vetor com conteúdo, 1, 2, 3, 4, 5, identificado por VTR:

std :: vetor vtr 1, 2, 3, 4, 5;

Aqui, a inicialização com 1, 2, 3, 4, 5 é feita na mesma declaração da definição (declaração). O operador de atribuição não é usado. A declaração a seguir define uma matriz com conteúdo 1, 2, 3, 4, 5:

int arr [] = 1, 2, 3, 4, 5;

Desta vez, um operador de atribuição foi usado para a inicialização.

Identificador e referência

Considere o seguinte segmento de código:

int identd = 4;
int & ref1 = ident;
int & ref2 = ident;
cout<< ident <<"<< ref1 <<"<< ref2 << '\n';

A saída é:

4 4 4

Ident é um identificador, enquanto Ref1 e Ref2 são referências; Eles fazem referência ao mesmo local. Uma referência é um sinônimo de um identificador. Convencionalmente, Ref1 e Ref2 são nomes diferentes de um objeto, enquanto o identificador é o identificador do mesmo objeto. No entanto, o identificador ainda pode ser chamado de nome do objeto, o que significa, identificação, ref1 e ref2, nomeie o mesmo local.

A principal diferença entre um identificador e uma referência é que, quando passada como um argumento para uma função, se passada pelo identificador, uma cópia é feita para o identificador na função, enquanto se passada por referência, o mesmo local é usado no função. Então, a passagem pelo identificador acaba com dois locais, enquanto passa por referência acaba com o mesmo local.

referência de lvalue e referência de rvalue

A maneira normal de criar uma referência é a seguinte:

int ident;
ident = 4;
int & ref = ident;

O armazenamento (recurso) está localizado e identificado primeiro (com um nome como identificação), e depois uma referência (com um nome como uma ref) é feita. Ao passar como um argumento para uma função, uma cópia do identificador será feita na função, enquanto para o caso de uma referência, o local original será usado (referido) na função.

Hoje, é possível apenas ter uma referência sem identificá -la. Isso significa que é possível criar uma referência primeiro sem ter um identificador para o local. Isso usa &&, como mostrado na seguinte declaração:

int && ref = 4;

Aqui, não há identificação anterior. Para acessar o valor do objeto, basta usar o REF como você usaria o identificador acima.

Com a declaração &&, não há possibilidade de passar um argumento para uma função por identificador. A única opção é passar por referência. Nesse caso, existe apenas um local usado na função e não no segundo local copiado, como em um identificador.

Uma declaração de referência com & é chamada de referência lvalue. Uma declaração de referência com && é chamada de referência RValue, que também é uma referência de Prvalue (veja abaixo).

Ponteiro

Considere o seguinte código:

int ptdint = 5;
int *ptrint;
pTrint = &ptdInt;
cout<< *ptrInt <<'\n';

A saída é 5.

Aqui, Ptdint é um identificador como o identificador acima. Existem dois objetos (locais) aqui em vez de um: o objeto pontiagudo, o ptdint identificado por ptdint e o objeto Pointer, ptrint identificado por pTrint. & ptdint retorna o endereço do objeto pontiagudo e o coloca como o valor no objeto Pointer Ptrint. Para devolver (obter) o valor do objeto pontiagudo, use o identificador para o objeto Ponteiro, como em "*ptrint".

Observação: Ptdint é um identificador e não uma referência, enquanto o nome, ref, mencionado anteriormente, é uma referência.

A segunda e a terceira linhas no código acima podem ser reduzidas a uma linha, levando ao seguinte código:

int ptdint = 5;
int *pTrint = &ptdInt;
cout<< *ptrInt <<'\n';

Observação: Quando um ponteiro é incrementado, ele aponta para o próximo local, o que não é uma adição do valor 1. Quando um ponteiro é diminuído, ele aponta para o local anterior, o que não é uma subtração do valor 1.

Loja grátis

Um sistema operacional aloca memória para cada programa que está em execução. Uma memória que não é alocada a nenhum programa é conhecida como a loja gratuita. A expressão que retorna um local para um número inteiro da loja gratuita é:

novo int

Isso retorna um local para um número inteiro que não é identificado. O código a seguir ilustra como usar o ponteiro com a loja gratuita:

int *ptrint = new int;
*pTrint = 12;
cout<< *ptrInt <<'\n';

A saída é 12.

Para destruir o objeto, use a expressão de exclusão da seguinte maneira:

excluir pTrint;

O argumento para a expressão de exclusão é um ponteiro. O código a seguir ilustra seu uso:

int *ptrint = new int;
*pTrint = 12;
excluir pTrint;
cout<< *ptrInt <<'\n';

A saída é 0, e não nada como nulo ou indefinido. Delete substitui o valor para o local pelo valor padrão do tipo específico do local e, em seguida, permite o local para reutilizar. O valor padrão para um local int é 0.

Reutilizando um recurso

Na categoria de expressão taxonomia, reutilizar um recurso é o mesmo que reutilizar um local ou armazenamento para um objeto. O código a seguir ilustra como um local da loja gratuita pode ser reutilizada:

int *ptrint = new int;
*pTrint = 12;
cout<< *ptrInt <<'\n';
excluir pTrint;
cout<< *ptrInt <<'\n';
*pTrint = 24;
cout<< *ptrInt <<'\n';

A saída é:

12
0
24

Um valor de 12 é atribuído primeiro ao local não identificado. Então o conteúdo do local é excluído (em teoria, o objeto é excluído). O valor de 24 é re-atribuído ao mesmo local.

O programa a seguir mostra como uma referência inteira retornada por uma função é reutilizada:

#incluir
usando namespace std;
int & fn ()
int i = 5;
int & j = i;
retornar j;

int main ()
int & myint = fn ();
cout<< myInt <<'\n';
myint = 17;
cout<< myInt <<'\n';
retornar 0;

A saída é:

5
17

Um objeto como eu, declarado em um escopo local (escopo da função), deixa de existir no final do escopo local. No entanto, a função fn () acima retorna a referência de i. Através desta referência retornada, o nome, Myint na função Main (), reutiliza o local identificado por I pelo valor 17.

lvalue

Um lvalue é uma expressão cuja avaliação determina a identidade de um objeto, campo de bits ou função. A identidade é uma identidade oficial como identidade acima, ou um nome de referência lvalue, um ponteiro ou o nome de uma função. Considere o seguinte código que funciona:

int myint = 512;
int & myref = myint;
int* ptr = &myInt;
int fn ()
++ptr; --ptr;
retornar myint;

Aqui, Myint é um LValue; MyRef é uma expressão de referência de LValue; *PTR é uma expressão de LValue porque seu resultado é identificável com PTR; ++ ptr ou -ptr é uma expressão de LValue porque seu resultado é identificável com o novo estado (endereço) de PTR, e FN é um lvalue (expressão).

Considere o seguinte segmento de código:

int a = 2, b = 8;
int c = a + 16 + b + 64;

Na segunda declaração, o local para 'a' tem 2 e é identificável por 'a', e também é um lvalue. O local para B tem 8 e é identificável por B, e também é um LValue. A localização para C terá a soma e é identificável por C, e também é um lvalue. Na segunda declaração, as expressões ou valores de 16 e 64 são rvalues ​​(veja abaixo).

Considere o seguinte segmento de código:

Char Seq [5];
seq [0] = 'l', seq [1] = 'o', seq [2] = 'v', seq [3] = 'e', ​​seq [4] = '\ 0';
cout<< seq[2] <<'\n';

A saída é 'v';

SEQ é uma matriz. O local para 'v' ou qualquer valor semelhante na matriz é identificado pelo SEQ [i], onde eu é um índice. Então, a expressão, seq [i], é uma expressão de lvalue. SEQ, que é o identificador para toda a matriz, também é um lvalue.

Prvvalue

Um prvalue é uma expressão cuja avaliação inicializa um objeto ou um campo de bits ou calcula o valor do operando de um operador, conforme especificado pelo contexto em que aparece.

Na declaração,

int myint = 256;

256 é um prvalue (expressão prvalue) que inicializa o objeto identificado por myint. Este objeto não é referenciado.

Na declaração,

int && ref = 4;

4 é um Prvvalue (Expressão Prvvalue) que inicializa o objeto referenciado por REF. Este objeto não é identificado oficialmente. REF é um exemplo de expressão de referência de RValue ou expressão de referência prvalue; É um nome, mas não um identificador oficial.

Considere o seguinte segmento de código:

int ident;
ident = 6;
int & ref = ident;

6 é um Prvvalue que inicializa o objeto identificado por Ident; O objeto também é referenciado por REF. Aqui, o REF é uma referência de LValue e não uma referência de Prvalue.

Considere o seguinte segmento de código:

int a = 2, b = 8;
int c = a + 15 + b + 63;

15 e 63 são uma constante que se calcula, produzindo um operando (em bits) para o operador de adição. Então, 15 ou 63 é uma expressão de prvalue.

Qualquer literal, exceto o literal de corda, é um prvalue (i.e., uma expressão prvalue). Então, um literal como 58 ou 58.53, ou verdadeiro ou falso, é um prvalue. Um literal pode ser usado para inicializar um objeto ou calcular para si mesmo (em alguma outra forma em bits) como o valor de um operando para um operador. No código acima, os 2 literais inicializam o objeto, um. Ele também se calcula como um operando para o operador de atribuição.

Por que uma corda é literal não é um prvalue? Considere o seguinte código:

char str [] = "amor não ódio";
cout << str <<'\n';
cout << str[5] <<'\n';

A saída é:

amor não ódio
n

str identifica toda a corda. Então, a expressão, str, e não o que identifica, é um lvalue. Cada caractere na string pode ser identificado por str [i], onde eu é um índice. A expressão, str [5], e não o personagem que identifica, é um lvalue. A corda literal é um lvalue e não um prvalue.

Na declaração a seguir, uma matriz literal inicializa o objeto, arr:

PTRINT ++ ou PTRINT--

Aqui, Ptrint é um ponteiro para um local inteiro. Toda a expressão, e não o valor final do local em que aponta, é um prvalue (expressão). Isso ocorre porque a expressão, pTrint ++ ou pTrint-, identifica o primeiro valor original de sua localização e não o segundo valor final do mesmo local. Na outra mão, -ptrint ou -ptrint é um lvalue porque identifica o único valor do interesse no local. Outra maneira de analisar é que o valor original calcula o segundo valor final.

Na segunda declaração do código a seguir, A ou B ainda podem ser considerados como um Prvvalue:

int a = 2, b = 8;
int c = a + 15 + b + 63;

Portanto, A ou B na segunda declaração é um LValue porque identifica um objeto. É também um prvalue, pois calcula o número inteiro de um operando para o operador de adição.

(novo int), e não o local que estabelece é um prvalue. Na declaração a seguir, o endereço de devolução do local é atribuído a um objeto de ponteiro:

int *ptrint = novo int

Aqui, *PTRINT é um lvalue, enquanto (novo int) é um prvalue. Lembre -se, um lvalue ou um prvalue é uma expressão. (novo int) não identifica nenhum objeto. Retornar o endereço não significa identificar o objeto com um nome (como identificação, acima). Em *PTRINT, o nome, PTRINT, é o que realmente identifica o objeto, então *PTRINT é um lvalue. Por outro lado, (New Int) é um Prvvalue, pois calcula um novo local para um endereço do valor do operando para o operador de atribuição =.

xvalue

Hoje, o LValue significa valor de localização; Prvvalue significa RValue "puro" (veja o que Rvalue significa abaixo). Hoje, o XValue significa LValue "expirando".

A definição de XValue, citada na especificação C ++, é a seguinte:

“Um XValue é um GlValue que denota um objeto ou campo de bits cujos recursos podem ser reutilizados (geralmente porque está perto do final de sua vida). [Exemplo: certos tipos de expressões envolvendo referências de rvalue produzem XValues, como uma chamada para uma função cujo tipo de retorno é uma referência de Rvalue ou um elenco para um exemplo do tipo de referência Rvalue] ””

O que isso significa é que tanto o LValue quanto o Prvalue podem expirar. O código a seguir (copiado de cima) mostra como o armazenamento (recurso) do LValue, *PTRINT é reutilizado depois de excluído.

int *ptrint = new int;
*pTrint = 12;
cout<< *ptrInt <<'\n';
excluir pTrint;
cout<< *ptrInt <<'\n';
*pTrint = 24;
cout<< *ptrInt <<'\n';

A saída é:

12
0
24

O programa a seguir (copiado de cima) mostra como o armazenamento de uma referência inteira, que é uma referência de LValue retornada por uma função, é reutilizada na função principal ():

#incluir
usando namespace std;
int & fn ()
int i = 5;
int & j = i;
retornar j;

int main ()
int & myint = fn ();
cout<< myInt <<'\n';
myint = 17;
cout<< myInt <<'\n';
retornar 0;

A saída é:

5
17

Quando um objeto como eu na função fn () sai do escopo, naturalmente é destruído. Nesse caso, o armazenamento de I ainda foi reutilizado na função principal ().

As duas amostras de código acima ilustram a reutilização do armazenamento de lvalues. É possível ter uma reutilização de armazenamento de prvalos (rvalues) (veja mais adiante).

A citação a seguir relativa ao XValue é da especificação C ++:

“Em geral, o efeito desta regra é que as referências de nome nomeadas são tratadas como lvalues ​​e referências de Rvalue sem nome aos objetos são tratadas como Xvalues. Rvalue referências a funções são tratadas como lvalues, nomeadas ou não.”(Veja mais tarde).

Portanto, um XValue é um lvalue ou um prvalue cujos recursos (armazenamento) podem ser reutilizados. XValues ​​é o conjunto de interseção de lvalues ​​e prvalos.

Há mais no XValue do que o que foi abordado neste artigo. No entanto, o XValue merece um artigo inteiro por conta própria e, portanto, as especificações extras para XValue não são abordadas neste artigo.

Conjunto de taxonomia da categoria de expressão

Outra citação da especificação C ++:

““Observação: Historicamente, Lvalues ​​e Rvalues ​​eram chamados porque podiam aparecer no lado esquerdo e direito de uma tarefa (embora isso não seja mais verdadeiro); Glvalues ​​são lvalues ​​"generalizados", os prvalos são "puras" rvalues ​​e os Xvalues ​​estão "expirando" os lvalues. Apesar de seus nomes, esses termos classificam expressões, não valores. - nota final ”

Então, Glvalues ​​é o conjunto de lavais e XValues ​​e Rvalues ​​são o conjunto de XValues ​​e Prvalues ​​da União. XValues ​​é o conjunto de interseção de lvalues ​​e prvalos.

A partir de agora, a taxonomia da categoria de expressão é melhor ilustrada com um diagrama de Venn da seguinte maneira:

Conclusão

Um lvalue é uma expressão cuja avaliação determina a identidade de um objeto, campo de bits ou função.

Um prvalue é uma expressão cuja avaliação inicializa um objeto ou um campo de bits ou calcula o valor do operando de um operador, conforme especificado pelo contexto em que aparece.

Um XValue é um LValue ou um Prvalue, com a propriedade adicional de que seus recursos (armazenamento) podem ser reutilizados.

A especificação C ++ ilustra a categoria de expressão taxonomia com um diagrama de árvores, indicando que há alguma hierarquia na taxonomia. A partir de agora, não há hierarquia na taxonomia, então um diagrama de Venn é usado por alguns autores, pois ilustra a taxonomia melhor do que o diagrama de árvores.