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’!!! */