Mitä C kielessä tällä 'funktion esittelyllä' tarkoitetaan. Esim.
void funktio(void)
Olenko vain tyhmä kun en irti saanut tutoriaalista.
Sillä kerrotaan kääntäjälle, että tällainen funktio on olemassa, mutta sen implementaatio tulee vasta myöhemmin.
Selvä. Mutta mitäs tämä void tarkottanee tässä.
Ei mitään.
Eli palautusarvo on tyypiltään "ei mitään" ja parametrit ovat "ei mitään".
Miksi tämä sitten pitää kirjoittaa? Varmaankin siksi, että silloin et ole ainakaan unohtanut, vaan olet tietoisesti päättänyt, että se on "ei mitään".
Tuntuisi varsin tyhmältä esitellä funktioita jos ne eivät tarkoittaisi mitään. Siten voidilla on merkitys toisin kuin Metabolix sanoi, mutta merkitys on sama kuin mitä Metabolix sanoi, eli ei paluuarvoa tai ei parametreja. Funktion esittelyllä ohjelman lähdekoodista näkee heti minkä tyyppisiä parametreja funktio saa ja minkä tyyppisiä palauttaa.
void-tyyppiä käytetään myös paljon tilanteissa, jossa halutaan varmistaa äärimmäinen yhteensopivuus. Kuvitellaan tilanne, että yritys X tilaa sinulta tehokkaan yksikön bittivirran käsittelyyn. Yksikkö sisältäisi funktiot:
writeToStream readFromStream
Päätät käsitellä bittivirtaa yksikössäsi tavuina. X antaa sinulle vapaat kädet suunnitella rajapinta tarpeeseen sopivaksi. Kysyt X:ltä, että kuinka monta bittiä virtaan tarvitsee kerralla pystyä kirjoittamaan. X tuumailee vastausta vähän aikaa, ja sanoo likimääräisesti: muutaman ja melko paljon väliltä.
Jos teet lisäkysymyksen, että tuo asia on pakko tietää, ennen kuin yksikön voi tehdä. X vastaa: 20 muuta alihankkijaa ei tuota tietoa ole kysynyt, tehtävä on ilmeisesti liian vaativa Teille, joten palataan astialle mahdollisesti tulevissa helpommissa projekteissa.
Mutta jos olet sinut void:in kanssa, et edes rupea kysymään moista lisämääritettä. Teet sitten viikon hiki hatussa hommia, ja yksikkösi on valmis dokumentoitavaksi ja myytäväksi X:lle.
Dokumentissa mainitset tietysti ensimmäisenä, että yksikkösi on äärimmäisen joustava. Funktiot voi dokumentoida esimerkiksi seuraavsti:
int writeToStream(void *dst, void *src, int write_bit_offset, int write_bit_len) int readFromStream(void *dst, void *src, int read_bit_offset, int read_bit_len)
Funktiota writeToStream voi kutsua millä tahansa osoittimilla. (Osoittimia ei tarvitse "kastaa" void-tyyppisiksi, mutta ei siitä haittaakaan ole.)
PS. Jos olet määritellyt osoittimet joksikin muuksi, kuin void-tyyppisiksi, kaikki kääntäjät antavat lukemattomia varoituksia, jopa vakavia pointterikonversiovaroituksia. Vaikka void on "ei mitään", toisaalta se tarkoittaa myös päinvastaisesti "kaikki käy".
Parametri write_bit_offset on offset puskurin dst-bittipositioon, josta lähtien puskurin src bittejä kirjoitetaan parametrin write_bit_len verran.
...jne.
Pääpiirteissään näin. Funktion palautusarvoille on luonnollista määritellä jonkinlaiset mahdollisia ajonaikaisia virheitä ja onnistumista kuvaavia arvoja.
(Muuten, jos X ei olisi tyytyväinen, että kirjoitettavien ja luettavien bittien puskurit ovat tasattu alkamaan offsetista 0, funktiorunkoihin tarvittaisiin vielä yhdet offset-parametri kertomaan, mistä toisen puskurin offset alkaa.)
Hyvä, nyt sinulla on vapaat kädet funktioiden sisällä. Sinällään void on melko kankea käytettäväksi sellaisenaan, jonka vuoksi se assosioidaan tilanteesta riippuen sopivan tyyppiseksi, esim.:
int writeToStream(void *dst, void *src, int write_bit_offset, int write_bit_len) { int errMess=0; /* 0=OK */ char *kohde=(char*)dst; char *lahde=(char*)src; /* rutiinin koodi tähän */ return errMess; }
Toivottavasti selvensi lisää void:in merkitystä.
Tuo JoinTuanJanohonin esimerkki kyllä meni näin koodausta aloittelevalle vähän liian monimutkaiseksi. Kuitenkin jos ymmärsin asian oikein, eli jos vaikka luvulle 2 suoritetaan seuraavanlainen funktio:
void funktio(int luku) { luku = luku * 4; }
Tällöin luvulla 2 ei olisi palautusarvoa?
Mitä järkeä on esitellä funktio kääntäjälle jos kerran ilman esittelyäkin omien testieni perusteella toimi?
Funktio pitää esitellä jos funktion runko on Main funktion alapuolella.
#include <cstdlib> #include <iostream> using namespace std; void Funktio() //Koska funktion runko ennen Main funktiota funktio toimii { cout<<"Toimii!"<<endl; } int main(int argc, char *argv[]) { Funktio(); system("pause");//Elkääkä ruvetko itkeen tätä, dev pistää tän automaagisesti... return EXIT_SUCCESS; }
Yllä oleva toimii mutta alla oleva ei
#include <cstdlib> #include <iostream> using namespace std; //Esittely puuttuu int main(int argc, char *argv[]) { Funktio(); system("pause");//Elkääkä ruvetko itkeen tätä, dev pistää tän automaagisesti... return EXIT_SUCCESS; } void Funktio() //Koska funktion runko Main funktion jälkeen eikä sitä ole esitelty funktio ei toimi { cout<<"Ei toimi!"<<endl; }
Edit:Ja ihan vielä selvennykseksi:
#include <cstdlib> #include <iostream> using namespace std; void Funktio();//Koska esitelty Funktio toimii int main(int argc, char *argv[]) { Funktio(); system("pause");//Elkääkä ruvetko itkeen tätä, dev pistää tän automaagisesti... return EXIT_SUCCESS; } void Funktio() //Koska funktion runko Main funktion jälkeen ja se on esitelty funktio toimii { cout<<"Toimii!"<<endl; }
Eli Funktion pitää olla joko esitelty tai kokonaan(runkoineen päivineen)ennen mainia
Ok. Miksi näin? Itselläni kyllä toimii vaikka main() funktion alapuolella olisi esittelemättömiä funktioita. Kiinni kääntäjästä?
Veikkaisin, että koodin toimivuus riippuu puhtaasti kääntäjästä, minulla toimii myös Baglairin tapaan ennestään esittelemättömät funktiot mainin jälkeen. Funktiot kannattaa siis esitellä mikäli haluat että koodisi kääntyy myös Jäyniksen kääntäjällä ilman muutoksia koodiin.
Stroustupin kirja C++-ohjelmointi kertoo vain, että jokainen ohjelmassa esitetty funktio on esiteltävä jossakin. Se ei siis ota kantaa siihen, onko funktiot ja niiden esittelyt mainia ennen vai jälkeen. Pidän kirjaa luotettavana lähteenä, sillä mies itse kehitti C++:n.
Huomaa Baglair, että C++:ssa jokainen funktio pitää esitellä. Et siis voi kirjoittaa esittelemättömiä funktioita mainin jälkeen. Vaikka kirjoittaisit funktion esittelyn alle suoraan funktion määrittelyn, on funktio tässäkin tapauksessa esitelty.
Baglair kirjoitti:
Ok. Miksi näin?
C-kielen tyhmempiä rajoituksia, tälle löytynee historiallinen syy.
lainaus:
Itselläni kyllä toimii vaikka main() funktion alapuolella olisi esittelemättömiä funktioita. Kiinni kääntäjästä?
Todennäköisesti.
Hola, tuli noista funktioista, ja niiden esittelemisestä yksi juttu mieleen. Osa koodaa ylhäältä alas, jolloin kutsuttava funktio on aina kutsuvan funktion yläpuolella. Toiset taas tykkäävät koodata päinvastoin alhaalta ylös. Silloin usein yksikön ensimmäinen funktio voi olla main, josta lähdetään kutsumaan rutiineja alspäin. Tässä tapauksessa kutsuttavan funktion runko sitten joudutaan esittelemään ennen funktion kutsua.
Itse oon koodannut aina ylhäältä alas, jolloin kutsuttavat funktiorutiinit voi ensin testata, ennen kuin sitä kutsutaan. Tämä kuitenkaan tuskin on sen parempi tapa, kuin mitä on tuo toinen tapa. Jossain määrin se jopa auttaa täysin tuntemattoman moduulin/yksikön hahmottamista.
Se mun esimerkki ei kyllä ollut sieltä selventävimmästä päästä. void on "ei mitään", mutta pointtereiden kohdalla se tarkoittaa kaikkien pointtereiden yhteistä tekijää. Jokainen pointteri voi olla aina myös void* -pointteri, jolla siis lähinnä kutsuvan ja kutsuttavan funktioiden välille voidaan muodostaa väljempi ja joustavampi sidos.
Esimerkkiprojekti saattaisi jakautua niin, että osa koodista tehdään Rovaniemellä, ja osa Helsingissä. Tiimin osapuolet eivät silloin välttämättä halua tuntea toistensa yksiköistä muuta, kuin sen vähimmäismäärän, miten yksiköt toistensa kanssa kommunikoivat ja käyttävät hyväkseen toistensa rutiineja.
Rovaniemen tiimi voi määritellä rakenteen:
typedef struct { int32 a; uint16 b; int8 taulu[101]; /* jne. Rovaniemen juttuja */ /* Helsingin tarvitsemat parametrit alkavat tästä */ int16 H1; uint32 C3; /* jne. Helsingin tarvitsemia juttuja */ } rakenne;
Nyt Rovaniemen tiimi voi halutessaan dokumentoida vain parametrit, jotka alkavat Helsingin kohdasta. Tässä tapauksessa Rovaniemen on helppo sanoa Helsinkiläisille,
että välitämme rakenteen void* pointterina, ja teidän tarvitsemanne offset alkaa siitä ja siitä positiosta. Sen jälkeen parametreistä ja IO:sta dokumentoidaan vain Helsingin jutut, koska heitä ei välttämättä kiinnosta lainkaan Rovaniemen data. Esimerkiksi tällaisessa yhteydessä ja tarpeessa void* -pointteri on kätevä.
Olen pahoillani edellisestä viestistäni. Aloin höpöttämään C++:n asioita, vaikka kielenä olikin C. Tykkään itse tehdä asioita C++:lla, joten sekoilin kielissä. En tiedä miten asia on tarkalleen määritelty C:ssä.
Niin, ja sitten vielä muistin oman hetkeni, kun aloin sisäistämään maremmin void* pointterin merkitykstä koodaamisessa. <stdlib.h>:ssa on määritelty quicksort-algoritmin prototyyppi. (qsort on muuten määritelty ANSI C:ssä, ja toimii aina heittämällä myös UNIX-järjestelmissä, hyvä esimerkki C:n siirrettävyydestä.)
Mutta kuitenkin, funktion prototyyppi on seuraavanlainen:
void qsort(void *base, size_t elemLkm, size_t elemWidth, int (*ptrOfSortLogic)(const void*, const void*))
Nyt, kun parametrien merkitys avataan, void* ositin base sisältää pointterin lajiteltavan taulukon alkuun. Nyt jos on int-taulukko, double-taulukko, tai edellä mainittu rakenne -taulukko, niin funktiota voidaan kutsua raa'asti eri taulukoilla:
int intTaulukko[256]; double doubleTaulukko[256]; rakenne rakenneTaulukko[256]; /* koodia... */ qsort(intTaulukko, 256, xxx, xxx qsort(doubleTaulukko, 256, xxx, xxx qsort(rakenneTaulukko, 256, xxx, xxx
Funktioprototyypin toinen parametri size_t elemLkm sisältää siis lajiteltavan taulukon elementtien lukumäärän.
Seuraava parametri size_t elemWidth sisältää tiedon, kuinka monta tavua lajiteltavan elementti koko on. Funktio tarvitsee tietää lajiteltavan taulukon elementtien koko silloin, kun qsort-algoritmi haluaa vaihtaa kahden elementin paikkaa.
Viimeinen parametri oli ainakin minulle aluksi hurjan gryptinen. Ajattelin päätäni puhki, että mitä ihmettä. Ehkä siihen ei olisi tarvinnut käyttääkään niin paljoa aikaa, koska noi funktion -pointterit ovat C:ssä (ja miksei myös C++:kin) huidsin käyttökelpoisia. Joka tapauksessa pitemmittä puheitta esimerkkiin:
#include <stdlib.h> #include <stdio.h> #include <conio.h> int rnd(void) { static unsigned long r1=1L; /* voi olla mikä tahansa alkuarvo */ static unsigned long r2=2L; /* voi olla mikä tahansa alkuarvo */ r1 -= 0x012357bfL; /* 16 bittiä sisältävä alkuluku */ r2 += 0xfedca201L; /* 16 bittiä sisältävä alkuluku */ r1 += (r1>>16)^(r2<<16); /* sekoitetaan */ r2 -= (r1<<16)^(r2>>16); /* sekoitetaan */ return (int)(r1^r2); /* kerran vielä kiellon päälle */ } /***************************************************** * Tämä funktio antaa tietoa qsort-algoritmille, * * mitä taulukon elementtien keskinäiset järjestykset * * milloinkin ovat. qsort-algoritmi kutsuu tiuhaan * * algoritmin ajoaikana tätä funktiota. Ks. main, * * jossa tämän funktion pointteri edelleen välitetään * * qsort-algoritmille. * *****************************************************/ int funcOfSortLogic(const void *elem1, const void *elem2) { int arvo1=*(int*)elem1; int arvo2=*(int*)elem2; if (arvo1 < arvo2) { return -1; /* ensimmäinen elementti oli pienempi */ } else if (arvo1 > arvo2) { return +1; /* ensimmäinen elementti oli suurempi */ } else { return 0; /* elementit ovat yhtäsuuria */ } } void main(void) { int intTaulukko[256]; int i; for (i=0; i<256; i++) { intTaulukko[i]=rnd()&0xff; } qsort(intTaulukko, 256, sizeof(int), funcOfSortLogic); for (int i=0; i<256; i++) { printf("taulukon indeksissa %003d on luku %3d\n", i, intTaulukko[i]); } getch(); }
Aihe on jo aika vanha, joten et voi enää vastata siihen.