C4CL
4.12 4.12.1
8. April 2013
Datenstrukturen/String-Bibliothek Vergleich von Strings: strcmp()
Ein Programm soll zu einem eingegebenen Bauelement die Betriebsmittelkennzeichnung ausgeben. Ein erster Entwurf sieht so aus (src/betriebsmittel0.c): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include #define LEN 80 i nt main ( void ) { char wort [LEN+1]=” ” ; p r i n t f ( ” Bauelement e i n g e b e n : ” ) ; s c a n f ( ”%80 s ” , wort ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { } p r i n tf (” Betriebsmittelkennzeichnung : ” ) ; if ( wort==” T r a n s i s t o r ” ) p r i n t f ( ”V\n” ) ; e l s e i f ( wort==” Widerstand ” ) p r i n t f ( ”R\n” ) ; e l s e i f ( wort==” Kondensator ” ) p r i n t f ( ”C\n” ) ; e l s e i f ( wort==” Spule ” ) p r i n t f ( ”L\n” ) ; else p r i n t f ( ” unbekannt \n” ) ; return 0 ; } Nun wird getestet: jk@r155pc1:˜$ gcc -o src/betriebsmittel0 betriebsmittel0.c jk@r155pc1:˜$ betriebsmittel0 Bauelement eingeben:Transistor Betriebsmittelkennzeichnung: unbekannt Warum wird keine Eingabe richtig erkannt? Warum werden gleiche Strings nicht als gleich angesehen? Die L¨ osung liegt darin, dass mit dem Zugriff auf den Namen der Arrays nur die Adressen verglichen werden und nicht die Inhalte. Hier noch ein Beispiel (src/vergleichsoperator_demo.c):
1 2 3 4 5
#include i nt main ( void ) { char f r u c h t 1 [ ] = ” K i r s c h e ” ; char f r u c h t 2 [ ] = ” K i r s c h e ” ;
6 7
i f ( f r u c h t 1==f r u c h t 2 ) p r i n t f ( ” g l e i c h \n” ) ; else p r i n t f ( ” u n g l e i c h \n” ) ;
8 9 10 11 12
i f ( ” Z i t r o n e ”==” Z i t r o n e ” ) p r i n t f ( ” g l e i c h \n” ) ; else p r i n t f ( ” u n g l e i c h \n” ) ; return 0 ;
13 14 15 16 17
} jk@r155pc1:˜$ gcc -o vergleichsoperator_demo src/vergleichsoperator_demo.c jk@r155pc1:˜$ ./vergleichsoperator_demo ungleich gleich 1
C4CL
8. April 2013
Die beiden Variablen, die zuerst verglichen werden, liegen an unterschiedliche Adressen, also sind sie ungleich. Die beiden literalen (w¨ortlichen) Stringkonstanten, die danach verglichen werden, liegen interessanterweise bei diesem Compiler (GNU-C) an derselben Adresse. Dadurch werden sie als gleich angesehen.1 Wenn man die Inhalte zweier Strings vergleichen will, braucht man also eine andere L¨osung. Sie besteht aus der Funktion strcmp() und einigen verwandten Funktionen. strcmp() wird ¨ in der C-Standard-Bibliothek mitgeliefert. Uber wird sie eingebunden. strcmp() erh¨alt zwei Strings (d¨ urfen auch konstant sein) als Parameter und gibt bei Gleichheit eine Null aus (Beispiel: src/strcmp_demo.c): 1 2 3 4 5 6 7 8 9
#include #include < s t r i n g . h> i nt main ( void ) { char s t r i n g 1 [ 2 1 ] = ” ” ; char s t r i n g 2 [ 2 1 ] = ” ” ; p r i n t f ( ” Zeichenkette 1 eingeben : ” ) ; s c a n f ( ” %20[ˆ\n ] ” , s t r i n g 1 ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) ;
10
p r i n t f ( ” Zeichenkette 2 eingeben : ” ) ; s c a n f ( ” %20[ˆ\n ] ” , s t r i n g 2 ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) ;
11 12 13 14
p r i n t f ( ” strcmp (\”% s \” ,\”% s \ ” ) e r g i b t %i \n” , s t r i n g 1 , s t r i n g 2 , strcmp ( s t r i n g 1 , s t r i n g 2 ) ) ; return 0 ;
15 16 17 18
} Bei Ungleichheit wird eine Zahl gr¨ oßer oder kleiner als null ausgegeben2 . Das Programm vom Beginn des Abschnitts kann damit so geschrieben werden (src/betriebsmittel1.c):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include #include < s t r i n g . h> #define LEN 80 i nt main ( void ) { char wort [LEN+1]=” ” ; p r i n t f ( ” Bauelement e i n g e b e n : ” ) ; s c a n f ( ”%80 s ” , wort ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { } p r i n tf (” Betriebsmittelkennzeichnung : ” ) ; if ( strcmp ( wort , ” T r a n s i s t o r ”)==0) p r i n t f ( ”V\n” ) ; e l s e i f ( strcmp ( wort , ” Widerstand ”)==0) p r i n t f ( ”R\n” ) ; e l s e i f ( strcmp ( wort , ” Kondensator ”)==0) p r i n t f ( ”C\n” ) ; e l s e i f ( strcmp ( wort , ” Spule ”)==0) p r i n t f ( ”L\n” ) ; else p r i n t f ( ” unbekannt \n” ) ; return 0 ; } 1 Ein anderer Compiler, der vielleicht nicht merkt, dass die beiden Konstanten gleich sind, legt die beiden Strings einzeln an zwei verschiedene Adressen. Bei ihm ergibt der Vergleich dann eine Ungleichheit. 2 Je nachdem, ob der erste String in alphabetischer Sortierung hinter oder vor dem zweiten String kommt
2
C4CL
4.12.2
8. April 2013
Kopieren von Strings: strcpy()
Das Programm von oben soll eleganter gestaltet werden. Die Antwort soll in einen dritten String gegeben werden, der erst zum Schluss ausgegeben werden soll (src/betriebsmittel2.c): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include #include < s t r i n g . h> #define LEN 80 i nt main ( void ) { char wort [LEN+1]=” ” ; char a ntwo r t [LEN+1]=” unbekannt ” ; p r i n t f ( ” Bauelement e i n g e b e n : ” ) ; s c a n f ( ”%80 s ” , wort ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { } p r i n tf (” Betriebsmittelkennzeichnung : ” ) ; if ( strcmp ( wort , ” T r a n s i s t o r ”)==0) a ntwo r t=”V” ; e l s e i f ( strcmp ( wort , ” Widerstand ”)==0) a ntwo r t=”R” ; e l s e i f ( strcmp ( wort , ” Kondensator ”)==0) a ntwo r t=”C” ; e l s e i f ( strcmp ( wort , ” Spule ”)==0) a ntwo r t=”L” ; p r i n t f ( ”%s \n” , a ntwo r t ) ; return 0 ; } Der Test ergibt: jk@r155pc1:˜$ gcc -o src/betriebsmittel2 betriebsmittel2.c betriebsmittel2.c: In function ’main’: betriebsmittel2.c:11: error: incompatible types when assigning to type ’char[81]’ from type ’char *’ Stimmt, man darf an ein Array (also auch an einen String) nichts zuweisen. Aber auch f¨ ur dieses Problem gibt es eine L¨ osung aus , n¨amlich die Funktion strcpy(). Auch strcpy() bekommt zwei Strings als Parameter, der erst davon muss eine Variable sein. Dann wird der zweite String in den ersten kopiert (Beispiel: src/strcpy_demo.c):
1 2 3 4 5 6 7
#include #include < s t r i n g . h> i nt main ( void ) { i nt c ; char s2 [ 4 0 0 1 ] = ” ” ; char s1 [ 5 ] = ”” ;
8
p r i n t f ( ” Z e i c h e n k e t t e nach s2 e i n g e b e n (max . 4 Z e i c h e n ) : ” ) ; s c a n f ( ” %4000[ˆ\ n ] ” , s2 ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { }
9 10 11 12
p r i n t f ( ” k o p i e r e s2 nach s1 mit A u f r u f s t r c p y ( s1 , s2 ) ; \ n” ) ; s t r c p y ( s1 , s2 ) ; p r i n t f ( ” I n h a l t von s1 : >>%s%sLala>Lala #define LEN 80 i nt main ( void ) { char wort [LEN+1]=” ” ; char a ntwo r t [LEN+1]=” unbekannt ” ; p r i n t f ( ” Bauelement e i n g e b e n : ” ) ; s c a n f ( ”%80 s ” , wort ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { } p r i n tf (” Betriebsmittelkennzeichnung : ” ) ; if ( strcmp ( wort , ” T r a n s i s t o r ”)==0) s t r c p y ( antwort e l s e i f ( strcmp ( wort , ” Widerstand ”)==0) s t r c p y ( antwort e l s e i f ( strcmp ( wort , ” Kondensator ”)==0) s t r c p y ( antwort e l s e i f ( strcmp ( wort , ” Spule ”)==0) s t r c p y ( antwort p r i n t f ( ”%s \n” , a ntwo r t ) ; return 0 ; }
4.12.3
, ”V” ) ; , ”R” ) ; , ”C” ) ; , ”L” ) ;
Verketten von Strings: strcat()
Nun soll das Programm – ganz nach dem Anspruch des E-V-A-Prinzips – alle Ausgaben im Antwortstring sammeln und erst zum Schluss mit einem printf()-Befehl ausgeben. Hier ist die erste Version (src/betriebsmittel4.c): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#include #include < s t r i n g . h> #define LEN 80 i nt main ( void ) { char wort [LEN+1]=” ” ; char a ntwo r t [LEN+1]=” unbekannt ” ; p r i n t f ( ” Bauelement e i n g e b e n : ” ) ; s c a n f ( ”%80 s ” , wort ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { } s t r c p y ( antwort , ” B e t r i e b s m i t t e l k e n n z e i c h n u n g : ” ) ; a ntwo r t=a ntwo r t+wort ; if ( strcmp ( wort , ” T r a n s i s t o r ”)==0) a ntwo r t=a ntwo r t+”V” ; e l s e i f ( strcmp ( wort , ” Widerstand ”)==0) a ntwo r t=a ntwo r t+”R” ; e l s e i f ( strcmp ( wort , ” Kondensator ”)==0) a ntwo r t=a ntwo r t+”C” ; e l s e i f ( strcmp ( wort , ” Spule ”)==0) a ntwo r t=a ntwo r t+”L” ; else a ntwo r t=a ntwo r t+” unbekannt ” ; p r i n t f ( ”%s \n” , a ntwo r t ) ; return 0 ; 4
C4CL
20
8. April 2013
} Dieses Programm enth¨ alt offensichtlich an mehreren Stellen denselben Fehler: Man kann Strings in C nicht einfach addieren. Gesucht wird eine Funktion, die einen String an einen anderen anh¨angt. Wieder gibt es eine Funktion aus , die diese Aufgabe l¨ost: strcat() heißt sie. Sie erh¨alt (wie strcpy()) zwei Strings als Parameter, von denen der erste variabel sein muss. Dann wird der zweite String an das Ende des ersten Strings kopiert (Beispiel: src/strcat_demo.c):
1 2 3 4 5 6 7 8
#include #include < s t r i n g . h> i nt main ( void ) { i nt c ; char s2 [ 1 1 ] = ” ” ; char s1 [ 1 1 ] = ” ” ;
9 10
p r i n t f ( ” Z e i c h e n k e t t e nach s1 e i n g e b e n (max . 10 Z e i c h e n ) : ” ) ; s c a n f ( ” %10[ˆ\n ] ” , s1 ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { }
11 12 13
p r i n t f ( ” Z e i c h e n k e t t e nach s2 e i n g e b e n (max . 10 Z e i c h e n ) : ” ) ; s c a n f ( ” %10[ˆ\n ] ” , s2 ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { }
14 15 16 17 18
p r i n t f ( ” haenge s2 an s1 an mit A u f r u f s t r c a t ( s1 , s2 ) ; \ n” ) ; s t r c a t ( s1 , s2 ) ; p r i n t f ( ” I n h a l t von s1 : >>%s%sToastBrot>Brot #define LEN 80 i nt main ( void ) { char wort [LEN+1]=” ” ; char a ntwo r t [LEN+ 1 ] ; p r i n t f ( ” Bauelement e i n g e b e n : ” ) ; s c a n f ( ”%80 s ” , wort ) ; while ( g e t c h a r ( ) ! = ’ \n ’ ) { } s t r c p y ( antwort , ” B e t r i e b s m i t t e l k e n n z e i c h n u n g : ” ) ; 5
C4CL
s t r c a t ( antwort , wort ) ; if ( strcmp ( wort , ” T r a n s i s t o r ”)==0) e l s e i f ( strcmp ( wort , ” Widerstand ”)==0) e l s e i f ( strcmp ( wort , ” Kondensator ”)==0) e l s e i f ( strcmp ( wort , ” Spule ”)==0) else p r i n t f ( ”%s \n” , a ntwo r t ) ; return 0 ;
12 13 14 15 16 17 18 19 20
8. April 2013
s t r c a t ( antwort s t r c a t ( antwort s t r c a t ( antwort s t r c a t ( antwort s t r c a t ( antwort
, ”V” ) ; , ”R” ) ; , ”C” ) ; , ”L” ) ; , ” unbekannt ” ) ;
}
4.12.4
Anf¨ ange vergleichen mit strncmp()
Die Funktion strncmp() ist eine Erweiterung zu strcmp(): Man kann damit die Anzahl der Zeichen, die verglichen werden sollen, begrenzen: 1
x=strncmp ( ” E i s z e i t ” , ” Eisenbahn ” , 3 ) ; In diesem Beispiel werden nur die ersten drei Buchstaben verglichen, und damit erh¨alt x den Wert null (=gleich). 4.12.5
Anfang kopieren mit strncpy()
Wesentlich h¨ aufiger wird eine Funktion ben¨otigt, die nur den Anfang von s2 nach s1 kopiert. Die bisherige Funktion strcpy() hat n¨ amlich einen Nachteil, der nicht sofort auff¨allt: jk@r155pc1:˜$ ./strcpy_demo Zeichenkette nach s2 eingeben (max. 4 Zeichen):123456789 kopiere s2 nach s1 mit Aufruf strcpy(s1,s2); Inhalt von s1: >>123456789>6789>1234_123456789>123456789>ABCDE12345>1234567890 i nt main ( void ) { char ∗p ; char s t r i n g [ ] = ”Das i s t das Haus , w e l c h e s dem N i k o l a u s g e h o e r t ” ; char t r e n n e r [ ] = ” , ; ” ; p r i n t f ( ” s t r t o k (\”% s \” ,\”% s \ ” ) e r g i b t : ” , s t r i n g , t r e n n e r ) ; p=s t r t o k ( s t r i n g , t r e n n e r ) ; p r i n t f ( ”>>%s%s