Perché abbiamo l'incremento postfix?


55

Disclaimer : conosco perfettamente la semantica del prefisso e l'incremento postfisso. Quindi per favore non spiegarmi come funzionano.

Leggendo le domande sullo overflow dello stack, non posso fare a meno di notare che i programmatori vengono continuamente confusi dall'operatore di incremento postfix. Da ciò nasce la seguente domanda: esiste qualche caso d'uso in cui l'incremento postfix offre un reale vantaggio in termini di qualità del codice?

Vorrei chiarire la mia domanda con un esempio. Ecco un'implementazione super-concisa di strcpy:

while (*dst++ = *src++);

Ma questo non è esattamente il codice più auto-documentante nel mio libro (e produce due fastidiosi avvertimenti sui compilatori sani di mente). Quindi cosa c'è che non va nella seguente alternativa?

while (*dst = *src)
{
    ++src;
    ++dst;
}

Possiamo quindi eliminare l'incarico confuso nella condizione e ottenere un codice completamente privo di avvisi:

while (*src != '\0')
{
    *dst = *src;
    ++src;
    ++dst;
}
*dst = '\0';

(Sì, lo so, srce dstavranno valori finali diversi in queste soluzioni alternative, ma poiché strcpyritorna immediatamente dopo il ciclo, in questo caso non importa.)

Sembra che lo scopo dell'incremento postfix sia di rendere il codice il più conciso possibile. Semplicemente non riesco a vedere come questo è qualcosa per cui dovremmo lottare. Se inizialmente si trattava di prestazioni, è ancora rilevante oggi?


7
La variante postfix è la variante più comunemente usata, quindi se hai intenzione di fare un caso contro di essa, è meglio che sia un caso piuttosto forte. E il tuo primo esempio è un po 'un uomo di paglia, dal momento che sospetto che poche persone in realtà codifichino il strcpymetodo in questo modo (per motivi che hai già menzionato).
Robert Harvey,

4
Perché l'uno o l'altro?
James McNellis,

31
Se togliessimo tutto dalla lingua che aveva il potenziale per confondere i programmatori, non avremmo molte funzionalità. Il fatto che qualcosa non ti sia utile, o solo raramente utile, non significa che debba essere tagliato. Se non è più rilevante, non utilizzarlo; fine.
Cody Grey,

4
Sì, ma non puoi mai farloint c = 0; c++;
Biff MaGriff,

4
@Cody: perché alcune cose sono sia confuse che utili. Non è confuso dallo stesso post-incremento, è confuso dalla sua utilità . Non dovremmo avere cose inutili, confuse o no.
GManNickG

Risposte:


23

Mentre una volta aveva alcune implicazioni sulle prestazioni, penso che la vera ragione sia esprimere il tuo intento in modo pulito. La vera domanda è se qualcosa while (*d++=*s++);esprime chiaramente o meno l'intento. IMO, lo fa, e trovo le alternative che offri meno chiare, ma ciò potrebbe (facilmente) essere il risultato di aver passato decenni ad abituarsi al modo in cui le cose vengono fatte. Avere imparato C da K&R (perché all'epoca non c'erano quasi altri libri su C) probabilmente aiuta anche.

In una certa misura, è vero che la terseness è stata valutata in misura molto maggiore nel codice precedente. Personalmente, penso che questa sia stata in gran parte una buona cosa: capire alcune righe di codice è di solito abbastanza banale; ciò che è difficile è comprendere grandi blocchi di codice. Test e studi hanno dimostrato ripetutamente che l'adattamento di tutto il codice sullo schermo in una sola volta è un fattore importante nella comprensione del codice. Man mano che le schermate si espandono, questo sembra rimanere vero, quindi mantenere il codice (ragionevolmente) conciso rimane prezioso.

Ovviamente è possibile esagerare, ma non credo che lo sia. In particolare, penso che esagerare quando la comprensione di una singola riga di codice diventa estremamente difficile o richiede tempo, in particolare quando la comprensione di un numero minore di righe di codice richiede più sforzo rispetto alla comprensione di più righe. Questo è frequente in Lisp e APL, ma non sembra (almeno per me) essere il caso qui.

Sono meno preoccupato per gli avvisi del compilatore - è la mia esperienza che molti compilatori emettono avvisi assolutamente ridicoli su una base abbastanza regolare. Mentre certamente penso che le persone dovrebbero capire il loro codice (e tutti gli avvisi che potrebbe produrre), il codice decente che si verifica per attivare un avviso in alcuni compilatori non è necessariamente sbagliato. Certo, i principianti non sanno sempre cosa possono tranquillamente ignorare, ma non restiamo per sempre principianti e non abbiamo bisogno di programmare come noi.


12
+1 per sottolineare che la versione concisa è più facile da leggere per alcuni di noi. :-)

1
Se non come alcuni dei vostri avvisi del compilatore (e ci sono più di un paio ho potuto fare a meno - sul serio, io intendevo a digitare if(x = y), lo giuro!) È necessario regolare le opzioni avvertenze del compilatore in modo da non perdere accidentalmente il avvertimenti si fanno simili.

4
@Brooks Moses: sono molto meno entusiasta di questo. Non mi piace inquinare il mio codice per compensare le carenze del compilatore.
Jerry Coffin,

1
@Jerry, @Chris: questa annotazione è intesa nel caso in cui pensi che l'avvertimento sia di solito una buona cosa, ma non vuoi che si applichi a una particolare riga che sta facendo qualcosa di insolito. Se non vuoi mai l'avvertimento e lo consideri un difetto, usa -Wno-X, ma se vuoi fare solo un'eccezione e vuoi che l'avviso si applichi ovunque, questo è molto più comprensibile dell'uso del salto con il cerchio arcano come Chris fa riferimento .

1
@Brooks: le doppie parentesi e gli (void)xavvisi di silenzio non utilizzati sono idiomi abbastanza noti per mettere a tacere avvertimenti altrimenti utili. L'annotazione assomiglia un po ' pragmas, per niente portatile: /
Matthieu M.

32

È, err, era, una cosa hardware


Interessante da notare. L'incremento di Postfix è probabilmente lì per una serie di ragioni perfettamente valide, ma come molte cose in C, la sua popolarità può essere fatta risalire all'origine della lingua.

Sebbene C sia stato sviluppato su una varietà di macchine precoci e sottodimensionate, C e Unix hanno colpito per la prima volta il relativo big-time con i modelli di PDP-11 gestiti dalla memoria. Questi erano computer relativamente importanti ai loro tempi e Unix era di gran lunga migliore - esponenzialmente migliore - degli altri 7 sistemi operativi scadenti disponibili per -11.

E, succede così, che sul PDP-11,

*--p

e

*p++

... sono stati implementati nell'hardware come modalità di indirizzamento. (Inoltre *p, ma nessun'altra combinazione.) Su quei primi computer, tutti a meno di 0,001 GHz, il salvataggio di un'istruzione o due in un ciclo deve essere quasi stato un attesa al secondo o attendere un minuto o uscire per -grassa differenza. Questo non parla precisamente del postincremento, ma un ciclo con postincremento puntatore avrebbe potuto essere molto meglio dell'indicizzazione di allora.

Di conseguenza, il design si modella perché i modi di dire C sono diventati macro mentali.

È qualcosa come dichiarare le variabili subito dopo un {... non da quando C89 era attuale, questo è stato un requisito, ma ora è un modello di codice.

Aggiornamento: ovviamente, il motivo principale *p++è nella lingua perché è esattamente ciò che si vuole fare così spesso. La popolarità del modello di codice fu rafforzata dall'hardware popolare che arrivò e corrispondeva al modello già esistente di un linguaggio progettato leggermente prima dell'arrivo del PDP-11.

In questi giorni non fa alcuna differenza quale modello usi o se usi l'indicizzazione e di solito programmiamo a un livello superiore, ma deve aver avuto molta importanza su quelle macchine a 0,001 GHz e usare qualsiasi cosa diversa da *--xo ti *x++avrebbe significato non hai "preso" il PDP-11 e potresti avere persone che ti vengono incontro e che dicono "lo sapevi che ..." :-) :-)


25
Wikipedia dice: "Un mito popolare (falso) è che l'architettura del set di istruzioni del PDP-11 ha influenzato l'uso idiomatico del linguaggio di programmazione C. Le modalità di indirizzamento di incremento e decremento del PDP-11 corrispondono a −−ie i++costruiscono in C. Se ie jerano entrambe variabili di registro, un'espressione come quella che *(−−i) = *(j++)poteva essere compilata in una singola istruzione macchina. [...] Dennis Ritchie contraddice inequivocabilmente questo mito popolare. [Lo sviluppo del linguaggio C ] "
fredoverflow,

3
Huh (dal link fornito nel commento di FredOverflow): "Le persone spesso immaginano che siano state create per utilizzare le modalità di indirizzamento e decremento automatico fornite dal DEC PDP-11 su cui C e Unix sono diventati popolari per la prima volta. Questo è storicamente impossibile, dal momento che non è stato sviluppato PDP-11 quando è stato sviluppato B. Il PDP-7, tuttavia, aveva alcune celle di memoria "auto-incrementate", con la proprietà che un riferimento di memoria indiretta attraverso di esse aumentava la cella. ha suggerito tali operatori a Thompson; la generalizzazione per renderli sia prefisso che postfisso era sua ".

3
DMR ha solo affermato che il PDP-11 non è stato il motivo per cui è stato aggiunto a C. Ho detto anche questo. Quello che ho detto è che è stato usato per trarre vantaggio dal PDP-11 e per questo motivo è diventato un modello di progettazione popolare. Se contraddice Wikipedia che poi vengono semplicemente sbagliato, ma non leggo in questo modo. È espresso male su quella pagina W.
DigitalRoss

3
@FredOverflow. Penso che tu abbia frainteso esattamente quale sia il "mito popolare". Il mito è che C è stato progettato per sfruttare quelle 11 operazioni, non che non esistano e non che il modello di codice non sia diventato popolare perché tutti sapevano di essere mappati sull'11 bene. Nel caso in cui non fosse chiaro: il PDP-11 implementa davvero queste due modalità hardware per quasi tutte le istruzioni come modalità di indirizzamento, ed è stata davvero la prima macchina importante su cui C ha funzionato.
DigitalRoss

2
"deve aver avuto molta importanza su quelle macchine a 0,001 GHz". Ehm, odio dirlo perché mi risale, ma avevo i manuali Motorola e Intel per le mie CPU per cercare le dimensioni delle istruzioni e il numero di cicli necessari. Trovare i codici più piccoli aggiunti alla possibilità di aggiungere un'altra funzionalità a uno spooler di stampa e salvare un paio di cicli significava che una porta seriale poteva tenere il passo con un protocollo come quello MIDI appena inventato. C alleviò un po 'di nit-picking, specialmente quando i compilatori migliorarono, ma era comunque importante sapere qual era il modo più efficiente di scrivere codice.
Tin Man,

14

Il prefisso, il postfisso --e gli ++operatori furono introdotti nel linguaggio B (il predecessore di C) da Ken Thompson - e no, non furono ispirati dal PDP-11, che all'epoca non esisteva.

Citando da "Lo sviluppo del linguaggio C" di Dennis Ritchie :

Thompson ha fatto un ulteriore passo avanti inventando il ++e--operatori, che incrementano o diminuiscono; la loro posizione prefisso o postfisso determina se l'alterazione si verifica prima o dopo aver notato il valore dell'operando. Non erano nelle prime versioni di B, ma apparvero lungo la strada. Le persone spesso immaginano di essere state create per utilizzare le modalità di indirizzo di auto-incremento e decremento automatico fornite dal DEC PDP-11 su cui C e Unix sono diventati popolari per la prima volta. Questo è storicamente impossibile, dal momento che non c'era PDP-11 quando B è stato sviluppato. Il PDP-7, tuttavia, aveva alcune celle di memoria "auto-incrementate", con la proprietà che un riferimento di memoria indiretta attraverso di esse incrementava la cella. Questa caratteristica suggeriva probabilmente tali operatori a Thompson; la generalizzazione per renderli sia prefissi che postfissi era la sua. Infatti,++xera più piccolo di quello di x=x+1.


8
No, non sono imparentato con Ken Thompson.
Keith Thompson,

Grazie per questo riferimento molto interessante! Ciò conferma la mia citazione dall'originale K&R che suggeriva l'obiettivo di compattezza piuttosto che di ottimizzazione tecnica. Non sapevo tuttavia che questi operatori esistessero già in B.
Christophe,

@BenPen Per quanto ne so, non esiste una politica di chiusura delle domande se esiste un duplicato su un sito SE diverso , purché entrambe le domande si adattino ai rispettivi siti.
Ordous,

Interessante ... Il programmatore non è certo una domanda così vicina.
BenPen,

@BenPen: la risposta accettata alla domanda Stack Overflow cita già la stessa fonte che ho fatto (anche se non abbastanza).
Keith Thompson,

6

La ragione ovvia dell'esistenza dell'operatore di postincremento è che non è necessario scrivere espressioni simili (++x,x-1)o (x+=1,x-1)dappertutto o separare inutilmente dichiarazioni banali con effetti collaterali di facile comprensione in più istruzioni. Ecco alcuni esempi:

while (*s) x+=*s++-'0';

if (x) *s++='.';

Ogni volta che leggere o scrivere una stringa in avanti, il postincremento e non il preincremento, è quasi sempre un'operazione naturale. Non ho quasi mai incontrato gli usi del mondo reale per il preincremento, e l'unico uso principale che ho trovato per il pre-incremento è la conversione di numeri in stringhe (perché i sistemi di scrittura umana scrivono numeri al contrario).

Modifica: In realtà non sarebbe così brutto; invece di (++x,x-1)te, ovviamente, puoi usare ++x-1o (x+=1)-1. È ancora x++più leggibile.


Devi stare attento a quelle espressioni poiché stai modificando e leggendo una variabile tra due punti di sequenza. L'ordine in cui vengono valutate tali espressioni non è garantito.

@Snowman: quale? Non vedo tali problemi nella mia risposta.
R ..

se hai qualcosa del tipo (++x,x-1)(funzione chiamata, forse?)

@Snowman: non è una chiamata di funzione. È l'operatore virgola C, che è un punto sequenza, tra parentesi per controllare la precedenza. Il risultato è lo stesso x++della maggior parte dei casi (un'eccezione: xè un tipo senza segno con rango inferiore inte il valore iniziale di xè il valore massimo di quel tipo).
R ..

Ah, l'operatore virgola difficile ... quando una virgola non è una virgola. La parentesi e la rarità dell'operatore virgola mi hanno buttato via. Non importa!

4

Il PDP-11 offriva operazioni di post-incremento e pre-decremento nel set di istruzioni. Ora queste non erano istruzioni. Erano modificatori di istruzioni che ti permettevano di specificare che volevi il valore di un registro prima che fosse incrementato o dopo che fosse diminuito.

Ecco un passaggio per la copia di parole nel linguaggio macchina:

movw (r1)++,(r2)++

che spostava una parola da una posizione all'altra, indicata dai registri, e i registri sarebbero pronti a farlo di nuovo.

Poiché C è stato sviluppato e per il PDP-11, molti concetti utili hanno trovato la loro strada in C. C era inteso come un utile sostituto del linguaggio assemblatore. Sono stati aggiunti pre-incremento e post-decremento per simmetria.


Sono brillanti modificatori ... Mi fa venire voglia di imparare l'assemblaggio PDP-11. A proposito, hai un riferimento su "per simmetria?" Ha senso, ma questa è storia, a volte ha senso non era la chiave. LOL
BenPen,

2
Conosciuto anche come modalità di indirizzamento . Il PDP-11 ha fornito post-incremento e pre-decremento che si sono accoppiati perfettamente per le operazioni dello stack.
Erik Eidt,

1
Il PDP-8 ha fornito una indiretta di memoria post-incremento, ma nessuna corrispondente operazione di pre-decremento. Con la memoria del nucleo magnetico, la lettura di una parola di memoria l'ha cancellata (!), Quindi il processore ha utilizzato una funzione di riscrittura per ripristinare la parola appena letta. L'applicazione di un incremento prima della riscrittura è avvenuta in modo piuttosto naturale e è diventato possibile uno spostamento del blocco più efficiente.
Erik Eidt,

3
Spiacenti, il PDP-11 non ha ispirato questi operatori. Non esisteva ancora quando Ken Thompson li ha inventati. Vedere la mia risposta .
Keith Thompson,

3

Dubito che sia mai stato davvero necessario. Per quanto ne so, non si compila in qualcosa di più compatto sulla maggior parte delle piattaforme rispetto all'utilizzo del pre-incremento come hai fatto tu, nel ciclo. Era solo che al momento della creazione, la terseness del codice era più importante della chiarezza del codice.

Ciò non vuol dire che la chiarezza del codice non sia (o non fosse) importante, ma quando si digitava su un modem a basso baud (tutto ciò che si misura in baud è lento) in cui ogni sequenza di tasti doveva farcela il mainframe e quindi riecheggiare di nuovo un byte alla volta con un singolo bit utilizzato come controllo di parità, non è stato necessario digitare molto.

Questo è un po 'come il & e | operatore con precedenza inferiore a ==

È stato progettato con le migliori intenzioni (una versione non in corto circuito di && e ||), ma ora confonde i programmatori ogni giorno e probabilmente non cambierà mai.

Comunque, questa è solo una risposta in quanto penso che non ci sia una buona risposta alla tua domanda, ma probabilmente sarò smentito da un programmatore più guru di I.

--MODIFICARE--

Vorrei sottolineare che io trovo avendo entrambi incredibilmente utile, sto solo sottolineando che non cambierà se qualcuno piaccia o no.


Questo non è neanche lontanamente vero. La "terseness of code" non è mai stata più importante della chiarezza.
Cody Grey,

Bene, direi che era molto più un punto focale. Hai ragione, però, non è mai stato più importante della chiarezza del codice ... per la maggior parte delle persone.

6
Bene, la definizione di "terse" è "elegantemente liscia: raffinata" o "usando poche parole: priva di superfluità", entrambe cose generalmente buone quando si scrive un codice. "Terse" non significa "oscuro, difficile da leggere e offuscato" come pensano molte persone.
James McNellis,

@James: Mentre hai ragione, penso che la maggior parte delle persone intenda la seconda definizione di "terse" quando la si utilizza in un contesto di programmazione: "usando poche parole; priva di superfluità". In base a tale definizione, è certamente più facile immaginare situazioni in cui vi sia un compromesso tra la brevità e l'espressività di un particolare codice. Ovviamente la cosa giusta da fare quando si scrive codice è la stessa cosa di quando si parla: abbreviare le cose come devono essere, ma non più brevi. Valutare la terseness sull'espressività può portare a un codice dall'aspetto offuscato, ma certamente non dovrebbe.
Cody Grey,

1
riavvolgi 40 anni e immagina di dover adattare il tuo programma a quel tamburo che conterrà solo circa 1000 caratteri, oppure scrivere un compilatore può funzionare solo con 4 mila byte di RAM. Ci sono stati momenti in cui terseness vs chiarezza non era nemmeno un problema. Dovevi essere conciso, non esisteva un computer su questo pianeta in grado di memorizzare, eseguire o compilare il tuo programma non conciso.
nn

3

Mi piace il postfix aperator quando ho a che fare con i puntatori (indipendentemente dal fatto che li dereferenzi o meno) perché

p++

legge più naturalmente come "passa al punto successivo" rispetto all'equivalente

p += 1

che sicuramente deve confondere i principianti la prima volta che lo vedono usato con un puntatore dove sizeof (* p)! = 1.

Sei sicuro di affermare correttamente il problema della confusione? Non è ciò che confonde i principianti il ​​fatto che l'operatore postfix ++ abbia una precedenza maggiore rispetto all'operatore dereference * in modo che

*p++

analizza come

*(p++)

e non

(*p)++

come qualcuno potrebbe aspettarsi?

(Pensi che sia male? Vedi la lettura delle dichiarazioni C. )


2

Tra gli elementi sottili di una buona programmazione ci sono la localizzazione e il minimalismo :

  • mettere le variabili in un ambito di utilizzo minimo
  • usando const quando non è richiesto l'accesso in scrittura
  • eccetera.

Nello stesso spirito, x++può essere visto come un modo per localizzare un riferimento al valore corrente di xmentre indica immediatamente che hai - almeno per ora - finito con quel valore e vuoi passare al successivo (valido se xè un into un puntatore o iteratore). Con un po 'di immaginazione, potresti paragonare questo a lasciare il vecchio valore / posizione di x"fuori campo" immediatamente dopo che non è più necessario e passare al nuovo valore. Il punto preciso in cui è possibile tale transizione è evidenziato dal postfisso++ . L'implicazione che questo è probabilmente l'uso finale del "vecchio" xpuò essere una preziosa visione dell'algoritmo, aiutando il programmatore mentre scansionano il codice circostante. Certo, il postfisso++ può mettere il programmatore alla ricerca di usi del nuovo valore, che può essere o meno buono a seconda di quando quel nuovo valore è effettivamente necessario, quindi è un aspetto dell '"arte" o "mestiere" della programmazione per determinare cosa c'è di più utile nelle circostanze.

Mentre molti principianti possono essere confusi da una caratteristica, vale la pena bilanciarlo con i vantaggi a lungo termine: i principianti non rimangono principianti a lungo.


2

Wow, molte risposte non del tutto esatte (se posso essere così audace), e per favore perdonami se sto sottolineando l'ovvio, in particolare alla luce del tuo commento per non sottolineare la semantica, ma l'ovvio (dal punto di vista di Stroustrup, suppongo) non sembra ancora essere stato pubblicato! :)

Postfix x++produce un temporaneo che viene passato "verso l'alto" nell'espressione, mentre la variabile xviene successivamente incrementata.

Il prefisso ++xnon produce un oggetto temporaneo, ma incrementa 'x' e passa il risultato all'espressione.

Il valore è convenienza, per chi conosce l'operatore.

Per esempio:

int x = 1;
foo(x++); // == foo(1)
// x == 2: true

x = 1;
foo(++x); // == foo(2)
// x == 2: true

Naturalmente, i risultati di questo esempio possono essere prodotti da un altro codice equivalente (e forse meno obliquo).

Quindi perché abbiamo l'operatore postfix? Immagino perché sia ​​un linguaggio persistente, nonostante la confusione che ovviamente crea. È un costrutto legacy che una volta era percepito avere valore, anche se non sono sicuro che il valore percepito fosse tanto per le prestazioni quanto per la convenienza. Quella convenienza non è andata persa, ma penso che sia aumentato l'apprezzamento della leggibilità, con il risultato di mettere in discussione lo scopo dell'operatore.

Abbiamo più bisogno dell'operatore postfix? Probabilmente no. Il suo risultato è confuso e crea una barriera alla comprensibilità. Alcuni bravi programmatori sapranno immediatamente dove trovarlo utile, spesso in luoghi in cui ha una bellezza particolarmente "PERL-esque". Il costo di quella bellezza è leggibilità.

Concordo sul fatto che il codice esplicito abbia vantaggi rispetto a terseness, in leggibilità, comprensibilità e manutenibilità. Buoni designer e manager lo vogliono.

Tuttavia, c'è una certa bellezza che alcuni programmatori riconoscono che li incoraggia a produrre codice con cose puramente per una bella semplicità - come l'operatore postfix - quale codice che altrimenti non avrebbero mai pensato - o trovato stimolante intellettualmente. C'è qualcosa che lo rende più bello, se non più desiderabile, nonostante la devozione.

In altre parole, alcune persone trovano while (*dst++ = *src++);semplicemente una soluzione più bella, qualcosa che toglie il respiro con la sua semplicità, proprio come se fosse un pennello su tela. Il fatto che tu sia costretto a capire la lingua per apprezzare la bellezza non fa che aumentare la sua magnificenza.


3
+1 "alcune persone trovano while (* dst ++ = * src ++); per essere semplicemente una soluzione più bella". Decisamente. Le persone che non capiscono il linguaggio assembly non capiscono davvero quanto possa essere elegante la C, ma quella particolare frase di codice è una stella splendente.
Tin Man,

"Abbiamo più bisogno dell'operatore postfix? Probabilmente no." - Non posso fare a meno di pensare alle macchine di Turing qui. Abbiamo mai avuto bisogno di variabili automatiche, l' foraffermazione, diamine, anche while(abbiamo goto, dopo tutto)?

@Bernd: Ah! Immagina chiusure! Chiusure! Scandaloso! lol
Brian M. Hunt,

Chiusure! Quanto è ridicolo. Dammi solo un nastro! Seriamente, ciò che intendevo dire è che non penso / che necessiti / una caratteristica dovrebbe essere il criterio decisionale primario (a meno che tu non voglia progettare, diciamo, lisp). IME I uso (anzi, necessità ) gli operatori postfix quasi esclusivamente, e solo raramente è necessario quello prefisso.

2

Diamo un'occhiata alla giustificazione originale di Kernighan & Ritchie (K&R originali pagina 42 e 43):

Gli aspetti insoliti sono che ++ e - possono essere usati come prefisso o come postfisso. (...) Nel contesto in cui non si desidera alcun valore (..) scegliere il prefisso o il postfisso secondo i propri gusti. Ma i problemi sono situazioni in cui l'uno o l'altro è specificamente richiesto.

Il testo continua con alcuni esempi che utilizzano incrementi all'interno dell'indice, con l'obiettivo esplicito di scrivere codice " più compatto ". Quindi la ragione dietro questi operatori è la convenienza di un codice più compatto.

I tre esempi forniti ( squeeze(), getline()e strcat()) usano solo postfix all'interno delle espressioni usando l'indicizzazione. Gli autori confrontano il codice con una versione più lunga che non utilizza incrementi incorporati. Ciò conferma che l'attenzione è rivolta alla compattezza.

Evidenzia K&R a pagina 102, l'uso di questi operatori in combinazione con la dereferenziazione dei puntatori (ad es. *--pE *p--). Non viene fornito alcun esempio, ma ancora una volta chiariscono che il vantaggio è la compattezza.

Il prefisso è ad esempio molto comunemente usato quando si decrementa un indice o un puntatore che fissa dalla fine.

 int i=0; 
 while (i<10) 
     doit (i++);  // calls function for 0 to 9  
                  // i is 10 at this stage
 while (--i >=0) 
     doit (i);    // calls function from 9 to 0 

1

Non sono d'accordo con la premessa che ++p, in p++qualche modo, è difficile da leggere o poco chiaro. Uno significa "incrementa p e poi leggi p", l'altro significa "leggi p e quindi incrementa p". In entrambi i casi, l'operatore nel codice è esattamente dove si trova nella spiegazione del codice, quindi se sai cosa ++significa, sai cosa significa il codice risultante.

Puoi creare codice offuscato usando qualsiasi sintassi, ma qui non vedo un caso che è p++/ intrinsecamente offuscato.++p


1

questo è dal punto di vista di un ingegnere elettrico:

ci sono molti processori che dispongono di operatori post-incremento e pre-decremento integrati allo scopo di mantenere uno stack LIFO (last-in-first-out).

potrebbe essere come:

 float stack[4096];
 int stack_pointer = 0;

 ... 

 #define push_stack(arg) stack[stack_pointer++] = arg;
 #define pop_stack(arg) arg = stack[--stack_pointer];

 ...

non lo so, ma questo è il motivo per cui mi aspetterei di vedere gli operatori di incremento sia prefisso che postfisso.


1

Per sottolineare il punto di vista di Christophe sulla compattezza, voglio mostrare a tutti voi del codice V6. Questo fa parte del compilatore C originale, da http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/source/c/c00.c :

/*
 * Look up the identifier in symbuf in the symbol table.
 * If it hashes to the same spot as a keyword, try the keyword table
 * first.  An initial "." is ignored in the hash.
 * Return is a ptr to the symbol table entry.
 */
lookup()
{
    int ihash;
    register struct hshtab *rp;
    register char *sp, *np;

    ihash = 0;
    sp = symbuf;
    if (*sp=='.')
        sp++;
    while (sp<symbuf+ncps)
        ihash =+ *sp++;
    rp = &hshtab[ihash%hshsiz];
    if (rp->hflag&FKEYW)
        if (findkw())
            return(KEYW);
    while (*(np = rp->name)) {
        for (sp=symbuf; sp<symbuf+ncps;)
            if (*np++ != *sp++)
                goto no;
        csym = rp;
        return(NAME);
    no:
        if (++rp >= &hshtab[hshsiz])
            rp = hshtab;
    }
    if(++hshused >= hshsiz) {
        error("Symbol table overflow");
        exit(1);
    }
    rp->hclass = 0;
    rp->htype = 0;
    rp->hoffset = 0;
    rp->dimp = 0;
    rp->hflag =| xdflg;
    sp = symbuf;
    for (np=rp->name; sp<symbuf+ncps;)
        *np++ = *sp++;
    csym = rp;
    return(NAME);
}

Questo codice è stato diffuso in samizdat come un'istruzione di buon stile, eppure oggi non lo scriveremmo mai così. Osserva quanti effetti collaterali sono stati inseriti ife le whilecondizioni e , al contrario, in che modo entrambi i forloop non hanno un'espressione incrementale perché è presente nel corpo del loop. Guarda come i blocchi vengono avvolti tra parentesi graffe solo quando assolutamente necessario.

Parte di questo era sicuramente la micro-ottimizzazione manuale del tipo che ci aspettiamo che il compilatore faccia per noi oggi, ma vorrei anche avanzare l'ipotesi che quando tutta la tua interfaccia con il computer è una singola tty di vetro 80x25, vuoi il tuo codice essere il più denso possibile in modo da poterne vedere di più contemporaneamente.

(L'uso di buffer globali per tutto è probabilmente una sbornia dal taglio dei denti sul linguaggio assembly.)


Gestire con cura: "ihash = + * sp ++;" Ad un certo punto C aveva non solo + = ma anche = + operatore. Oggi, = + è un operatore di assegnazione, seguito da un unario più che non fa nulla, quindi equivale a "ihash = * sp; sp + = 1;"
gnasher729,

@ gnasher729 Sì, e poi ha rp->hflag =| xdflg, che credo sarà un errore di sintassi su un compilatore moderno. "Ad un certo punto" è precisamente V6, come accade; il passaggio alla +=forma avvenire in V7. (Il compilatore V6 ha accettato solo=+ se sto leggendo correttamente questo codice - vedi il simbolo () nello stesso file.)
zwol,

-1

Forse dovresti pensare anche ai cosmetici.

while( i++ )

probabilmente "sembra migliore" di

while( i+=1 )

Per qualche motivo, l'operatore post-incremento sembra molto interessante. È breve, dolce e aumenta tutto di 1. Confrontalo con il prefisso:

while( ++i )

"guarda indietro" non è vero? Non vedi mai un segno più PRIMA in matematica, tranne quando qualcuno è sciocco nel specificare qualcosa di positivo ( +0o qualcosa del genere). Siamo abituati a vedere OGGETTO + QUALCOSA

Ha a che fare con la valutazione? A, A +, A ++? Posso dirti che all'inizio ero piuttosto sconcertato dal fatto che le persone di Python si allontanassero operator++dalla loro lingua!


1
I tuoi esempi non fanno la stessa cosa. i++restituisce il valore originale di ie i+=1e ++irestituire il valore originale di ipiù 1. Dal momento che si sta utilizzando i valori di ritorno per il flusso di controllo, questo importa.
David Thornley,

Sì hai ragione. Ma sto solo guardando la leggibilità qui.
Bobobobo,

-2

Conteggio all'indietro da 9 a 0 incluso:

for (unsigned int i=10; i-- > 0;)  {
    cout << i << " ";
}

O l'equivalente ciclo while.


1
Che ne dici for (unsigned i = 9; i <= 9; --i)?
Fredoverflow,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.