• Nie Znaleziono Wyników

Tablice a wskaniki

N/A
N/A
Protected

Academic year: 2021

Share "Tablice a wskaniki"

Copied!
67
0
0

Pełen tekst

(1)

Podstawy programowania

Podstawy programowania

w j

w języku C++

ęzyku C++

Tablice a zmienne wskaźnikowe

Część dziewiąta

Niniejsze opracowanie zawiera skrót treści wykładu, lektura tych materiałów nie zastąpi uważnego w nim uczestnictwa. Opracowanie to jest chronione prawem autorskim. Wykorzystywanie jakiegokolwiek fragmentu w celach innych niż nauka własna jest nielegalne. Dystrybuowanie tego opracowania lub jakiejkolwiek jego części oraz wykorzystywanie zarobkowe bez zgody autora jest zabronione. Roman Simiński

roman.siminski@us.edu.pl www.us.edu.pl/~siminski Autor

(2)

Nazwa tablicy jako wskaźnik na jej początek

Nazwa tablicy jako wskaźnik na jej początek

Copyright © Roman Simiński Strona : 2

Nazwa tablicy jest interpretowana jako ustalony wskaźnik na jej początek

(pierwszy element).

int tab[ 10 ]; 0 1 2 3 4 5 tab 6 7 8 9 10 elementów

(3)

Nazwa tablicy jako wskaźnik na jej początek, cd. ...

Nazwa tablicy jako wskaźnik na jej początek, cd. ...

Copyright © Roman Simiński Strona : 3

int tab[ 10 ]; int * p; . . . p = tab; 0 1 2 3 4 5 tab 6 7 8 9 10 elementów

Pierwszy element: tab[ 0 ] p

Przypisanie:

Jest równoznaczne z:

p = tab;

(4)

Nazwa tablicy jako wskaźnik na jej początek, cd. ...

Nazwa tablicy jako wskaźnik na jej początek, cd. ...

Copyright © Roman Simiński Strona : 4

tab[ 0 ] = 5; tab[ 1 ] = 1; tab[ 2 ] = 10; . . . tab[ i ] = 22; 0 1 2 3 i tab 8 9 p + 0 *p = 5 *( p + 1 ) = 1 *( p + 2 ) = 10 . . . *( p + i ) = 22 p + 1 p + 2 p + 3 p + i p Odwołania równoważne

(5)

Nazwa tablicy jako wskaźnik na jej początek, cd. ...

Nazwa tablicy jako wskaźnik na jej początek, cd. ...

Copyright © Roman Simiński Strona : 5

Nazwa tablicy z indeksem 0 1 2 3 i tab 8 9 Wska nik z ź przesuni ciemę p Odwołania równoważne Odwołanie: tab[ i ] Odwołanie: *( p + i )

(6)

Nazwa tablicy jako wskaźnik na jej początek, cd. ...

Nazwa tablicy jako wskaźnik na jej początek, cd. ...

Copyright © Roman Simiński Strona : 6

Wyrażenie p + i jest wyrażeniem wskaźnikowym, wskazuje ono na obiekt oddalony

o i obiektów danego typu od p.

Wartość dodawana do wskaźnika jest skalowana rozmiarem typu obiektu

wskazywanego.

Każde odwołanie:

Oraz

tab[ i ];

można zapisać tak:

*( tab + i )

(7)

Uwaga, wskaźnik to nie tablica!

Uwaga, wskaźnik to nie tablica!

Copyright © Roman Simiński Strona : 7

int tab[ 10 ]; int * p = tab; 0 1 2 3 4 5 tab 6 7 8 9 10 elementów p Nazwa tablicy

int tab[ 10 ]

obszar danych + wskaźnika na jego początek

int * p = tab

wskaźnik zakotwiczony o początek tablicy

(8)

Nazwa tablicy to

Nazwa tablicy to

ustalony

ustalony

wskaźnik na jej początek

wskaźnik na jej początek

Copyright © Roman Simiński Strona : 8

Nazwa tablicy jest ustalonym (niemodyfikowalnym) wskaźnikiem na pierwszy jej

element. Nazw tablic nie wolno modyfikować! Zwykłe wskaźniki można.

int tab[ 10 ]; int * p = tab; tab = p; tab++; p = tab + 8; p++;

OK

Źle

(9)

Ciekawostka

Ciekawostka

Copyright © Roman Simiński Strona : 9

Wiemy, że odwołanie:

tab[ i ]

można zapisać tak:

*( tab + i )

Wiemy również, że dodawanie jest przemienne, zatem każde odwołanie:

*( tab + i )

można zapisać tak:

*( i + tab )

Czy zatem odwołanie:

*( i + tab )

można zapisać tak:

i[ tab ]

?

Oraz, że odwołanie

(10)

Ciekawostka, cd. ...

Ciekawostka, cd. ...

Copyright © Roman Simiński Strona : 10

Tak, można, dla kompilatora nie ma to większego znaczenia.

char napis[] = "j zyk c";ę . . .

cout << napis << endl;

0[ napis ] = 'J'; // Zamiast napis[ 0 ] 6[ napis ] = 'C'; // Zamiast napis[ 6 ]

cout << napis << endl;

język c

Język C

(11)

Dlaczego nie wolno przypisywać tablic, posługując się ich nazwami?

Dlaczego nie wolno przypisywać tablic, posługując się ich nazwami?

Copyright © Roman Simiński Strona : 11

int a[ 10 ]; int b[ 10 ];

b = a; // Nie wolno przypisywać do siebie tablic!

Gdyby przypisywanie było możliwe...

0 1 2 3 4 5 a 6 7 8 9 0 1 2 3 4 5 b 6 7 8 9

To po wykonaniu tej linii:

b = a;

gubimy obszar danych tablicy b!

0 1 2 3 4 5 a 6 7 8 9 0 1 2 3 4 5 b 6 7 8 9

(12)

Arytmetyka na wskaźnikach — podsumowanie

Arytmetyka na wskaźnikach — podsumowanie

Copyright © Roman Simiński Strona : 12

Dozwolone operacje wskaźnikowe to:

przypisywanie wskaźników do obiektów tego samego typu,

przypisywanie wskaźników do obiektów innego typu po konwersji,

dodawanie lub odejmowanie wskaźnika i liczby całkowitej,

odejmowanie lub porównanie dwóch wskaźników związanych z ta samą tablicą,

przypisanie wskaźnikowi wartości zero (lub wskazania puste NULL) lub

(13)

Wskaźniki w akcji — metamorfoza funkcji put_string

Wskaźniki w akcji — metamorfoza funkcji put_string

Copyright © Roman Simiński Strona : 13

void put_string( char s[] ) { int i; for( i = 0; s[ i ] != '\0'; i++ ) putchar( s[ i ] ); } Wersja pierwotna char napis[] = "J zyk C i C++";ę

(14)

Wskaźniki w akcji — metamorfoza funkcji put_string

Wskaźniki w akcji — metamorfoza funkcji put_string

Copyright © Roman Simiński Strona : 14

void put_string2( char * s ) { for( ; *s != '\0'; s++ ) putchar( *s ); } Eliminujemy zmienną i char napis[ 80 ] = "C++"; put_string( napis );

Jak to działa... ?

(15)

Wywołanie funkcji put_string

Wywołanie funkcji put_string

Copyright © Roman Simiński Strona : 15

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Kopiowanie parametru aktualnego napis do parametru s char napis[ 80 ] = "C++";

put_string( napis );

C

+

+

\0

napis s

(16)

Parametr s jest kopią wskaźnika napis

Parametr s jest kopią wskaźnika napis

Copyright © Roman Simiński Strona : 16

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Kopiowanie parametru aktualnego napis do parametru s char napis[ 80 ] = "C++";

put_string( napis );

C

+

+

\0

napis s

(17)

Czy obiekt wskazywany przez s jest znacznikiem końca napisu?

Czy obiekt wskazywany przez s jest znacznikiem końca napisu?

Copyright © Roman Simiński Strona : 17

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na pierwszy element tablicy napis char napis[ 80 ] = "C++";

put_string( napis );

C

+

+

\0

napis s

(18)

Znak wskazywany przez s wyprowadzamy jest do stdout

Znak wskazywany przez s wyprowadzamy jest do stdout

Copyright © Roman Simiński Strona : 18

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na pierwszy element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C

(19)

Wskaźnik s przesuwamy na następny znak

Wskaźnik s przesuwamy na następny znak

Copyright © Roman Simiński Strona : 19

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na kolejny element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C

(20)

Czy obiekt wskazywany przez s jest znacznikiem końca napisu?

Czy obiekt wskazywany przez s jest znacznikiem końca napisu?

Copyright © Roman Simiński Strona : 20

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na kolejny element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C

(21)

Znak wskazywany przez s wyprowadzamy jest do stdout

Znak wskazywany przez s wyprowadzamy jest do stdout

Copyright © Roman Simiński Strona : 21

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na kolejny element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C+

(22)

Wskaźnik s przesuwamy na następny znak

Wskaźnik s przesuwamy na następny znak

Copyright © Roman Simiński Strona : 22

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na kolejny element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C+

(23)

Czy obiekt wskazywany przez s jest znacznikiem końca napisu?

Czy obiekt wskazywany przez s jest znacznikiem końca napisu?

Copyright © Roman Simiński Strona : 23

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na kolejny element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C+

(24)

Znak wskazywany przez s wyprowadzamy jest do stdout

Znak wskazywany przez s wyprowadzamy jest do stdout

Copyright © Roman Simiński Strona : 24

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na kolejny element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C++

(25)

Wskaźnik s przesuwamy na następny znak

Wskaźnik s przesuwamy na następny znak

Copyright © Roman Simiński Strona : 25

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na kolejny element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C++

(26)

Czy obiekt wskazywany przez s jest znacznikiem końca napisu?

Czy obiekt wskazywany przez s jest znacznikiem końca napisu?

Copyright © Roman Simiński Strona : 26

void put_string2( char * s ) {

for( ; *s != '\0'; s++ ) putchar( *s );

}

Parametr s wskazuje na kolejny element tablicy napis char napis[ 80 ] = "C++"; put_string( napis );

C

+

+

\0

napis s

C++

(27)

Wskaźniki w akcji — metamorfoza funkcji

Wskaźniki w akcji — metamorfoza funkcji

put_string,

put_string,

wersja 3 i 4

wersja 3 i 4

Copyright © Roman Simiński Strona : 27

void put_string3( char * s ) {

for( ; *s != '\0' ; putchar( *s++ ) ) ;

}

„Kompresja” iteracji for

++

*s

Najpierw pobierz znak wskazywany przez s, użyj go.

Potem zwiększ o jeden wartość wskaźnika s — będzie on wtedy

wskazywał na następny element tablicy.

void put_string4( char * s ) {

while( *s )

putchar( *s++ ); }

Iteracja while nie jest taka zła... Znak '\0' to bajt o wartości 0

(28)

Wskaźniki pod lupą — metamorfoza funkcji

Wskaźniki pod lupą — metamorfoza funkcji

strcpy

strcpy

Copyright © Roman Simiński Strona : 28

char s1[ 80 ] = "Język C"; char s2[ 20 ]; . . . strcpy( s2, s1 ); 0 1 2 3 4 5

J

ę

z

y

k

. . .

79

C

\ 0

6 0 1 2 3 4 5

J

ę

z

y

k

. . .

19

C

\ 0

6

i++

. . .

s1

s2

s

d

void strcpy( char d[], char s[] ) { int i; for( i = 0; s[ i ] != '\0'; i++ ) d[ i ] = s[ i ]; d[ i ] = '\0'; } Wersja początkowa

(29)

Wskaźniki pod lupą — metamorfoza funkcji

Wskaźniki pod lupą — metamorfoza funkcji

strcpy

strcpy

Copyright © Roman Simiński Strona : 29

void strcpy1( char * d, char * s ) { while( *s != '\0' ) { *d = *s; d++; s++; } *d = '\0'; } Odwołania wskaźnikowe To właściwie nie wiele zmienia,

poza wyeliminowaniem zmiennej i

void strcpy2( char * d, char * s ) {

while( *s != '\0' ) *d++ = *s++;

*d = '\0'; }

(30)

Wskaźniki pod lupą — metamorfoza funkcji

Wskaźniki pod lupą — metamorfoza funkcji

strcpy

strcpy

Copyright © Roman Simiński Strona : 30

void strcpy3( char * d, char * s ) {

while( ( *d++ = *s++ ) != '\0' ) ;

}

„Kompresja” — krok drugi

!= '\0'

(

=

*d++

*s++

)

Wartością tego wyrażenia jest znak (bajt) przepisany z obszaru

wskazywanego przez s do obszaru wskazywanego przez d.

Pobierz znak wskazywany, wykorzystaj go,

zwiększ wskaźnik tak, by pokazywał na następny element tablicy.

(31)

Wskaźniki pod lupą — metamorfoza funkcji

Wskaźniki pod lupą — metamorfoza funkcji

strcpy

strcpy

Copyright © Roman Simiński Strona : 31

void strcpy4( char * d, char * s ) {

while( *d++ = *s++ ) ;

}

„Kompresja” — krok trzeci Znak '\0' to bajt o wartości 0

char * strcpy5( char * d, char * s ) {

while( *d++ = *s++ ) ;

return d; }

Tablica d jako rezultat funkcji

Często spotykaną praktyką w funkcjach bibliotecznych jest udostępnianie wskaźnika

do tablicy (jednej z tablic) będącej parametrem:

(32)

Wskaźniki pod lupą — metamorfoza funkcji

Wskaźniki pod lupą — metamorfoza funkcji

strcpy

strcpy

Copyright © Roman Simiński Strona : 32

char s1[ 80 ] = "C i C++"; char s2[ 80 ];

char s3[ 80 ];

Pozwala to na skrócenie kodu, załóżmy następujące definicje tablic s1, s2, s3:

Następujący fragment kodu:

strcpy5( s2, s1 ); strcpy5( s3, s2 ); puts( s3 );

Można zapisać krócej:

(33)

Wskaźniki pod lupą — metamorfoza funkcji

Wskaźniki pod lupą — metamorfoza funkcji

strcpy

strcpy

Copyright © Roman Simiński Strona : 33

char * strcpy5( char * d, char * s ) {

*s = 'A'; . . . }

Modyfikacja tablicy źródłowej dozwolona, choć merytorycznie niepoprawna

char * strcpy6( char * d, const char * s ) {

*s = 'A'; . . . }

Tablica źródłowa jest chroniona

Aby temu zaradzić, można zadeklarować parametr reprezentujący tablicę źródłową

w specyficzny sposób:

W dotychczasowych realizacjach funkcji strcpyX, funkcja może modyfikować

zawartość tablicy źródłowej:

(34)

Wskaźniki pod lupą — zastosowanie modyfikatora

Wskaźniki pod lupą — zastosowanie modyfikatora

const

const

Copyright © Roman Simiński Strona : 34

Można wyróżnić następujące kombinacje definicji wskaźnika z/bez const:

Aby funkcja nie mogła zmodyfikować parametru przekazanego za pośrednictwem

wskaźnika, należy w deklaracji użyć słowa const. Deklaracja:

const char * s;

oznacza, że s jest wskaźnikiem na stały (niemodyfikowalny) obiekt typu char.

const int * const p; // Ustalony wska nika na niemodyfikowalny obiektź

int * const p; // Ustalony wska nika na modyfikowalny obiekt ź const int * p; // Zwykły wska nika na niemodyfikowalny obiekt ź

(35)

Wskaźniki pod lupą — zastosowanie modyfikatora

Wskaźniki pod lupą — zastosowanie modyfikatora

const

const

Copyright © Roman Simiński Strona : 35

Wersja najbardziej restrykcyjna pod lupą:

const int * const p;

int i = 10;

const int * const p = &i; . . . j = *p + 10; . . . *p = 20; . . . p = &j;

Niedozwolone, odwołanie modyfikujące obiekt To jest OK, odwołanie nie modyfikujące obiektu

Niedozwolone, odwołanie modyfikujące wskaźnik

Posługiwanie się ustalonym wskaźnikiem do stałego obiektu:

(36)

Wyznaczanie długości napisu — funkcja strlen klasycznie

Wyznaczanie długości napisu — funkcja strlen klasycznie

Copyright © Roman Simiński Strona : 36

Realizacja w wykorzystaniem iteracji while:

int strlen( char s[] ) { int len = 0; while( s[ len ] != '\0' ) len++; return len; }

Realizacja w wykorzystaniem iteracji for:

int strlen( char s[] ) {

int len;

for( len = 0; s[ len ] != '\0'; len++ ) ;

return len; }

(37)

Wyznaczanie długości napisu — funkcja strlen wskaźnikowo

Wyznaczanie długości napisu — funkcja strlen wskaźnikowo

Copyright © Roman Simiński Strona : 37

int strlen( char * s ) { char * ptr = s; while( *ptr != '\0' ) ptr++; return ( int )( ptr – s ); }

int strlen( char * s ) { char * ptr; for( ptr = s; *ptr != '\0'; ptr++ ) ; return ( int )( ptr – s ); }

Realizacja w wykorzystaniem iteracji while:

(38)

Odwracanie kolejności znaków w napisie — strrev klasycznie

Odwracanie kolejności znaków w napisie — strrev klasycznie

Copyright © Roman Simiński Strona : 38

char * strrev( char s[] ) {

int begin, end;

// Szukanie konca napisu

for( end = 0; s[ end ] != '\0'; end++ ) ;

// Zamiana znakow miejscami

for( begin = 0, end--; begin < end; begin++, end-- ) { char c = s[ begin ]; s[ begin ] = s[ end ]; s[ end ] = c; } return s; }

(39)

Odwracanie kolejności znaków w napisie — strrev wskaźnikowo

Odwracanie kolejności znaków w napisie — strrev wskaźnikowo

Copyright © Roman Simiński Strona : 39

char * strrev( char * s ) {

char * begin, * end;

// Szukanie znacznika konca

for( end = s; *end ; end++ ) ;

// Zamiana znakow miejscami

for( begin = s, end--; begin < end; begin++, end-- ) { char c = *begin; *begin = *end; *end = c; } return s; }

(40)

Dynamiczna alokacja tablic — konwencja języka C

Dynamiczna alokacja tablic — konwencja języka C

Copyright © Roman Simiński Strona : 40

char * s = NULL;

int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę s = malloc( n * sizeof( char ) );

if( s != NULL ) {

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); }

Na tablicach alokowanych dynamicznie na stercie, można wykonywać takie same

operacje, jak na tablicach statycznych. Należy tylko uważnie przydzielać i zwalniać

pamięć.

(41)

Dynamiczna alokacja tablic — konwencja języka C, etap 1-szy

Dynamiczna alokacja tablic — konwencja języka C, etap 1-szy

Copyright © Roman Simiński Strona : 41

char * s = NULL; int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę

s = malloc( n * sizeof( char ) ); if( s != NULL )

{

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); }

Definicja wskaźnika — typ obiektu wskazywanego taki, jak typ elementów tablicy

jakich potrzebujemy. Zerowanie wskaźnika to dobra praktyka.

Pamięć operacyjna Sterta s

(42)

Dynamiczna alokacja tablic — konwencja języka C, etap 2-gi

Dynamiczna alokacja tablic — konwencja języka C, etap 2-gi

Copyright © Roman Simiński Strona : 42

char * s = NULL;

int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę

s = malloc( n * sizeof( char ) ); if( s != NULL )

{

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); }

Zwykle korzystamy ze zmiennej, która pozwoli zapamiętać ilu elementową tablicę

potrzebujemy.

Pamięć operacyjna Sterta s

(43)

Dynamiczna alokacja tablic — konwencja języka C, etap 3-ci

Dynamiczna alokacja tablic — konwencja języka C, etap 3-ci

Copyright © Roman Simiński Strona : 43

char * s = NULL; int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę

s = malloc( n * sizeof( char ) ); if( s != NULL )

{

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); }

Przed utworzeniem tablicy musimy ustalić konkretną liczbę elementów tablicy. Jak

ustalimy tę liczbę zależy od konkretnego zastosowania.

Pamięć operacyjna Sterta s

(44)

Dynamiczna alokacja tablic — konwencja języka C, etap 4-ty

Dynamiczna alokacja tablic — konwencja języka C, etap 4-ty

Copyright © Roman Simiński Strona : 44

char * s = NULL; int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę s = malloc( n * sizeof( char ) );

if( s != NULL ) {

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); }

Przydział pamięci dla tablicy — funkcja malloc otrzymuje liczbę bajtów potrzebnych

do przechowania ustalonej liczby elementów tablicy.

Pamięć operacyjna

s Pamięć operacyjna

?...? Sterta

(45)

Dynamiczna alokacja tablic — konwencja języka C, etap 5-ty

Dynamiczna alokacja tablic — konwencja języka C, etap 5-ty

Copyright © Roman Simiński Strona : 45

char * s = NULL; int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę

s = malloc( n * sizeof( char ) );

if( s != NULL )

{

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); }

Kontrola poprawności przydziału pamięci. Uwaga — to koniecznie niezbędny etap!

Przydzielony obszar pamięci ma przypadkową zawartość.

Pamięć operacyjna

s Pamięć operacyjna

???...? Sterta

(46)

Dynamiczna alokacja tablic — konwencja języka C, etap 6-ty

Dynamiczna alokacja tablic — konwencja języka C, etap 6-ty

Copyright © Roman Simiński Strona : 46

char * s = NULL; int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę

s = malloc( n * sizeof( char ) ); if( s != NULL )

{

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); }

Tak utworzona tablicę można używać tak samo, jak każdą inną tablicę w języku C.

Wszystkie funkcje do manipulowania np. napisami działają bez problemu.

Pamięć operacyjna

s Pamięć operacyjna

J zyk C fajny jest!ę

(47)

Dynamiczna alokacja tablic — konwencja języka C, etap 6-ty

Dynamiczna alokacja tablic — konwencja języka C, etap 6-ty

Copyright © Roman Simiński Strona : 47

char * s = NULL; int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę

s = malloc( n * sizeof( char ) ); if( s != NULL )

{

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); }

Gdy tablica nie jest już potrzebna, zwalniamy przydzieloną pamięć i oddajemy do

puli wolnych bloków. Uwaga, wskaźnik pokazuje dalej na zwolniony obszar pamięci!

Pamięć operacyjna

s Pamięć operacyjna

J zyk C fajny jest!ę

(48)

Dynamiczna alokacja tablic — konwencja języka C, etap 6-ty

Dynamiczna alokacja tablic — konwencja języka C, etap 6-ty

Copyright © Roman Simiński Strona : 48

char * s = NULL; int n;

/* Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n */ę

s = malloc( n * sizeof( char ) ); if( s != NULL )

{

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

free( s ); s = NULL; }

Zerowanie wskaźnika po zwolnieniu pamięci jest dobrą praktyką.

Pamięć operacyjna Sterta s

(49)

Dynamiczna alokacja tablic — konwencja języka C++

Dynamiczna alokacja tablic — konwencja języka C++

Copyright © Roman Simiński Strona : 49

char * s = 0; int n;

// Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n ę s = new char [ n ];

if( s != 0 ) {

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

delete [] s; }

W języku C++ wykorzystujemy operatory new i delete. „Stara” wersja, zakładająca,

że operator new oddaje wskaźnik zerowy w przypadku braku wolnej pamięci:

(50)

Dynamiczna alokacja tablic — konwencja języka C++

Dynamiczna alokacja tablic — konwencja języka C++

Copyright © Roman Simiński Strona : 50

char * s = 0; int n;

// Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n ę try

{

s = new char [ n ];

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s ); . . . delete [] s; } catch( ... ) {

cout << "Brak pami ci dla wykonania tej operacji";ę }

„Nowa” wersja, zakładająca, że operator new generuje wyjątek w przypadku braku

wolnej pamięci:

(51)

Dynamiczna alokacja tablic — konwencja języka C++

Dynamiczna alokacja tablic — konwencja języka C++

Copyright © Roman Simiński Strona : 51

char * s = 0;

int n;

// Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n ę s = new (nothrow) char [ n ];

if( s != 0 ) {

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

delete [] s; }

Wykorzystanie operatora new (nothrow) nie generującego wyjątków, obsługa jak w

„starej” wersji:

(52)

Dynamiczna alokacja tablic — konwencja języka C++

Dynamiczna alokacja tablic — konwencja języka C++

Copyright © Roman Simiński Strona : 52

char * s = 0; int n;

// Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n ę s = new (nothrow) char [ n ];

if( s != 0 ) {

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s );

. . .

delete [] s; s = 0;

}

(53)

Dynamiczna alokacja tablic — konwencja języka C++

Dynamiczna alokacja tablic — konwencja języka C++

Copyright © Roman Simiński Strona : 53

Zerowanie wskaźnika po zwolnieniu pamięci jest dobrą praktyką.

char * s = 0; int n;

// Tu ustalenie liczby potrzebnych elementów i zapami tanie w zmiennej n ę try

{

s = new char [ n ];

strcpy( s, "J zyk C " );ę strcat( s, "fajny jest!" ); puts( s ); . . . delete [] s; s = 0; } catch( ... ) {

cout << "Brak pami ci dla wykonania tej operacji";ę }

(54)

Ważna sprawa — ostrożnie z parametrami wskaźnikowymi!

Ważna sprawa — ostrożnie z parametrami wskaźnikowymi!

Copyright © Roman Simiński Strona : 54

W funkcjach bibliotecznych języka C i C++ stałą praktyką jest deklarowanie

parametrów tablicowych z wykorzystaniem wskaźników, np:

int strlen( char * s );

zamiast

int strlen( char s[] );

Wymaga to dokładnego przeczytania dokumentacji, bowiem programiści często się

mylą. Rozważmy następujący przykład (fragment systemu pomocy firmy Borland):

Prototype

char *gets(char *s);

Description

Gets a string from stdin.

gets collects a string of characters terminated by a new line from the standard input stream stdin and puts it into s. The new line is replaced by a null character (\0) in s.

gets allows input strings to contain certain whitespace characters (spaces, tabs). gets returns when it encounters a new line; everything up to the new line is copied into s.

(55)

Ważna sprawa — ostrożnie z parametrami wskaźnikowymi!

Ważna sprawa — ostrożnie z parametrami wskaźnikowymi!

Copyright © Roman Simiński Strona : 55

Niedokładna lektura dokumentacji może sugerować, że funkcji należy użyć tak:

char * imie;

printf( "Podaj imie: " );

gets( imie ); Pamięć operacyjna

imie Pamięć operacyjna Aga

???

gets( imie )

Gdyby wskaźnik był wyzerowany, kompilator czasem pomoże:

char * imie = NULL;

printf( "Podaj imie: " );

gets( imie ); Pamięć operacyjna

imie Pamięć operacyjna gets( imie )

Null pointer assignment

(56)

Ważna sprawa — ostrożnie z parametrami wskaźnikowymi!

Ważna sprawa — ostrożnie z parametrami wskaźnikowymi!

Copyright © Roman Simiński Strona : 56

A trzeba np. tak:

char imie[ 80 ];

printf( "Podaj imie: " );

gets( imie ); Pamięć operacyjna

imie Pamięć operacyjna Aga

gets( imie )

Lub tak:

char * imie = NULL;

imie = new (nothrow) char[ 80 ]; if( imie != 0 )

{

printf( "Podaj imie: " ); gets( imie );

. . .

delete [] imie; }

Pamięć operacyjna imie Pamięć operacyjna

Aga gets( imie )

(57)

Można tworzyć dynamicznie tablice dowolnych typów

Można tworzyć dynamicznie tablice dowolnych typów

Copyright © Roman Simiński Strona : 57

double * dochody = 0;

int liczbaMiesiecy;

// Tu ustalenie liczby miesiecy okresu rozrachunkowego dochody = new (nothrow) double [ liczbaMiesiecy ];

if( dochody != 0 ) {

for( int miesiac = 0; miesiac < liczbaMiesiecy; miesiac++ ) dochody[ miesiac ] = 0;

. . .

delete [] dochody; }

(58)

Można tworzyć dynamicznie tablice dowolnych typów

Można tworzyć dynamicznie tablice dowolnych typów

Copyright © Roman Simiński Strona : 58

typedef unsigned char byte; byte * bitmapa = 0;

int rozmiarRysunku;

// Tu ustalenie liczby bajtów rysunku bitmapowego bitmapa = new (nothrow) byte [ rozmiarRysunku ]; if( bitmapa != 0 )

{

// Zaladuj bitmape

// Zrob z nia co trzeba // Gdy juz niepotrzebna

delete [] bitmapa; }

(59)

Copyright © Roman Simiński Strona : 59

Tablice a problem ich początkowego rozmiaru

Tablice a problem ich początkowego rozmiaru

Według standardu C89 i C++:

tablica zawsze składa się z ustalonej, i znanej na etapie kompilacji liczby

elementów,

liczba elementów tablicy nie ulega zmianie w trakcie działania programu ―

tablice są statyczne.

W standardzie C99 istnieją tablice VLA (ang. variable length array):

liczba elementów tablicy może być zdefiniowany w trakcie wykonania

programu — może być określona wartością zmiennej, ta wartość nie musi być

znana na etapie kompilacji,

liczba elementów tablicy nie ulega zmianie w trakcie działania programu ― raz

stworzona tablica zachowuje swój rozmiar.

(60)

Copyright © Roman Simiński Strona : 60

Tablice a problem ich początkowego rozmiaru — przykład

Tablice a problem ich początkowego rozmiaru — przykład

Problem

Należy napisać program pozwalający na ewidencjonowanie czasów

osiągniętych przez zawodników maratonu.

Liczba zawodników nie jest dokładnie znana, zakłada się jednak, że startowa

pula numerów jest ograniczona do

300

.

Należy ewidencjonować dokładnie tyle czasów ile to potrzebne, program będzie

być może uruchamiany na starym komputerze przenośnym.

(61)

Copyright © Roman Simiński Strona : 61

C89 i tablica czasów dla liczby zawodników nie większej niż 300

C89 i tablica czasów dla liczby zawodników nie większej niż 300

int lb_zawodnikow;

printf( "\nPodaj liczbe zawodnikow (maks. 300): " ); scanf( "%d", &lb_zawodnikow );

if( lb_zawodnikow > 0 && lb_zawodnikow <= 300 ) {

float * czasy; int nr_zawodnika;

if( ( czasy = malloc( lb_zawodnikow * sizeof( float ) ) ) != NULL ) {

printf( "\nPodaj czasy kolejnych zawodnikow:\n" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) {

printf( "%d>", nr_zawodnika + 1 );

scanf( "%f", &czasy[ nr_zawodnika ] ); }

/* Jakies operacje na tablicy czasy */

printf( "\n\nZawodnik Czas" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) printf( "\n%-10d%-.2f", nr_zawodnika + 1, czasy[ nr_zawodnika ] ); free( czasy );

} else

printf( "\nBrak pamieci!" ); }

else

printf( "Zbyt duza liczba zawodnikow" );

Ustalenie liczby zawodników, których czasy będą ewidencjonowane

(62)

Copyright © Roman Simiński Strona : 62

C89 i tablica czasów dla liczby zawodników nie większej niż 300

C89 i tablica czasów dla liczby zawodników nie większej niż 300

int lb_zawodnikow;

printf( "\nPodaj liczbe zawodnikow (maks. 300): " ); scanf( "%d", &lb_zawodnikow );

if( lb_zawodnikow > 0 && lb_zawodnikow <= 300 ) {

float * czasy; int nr_zawodnika;

if( ( czasy = malloc( lb_zawodnikow * sizeof( float ) ) ) != NULL ) {

printf( "\nPodaj czasy kolejnych zawodnikow:\n" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) {

printf( "%d>", nr_zawodnika + 1 );

scanf( "%f", &czasy[ nr_zawodnika ] ); }

/* Jakies operacje na tablicy czasy */

printf( "\n\nZawodnik Czas" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) printf( "\n%-10d%-.2f", nr_zawodnika + 1, czasy[ nr_zawodnika ] ); free( czasy );

} else

printf( "\nBrak pamieci!" ); }

else

printf( "Zbyt duza liczba zawodnikow" );

Czy liczba zawodników nie przekracza zadanego maksimum?

(63)

Copyright © Roman Simiński Strona : 63

C89 i tablica czasów dla liczby zawodników nie większej niż 300

C89 i tablica czasów dla liczby zawodników nie większej niż 300

int lb_zawodnikow;

printf( "\nPodaj liczbe zawodnikow (maks. 300): " ); scanf( "%d", &lb_zawodnikow );

if( lb_zawodnikow > 0 && lb_zawodnikow <= 300 ) {

float * czasy; int nr_zawodnika;

if( ( czasy = malloc( lb_zawodnikow * sizeof( float ) ) ) != NULL ) {

printf( "\nPodaj czasy kolejnych zawodnikow:\n" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) {

printf( "%d>", nr_zawodnika + 1 );

scanf( "%f", &czasy[ nr_zawodnika ] ); }

/* Jakies operacje na tablicy czasy */

printf( "\n\nZawodnik Czas" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) printf( "\n%-10d%-.2f", nr_zawodnika + 1, czasy[ nr_zawodnika ] ); free( czasy );

} else

printf( "\nBrak pamieci!" ); }

else

printf( "Zbyt duza liczba zawodnikow" );

Tablica ewidencjonująca czasy zostanie przydzielona dynamicznie

(64)

Copyright © Roman Simiński Strona : 64

C89 i tablica czasów dla liczby zawodników nie większej niż 300

C89 i tablica czasów dla liczby zawodników nie większej niż 300

int lb_zawodnikow;

printf( "\nPodaj liczbe zawodnikow (maks. 300): " ); scanf( "%d", &lb_zawodnikow );

if( lb_zawodnikow > 0 && lb_zawodnikow <= 300 ) {

float * czasy; int nr_zawodnika;

if( ( czasy = malloc( lb_zawodnikow * sizeof( float ) ) ) != NULL ) {

printf( "\nPodaj czasy kolejnych zawodnikow:\n" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) {

printf( "%d>", nr_zawodnika + 1 );

scanf( "%f", &czasy[ nr_zawodnika ] ); }

/* Jakies operacje na tablicy czasy */

printf( "\n\nZawodnik Czas" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) printf( "\n%-10d%-.2f", nr_zawodnika + 1, czasy[ nr_zawodnika ] ); free( czasy );

} else

printf( "\nBrak pamieci!" ); }

else

printf( "Zbyt duza liczba zawodnikow" );

Przydział pamięci dla tablicy czasów i kontrola poprawności jego wykonania

(65)

Copyright © Roman Simiński Strona : 65

C89 i tablica czasów dla liczby zawodników nie większej niż 300

C89 i tablica czasów dla liczby zawodników nie większej niż 300

int lb_zawodnikow;

printf( "\nPodaj liczbe zawodnikow (maks. 300): " ); scanf( "%d", &lb_zawodnikow );

if( lb_zawodnikow > 0 && lb_zawodnikow <= 300 ) {

float * czasy; int nr_zawodnika;

if( ( czasy = malloc( lb_zawodnikow * sizeof( float ) ) ) != NULL ) {

printf( "\nPodaj czasy kolejnych zawodnikow:\n" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) {

printf( "%d>", nr_zawodnika + 1 );

scanf( "%f", &czasy[ nr_zawodnika ] ); }

/* Jakies operacje na tablicy czasy */

printf( "\n\nZawodnik Czas" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) printf( "\n%-10d%-.2f", nr_zawodnika + 1, czasy[ nr_zawodnika ] ); free( czasy );

} else

printf( "\nBrak pamieci!" ); }

else

printf( "Zbyt duza liczba zawodnikow" );

Operacje na tablicy — przetwarzana jest rzeczywista liczba elementów

(66)

Copyright © Roman Simiński Strona : 66

C89 i tablica czasów dla liczby zawodników nie większej niż 300

C89 i tablica czasów dla liczby zawodników nie większej niż 300

int lb_zawodnikow;

printf( "\nPodaj liczbe zawodnikow (maks. 300): " ); scanf( "%d", &lb_zawodnikow );

if( lb_zawodnikow > 0 && lb_zawodnikow <= 300 ) {

float * czasy; int nr_zawodnika;

if( ( czasy = malloc( lb_zawodnikow * sizeof( float ) ) ) != NULL ) {

printf( "\nPodaj czasy kolejnych zawodnikow:\n" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) {

printf( "%d>", nr_zawodnika + 1 );

scanf( "%f", &czasy[ nr_zawodnika ] ); }

/* Jakies operacje na tablicy czasy */

printf( "\n\nZawodnik Czas" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) printf( "\n%-10d%-.2f", nr_zawodnika + 1, czasy[ nr_zawodnika ] ); free( czasy );

} else

printf( "\nBrak pamieci!" ); }

else

printf( "Zbyt duza liczba zawodnikow" );

Tablica nie jest już dłużej potrzebna, zwolnienie pamięci jej przydzielonej

(67)

Copyright © Roman Simiński Strona : 67

C99 i tablica czasów dla liczby zawodników nie większej niż 300

C99 i tablica czasów dla liczby zawodników nie większej niż 300

#include <stdlib.h>

#include <stdio.h> int main()

{

int lb_zawodnikow;

printf( "\nPodaj liczbe zawodnikow (maks. 300): " ); scanf( "%d", &lb_zawodnikow );

if( lb_zawodnikow > 0 && lb_zawodnikow <= 300 ) {

float czasy[ lb_zawodnikow ]; int nr_zawodnika;

printf( "\nPodaj czasy kolejnych zawodnikow:\n" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) {

printf( "%d>", nr_zawodnika + 1 ); scanf( "%f", &czasy[ nr_zawodnika ] ); }

/* Jakies operacje na tablicy czasy */

printf( "\n\nZawodnik Czas" );

for( nr_zawodnika = 0; nr_zawodnika < lb_zawodnikow; nr_zawodnika++ ) printf( "\n%-10d%-.2f", nr_zawodnika + 1, czasy[ nr_zawodnika ] ); }

else

printf( "Zbyt duza liczba zawodnikow" ); return EXIT_SUCCESS;

}

Tablica VLA o rozmiarze początkowym definiowanym w trakcie działania programu

Cytaty

Powiązane dokumenty

Przenoszenie zakażenia COVID-19 z matki na dziecko rzadkie Wieczna zmarzlina może zacząć uwalniać cieplarniane gazy Ćwiczenia fizyczne pomocne w leczeniu efektów długiego

Tworząc “Salamandra Ultra Trail”, która jest spadkobierczynią “Beskidzkiej 160 na Raty” przyświecała nam ta sama myśl, ma być pięknie, ma być trudno i ma

Tworząc “Salamandra Ultra Trail”, która jest spadkobierczynią “Beskidzkiej 160 na Raty” przyświecała nam ta sama myśl, ma być pięknie, ma być trudno i ma

Tak skonfigurowany System „BluBerd” jest więc systemem samodoskonalą- cym się i z powodzeniem będzie mógł być stosowany w szpitalach, poradniach, klinikach, gabinetach

Do wniosku powinny być dołączone dokumenty potwierdzające spełnienie warunków zawartych w pkt 6 niniejszego regulaminu (najwyższe osiągnięcie zawodnika/drużyny) w

Niezawodny respirator przez cały okres terapii oddechowej: Savina 300 Select umożliwia wentylację inwazyjną i nieinwazyjną w terapii intensywnej i pacjentów przewlekle chorych,

Kurs programowania z Abilix Krypton dla początkujących i średniozaawansowanych obejmuje 15 tematów lekcji dla robotów Krypton v.2 0 i 2 oraz 25 tematów lekcji dla robotów Krypton

Jeżeli projekt będzie realizowany na rzecz dwóch lub większej liczby gmin, podmiot zgłaszający zobowiązany jest w tym miejscu wpisać każdą gminę, na rzecz której