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 ) 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:
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 ).
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.
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.
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 ).
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.
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 ), estruturas (veja ) e uniões (veja).
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 .
Veja para as regras completas sobre tipos de dados usados em atribuições.
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 ) usando o unário ‘*’.
Uma referência a um campo de estrutura (veja ) 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 ), 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.
Você pode abreviar a construção comum
como
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.
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,
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,
imprime três linhas que contêm (respectivamente) ‘5’, ‘4’ e novamente ‘4’.
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,
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 .
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 Um Exemplo com Arrays), que é quase o mesmo, mas não usa pós-incremento.
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:
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 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 .
Loading...
É 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:
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:
Isso torna inconfundivelmente claro que x
está recebendo um novo valor.
Outro método é usar o operador vírgula (veja ), 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.