58127-1 C-ohjelmointi : 5 Merkkiliteraalit ja merkkijonot
- 5.1 Merkkijonojen vertaaminen ja pituus
- 5.2 Merkkijonojen kopiointi
- 5.3 Merkkijonojen luonti
- 5.4 Merkkijonojen yhdistäminen
- 5.5 Esimerkki: Komentoriviparametrien listaus
- 5.6 Tekstin etsiminen merkkijonosta
- 5.7 Merkkijonojen jakaminen osiin
- 5.8 Merkkijonojen konvertointi
- 5.9 Merkkien luokittelu
5 Merkkiliteraalit ja merkkijonot
Tyyppiä char olevan suureen mahdolliset arvot ovat yksittäisiä merkkejä. Merkki voi olla esim. kirjain, numero, erikoismerkki taikka sellainen kontrollimerkki, jolla ei ole näkyvää esitysasua, esim. rivinvaihtomerkki. Teknisesti char on kokonaislukutyyppi, joka vastaa käytettävän tietokoneen tavun (byte) käsitettä. Standaridikirjastossa ctype.h on hyödyllisiä funktioita, joiden argumentti on merkkityyppinen. Ne saadaan käyttämällä direktiiviä #include<ctype.h>. Standardin mukaan ovat char-tyypin ohella käytettävissä tyypit unsigned char ja signed char, jotka vastaavat etumerkitöntä ja etumerkillistä tulkintaa. Tyyppi char on sama kuin jompikumpi em. tyypeistä. Merkit esitetään literaaleina:
- Kirjoittamalla merkki heittomerkkeihin, esim. '0'; tätä esitystä
voidaan käyttää kaikille kirjoittuville (printable) merkeille paitsi
heittomerkille itselleen ja kenoviivalle.
- Muodossa '\n', missä n on merkin koodiarvo oktaalilukuna, esim. '\014'.
- Muodossa '\xn', missä n on merkin koodiarvo hexsadesimaalilukuna,
esim. '\x12'.
- Eräissä tapauksissa muodossa '\x', missä x on kirjain tai erikoismerkki:
- '\n' = rivinvaihtomerkki
- '\t' = tabulaattori
- '\v' = pystysuora tabulointi
- '\b' = peruutusmerkki
- '\r' = rivinalkuunpalautusmerkki
- '\f' = sivunvaihtomerkki
- '\a' = hälytysmerkki, yleensä äänimerkki
- '\\' = kenoviiva
- '\'' = heittomerkki
- '\"' = lainausmerkki
NULL-merkki eli '\0'-merkki, josta merkkijonon loppu on tunnistettavissa. Tästä seuraa, että n:n merkin mittaisen merkkijonoliteraalin sisäinen esitys vie muistissa n+1 tavua.
Merkkijono C:ssä on taulukko jonka elementit ovat tyyppiä char. Merkkijonotyyppiä ei ole. Viimeinen merkki pitää olla NULL-merkki, joka kertoo missä merkkijono päättyy. Lähes kaikki merkkijonoja käsittelevät funktiot olettavat tämän merkin olemassaolon. Siksi taulukon on aina oltava yksi merkki isompi kuin sinne haluttu teksti. Määritellään taulukkoja:
char Teksti[] = { 'H', 'e', 'l', 'l', 'o', '\0' }; char Aine[3]; int Numeroita[5] = {0}; Aine[0] = 'A': Aine[1] = 'u': Aine[2] = '\0':´Taulukko voidaan myös kätevämmin alustaa seuraavilla tavoilla:
char Teksti [] = "Hello"; char Aine [3] = "Au";Ensimmäisessä tapauksessa kääntäjä varaa tarvittavan muistin (teksti + nolla) toisessa tapauksessa meidän on itse huolehdittava että taulukko on tarpeeksi iso. Kääntäjä varoittaa tavallisesti jos taulukko on liian pieni, mutta ei jos nolla ei mahdu.
5.1 Merkkijonojen vertaaminen ja pituus
- Merkkijonojen pituus pitää erikseen laskea merkeittäin, esim:
#include<stdio.h> int pituus(char *jono) { int koko = 0; /* Läpikäy merkkijonoa kunnes NULL-merkki havaitaan */ for(koko=0;jono[koko] != '\0'; koko++); return koko; } int main(void) { char MerkkiJono[] = "Paljonmerkkejäperäkkäin"; int koko; koko = pituus(MerkkiJono); /* Laske koko */ printf("Merkkijonon pituus on %d merkkiä\n",koko); return 0; }
- merkkijonon pituus saadaan myös funktiolla strlen(char *Merkkijono)
- palauttaa merkkien määrän, ei laske mukaan nollaa
- liitä mukaan otsikkotiedosto <string.h>
- merkkijonoja ei voi verrata normaalisti operaattorilla ==
- vertaa osoittimien arvoa
- vertaaminen pitää suorittaa merkeittäin, esim:
#include<stdio.h> #include<string.h> int vertaa(char *jono, char *jono2) { int i; /* Jos merkkijonot erimittaisia, eivät ole samoja */ if ( strlen(jono) != strlen(jono2)) return 0; for(i=0; jono[i] != '\0';i++) { /* Jos merkit eroavat, eivät ole samoja */ if ( jono[i] != jono2[i] ) return 0; } /* Jos tänne asti päästiin, niin merkit ovat samoja */ return 1; } int main(void) { char MerkkijonoA[] = "Suomi"; char MerkkijonoB[] = "suomi"; if ( vertaa(MerkkijonoA,MerkkijonoB) ) printf("Merkkijonot ovat samoja\n"); else printf("Merkkijonot eivät ole samoja\n"); }
- funktio strcmp(char *S1, char *S2) hoitaa saman asian
- palauttaa kolme eri arvoa
- 0 jos merkkijonot ovat identtisiä
- negatiivisen arvo jos S1 < S2
- positiivisen arvo jos S1 > S2
- löytyy myöskin otsikkotiedostosta <string.h>
- palauttaa kolme eri arvoa
- funktio strncmp(char *S1, char *S2, size_t Koko) vertaa vain Koko merkkiä
5.2 Merkkijonojen kopiointi
- kopioinnin voi suorittaa merkeittäin
- helpompaa on käyttää funktiota strcpy(char *To, char *From)
- kopioi koko merkkijonon From merkkijonoon To
- kopioi myös nollan
- ei varaa muistia, joten merkkijonon To pituus on oltava vähintään
strlen(From) + 1 pitkä!
- virheitä helppo tehdä
- funktio strncpy(char *To, char *From, size_t Koko)
kopioi vain Koko merkkiä
#include <stdio.h> #include <string.h> int main(void) { char Alkuperainen[] = "Tekstiä"; char Kopio[11]; char Lyhyt[3]; /* kopioi merkkijono */ strcpy( Kopio, Alkuperainen ); strncpy( Lyhyt, Alkuperainen, 2); printf( "Alkuperäinen: %s, kopio: %s\n", Alkuperainen, Kopio); printf( "Alkuperäinen: %s, lyhyt: %s\n", Alkuperainen, Lyhyt); return 0; }
5.3 Merkkijonojen luonti
C:ssä ei ole hyvää tapaa konvertoida esim. int tai float merkkijonoksi. Funktiolla sprintf() voidaan kuitenkin tulostaa merkkijonoon. Toimii aiva kuten normaali printf(), ainoa ero on että annetaan lisäparametri joka on merkkijono johon "tulostus" halutaan.
#include <stdio.h> #include <string.h> int main (void) { int Vuosi = 2000; char Tyyppi[] = "Auto"; char SQL[100]; /* luo SQL-lause */ sprintf ( SQL, "SELECT * FROM Tuotteet WHERE Tyyppi='%s' AND Vuosi=%d", Tyyppi, Vuosi ); /* tulosta syntynyt lause */ printf ( "%s\n", SQL ); return 0; }ohjelman ajo antaa tuloksen: SELECT * FROM Tuotteet WHERE Tyyppi='Auto' AND Vuosi=2000
- koko tulostuksen on mahduttava tulostettavaan merkkijonoon
- voi syntyä muistivirhe (segmentation fault)
- voi myös korruptoida muistia
- kääntäjä ei varoita vaikka merkkijono on liian lyhyt
5.4 Merkkijonojen yhdistäminen
Merkkijonoja ei voi suoraan yhdistää esim. operaattorilla +. Funktio strcat(char *Minne, char *Mista) hoitaa tehtävän. Parametri Minne on se merkkijono johon liitetään merkkijono Mista. Funktio löytyy otsikkotiedostosta <string.h>
- merkkijono johon "konkatenoidaan" on oltava tarpeeksi pitkä
- funktio strncat(char *Minne, char *Mista, size_t
Pituus) kopioi maksimissaan Pituus merkkiä
#include <stdio.h> #include <string.h> int main (void) { char ViestiJono[80]; /* kopioi ensimmäinen osa viestistä */ strcpy ( ViestiJono, "Maapallo" ); /* lisää loppu */ strcat ( ViestiJono, " on pyöreä" ); printf ( "Viesti: %s\n", ViestiJono ); return 0; }
5.5 Esimerkki: Komentoriviparametrien listaus
#include<stdio.h> #include<string.h> int main(int argc, char **argv,char **env) { unsigned int i; printf("Ohjelma sai %d argumenttia\n",argc-1); for( i = 0; i < argc ; i++ ) printf("Parametrin %d on %s ja sen pituus oli %d\n", i, argv[i], strlen(argv[i]) ); for ( i = 0; env[i] != 0; i++ ) printf("Ympäristömuuttujan %d arvo on %s\n",i,env[i]); return 0; }Ohjelman käynnistys komentorivillä : par eka : saa aikaan esimerkiksi seuraavanlaisen tulostuksen:
Ohjelma sai 1 argumenttia Parametrin 0 on par ja sen pituus oli 3 Parametrin 1 on eka ja sen pituus oli 3 Ympäristömuuttujan 0 arvo on ignoreeof=10 Ympäristömuuttujan 1 arvo on COLORTERM=rxvt Ympäristömuuttujan 2 arvo on LOGNAME=jplindst Ympäristömuuttujan 3 arvo on JIKESPATH=/usr/local/java/jre/lib/rt.jar Ympäristömuuttujan 4 arvo on BIBINPUTS=:/home/group/rodin/documents// Ympäristömuuttujan 5 arvo on VISUAL=ue Ympäristömuuttujan 6 arvo on MAIL=/var/spool/mail/jplindst Ympäristömuuttujan 7 arvo on PAGER=less Ympäristömuuttujan 8 arvo on WWW_HOME=http://www.cs.Helsinki.FI/ Ympäristömuuttujan 9 arvo on CDPATH=.:..:../.. Ympäristömuuttujan 10 arvo on CLASSPATH=. Ympäristömuuttujan 11 arvo on TERM=xterm Ympäristömuuttujan 12 arvo on HOSTTYPE=i386 Ympäristömuuttujan 13 arvo on PATH=/usr/local/ssh-bin:/usr/bin:/bin:/usr/local/bin:/usr/bin/X11:/usr/openwin/bin:/usr/andrew/bin:/usr/games:/home/jplindst/bin:.:/opt/cvs/bin:/home/group/saha/bin/:/opt/lyx-1.1.5fix1/bin/ Ympäristömuuttujan 14 arvo on HOME=/home/jplindst Ympäristömuuttujan 15 arvo on SHELL=/bin/bash Ympäristömuuttujan 16 arvo on XAUTHORITY=/home/jplindst/.Xauthority Ympäristömuuttujan 17 arvo on PS1=\u@\h:\w\$ Ympäristömuuttujan 18 arvo on PS2=> Ympäristömuuttujan 19 arvo on TEXINPUTS=:/home/group/rodin/documents/Styles// Ympäristömuuttujan 20 arvo on MANPATH=/usr/local/man:/usr/man:/usr/man/preformat:/usr/X11/man:/usr/openwin/man Ympäristömuuttujan 21 arvo on LESS=-MM Ympäristömuuttujan 22 arvo on LESSCHARSET=latin1 Ympäristömuuttujan 23 arvo on HOSTDISPLAY=vaskiluoto:0.0 Ympäristömuuttujan 24 arvo on JAVA_HOME=/usr/local/java Ympäristömuuttujan 25 arvo on DISPLAY=:0.0 Ympäristömuuttujan 26 arvo on OSTYPE=Linux Ympäristömuuttujan 27 arvo on WINDOWID=58720259 Ympäristömuuttujan 28 arvo on OPENWINHOME=/usr/openwin Ympäristömuuttujan 29 arvo on SHLVL=4 Ympäristömuuttujan 30 arvo on EDITOR=ue Ympäristömuuttujan 31 arvo on TZ=EET-2EET DST,M3.5.0/3,M10.5.0/4 Ympäristömuuttujan 32 arvo on CVSROOT=/home/group/rodin/Version1source Ympäristömuuttujan 33 arvo on _=./parYmpäristömuuttujat tulisi oikeastaan hakea esille getenv() nimisellä funktiolla. Palaamme tähän asiaan myöhemmin.
5.6 Tekstin etsiminen merkkijonoista
Merkkejä voi etsiä merkkijonoista funktiolla char *strchr(char * Teksti, int Merkki). Funktio palauttaa osoittimen ensimmäiseen löydettyyn merkkiin tai NULL jos ei löytynyt. Esimerkiksi:
#include <stdio.h> #include <string.h> int main (int argc, char **argv) { char *Paikka; if ( argc != 3 ) { printf ("Käyttö: %s teksti merkki\n", argv[0] ); exit ( 1 ); } /* löydä merkki */ Paikka = strchr ( argv[1], argv[2][0] ); /* löytyikö? */ if ( Paikka == NULL ) { printf ( "Merkkiä %c ei löytynyt merkkijonosta '%s'\n", argv[2][0], argv[1] ); } else { printf ( "Merkki %c löytyi merkkijonosta '%s'", argv[2][0], argv[1]); printf ( ", indeksi %d\n", Paikka - argv[1]); } return 0; }Funktio char *strrchr(char *Teksti, int Merkki) toimii kuten strchr(), mutta palauttaa osoittimen viimeiseen löydettyyn merkkiin. Funktio strstr(char *Teksti, char *Etsitty) palauttaa osoittimen paikkaan josta merkkijono Etsitty löytyy merkkijonosta Teksti tai palauttaa myös NULL jos ei löytynyt. Kaikki edellämainitut funktiot löytyvät otsikkotiedostosta <string.h>.
5.7 Merkkijonojen jakaminen osiin
Usein on tarve jakaa merkkijono osiin, esim. kun tehdään parseri. Funktiolla char *strtok(char *Teksti, char *Rajoitin) voi hoitaa asian. Parametri Teksti on teksti, jota jaetaan osiin. Parametri Rajoitin on merkkijono joka sisältää rajoitinmerkkejä, eli merkkejä jonka kohdalla merkkijono katkaistaan. Funktio palauttaa löydetyn osan, tai NULL kun ei enää löydy. Funktiota on kutsuttava monta kertaa, mutta ainoastaan ensimmäisellä kerralla annetaan Teksti, muilla kerroilla parametriksi annetaan NULL. Esimerkiksi:
#include <stdio.h> #include <string.h> int main (int argc, char **argv) { char *Osa; int Index = 0; if ( argc != 3 ) { printf ("Käyttö: %s teksti rajottimia\n", argv[0] ); exit ( 1 ); } /* aloita jakaminen */ Osa = strtok ( argv[1], argv[2] ); /* iteroi niin kauan kun osia riittää */ while ( Osa != NULL ) { /* kirjoita osa */ printf ( "%d: '%s'\n", Index++, Osa ); /* hae seuraava osa */ Osa = strtok ( NULL, argv[2] ); } return 0; }Ohjelman ajo voi antaa vaikka seuraavanlaisen tulostuksen:
% ./mystrtok "d,e,f," , 0: 'd' 1: 'e' 2: 'f' % ./mystrtok "20 * 15 + 12 - 5" "*+- " 0: '20' 1: '15' 2: '12' 3: '5' %
5.8 Merkkijonojen konvertointi
Otsikkotiedostossa <stdlib.h> on määritelty muutama funktio jolla voi konvertoida merkkijonoja luvuiksi. Funktiot ovat:- strtod(char *Teksti, char **Lippu)
- strtol(char *Teksti, char *Lippu, int Kanta)
- strtoul(char *Teksti, char *Lippu, int Kanta)
#include <stdio.h> #include <stdlib.h> int main (int argc, char * argv[]) { char *Lippu; long Arvo; if ( argc != 2 ) { printf ("Käyttö: %s luku\n", argv[0] ); exit ( 1 ); } /* tee konversio. Lähetä lipun osoite */ Arvo = strtol ( argv[1], &Lippu, 10 ); /* menikö oikein? */ if ( *Lippu == 0 ) { printf ( "Ok %ld\n", Arvo ); } else { printf ("Not ok %c\n", *Lippu ); } return 0; }Voi myös käyttää funktioita:
- double atof(char *Teksti)
- int atoi(char *Teksti)
- long atol(char *Teksti)
.
5.9 Merkkien luokittelu
Merkkejä voi luokitella tyyppinsä mukaan funktioilla:
- int isalnum (int c) : tarkistaa jos merkki on kirjain tai numero
- int isalpha (int c) : tarkistaa jos merkki on kirjain
- int iscntrl (int c) : tarkistaa jos merkki on kontrollimerkki
- int isdigit (int c) : tarkistaa jos merkki on numero
- int isgraph (int c) : tarkistaa jos merkki on tulostettava (ei
välilyönti)
- int islower (int c) : tarkistaa jos merkki on pieni
- int isupper (int c) : tarkistaa jos merkki on iso
- int isprint (int c) : tarkistaa jos merkki on tulostettava (myös
välilyönti)
- int ispunct (int c) : tarkistaa jos merkki on tulostettava mutta ei
kirjain, numero tai välilyönti
- int isspace (int c) : tarkistaa jos merkki on "tyhjä", eli esim.
välilyönti,'\t', '\n' jne.
- int isxdigit (int c) : tarkistaa jos merkki on heksadesimaali numero
#include <stdio.h> #include <stdlib.h> #include <ctype.h> /** * Tarkistaa jos parametrina annettu merkkijono on kokonaisluku. * Palauttaa 1 jos on ja 0 jos ei. Numero saa olla positiivinen tai * negatiivinen. Merkkijonon alussa ja lopussa saa olla tyhjää. * * Parametri: char *Teksti - tarkistettava merkkijono */ int isNumber (char *Teksti) { int Index = 0; int Pituus = strlen ( Teksti ); /* onko annettu osoitin edes kunnollinen? */ if ( Teksti == NULL ) /* ei, joten se ei ole numerokaan */ return 0; /* lue tyhjää merkkijono alusta */ while ( Index < Pituus && isspace (Teksti[Index]) ) Index++; /* onko seuraava merkki '-'? */ if ( Teksti[Index] == '-' ) Index++; /* lue numeroita */ while ( Index < Pituus && isdigit (Teksti[Index]) ) Index++; /* lue tyhjää merkkijono alusta */ while ( Index < Pituus && isspace (Teksti[Index]) ) Index++; /* nyt meidän olisi pitänyt saavuttaa merkkijonon viimeinen merkki */ if ( Index == Pituus ) { /* koko merkkijono on numero */ return 1; } else { /* ei ole numero */ return 0; } } int main (int argc, char * argv[]) { /* tarkista parametrien määrä */ if ( argc != 2 ) { printf ("Käyttö: %s merkkijono\n", argv[0] ); exit ( 1 ); } /* testaa merkkijonoa */ if ( isNumber ( argv[1] ) == 1 ) { printf ( "'%s' on numero\n", argv[1] ); } else { printf ( "'%s' ei ole numero\n", argv[1] ); } return 0; }Ohjelman ajo voi antaa seuraavanlaisen tulostuksen:
% ./IsNumber foo 'foo' ei ole numero % ./IsNumber 10 '10' on numero % ./IsNumber " 10" ' 10' on numero % ./IsNumber "-10 " '-10 ' on numero % ./IsNumber " - 10 " ' - 10 ' ei ole numero %
Esimerkki 2:
#include <stdio.h> #include <stdlib.h> #include <ctype.h> /** * Muuttaa parametrina lähetetyn merkkijonon kirjaimet pieniksi. * Palauttaa myös muutetun merkkijonon. * * Parametri: char* Teksti - muutettava merkkijono */ char *stringToLower (char *Teksti) { int Index = 0; int Pituus = strlen ( Teksti ); /* onko annettu osoitin edes kunnollinen? */ if ( Teksti == NULL ) /* ei, joten palauta se suoraan */ return 0; /* iteroi kaikkien merkkien yli */ for ( Index = 0; Index < Pituus; Index++ ) { Teksti [Index] = tolower ( Teksti [Index] ); } /* palauta konvertoitu merkkijono */ return Teksti; } /** * Muuttaa parametrina lähetetyn merkkijonon kirjaimet isoiksi. * Palauttaa myös muutetun merkkijonon. * * Parametri: char *Teksti - muutettava merkkijono */ char *stringToUpper (char *Teksti) { int Index = 0; int Pituus = strlen ( Teksti ); /* onko annettu osoitin edes kunnollinen? */ if ( Teksti == NULL ) /* ei, joten palauta se suoraan */ return 0; /* iteroi kaikkien merkkien yli */ for ( Index = 0; Index < Pituus; Index++ ) { Teksti [Index] = toupper ( Teksti [Index] ); } /* palauta konvertoitu merkkijono */ return Teksti; } int main (int argc, char * argv[]) { /* tarkista parametrien määrä */ if ( argc != 2 ) { printf ("Käyttö: %s merkkijono\n", argv[0] ); exit ( 1 ); } /* kirjoita tulos */ printf ( "Alkuperäinen: %s\n", argv[1] ); printf ( "Pienillä: %s\n", stringToLower ( argv[1] ) ); printf ( "Isoilla: %s\n", stringToUpper (argv[1] ) ); return 0; }
Jan Lindström (Jan.Lindstrom@cs.Helsinki.FI)