Praktikumi materjal
- Slaidid: Sõned
- Näide: mõningad lihtsamad tähemärkide omistamised
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
void DebugString(char str[]) { printf("String is: '%s'\n", str); int i = 0; while (str[i] != '\0') { printf("str[%d] = %3hhu %c\n", i, str[i], str[i]); i++; } printf("\n"); } |
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.|
1 2 3 4 5 6 7 8 9 10 11 |
void GetString(char str[], int max) { // Read the string from keyboard fgets(str, max, stdin); // TODO: Find the length of the actual string we just read // size_t len = ???; // TODO: Write the string terminator in place of the newline to fix the string // str[ ??? ] = ???; } |
|
1 2 3 4 5 |
void PromptString(char str[], int max, char prompt[]) { printf("%s: ", prompt); GetString(str, max); } |
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.
|
1 2 3 4 5 |
// Generic version (sentence declared somewhere else or it's not a local array) PromptString(sentence, STR_MAX, "Please enter a sentence"); // If sentence is declared in the same function as this function call PromptString(sentence, sizeof(sentence), "Please enter a sentence"); |
2. samm: lause lugemine ja selle pikkus
Loe kasutajalt lause. Seejärel leia ja väljasta sisestatud lause pikkus kasutades standardfunktsiooni string.h teegist.
|
1 2 |
Enter a sentence: I do wish we could chat longer, but I'm having an old friend for dinner. The length of the entered sentence is 72 |
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,
|
1 2 3 4 5 |
Enter a sentence: I do wish we could chat longer, but I'm having an old friend for dinner. The length of the entered sentence is 72 Please enter a search phrase: old friend Your search phrase "old friend" exists in the originally entered sentence |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Please enter a password: password Invalid password! Try again! Please enter a password: again Invalid password! Try again! Please enter a password: hunter2 Password accepted. Welcome AzureDiamond! Enter a sentence: I do wish we could chat longer, but I'm having an old friend for dinner. The length of the entered sentence is 72 Please enter a search phrase: old friend Your search phrase "old friend" exists in the originally entered sentence |
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
- Looma kaks sõne tüüpi muutujat, kuhu loed kasutaja sisestatud sõned
- Looma kolmanda sõne, kuhu salvestad moodustatava lause (allolevas näites muutuja sentence )
- Kleepima kokku lause ja selle salvestama muutujasse sentence
- 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!):
|
1 2 3 4 5 6 7 8 9 10 11 12 |
void FormulateSentence(void) { // String where the final sentence will be held char sentence[ ??? ]; // Print the final formulated sentence printf("Result: %s\n", sentence); } |
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!
Programmi lõpptulemus
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Please enter a password: password Invalid password! Try again! Please enter a password: again Invalid password! Try again! Please enter a password: hunter2 Password accepted. Welcome AzureDiamond! Enter a sentence: I do wish we could chat longer, but I'm having an old friend for dinner. The length of the entered sentence is 72 Please enter a search phrase: old friend Your search phrase "old friend" exists in the originally entered sentence Enter a name: Pauline Enter an adjective: awesome Result: Pauline is an awesome person! |
Ü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:
|
1 2 |
Mari,Maasikas,112222IACB,49001013333 Toomas,Toomingas,111111MVEB,39002204444 |
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Number of CSV lines: 3 Processing line: 'Maria,Kask' Name: Maria Kask E-mail: markas@ttu.ee Processing line: 'Johanna-Maria,Kask' Name: Johanna-Maria Kask E-mail: johkas@ttu.ee Processing line: 'Kalev Kristjan,Kuusk' Name: Kalev Kristjan Kuusk E-mail: kalkuu@ttu.ee |
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
|
1 2 3 4 |
Sentence entered: Hi, Bob! Sentence length: 8 Alphabetical characters: 5 Percentage of other characters: 37,5% |
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
|
1 2 3 4 5 6 7 8 |
const char *data[] = {"Maria,Kask,Tallinn", "Johanna-Maria,Kask,Tartu", "Kalev,Kristjan,Kuusk,Tartu", "Margit,Kasemets,Narva", "Ly,Kase,Tapa", "Marko,Au,Loobu", "Al,Au,Viljandi" }; |
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
- Characters, Symbols and the Unicode Miracle – Computerphile
https://www.youtube.com/watch?v=MijmeoH9LT4 - A beginners guide away from scanf
https://www.sekrit.de/webdocs/c/beginners-guide-away-from-scanf.html - ASCII
https://en.wikipedia.org/wiki/ASCII - ASCII table
https://www.rapidtables.com/code/text/ascii-table.html - Character encoding
https://en.wikipedia.org/wiki/Character_encoding - String.h library
https://www.cplusplus.com/reference/cstring/ - Strings in C
https://www.geeksforgeeks.org/strings-in-c-2/ - CSV
https://en.wikipedia.org/wiki/Comma-separated_values