Lasciatemelo dire molto chiaramente, perché le persone lo fraintendono tutto il tempo:
L'ordine di valutazione delle sottoespressioni è indipendente sia dall'associatività che dalla precedenza . L'associatività e la precedenza determinano in quale ordine vengono eseguiti gli operatori ma non determinano in quale ordine vengono valutate le sottoespressioni . La tua domanda riguarda l'ordine in cui vengono valutate le sottoespressioni .
Considera A() + B() + C() * D()
. La moltiplicazione è una precedenza più alta dell'addizione e l'addizione è associativa a sinistra, quindi equivale a (A() + B()) + (C() * D())
Ma sapere che ti dice solo che la prima addizione avverrà prima della seconda addizione e che la moltiplicazione avverrà prima della seconda addizione. Non ti dice in quale ordine verranno chiamati A (), B (), C () e D ()! (Inoltre non ti dice se la moltiplicazione avviene prima o dopo la prima addizione.) Sarebbe perfettamente possibile obbedire alle regole di precedenza e associatività compilando questo come:
d = D()
b = B()
c = C()
a = A()
sum = a + b
product = c * d
result = sum + product
Qui vengono seguite tutte le regole di precedenza e associatività: la prima addizione avviene prima della seconda addizione e la moltiplicazione avviene prima della seconda addizione. Chiaramente possiamo fare le chiamate ad A (), B (), C () e D () in qualsiasi ordine e comunque obbedire alle regole di precedenza e associatività!
Abbiamo bisogno di una regola non correlata alle regole di precedenza e associatività per spiegare l'ordine in cui vengono valutate le sottoespressioni. La regola rilevante in Java (e C #) è "le sottoespressioni vengono valutate da sinistra a destra". Poiché A () appare a sinistra di C (), A () viene valutato per primo, indipendentemente dal fatto che C () sia coinvolto in una moltiplicazione e A () sia coinvolto solo in un'addizione.
Quindi ora hai abbastanza informazioni per rispondere alla tua domanda. Nelle a[b] = b = 0
regole dell'associatività dire che questo è a[b] = (b = 0);
ma ciò non significa che la b=0
corsa prima! Le regole di precedenza dicono che l'indicizzazione è una precedenza più alta dell'assegnazione, ma ciò non significa che l'indicizzatore venga eseguito prima dell'assegnazione più a destra .
(AGGIORNAMENTO: Una versione precedente di questa risposta aveva alcune piccole e praticamente irrilevanti omissioni nella sezione che segue che ho corretto. Ho anche scritto un articolo sul blog che descrive perché queste regole sono ragionevoli in Java e C # qui: https: // ericlippert.com/2019/01/18/indexer-error-cases/ )
La precedenza e l'associatività ci dicono solo che l'assegnazione di zero a b
deve avvenire prima dell'assegnazione a a[b]
, perché l'assegnazione di zero calcola il valore assegnato nell'operazione di indicizzazione. Precedenza e associatività da soli non dicono nulla sul fatto che la a[b]
viene valutata , prima o dopo il b=0
.
Ancora una volta, questo è esattamente lo stesso di: A()[B()] = C()
- Tutto ciò che sappiamo è che l'indicizzazione deve avvenire prima dell'assegnazione. Non sappiamo se A (), B () o C () viene eseguito prima in base alla precedenza e all'associatività . Abbiamo bisogno di un'altra regola per dircelo.
La regola è, ancora una volta, "quando hai una scelta su cosa fare prima, vai sempre da sinistra a destra". Tuttavia, c'è una ruga interessante in questo scenario specifico. L'effetto collaterale di un'eccezione generata causata da una raccolta nulla o da un indice fuori intervallo è considerato parte del calcolo del lato sinistro dell'assegnazione o parte del calcolo dell'assegnazione stessa? Java sceglie quest'ultimo. (Ovviamente, questa è una distinzione che importa solo se il codice è già sbagliato , perché il codice corretto non dereferenzia nulla o passa un indice errato in primo luogo.)
Allora cosa succede?
- Il
a[b]
è a sinistra del b=0
, quindi le a[b]
piste prima , con conseguente a[1]
. Tuttavia, la verifica della validità di questa operazione di indicizzazione viene ritardata.
- Poi
b=0
succede.
- Quindi
a
avviene la verifica che è valida ed a[1]
è nel range
- L'assegnazione del valore a
a[1]
avviene per ultima.
Quindi, sebbene in questo caso specifico ci siano alcune sottigliezze da considerare per quei rari casi di errore che non dovrebbero verificarsi nel codice corretto in primo luogo, in generale puoi ragionare: le cose a sinistra accadono prima delle cose a destra . Questa è la regola che stai cercando. Parlare di precedenza e associatività è al tempo stesso confuso e irrilevante.
Le persone sbagliano sempre queste cose , anche le persone che dovrebbero saperlo meglio. Ho modificato troppi libri di programmazione che affermavano le regole in modo errato, quindi non sorprende che molte persone abbiano convinzioni completamente errate sulla relazione tra precedenza / associatività e ordine di valutazione, ovvero che in realtà non esiste tale relazione ; sono indipendenti.
Se questo argomento ti interessa, consulta i miei articoli sull'argomento per ulteriori letture:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
Riguardano C #, ma la maggior parte di queste cose si applica altrettanto bene a Java.