Tablice

Transkrypt

Tablice
Tablice
• grupa obiektów tego samego typu tablica
• tablica – ciąg obiektów tego samego typu,
zajmujący ciągły obszar w pamięci
• korzyść – zamiast wielu definicji poszczególnych
obiektów jedna wspólna; odniesienia do n-tego
elementu tablicy
• składnia definicji
typ nazwa_tablicy[rozmiar]
np.
int a[10]; rezerwuje w pamięci miejsce dla 10
liczb typu int
rozmiar – to musi być stała, znana juŜ w trakcie
kompilacji; niedopuszczalne
rozwiązanie jak niŜej:
int rozmiar;
cout << ”Jaki ma być rozmiar tabeli ? ”;
cin >> rozmiar;
{
int tablica[rozmiar]; /* TU JEST ŹLE */
...
}
• moŜliwość definicji tablicy o wielkości dopasowanej
do potrzeb – dynamiczna alokacja tablicy
Elementy tablicy
• definicja
int a[4];
określa tablicę 4-elementów: a[0], a[1], a[2], a[3]
• numeracja zaczyna się od zera, kończy na
(rozmiar–1) (inaczej niŜ w innych językach !!!)
• gdy się będziemy odnosili do zakresu 1..rozmiar, to:
o zapominamy o a[0] tracimy jeden element
o próbujemy zmieniać wartość elementu a[rozmiar] – to juŜ
nie jest element tablicy
o kompilator nie kontroluje dopuszczalnego zakresu
indeksu tablicy, nie blokuje próby zmiany wartości
a[rozmiar] – to na ogół prowadzi do zniszczenia
zawartości innej komórki pamięci (zwykle przyległej do
tablicy a)
Przykład:
int a, b[4], c;
int k;
a=c=0;
for (k=0; k<=4; k++)
b[k]=2*k+1;
cout << a << endl;
a=9
Wnioski:
• tablica n-elementowa: numeracja 0..n-1
• element o indeksie n (czyli a[n]) nie istnieje
• kompilator nie zajmuje się sprawdzaniem
poprawności zakresu – to jest rola programisty
• zysk – szybszy dostęp do tablicy
• próba podstawienia elementu o indeksie n –
zniszczenie wartości innych zmiennych
Obsługa tablic
• na ogół pętle for; dwie moŜliwe formy
for (k=0; k<rozmiar; k++)
lub:
for (k=0; k<=rozmiar-1; k++)
Inicjalizacja tablic
• podobnie jak dla typów fundamentalnych moŜliwa
inicjalizacja w trakcie definicji
int a[4] = {15,3,0,-7};
/*równowaŜne poniŜszemu ciągowi*/
int a[4];
a[0]=15;
a[1]=3;
a[2]=0;
a[3]=-7;
• gdy lista krótsza niŜ rozmiar tablicy – pozostałe
elementy podstawiane zerami
int a[4] = {15, 3};
// a[0]=15; a[1]=3; a[2]=0; a[3]=0;
int a[4] = { };
// zerowanie tablicy
• gdy lista dłuŜsza niŜ rozmiar tablicy – błąd kompilacji
int a[4] = {15, 3, 0, -7, 8}
// błąd kompilacji !!!
• jeszcze jedna forma definicji i inicjalizacji zbiorczej
int a[ ] = {1, 2, 3, 4, 5, 6};
o nie podajemy rozmiaru tablicy
o kompilator liczy ilość elementów w liście, na tej podstawie
ustala rozmiar tablicy, rezerwuje pamięć i ją inicjalizuje
Działania na tablicach
• w C nie ma globalnych operacji na tablicach
• musimy określić działanie dla kaŜdej ze składowych
int a[4] = {0, 1, 2, 3};
int b[4] = {1, 2, 3, 4};
int c[4], k;
c = a + b;
// operacja nielegalna !!!!!
for (k=0; k<4; k++)
c[k] = a[k] + b[k];
Tablica jako argument funkcji
• dwa przypadki
o funkcja zaleŜy od pojedynczego (skalarnego) argumentu i
jest wywoływana z argumentem aktualnym równym
jednemu z elementów tablicy
o funkcja zaleŜy od tablicy jako całości
• przypadek pierwszy
int kwadrat(int k)
{
return k*k;
}
int main()
{
int w1, w2, m=5;
int a[100]={1,2,3,4,5,6};
w1=kwadrat(m);
w2=kwadrat(a[4]);
}
• przekazanie poprzez wartość (jak do tej pory)
• mechanizm taki sam, jak dla typów fundamentalnych
• przypadek drugi
Przykłady:
• funkcja, która oblicza długość wektora
reprezentowanego przez tablicę
• transformacja tablicy zawierającej temp. Fahrenheita
na tablicę z temperaturami w skali Celsjusza
Problemy:
• jak przekazać całą tablicę (często – duŜo wartości)?
• jak zwrócić wynik, który jest tablicą?
Rozwiązanie – przekazanie tablicy przez adres. Jest
ono oparte na dwóch zasadach ogólnych:
1. Tablicę przekazujemy do funkcji podając adres
jej początku
2. Nazwa tablicy jest jednocześnie adresem jej
zerowego elementu
#include <cmath>
#include <iostream>
using namespace std;
double dlugosc(double x[ ], int n)
{
int k;
double s=0.0;
for (k=0; k<n; k++)
s+=x[k]*x[k];
return sqrt(s);
}
int main()
{
double a[3]={1.1, -0.7, 1.0};
cout << “Dlugosc wektora ddd = ” << dlugosc(a, 3)) << endl;
}
void konwertuj_t(float t[ ], int n)
{
int k;
for (k=0; k<n; k++)
t[k]=5.0*(t[k] – 32.0)/9.0;
}
int main()
{
int m;
float temp[101];
for (m=0; m<101; m++)
temp[m]=m;
konwertuj_t(temp, 101);
}
UWAGI:
• wywołanie tablicy jako argumentu funkcji – wypisanie
nazwy tablicy (bez nawiasów) – to określa adres początku
tablicy
• w definicji funkcji (nagłówku) – określamy tylko typ tablicy;
nie określamy jej rozmiaru. Dlatego funkcja będzie działać
dobrze dla kaŜdej tablicy danego typu (bez względu na
rozmiar)
• podanie tylko adresu początku nie określa rozmiaru tablicy
– zwykle potrzebny drugi parametr określający jej rozmiar
• przekazany adres słuŜy do zbudowania takiego sposobu
obsługi tablicy t[ ] w funkcji, by odniesienie się do t[5] było
dokładnie tym, co odniesienie się do temp[5] –
manipulujemy oryginałem tablicy
• jeŜeli d[ ] – tablica (np. int d[100]; ) to:
o d – adres zerowego elementu
o d – stała (nie wolno zmieniać jej wartości)
o d nie jest l-wartością
int d[100]; *wd;
wd=d;
d = cokolwiek;
/* legalna instrukcja */
/* nielegalna instrukcja; błąd !!! */
Tablice znakowe
• specjalny, jeden z najczęściej uŜywanych rodzajów
tablic; słuŜący do przechowywaniu napisów
• definicja (deklaracja)
char nazwa[rozmiar];
• przechowywanie tekstu:
o kolejne elementy – kody ASCII kolejnych znaków
o po umieszczeniu wszystkich znaków kolejnemu
elementowy przypisujemy ‘\0’ (inaczej NULL) – znak
końca stringu
o pozycja znaku NULL – długość stringu
o napis w C/C++ - ciąg liter zakończony znakiem NULL –
C-string
Wniosek: tablica tekstowa o rozmiarze NMAX moŜe
przechować string o maksymalnej długości NMAX – 1
Inicjalizacja tablic tekstowych
• jak dla wszystkich tablic moŜliwa inicjalizacja przy
okazji definicji:
• moŜliwe róŜne formy inicjalizacji
char napis[80] = { ”kot”}
k o t 0
.....
0 1 2 3 4 5
0 0
78 79
char napis[80] = { ‘k’, ‘o’, ‘t’};
k o t 0 0 0
0 1 2 3 4 5
.....
0 0
78 79
char napis[ ] = { ”kot”}
k o t 0
0 1 2 3
char napis[ ] = { ‘k’, ‘o’, ‘t’};
k o t
0 1 2
• ostatnia inicjalizacja nie daje C-stringu (brak znaku NULL
zaznaczającego koniec napisu) – jest to tylko zbiór
pojedynczych liter
• długość C-stringu – ilość aktualnie przechowywanych
znaków
#include <cstring>
int k;
char s[80]=”Ala ma kota”;
cout << “sizeof: “<<sizeof(s) << endl;
k=0;
while(s[k]) k++;
cout << “zliczenia: “ <<k << endl;
cout << “strlen: “ << strlen(s);
Działania na stringach
• zbiorcze podstawienie moŜliwe tylko w czasie
inicjalizacji; później nie jest moŜliwe:
char a[80];
a[80] = ”kot”;
a = ”kot”;
// operacja niedozwolona !!!
// operacja niedozwolona !!!
• aby zmienić juŜ istniejący C-string – uŜyj odpowiedniej
funkcji
Przykłady:
kopiowanie stringu z tablicy wzor[ ] do tablicy kopia[ ]
#include <iostream>
using namespace std;
void strcpy(char kopia[], char wzor[])
{
int k;
for (k=0;
; k++)
{
kopia[k]=wzor[k];
if (kopia[k]==0) break;
}
}
int main()
{
char tekst[30]={”To jest napis 1”};
char a[30];
strcpy(a, tekst);
cout << a << ”\n”;
strcpy(a,”A to jest napis 2”);
cout << a << endl;
}
To jest napis 1
A to jest napis 2
• podawanie rozmiarów tablic niekonieczne – kompilator
sam je ustali na podstawie połoŜenie znaku NULL
• istnieje wiele moŜliwości realizacji funkcji strcpy – oto
przykład:
void strcpy(char kopia[ ], char wzor[ ]);
{
int k=0;
while (kopia[k]=wzor[k])
k++;
}
• niebezpieczeństwo: rozmiar tablicy wzor większy niŜ
maksymalny dopuszczalny rozmiar tablicy kopia
• zagroŜenie – brak sprawdzenia dopuszczalnego zakresu
indeksu tablicy, zniszczenie zawartości pewnej części
pamięci
• rozwiązanie - modyfikacja: funkcja strncpy – kopiowanie
maksymalnie n znaków
void strncpy(char kopia[ ],char wzor[ ],int n);
{
int k=0;
while (k<n && (kopia[k]=wzor[k]))
k++;
kopia[k]=0;
}
• wiele moŜliwych funkcji operujących na stringach
• wiele juŜ gotowych – część biblioteki standardowej
• uŜycie wymaga włączenia string.h (lub cstring)
Przykłady - string.h (lub cstring)
char *strcpy(s, ct)
kopiuje tekst z ct do s łącznie ze znakiem
‘\0’; zwraca s
char *strncpy(s,ct,n) kopiuje co najwyŜej n znaków z ct do s;
zwraca s
char *strcat(s, ct)
dopisuje znaki z ct na koniec s; zwraca s
char *strncat(s,ct,n) dopisuje co najwyŜej n znaków z ct na
koniec s; kończy s znakiem ‘\0’, zwraca s
Int strcmp(cs, ct)
porównuje teksty zawarte w cs i ct;
zwraca wartość<0 gdy cs<ct; zero gdy
cs==ct; >0 gdy cs>ct
char *strchr(cs,c)
zwraca wskaźnik do pierwszego
wystąpienia znaku c w tekście cs lub
NULL, jeśli ten znak nie występuje
Stringi – podsumowanie:
•
•
•
•
•
kolejne znaki – kolejne miejsca tabeli
koniec stringu – znak NULL
nazwa – jednoznaczny adres początku tablicy
do funkcji – wysyłamy nazwę (adres stringu)
nie podajemy długości – funkcja moŜe wyznaczyć ją
sama
Tablice wielowymiarowe
• tablice moŜna tworzyć z róŜnych obiektów – w
szczególności z tablic; tablica wielowymiarowa
float aaa[4][2]; /* aaa jest tablicą czterech elementów, z
których kaŜdy jest tablicą dwuelementową liczb typu float */
• obowiązuje zapis:
aaa[i][j]
a nie
aaa[i,j]
• elementy tablicy aaa[4][2]:
aaa[0][0]
aaa[2][0]
aaa[0][1]
aaa[2][1]
aaa[1][0]
aaa[3][0]
aaa[1][1]
aaa[3][1]
przechowywane są w pamięci tak, by najszybciej zmieniał się
skrajny prawy indeks
• inicjalizacja zbiorcza
float aaa[4][2]={1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}
jest równowaŜna ciągowi instrukcji:
aaa[0][0]=1.0;
aaa[0][1]=2.0;
aaa[1][0]=3.0;
...
aaa[4][0]=7.0;
aaa[4][1]=8.0;
• wyznaczenie względnego adresu elementu tablicy
t[N][M] – tablica dwuwymiarowa
element t[1][0] – przesunięty o M pozycji w stosunku do t[0][0]
element t[i][j] – przesunięty o i*M + j pozycji w stosunku do
t[0][0]
Wniosek: znajomość M konieczna do wyznaczenia offsetu –
waŜne gdy przekazujemy tablicę wielowymiarową jako
argument funkcji
• tablice wielowymiarowe jako argumenty funkcji
#include <iostream>
#define N 4
#define M 3
#define K 2
using namespace std;
void mno_mac(int a[ ][M], int b[ ][K], int c[ ][K])
{
int n,m,k,s;
for (n=0; n<N; n++)
{
for (k=0; k<K; k++)
{
for (s=0,m=0; m<M; m++)
s+=a[n][m]*b[m][k];
c[n][k]=s;
}
}
}
int main()
{
int aa[N][M]={1,2,3, 1,2,3, 1,2,3, 1,2,3};
int bb[M][K]={1,2,1,2,1,2};
int cc[N][K];
mno_mac(aa, bb, cc);
cout << cc[0][0] << cc[0][1]) << eoln;
}
Musimy przekazać:
• typ elementów tablicy
• wymiary tablicy (oprócz lewego skrajnego)
• bliski związek tablic i wskaźników – temat następnego
wykładu