C ++

Taxonomia de categoria de expressão em C ++

Taxonomia de 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 operandos que especifica um cálculo. Em outras palavras, uma expressão é um identificador ou um literal, ou uma sequência de ambos, unidos por operadores.Na programação, uma expressão pode resultar em um valor e / ou causar algum acontecimento. Quando resulta em um valor, a expressão é glvalue, rvalue, lvalue, xvalue ou prvalue. Cada uma dessas categorias é um conjunto de expressões. Cada conjunto possui uma definição e situações particulares onde prevalece seu significado, 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 realmente valores.

glvalue e rvalue são os dois subconjuntos da expressão de conjunto grande. glvalue existe em mais dois subconjuntos: lvalue e xvalue. rvalue, o outro subconjunto de expressão, também existe em dois outros subconjuntos: xvalue e prvalue. Portanto, xvalue é um subconjunto de glvalue e rvalue: ou seja, xvalue é a interseção de glvalue e rvalue. O seguinte diagrama de taxonomia, retirado da especificação C ++, ilustra a relação de todos os conjuntos:

prvalue, xvalue e lvalue são os principais valores da categoria. glvalue é a união de lvalues ​​e xvalues, enquanto rvalues ​​é 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

Para realmente entender a taxonomia da categoria de expressão, você precisa lembrar ou conhecer os seguintes recursos básicos primeiro: localização e objeto, armazenamento e recurso, inicialização, identificador e referência, referências lvalue e rvalue, ponteiro, loja gratuita 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. Uma localização é um conjunto particular 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 de um 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 tanto o local quanto o conteúdo; ou seja, o identificador identifica a localização e o valor.

Considere as seguintes declarações:

int ident1 = 5;
int ident2 = 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 de comprimento. O primeiro identificador identifica um local e um valor. O segundo identificador também identifica ambos.

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

Armazenamento e recurso de objeto

A localização de um objeto também é chamada de armazenamento ou recurso do objeto.

Inicialização

Considere o seguinte segmento de código:

int ident;
ident = 8;

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

A instruçã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 instrução da definição (declaração). O operador de atribuição não é usado. A instruçã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 ident = 4;
int & ref1 = ident;
int & ref2 = ident;
cout<< ident <<"<< ref1 <<"<< ref2 << '\n';

O resultado é:

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 ident é o identificador do mesmo objeto. No entanto, ident ainda pode ser chamado de nome do objeto, o que significa que ident, ref1 e ref2 nomeiam o mesmo local.

A principal diferença entre um identificador e uma referência é que, quando passado como um argumento para uma função, se passado por identificador, uma cópia é feita para o identificador na função, enquanto se passado por referência, o mesmo local é usado dentro do função. Assim, a passagem pelo identificador acaba com duas localizações, enquanto a passagem pela referência acaba com a mesma localização.

Referência lvalue e Referência rvalue

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

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

O armazenamento (recurso) é localizado e identificado primeiro (com um nome como ident), e então uma referência (com um nome como 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, a localização original será utilizada (referida) na função.

Hoje é possível ter apenas 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 &&, conforme mostrado na seguinte declaração:

int && ref = 4;

Aqui, não há identificação anterior. Para acessar o valor do objeto, simplesmente use ref da mesma forma que você usaria o ident 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. Neste caso, há apenas um local usado dentro da função e não o segundo local copiado como com um identificador.

Uma declaração de referência com & é chamada de referência de valor. Uma declaração de referência com && é chamada de referência rvalue, que também é uma referência prvalue (veja abaixo).

Pointer

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 ident acima. Existem dois objetos (locais) aqui em vez de um: o objeto apontado, ptdInt identificado por ptdInt, e o objeto de ponteiro, ptrInt identificado por ptrInt. & ptdInt retorna o endereço do objeto apontado e o coloca como o valor no objeto ptrInt do ponteiro. Para retornar (obter) o valor do objeto apontado, use o identificador do 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, que não é uma adição do valor 1. Quando um ponteiro é decrementado, ele aponta para o local anterior, que não é uma subtração do valor 1.

Loja Gratuita

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

novo int

Isso retorna uma localização para um 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:

delete 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;
delete ptrInt;
cout<< *ptrInt <<'\n';

A saída é 0, e nada como nulo ou indefinido. delete substitui o valor do local pelo valor padrão do tipo específico do local e, em seguida, permite que o local seja reutilizado. O valor padrão para um local interno é 0.

Reutilizar um recurso

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

int * ptrInt = new int;
* ptrInt = 12;
cout<< *ptrInt <<'\n';
delete ptrInt;
cout<< *ptrInt <<'\n';
* ptrInt = 24;
cout<< *ptrInt <<'\n';

O resultado é:

12
0
24

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

O programa a seguir mostra como uma referência de número inteiro retornada por uma função é reutilizada:

#incluir
usando namespace std;
int & fn ()

int i = 5;
int & j = i;
return j;

int main ()

int & myInt = fn ();
cout<< myInt <<'\n';
myInt = 17;
cout<< myInt <<'\n';
return 0;

O resultado é:

5
17

Um objeto como i, declarado em um escopo local (escopo de função), deixa de existir no final do escopo local. No entanto, a função fn () acima, retorna a referência de i. Por meio dessa referência retornada, o nome, myInt na função main (), reutiliza a localização identificada por i para o 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 ident acima, ou um nome de referência de 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;
return myInt;

Aqui, myInt é um lvalue; myRef é uma expressão de referência de lvalue; * ptr é uma expressão lvalue porque seu resultado é identificável com ptr; ++ ptr ou -ptr é uma expressão lvalue porque seu resultado é identificável com o novo estado (endereço) de ptr, e fn é uma 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, a localização de 'a' tem 2 e é identificável por 'a', e também é um lvalue. A localização de b tem 8 e é identificável por b, assim como um lvalue. A localização de c terá a soma e é identificável por c, assim como 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 é um array. A localização de 'v' ou qualquer valor semelhante na matriz é identificada por seq [i], onde i é um índice. Então, a expressão, seq [i], é uma expressão de lvalue. seq, que é o identificador de toda a matriz, também é um lvalue.

prvalue

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.

No depoimento,

int myInt = 256;

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

No depoimento,

int && ref = 4;

4 é um prvalue (expressão prvalue) que inicializa o objeto referenciado por ref. Este objeto não é identificado oficialmente. ref é um exemplo de uma expressão de referência 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 prvalue 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 cada uma constante que calcula para si mesma, produzindo um operando (em bits) para o operador de adição. Portanto, 15 ou 63 é uma expressão prvalue.

Qualquer literal, exceto o literal de string, é um prvalue (i.e., uma expressão prvalue). Portanto, 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, o literal 2 inicializa o objeto, um. Ele também se calcula como um operando para o operador de atribuição.

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

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

O resultado é:

amor não odeio
n

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

Na instrução a seguir, um literal de array 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 da localização para a qual 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 da mesma localização. Por outro lado, -ptrInt ou -ptrInt é um lvalue porque identifica o único valor do interesse no local. Outra maneira de ver isso é que o valor original calcula o segundo valor final.

Na segunda instrução do código a seguir, a ou b ainda pode ser considerado como um prvalue:

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

Portanto, a ou b na segunda instrução é um lvalue porque identifica um objeto. É também um prvalue, uma vez que calcula o número inteiro de um operando para o operador de adição.

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

int * ptrInt = new int

Aqui, * ptrInt é um lvalue, enquanto (new int) é um prvalue. Lembre-se, um lvalue ou um prvalue é uma expressão. (new int) não identifica nenhum objeto. Devolver o endereço não significa identificar o objeto com um nome (como ident, acima). Em * ptrInt, o nome, ptrInt, é o que realmente identifica o objeto, então * ptrInt é um lvalue. Por outro lado, (new int) é um prvalue, pois calcula uma nova localização para um endereço de valor do operando para o operador de atribuição =.

xvalue

Hoje, lvalue significa Location Value; prvalue significa "puro" rvalue (veja o que rvalue significa abaixo). Hoje, xvalue significa "eXpiring" lvalue.

A definição de xvalue, citada da 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á próximo do fim de sua vida útil). [Exemplo: Certos tipos de expressões envolvendo referências rvalue produzem xvalues, como uma chamada para uma função cujo tipo de retorno é uma referência rvalue ou uma conversão para um tipo de referência rvalue - exemplo final] ”

O que isso significa é que lvalue e prvalue podem expirar. O código a seguir (copiado de cima) mostra como o armazenamento (recurso) de lvalue, * ptrInt é reutilizado após ter sido excluído.

int * ptrInt = new int;
* ptrInt = 12;
cout<< *ptrInt <<'\n';
delete ptrInt;
cout<< *ptrInt <<'\n';
* ptrInt = 24;
cout<< *ptrInt <<'\n';

O resultado é:

12
0
24

O programa a seguir (copiado de cima) mostra como o armazenamento de uma referência de inteiro, que é uma referência de lvalue retornada por uma função, é reutilizado na função main ():

#incluir
usando namespace std;
int & fn ()

int i = 5;
int & j = i;
return j;

int main ()

int & myInt = fn ();
cout<< myInt <<'\n';
myInt = 17;
cout<< myInt <<'\n';
return 0;

O resultado é:

5
17

Quando um objeto como i na função fn () sai do escopo, ele é naturalmente destruído. Neste caso, o armazenamento de i ainda foi reutilizado na função main ().

Os dois exemplos de código acima ilustram a reutilização do armazenamento de lvalues. É possível ter uma reutilização de armazenamento de prvalues ​​(rvalues) (ver mais tarde).

A seguinte citação a respeito de xvalue é da especificação C ++:

“Em geral, o efeito desta regra é que as referências rvalue nomeadas são tratadas como lvalues ​​e as referências rvalue não nomeadas a objetos são tratadas como xvalues. referências de rvalue para funções são tratadas como lvalues, sejam 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 intersecção de lvalues ​​e prvalues.

Há mais para xvalue do que o que foi abordado neste artigo. No entanto, 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 de 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 atribuição (embora isso não seja mais verdade em geral); glvalues ​​são "generalizados" lvalues, prvalues ​​são rvalues ​​"puros" e xvalues ​​são lvalues ​​"eXpirantes". Apesar de seus nomes, esses termos classificam expressões, não valores. - nota final ”

Portanto, glvalues ​​é o conjunto de união de lvalues ​​e xvalues ​​e rvalues ​​são o conjunto de união de xvalues ​​e prvalues. xvalueséo conjunto de intersecção de lvalues ​​e prvalues.

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

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 taxonomia de categoria de expressão com um diagrama de árvore, indicando que há alguma hierarquia na taxonomia. No momento, 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 árvore.

Remapeie os botões do mouse de maneira diferente para software diferente com o controle de botão do mouse X
Talvez você precise de uma ferramenta que possa fazer o controle do seu mouse mudar com cada aplicativo que você usa. Se for esse o caso, você pode ex...
Análise do mouse sem fio Microsoft Sculpt Touch
Recentemente li sobre o Microsoft Sculpt Touch mouse sem fio e decidi comprá-lo. Depois de usá-lo por um tempo, decidi compartilhar minha experiência ...
AppyMouse na tela Trackpad e ponteiro do mouse para tablets Windows
Os usuários de tablets costumam perder o ponteiro do mouse, especialmente quando costumam usar laptops. Os smartphones e tablets touchscreen vêm com m...