1 Przetwarzanie tablic znakowych
Transkrypt
1 Przetwarzanie tablic znakowych
Przetwarzanie tablic znakowych 1 Przetwarzanie tablic znakowych Ćwiczenie to poświęcone jest poznaniu i przetrenowaniu metod przetwarzania tablic znakowych. Tablice znakowe w języku C i C++ umożliwiają przetwarzanie napisów. Umiejętność sprawnego operowania napisach jest ważna, szczególnie aktualnie, kiedy powszechnym jest przetwarzanie tekstów w formacie HTML czy XML. W ramach tego ćwiczenia przewidziane jest wykonanie zbioru funkcji służących do przetwarzania tablic znakowych. 1.1 Przetwarzanie tablic znakowych w pigułce Ponieważ ćwiczenia często wyprzedzają wykład, poniżej przedstawiam streszczenie materiału wykładowego poświęconego tablicom znakowym. Zawiera ono podstawowe informacje związane z deklarowaniem takich tablic i wykonywaniem na nich podstawowych operacji. 1.1.1 Tablice znakowe — deklaracja, reprezentacja wewnętrzna Tablice znakowe nie różnią się od innych tablic w języku C. Tablica jest zmienną złożoną z elementów tego samego typu, w tym przypadku są to elementy typu char. Istotnym problemem związanym z reprezentowaniem napisów jest ich zmienna długość. Załóżmy, że zdefiniowano następującą zmienną tablicową: char imie[ 80 ]; Następnie do tablicy imie wczytujemy ze strumienia wejściowego (klawiatury) napis. Załóżmy, że to jest trzyznakowy napis "Aga". printf( "Podaj imię:" ) gets( imie ); W tym momencie 80-cio znakowa tablica imie zawiera trzyznakowy napis. Jak można określić, że z owych 80-ciu znaków interesują nas tylko pierwsze trzy? Zmienna imie : A g a \0 0 1 2 3 ... 4 5 78 79 Rysunek 1: Reprezentacja wewnętrzna napisu W języku C i C++ przyjęto koncepcję łańcuchów ze znacznikiem końca (ang. null terminated strings). Oznacza to, że na końcu właściwego napisu dopisany jest znacznik, informujący, że w tym miejscu napis się kończy. Ilustruje to Rysunek 1. Znacznikiem tym jest znak o kodzie ASCII równym zero, reprezentowanym symbolicznie w języku C w postaci literału '\0'. 1 Przetwarzanie tablic znakowych Literał łańcuchowy "Aga" w istocie jest reprezentowany nie na trzech znakach, a na czterech. Kompilator dla takiego napisu rezerwuje o jeden bajt w pamięci więcej, tak by zmieścić jeszcze znacznik końca napisu. Wszystkie funkcje biblioteczne języka C zakładają, że na końcu napisu jest umieszczony znacznik '\0'. Co się stanie, gdy go nie będzie? Większość funkcji „zgłupieje” i będzie przetwarzała feralny napis tak długo, aż napotka w pamięci operacyjnej bajt o wartości 0. Tak bowiem w pamięci operacyjnej reprezentowany jest znacznik '\0'. Należy zwrócić uwagę, że zainicjowanie tablicy na etapie jej deklaracji w sposób następujący: char imie[ 80 ] = { ’A’, ’g’, ’a’ }; nie dopisuje jawnie znacznika końca. Jednak w przypadku inicjowania tablic (dowolnych, nie tylko znakowych), gdy wyrażeń inicjalizujących jest mniej niż elementów tablicy, w miejsce brakujących inicjatorów wpisywane są wartości zerowe. I tak, przypadkiem bajt o wartości 0 znajdzie się tuż za znakiem '\a'. Można oczywiście zadeklarować i zainicjować tablicę imię w następujący sposób: char imie[ 80 ] = { ’A’, ’g’, ’a’, ’\0’ }; co zapewni niezawodne dopisanie znacznika końca we właściwym miejscu. Zwróćmy jednak uwagę, że oba powyższe sposoby inicjowania tablic nie są wygodne. Dlatego dla tablic znakowych dozwolone jest inicjowanie zawartością odpowiedniego literału łańcuchowego: char imie[ 80 ] = "Aga"; Jak wiemy, literał taki zawiera znacznik końca napisu, i zostanie on wstawiony do zmiennej imie. Ilustruje to Rysunek 2. Literał łańcuchowy : Zmienna imie : A g a \0 0 1 2 3 A g a \0 0 1 2 3 ... 4 5 78 79 Rysunek 2: Literał łańcuchowy inicjalizujący zmienną imie Jak reprezentowany jest napis pusty? To proste – znacznik końca napisu występuje na samym jego początku – zobacz Rysunek 3. Zmienna imie: \0 0 ... 1 2 3 4 5 Rysunek 3: Reprezentacja napisu pustego 78 79 Na etapie definiowania tablic można je uczynić pustymi wykorzystując jeden z podanych dalej sposobów. char imie[ 80 ] = {’\0’}; 2 Przetwarzanie tablic znakowych lub w sposób bardzie popularny: char imie[ 80 ] = ""; Uczynienie łańcucha pustym po jego zadeklarowaniu zrealizować można następująco: imie[ 0 ] = ’\0’; 1.1.2 Podstawowe operacja na tablicach znakowych Przetwarzanie tablic polega zwykle na „przemaszerowaniu” zmienna indeksową po tablicy, dopóki nie ma końca napisu oznaczanego znakiem ’\0’: int i; char s[ 80 ]; . . . for( i = 0; s[ i ] != ’\0’; i++ ) < tu jakieś operacje na każdym znaku s[ i ] > Wyprowadzanie napisu do stdout znak po znaku: void put_string( char s[] ) { int i; for( i = 0; s[ i ] != ’\0’; i++ ) putchar( s[ i ] ); } choć wielu programistów napisze to tak: void put_string( char s[] ) { int i; for( i = 0; s[ i ] != ’\0’; putchar( s[ i++ ] ) ) ; } Do manipulowania tablicami znakowymi opracowano szereg funkcji bibliotecznych (plik nagłówkowy string.h) ... większość z nich można łatwo napisać samemu! Gdy trzeba znać liczbę znaków w łańcuchu, używamy funkcji strlen. Jej rezultatem jest liczba znaków napisu, przekazanego tej funkcji parametrem. #define N 80 char imie[ N ] = "Aga"; . . . printf( "\nLiczba znaków w %s wynosi:%d", imie, strlen( imie ) ); Dwie możliwe implementacje funkcji strlen: int strlen( char s[] ) { int len = 0; while( s[ len ] != '\0' ) len++; return len; } /* Wersja 1-sza */ 3 Przetwarzanie tablic znakowych int strlen( char s[] ) /* Wersja 2-ga */ { int len; for( len = 0; s[ len ] != '\0'; len++ ) ; return len; } Przykładowe funkcje operujące na tablicach znakowych: char a[ N ] = ”Ala”; char b[ N ]; char C[ N ]; /*Przepisanie zawartości a do b, teraz w b to samo co w a: ”Ala”*/ strcpy( b, a ); /*Tak samo należy przepisywać zawartość literałów łańcuchowych strcpy( c, ” ma kota” ); /* Można również tak ”zerować” tablicę znakową” */ strcpy( b, ”” ); /* Ale szybciej jest tak: b[ 0 ] = ’\0’ */ */ /* Łączenie (sklejanie) napisów. Zawartość c zostaje doklejona do a : ”Ala ma kota”” */ strcat( a, c ); /* Porównywanie łańcuchów znakowych realizuje funkcja strcmp printf( ”\nŁańcuchy znaków %s i % s ”, a, b ) if( strcmp( a, c )== 0 ) printf( ”są jednakowe” ); else printf( ”są różne” ); */ /* Porównywanie łańcuchów znakowych bez uwzględnienia wielkości liter, funkcja stricmp */ strcpy( a, ”ALA” ); strcpy( b, ”ala” ); printf( ”\nŁańcuchy znaków %s i % s ”, a, b ) if( stricmp( a, b ) == 0 ) printf( ” są jednakowe” ); else printf( ” są różne” ); Uwaga – mimo iż wolno inicjalizować tablicę literałem łańcuchowym na etapie deklaracji: char imie[ 80 ] = "Aga"; nie wolno przypisywać literałów łańcuchowych do tablic: imie = "Aga";<<= Błąd oraz tablic między sobą: char imie[ 80 ] = "Aga"; char drugie[ 80 ]; drugie = imie; <<= Błąd 4 Przetwarzanie tablic znakowych Jak zatem przypisać do tablicy znakowej literał lub zawartość innej tablicy? Należy do tego użyć funkcji kopiowania łańcuchów znakowych — strcpy. strcpy( imie, "Aga" ); strcpy( drugie, imie ); Jak działa funkcja kopiowania łańcuchów? Funkcja „maszeruje” po kolejnych znakach łańcucha źródłowego (aż do napotkania znacznika końca) i kopiuje zawartość każdego odwiedzonego elementu tablicy źródłowej do odpowiedniego elementu tablicy docelowej. Ilustruje to poglądowo Rysunek 4. char s1[ 80 ] = "Język C"; char s2[ 20 ]; . . . strcpy( s2, s1 ); i++ s d s1 s2 . . . J ê z y k 0 1 2 3 4 J ê z y k 0 1 2 3 4 C 5 ... 6 C 5 \0 79 \0 ... 6 19 void strcpy( char d[], char s[] ) { int i; for( i = 0; s[ i ] != '\0'; i++ ) d[ i ] = s[ i ]; d[ i ] = '\0'; } Rysunek 4: Ilustracja działania funkcji strcpy Inne operacja na napisach – konwersja wielkości liter. strcpy( a, ”ALA” ); strcpy( b, ”ala” ); /* Konwersja liter - małe na duże: strupr, duże na małe: strlwr */ strlwr( a ); /* Po wywołaniu strlwr a zawiera napis ”ala” */ strupr( b ); /* Po wywołaniu strupr a zawiera napis ”ALA” */ Przykładowe implementacje funkcji strlwr i strupr mogą mieć następującą postać: void strupr( char s[] ) { int i; for( i = 0; s[ i ] != '\0'; i++ ) s[ i ] = toupper( s[ i ] ); } void strlwr( char s[] ) { int i; 5 Przetwarzanie tablic znakowych for( i = 0; s[ i ] != '\0'; i++ ) s[ i ] = tolower( s[ i ] ); } Funkcje konwersji wielkości liter wykorzystują do zamiany wielkości pojedynczych znaków odpowiednio funkcji tolower i toupper. Mogą one mieć następującą postać (dla kodowania znaków zgodnego z ASCII): char tolower( char c ) { return ( c >= 'A' && c <= 'Z' ) ? c + 32 : c; } char toupper( char c ) { return ( c >= 'a' && c <= 'z' ) ? c - 32 : c; } Oczywiście powyższe funkcje występują w bibliotekach jeżyka C, znaleźć je można w pliku nagłówkowym ctype.h. 1.2 Zestaw funkcji do własnoręcznej implementacji W ramach ćwiczeń należy zaimplementować funkcje opisane w przedstawionym dalej programie-szablonie. 1.2.1 Funkcja str_len Własna implementacja funkcji wyznaczającej długość napisu liczoną w znakach, prototyp: int str_len( char s[] ); Parametry: char s[] — tablica zawierająca napis, którego długość jest wyznaczana. Rezultat: Długość napisu liczona w znakach, czli liczba znaków przed znacznikiem końca napisu '\0'. Wykorzystanie: char imie[ 80 ] = "Kunegunda"; int l = str_len( imie ); printf( "Dlugosc napisu %s to %d", imie, l ); 1.2.2 Funkcja lwr_str_cnt Wyznacza liczbę małych liter w napisie przekazanym parametrem, prototyp: int lwr_str_cnt( char s[] ); Parametry: 6 Przetwarzanie tablic znakowych char s[] — tablica zawierająca napis, w którym należy wyznaczyć liczbę małych liter. Rezultat: Liczba małych liter w napisie s. Wykorzystanie: char napis[ 80 ] = "ABCabc"; int l = lwr_str_cnt( napis ); printf( "Liczba malych liter w napisie %s to %d", napis, l ); 1.2.3 Funkcja upr_str_cnt Wyznacza liczbę dużych liter w napisie przekazanym parametrem, prototyp: int upr_str_cnt( char s[] ); Parametry: char s[] — tablica zawierająca napis, w którym należy wyznaczyć liczbę dużych liter. Rezultat: Liczba dużych liter w napisie s. Wykorzystanie: char napis[ 80 ] = "ABCabc"; int l = upr_str_cnt( napis ); printf( "Liczba duzych liter w napisie %s to %d", napis, l ); 1.2.4 Funkcja dgt_str_cnt Wyznacza liczbę cyfr w napisie przekazanym parametrem, prototyp: int dgt_str_cnt( char s[] ); Parametry: char s[] — tablica zawierająca napis, w którym należy wyznaczyć cyfr. Rezultat: Liczba cyfr w napisie s. Wykorzystanie: char napis[ 80 ] = "ABC123"; int l = dgt_str_cnt( napis ); printf( "Liczba cyfr w napisie %s to %d", napis, l ); 1.2.5 Funkcja nalpha_str_cnt Wyznacza liczbę liter i cyfr w napisie przekazanym parametrem, prototyp: 7 Przetwarzanie tablic znakowych int nalpha_str_cnt( char s[] ); Parametry: char s[] — tablica zawierająca napis, w którym należy wyznaczyć liczbę liter i cyfr. Rezultat: Liczba liter i cyfr w napisie s. Wykorzystanie: char napis[ 80 ] = "ABC123"; int l = nalpha_str_cnt( napis ); printf( "Liczba liter i cyfr w napisie %s to %d", napis, l ); 1.2.6 Funkcja chr_str_cnt Wyznacza liczbę wystąpień znaku (pierwszy parametr) w napisie przekazanym drugim parametrem, prototyp: int chr_str_cnt( char c, char s[] ); Parametry: char c — znak poszukiwany w napisie, char s[] — tablica zawierająca napis do przeszukania. Rezultat: Liczba wystąpień znaku c w napisie s. Wykorzystanie: char napis[ 80 ] = "Jezyk c i C++"; int l = chr_str_cnt( 'C', napis ); printf( "Liczba wystapien znaku %c w napisie %s to %d", 'C', napis, l ); 1.2.7 Funkcja chr_str_cnt Wyznacza pozycję (indeks) pierwszego wystąpienia znaku (pierwszy parametr) w napisie przekazanym drugim parametrem, prototyp: int chr_str_pos( char c, char s[] ); Parametry: char c — znak poszukiwany w napisie, char s[] — tablica zawierająca napis do przeszukania. Rezultat: Pozycja (indeks) pierwszego wystąpienia znaku c w napisie s. 8 Przetwarzanie tablic znakowych Wykorzystanie: char napis[ 80 ] = "Jezyk c i C++"; int l = chr_str_pos( 'C', napis ); printf( "Znak %c wystepuje w napisie %s na pozycji %d", 'C', napis, l ); 1.2.8 Funkcja chr_str_pos Wyznacza pozycję (indeks) pierwszego wystąpienia znaku (pierwszy parametr) w napisie przekazanym drugim parametrem, prototyp: int chr_str_pos( char c, char s[] ); Parametry: char c — znak poszukiwany w napisie, char s[] — tablica zawierająca napis do przeszukania. Rezultat: Pozycja (indeks) pierwszego wystąpienia znaku c w napisie s lub −1, gdy znak nie został znaleziony. Wykorzystanie: char napis[ 80 ] = "Jezyk c i C++"; int l = chr_str_pos( 'C', napis ); if( l != -1 ) printf( "Znak %c wystepuje w napisie %s na pozycji %d", 'C', napis, l ); else puts( "Znak nie został znaleziony" ); 1.2.9 Funkcja chr_str_rpos Wyznacza pozycję (indeks) ostatniego wystąpienia znaku (pierwszy parametr) w napisie przekazanym drugim parametrem, prototyp: int chr_str_rpos( char c, char s[] ); Parametry: char c — znak poszukiwany w napisie, char s[] — tablica zawierająca napis do przeszukania. Rezultat: Pozycja (indeks) ostatniego wystąpienia znaku c w napisie s lub −1 gdy znak nie został znaleziony. Wykorzystanie: char napis[ 80 ] = "Jezyk c i C++"; int l = chr_str_pos( 'C', napis ); 9 Przetwarzanie tablic znakowych if( l != -1 ) printf( "Znak %c w %s wystepuje na pozycji %d, liczac od konca", 'C', napis, l ); else puts( "Znak nie został znaleziony" ); 1.2.10 Funkcja str_rev Odwraca kolejność znaków w napisie przekazanym parametrem, prototyp: void str_rev( char s[] ); Parametry: char s[] — tablica zawierająca napis do odwrócenia. Rezultat: brak. Wykorzystanie: char napis[ 80 printf( "Napis str_rev( napis printf( "Napis ] = "Jezyk C i C++"; przed odwroceniem %s: ", napis ); ); po odwroceniu %s: ", napis ); 1.3 Szablon programu Przedstawiony niże kod stanowi szablon programu, pozwalającego na realizację opisanych wyżej funkcji. Oczywiście ciała funkcji znajdujące sie w szablonie są puste, ich napisanie jest przedmiotem tego ćwiczenia. /*----------------------------------------------------------------napis_s.c Szablon do cwiczenia wpisaniu funkcji operujacych na lancuchach znakowych -----------------------------------------------------------------*/ #include <stdio.h> #include <stdlib.h> #define MAX_LEN 128 /*----------------------------------------------------------------int str_len( char s[] ) Funkcja: Wlasna implementacja funkcji wyznaczajacej dlugosc napisu liczona w znakach Parametry: char s[] -- tablica zawierajaca napis ktorego dlugosc jest wyznaczana Rezultat: 10 Przetwarzanie tablic znakowych dlugosc napisu liczona w znakach -----------------------------------------------------------------*/ int str_len( char s[] ) { /* To należy napisać */ } /*----------------------------------------------------------------int lwr_str_cnt( char s[] ) Funkcja: Wyznacza liczbe malych liter w napisie s Parametry: char s[] -- tablica zawierajaca napis Rezultat: Liczba malych liter w napisie s -----------------------------------------------------------------*/ int lwr_str_cnt( char s[] ) { /* To należy napisać */ } /*----------------------------------------------------------------int upr_str_cnt( char s[] ) Funkcja: Wyznacza liczbe duzych liter w napisie s Parametry: char s[] -- tablica zawierajaca napis Rezultat: Liczba duzych liter w napisie s -----------------------------------------------------------------*/ int upr_str_cnt( char s[] ) { /* To należy napisać */ } /*----------------------------------------------------------------int dgt_str_cnt( char s[] ) Funkcja: Wyznacza liczbe cyfr w napisie s Parametry: char s[] -- tablica zawierajaca napis Rezultat: Liczba cyfr w napisie s -----------------------------------------------------------------*/ int dgt_str_cnt( char s[] ) { /* To należy napisać */ } /*----------------------------------------------------------------int nalpha_str_cnt( char s[] ) Funkcja: Wyznacza liczbe liter i cyfr w napisie s 11 Przetwarzanie tablic znakowych Parametry: char s[] -- tablica zawierajaca napis Rezultat: Liczba liter i cyfr w napisie s -----------------------------------------------------------------*/ int nalpha_str_cnt( char s[] ) { /* To należy napisać */ } /*----------------------------------------------------------------int chr_str_cnt( char c, char s[] ) Funkcja: Wyznacza liczbe wystapien znaku c w s Parametry: char c -- znak poszukiwany w napisie char s[] -- tablica zawierajaca napis Rezultat: Liczba wystapien znaku c w napisie s -----------------------------------------------------------------*/ int chr_str_cnt( char c, char s[] ) { /* To należy napisać */ } /*----------------------------------------------------------------int chr_str_pos( char c, char s[] ) Funkcja: Wyznacza indeks (pozycje w napisie) pierwszego wystapienia znaku c w s Parametry: char c -- znak poszukiwany w napisie char s[] -- tablica zawierajaca napis Rezultat: Pozycja znaku w tablicy liczona od 0 lub -1 gdy znak nie znaleziony -----------------------------------------------------------------*/ int chr_str_pos( char c, char s[] ) { /* To należy napisać */ } /*----------------------------------------------------------------int chr_str_rpos( char c, char s[] ) Funkcja: Wyznacza indeks (pozycje w napisie) ostatniego wystapienia znaku c w s Parametry: char c -- znak poszukiwany w napisie char s[] -- tablica zawierajaca napis Rezultat: 12 Przetwarzanie tablic znakowych Pozycja znaku w tablicy liczona od 0 lub -1 gdy znak nie znaleziony -----------------------------------------------------------------*/ int chr_str_rpos( char c, char s[] ) { /* To należy napisać */ } /*----------------------------------------------------------------void str_rev( char s[] ) Funkcja: Odwraca kolejnosc znakow w tablicy s Parametry: char s[] -- tablica zawierajaca napis Rezultat: Brak -----------------------------------------------------------------*/ void str_rev( char s[] ) { /* To należy napisać */ } /* Zadaniem fukcji main jest przetestowanie zaimplementowanych wyzej funkcji */ int main() { char line[ MAX_LEN ]; char c; int pos; printf( "\n\nWpisz linie tekstu: " ); gets( line ); printf( printf( printf( printf( printf( "\nLiczba "\nLiczba "\nLiczba "\nLiczba "\nLiczba znakow malych liter duzych liter cyfr innych znakow : : : : : %d", %d", %d", %d", %d", str_len( line ) ); lwr_str_cnt( line ) ); upr_str_cnt( line ) ); dgt_str_cnt( line ) ); nalpha_str_cnt( line ) ); printf( "\n\nPodaj pojedynczy znak: " ); c = getchar(); fflush( stdin ); printf( "\nLiczba wystapien znaku %c chr_str_cnt( c, line ) ); : %d", c, if( ( pos = chr_str_pos( c, line ) ) != -1 ) printf( "\nPierwsze wystapienie znaku %c od poczatku : %d", c, pos + 1 ); else printf( "\nZnak %c nie zostal znaleziony", c ); if( ( pos = chr_str_rpos( c, line ) ) != -1 ) printf( "\nPierwsze wystapienie znaku %c od konca pos + 1 ); else printf( "\nZnak %c nie zostal znaleziony", c ); : %d", c, printf( "\n\nNapis oryginalny : %s", line ); str_rev( line ); 13 Przetwarzanie tablic znakowych printf( "\nNapis odwrocony : %s", line ); ( void )getchar(); return EXIT_SUCCESS; } 14