↓
 
Visit the English version
Blogg Off

Blogg Off

Blandade tankar

  • Idéer
  • Matematik
  • Musik
  • Språkligt
  • Logik
  • Programmering
  • Om bloggaren

Kategoriarkiv: Programmering

Logiska operatorer i C++

Blogg Off Postat 10 mars, 2010 av Simon Jensen11 januari, 2015  

Det här inlägget handlar om de logiska operatorerna i C++ och om en speciell egenskap vid två av dessa operatorer – en egenskap som kan utnyttjas i vissa situationer.

Till vårt förfogande finns följande operatorer:
|| (eller)
&& (och)
! (icke)

Dessa tre operatorer har logiska uttryck som operander och deras resultat blir likaledes ett logiskt värde. Ett logiskt uttryck i C++ är ett uttryck som entydigt kan typomvandlas till datatypen bool. Exempelvis kan alla tal omvandlas till true eller false (noll blir false och alla andra värden blir true).

Om A och B är logiska uttryck i C++, så gäller som bekant följande:

A || B är true om A eller B (eller båda) är true, annars får A || B värdet false.
A && B är false om A eller B (eller båda) är false, annars får A && B värdet true.

Detta gör att följande situation uppstår:

Om vi ska beräkna A || B och vet att A blir true, behöver vi inte räkna ut B för att få svaret.
Om vi ska beräkna A && B och vet att A blir false, behöver vi inte räkna ut B för att få svaret.

I standarden för C++ har detta tagits i beaktande, och uttrycket B räknas aldrig ut i de situationer som nämns ovan. Detta ska vi utnyttja nedan.

I C++ kan man som bekant skapa egendefinierade typomvandlingsoperatorer för en klass, vilket exempelvis har gjorts för klassen istream (och därmed objektet cin).

Nästan alla som håller på att lära sig C++ kommer någon gång att skapa textbaserade applikationer (exempelvis Win32 Console) och utnyttja standard in- och utmatningsobjekten cin och cout. I annat fall kommer många att läsa från filer med objekt av klassen ifstream, och då gäller samma princip: Objektet cin kan tolkas logiskt med hjälp av en fördefinierad typomvandlingsoperator. Om ett internt feltillstånd uppstår vid inmatning, så tolkas cin som false. Om allting har gått bra vid inmatning tolkas cin som true.

Ett exempel på en misslyckad inmatning som leder till att cin tolkas som false kan vara att det läsas in en bokstav när inmatningsoperatorn förväntar ett heltal. Detta kan man vanligtvis behandla så här:

Variabeln tal nedan kan vara av typen int eller en annan typ som inmatningsoperatorn kan hantera:

cin>>tal;
while (!cin)
{
cin.clear();
cin.ignore(1024,’\n’);
cin>>tal;
}

Slingan ovan fortsätter tills ett giltigt värde har lästs in till variabeln tal (först då får !cin värdet false och while-snurran avslutas). Funktionen clear nollställer feltillståndet i objektet cin och funktionen ignore ser till att alla tecken som har hunnit läggas i kö (till inmatningsströmmen) ignoreras, ända fram till tecknet ’\n’ som är det radslutstecken som genereras av den ENTER-tangent som förväntas avsluta inmatningen av ett heltal. Funktionsanropet ovan ignorerar maximalt 1024 tecken, men det är knappast troligt att användaren har hunnit trycka på så många tangenter.

Låt oss nu roa oss med och-operatorn. Hela programfragmentet ovan får plats på en enda rad:

while (!(cin>>tal) && (cin.clear(),cin.ignore(1024,’\n’)));

Ovanstående while-sats utnyttjar följande fakta:
1) Inmatningsoperatorn >> ger som resultat en referens till objektet cin som i sin tur tolkas som false om inmatningen inte gick bra.
2) Den logiska operatorn && beräknar endast sin högra operand ifall den vänstra är true.
3) Kommaoperatorn beräknar sina operander i tur och ordning och lämnar högra som resultat.
4) Funktionen ignore returnerar själva objektet cin vilket i fallet ovan tolkas som true (efter clear-anropet).
5) Satsens sista semikolon innebär att while-snurrans kropp är en tom sats.

I dina Win32 Console-applikationer kan du alltså med fördel ersätta alla inmatningssatser av typen

cin>>tal;

med satsen

while(!(cin>>tal)&&(cin.clear(),cin.ignore(1024,’\n’)));

för att förhindra att programmet hänger sig när användaren skriver in en bokstav och programmet förväntar sig ett tal. Eventuellt kan du lägga till ett litet felmeddelande och satsen är komplett:

while(!(cin>>tal)&&(cin.clear(),cin.ignore(1024,’\n’)) cout<<”Felaktigt tal!”<<endl;

Detta sätt att utnyttja de logiska operatorerna för att förenkla programstrukturen kan tillämpas i många situationer. Enbart din fantasi sätter gränserna.

Publicerat i Programmering | Lämna ett svar

Pekare i C++ (en grafisk introduktion)

Blogg Off Postat 10 februari, 2009 av Simon Jensen18 januari, 2015  

För en del år sedan hade jag hand om fyra programmeringskurser vid Göteborgs universitet. Nedanstående artikel är hämtad ur min disposition för en introduktionsföreläsning om pekare.

Du är välkommen att kopiera och använda texten såframt källan anges. Materialet kan användas som kompendium för både högskola och gymnasium.

Hursomhelst, här kommer texten:

Definition av variabler
När vi definierar en variabel av en viss typ (exempelvis double) reserveras ett litet utrymme i datorns minne. Detta utrymme är exakt så stort att ett värde av denna typ (alltså double) får plats. Vi kan föreställa oss att vi skapar en liten korg som sedan kan innehålla ett värde. Korgen har ett namn (variabelns namn) och en storlek (variabelns datatyp).

Vi kan rita detta:
Definition av variabel
Om vi nu lägger in ett tal i variabeln, så kan detta också ritas:
Tilldelning av värdet 17.3 till en svariabel
Pekare
En pekare används för att beskriva den plats vi har reserverat i datorns minne när vi har definierat någonting (till exempel en variabel, ett objekt eller en funktion).

En sådan plats kännetecknas av:
• att den finns på ett ställe (detta ställe kallas vanligtvis adressen för variabeln)
• att den har en viss storlek (som kan mätas i bytes)
• att den har en viss typ (vilket innebär att enbart värden av denna typ kan lagras på platsen)

Kort sagt: En pekare är en liten mängd information som beskriver en plats i datorns minne genom att lagra platsens adress och platsens datatyp (vilken i sin tur lagrar platsens storlek).

Egentligen är ordet pekare en gemensam benämning på två olika begrepp, nämligen pekarvärde och pekarvariabel. Det blir enklast om vi härefter skiljer på dessa två begrepp, precis som när vi skiljer på exempelvis heltalsvärde och heltalsvariabel.

Pekarvärden
Ett pekarvärde är den adress som beskriver för datorn på vilken plats en variabel, ett objekt eller en funktion finns. Varje ny variabel som definieras får en helt egen adress eftersom varje variabel placeras på en ny plats. Två olika variabler kan aldrig ha samma adress.

Varje adress är ett heltal som skrivs på hexadecimal form, exempelvis 55A2116B. Vi kan aldrig veta i förväg vilken adress som tilldelas till en variabel. Detta avgörs internt av programmet (i en sorts samarbete med operativsystemet) när programmet körs.

Men vi kan rita ett pekarvärde som en pil. Eftersom ett pekarvärde är adressen till en variabel, så låter vi pilen peka på denna variabel. Så här kan det se ut:
Adressoperatorn &
Adressoperatorn &
Om vi har en variabel så kan vi få fram ett pekarvärde för denna variabel genom att använda adressoperatorn. Den skrivs med &-tecknet.

Uttrycket &tal ger som resultat det pekarvärde som pekar på variabeln tal, d.v.s. adressen till tal.

Pekarvariabler
Precis som med alla andra värden (heltalsvärden, flyttalsvärden etc.) så går det att skapa en variabel som kan innehålla pekarvärden. En sådan variabel kallas en pekarvariabel.

För att definiera en pekarvariabel måste man använda tecknet * vid definitionen:
Definition av en vanlig variabel samt en pekarvariabel
Om vi nu tilldelar pekarvärdet &tal till pekarvariabeln pek, så ser det ut så här:
Tilldelning av pekarvärde till en pekarvariabel
Avrefereringsoperatorn *
Om vi har ett pekarvärde så kan vi få fram det som finns på denna adress genom att använda *-operatorn (även kallad stjärnoperatorn ibland). Den skrivs med tecknet * (förväxla inte denna operator med samma symbol som används vid multiplikation av tal eller, som ovan, vid definiering av pekarvariabler).

Uttrycket *pek ger som resultat det som finns på den adress som lagrats i pekarvariabeln pek.

Man kan använda stjärnoperatorn både vid tilldelningar (jmf. exemplet nedan) eller som ett ingående uttryck i andra typer av satser.
Användning av avrefereringsoperator
Tom pekare
Säg att vi har skapat en pekarvariabel för senare bruk, men ännu inte satt denna pekare att peka på någonting. Då finns det inget giltigt pekarvärde i pekarvariabeln, vilket kan leda till problem om man försöker använda det som pekaren pekar på – eftersom vi inte vet vart den pekar! För att undvika detta problem bör man använda en så kallad tom pekare (även kallad nollpekare). En tom pekare skapas genom att tilldela pekarvariabeln heltalsvärdet noll.

Man kan rita ett kryss i bilden av pekarvariabeln för att visa att den inte innehåller någon pil.
Tom pekare

Ett exempel med pekare
Exempel med 3 pekarvariabler

Fält och pekare
I mina texter använder jag ofta benämningen fält för det begrepp som på engelska heter array, eftersom jag tycker, att har man nu översatt större delen av de engelska programmeringsbegreppen till svenska (t.ex. pointer=pekare, value=värde och integer=heltal), så bör man vara konsekvent och hitta lämpliga översättningar på samtliga begrepp. I en del programmeringsböcker föreslås den svenska termen vektor för det engelska array. Men detta är tanklöst eftersom den termen redan är upptagen. Dels används matematiska vektorer flitigt vid avancerade beräkningar i datorprogram och dels finns det en standardmall i C++ med namnet vector.

Hursomhelst, följande gäller: namnet på ett fält är i själva verket ett konstant pekarvärde som pekar på första elementet i fältet. Fältnamnet kan aldrig sättas att peka på någonting annat än första elementet i fältet. När man har definierat ett fält, så finns det alltid detta konstanta pekarvärde av samma typ som fältet.
Exempel med pekare och en array
Med indexoperatorn [ ] kan man få fram ett visst element i ett fält. Men detta skulle man lika gärna kunna göra med hjälp av stjärnoperatorn och additionsoperatorn. Uttrycket hf[3] har samma värde som uttrycket *(hf+3) och är alltså det som finns 3 steg framåt från första elementet i fältet. Storleken på varje steg beror på vilken typ pekarvärdet har. Exempelvis har en int storleken 4 bytes.

Strängar och pekare
Den typen av strängar som vi skapar med hjälp av ett fält av tecken, d.v.s. ett fält vars element har typen char, hänger nära ihop med pekare. Namnet på ett fält är alltid ett konstant pekarvärde som pekar på första elementet i fältet. Detta gäller även för strängar.
Namnet på ett fält är alltid ett konstant pekarvärde som pekar på första elementet i fältet
Lägg märke till att strängens sista element är talet 0. När detta tal tolkas som ett tecken kallas det ett nolltecken och markerar strängens slut. Förväxla inte talet 0 med tecknet ’0’ som har värdet 48.

Det går som bekant även att i sin kod (C eller C++) skriva in teckenfältsliteraler (konstanta teckenfält, d.v.s. strängar som inte ligger i ett definierat fält). En teckenfältsliteral är alltid ett pekarvärde som pekar på sitt eget första element. Detta kan också ritas:
En pekarvariabel pekar på en teckenfältsliteral
Dynamisk minnesallokering
Med new-operatorn (och new[ ]-operatorn) kan man direkt allokera minne (d.v.s. reservera minne) under programkörningen. Operatorn new gör två saker:
1) Den reserverar ett minnesutrymme av samma typ som den man anger när operatorn används.
2) Den lämnar ett pekarvärde som resultat. Pekarvärdet pekar på det utrymme som har reserverats.
En pekarvariabel som pekar på  allokerat utrymme
Här har vi en situation där det reserverade minne inte har något eget namn, varken direkt (t.ex. ett variabelnamn) eller indirekt (t.ex. namnet på ett statiskt definierat fält). Det enda sättet för programmeraren att komma åt det reserverade utrymmet är via det pekarvärdet som lämnades som resultat av new-operatorn då denne användes. Därför är det viktigt att inte ”slarva bort” detta pekarvärde utan genast spara det i en lämplig pekarvariabel, precis som i exemplet ovan där pekarvärdet sparas i pekarvariabeln pp.

Pekararitmetik
Aritmetik betyder räknelära eller en mängd regler för hur man får räkna. Helt beroende på vilka enheter beräkningarna innehåller (exempelvis reella tal, hela tal, äpplen, pekarvärden i C++ eller tidpunkter) så används olika aritmetiker. För alla våra vardagliga siffror används den aritmetik som vi lär oss redan i grundskolan, nämligen den som innehåller de fyra räknesätten: addition, subtraktion, multiplikation och division. Men det är långt ifrån alla aritmetiker som har fyra räknesätt (i C++ talar man om s.k. operatorer istället för räknesätt).

För pekarvärden finns det två grundläggande aritmetiska operationer vi kan utföra: 1) Lägga till ett heltal och 2) dra ifrån ett heltal. Om vi adderar ett pekarvärde, säg p, med ett heltal, säg h, så får vi som resultat ett nytt pekarvärde som pekar h steg framåt, räknat från p. Det samma gäller vid subtraktion.

Om vi bara vill ändra pekaren till att peka ett steg fram eller tillbaka i minnet, så används med fördel operatorerna ++ och –– (öknings- och minskningsoperatorn).

Ett exempel med pekaraddition
Ett pekarvärde plus ett heltal ger ett nytt pekarvärde
Mer om indexeringsoperatorn
Indexeringsoperatorn [ ] skulle man i princip kunna ta bort ur språket C++, eftersom allt den gör är att sätta ihop en pekaraddition och en *-operation. Men det underlättar onekligen läsningen av vår källkod om vi använder indexeringsoperatorn när vi arbetar med fält.

Hursomhelst, ISO-standarden för C++ säger att:
a[b] == *(a+b), där a är ett pekarvärde och b är ett heltal.

En lite bisarr konsekvens av detta blir att eftersom addition är kommutativ (detta gäller även i pekararitmetiken), så får vi följande:

a[b] == *(a+b) == *(b+a) == b[a]

Exempelvis kan vi istället för text[4] skriva 4[text] om vi vill ha tag i element nummer 4 i ett fält som heter text. Eller ännu värre:

Uttrycket ”Hejsan”[3] har som resultat tecknet ’s’ eftersom indexeringsoperatorn tar fram element nummer 3 (alltså det fjärde elementet) i teckenfältsliteralen (”Hejsan” är ju ett pekarvärde). Men med ovanstående resonemang kan vi lika gärna skriva 3[”Hejsan”] – och visst, det går bra!

Publicerat i Programmering | Lämna ett svar

En liten uppgift i C++

Blogg Off Postat 9 februari, 2009 av Simon Jensen18 januari, 2015  

”Att lära genom att lära ut”
(Jean-Pol Martin)

Innan jag började undervisa i C++ var jag övertygad om att jag hade fullständigt koll på alla teoretiska aspekter av detta omfattande programmeringsspråk.

Men oförutsedda frågor från mina skarpa elever fick mig snart på andra tankar.

Idag, hundratals föreläsningar och lektioner senare, vill jag påstå att jag har koll. Tack vare att jag fick lära mig genom att lära ut!

Därför börjar jag denna kategori med en liten teoretisk uppgift till alla professionella programmerare:

Betrakta följande uttryck i C++:

(1==0)[”0==1”]

Vilket värde har detta uttryck?
Vilken datatyp har uttrycket?
Varför?

Publicerat i Programmering | Lämna ett svar
©2021 - Blogg Off
↑