Omówienie tablic
Transkrypt
Omówienie tablic
Wprowadzenie do programowania w języku C — przetwarzanie tablic Tablice — deklaracja, reprezentacja wewnętrzna Tablica jest zmienną złożoną z elementów tego samego typu. Obejmuje ona ciągły obszar pamięci operacyjnej dokładnie tak duży, aby zmieścić wszystkie jej elementy. Deklaracja tablicy — zmiennej tablicowej: typ_elemetu nazwa_tablicy[ <wyrażenie_ stałe> ] wyrażenie_ stałe — wyrażenie określające liczbę elementów tablicy, wartość tego wyrażenia musi być znana na etapie kompilacji. Dziesięcioelementowa tablica liczb całkowitych Różne warianty deklaracji int tab[ 10 ]; #define N 10 . . . int tab[ N ]; const int N = 10; . . . int tab[ N ] C C++ poprawne poprawne poprawne poprawne niepoprawne poprawne Uwaga ð Kwalifikator typu const może wystąpić z każdą specyfikacją typu. Zmienna z kwalifikatorem const powinna być zainicjowana ale potem nie może zmieniać wartości. Uwaga ð Zmienna z kwalifikatorem const w języku C nie jest traktowana jako stała i nie może być wykorzystywana do określania rozmiaru tablicy. W języku C/C++ rzadko spotyka się deklaracje typów tablicowych — nie ma to wiele wspólnego z deklaracją typów np. w języku Pascal, i jest mało użyteczne. 10 elementów tab 0 1 2 3 4 5 6 7 8 9 Odwołania do elementów tablicy: tab[ 0 ] = 1; tab[ N - 1 ] = 5; a = 2*tab[3]; int i = 0, j = N – 1; a = tab[ i ] + tab [ j ]; Uwaga ð W języku C i C++ nie ma żadnych wbudowanych mechanizmów zabezpieczających przed odwoływaniem się do „elementów” leżących poza zakresem indeksowym tablic. Wprowadzenie do programowania w języku C — przetwarzanie tablic tab[ 12 ] = 10; tab 0 1 2 3 4 5 6 7 8 9 ? 10 ? 11 10 12 Obszar poza zakresem tablicy ! Tablice — typowe operacje Iteracja for jest najczęściej stosowaną iteracją do przetwarzania tablic. Przykład 1 — ustawianie wartości wszystkich elementów tablicy, np. zerowanie: int i; . . . for( i = 0; i < N; tab[ i++ ] = 0 ) ; Przykład 2 — wczytywanie danych do tablicy: int i; char linia[ 80 ]; . . . for( i = 0; i < N; i++ ) { printf( ”\n>” ); gets( linia ); tab[ i ] = atoi( linia ); } lub nieco krócej: int i; char linia[ 80 ]; . . . for( i = 0; i < N; printf( ”\n>” ), tab[ i++ ] = atoi( gets( linia ) ) ) ; Przykład 3 — wyprowadzanie zawartości tablicy do stout: int i; char linia[ 80 ]; . . . for( i = 0; i < N; printf( ”\n%d”, tab[ i++ ] ) ) ; Przykład 4 — sumowanie liczb zapisanych w tablicy: int i, suma = 0; char linia[ 80 ]; . . . for( i = 0; i < N; i++ ) suma = suma + tab[ i ]; lub nieco krócej: int i, suma; char linia[ 80 ]; . . . for( i = 0, suma = 0; i < N; suma += tab[ i++ ] ) ; Wprowadzenie do programowania w języku C — przetwarzanie tablic Przykład 5 — suma co drugiego elementu podzielnego przez 3: int i, suma; char linia[ 80 ]; . . . for( i = 0, suma = 0; i < N; i+= 2 ) suma += ( tab[ i ] % 3 == 0 ) ? tab[ i ] : 0; /* Niby sprytne, ale ... */ Przykład 5 — cd., inna wersja — niby mniej sprytna a szybsza: int i, suma; char linia[ 80 ]; . . . for( i = 0, suma = 0; i < N; i+= 2 ) if( tab[ i ] % 3 == 0 ) suma += tab[ i ]; Uwaga na takie konstrukcje: suma = tab[ i++ ] + tab[ i ]; tab[ ++i ] = a * ++i; Tablice wolno inicjalizować na etapie deklaracji: int tab[ 10 ] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int dni_miesiecy[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; float przychody[ 12 ] = { 0, 0, 0 }; /* Za mało wartości początkowych */ • Jeżeli liczba wartości początkowych jest mniejsza od rozmiaru tablicy, to brakujące elementy otrzymują wartość zero (zmienne zewnętrzne, statyczne i automatyczne). Podanie zbyt wielu wartości początkowych jest błędem. • Nie ma sposobu na zainicjowanie środkowego elementu bez podania wszystkich wartości pośrednich. Kopiowanie zawartości tablic Nie wolno przypisywać całych tablic. Musi to być wykonane poprzez przekopiowanie zawartości tablicy element po elemencie. #define N 5 int a[] = { 1, 2, 3, 4, 5 }; int b[ N ]; int i; . . . b = a; /* <---- Nigdy tak! */ . . . for( i = 0; i < N; i++ ) /* Niestety trzeba tak b[ i ] = a[ i ]; */ Czasem jest wygodnie napisać funkcję kopiującą: void copyinttable( int d[], int s[], int n ) /* Jak z tymi parametrami? */ { /* Niby tak najprościej ... */ /* A może jednak tak */ int i = 0; { for( i = 0; i < n; i++ ) while( --n >= 0 ) d[ i ] = s[ i ]; d[ n ] = s[ n ]; } } Wprowadzenie do programowania w języku C — przetwarzanie tablic Choć może lepiej wykorzystać funkcję memmove lub memcpy (nagłówek mem.h, zgodne z ANSI C): memmove( b, a, N * sizeof( int ) ); memcpy( b, a, N * sizeof( int ) ); memmove( b, a, N * sizeof( a[ 0 ] ) ); memcpy( b, a, N * sizeof( a[ 0 ] ) ); A funkcję memset można wykorzystać do wyzerowania tablicy: memset( b, 0, 5 * sizeof( a[ 0 ] ) ); Tablice znaków — deklaracja, reprezentacja wewnętrzna Do reprezentacji łańcuchów znakowych w języku C wykorzystuje się tablice znakowe. W języku C przyjęto koncepcję łańcuchów ze znacznikiem końca (ang. null terminated strings). Tablice znakowe można inicjować w zwykły sposób, przewidziany dla tablic: #define N 80 char imie[ N ] = {’A’, ’g’, ’a’}; char imie[] = {’A’, ’g’, ’a’, ’\0’}; /* !!! */ choć można wykorzystywać wygodniejszą formę: char imie[ N ] = ”Aga”; Uwaga ð powyższe przypisanie wystąpić może jedynie przy definicji zmiennej! Reprezentacja wewnętrzna: 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 Deklaracja łańcucha zainicjowanego napisem pustym: lub char imie[ N ] = {’\0’}; char imie[ N ] = ””; i jego reprezentacja wewnętrzna: Zmienna imie: \0 0 imie[ 0 ] = ’\0’; ... 1 2 3 4 5 78 79 /* Najprostszy sposób uczynienia łańcucha pustym */ Tablice znaków — typowe operacje Przetwarzanie tablic polega zwykle na „przemaszerowaniu” zmienna indeksową po tablicy, dopóki nie ma końca napisu oznaczanego znakiem ’\0’: int i; char s[ N ]; . . . for( i = 0; s[ i ] != ’\0’; i++ ) < tu jakieś operacje na każdym znaku s[ i ] > Wyprowadzanie napisu do stdout znak po znaku: Wprowadzenie do programowania w języku C — przetwarzanie tablic void put_string( char s[] ) { int i; for( i = 0; s[ i ] != ’\0’; putchar( s[ i++ ] ) ) ; } Wprowadzenie do programowania w języku C — przetwarzanie tablic Przetwarzanie tablic — string.h Do manipulowania tablicami znakowymi opracowano szereg funkcji bibliotecznych (plik nagłówkowy string.h) ... większość z nich można łatwo napisać samemu! Przykład: 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 łańcuchu:%s wynosi:%d“, imie, strlen( imie ) ); Dwie możliwe implementacje funkcji strlen int strlen( char s[] ) int strlen( char s[] ) { { int len = 0; int len; while( s[ len ] != '\0' ) for( len = 0; s[ len ] != '\0'; len++ ) len++; ; return len; return len; } }/* ... ale można jeszcze prościej ;-) */ 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” ); Wprowadzenie do programowania w języku C — przetwarzanie tablic Przykładowe funkcje operujące na tablicach znakowych, cd.: strcpy( a, ”ALA” ); strcpy( b, ”ala” ); /* Konwersja - małe litery na duże: strupr, duże litery na małe: strlwr */ strlwr( a ); /* Po wywołaniu strlwr zmienna a zawiera napis ”ala” */ strupr( b ); /* Po wywołaniu strupr zmienna a zawiera napis ”ALA” */ Zaraz, zaraz tutaj coś jest nie tak ... przecież w języku C parametry przekazywane są przez wartość. Dlaczego „wnętrze” funkcji modyfikuje parametr aktualny wywołania funkcji? Czy tablice są przekazywane inaczej niż parametry innych typów? Przykładowe implementacje funkcji strupr i strlwr void strupr( char s[] ) void strlwr( char s[] ) { { int i; int i; for( i = 0; s[ i ] != '\0'; i++ ) for( i = 0; s[ i ] != '\0'; i++ ) s[ i ] = toupper( s[ i ] ); s[ i ] = tolower( s[ i ] ); } } Przykładowe implementacje funkcji strcpy void strcpy( char d[], char s[] ) void strcpy( char d[], char s[] ) { { int i; int i = 0; for( i = 0; s[ i ] != '\0'; i++ ) while( ( d[ i ] = s[ i ] ) != ‘\0’ ) d[ i ] = s[ i ]; i++; d[ i ] = ’\0’; } } Jak to działa? 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 void strcpy( char d[], char s[] ) { int i; for( i = 0; s[ i ] != '\0'; i++ ) d[ i ] = s[ i ]; d[ i ] = '\0'; } C 5 ... 6 C 5 \0 6 79 \0 ... 19 Wprowadzenie do programowania w języku C — przetwarzanie tablic Przykładowa implementacje funkcji strcat void strcat( char d[], char s[] ) { int i = 0, j = 0; while( d[ i ] != ’\0’ ) i++; while( ( d[ i++ ] = s[ j++ ] ) != ’\0’ ) ; } Co się stanie, gdy tablica docelowa jest za krótka? void main() { char s1[ 5 ] = "AAAA"; char c1 = 'A'; char c2 = 'B'; char s2[ 5 ] = "BBBB"; strcpy( s2, "XXXXXXXXXXXXXXXXXXXX" ); printf( "\ns1 : %s\nc1 : %c\nc2 : %c\ns2 : %s", s1, c1, c2, s2 ); } Wyniki działania programu: Gdzie oryginalna zawartość tablicy s1? Co się stało ze zmienną c2? Dlaczego zmienna c1 jest OK? Jak się ustrzec przed przekroczeniem zakresu tablic? Nigdy nie należy zakładać, że „się uda”, czyli że tablica docelowa jest wystarczająco długa. Należy szacować, przewidywać, jeszcze raz przewidywać i programować defensywnie. Biblioteka funkcji operujących na tablicach znaków zawiera funkcje wykonujące operacje analogiczne do przedstawionych uprzednio, pozwalające na kontrolę liczby znaków biorących udział np. w kopiowaniu. Są to np. funkcje: strncpy, strncat, strnset. #define N 10 #define M 80 char s1[ N ]; char s2[ M ] = "Język C jest świetny lecz pełen pułapek"; strncpy( s1, s2, N - 1 ); s1[ N - 1 ] = '\0'; /* strncpy nie zawsze przekopiuje ’\0’!!! */ strncpy( s1, s2, sizeof( s1 ) - 1 ); s1[ sizeof( s1 ) - 1 ] = ‘\0’; puts( s1 ); /* strncpy nie zawsze przekopiuje ’\0’!!! */