Laboratorium 3: ,,Tablice, tablice znaków i funkcje operujące na

Transkrypt

Laboratorium 3: ,,Tablice, tablice znaków i funkcje operujące na
Laboratorium 3: „Tablice, tablice znaków i funkcje operujące na
ciągach znaków”
dr inż. Arkadiusz Chrobot
dr inż. Grzegorz Łukawski
7 kwietnia 2014
1.
Wprowadzenie
Pierwsza część instrukcji zawiera informacje o sposobie tworzenia i korzystania z tablic w języku c.
Druga część poświęcona jest zmiennym, które służą do przechowywania łańcuchów znaków w tym języku,
a ostania traktuje o funkcjach umożliwiających wykonywanie operacji na tych łańcuchach.
2.
Tablice
Tablice należą do podstawowych struktur danych, dlatego są dostępne w większości współczesnych
języków programowania. Język c nie jest tutaj wyjątkiem. W tym rozdziale zostaną opisane zasady
posługiwania się tablicami w tym języku.
2.1. Tworzenie tablic
Tablicę w języku c deklarujemy podając najpierw typ jej elementów, potem jej nazwę, a na końcu,
w nawiasach kwadratowych umieszczamy liczbę jej elementów. Deklaracja tablicy kończy się znakiem
średnika. Istnieje także możliwość stworzenia tablicy zainicjowanej. W jej deklaracji nawiasy kwadratowe możemy pozostawić puste. Po nich umieszczamy instrukcję przypisania i w nawiasach klamrowych
wymieniamy wartości elementów, które rozdzielamy przecinkami. Tablica będzie miała tyle elementów,
ile podamy wartości. Tablice wielowymiarowe tworzymy podając kilka par nawiasów kwadratowych za
nazwą tablicy, a w nich odpowiednie liczby elementów. Można również tworzyć zainicjowane tablice wielowymiarowe. Przykłady deklaracji tablic podano w listingu 1. Tablice mogą być tworzone zarówno jako
zmienne lokalne, jak i globalne.
#define N 10
// Definicja stałej o nazwie N i wartości 10.
int t[N];
// Tablica o dziesięciu elementach typu int.
int tablica[3]; // Tablica o trzech elementach typu int.
char tab[3][3]; // Tablica dwuwymiarowa o dziewięciu elementach (3x3) typu char.
double ulamki[] = {0.1, 0.2, 0.3}; // Tablica zainicjowana o trzech elementach
// typu double.
double ulamki2[3] = {0.1, 0.2, 0.3}; // Jak wyżej, ale tym razem podajemy liczbę
// elementów należących do tablicy.
int macierz[][3] = {{1,2,3},{4,5,6},{7,8,9}};
// Zainicjowana tablica dwuwymiarowa (3x3). W deklaracji możemy opuścić tylko
// jeden wymiar, pozostałe należy podać. Dodatkowe pary nawiasów klamrowych,
// wewnątrz pierwszej pary, nie są konieczne.
Listing 1: Deklaracje tablic
2.2. Dostęp do elementów tablicy
W języku c elementy tablicy indeksowane są zawsze od zera, a indeks ostatniego elementu jest
równy liczbie elementów tablicy pomniejszonej o jeden. Standard c99 dopuszcza użycie ujemnych liczb
całkowitych do indeksowania tablic. Aby takie indeksy były użyte prawidłowo należy użyć ich razem
ze wskaźnikiem na tablicę, który nie wskazuje na jej pierwszy element. Związki między wskaźnikami
i tablicami zostaną opisane w przyszłych instrukcjach. Należy też wiedzieć, że kompilator nie sprawdza
zakresu wartości indeksów. Listing 2 zawiera kilka przykładów.
1
#define N 10
int a[N];
int i;
for(i=0;i<N;i++)
scanf("%d",&a[i]);
int m[2][3];
m[1][2] = 4;
m[0][0] = a[0];
Listing 2: Sposoby dostępu do elementów tablicy
2.3. Parametry tablic
Wielkość tablicy, czyli liczbę bajtów zajmowanych przez nią w pamięci możemy wyznaczyć za pomocą operatora sizeof. Liczbę elementów tablicy możemy obliczyć dzieląc jej rozmiar przez wielkość
pierwszego elementu (jego indeks zawsze wynosi zero). Listing 3 zawiera odpowiednie przykłady. Należy pamiętać, że w przypadku funkcji, które będą opisane w kolejnych instrukcjach, te rozwiązania nie
działają poprawnie.
int a[10];
int b = sizeof(a); //lub 10*sizeof(int);
int c = sizeof(a)/sizeof(a[0]); // Obliczenie liczby elementów tablicy.
Listing 3: Wyznaczanie rozmiaru tablic
3.
Łańcuchy znaków
Język c nie posiada osobnego typu o nazwie „string”. Zamiast tego pozwala on przechowywać ciągi
(łańcuchy) znaków w tablicach elementów typu char. Należy pamiętać, że taka tablica musi mieć o jeden
element więcej niż jest znaków w łańcuchu, który chcemy w niej przechować. Dodatkowe miejsce jest
potrzebne do zapamiętania znaku o kodzie ascii równym zero, zapisywanym w języku c jako '\0',
który oznacza koniec ciągu znaków. Takie tablice mogą być tworzone jako tablice zainicjowane. Listing
4 zawiera kilka przykładów.
char ciag[] = {'P','r','z','y','k','ł','a','d','\0'};
char ciag1[] = "Przykład"; // Tekst podany w~cudzysłowie jest traktowany jako
// łańcuch znaków i nie wymaga podawania znaku
// końca ciągu.
char ciag2[10]; // Niezainicjowana tablica znaków, która może pomieścić łańcuchy
// o długości maksymalnie dziewięciu znaków.
Listing 4: Tablice znaków
2
3.1. Operacje na łańcuchach
Tablice w języku c są zrealizowane jako wskaźniki na obszary pamięci mogące pomieścić określoną
liczbę elementów danego typu. W tej instrukcji ta cecha tablic nie będzie głębiej analizowana, jednakże
ma ona pewne konsekwencje, które są szczególnie widoczne w przypadku tablic znaków. Do każdego
elementu takiej tablic możemy dostać się tak samo jak do elementu w zwykłej tablicy, ale nie możemy
łączyć ze sobą łańcuchów znaków za pomocą operatora + lub porównywać ich za pomocą operatora
==1 . Standardowa biblioteka języka c dostarcza funkcji, które umożliwiają wykonanie takich operacji.
Zacznijmy jednak od wczytania ciągu znaków z klawiatury i wypisania go na ekran. Możemy wydrukować
łańcuch znaków na ekranie za pomocą funkcji puts() lub printf(). Listing 5 zawiera trzy przykłady.
puts("Ala ma kota.");
printf("Ala ma kota.\n");
char ciag[] = "Ala ma kota.";
puts(ciag);
printf("%s\n",ciag);
Listing 5: Wypisanie łańcucha znaków na ekran
W funkcji printf() do wypisania zawartości zmiennej będącej tablicą znaków został użyty ciąg
formatujący ”%s”. Tego samego ciągu można użyć do wprowadzania łańcucha z klawiatury przy pomocy
funkcji scanf(), ale tak użyta funkcja scanf() będzie wczytywała ciąg do napotkania pierwszego znaku
białego (np. w przypadku ciągu ”Ala ma kota.” wczyta tylko ”Ala”). Aby wczytać cały ciąg, należy użyć
wzorców regularnych i przekazać funkcji, że ma czytać wszystkie znaki oprócz znaku końca wiersza (’\n’),
który generowany jest przez klawisz Enter. Można też użyć w tym celu funkcji fgets()2 . Przyjmuje ona
trzy argumenty wywołania: tablicę znaków, maksymalny rozmiar wprowadzanego łańcucha oraz zmienną
stdin będącą strumieniem standardowego wejścia, czyli połączeniem z klawiaturą. Listing 6 zawiera
odpowiednie przykłady.
char str[40];
scanf("%s",str); // Wczytanie ciągu do pierwszego napotkanego znaku białego.
while(getchar()!='\n'); // Wyczyszczenie strumienia wejściowego.
scanf("%[^\n]s",str); // Wczytanie całego ciągu znaków.
while(getchar()!='\n'); // Wyczyszczenie strumienia wejściowego.
fgets(str,40,stdio); // Wczytanie całego ciągu znaków.
Listing 6: Wprowadzanie ciągu znaków z klawiatury
Należy zauważyć, że w funkcji scanf() drugi argument (zmienna string) nie jest poprzedzona
operatorem, który zwraca jej adres (znak &). Dzieje się tak, ponieważ jako drugi argument wywołania ta
funkcja przyjmuje adres zmiennej do której ma zapisać wartość, a ponieważ nazwa tablicy sama w sobie
jest wskaźnikiem, który zawiera taki adres, więc nie należy używać operatora & w stosunku do niej.
Zapis [^\n] informuje funkcję scanf(), że ma akceptować wszystkie znaki poza znakiem nowego wiersza.
W nawiasach kwadratowych umieszczany jest zbiór znaków, które mają być zaakceptowane, a znak „^”
oznacza „wszystkie, oprócz …”.
Funkcje wykonujące inne operacje na łańcuchach znaków dostępne są po włączeniu do programu
pliku nagłówkowego strings.h lub string.h. Spis wszystkich podprogramów, które dostarczają te pliki
otrzymujemy po wydaniu polecania man string w terminalu. Tabela 1 zawiera wykaz kilku z najczęściej
używanych. Używając funkcji, które modyfikują zawartość zmiennych (np. dodają do ciągu nowe znaki
lub kopiują ciągi znaków) należy pamiętać, że nie sprawdzają one pojemności zmiennych do
1 Dokładniej rzecz ujmując, można za pomocą tego operatora porównywać jedynie adresy takich tablic, ale nie ich
zawartość.
2 Dostępna jest także funkcja gets(), ale jej użycie nie jest zalecane.
3
których zmodyfikowane ciągi są zapisywane. Trzeba być ostrożnym, aby nie zapisywać danych
poza zmienną.
Przykład użycia
int dl = strlen(str);
int cmp = strcmp(str1,str1);
int cmp = strncmp(str1,str2,n);
strcpy(str1,str2);
strncpy(str1,str2,n);
strcat(str1,str2);
strncat(str1,str2,n);
Opis działania
Zwraca długość ciągu znaków zapisanego w zmiennej przekazanej jej przez parametr. Nie uwzględnia znaku końca
ciągu, czyli '\0'.
Porównuje dwa łańcuchy znaków. Może być używana w instrukcjach warunkowych i pętlach while oraz do …while.
Zwraca wartość zero jeśli łańcuchy są identyczne, wartość
mniejszą od zera, jeśli łańcuch pierwszy jest „mniejszy”
od drugiego, lub dodatnią jeśli jest odwrotnie.
Jak wyżej, ale porównuje tylko n pierwszych znaków. Z tego względu jest uważana za bezpieczniejszą w użyciu niż
strcmp().
Kopiuje łańcuch będący drugim argumentem jej wywołania do zmiennej będącej pierwszym argumentem wywołania. Zmienna ta powinna być wystarczająco pojemna, żeby pomieścić cały łańcuch wraz ze znakiem końca. Zwraca wskaźnik do skopiowanego łańcucha, ale najczęściej ta
wartość jest ignorowana.
Jak wyżej, ale ogranicza kopiowanie do n początkowych
znaków.
Dołącza do łańcucha zapisanego w pierwszym argumencie
wywołania łańcuch z drugiego argumentu.
Jak wyżej, ale ogranicza się do n znaków drugiego łańcucha.
Tabela 1: Wybrane funkcje z pliku string.h
4

Podobne dokumenty