arrow-left

Todas as páginas
gitbookFornecido por GitBook
1 de 8

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Atribuição Modificadora

Você pode abreviar a construção comum

lvalue = lvalue + expressão

como

lvalue += expressão

Isso é conhecido como uma atribuição modificadora. Por exemplo,

mostra duas instruções equivalentes. A primeira usa atribuição simples; a segunda usa atribuição modificadora.

A atribuição modificadora funciona com qualquer operador aritmético binário. Por exemplo, você pode subtrair algo de um lvalue assim:

ou multiplicá-lo por uma certa quantia assim:

ou deslocá-lo por uma certa quantia assim:

Na maioria dos casos, esse recurso não adiciona mais poder à linguagem, mas fornece uma conveniência substancial. Além disso, quando o lvalue contém código que possui efeitos colaterais, a atribuição simples executa esses efeitos colaterais duas vezes, enquanto a atribuição modificadora os executa apenas uma vez. Por exemplo,

chama foo duas vezes, e ele pode retornar valores diferentes a cada vez. Se foo () retornar 1 na primeira vez e 3 na segunda vez, o efeito pode ser somar x[3] e 5 e armazenar o resultado em x[1], ou somar x[1] e 5 e armazenar o resultado em x[3]. Não sabemos qual dos dois ocorrerá, porque C não especifica qual chamada a foo é computada primeiro.

Tal instrução não é bem definida e não deve ser usada.

Por outro lado,

é bem definida: chama foo apenas uma vez para determinar qual elemento de x ajustar, e ajusta esse elemento somando 5 a ele.

Armadilha: Atribuição em Subexpressões

Em C, a ordem de cálculo das partes de uma expressão não é fixa. Com exceção de alguns poucos casos especiais, as operações podem ser calculadas em qualquer ordem. Se uma parte da expressão tem uma atribuição para x e outra parte da expressão usa x, o resultado é imprevisível, pois esse uso pode ser calculado antes ou depois da atribuição.

Aqui está um exemplo de código ambíguo:

Se o segundo argumento, x, for calculado antes do terceiro argumento, x = 4, o valor do segundo argumento será 20. Se eles forem calculados na outra ordem, o valor do segundo argumento será 4.

Aqui está uma maneira de tornar esse código não ambíguo:

Atribuição Simples

Uma expressão de atribuição simples calcula o valor do operando à direita e o armazena no lvalue à esquerda. Aqui está uma expressão de atribuição simples que armazena 5 em i:

Dizemos que isso é uma atribuição para a variável i e que atribui a i o valor 5. Não tem ponto e vírgula porque é uma expressão (portanto, tem um valor). Adicionar um ponto e vírgula no final a tornaria uma instrução (veja Instrução de Expressão).

Aqui está outro exemplo de uma expressão de atribuição simples. Seus operandos não são simples, mas o tipo de atribuição feita aqui é uma atribuição simples.

i = i + 5;
i += 5;
lvalue -= expression
lvalue *= expression
lvalue <<= expression
lvalue >>= expression
x[foo ()] = x[foo ()] + 5;
x[foo ()] += 5;
Uma atribuição simples com dois tipos de dados numéricos diferentes converte o valor do operando à direita para o tipo do lvalue, se possível. Ela pode converter qualquer tipo numérico para qualquer outro tipo numérico.

A atribuição simples também é permitida em alguns tipos não numéricos: ponteiros (veja Ponteiros), estruturas (veja Atribuição de Structs) e uniões (veja Uniões).

Aviso: A atribuição não é permitida em arrays porque em C não existem valores de array; variáveis em C podem ser arrays, mas esses arrays não podem ser manipulados como um todo. Veja Limitações de Arrays em C.

Veja Conversões de Tipos em Atribuições para as regras completas sobre tipos de dados usados em atribuições.

i = 5
x[foo ()] = y + 6

Operadores de Incremento e Decremento

Os operadores ‘++’ e ‘--’ são os operadores de incremento e decremento. Quando usados em um valor numérico, eles adicionam ou subtraem 1. Não os consideramos como atribuições, mas eles são equivalentes a atribuições.

Usar ‘++’ ou ‘--’ como prefixo, antes de um lvalue, é chamado de pré-incremento ou pré-decremento. Isso adiciona ou subtrai 1, e o resultado se torna o valor da expressão. Por exemplo,

#include <stdio.h>   /* Declara printf. */

int
main (void)
{
  int i = 5;
  printf ("%d\n", i);
  printf ("%d\n", ++i);
  printf ("%d\n", i);
  return 0;
}

imprime linhas contendo 5, 6 e novamente 6. A expressão ++i incrementa i de 5 para 6 e tem o valor 6, então a saída do printf nessa linha exibe ‘6’.

Usando ‘--’ em vez disso, para pré-decremento,

#include <stdio.h>   /* Declara printf. */

int
main (void)
{
  int i = 5;
  printf ("%d\n", i);
  printf ("%d\n", --i);
  printf ("%d\n", i);
  return 0;
}

imprime três linhas que contêm (respectivamente) ‘5’, ‘4’ e novamente ‘4’.

Aqui está outra maneira, com o outro significado:

Esse problema se aplica a todos os tipos de atribuições, e aos operadores de incremento e decremento, que são equivalentes a atribuições. Veja Ordem de Execução para mais informações sobre isso.

No entanto, pode ser útil escrever atribuições dentro de uma condição if ou um teste while, junto com operadores lógicos. Veja Operadores Lógicos e Atribuições.

x = 20;
printf ("%d %d\n", x, x = 4);
y = 20;
printf ("%d %d\n", y, x = 4);
x = 4;
printf ("%d %d\n", x, x);

Pós-incremento e Pós-decremento

Usar ‘++’ ou ‘--’ após um lvalue faz algo peculiar: obtém o valor diretamente do lvalue e, em seguida, o incrementa ou decrementa. Assim, o valor de i++ é o mesmo que o valor de i, mas i++ também incrementa i “um pouco depois”. Isso é chamado de pós-incremento ou pós-decremento.

Por exemplo,

#include <stdio.h>   /* Declara printf. */

int
main (void)
{
  int i = 5;
  printf ("%d\n", i);
  printf ("%d\n", i++);
  printf ("%d\n", i);
  return 0;
}

imprime linhas contendo 5, novamente 5, e 6. A expressão i++ tem o valor 5, que é o valor de i naquele momento, mas incrementa i de 5 para 6 logo em seguida.

O quanto "logo em seguida" acontece? O compilador tem certa flexibilidade para decidir isso. A regra é que o incremento deve ocorrer até o próximo sequence point; em casos simples, isso significa até o final da instrução. Veja Pontos de Sequência.

Independentemente de onde exatamente o código compilado incrementa o valor de i, o ponto crucial é que o valor de i++ é o valor que i tinha antes de ser incrementado.

Se um operador unário precede uma expressão de pós-incremento ou pós-decremento, o incremento é aninhado internamente:

Essa é a única ordem que faz sentido; -a não é um lvalue, então não pode ser incrementado.

O uso mais comum de pós-incremento é com arrays. Aqui está um exemplo de uso de pós-incremento para acessar um elemento de um array e avançar o índice para o próximo acesso. Compare com o exemplo avg_of_double (veja ), que é quase o mesmo, mas não usa pós-incremento.

7. Expressões de Atribuição

Como conceito geral em programação, uma atribuição é uma construção que armazena um novo valor em um local onde valores podem ser armazenados — por exemplo, em uma variável. Esses locais são chamados de lvalues (veja Lvalues) porque são locais (por isso o "l") que armazenam um valor.

Uma atribuição em C é uma expressão porque possui um valor; chamamos isso de expressão de atribuição. Uma atribuição simples se parece com:

lvalue = valor-para-armazenar

Dizemos que ela atribui o valor da expressão valor-para-armazenar ao local lvalue, ou que armazena valor-para-armazenar lá. Você também pode pensar no "l" de "lvalue" como significando "esquerda" (left), já que é o que você coloca no lado esquerdo do operador de atribuição.

No entanto, essa não é a única forma de usar um lvalue, e nem todos os lvalues podem ser atribuídos. Para usar o lvalue no lado esquerdo de uma atribuição, ele precisa ser modificável. Em C, isso significa que ele não foi declarado com o qualificador de tipo const (veja const).

O valor da expressão de atribuição é o de lvalue após o novo valor ser armazenado nele. Isso significa que você pode usar uma atribuição dentro de outras expressões. Os operadores de atribuição são associativos à direita, de modo que:

é equivalente a:

Essa é a única maneira útil para associá-los; a outra maneira,

seria inválida, pois uma expressão de atribuição como x = y não é válida como um lvalue.

circle-info

Quando o texto diz que o valor de expressão de atribuição é o de lvalue, quer dizer que a expressão "retorna" o lvalue. Por exemplo, i = 10 "retorna" 10. Ou seja, se essa expressão de atribuição inteira for utilizada à direita de um operador de atribuição, o valor dela será 10.

Aviso: Coloque parênteses ao redor de uma atribuição se você a aninhar dentro de outra expressão, a menos que seja uma expressão condicional, uma série separada por vírgulas ou outra atribuição.

-a++   é equivalente a   -(a++)
double
avg_of_double_alt (int length, double input_data[])
{
  double sum = 0;
  int i;

  /* Pega cada elemento e adiciona à soma.  */
  for (i = 0; i < length;)
    /* Usa o índice i, depois o incrementa.  */
    sum += input_data[i++];

  return sum / length;
}
Um Exemplo com Arrays
x = y = z = 0;
x = (y = (z = 0));
((x = y) = z) = 0;

Lvalues

Uma expressão que identifica um espaço de memória que armazena um valor é chamada de lvalue, porque é uma localização que pode armazenar um valor.

Os tipos padrão de lvalues são:

  • Uma variável.

  • Uma expressão de desreferenciamento de ponteiro (veja Desreferenciamento de Ponteiro) usando o unário ‘*’.

  • Uma referência a um campo de estrutura (veja Estruturas) usando ‘.’, se o valor da estrutura for um lvalue.

  • Uma referência a um campo de estrutura usando ‘->’. Isso é sempre um lvalue já que ‘->’ implica desreferenciamento de ponteiro.

  • Uma referência a uma alternativa de união (veja Uniões), nas mesmas condições que para campos de estruturas.

  • Uma referência a um elemento de um array usando ‘[…]’, se o array for um lvalue.

Se a operação mais externa de uma expressão for qualquer outro operador, essa expressão não é um lvalue. Assim, a variável x é um lvalue, mas x + 0 não é, mesmo que essas duas expressões calculem o mesmo valor (supondo que x seja um número).

Um array pode ser um lvalue (as regras acima determinam se ele é um), mas usar o array em uma expressão o converte automaticamente em um ponteiro para o elemento zero. O resultado dessa conversão não é um lvalue. Portanto, se a variável a for um array, você não pode usá-la sozinha como o operando esquerdo de uma atribuição. Mas você pode atribuir a um elemento de a, como a[0]. Isso é um lvalue, já que a é um lvalue.

Escreva Atribuições em Instruções Separadas

É frequentemente conveniente escrever uma atribuição dentro de uma condição if, mas isso pode reduzir a legibilidade do programa. Aqui está um exemplo do que evitar:

if (x = advance (x))
  …

A ideia aqui é avançar x e testar se o valor é diferente de zero. No entanto, os leitores podem não perceber que está sendo usado ‘=’ e não ‘==’. De fato, escrever ‘=’ onde ‘==’ era pretendido dentro de uma condição é um erro comum, por isso o GNU C pode emitir avisos quando ‘=’ aparece de uma forma que sugere ser um erro.

É muito mais claro escrever a atribuição como uma instrução separada, assim:

x = advance (x);
if (x != 0)
  …

Isso torna inconfundivelmente claro que x está recebendo um novo valor.

Outro método é usar o operador vírgula (veja Operador Vírgula), assim:

No entanto, colocar a atribuição em uma instrução separada geralmente é mais claro, a menos que a atribuição seja muito curta, pois isso reduz o aninhamento.

if (x = advance (x), x != 0)
  …