CD5250 - Objektorienterad programutveckling med C++, 5 poäng, period 4, 2002

C++ Introduktion

Introduktion till C++ för C programmerare

Detta dokument är tänkt som en kort förklaring till varför C++ har uppstått och varför det fungerar som det gör. Givetvis är detta inte en fullständig beskrivning av C++, men det skall förhoppningsvis täcka de mest grundläggande idéerna.

Dokumentet är upplagd så att ett program skrivet som ett typisk C-program utifrån en enkel kravspecifikation transformeras i några diskreta steg till ett motsvarande C++program. Detta C++program förbättras i ett sista steg med några finjusteringar till att vara ett typiskt C++program.

Kravspecifikation

Gör ett program som simulerar två golfspelare som slår golfbollar mot ett hål. Programmet skall låta bägge spelarna slå tio bollar var, för att sedan skriva ut vem som kom närmast tillsammans med hur nära han/hon var. Var och en av spelarna skall ha ett "handikapp" som beskriver hur stor spridning från det tänkta målet de har.

C-kod

Följande implementation av problemet är skivet av en tänkt C-hacker som inte har läst obektorienterad programmering. Programmet fungerar bra, så implementatören är nöjd, så även kunden.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>

float flagXPos = 5;
float flagYPos = 12;

void hitBall(float spreadX, float spreadY, float *xPos, float *yPos)
{
	*xPos = flagXPos + spreadX * rand() / RAND_MAX - spreadX / 2;
	*yPos = flagYPos + spreadY * rand() / RAND_MAX - spreadY / 2;
}

int main(void)
{
	float ballXPos, ballYPos;
	float currentDist;
	float minDist = FLT_MAX;
	float person1spreadX = 20;
	float person1spreadY = 20;
	float person2spreadX = 10;
	float person2spreadY = 10;
	char * names[] = {"Evert", "Hans"};
	int leader = 0;
	int i;

	for(i=0; i<10; i++)
	{
		// Person 1 hit
		hitBall(person1spreadX, person1spreadY, &ballXPos, &ballYPos);

		currentDist = (float)sqrt((ballXPos-flagXPos)*(ballXPos-flagXPos) +
			(ballYPos-flagYPos)*(ballYPos-flagYPos));

		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = 1;
		}

		// Person 2 hit
		hitBall(person2spreadX, person2spreadY, &ballXPos, &ballYPos);

		currentDist = (float)sqrt((ballXPos-flagXPos)*(ballXPos-flagXPos) +
			(ballYPos-flagYPos)*(ballYPos-flagYPos));

		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = 2;
		}
	}

	printf("Vinnare: %s\n", names[leader-1]);
	printf("Minsta avstånd från flaggan: %f meter\n", minDist);

	return 0;
}
Exempel 1.Ursprungligt C-program.

Detta program kanske uppfyller kravspecifikationen, men det är svårt att snabbt sätta sig in i det och det är dessutom inte så flexibelt. Om man skulle vilja ändra beteende på nåt sätt så måste massor av kod skrivas om. Det finns några enkla steg vi kan ta för att förändra koden så att den blir mer rustad för förändringar, samt att göra den mer läsbar.

Steg 1

Det första vi kan observera är att det finns ett antal olika återkommande par av heltal (exempelvis flagXPos och flagYPos). Dessa kan grupperas i en struct. Likaså ser vi att hanteringen av personrelaterad information ligger utspridd bland massor av variabler. Dessa placerar vi också i en struct.

Följande kod har vi anpassat så att programmet använder strukturerna Point och Person.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>

struct Point
{
	float x;
	float y;
};

struct Person
{
	struct Point spread;
	char name[40];
};

struct Point flag = {5, 12};

void hitBall(struct Person *person, struct Point *pos)
{
	pos->x = flag.x + person->spread.x * rand() / RAND_MAX - person->spread.x / 2;
	pos->y = flag.y + person->spread.y * rand() / RAND_MAX - person->spread.y / 2;
}

int main(void)
{
	struct Point ball;
	struct Person player[] =
	{
		{{20, 20}, "Evert"},
		{{10, 10}, "Hasse"}
	};

	float currentDist;
	float minDist = FLT_MAX;
	int leader = 0;
	int i;

	for(i=0; i<10; i++)
	{
		// Person 1 hit
		hitBall(&player[0], &ball);

		currentDist = (float)sqrt((ball.x-flag.x)*(ball.x-flag.x) +
			(ball.y-flag.y)*(ball.y-flag.y));
		
		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = 1;
		}

		// Person 2 hit
		hitBall(&player[1], &ball);

		currentDist = (float)sqrt((ball.x-flag.x)*(ball.x-flag.x) +
			(ball.y-flag.y)*(ball.y-flag.y));
		
		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = 2;
		}
	}

	printf("Vinnare: person %s\n", player[leader-1].name);
	printf("Minsta avstånd från flaggan: %f meter\n", minDist);

	return 0;
}
Exempel 2.Steg 1 i transformationen.

Som programmerare med kunskap i objektorienterad programmering så kan vi se att detta inte är det enda vi kan göra för att förbättra programmet. I nästa steg fortsätter vi omvandlingen av programmet.

Steg 2

Vi observerar att en del funktioner i programmet kan brytas ut. Längden beräknas t.ex. mellan två punkter på ett par ställen i koden. Det är för det första jobbigt att skriva om denna kod varje gång det skall användas. Dessutom är det felbenäget att klippa och klistra. Vi gör därför en funktion distance som beräknar längden mellan två "Point" strukturer. Vi har dessutom lärt oss att man skall undvika globala variabler. Vi bestämmer oss därför att modifiera funktionen hitBall så att den får flaggans koordinater som en Point-parameter istället.

Vad vi dessutom kan iaktta är att dessa två funktioner vi har gjort i ordning har logiska kopplingar till de två strukturerna vi skapade i förra steget (Point och Person). Funktionen distance hör ihop med Point och hitBall hör ihop med Person. Dessa logiska kopplingar kommer i och med att distance arbetar med Points och hitBall arbetar primärt med Person-data (hitBall bör ju inte heller fungera utan en Person).

Följande kod har funktionerna grupperade tillsammans med respektive struct. Dessutom har namnet fått ett prefix som är samma som struct-namnet för att man lätt skall kunna känna igen funktionen i programkoden.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>

/*------------------------------------------------*/

/* struct Point beskriver en tvådimensionell punkt */
struct Point
{
	float x;
	float y;
};

/* Point_Distance returnerar avståndet mellan två punkter */
float Point_Distance(struct Point *source, struct Point *dest)
{
	float xdiff = source->x - dest->x;
	float ydiff = source->y - dest->y;
	return (float)sqrt(xdiff*xdiff + ydiff*ydiff);
}

/*------------------------------------------------*/

struct Person
{
	struct Point spread;
	char name[40];
};

/*
 * Person_HitBall slumpar fram en position för bollen
 * med ledning av personens träffsäkerhet
 */
void Person_HitBall(struct Person *person, struct Point *flag, struct Point *pos)
{
	pos->x = flag->x + person->spread.x * rand() / RAND_MAX - person->spread.x / 2;
	pos->y = flag->y + person->spread.y * rand() / RAND_MAX - person->spread.y / 2;
}

/*------------------------------------------------*/

int main(void)
{
	struct Point flag = {5, 12};
	struct Point ball;
	struct Person player[] =
	{
		{{20, 20}, "Evert"},
		{{10, 10}, "Hasse"}
	};

	float currentDist;
	float minDist = FLT_MAX;
	int leader = 0;
	int i;

	for(i=0; i<10; i++)
	{
		// Person 1 hit
		Person_HitBall(&player[0], &flag, &ball);
		currentDist = Point_Distance(&ball, &flag);
		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = 1;
		}

		// Person 2 hit
		Person_HitBall(&player[1], &flag, &ball);
		currentDist = Point_Distance(&ball, &flag);
		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = 2;
		}
	}

	printf("Vinnare: person %s\n", player[leader-1].name);
	printf("Minsta avstånd från flaggan: %f meter\n", minDist);

	return 0;
}
Exempel 3.Steg 2 i transformationen.

Programmet har nu fått några fler rader källkod, men den ökningen består mest av kommentarer och luftigare och inte lika otydlig kompakt kod. Programmet har dessutom blivit mer förberett på framtida ändringar. Inga globala variabler m.m. (Ett tankeexperiment man kan göra är att tänka vad man skulle behöva göra för förändringar om man skulle vilja ha flera flaggor som mål. Jämför detta sedan med hur det skulle göras i det första C-programexemplet. Ledtråd: Det är mycket enklare nu!)

Steg 3

Program skrivna i C har en tendens att likna det första exemplet p.g.a. att det är enkelt och snabbt att slänga ihop fungerande program i C. När projekt skrivna i C börjar nå en viss storlek ställs mycket mer krav på att programmet skall vara läsligt och flexibelt. Koden i dessa projekt brukar då få en större likhet med det senaste exemplet. Detta har man tagit fasta vid när man har gjort C++. Programspråket har fått funktioner för att hjälpa programmeraren att skriva program som liknar det senaste exemplet.

Följande exempel är en direkt översättning av programmet till C++kod.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>

//------------------------------------------------

class Point
{
public:
	float x;
	float y;

	float Distance(Point *dest)
	{
		float xdiff = this->x - dest->x;
		float ydiff = this->y - dest->y;
		return (float)sqrt(xdiff*xdiff + ydiff*ydiff);
	}
};

//------------------------------------------------

class Person
{
public:
	Point spread;
	char name[40];

	void HitBall(Point *flag, Point *pos)
	{
		pos->x = flag->x + this->spread.x * rand() / RAND_MAX - this->spread.x / 2;
		pos->y = flag->y + this->spread.y * rand() / RAND_MAX - this->spread.y / 2;
	}
};

//------------------------------------------------

int main(void)
{
	Point flag;
	flag.x = 5;
	flag.y = 12;
	Point ball;
	Person player[2];
//	player[0].name = "Evert"
	player[0].spread.x = 20;
	player[0].spread.y = 20;

//	player[0].name = "Hasse"
	player[0].spread.x = 10;
	player[0].spread.y = 10;

	float minDist = FLT_MAX;
	int leader = 0;

	for(int i=0; i<10; i++)
	{
		float currentDist;

		// Person 1 hit
		player[0].HitBall(&flag, &ball);
		currentDist = ball.Distance(&flag);
		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = 1;
		}

		// Person 2 hit
		player[1].HitBall(&flag, &ball);
		currentDist = ball.Distance(&flag);
		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = 2;
		}
	}

	printf("Vinnare: person %s\n", player[leader-1].name);
	printf("Minsta avstånd från flaggan: %f meter\n", minDist);

	return 0;
}
Exempel 4.Fungerande C++program.

Här kan vi se att vi har något som heter class. Dess innebörd är som struct men med tillägget att man kan lägga funktionerna i klassen. Man har därför fått ett hjälpmedel för att gruppera funktionerna till tillhörande "struct".

Det är ett antal andra småförändringar som har skett i programmet också. Funktionsanropen ser något annorlunda ut än i C-programmen. Funktionsanropen i C++ påminner om hur man gör för att få tillgång till variabler i strukturer. Exempel på detta är funktionsanropet ball.Distance(&flag). Detta gör det onödigt att skicka ball som första parameter i anropen eftersom det är ball vi opererar på. I funktionen Distance får man tillgång till ball genom att använda den "magiska" pekaren this.

Nu har vi inte gått igenom alla verktyg som C++ har att erbjuda för att förenkla programmering och underhåll. Men vi har i alla fall sett hur tankesättet har gått och hur det bör gå när man bestämmer sig för en viss koddesign.

I nästa steg så har C++ programmet förbättrats med ett antal C++konstruktioner som skall göra programmet ännu mer rustat för förändringar.

Steg 4

Detta exempel har använt sig av ett antal nya konstruktioner i C++ samt utnyttjat standardbiblioteket för att skapa en liten och flexibel C++version av det ursprungliga C-programmet. Det ges ingen utförlig förklaring till de nya konstruktionerna, utan exemplet ges för en eventuell återblick senare i kursen.

#include <iostream>
#include <string>
#include <cmath>
#include <climits>
#include <cfloat>

using namespace std;

//------------------------------------------------

class Point
{
private:
	float x;
	float y;

public:
	Point() : x(0), y(0) {}
	Point(float xPos, float yPos) { Set(xPos,yPos); }
	Point(Point const& p) { Set(p.X(), p.Y()); }
	void Set(float xPos, float yPos) { x = xPos; y = yPos; }
	float & X() { return x; }
	float X() const { return x; };
	float & Y() { return y; }
	float Y() const { return y; };

	float Distance(Point const & dest) const
	{
		float xdiff = x - dest.x;
		float ydiff = y - dest.y;
		return (float)sqrt(xdiff*xdiff + ydiff*ydiff);
	}

	void Offset(float xOffset, float yOffset)
	{
		x += xOffset;
		y += yOffset;
	}
};

//------------------------------------------------

class Person
{
private:
	Point spread;
	string name;

public:
	Person() {};
	Person(string const& firstname, float spreadX, float spreadY)
	{
		Init(firstname, spreadX, spreadY);
	}

	void Init(string const& firstname, float spreadX, float spreadY)
	{
		spread.Set(spreadX, spreadY);
		name = firstname;
	}

	void HitBall(Point & pos, Point const& flag) const
	{
		float xOffset = spread.X() * (rand()/RAND_MAX - 0.5);
		float yOffset = spread.Y() * (rand()/RAND_MAX - 0.5);
		pos = flag;
		pos.Offset(xOffset, yOffset);
	}

	string const& GetName() const { return name; }
};

//------------------------------------------------

int main(void)
{
	Point flag(5,12);
	Point ball;
	Person player[2];
	player[0].Init("Evert", 20, 20);
	player[1].Init("Hasse", 10, 10);

	float minDist = FLT_MAX;
	int leader = 0;

	for(int i=0; i<20; i++)
	{
		int currentPlayer = i % 2;
		float currentDist;

		player[currentPlayer].HitBall(ball, flag);
		currentDist = ball.Distance(flag);
		if(currentDist < minDist)
		{
			minDist = currentDist;
			leader = i;
		}
	}

	cout << "Vinnare: " << player[leader].GetName() << endl;
	cout << "Minsta avstånd från flaggan: " << minDist << " meter" << endl;

	return 0;
}
Exempel 5.Fullt utbyggt C++program.
CD5250
Nyheter
Kursinformation
Föreläsningar
Labbar
Projektuppgift
Gott och blandat
C --> C++
STL-intro
Size-klassen
Visual C++
Kursplan
Schema

 

Underrubriker
Kravspecifikation
C-kod
Steg 1
Steg 2
Steg 3
Steg 4
Ansvarig lärare: Stefan Råmonth - stefan.ramonth@realfast.se
Senast uppdaterad: