Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: C++: Jäsenfunktioon osoittaminen

Sivun loppuun

crafn [20.02.2007 15:22:28]

#

Moi taas.. Täl kertaa mul on ongelmia olioiden ja luokkien kans, siis leikitään vaikka et on Luokka1 ja Luokka2.

Luokka1 sisältää Luokka2:n olion testiolio ja funktion testifunktio. Luokka2 sisältää osoittimen tosoitin.

Yritän Luokka1:n konstruktorissa testiolion tosoitin:men osottamaan testifunktioon, eli koodina:

class Luokka2{
    public:
    void (*tosoitin)(int);
};
class Luokka1 {
    public:
    Luokka2 testiolio;
    void testifunktio(int joopajoo);
    Luokka1();
};
Luokka1 :: Luokka1(){
    testiolio.tosoitin = testifunktio;
}
Luokka1 :: testifuntkio(int joopajoo){
    int numero = 0;
}

Virheilmotukseks tulee 'argument of type `void (Luokka1::)(int)' does not match `void (*)(int)''
Mikä mättää? :(

Metabolix [20.02.2007 15:46:41]

#

Mitähän tuolla olisi tarkoitus tehdä? :o
Kun jäsenfunktiota kutsutaan, sille annetaan parametriksi kyseisen olion osoite (this). Se on sinänsä aivan tavallinen parametri aivan samoin kuin int joopajoo, se ei vain näy ohjelmoijalle samalla tavalla. (Ei nyt puututa pikkuseikkoihin siitä, missä parametrit asuvat, rekisterissä vai pinossa...) Funktio-osoittimessasi et ole määritellyt sellaista parametria, tuskinpa sellainen edes on mahdollista. Funktio-osoittimesi ei siis ole samaa tyyppiä kuin funktio, jota yrität siihen laittaa, koska siinä ei ole kyseistä parametria.

crafn [20.02.2007 15:53:07]

#

Testifunktion pitäis pystyy käyttää Luokka1:n jäsenmuuttujia, ja sitä pitäs pystyy osottaa Luokka2:n osotin.
Mitä siis pitää muuttaa?

Metabolix [20.02.2007 16:22:05]

#

Tarvitset tietenkin myös luokan ilmentymän, eihän mitään jäsenmuuttujia muuten ole.

#include <iostream>
using namespace std;

class A_luokka;
class B_luokka;

// Osoitin luokan A_luokka funktioon,
// joka ottaa kaksi inttiä ja palauttaa intin
typedef int (A_luokka::*A_funktio)(int luku, int toinen);

class B_luokka {
public:
	A_luokka *objekti;
	A_funktio funktio;
	int x, y;

	void kutsu() {
		cout << (objekti->*funktio)(x, y) << endl;
	}
};

class A_luokka {
public:
	int oma;
	int yhteenlasku(int a, int b) {
		return a + b + oma;
	}
};

int main()
{
	A_luokka a;
	B_luokka b;
	b.funktio = &A_luokka::yhteenlasku;
	b.objekti = &a;

	b.x = 1;
	b.y = 2;
	a.oma = 3;

	b.kutsu();

	return 0;
}

Näin se selvisi ilman aiempaa tietoa asiasta.

Mazzimo [21.02.2007 16:33:01]

#

Oma viritykseni tästä ns. functor:ista (onko oikea termi? :O):
http://koti.mbnet.fi/masa_89/koodit/FunctionPtr.h

Sisältää tietysti paljon virheitä, joita en ole huomannut... Pitäisi toimia kaikkien parametri-arvojen kanssa sekä kaikilla paluutyypeillä void:ia lukuun ottamatta.

Grusifix [21.02.2007 21:05:43]

#

Tällainen linkki muistui mieleen http://www.devmaster.net/forums/showthread.php?t­=5884&highlight=function proxy

crafn [25.02.2007 09:50:23]

#

Testasin tota Metabolixin koodia. Se toimi hyvin. Sit koitin laittaa A_luokka:n ja B_luokka:n omiin tiedostoihin. En saanu toimii mitenkää. Mite siis saan ne luokat omiin .h ja .cpp tiedostoihin että toimiski viel?

Metabolix [25.02.2007 16:52:13]

#

Ennen B-luokan määrittelyä täytyy olla määritelty siinä käytetyt asiat, kuten nyt tuossa koodissa on. A-luokka taas ei tarvitse B-luokan tietoja, joten tilanne selviää helposti niin, että B-luokan otsikkoon liitetään A-luokan otsikko.

A_luokka.h:

#ifndef _A_luokka_H_
#define _A_luokka_H_ 1

class A_luokka;
typedef int (A_luokka::*A_funktio)(int luku, int toinen);

// Jos A-luokka tarvitsisi B-luokkaa, niin tässä kohti #include "B_luokka.h"

class A_luokka {
public:
	int oma;
	int yhteenlasku(int a, int b) {
		return a + b + oma;
	}
};

#endif

B_luokka.h:

#ifndef _B_luokka_H_
#define _B_luokka_H_ 1

// Jos A-luokka tarvitsisi B-luokkaa, seuraava rivi olisi melkeinpä pakollinen. Nyt sillä ei ole niin merkitystä.
class B_luokka;

#include "A_luokka.h"

#include <iostream>

class B_luokka {
public:
	A_luokka *objekti;
	A_funktio funktio;
	int x, y;

	void kutsu() {
		std::cout << (objekti->*funktio)(x, y) << std::endl;
	}
};

#endif

main.cpp:

#include "A_luokka.h"
#include "B_luokka.h"

int main()
{
	A_luokka a;
	B_luokka b;
	b.funktio = &A_luokka::yhteenlasku;
	b.objekti = &a;

	b.x = 1;
	b.y = 2;
	a.oma = 3;

	b.kutsu();

	return 0;
}

Jos A-luokkaan pitäisi saada B-luokka suoraan eikä osoittimena, joutuisi otsikoille tekemään mutkikkaamman rakenteen, jotta moneen kertaan liittäminenkään ei aiheuttaisi ongelmia.

crafn [27.02.2007 14:52:14]

#

Kyllä A-luokkaan pitäis saada B-luokan olio, eli miten se mutkikkaampi rakenne tehään? Tai jossain linkkiä?

koo [27.02.2007 17:30:36]

#

MR.Coodari, voisitko tarkemmin kertoa, mitä oikein olet tekemässä?

Ei siinä mitään, kyllä tuohon kysymykseesikin pystyy vastaamaan ihan täsmällisesti, mutta kun se kysymys taustaa tietämättömälle kuulostaa vähän samalta kuin "miten naulaan vatupassilla?"

crafn [28.02.2007 15:58:04]

#

Oon tekemäs pientä tekstiseikkailua, ja siin mul on game-luokka jonne isken kaiken joka kuuluu peliin. Oon tehny menusysteemin sillee et mul on menu-luokka jossa on osoitin funktioon jolle annetaan valinnan numero parametrinä. Mulla on siis game-luokassa menu-olioita ja game-luokassa on funktiot johon menu-luokasta osotellaan. Semmosta teen eikä taho onnistuu.

FooBat [28.02.2007 17:02:06]

#

Itse tekisin tuon yksinkertaisuuden nimissä siten, että menua luodessa sille annetaan vain näkyvä nimi ja toimintakoodi, jolla se sitten kutsuu game-luokan jotain yleistä menun käsittelijämetodia, kun menu valitaan. Funktioiden kutsu tapahtuisi silloin Game-luokan sisällä eikä mitään funktiopointtereita tarvittaisi. Syy tälläiseen ratkaisuun on osittain siinä, että en alkuunkaan tykkää C/C++:n funktiopointterisyntaksista ja osittain siinä, että tällöin Game-luokan metodeja ei tarvitse tehdä ulospäin näkyviksi.

Kai tuo funktiopointtereillakin onnistuu, jos vain määrittelyt on kunnossa. Jos pointterit luokan sisäisiin funktioihin tuottaa ylitsepääsemättömiä ongelmia, voi tietenkin tehdä luokan ulkopuolisia funktioita, jotka toimivat wrappereinä luokan sisäisiin funktioihin.

koo [01.03.2007 01:13:54]

#

MR.Coodari, mistä se menusysteemi tietää kutsua noita menu-olioita? Arvaanko oikein, että game-olio jollain tapaa rekisteröi menu-olionsa menusysteemille ja menusysteemi sitten aikanaan kutsuu menu-olion funktiota? Onko game-luokassa monta menu-oliota vai onko menu-olioita vain yksi?

Ilmeisesti et halua, että menusysteemi tietää yhtään mitään game-oliosta suoraan ja siksi käytät menu-olioita? Tollaista kuviota nimitellään oliomaailmassa yleisemmin callback:iksi. Sitä nuo Mazzimonkin jutut näemmä edustavat (functor ei ihan taida tarkoittaa tätä).

Menusysteemi varmaankin toimii niin, että kun jokin valinta tehdään, systeemi kutsuu sille rekisteröidyn olion tiettyä funktiota? Meneekö homma menee niin, että menuvalinta kutsuu aina saman olion samaa funktiota mutta eri parametreilla riippuen valinnasta? Silloin yksinkertaisin tapa on, että game-olio rekisteröi itsensä menusysteemille ja menusysteemi sitten kutsuu game-luokan jotakin public-funktiota. Mutta tätä et ilmeisesti halua esmes siksi, että se kytkee game-luokan ja menusysteemin tiukasti toisiinsa?

Seuraava tapa voisi olla, että game-luokka periytetään jostakin menuaction-luokasta. Menuaction-luokassa on sitten pure virtual funktio, jota menusysteemi kutsuu ja tuon funktion toteutus tehdään game-luokassa. Noin se suora kytkentä menusysteemin ja game-luokan välillä vältetään, kun menusysteemi tunteekin vain menuaction-rajapinnan. Tässä voisi käyttää jotenkin private-perintääkin, siitä näkyy olleen juttua newseissä.

Jos menusysteemin on tarkoitus kutsua valinnasta riippuen eri funktioita, noita varten voi tehdä erilaisia menuaction-luokkia. Game-luokka johdetaan moniperinnällä niistä kaikista. Hankaluutta kyllä tulee, jos se kutsuttava pure virtual funktio on samanniminen noissa kaikissa eri menuaction-luokissa.

Enemmän joustavuutta saisi jalostamalla tuota Mazzimon koodia. Silloin ne menuoliot olisivatkin irrallisempia callback-otuksia, sillä ei niiden muutenkaan kannata olla game-luokan jäsenmuuttujia.

Toivottavasti tämä nyt vähän auttaa oikeaan suuntaan, kun tarkemmista yksityiskohdista ja tarpeista riippuen homman voi tehdä joko tsiljoonalla tai kiljoonalla eri tavalla, joista sitten varmaan öpaut kaksi on kumminkaan järkeviä. :-)

Mazzimo [01.03.2007 13:47:07]

#

Kyllä.. Kyllä.. Callback:iksi tuota systeemiä taidetaan kutsua, mutta functoreita nuo kutsuvat oliot taitavat olla. En ole varma, ei siitä sen enempää..

Mutta mutta, kuten koo jo mainitsikin tuossa, ehdottomasti helpoin tapa toteuttaa tämä ns. callback-systeemi, on käyttää polymorfismia. Itsekin käytän tätä systeemiä, jos vain mahdollista. Omasta mielestäni tilakone on tällä tavalla erittäin kätevä tehdä. Viestistäni tulee hieman pitkä, mutta toivon asian tulevan selväksi. Selvitän ensiksi tuon polymorfismin näyttämällä, miten olen tilakoneeni rakentanut.

Eli ensiksi minulla abstrakti luokka, joka sisältää tarvittavat metodit tilan hallintaan:

#pragma once
// StateTemplate.h

/* 24.2.2007 - Tiedosto luotu.
 */



/** Pohja tapahtuma-kontrollerin jäsenille.
@remars
	Tämä luokka on tarkoitettu periytettäväksi eri ohjelman tiloille.
	Tämän luokan osoittimia on StateManager-luokan instanssissa ja ne
	on merkitty tila-avaimen (String) taakse. Tila-elementtien toiminta
	on seuraava: tilakontrollerilla on avain, jonka tilaa se käy selvittä-
	mään. Tilakontrolleri hakee tilan tämän avaimen perusteella ja alkaa
	suorittamaan tilan start-metodia. Tätä metodia suoritetaan niin kauan,
	kunnes se palauttaa FALSE. Sitten siirrytään suorittamaan saman tila-
	elementin loop-metodia, lopuksi end-metodia. Näissä kahdessa on sama
	ehto kuin start-metodillakin. Kun end on palauttanut FALSE, katsoo
	tilakontrolleri seuraavalla loopilla nykyisen avaimensa perusteella
	seuraavan tila-elementin, jota käydään toteuttamaan.
@notes
	Luokka on abstrakti, muista periyttää se ylikirjoittaa virtuaalimetodit.
	Tämän jälkeen lisää yksi luokan instanssi tilakontrolleriin valmiiksi
	käytettäväksi.
*/
class StateTemplate
{
	public:

		virtual ~StateTemplate() {}

		/** Ensimmäinen kohta, joka suoritetaan. */
		virtual bool start() = 0;

		/** Toinen kohta, joka suoritetaan. */
		virtual bool loop() = 0;

		/** Viimeinen kohta, joka suoritetaan. */
		virtual bool end() = 0;

};

Eli systeemi on hyvin yksinkertainen. Tuon luokan osoittimia varastoidaan itse tilakontrolleriin ja start-loop-end -metodeita kutsutaan, kun tila saadaan voimaan.

Sitten tarvitaan manageri, joka huolehtii varastoiduista tila-elementeistä. Systeemissäni tilat luodaan ohjelman alussa ja ne poistetaan ohjelman lopussa.

#pragma once
// StateManager.h

/* 24.2.2007 - Tiedosto luotu.
 */

#include "BaseItems.h"


// esitellään
class StateTemplate;



/** Ohjelman tilaa valvova ja nykyisen tilan toteuttava luokka.
@remarks
	Ohjelman FrameListener kutsuu metodissaan "frameStarted" tämän
	luokan globaalia singleton-instanssia ja siitä update-metodia.
	Update-metodi valvoo nykyistä tilaa ja toteuttaa nykyisen tilan
	toiminnot, jotka instanssiin on määritetty. Intanssin seuraavan
	tilan saa määritettyä setState-metodilla. Tämä tila tulee voimaan
	heti, kun nykyinen on lopettanut toimintansa. Eri tiloja saa
	asetettua avainten taakse addState-metodilla.
@notes
	Tämän luokan instanssi luodaan createScene-funktiossa, se tuhotaan
	destroyScene-funktiossa. Luokka on globaali singletonluokka.
*/
class StateManager : public Ogre::Singleton<StateManager>
{
	public:

		/** Luo uuden alustetun instanssin. */
		StateManager();



		/** Tuhoaa instanssin.
		@notes
			Tuhoaa instanssiin dynaamisesti luodut tila-elementit
			niiden osoittimiensa perusteella.
		*/
		~StateManager();



		/** Päivittää tilan.
		@notes
			Tätä metodia tulee kutsua joka framella.
		@return TRUE, jos ei ole annettu lopetustilaa, FALSE jos ohjelma halutaan lopettaa.
		*/
		bool update();



		/** Lisää manageriin uuden tilan.
		@param key Avain, jonka taakse uusi tila luodaan.
		@param element Tila-elementti, joka tulee luoda dynaamisesti ja asettaa avaimen taakse.
		@notes
			Lisätty tila-elementti tuhotaan automaattisesti tuhoajafunktiossa.
			Tämä metodi heittää poikkeuksen, jos avaimen taakse on jo määritetty
			tilaelementti. Avain nimeltä "StateShutDown" on varattu ja sitä
			ei saa antaa avaimeksi. Tässä tapauksessa heitetään poikkeus.
		*/
		void addState(const String& key, StateTemplate* element);



		/** Asettaa managerin seuraavan tilan.
		@param key Avain, jonka tila halutaan asettaa.
		@notes
			Metodi heittää poikkeuksen, jos asetettavalle tilalle ei löydy
			elementtiä. Jos löytyy, tila tulee voimaan vasta, kun nykyinen tila
			on saatu päätökseen. Tämä metodi heittää poikkeuksen, jos avaimen
			taakse ei ole määritetty tilaelementtiä.
		*/
		void setState(const String& key);



		/** Pakottaa nykyisen tilan loppumaan.
		@remarks
			Nykyinen tila suljetaan heti paikalla ja seuraava tila tulee voimaan
			heti seuraavalla framella.
		@notes
			Metodi on hyvin vaarallinen, koska tilan end-metodia ei kutsuta, joten
			tilan mahdollisesti varaamaa dynaamista muistia ei vapauteta. Tätä
			metodia tulee käyttää vain, jos tietää mitä tekee.
		*/
		void forceStateEnd();



	private:

		/** Kartta tila-elementeille. */
		typedef std::map<String, StateTemplate*>	StateMap;


		StateMap		mStates;		// kartta asetetuista tiloista
		String			mCurStateName;	// viimeiseksi asetetun tilan nimi
		int				mStatePos;		// tilan suorituskohta
		StateTemplate*	mCurState;		// nykyisen tila-elementin osoitin

};

Katso erityisesti rivillä 96 oleva typedef ja 99 rivillä oleva jäsenmuuttuja. mStates-jäsenmuuttujaan sisällytetään kaikki ohjelman tilat käyttäen tilan avaimena merkkijonoa (esimerkiksi: "StateMenu"). Vain yksi merkkijono on varattu, "StateShutDown", joka sammuttaa ohjelman.

Seuraavassa näet tilamanagerin toteutuksen:

#include "StateManager.h"
#include "StateTemplate.h"


StateManager* Ogre::Singleton<StateManager>::ms_Singleton = 0;




StateManager::StateManager()
: mCurState(0), mStatePos(0), mCurStateName("Undefined")
{
}




StateManager::~StateManager()
{
	for( StateMap::iterator it = mStates.begin() ; it != mStates.end() ; it++ )
	{
		if( it->second )
		{
			delete it->second;
		}
	}
	mStates.clear();
}




bool StateManager::update()
{

	if( mCurState )
	{

		switch( mStatePos )
		{
			case 0:
				if( !mCurState->start() )	mStatePos++;
				break;
			case 1:
				if( !mCurState->loop() )	mStatePos++;
				break;
			case 2:
				if( !mCurState->end() )		forceStateEnd();
				break;
		};

	}
	else
	{

		if( mCurStateName == "StateShutDown" )	return false;
		if( !mStates.count( mCurStateName ) ) OGRE_EXCEPT(0, "State not defined behind key \""+mCurStateName+"\"", "StateManager::update");
		mCurState = mStates[mCurStateName];

	}

	return true;

}





void StateManager::addState(const Ogre::String &key, StateTemplate *element)
{
	if( mStates.count( key ) ) OGRE_EXCEPT(0, "State already defined behind key \""+key+"\".", "StateManager::addState");
	mStates[key] = element;
}




void StateManager::setState(const Ogre::String &key)
{
	mCurStateName = key;
	if( key == "StateShutDown" )	return;
	if( !mStates.count( key ) ) OGRE_EXCEPT(0, "State not defined behind key \""+key+"\".", "StateManager::setState");
}




void StateManager::forceStateEnd()
{
	mStatePos = 0;
	mCurState = 0;
}

Kun ohjelmaan halutaan luoda tila odottamaan vuoroaan, sille annetaan tunnus josta sitä voi kutsua. Lisäksi tilalla tulee olla myös osoitin olioon, jossa tila tullaan toteuttamaan. Kun katsot metodia StateManager::update, huomaat sen kutsuvan sokeasti nykyistä tilaelementtiä. StateManager ei tiedä, mitä tilaelementti ohjelmaan tekee, se kutsuu elementin metodeita sokeasti, vaikka tilaelementti potkisi mummoja perseelle minkä ehtii. StateManager::update-metodia kutsutaan yhden kerran framessa, joten tilaa päivitetään koko ajan. StateManager tietää vain sen, että kun tilaelementti palauttaa FALSE, on se tehnyt haluamansa. Tämän jälkeen manageri alkaa etsimään uutta tilaa, jota voisi toteuttaa. Tilaelementit voivat tehdä IHAN MITÄ TAHANSA, käyttäjä voi itse päättää siitä. Seuraavassa esimerkki siitä, miten tiloja voi luoda erilaisia:

#pragma once
// GameState.h

#include "StateTemplate.h"
#include "StateManager.h"
#include "MapManager.h"




class StateGame : public StateTemplate
{
	public:

		virtual bool start()
		{
			MapManager::getSingleton().loadMap("Nemeko");
			return false;
		}



		virtual bool loop()
		{
			bool state = true;
			// PÄIVITETÄÄN PELI
			if( vihu_osuu_pelaajaan ) state = false;
			return state;
		}



		virtual bool end()
		{
			MapManager::getSingleton().destroyMap();
			StateManager::getSingleton().setState("StateShutDown");
			return false;
		}

};



class StateMenu : public StateTemplate
{
	public:

		virtual bool start()
		{
			// ladataan menu-elementit
			return false;
		}



		virtual bool loop()
		{
			bool state = true;
			if( start_button_cliked ) { state = false; StateManager::getSingleton().setState("StateGame"); }
			if( quit_button_cliced ) { state = false; StateManager::getSingleton().setState("StateShutDown"); }
			return state;
		}



		virtual bool end()
		{
			// tuhotaan menu-elementit
			return false;
		}

};

Sitten vielä ohjelman alussa lisään tilat tilamanageriin ja käynnistän ensimmäisen tilan, joka on menu.

StateManager& sm = StateManager::getSingleton();
sm.addState( "StateMenu", new StateMenu() );
sm.addState( "StateGame", new StateGame() );

sm.setState( "StateMenu" );

Toivottavasti tajusit edes hieman, miten käyttää polymorfismia hyväksesi joustavaa koodia tehdessäsi.


Ja sitten seuraava eli functorit. Functoreita kannattaa käyttää vain, jos haluaa erittäin joustavaa koodia ja kutsuttava funktio saattaa todellakin tulla mistä tahansa, joko tavallisesta funktiosta tai sitten tietyn olion jäsenfunktiosta. Itse olen toteuttanut esimerkiksi hiiren ja näppäimistön tällä tavalla. Jokaiselle syötteelle asetetaan oma tapahtuma, joka laukaistaan kyseisen syötteen tullessa.

Ensiksi tarvitaan instanssin määrittely:
http://koti.mbnet.fi/masa_89/koodit/BaseMouse.h

Kiinnitä tässä erityisesti huomiota riveihin

typedef FunctionPtr<bool, const EventArgs&> EventPtr;

// ja
typedef std::map<String, EventPtr> EventMap;

sekä komentoon setEvent(const String& type, EventPtr ptr). Kyseisellä komennolla lisätään hiireen tapahtuma, joka laukaistaan tarvittaessa. Seuraavassa hiiren toteutus:
http://koti.mbnet.fi/masa_89/koodit/BaseMouse.cpp

Näet Mouse::clearEvent-metodista, miten jäsenfunktioon voidaan osoittaa. On myös erittäin suositeltavaa, että teet struktuurin parametreistä, jotka välität. Esimerkissäni välitettävä struktuuri on Mouse::EventArgs, joka sisältää hiiren sijainnin sekä liikkeen viime framen aikana. Se on oikeastaan kaikki mitä käyttäjä tarvitsee.

Mistä hiiri sitten tietää, milloin käyttäjä on painanut jotain näppäintä? Huomasit varmasti, että olin määrittänyt hiirelle ystävyysluokaksi BaseFrameListener:in. Tämä luokka välittää hiirelle tapahtuneet syötteet joiden pohjalta hiiri osaa laukaista sille viritetut tapahtumat.
http://koti.mbnet.fi/masa_89/koodit/BaseFrameListenerMouseInput.cpp

Tuosta löytyy koodi, jolla syötteet kommunikoivat hiiren kanssa. En kommentoi sitä enempää. Tuon lisäksi hiiri kutsuu joka framella update-metodia, jotta alas painetut näppäimet saadaan päivitetyksi. Jotta saat selvää functorien kutsumis-metodeista, kannattaa katsoa BaseFrameListener::mouseMoved-metodi tarkkaan, rivi m.myEvents[Mouse::MouseMoved](m.myAttributes); on kaiken avain.

Jeps. Eli funktioita saa välitettyä hiirelle seuraavalla tavalla:

bool ammu(const Mouse::EventArgs& e)
{
	// pum pum
	return true;
}

class Ukko
{
public:

	bool hyppelehdi(const Mouse::EventArgs& e)
	{
		// hop hop
		return true;
	}
};

// koodia....

Ukko* u = new Ukko();

Mouse& m = Mouse::getSingleton();
m.setEvent(Mouse::MouseHit[0], Mouse::EventPtr(ammu));
m.setEvent(Mouse::MouseHit[1], Mouse::EventPtr(&Ukko::hyppelehdi, u);

Aina kun hiiren vasenta näppäintä klikataan, ammutaan. Aina kun oikeaa näppäintä klikataan, ukko hyppii.

Tässäpä oli pieni esimerkki functoreista.

crafn [04.03.2007 11:18:02]

#

Nää functorihommat taitaa olla just sitä mitä hain, kiitos :)


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta