Video: Linux Tutorial for Beginners: Introduction to Linux Operating System 2024
di Stephen R. Davis
C ++ non è un linguaggio di programmazione facile da padroneggiare. Solo attraverso l'esperienza le innumerevoli combinazioni di simboli inizieranno a sembrarti naturali. Questo Cheat Sheet, tuttavia, offre alcuni validi suggerimenti su come facilitare la transizione dal principiante C ++ al guru C ++: sapere come leggere espressioni C ++ complesse; impara come evitare i problemi del puntatore; e capire come e quando fare copie profonde.
Come leggere un'espressione C ++ complessa
C ++ è pieno di piccoli simboli, ognuno dei quali aggiunge al significato delle espressioni. Le regole della grammatica C ++ sono così flessibili che questi simboli possono essere combinati in combinazioni quasi impenetrabilmente complesse. Le espressioni nel linguaggio C più semplice possono diventare così ottuse che una volta era un concorso annuale per chi poteva scrivere il programma più oscuro e chi poteva capirlo.
Non è mai una buona idea provare a scrivere codice complesso, ma a volte si incontrano espressioni in C ++ che sono un po 'sconcertanti a prima vista. Basta usare i seguenti passaggi per capirli:
-
Inizia dalla parentesi più incorporata.
Inizia a cercare le parentesi più esterne. All'interno di quelli, cerca parentesi incorporate. Ripeti il processo fino a quando non ti sei avvicinato alla coppia più profonda di parentesi. Inizia a valutare quella sottoespressione prima usando le seguenti regole. Una volta compresa questa espressione, torna al livello successivo e ripeti il processo.
-
All'interno della coppia di parentesi, valuta ogni operazione in ordine di precedenza.
L'ordine di valutazione degli operatori è determinato dalla precedenza dell'operatore indicata nella tabella. L'indirezione viene prima della moltiplicazione che precede l'aggiunta, quindi il seguente aggiunge 1 più 2 volte il valore indicato da * ptr.
int i = 1 + 2 * * ptr;
Precedenza | Operatore | Significato |
---|---|---|
1 | () (unario) | Richiama una funzione |
2 | * e -> (unario) | Dereferenziamento di un puntatore |
2 | - (unario) | Restituisce il negativo del suo argomento |
3 | ++ (unario) | Incremento |
3 > - (unario) | Decremento | 4 |
* (binario) | Moltiplicazione | 4 |
/ (binario) | Divisione | 4 |
% (binario) | Modulo | 5 |
+ (binario) | Aggiunta | 5 |
- (binario) | Sottrazione | 6 |
&& (binario) | E logico | 6 |
! ! | Logico OR | 7 |
=, * =,% =, + =, - = (speciale) | Tipi di assegnazione | Valutare le operazioni della stessa precedenza da sinistra a destra (eccetto l'assegnazione, che va dall'altra parte). |
-
La maggior parte degli operatori della stessa precedenza valuta da sinistra a destra. Quindi il seguente aggiunge 1 a 2 e aggiunge il risultato a 3:
int i = 1 + 2 + 3;
L'ordine di valutazione di alcuni operatori non ha importanza. Ad esempio, l'aggiunta funziona allo stesso modo da sinistra a destra come da destra a sinistra. L'ordine di valutazione fa molta differenza per alcune operazioni come la divisione. Il seguente divide 8 per 4 e divide il risultato per 2:
int i = 8/4/2;
L'eccezione principale a questa regola è l'assegnazione, che viene valutata da destra a sinistra:
a = b = c;
Assegna a c a b e il risultato a a.
Valuta le sottoespressioni in nessun ordine particolare.
-
Considera la seguente espressione:
int i = f () + g () * h ();
La moltiplicazione ha una precedenza più alta, quindi si potrebbe presumere che le funzioni g () eh () siano chiamate prima di f (), tuttavia, questo non è il caso. La chiamata alla funzione ha la precedenza più alta di tutte, quindi tutte e tre le funzioni vengono chiamate prima che venga eseguita la moltiplicazione o l'aggiunta. (I risultati restituiti da g () e h () vengono moltiplicati e quindi aggiunti ai risultati restituiti da f ().)
L'unica volta che l'ordine con cui vengono chiamate le funzioni fa la differenza è quando la funzione ha effetti collaterali come aprire un file o modificare il valore di una variabile globale. Non dovresti assolutamente scrivere i tuoi programmi in modo che dipendano da questo tipo di effetti collaterali.
Esegui qualsiasi tipo di conversione solo quando necessario.
-
Non dovresti fare più conversioni di tipo del necessario. Ad esempio, la seguente espressione ha almeno tre e possibilmente quattro conversioni di tipi:
float f = 'a' + 1;
Il carattere "a" deve essere promosso a un int per eseguire l'aggiunta. L'int viene quindi convertito in double e quindi convertito in down in un singolo float di precisione. Ricorda che tutta l'aritmetica viene eseguita sia in int che in double. In generale, dovresti evitare di eseguire operazioni aritmetiche sui tipi di caratteri ed evitare del tutto il galleggiamento a precisione singola.
5 modi per evitare i problemi con i puntatori in C ++
In C ++, un puntatore
è una variabile che contiene l'indirizzo di un oggetto nella memoria interna del computer. Utilizzare questa procedura per evitare problemi con i puntatori in C ++: Inizializza i puntatori quando vengono dichiarati.
-
Non lasciare mai le variabili del puntatore non inizializzate - le cose non sarebbero male se i puntatori non inizializzati contengono sempre valori casuali - la maggior parte dei valori casuali sono valori di puntatore illegali e causeranno il crash del programma non appena vengono utilizzati. Il problema è che le variabili non inizializzate tendono ad assumere il valore di altre variabili puntatore precedentemente utilizzate. Questi problemi sono molto difficili da eseguire il debug.
Se non sai in quale altro modo inizializzare un puntatore, inizializzalo su nullptr. nullptr è garantito essere un indirizzo illegale.
Elimina i puntatori dopo averli utilizzati.
-
Allo stesso modo, azzerare sempre una variabile puntatore quando il puntatore non è più valido assegnandogli il valore nullptr. Questo è particolarmente vero quando si restituisce un blocco di memoria all'heap usando delete; azzerare sempre il puntatore dopo aver restituito la memoria heap.
Assegna memoria dall'heap e restituiscila nell'heap allo stesso "livello" per evitare perdite di memoria.
-
Cerca sempre di restituire un blocco di memoria all'heap allo stesso livello di astrazione che hai assegnato. Ciò significa in genere cercare di eliminare la memoria allo stesso livello delle chiamate di funzione.
Cattura un'eccezione per eliminare la memoria quando necessario.
-
Non dimenticare che un'eccezione può verificarsi in qualsiasi momento. Se si intende rilevare l'eccezione e continuare a funzionare (al contrario di lasciare il programma in crash), assicurarsi di rilevare l'eccezione e restituire eventuali blocchi di memoria all'heap prima che i puntatori che puntano su di essi vadano fuori ambito e la memoria sia perduto.
Assicurarsi che i tipi corrispondano esattamente.
-
Assicurarsi sempre che i tipi di puntatori corrispondano al tipo richiesto. Non modificare un puntatore senza una ragione specifica. Considera quanto segue:
void fn (int * p); void myFunc () {char c = 'a'; char * pC = & c; fn ((int *) pC);}
La funzione sopra riportata si compila senza lamentele poiché il puntatore di caratteri pC è stato rifuso a un * int per corrispondere alla dichiarazione di fn (int *); tuttavia, questo programma quasi sicuramente non funzionerà. La funzione fn () è in attesa di un puntatore a un intero intero a 32 bit e non un po 'rinky-dink 8 bit. Questi tipi di problemi sono molto difficili da risolvere.
Come e quando fare copie profonde in C ++
Le classi che assegnano risorse nel loro costruttore dovrebbero normalmente includere un costruttore di copie per creare copie di queste risorse. L'assegnazione di un nuovo blocco di memoria e la copia del contenuto dell'originale in questo nuovo blocco è noto come creazione di una copia profonda
(in contrasto con la copia superficiale predefinita). Utilizzare la seguente procedura per determinare come e quando eseguire copie approfondite in C ++: Eseguire sempre una copia profonda se il costruttore assegna le risorse.
-
Per impostazione predefinita, C ++ crea le cosiddette copie "superficiali" membro per membro degli oggetti quando li passa a funzioni o come risultato di un compito. È necessario sostituire gli operatori di copia superficiale predefiniti con il loro equivalente di copia profonda per qualsiasi classe che alloca risorse nel costruttore. La risorsa più comune che viene allocata è la memoria heap restituita dal nuovo operatore.
Includere sempre un distruttore per una classe che assegna risorse.
-
Se si crea un costruttore che assegna risorse, è necessario creare un distruttore che lo ripristini. Nessuna eccezione.
Dichiara sempre il distruttore virtuale.
-
Un errore principiante comune è dimenticare di dichiarare virtuale il distruttore. Il programma funzionerà fino a quando un programmatore ignaro arriva e eredita dalla tua classe. Il programma sembra funzionare, ma poiché il distruttore nella classe base non può essere invocato correttamente, la memoria perde dal tuo programma come un setaccio finché non si arresta in modo anomalo. Questo problema è difficile da trovare.
Includere sempre un costruttore di copie per una classe che assegna risorse.
-
Il costruttore di copie crea una copia corretta dell'oggetto corrente allocando la memoria dall'heap e copiando il contenuto dell'oggetto sorgente.
Esegui sempre l'override dell'operatore di assegnazione per una classe che assegna risorse.
-
I programmatori dovrebbero essere scoraggiati dagli operatori preposti, ma l'operatore di assegnazione è un'eccezione. È necessario sovrascrivere l'operatore di assegnazione per qualsiasi classe che alloca risorse nel costruttore.
L'operatore di assegnazione dovrebbe fare tre cose:
Assicurarsi che l'oggetto sinistro e destro non siano lo stesso oggetto. In altre parole, assicurarsi che il programmatore dell'applicazione non abbia scritto qualcosa come (a = a). Se lo sono, non fare nulla.
-
Richiama lo stesso codice del distruttore sull'oggetto a sinistra per restituire le sue risorse.
-
Richiama lo stesso codice di un costruttore di copie per creare una copia profonda dell'oggetto a destra nell'oggetto a sinistra.
-
Se non è possibile farlo, eliminare il costruttore della copia e l'operatore di assegnazione in modo che il programma non possa creare copie del proprio oggetto.
-
-
Se non si riesce nemmeno a farlo perché il compilatore non supporta la funzione di costruzione dell'eliminazione di C ++ 2011, creare un costruttore di copia e un operatore di assegnazione vuoto e dichiararli protetti per impedire alle altre classi di utilizzarli.