Göm menyn

TDDC68 Imperativ programmering i Ada

Lathund för Ada till C++


I denna kurs ingår en liten introduktion av C-delen i C++. Det är alltså inte meningen att ni skall använda saker som har med klasser, arv och objektorientering i denna kurs. Det kommer en fortsättning på C++ i Java-kursen till hösten.

Det är två laborationer i C++-delen och dessa berör i princip samma saker som vi berört i Ada sen tidigare. Lab 1 är en "kopia" av lab 3 i Ada-delen och lab 2 är en kombinerad fil- och pekarlaboration.

OBS! Det är inte så att vi tar upp programmeringstänkandet en gång till nu utan detta ses som en del i kursen där ni tar reda på den information som behövs. De FÖ och eventuellt LE som finns är en snabbgenomgång av likheter skillnader som finns mellan Ada och C++.

Kompilering

När det gäller C++-program finns det ett flertal gångbara filändelser på de program man skriver (t.ex. ".cxx", ".c++", ".C", ".cp"), men ".cc", ".cpp" verkar vara de absolut vanligaste. Det finns flera varianter beroende på att det finns flera olika kompilatorer.

Vad det gäller kompilatorer finns det här på skolan "g++" (en variant som finns med i GCC) som ser till att C++ länkbibliotek kommer med (vilket det inte gör om man använder kommandot "gcc"). "g++" anropar "gcc". Sun:s variant av kompilator heter "CC". I denna kurs använder vi dock "g++".

Antag att vi har ett program som heter "prog.cc". Vi skall kompilera detta till en körbar fil. Detta gör vi genom att skriva:

        g++ prog.cc

Detta leder till en körbar fil som heter "a.out". För att ange vilket filnamn den körbara filen skall få får man lägga till en flagga till "g++".

        g++ -o prog prog.cc

Flaggan "-o" samt det efterföljande filnamnet ger oss den körbara filen "prog".

Vill man få en riktigt bra kompilering bör man lagga till följande flaggor:

        -std=c++98 -pedantic -Wall -Wextra 

Fler flaggor som kan vara bra finns att finna via de länkar vi har på kurshemsidan. Dessa leder till den avacerade kursen i C++ där det också finns bokförslag m.m. Ni kan om ni vill använda den e-bok som finns där för att finna det vi inte tar upp som ni kanske måste ha för att göra laborationerna.

Något som skiljer sig från Ada:s kompilator "gnatmake" är att "g++" inte själv tar reda på vilka delar som hör ihop och måste kompileras. Detta gör att du som programmerare har ett större ansvar att se till att kompilera alla delar som behövs. Dessutom måste du för att få den slutgiltiga körbara filen kompilera ihop alla de delprogram som behövs.

Antag att vi har två delar i programmet. Ett huvudprogram som ligger i filen "main.cc" och ett antal underprogram som ligger i filen "subprograms.cc". Här följer en kompileringssekvens man kan göra för att få ihop allt till ett körbart program (OBS! här finns inte de extra flaggorna med, men de tar du givetvis med också).

        g++ -c subprograms.cc
        g++ -c main.cc
        g++ -o main main.o subprograms.o

De första två kompileringsstegen (med "-c") anger att vi kompilerar de två delarna var för sig och skapar ".o"-filer (objektfiler). Dessa filer är alltså inte ihoplänkade på något sätt utan är endast halvvägs kompilerade. De innehåller information om var och hur de olika rutinerna skall sammanlänkas senare.

Det sista kompileringssteget utför själva länkningen av de ".o"-filer som anges och "-o main" anger att vi får den körbara filen "main" senare.

En variant på ovanstående är att man skriver:

        g++ -o main main.cc subprograms.cc

Här kommer nu båda filerna att kopileras och sen länkas ihop. En nackdel är att man alltid kommer att kompilera om alla delarna och det är ju onödigt.

Man kan förutom ".cc"-filen ha en tillhörande ".h"-fil (eller ".hh"-fil). Denna kallas för en "header-fil" eller "inkluderingsfil" och innehåller deklarationer av de delar man vill att andra C++-filer skall ha tillgång till. Man kan jämföra detta med ".ads"-filen för ett paket i Ada.

Man kan också tänka sig att enbart ha en ".h"-fil (utan tillhörande ".cc"-fil) där man endast har definitioner av t.ex. typer, konstanter o.dyl.

Makefile

Om man vill underlätta för sig själv och slippa hålla reda på alla delar som skall kompileras och vad som beror av vartannat kan man skapa sig en så kallad "Makefile" (detta är alltså namnet på filen).

I denna fil lägger man in alla kompileringsdirektiv och sen startar man hela kompleringen genom att skriva endast "make" (i terminalen).

Ett exempel på en "Makefile" för exemplet med "main.cc" och "subprograms.cc" skulle i sin enklaste form kunna se ut så här:

        main:		main.o subprograms.o
        	g++ -o main main.o subprograms.o
    
        main.o:		main.cc main.hh \
    			subprograms.hh
        	g++ -c main.cc
    
        subprograms.o:	subprograms.cc subprograms.hh
        	g++ -c subprograms.cc

Generellt finns det några olika delar i en "Makefile". Det finns "mål" (main, main.o, subprograms.o) och dessa skrivs först på raden följt av ett kolon (:). Efter kolonet skall det vara ett "TAB" (kan vara blanktecken också, men varför inte vara lite extra tydlig) och sen de filer som målet beror av (för "main" är dessa "main.o" och "subprograms.o").

På raden/raderna efter målbeskrivningen följer instruktionerna som skall utföras om målets beroenden är uppdaterade sen senast målet var skapat. I vårt fall är detta själva kompileringsraderna. Först på dessa instruktionsrader måste det finnas ett "TAB" (här MÅSTE det vara "TAB").

Vill man slippa skriva extremt långa rader kan man låta en rad forsätta på nästa rad genom att sätta in markeringen "\" sist på raden. Exempel finns i beroendena för "main.o".

När man kör kommandot "make" kommer första målet i filen att försöka skapas. Detta mål beror av filer som i sin tur finns beskrivna som mål senare i "Makefile". Då måste först dessa mål skapas. Man kan se det som lite "rekursivt". När alla delmål är skapade kontrollerar "make" om målet (i detta fall "main") är nyare än alla delmål. Om så är fallet händer inget. Om målet är äldre kommer instruktionerna att utföras.

Om man t.ex. endast vill åstadkomma delmålet "subprograms.o" kan man köra kommandot "make" enligt följande:

        make subprograms.o

Om ett mål saknar beroenden kommer det alltid att utföras. Ett exempel på trevligt mål att ha i en "Makefile" är målet "clean" som skulle kunna rensa undan alla onödiga filer man har skapat vi t.ex. tidigare kompileringar. Här följer ett par målbeskrivningar som skulle kunna vara ok (vi antar att dessa ligger efter alla andra i din "Makefile"):

        clean:
        	\rm -f *~ *.o

        zap:		clean
        	\rm -f main

För att rensa bort alla backupfiler och ".o"-filer kan man alltså skriva:

        make clean

För att utföra det som "clean" gör samt ta bort den körbara filen "main" kan man skriva:

        make zap

OBS! "Makefile" hör inte endast ihop med kompilering utan är ett generellt verktyg som kan användas till mycket.

Vi tar inte upp allt som har med "make" och "Makefile" att göra här utan hänvisar till information som går att få fram via dator och internet.

Ada kontra C++ (första laborationen)

Ett enkelt C++-program kan se ut som följer:

        #include <iostream>		// Standardbibliotek för C++ strömmar
        #include <cmath>		// Standardbibliotek från C för matte.
        #include <cstring>		// Standardbibliotek från C för strängar.
        #include "subprograms.h"	// Egen fil som inkluderas.
    
        using namespace std;		// Ungefär som "use" i Ada.
    
        int main()
        {
          // Declarations ...
          int    i1, i2 = 0;  // i2 = 2 och i1 är oinitierad.
          float  f;
          double d;
          char   c;
          char   s[11];
          bool   b;
    
          // Statements ...
          i = 3;
          f = 3.14;
          f = i;
          c = 'K';
          c = i;
          i = f;
          c = f;
          b = true;
          b = 1;
          b = c;
          if ( i )
          {
            cout << "Funkar det med" << endl;
          }
    
          s = "GÅR INTE!!";		// Kompileringsvarning ...
          strcpy(s, "GÅR BRA!!!");
    
          s[0] = 'K';
          s[1] = '\0';			// Sätt in ett avslutande "null-tecken".
    
          cout << s;			// Skriver ut "K".
          cin >> i;
    
          d = floor(d);
    
          // Main program shall return 0 when no error ...
          return 0;	      
        }

Här följer en mycket kortfattad översättningstabell över hur saker i Ada kan skrivas i C++ (och vissa delar i C då ni kommer att använda det också i senare kurser). Det finns givetvis saker som går att göra på annat sätt och det finns saker som inte är perfekta översättningar, men det är ändå en start. Se mer om detaljer i böckerna som refereras via de länkar som finns.

        -- Kommentar			// Kommentar
					/* Kommentar som
					   avslutas med */

        with ...;			#include ...

        use ...;			using namespace ...;

        I : Integer;			int i;

        begin				{
        end				}

        I := ...;			i = ...;

        I := I + 1;			i = i + 1;
					i += 1;
					i++;
					++i;	// Denna är att föredra.

        if I = 2 then			if ( i == 2 )
					  {
					  }
        elsif ... then			else if ( ... )
					  {
					  }
        else				else
					  {
        end if;				  }

        case ... is			switch ( ... )
					{
          when ...    => ...;		  case ... : ...; break;
          when others => ...;		  default  : ...; break;
        end case;			}
					// Om ej "break" går
					// man vidare till
					// nästa gren.

        while ... loop			while ( ... )
					  {
        end loop;			  }

        for I in 1 .. 10 loop		for (int i = 0 ; i < 10 ; ++i)
					  {
        end loop;			  }

        loop				do
					  {
					  }
        end loop;			while (true);
					// "true" kan ersättas
					// med villkor så att
					// man kan upprepa "tills"
					// något är sant.

					// Vill man ha en oändlig loop
					// kan man förstås också
					// använda något av följande:
					for ( ; ; )
					  {
					  }

					while ( true )
					  {
					  }

        exit;				break;

        function X(I : in Integer)	float x(int i)
	     return Float is		{
          T : Character;		  char t;
        begin
          return ...;			  return ...;
        end X;				}

        procedure X(I : out Xx) is	void x(Xx &i)
        begin				{
          I := ...;			  i = ...;
        end X;				}


					Slumptalshantering:

					#include <cstdlib>
					i = rand();

					// Returnerar heltal i
					// intervallet [0, RAND_MAX].
					// Slumpmässig start.

					srand(123);

					// Sätter slumptalsfrö.
					// Ger möjlighet till samma
					// sekvens. Dock ej specificerat
					// vilken algoritm => olika för
					// olika system.
					// 123 motsvarar ett slumptalsfrö
					// som skall vara en "unsigned int".

					// Man kan också använda sig
					// av klockan i datorn för att
					// sätta ett "slumpmässigt
					// frö". Detta görs på
					// följande sätt:

					#include <time.h>
					srand(time(NULL));

        Put(...);			cout << ...
        Put(...);			     << ...
        New_Line;			     << endl;

        Get(...);			cin >> ...;

        Logiska operatorer:
          and				  saknar motsvarighet
          or				  saknar motsvarighet
          and then			  &&    (alt. & för "bitwise")
          or else			  ||	(alt. | för "bitwise")
          not				  !
          /=				  !=

        type Post is			typedef struct post
          record			{
            Data : Integer;		  int data;
          end record;			} post;

	  				// Detta var C-varianten och
					// här följer C++-varianten.

					struct post
					{
					  int data;
					};

        P : Post;			post p;

        P.Data := 1;			p.data = 1;

					// Fält (i språket C)

        type Arr is			typedef char arr[5];
          array (1 .. 5) of Char;

        A : Arr;			arr a;

        A(1) := 1;			a[0] = 1;

        type Arr2 is			typedef arr arr2[3];
          array (1 .. 3) of Arr;

        A2 : Arr2;			arr2 a2;

        A2(1)(3) := 1;			a2[0][2] = 1;

					// Fält (i språket C++)

					// Begreppet "vector" i C++
					// motsvarar dynamiska fält
					// (vilket vi inte kommer att
					// använda) som vi kan skapa
					// statiska fält utifrån.

					vector<int> v(5);

					// Inititerar en vektor till
					// att innehålla 5 st "int".

					// "v" initieras med nollor!
					// Man skulle kunna skriva
					// motsvarande initiering
					// genom att lägga till talet
					// som skall läggas i vektorns
					// positioner enligt följande:

					vector<int> v(5, 0);

					// Indexering enligt följande:

					v[0] = 7;

        I := A'Length;			i = v.size();

					// I C++09 (nästa standard)
					// kommer "array", som är en
					// ny typ av container med fix
					// storlek. Denna kommer att
					// mer direkt motsvara
					// statiska fält som vi har i
					// Ada.

					array<int, 5> a;

					// Detta borde i princip bli
					// nådastöten för C-fält i
					// C++.

Farliga saker i C++

Saker som man kan råka illa ut på i C++ som "nybörjare". Dessutom lite kuriosa som ställer till det lite extra om man har programmerat i Ada (om man inte tänker sig för).

I C++ har man en annan syn på hur man tar hand om värden från funktioner. Man kan ignorera returvärdet genom att bara låta det vara. Exempel:

        y = sin(x);		// Vanlig sats som lagrar värdet i "y".
        sin(x);			// Sats som igorerar returvärdet.

Generellt är nedanstående problem baserade på att man i C/C++ ser tilldelning som ett uttryck till skillnad från i Ada där man ser tilldelning som en sats.

I ett villkor är det tillåtet att utföra tilldelning:

        if ( x = 0 )		// "if"-satsen kommer aldrig utföras.

Om man programmerat Ada och vill göra en "skiljt ifrån":

        if ( x /= 0 )		// C++:s "x /= 0;" är samma som
				// Ada:s "X := X / 0;"

Om man råkar skriva lite fel (borde kanske vara "!="):

        while ( x =! 0 )	 // X = !0 = 1 = true

Ada kontra C++ (andra laborationen)

I andra laborationen skall ni hantera inläsning från fil och hantering av listor (en cirkulär sådan).

Vi väljer i detta fall att arbeta med pekarlistor och det är vanligast att man menar dessa om man bara säger lista. Här följer en figur som visar hur en enkellänkad lista med två data (heltal) kan se ut.

                   +-----------------+    +-----------------+
                   |      +--------+ |    |      +--------+ |
                   | Data |  ????  | |    | Data |  ????  | |
             +-+   |      +--------+ |    |      +--------+ |
        List |-+-->|                 |  ->|                 |
             +-+   |      +-+        | /  |      +-+        |
                   | Next |-+--------+/   | Next |/|        |
                   |      +-+        |    |      +-+        |
                   +-----------------+    +-----------------+

I Ada har ni skapat precis denna listtyp så det vet ni hur det ser ut. Hur skulle detta se ut i C++?

        struct ListNode;

        typedef ListNode* ListType;

        struct ListNode
        {
          int      data;
          ListType next;
        };


        ListType list;		// Här skapas själva listvariabeln.

        list = 0;		// C++-varianten av Ada:s "List := null;"
        list = NULL;		// C-varianten av samma sak.

				// I C++09 kommer "nullptr" att införas
				// som "NULL".

	ListType list = 0;	// Kortvariant av ovanstående.
        
        list = new ListNode;
        list->data = 1;
        list->next = 0;

        delete list;		// "delete" är en operator ...
        list = 0;		// "Måste" göras om man inte vill råka
    				// illa ut. Skiljer sig från Ada!

Det andra som behövdes var filhantering. Detta finns i C++ gällande filer:

        #include <fstream>

        using namespace std;

        ifstream  inFile;		// Deklaration av filvariabel
    					// som man kan öppna för läsning.

        ofstream  outFile;		// Deklaration av filvariabel
    					// som man kan öppna för skrivning.

        inFile.open("A.TXT");		// Öppna textfilen A.TXT.


        char c;

        inFile >> c;			// Läs ett tecken från filen.
    					// OBS! Formatterad inmatning!
    					// Blanksteg ignoreras!

        char s[11];			// C-varianten.

        inFile.getline(s, 11);		// Läser tecken till fältet.
    					// Ungefär som Ada:s Get_Line.
    					// returnerar värde som säger
    					// om det gick bra eller ej
    					// med läsning (en referens
    					// till filen).

        if ( inFile.getline(s, 11) )
          {
            // Det gick bra ...
          }
        else
          {
            // Det gick inte bra (kan ha varit slut på filen) ...
          }

        inFile.close();

        inFile.eof()		// Returnerar true om man tidigare
    				// försökt läsa förbi "end_of_file".
    				// Lite skillnad från Ada:s variant
    				// som returnerar True om "nästa
    				// tecken" motsvarar "end_of_file".

        while ( infile >> c )	// Avslutas när man inte kan läsa mer.

Givetvis finns det mycket mer, men vi har här tagit upp det som behövs för att klara av laborationen. Jag hoppas nu att ni har på fötterna så att ni kan klara av laborationerna.

Sidansvarig: Torbjörn Jonsson
Senast uppdaterad: 2012-01-09