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