Video: Review: Quiz 0 2024
Av Stephen R. Davis
C ++ är inte ett enkelt programmeringsspråk för att behärska. Endast genom erfarenhet kommer de myriade kombinationerna av symboler att verka som naturliga för dig. Detta fuskblad ger dig dock några soliga tips om att lätta övergången från C ++-nybörjare till C ++-guru: Lär dig hur man läser komplexa C ++-uttryck; lära sig att undvika pekare problem och räkna ut hur och när du ska göra djupa kopior.
Så här läser du en komplex C ++-uttryck
C ++ är full av små symboler, som alla lägger till betydelsen av uttryck. Reglerna för C ++ grammatik är så flexibla att dessa symboler kan kombineras i nästan impenetrably komplexa kombinationer. Uttryck i det enklare C-språket kan bli så otydliga att det var en årlig tävling för vem som kunde skriva det mest obskyra programmet och vem kunde förstå det.
Det är aldrig en bra idé att försöka skriva komplicerad kod, men du kommer ibland att springa över uttryck i C ++ som är lite förvirrande vid första anblicken. Använd bara följande steg för att räkna ut dem:
-
Börja med de flesta inbäddade parenteserna.
Börja leta efter de yttersta parenteserna. Inom dem, leta efter inbäddade parenteser. Upprepa processen tills du har arbetat dig till det djupaste paret. Börja utvärdera den subexpressionen först med hjälp av följande regler. När du förstår det uttrycket, spring tillbaka till nästa nivå och upprepa processen.
-
Inom paret parentes, utvärdera varje operation i prioritetsordning.
Den ordning som operatörerna utvärderas bestäms av operatörens företräde som visas i tabellen. Indirection kommer före multiplikation som kommer före tillsättning så lägger följande till 1 plus 2 gånger värdet pekat vid * ptr.
int i = 1 + 2 * * ptr;
Förekomst | Operator | Betydelse |
---|---|---|
1 | () (unary) | Inaktivera en funktion |
2 | * och -> (unary) | Dereference a pointer |
2 | - (unary) | Returnerar det negativa av sitt argument |
3 | ++ (unary) | Ökning |
3 > - (unary) | Decrement | 4 |
* (binär) | Multiplikation | 4 |
/ (binär) | Division | 4 |
% | Modulo | 5 |
+ (binär) | Addition | 5 |
- (binär) | Subtraktion | 6 |
&& (binär) | Logisk OCH | 6 |
! | Logisk ELLER | 7 |
=, * =,% =, + =, - = (special) | Uppgiftstyper | Utvärdera operationer med samma prioritet från vänster till höger (utom uppdrag, vilket går åt andra håll). |
-
De flesta operatörer med samma prioritet utvärderar från vänster till höger. Följande lägger till 1 till 2 och lägger resultatet till 3:
int i = 1 + 2 + 3;
Utvärderingsordningen för vissa operatörer spelar ingen roll. Till exempel fungerar tillägget samma från vänster till höger som det gör från höger till vänster. Utvärderingsordningen ger stor skillnad för vissa verksamheter som division. Följande delar 8 med 4 och delar resultatet med 2:
int i = 8/4/2;
Huvudundantaget från denna regel är uppgift, som utvärderas från höger till vänster:
a = b = c;
Detta tilldelar c till b och resultatet till a.
Utvärdera subexpressioner i ingen särskild ordning.
-
Tänk på följande uttryck:
int i = f () + g () * h ();
Multiplikation har högre prioritet, så du kan anta att funktionerna g () och h () kallas före f (), men detta är inte fallet. Funktionssamtal har högsta prioritet för alla, så alla tre funktioner kallas innan antingen multiplikationen eller tillägget utförs. (Resultaten som returneras från g () och h () multipliceras och läggs sedan till i resultaten som returneras från f ().)
Den enda gången som ordningen som fungerar kallas gör en skillnad är när funktionen har biverkningar till exempel att öppna en fil eller ändra värdet på en global variabel. Du bör absolut inte skriva dina program så att de är beroende av denna typ av biverkningar.
Utför endast typkonverteringar när det behövs.
-
Du bör inte göra mer typkonverteringar än absolut nödvändiga. Till exempel har följande uttryck åtminstone tre och möjligen fyra typkonverteringar:
float f = 'a' + 1;
Karet 'a' måste främjas till ett int för att utföra tillägget. Inten konverteras sedan till en dubbel och sedan ner omvandlas till en enda precisionsflotta. Kom ihåg att all aritmetik utförs antingen i int eller dubbel. Du borde i allmänhet undvika att utföra aritmetik på karaktärstyper och undviker enkel precisionsflotta helt och hållet.
5 sätt att undvika pekareproblem i C ++
I C ++ är en
pekare en variabel som innehåller adressen till ett objekt i datorns internminne. Använd dessa steg för att undvika problem med pekare i C ++: Initiera pekare när de deklareras.
-
Lämna aldrig pekervariablerna uninitialized - saker skulle inte vara så illa om uninitialiserade pekare alltid innehöll slumpmässiga värden. De allra flesta slumpmässiga värdena är olagliga pekarvärden och kommer att få programmet att krascha så snart de används. Problemet är att oinitierade variabler tenderar att ta på sig värdet av andra tidigare använda pekvariabler. Dessa problem är mycket svåra att felsöka.
Om du inte vet vad du vill initiera en pekare till, initiera den till nullptr. nullptr garanteras vara en olaglig adress.
Nollutpekare när du använder dem.
-
På samma sätt är alltid noll en pekervariabel en gång pekaren inte längre giltig genom att tilldela värdet nullptr. Det här är speciellt fallet när du returnerar ett minneblock till högen med hjälp av radering. noll pekaren alltid efter att ha återställt heapminne.
Tilldela minnet från högen och returnera det till högen på samma "nivå" för att undvika minnesläckor.
-
Försök alltid att returnera ett minnesblock till högen på samma abstraktionsnivå som du tilldelade den. Det innebär i allmänhet att försöka radera minnet på samma nivå av funktionssamtal.
Fånga ett undantag för att radera minne vid behov.
-
Glöm inte att ett undantag kan inträffa när som helst. Om du har för avsikt att fånga undantaget och fortsätta att fungera (i motsats till att låta programkraschen), se till att du tar undantaget och returnerar några minnesblock till högen innan de pekar som pekar på dem går utom räckhåll och minnet är förlorat.
Kontrollera att typerna matchar exakt.
-
Kontrollera alltid att typerna av pekare matchar önskad typ. Omarbeta inte en pekare utan någon särskild anledning. Tänk på följande:
void fn (int * p); void myFunc () {char c = 'a'; char * pC = & c; fn ((int *) pC);}
Ovanstående funktion kompilerar utan klagomål eftersom teckenpekaren pC har omarbetats till en int * för att matcha deklarationen av fn (int *); Detta program kommer dock nästan säkert inte att fungera. Funktionen fn () förväntar en pekare till ett fullständigt 32-bitars heltal och inte någon rinky-dink 8-bitars char. Dessa typer av problem är mycket svåra att sortera ut.
Hur och när man gör djupa kopior i C ++
Klasser som allokerar resurser i deras konstruktör bör normalt innehålla en kopiekonstruktör för att skapa kopior av dessa resurser. Att ange ett nytt minnesblock och kopiera innehållet i originalet till det här nya blocket är känt som att skapa en
djup kopia (i motsats till den grunda grunda kopian). Använd följande steg för att bestämma hur och när du ska göra djupa kopior i C ++: Gör alltid en djup kopia om konstruktören allokerar resurser.
-
Som standard gör C ++ så kallade "grunt" medlemskortskopior av objekt när de skickas till funktioner eller som resultat av en uppgift. Du måste ersätta de grundläggande grunda kopia operatörerna med deras exemplar av djup kopia för varje klass som allokerar resurser i konstruktören. Den vanligaste resursen som tilldelas är heapminne som returneras av den nya operatören.
Alltid inkludera en destructor för en klass som allokerar resurser.
-
Om du skapar en konstruktör som allokerar resurser måste du skapa en destructor som återställer dem. Inga undantag.
Angiv alltid destruktorns virtuella.
-
Ett vanligt nybörjarefel är att glömma att förklara din virtuella destructor. Programmet kommer att gå bra tills någon intet ont anande programmerare följer med och ärver från din klass. Programmet verkar fortfarande fungera, men eftersom destruktorn i basklassen kanske inte påberäknas ordentligt läcker minnet från ditt program som en sik tills det kraschar. Detta problem är svårt att hitta.
Ta alltid med en kopiekonstruktör för en klass som allokerar resurser.
-
Kopikonstruktören skapar en korrekt kopia av det aktuella objektet genom att allokera minnet av högen och kopiera innehållet i källobjektet.
Överrätta alltid uppdragsoperatören för en klass som allokerar resurser.
-
Programmerare bör avskräckas från överordnade operatörer, men uppdragsoperatören är ett undantag. Du bör åsidosätta uppdragsoperatören för vilken klass som allokerar resurser i konstruktören.
Överföringsoperatören bör göra tre saker:
Se till att vänster och höger handobjekt inte är samma objekt. Med andra ord, var noga med att applikationsprogrammeraren inte skrev något som (a = a). Om de är, gör ingenting.
-
Inkod samma kod som destructor på vänsterobjektet för att returnera sina resurser.
-
Insamla samma kod som en kopiekonstruktör för att göra en djup kopia av det högra objektet i det vänstra objektet.
-
Om du inte kan göra det, raderar du kopierarkonstruktören och uppdragsoperatören så att programmet inte kan göra kopior av ditt objekt.
-
-
Om du inte ens kan göra det eftersom din kompilator inte stöder funktionen C ++ 2011 delete constructor, skapa en tom kopiekonstruktör och uppdragsoperatör och förklara dem skyddade så att andra klasser inte kan använda dem.