PR1ET13: Sõned

Praktikumi materjal

Esitamisele kuuluvad ülesanded

Selles tunnis on kaks ülesannet, millest esimeses õpime tundma string.h teeki ning teises manipuleerime tähemärke käsitsi.

Ülesanne 1 [W13-1]: Tutvume string.h teegiga

Selle ülesande käigus lood samm-sammu haaval programmi, mille käigus tutvud peamiselt erinevate string.h teegis olevate standardfunktsioonidega.

Nõuded
  • Programmi koostamiseks liigu samm-sammult juhendis antud järjekorras.
  • Programmi käivitades küsitakse kasutajalt parooli. Enne õige parooli sisestamist ei tohi programm edasi minna.
  • Kasutajalt loetakse lause. Programm kuvab, mitu tähemärki lauses oli (sh tühikud, kirjavahemärgid).
  • Kasutajalt loetakse otsingufraas. Seejärel väljastatakse, kas eelnevalt sisestatud lauses see fraas esines või mite (jah/ei vastus).
  • Kasutajalt küsitakse kaks sõna, mida kasutatakse lause moodustamisel.
    • Sõnade tüübid võid ise valida (nt nimi, ese, omadussõna, tegusõna, …)
    • Kleebi kasutaja poolt sisestatud sõnad kokku, lisades enda poolt täiendavaid sõnu, moodustamaks vähemalt neljast sõnast koosneva lihtlause.
    • Üks kasutaja sisestatud sõnadest peab olema selle lauses esimene sõna.
    • Moodustatav lause tuleb salvestada täiesti uude tähemärgimassiivi, mis peab mahutama selle lause ka siis, kui loetud 2 sõna olid maksimaalse lubatud pikkusega.
  • Kogu ülesande vältel tohid sisestuse lugemiseks ja töötlemiseks kasutada vaid ohutuid funktsioone! St funktsioone, mis piiritlevad mitu tähemärki tohib maksimaalselt lugeda.
Abifunktsioon

Selleks, et paremini aru saada mis tähemärgid parasjagu tekstimassiivi sees paiknevad, pakun välja abifunktsiooni. Funktsioon trükib välja tervikliku sõne, misjärel trükitakse kõik tähemärgid ja selle vastavad ASCII tabeli täisarvulised väärtused. Nii on lihtsam tuvastada, kui näiteks mõni reavahetus või muu ootamatu sümbol jääb massiivi sisse.

Juhend
1. samm: kasutaja sisestuse lugemine

Esimese sammu teeme tunnis koos läbi! Selle käigus loome kaks vajalikku funktsiooni.

Alustame esimesest, mida kasutame sõne lugemiseks. Lahenduses on oluline, et suudaksime lugeda mitmest sõnadest koosnevaid sõnesid – st tühik ei tohi lugemist ära lõpetada. Selleks on mitmeid viise, kuid meie läheneme ülesandele kasutades funktsiooni fgets() . Soovi korral võid ise teist teed minna.

Funktsiooni fgets()  eripäradeks on, et ta on mõeldud lugemaks failist. Küll aga kõik asjad on failid, sh ka klaviatuurilt tulev andmevoog, seega saame kasutada failina stdin  nimelist faili. Teine keerukus antud funktsiooni juures on piirang mitu tähemärki lugeda tohib – see on ohutuse tagamiseks, et puhvri pikkusest üle ei mindaks. Kolmandaks keerukuse koht on reavahetus, mis tekib klaviatuurilt enter klahvi vajutamisel – ka sümbol \n  salvestatakse  massiivi.

Lähenemises kasutame põhimõtet, et loome funktsioonidele wrapperid ehk ümbritseme need täiendavate lausetega, mis muudavad funktsiooni kasutamise mugavamaks ja turvalisemaks.

Loodaval ümbrisel on vaja kahte sisendit – sõnet ehk tähemärgimassiivi, kuhu sisend salvestatakse ning selle pikkust, et vältida puhvri ületäitumise rünnakuid.

Lahenduses oleme jätnud kolme kohta küsimärgid sisse. Sinu esimeseks ülesandeks on täita lüngad. Vihjeks: kui loetud sõne pikkus on 10 tähemärki, siis indeksiga 8 paikneb viimane oluline tähemärk, mille kasutaja sisestas. Sellele järgneb reavahetuse tähemärk, millest tuleb lahti saada, asendades selle sõne lõpu sümboliga. Vajadusel kasuta varasemalt välja toodud abifunktsiooni sisendi analüüsimiseks.

size_t  on andmetüüp, mida kasutatakse pikkuste hoiustamiseks, massiivide indekseerimiseks ja loendamiseks. Tegelikkuses on see lihtsalt märgita täisarv.
Kui lugemisfunktsioon on valmis, loome järgmise ümbrise oma vastloodud GetString()  funktsioonile. Nii saame lisada ka sisestuse päringu.
Pane tähele, et mõlemaid funktsioone on võimalik kasutada – GetString()  kasuta siis kui päringuteksti pole vaja ning PromptString ()  ku soovid esmalt päringu esitada.

Oletame, et meil on olemas tähemärgimassiiv sentence[] , mille pikkus on defineeritud makroga MAX_STR . Sellisel juhul võib väljakutset luua kahte moodi, olenevalt kas sentence  on deklareeritud samas funktsioonis või mitte.

2. samm: lause lugemine ja selle pikkus

Loe kasutajalt lause. Seejärel leia ja väljasta sisestatud lause pikkus kasutades standardfunktsiooni string.h  teegist.

Lahenduseks vajalikud teadmised

Lahenduseks kasuta funktsiooni strlen() , tagastab mitu tähemärki funktsiooni parameetrina edastatud sõnes paikneb. Dokumentatsioon: https://en.cppreference.com/c/string/byte/strlen

Parameetriks sõne, mille pikkust soovid leida ( const char* str ). Tagastab tähemärkide arvu märgita täisarvuna ( size_t ).

Näiteks järgnev koodilõik väljastab numbri 8 kuna “Hello!!!” sisaldab 8 tähemärki

3. samm: fraasi otsimine

Lisa programmi funktsioon, milles küsitakse kasutajalt otsingufraas. Programm väljastab, kas otsitav fraas eksisteeris varasemalt sisestatud lauses või mitte. Lahenduseks piisab jah/ei vastuseks,

Lahenduseks vajalikud teadmised

Lahenduseks kasuta funktsiooni strstr() , mis otsib sõne seest alamosa, piltlikult öeldes nõela heinakuhjast. Dokumentatsioon: https://en.cppreference.com/c/string/byte/strstr

Parameetriteks on nõel ( const char* str ) ja heinakuhi ( const char* substr ). Tagastatakse asukoht ehk mäluaadress ( char* ) kust nõel leiti.

Järgnevas koodipõhjas on kasutusel  makro NULL , mis tähistab objekti või mäluaadressi, mida ei eksisteeri (kutsume null-viidaks, pikemalt tulevikus). See on vajalik kuna funktsioon strstr()  ei tagasta mitte jah/ei vastust, vaid ütleb asukoha (mäluaadressi), kus kohas leitud sõne paikneb. Kui otsitavat sõne ei leita, tagastatakse NULL .

4. samm: parooli küsimine

Lisa programmi funktsioon, mis küsib kasutajalt parooli ning ei luba kasutajat programmiga jätkata enne, kui korrektne parool on sisestatud. Parooli kontroll peab olema tõstutundlik (st suured ja väiketähed on erinevad).

Soovi korral võid lisada parooli sisestamise katsete arvu ning katsete täitumisel programmi sulgeda. Samuti võid lisada näiteks parooli vihje, mida kuvatakse vale parooli sisestamisel.

NB! Tänases tunnis on lähtekoodi sisse kirjutatud krüpteerimata parool OK, pärissüsteemis ära kunagi nii tee.

Lahenduseks vajalikud teadmised

Lahenduseks kasuta funktsiooni strcmp()  , mis võrdleb sõnesid tähemärkhaaval. Sel korral loe parameetrite ja tagastuse kohta dokumentatsioonist: https://en.cppreference.com/c/string/byte/strcmp

5. samm: lause moodustamine

Lisa programmi funktsioon, mille käigus moodustad lihtlause.  Lihtlause struktuur on sinu otsustada, kuid selles peab paiknema kaks lünka, mille sisu tuleb kasutajalt küsida. Lünkadesse käivate sõnade tähenduse või liigi otsustad ise, vastavalt millist lauset soovid moodustada (nt inimese nimi, nimisõna, tegusõna, …). Üks neist kahest sõnast peab paiknema lause alguses, teise asukoht on sinu otsustada. Loodava lause pikkus on minimaalselt 4 sõna. Näiteks <sõna1> on <sõna2> nimi! .

Olles kasutajalt sõnad pärinud ja programmi sisse lugenud tuleb sul need sõnad lauseks kokku kleepida. Kokku kleebitav lause peab olema salvestatud täiesti uude tühja tähemärgimassiivi.

Funktsiooni lahendamiseks pead

  1. Looma kaks sõne tüüpi muutujat, kuhu loed kasutaja sisestatud sõned
  2. Looma kolmanda sõne, kuhu salvestad moodustatava lause (allolevas näites muutuja sentence )
  3. Kleepima kokku lause ja selle salvestama muutujasse  sentence
  4. Väljastama moodustatud lause

Kuna funktsioonile meil head sisendit ega tagastust anda ei ole, võiksid alustada funktsiooni sedasi (void-void funktsioonid on erandlikud ja enamasti tasub neid vältida!):

Mõtle hoolikalt läbi sõnede pikkused! Massivi sentence  pikkus peab olema piisav, et mahutada mõlemad kasutaja sisestatavad sõnad ning lause moodustamisel sinu poolt lisatud sõnad, tühikud ja kirjavahemärgid. Suurus võib olla liigkaudne aga peab olema piisav! See on eriti oluline, kui kasutaja otsustab sisestada maksimaalse võimaliku pikkusega sõnad!

Lahenduseks vajalikud teadmised

Selle funktsiooni lahendamiseks võid kasutada erinevaid lähenemisi.

Sisendi lugemisel ära unusta kasutada ohutuid lugemisfunktsioone.

Lause kokku kleepimisel võiksid olla huvipakkuvateks funktsioonideks strncpy() , strncat() ja snprintf() . Kõiki neist korraga pole mõistlik kasutada, olenevalt lahendusest peaksid piirduma ühe-kahe eelnimetatud funktsiooniga.

Programmi lõpptulemus

Ülesanne 2 [W13-2]: CSV-st meiliaadresside genereerimine

Selle ülesande eesmärk on sulle tutvustada laialtlevinud andmeformaati CSV (comma separated value). Ülesande lahendamise käigus saad harjutada üksikute tähemärkide tuvastamist ja töötlemist.

Lae alla ülesande aluskood: 13_2_csv_starter.c

CSV formaat

CSV on struktuursete andmete hoiustamise formaat, kus iga andmeväli on eraldatud eelnevast ja järgnevast komaga. Tegu on tõenäoliselt kõige levinuma andmete varundamiseks ja hoiustamiseks kasutatava formaadiga väljaspool andmebaasisüsteeme. Tema peamisteks eelisteks on lihtne struktuur ning sellest tingitult on CSV toetatud praktiliselt kõigis rakendustes, mis vähegi andmetega töötlevad.

Kõige lihtsamal kujul nagu öeldud on kõik väljad eraldatud üksteisest komaga. Näiteks:

Täpselt sellise keerukusega andmeid vaatame ka selles tunnitöös. Nägemaks keerulisemaid formaate ja reegleid, kuidas hoiustada väljasid, mis peavad sisaldama komasid, jutumärke ning kuidas lisada pealkirju, võid lugeda siit: https://en.wikipedia.org/wiki/Comma-separated_values#Basic_rules. 

Nõuded
  • Ülesande lahendus on ehitatud antud aluskoodile
  • Programm loob igale aluskoodis antud inimesele e-postiaadressi.
    • e-postiaadressi nimeosa koosneb kolmest eesnimetähest ja kolmest perenimetähest.
    • Nimeosale järgneb sinu valitud domeen.
    • E-postiaadressi nimi ja domeen peavad koosnema vaid väiketähtedest.
    • E-postiaadress tuleb salvestada terviklikult tekstimassiivi (loo uus muutuja seal, kus vaja) ja väljastada selle kaudu. Jooksvalt tähemärkhaaval väljastus pole lubatud.
  • Programm väljastab:
    • Inimese täisnime. Eesnime ja perenime osad peavad olema eraldatud tühikuga
    • Genereeritud e-postiaadressi.
  • Aluskoodis juba olevat koodi ülesande lahendamiseks muuta ei tohi ilma juhendaja poolse nõusolekuta. Sinu poolt kirjutatava lahenduse alguspunkt peaks asuma  ProcessPerson()   funktsiooni sees. Soovi korral võid julgelt funktsioone juurde lisada.
Näide
Vihjed
  • Teades koma asukohta, tead ka mis indeksilt algab perenime esimene täht
  • ASCII suur- ja väiketäht erinevad üksteisest ühe biti võtta, mille järgu väärtuseks on 32 (nt A 65, a 97)
  • Kõik toimingud peale e-mailiaadressi lõpu lisamise on kõige lihtsam teha selles ülesandes tähemärk-haaval. Saab ka kasutada string.h  teegi funktsioone, kuid need võivad olla asjatult keerukad.
  • Kõige tüüpilisem viga selles ülesandes on unustada sõnele null-baidi ehk terminaatori lõppu lisamist pärast nimeosa koostamist!

Lisaülesanne 1 [W13-3]: Tähtede loendus

Esimene lisaülesanne laiendab esimest praktilist ülesannet. Selleks loo käsitsi uus loendamise funktsioon ning väljasta statistika

  • Loenda ja kuva, mitu tähte [a-zA-Z] oli lauses. Ära loenda kirjavahemärke, tühikuid jne.
  • Leia ja kuva, mitu protsenti kogu lausest moodustasid tühikud, kirjavahemärgid ja muud sümbolid.
  • Näita protsent ühe komakohaga.

Näide

Lisaülesanne 2 [W13-4]: CSV laiendus

Teine lisaülesanne on laiendus teisele praktilisele ülesandele. Lisaülesande lahendamiseks täienda rakendust sedasi, et su lahendus tuleks toime ka täiendava andmevälja ja lühemate nimedega.

  • Võta kasutusele laiendatud andmestik
  • Muuda algoritmi sedasi, et see töötaks ka nimedega, mis sisaldavad vaid 1 või 2 tähemärki.
  • Kuva linna nimi

Andmestik

Pärast tundi peaksid

  • Teadma, et tähemärkide jaoks kasutatakse erinevaid kodeeringuid, muuhulgas ASCII ja Unicode
  • Teadma, mis on ASCII tabel ning kuidas seda kasutada.
  • Teadma, kuidas töötavad sõned C keeles.
  • Teadma, kuidas lõpetatakse sõnet C keeles (null-terminaator/null-bait).
  • Seostama C keelseid sõnesid baidijadadega.
  • Teadma, mis on CSV.
  • Oskama kasutada string.h teeki sõnede manipuleerimiseks.
  • Oskama ka ise kirjutada sõnede manipulatsioone (tähemärkhaaval lähenemine).
  • Teadma, mis asi on puhvri ületäitumine ning selle kaudu tehtavatest rünnakutest.

Täiendav materjal