METODY I JĘZYKI PROGRAMOWANIA Programowanie w języku C
Transkrypt
METODY I JĘZYKI PROGRAMOWANIA Programowanie w języku C
Zmienne dynamiczne w języku C Pamięć komputera, dostępna dla programu, dzieli się na cztery obszary: • kod programu, • dane statyczne ( np. stałe i zmienne globalne programu), • dane automatyczne (zmienne lokalne funkcji - tworzone i usuwane automatycznie przez kompilator na stosie (ang. stack)) • dane dynamiczne (zmienne, tworzone i usuwane w trakcie pracy programu w obszarze tzw. STERTY (ang. heap)) Zmienne dynamiczne METODY I JĘZYKI PROGRAMOWANIA → są to zmienne tworzone przez programistę w trakcie pracy programu (na stercie) → dostęp do takiej zmiennej możliwy jest jedynie poprzez jej adres w pamięci (przechowywany w zmiennej wskaźnikowej). W języku „C” do dynamicznego przydzielania pamięci (tworzenia zmiennych dynamicznych) służyły specjalne funkcje z biblioteki < alloc.h > void ∗malloc( size_t rozmiar ); // przydział bloku o zadanej wielkosci void ∗calloc( size_t il_elementow, size_t rozmiar); Programowanie w języku C void free( void ∗wskaznik); notatki z wykładów unsigned coreleft( void ); - Zmienne dynamiczne w języku C - Tablice wielowymiarowe - Dostęp do dowolnego obszaru pamięci operacyjne - Wskaźniki funkcji // przydział tablicy // zwolnienie wskazywanego obszaru // sprawdzenie wolnego miejsca na stercie np. void main( void ) { int ∗wsk; // zmienna wskaźnikowa do zapamiętania adresu liczby int • • • wsk = (int∗) malloc( sizeof(int) ); // przydzielenie pamięci na liczbę int if( wsk == NULL ) { printf( ”Błąd przydziału pamięci” ); return; • • • // przykładowe operacje na dynamicznej liczbie int ∗wsk = 10; ∗wsk ∗= 2; printf( ”%d”, ∗wsk ); scanf( ”%d”, wsk ); • • • free( wsk ); // zwolnienie pamięci przed zakończeniem programu } 1 2 Przykład operacji na strukturze utworzonej dynamicznie: Przykład dynamicznej tablicy o ilości elementów wczytywanej z klawiatury: int rozmiar_tablicy; double ∗tablica_liczb; printf( ”Ile liczb chcesz wprowadzić: ” ); scanf( ”%d”, &rozmiar_tablicy ); if( tablica_liczb = (double∗) calloc( rozmiar_tablicy, sizeof(double) ) ) { for( int i = 0; i < rozmiar_tablicy, i++ ); ∗( tablica_liczb+i ) = 100; // tablica_liczb[ i ] = 100; • • • free( tablica_liczb ); } // ( typ <struct dane_osobowe> był zdefniniowany na poprzednim wykładzie ) struct dane_osobowe ∗wsk_osoby; wsk_osoby = (struct dane_osoby∗) malloc( sizeof(struct dane_osoby) ); if( wsk_osoby ) // if( wsk_osoby != NULL ) { printf( ”Podaj nazwisko: ” ); scanf( ”%s” ,wsk_osoby −> nazwisko ); • • • printf( ”Podaj stypendium: ” ); scanf( ”%lf” , &(wsk_osoby −> stypendium) ); • • • free( wsk_osoby ); } W języku „C++” do dynamicznego przydzielania pamięci wygodniej jest wykorzystywać operatory new i delete : Operacje na dynamicznej tablicy struktur:: <wskaźnik_na_obiekt> = new <typ_obiektu> [parametry_inicjacyjne] ; delete <wskaźnik_na_obiekt> ; int rozmiar_tablicy; struct dane_osobowe ∗baza; np. int∗ wsk ; // wskaźnik na zmienną typu całkowitego wsk = new int ; // utworzenie nowego obiektu (nowej zmiennej int) if( wsk != NULL ) { ∗wsk = 10 ; // przypisanie wartości (poprzez wskaźnik) printf( ”%d” , ∗wsk ); // wydrukowanie zawartości zmiennej dynam. • • • delete wsk ; } Porównanie utworzenia zwykłej tablicy i tablicy dynamicznej: const ROZMIAR_TABLICY = 100; double zwykła_tablica[ ROZMIAR_TABLICY ]; int rozmiar_tablicy; cout << ”Ile liczb chcesz wprowadzić: ” ; cin >> rozmiar_tablicy ; printf( ”Ile liczb chcesz wprowadzić: ” ); scanf( ”%d”, &rozmiar_tablicy ); baza = (dane_osobowe∗) calloc( rozmiar_tablicy, sizeof(dane_osobowe) ); if( baza == NULL ) { printf( ”Błąd przydziału pamięci” ); exit; } • • • // usunięcie zmiennej dynam. (zwolnienie pamięci) // wczytanie danych kilku osób do utworzonej dynamicznej tablicy for( int i = 0; i < rozmiar_tablicy, i++ ); { printf( ”Podaj nazwisko: ” ); scanf( ”%s” , ( baza+i ) −> nazwisko ); printf( ”Podaj stypendium: ” ); scanf( ”%lf” , &( ( baza+i ) −> stypendium) ); • • • } • • • double ∗tablica_dynamiczna; tablica_dynamiczna = new double[ rozmiar_tablicy ]; • • • if( baza != NULL ) free( baza ); // zwolnienie pamięci przed zakończeniem programu delete [ ] tablica_dynamiczna; 3 4 int tab[ 3 ][ 5 ] ; int i, j ; TABLICE WIELOWYMIAROWE Zapis indeksowy int tab[ 3 ][ 5 ] ; int i, j ; for( i=0 ; i<3 ; i++ ) for( j=0 ; j<5 ; j++ ) { printf( ” TAB[ %d , %d ]= ”, i, j ); scanf( ”%d” , &tab[ i ] [ j ] ); } tab[ i ][ j ] == ∗( ∗(tab + i) + j) np. tab[ 0][ 0] == ∗(∗(tab+0)+0) == ∗∗tab Dlaczego tab jest typu int (∗)[5] a nie typu int ∗∗ ? int ∗∗tab2 → Wskaźnik na wskaźnik zmiennej: // cout << ” TAB[ ” << i << ”,” << j << ”] =” // cin >> tab[ i ] [ j ] Reprezentacja tablicy int tab[3][5] w pamięci komputera: int ∗∗tab2 → Wskaźnik na tablicę wskaźników tablice: Operacje na tablicy dwuwymiarowej w zapisie wskaźnikowym int tab[ 3 ][ 5 ] ; int i, j ; for( i=0 ; i<3 ; i++ ) for( j=0 ; j<5 ; j++ ) { printf( ” TAB[ %d , %d ]= ”, i, j ); scanf( ”%d” , ∗(tab + i) + j ); } Operacje na tablicy dwuwymiarowej bez wykorzystywania indeksów liczbowych: int tab[ 3 ][ 5 ] ; int (∗wsk_w) [ 5 ] ; // wskaźnik na wiersz tzn. na 5-cio elementową tablicę int int∗ wsk_k ; // wskaźnik na kolumnę tzn. na liczbę int for( wsk_w = tab ; wsk_w < tab + 3 ; wsk_w++ ) for( wsk_k = ∗wsk_w ; wsk_k < ∗wsk_w + 5 ; wsk_k++ ) { printf( ” TAB[ %d , %d ]= ”, wsk_w − tab, wsk_k − ∗wsk_w ); scanf( ”%d” , wsk_k ); } // przykładowy program tworzący strukturę danych j.w. int tab_k_0 [ 5 ] ; int tab_k_1 [ 5 ] ; int tab_k_2 [ 5 ] ; int∗ tab2 [ 5 ] = { tab_k_0, tab_k_1, tab_k_2 } ; // lub inaczej: int∗ tab_w [ 5 ] = { tab_k_0, tab_k_1, tab_k_2 } ; int∗∗ tab2 = tab_w ; // zapis liczby 111 do wybranego elementu tablicy tab2 ∗(∗(tab2+2) + 3) = 111 ; tab2 [ 2 ][ 3 ] = 111 ; // zamiana miejscami wierszy o indeksach 0 i 2 int∗ wsk_pom ; wsk_pom = ∗tab2 ; ∗tab2 = ∗(tab2 + 2) ; ∗(tab2 + 2) = wsk_pom ; 5 // wsk_pom = ∗(tab2 + 0) ; // ∗(tab2 + 0) = ∗(tab2 + 2) ; 6 DOSTĘP DO DOWOLNEGO OBSZARU PAMIĘCI OPERACYJNEJ Dostęp do zmiennej poprzez nazwę lub wskaźnik: Obszar pamięci widziany jako tablice o różnej strukturze: double wzrost ; double ∗wysokość ; wysokość = &wzrost ; wzrost = 170 ; // równoważne ∗wysokość = 170 ; Dostęp do zmiennej dynamicznej poprzez wskaźnik: double ∗wysokość ; wysokość = new double ; sizeof(double) ) ; int i ; double A[ 2 ]; for( i=0 ; i < 2; i++ ) A[ i ] = 0 ; // wysokość = (double∗) malloc( long∗ B = (long∗) A ; for( i=0 ; i < 4; i++ ) B[ i ] = 0 ; ∗wysokość = 170 ; Dostęp do dowolnych bajtów pamięci: int∗ C = (int∗) A ; for( i=0 ; i < 8; i++ ) C[ i ] = 0 ; double far ∗wysokość ; wysokość = (double far∗) adres_początku_8_bajtów_w_pamięci ; char∗ D = (char∗) A ; for( i=0 ; i < 16; i++ ) D[ i ] = 0 ; // na komputerach typu IBM PC: wysokość = (double far∗) MK_FP( segment_adresu , offset_adresu ) ; ∗wysokość = 170 ; Bezpośredni zapis do pamięci ekranu (wypełnianie spacjami, biały znak, czarne tło): Np. bezpośredni zapis znaku do pamięci obrazu: // 4000-elementowa tablica znaków (25 ∗ 80 ∗ 2) char ekran_A = (char∗) MK_FP( 0xB800 , 0x0000 ) ; for( i=0 ; i < 4000; i+=2 ) { ekran_A[ i ] = 32 ; ekran_A[ i+1 ] = 15; } char far ∗pierwszy_znak ; pierwszy_znak = (char far∗) MK_FP( 0xB800 , 0x0000 ) ; ∗pierwszy_znak = ‘A’ ; // równoważne: gotoxy( 1, 1 ); cprintf( ”A” ); // 2000-elementowa tablica word (25 ∗ 80 ∗ 1) unsigned int ekran_B = (unsigned int∗) MK_FP( 0xB800 , 0x0000 ) ; for( i=0 ; i < 2000; i++ ) ekran_B[ i ] = 32 + 15∗256 ; Wyświetlanie bajtów kodujących zmienne: double zmienna = 111; unsigned char ∗wsk_bajtu ; wsk_bajtu = (unsigned char ∗) &zmienna ; for( int i=0 ; i < sizeof(double) ; i++) cout << ∗wsk_bajtu++ ; // dwuwymiarowa tablica 25-wierszy i 80-kolumn unsigned int (∗ekran_C) [ 80 ] ; ekran_C = (unsigned int (∗) [ 80 ]) MK_FP( 0xB800 , 0x0000 ) ; for( int w=0 ; w < 25; w++ ) for( int k=0 ; k < 80; k++ ) ekran_C[ w ][ k ] = 32 + 15∗256 ; 7 8 funkcja qsort WSKAŹNIKI DO FUNKCJI → nazwa funkcji jest stałą równą adresowi kodu funkcji w pamięci komputera (analogicznie jak nazwa tablicy jest stałą równą adresowi tablicy), #include <conio.h> ... clrscr ; // podanie samej nazwy funkcji jest równoważne podaniu adresu // i nie powoduje żadnej akcji (podobnie jak polecenie 10 ; ) clrscr() ; <stdlib.h> → implementacja algorytmu sortowania szybkiego (ang. quick sort) pozwalająca sortować tablice obiektów dowolnego typu według zadanego kryterium (funkcji definiującej relację porządku) prototyp funkcji: // nazwa funkcji z nawiasami () jest traktowana jako „wywolanie // funkcji” tzn. polecenie wykonania fragmentu kodu umieszczo// nego pod podanym adresem void qsort( void ∗base, // adres poczatku sortowanego obszaru size_t nelem, // rozmiar pojedynczego elementu size_t width, // ilosc sortowanych elementów int (∗fcmp)( void ∗, void ∗) //funkcja porównująca → możliwość pośredniego dostępu do funkcji (poprzez zmienną zawierającą adres / wskazanie na funkcję). Ogólna postać definicji wskaźnika funkcji: ); typ_zwracany_przez_funkcję ( ∗nazwa_zmiennej ) ( parametry_funkcji ); Przykład sortowania tablicy liczb całkowitych void clrscr( void ); // prototyp funkcji clrscr() void (∗nowy_clrscr) ( void ); // definicja zmiennej wskazującej na funkcję ... nowy_clrscr = clrscr; // przypisanie zmiennej nowy_clrscr adresu clrscr ... clrscr(); // bezposrednie wywołaniefunkcji clrscr() nowy_clrscr(); // wywolanie funkcji wskazywanej przez zmienną → możliwość napisania programów wywołujących funkcje, których adresy zostaną podane dopiero w trakcie wykonywania programu. // np. uniwersalna funkcja licząca sume N elementów dowolnego ciągu double Suma_Ciagu( double (∗Element)( int ) , int ilosc ) { double s = 0; for( int i = 0; i < ilosc; i++ ) s += Element( i ); } double Nty_Element ( int n ) { return( 1.0/(n+1) ); } // 1, 1/2, 1/3, 1/4, 1/5, ... #include <stdlib.h> #include <stdio.h> int liczby_rosnaco( const void∗ , const void∗ ); void wyswietl( int [ ], int ); void main( void ) { int tab[10] = { 12, −1, 3, 0, 10, 1, 2, 6, 4, 9 }; wyswietl( tab, 10 ); qsort( tab, 10, sizeof(int), liczby_rosnaco ); wyswietl( tab, 10 ); } int liczby_rosnaco( const void∗ wsk_1, const void∗ wsk_2) { int ∗wsk_liczby_1, ∗wsk_liczby_2; wsk_liczby_1 = (int∗)wsk_1; wsk_liczby_2 = (int∗)wsk_2; return( ∗wsk_liczby_1 − ∗wsk_liczby_2 ); } void wyswietl( int tab[ ], int ilosc ) { int i; for( i = 0; i<ilosc; i++ ) printf( "tab[%d]=%d\n" , i , tab[i] ); } ... printf( ”Suma elementów = %lf”, Suma_Ciagu( Nty_Element , 100 ); ... 9 10 Przykład sortowania tablicy tekstów PRZYKŁADY RÓŻNYCH KOMBINACJI WSKAŹNIKÓW #include <stdio.h> #include <stdlib.h> #include <string.h> Przykładowe elementy: float LICZBA; // liczba rzeczywista float int TAB_INT [ 5 ]; // 5-cio elementowa tablica liczb int int teksty_rosnaco( const void ∗wsk_1, const void ∗wsk_2) { return( strcmp( (char ∗) wsk_1, (char ∗) wsk_2) ); } double FUNKCJA ( int x ) // funkcja z parametrem int zwracająca { // wartość double return x+0.1; } void main( void ) { char tab_tekstow[5][10] = { "Opel", "Audi", "Ford", "Trabant", "Fiat" }; qsort( tab_tekstow, 5, sizeof( tab_tekstow[0] ) , teksty_rosnaco ); } Wskaźniki na w/w elementy: float∗ wsk_liczby ; // wskaźnik na liczbę float wsk_liczby = & LICZBA ; Przykład sortowania bazy danych (tablicy struktur) #include <stdio.h> #include <stdlib.h> #include <string.h> int ( ∗wsk_tab ) [ 5 ] ; wsk_tab = & TAB_INT ; // wskaźnik na 5-cio elementowa tablicę double ( ∗wsk_fun ) ( int ) ; wsk_fun = FUNKCJA ; // wskaźnik na funkcję Tablice elementów: struct student { char nazwisko[31]; char imie[16]; int wiek; char plec; float stypendium; }; float tab_liczb [ 10 ] ; // tablica liczb float int tab_tab [ 10 ] [ 5 ] ; // tablica tablic // − − − − − − − − − − // nie ma tablicy funkcji Tablice wskaźników na elementy: int wedlug_nazwisk( const void ∗wsk_1, const void ∗wsk_2) { struct student ∗osoba_1 = (struct student ∗) wsk_1; struct student ∗osoba_2 = (struct student ∗) wsk_2; return( strcmp( osoba_1−>nazwisko, osoba_2−>nazwisko ); } void main( void ) { #define MAX_IL 100 struct student baza[ MAX_IL ]; ... qsort( baza, MAX_IL, sizeof( struct student ), wedlug_nazwisk ); } float∗ tab_wsk_liczb [ 10 ] ; tab_wsk_liczb [ 2 ] = & LICZBA ; // tablica wskaźników na liczby int (∗ tab_wsk_tab [ 10 ] ) [ 5 ] ; tab_wsk_tab [ 2 ] = & TAB_INT ; // tablica wskaźników na tablice double (∗ tab_wsk_fun [ 10 ] ) ( int ) ; tab_wsk_fun [ 2 ] = FUNKCJA ; // tablica wskaźników na funkcje Funkcje zwracające elementy: float FUNKCJA_E1 ( void ) { return 0.1; } // funkcja zwracająca liczbę float // − − − − − − − − − − // nie ma funkcji zwracającej tablice // − − − − − − − − − − // nie ma funkcji zwracającej funkcje 11 12 Funkcje zwracające wskaźniki elementów: float∗ FUNKCJA_W1( void ) // funkcja (void) zwracająca { // wskaźnik na liczbę float float∗ wsk_liczby ; wsk_liczby = &LICZBA ; return wsk_liczby ; } int (∗ FUNKCJA_W2( void ) ) [ 5 ] { // wskaźnik na tablicę int (∗wsk_tab)[ 5 ] ; wsk_tab = &TAB_INT ; return wsk_tab ; } // funkcja (void) zwracająca double (∗ FUNKCJA_W3( void ) ) ( int ) { // wskaźnik na funkcję double (∗wsk_fun)( int ); wsk_fun = FUNKCJA ; return wsk_fun ; } // funkcja (void) zwracająca // pięciu liczb int // double (int) TABLICA WSKŹNIKÓW NA FUNKCJE Tablica wskaźników na funkcje double ( int ) double (∗ tab_wsk_fun[ 10 ] ) ( int ) ; Wskaźnik tablicy wskaźników na funkcje double ( int ) double (∗ (∗wsk_tab_wsk_fun) [ 10 ] ) ( int ) ; Funkcja (void) zwracająca wskaźnik tablicy wskaźników na funkcje double(int) double (∗ (∗ fun_wsk_tab_wsk_fun( void ) ) [ 10 ] ) ( int ) { return wsk_tab_wsk_fun ; } 13