Syöte- ja tulostus C-kielessä¶
Osaamistavoitteet: Tämän materiaalin luettuasi osaat tuottaa muotoiltua tulostusta sekä kysyä käyttäjältä syötettä C-kielellä.
Aiemmassa materiaalissa olemme jo onnistuneet tulostamaan tekstiä tietokoneen ruudulle C-kielellä printf-lauseella. Seuraavaksi paneudutaan asiaan hieman syvemmälti ja katsotaan miten tehdään muotoiltua tulostusta. Tässä yhteydessä esitellään C:n standardikirjaston
stdio
muotoiltuun tulostukseen ja syötteen lukemiseen (näppäimistöltä) tehdyt valmiit funktiot. Koska funktiot ovat osa standardia, joten ne toimivat kaikissa järjestelmissä samalla tavoin. Noo.. siltikään ne eivät toimi aivan samoin kaikissa järjestelmissä. Sulautetuissa järjestelmissä esiintyy standardikirjastojen toteutuksia, jossa syötteiden lukeminen tai kaikentyyppisen datan käsittely tulostukseen ei onnistu. Laitteiden resurssirajoitusten takia esimerkiksi liukulukujen käsittely ja tulostus voi jäädä pois. Noh, tätä asiaa murehdimme myöhemmin. Käy ilmi, että sulatetuissa järjestelmissä usein on omia toteutuksia näille funktioille, jotka tulostavat merkkijonomme milloin millekin oheislaitteelle.
Emme vielä tässä vaiheessa tiedä kaikkea C-kielen detaljeista, mutta pärjäämme näiden funktioiden kanssa aivan mainiosti.
Muotoiltu tulostus¶
C-kielessä on useita funktioita, joilla voidaan tuottaa muotoiltua tulostusta eri tilanteisiin. Käsittelemme tässä kaksi tapausta.
Funktio printf¶
Funktio printf tulostaa merkkijonon valitulle oheislaitteelle sen tulostusvuohon. printf:n syntaksi on seuraava:
printf(muotoilumerkkijono, argumentti1, argumentti2, ...);
Muotoilumerkkijono kertoo meille, mitä tulostetaan ja miten argumentit muokataan tulostukseen. Muotoilun perässä tulevat sitten argumentit joiden arvot tulostetaan osana merkkijonoa, jotka voivat olla esimerkiksi muuttujien arvoja. Tulostus toki usein koostuukin pelkästä muotoilumerkkijonosta, joskun emme halua tulostaa mitään muuttujia.
Katsotaanpa esimerkkiä printf:n käytössä..
uint8_t a=97;
printf("Luku on %d ja plus yksi on %d\n", a, a+1);
// Tulostaa näytölle
Luku on 97 ja plus yksi on 98 (+rivinvaihto)
printf:n ensimmäinen argumentti on siis muotoilu
"Luku on %d ja plus yksi on %d\n"
. Tämä tulostaa annetut merkit kuten ne on merkkijonoon laitettu, paitsi kun %
-merkillä kerromme argumentin paikan ja muotoilumääreen. Nyt meillä funktiokutsussa on kaksi argumentti a
ja a+1
, joiden paikan tulostuksessa ja muotoilun kerromme määreillä %d
. printf:lle näitä muotoilumääreitä on C-kielessä koko joukko, tässä alla yleisimmät.
(Kuvassa virhe, toinen double tarkoittaa float-tyyppiä.)
uint8_t a=97;
printf("Kirjain on %c\n", a);
printf("Heksaluku on %x\n", a);
printf("Liukukuku on %f\n", (float)a); // Tyyppimuunnos
// Tulostaa näytölle
Kirjain on a // Hox! ASCII-merkistö
Heksaluku on 61
Liukuluku on 97.000000
Tulostaessa tulee huomata, että printf itseasiassa tekee tarvittaessa muunnoksen argumentin muuttujatyypistä muotoilun muuttujatyyppiin! Tällöin esimerkiksi yllä esitelty muuttuja
int a=97
voidaan tulostaa merkkinä määreellä %c
. (Muistamme tässäkohdin ASCII-taulukon.) Jotta asiaan saadaan mutkia matkaan, niin printf-muunnos tekee pyöristyksen muuttujatyypin ja muotoilun välillä, eikä käytä sitä kuuluisaa giljotiinia. Muotoilua voidaan myös tarkentaa..
Esimerkiksi.
float pii = 3.14159;
printf("Pii on kahdella desimaalilla %.2f\n", pii);
// Tulostaa
Pii on kahdella desimaalilla 3.14 ( + rivinvaihto)
Muotoilumääreessä on vielä yksi asia, nimittäin lopussa oleva merkkivakio
\n
, joka tarkoittaa rivinvaihtoa. Eli kääntäjä asettaa tulosteeseen tälle kohdin rivinvaihdon. Merkkivakiot voivat toki olla missä kohti tulostusta hyvänsä. Alla lisää merkkivakioita (näistäkään emme nyt tarjoa täydellistä esitystä).Huomataan!, että kääntäjä joskus valittaa funktioiden argumenttien vääristä tyypeistä kun käytämme johdettuja muuttujatyyppejä. Tämän korostuu printf:n ja scanf:n kanssa koska ne ovat pääosin työasemaohjelmointiin kehitettyjä funktiota. Voimme poistaa näitä varoituksia esim. kappaleen 4. materiaalissa esitetyllä tyyppimuunnoksella.
Sulautetulla laitteella ohjelmoidessa joudumme vain elämään näiden varoitusten kanssa, mutta kuten sanottu, niin siellä ei esim. standardikirjaston printf-funktiolle ole juuri käyttöä, vaan laitteissa on omat toteutukset tulostusfunktioista eri oheislaitteille, esimerkiksi omansa sarjaporttiin ja omansa LCD-näytölle.
Funktio sprintf¶
Alussa lupasimme käsitellä kaksi eri printf-funktiota, joten esittelemme niistä nyt toisen, eli
sprintf
-funktion. Se toimii muuten samoin kuin printf, mutta siinä on yksi lisäargumentti, eli merkkijono johon tulostus menee. Ideana on siis muotoilla tekstiä toiseen merkkijonoon!sprintf(tulosmerkkijono, muotoilumerkkijono, argumentti1, argumentti2, ...);
Tälle funktiolle on oma tärkeä käyttönsä sulautetuissa järjestelmissä, esimerkiksi kun tulostetaan LCD-näytölle tekstiä tai lähetetään langattomalla radiolla viestiä toiselle koneelle. Usein valmiiden ohjelmakirjastojen funktiot, esimerkiksi viestin lähetykseen, ottavat argumentiksi valmiin käsitellyn merkkijonon, eikä laitteelle voi tulostaa suoraan printf:llä syntaksilla. Tällöin sprintf:llä ja muotoilumääreillä on helppo tehdä juuri speksien mukainen viesti merkkijonoon joka sitten lähetetään oheislaitteelle. Kurssin harjoitustyössä tullaan tarvitsemaan sprintf-funktiota ihan varmasti.
sprintf toimii seuraavasti:
uint8_t a=97;
char str[80]; // Tässä täytyy merkkijonolla olla ehdottomasti määritelty pituus
sprintf(str,"Muuttuja a on samaan aikaan %04d ja '%c', molemmat ovat %d%% oikein\n",a,a,100);
// ja sitten voidaan tulostaa laadittu merkkijono
printf(str);
Nyt merkkijono-taulukko
str
:n tallentui muotoilumääreen mukainen tulostus! Nyt str voidaan antaa argumentiksi toiselle funktiolle. Muotoilumääreiden kanssa täytyy ohjelmoijan huomioida merkkijono-taulukon pituus, jottä esimerkiksi erikokoiset muuttujien numeeriset arvot mahtuvat siihen. Muuten seurauksena on muistin ylivuoto. Esimerkiksi sulautetun laitteen LCD-näytön leveys voi olla 16 merkkiä ja tulostus pitää sovittaa siihen.
Hox! Merkkijonon ylikirjoittaminen on hyvin yleinen virhe, jonka seurauksena tapahtuu sulateutussa laitteessa outoa, kun saatamme kirjoittaa toisen muuttujan päälle muistiin. Tällainen virhe voi olla hankala selvitettävä, kun virheellinen toiminta ei suoraan näyttäisi liittyvän tulostukseen..
Vakiosyöte¶
Asiasta toiseen, eli stdio-kirjaston funktioilla voimme myös kysyä näppäimistösyötettä käyttäjältä (tai itseasiassa muultakin oheislaitteelta). Tässä funktio
getchar
tarjoaa keinon kysyä käyttäjältä syötettä merkki kerrallaan. Laiteläheisessä ohjelmoinnissa hyvin harvoin, jos koskaan, käytetään scanf-funktiota, joten tämä materiaali vain tiedoksi..Esimerkki. Seuraavalla do-while-rakenteella voimme kysyä käyttäjältä merkkejä, jotka tallentuvat muuttujaan
c
, kunnes käyttäjä painaa Enter-näppäintä (suom. palautusnäppäin..).uint8_t c = 0;
do {
c = getchar();
} while (c != '\n');
..eli on
do-while
-rakenteella ne kätevät puolensakin. Jos haluaisimme talteen koko syötteen, pitäisi se tallentaa merkkijonoon yksittäisen merkkijonon sijasta. Huomataan, että kun työasemalla testaamme ohjelmaa, näppäimistö kaiuttaa näppäinpainallukset ruudulle näkyviin. Noh, tämä on käyttöjärjestelmän ominaisuus ja sen kanssa joudumme elämään.
Muotoiltu syöte¶
Funktio
scanf
on standardikirjaston funktio, jolla voidaan kysyä käyttäjältä muotoiltua syötettä ja tallentaa se muuttujiin halutussa muodossa. Funktion esittely on samankaltainen kuin printf:n, pienillä, mutta oleellisilla eroilla.scanf(muotoilumerkkijono, &muuttuja1, &muuttuja2, ...);
Muotoilumerkkijonon syntaksi on ihan sama kuin printf:n, eli käytössä on samat muotoilumääreet. Argumentissa annettavien muuttujien nimen eteen tarvitaan
&
-operaattori. Tästä seuraa, että funktion argumentit eivät olekaan muuttujia, vaan niiden osoitteita! Ok, tästä lisää osoittimien yhteydessä, nyt riittää että tiedostamme tämän seikan. Esimerkiksi, tässä kysytään uint32_t-tyyppinen arvo käyttäjältä ja tulostetaan se takaisin ruudulle. Ja joo, scanf edellyttää että syöte lopetetaan Enter:n painalluksella.
uint32_t iso=0;
scanf("%ld",&iso); // Huomaa %ld-määre ja &-operaattori muuttujanimen edessä.
printf("%ld\n",iso);
scanf on paljon monikäyttöisempi kuin pelkän yhden numeron kysely, sillä voidaan kysyä pitempiä muotoiltuja syötteitäkin!
uint8_t paiva=0;
char kuukausi[12];
uint16_t vuosi=0;
printf("Anna paivamaara muodossa: 24.Joulukuuta 2017? ");
scanf("%2d.%s %4d", &paiva, kuukausi, &vuosi); // Huomaa &-operaattori, paitsi kuukausi-muuttujassa!
printf("Annoit %d.%s %d\n", paiva, kuukausi, vuosi);
Nyt muotoilumerkkijono
%2d.%s %4d
on huomattavasti tarkempi, se ottaa vastaan max. kaksinumeroisen kokonaisluvun muuttujaan paiva
, sen jälkeeen se odottaa pistettä ja sen jälkeen merkkijonoa muuttujaan kuukausi
. Ja lopuksi scanf odottaa välilyönnin jälkeen max. nelinumeroista lukua muuttujaan vuosi
. Eli muotoilumerkkijono tulkitaan todellakin valitettavan kirjaimellisesti. Argumenttien tyypeissäkin on nyt eroja, merkkijono-muuttujien kanssa ei tarvitse &-operaattoria. Miksi ei? ..maltetaan vielä hetki niin asia selviää osoitin-muuttujamateriaalissa. Tässä scanf:n muotoilu on kuitenkin helppo rikkoa, jos vaikka annetaan syötteeseen päivämääräksi kolminumeroisen luvun, tallentuu paiva-muuttujaan vain sen kaksi ensimmäistä numeroa, jolloin kolmas numero jää syötteeseen ja sotkee seuraavan argumentin arvon. Samoiten, jos piste jää pois syötteestä, scanf menee sekaisin. Kokeile!
Vähemmän yllättäen, vastaavasti sprintf-funktiota, syötteen merkkijonoon tallennukseen löytyy funktio
sscanf
. Nyt ohjelmassa käyttäjän syöte kerättään ensin talteen merkkijonoon, jolloin sitä voidaan käsitellä merkkijonona. Esimerkiksi voidaan etsiä annetusta syötteestä tiettyjä sanoja.Hox! Todella yleinen virhe on unohtaa
&
-operaattori scanf:n argumenttiluettelosta. Tätä ei joskus edes kääntäjä huomaa, mutta ohjelman suorituksessa asia huomataan kyllä.. Lopuksi¶
printf:n muotoilun salaisuuksista kiinnostuneille lisätietoa löytyy mm. dokumentista Secrets of printf.
Anna palautetta
Kommentteja materiaalista?