Język programowania C++
( wykł. dr Marek Piasecki )
Literatura:
dowolny podręcznik do języka C++ (na laboratoriach → Borland C++ 3.1)
Robert Lafore “Programowanie w języku C przy użyciu Turbo C++”
Jerzy Grębosz “Symfonia C++”
Andrzej Zalewski “Programowanie w językach C i C++ z wykorzystaniem
pakietu Borland C++”
----------------------------------------------
Bjarne Stroustrup “Język C++ “ ← książka napisana przez twórcę C++
Robert Sedgewick “Algorytmy w C ++ “
----------------------------------------------
Brian Kernigham, Dennis Ritchie “Język ANSI C“ ← trochę historii
PROGRAM WYKŁADU
Wstęp, schematy blokowe, struktura programu w języku C++
Typy, operatory i wyrażenia.
Operacje wejścia i wyjścia (podejście proceduralne i obiektowe)
Instrukcje if, if-else, switch. Zagnieżdżanie. Operator ? : .
Instrukcje iteracyjne: while, do-while, for.
Pętle zagnieżdżone. Instrukcje break i continue.
Tablice - deklaracja, inicjacja, operator indeksu.
Tablice w połączeniu z pętlą for. Tablice wielowymiarowe.
Wskaźniki zmiennych, adresy pamięci, arytmetyka wskaźników.
Związek pomiędzy wskaźnikami a tablicami.
Funkcje - deklaracja, definicja, parametry.
Funkcje operujące na pamięci: biblioteka <mem.h>
Łańcuchy znaków. Funkcje łańcuchowe <string.h>
Typ strukturalny - definicja, deklaracja i inicjacja zmiennych.
Zagnieżdżanie struktur. Rozszerzenie struktury o metody składowe.
Obsługa plików zewnętrznych. Pliki binarne i tekstowe.
podejście proceduralne - biblioteka <stdio.h>
podejście obiektowe - klasa fstream
Tablice wskaźników, wskaźniki na tablice.
Rzutowanie wskaźników. Dostęp do dowolnego obszaru pamięci. Wskaźniki na funkcje.
Przykłady różnych kombinacji wskaźników
Dynamiczne przydzielanie pamięci.
PODSTAWOWE POJĘCIA
Program − notacja opisująca proces przekształcania danych wejściowych w dane wyjściowe według pewnego algorytmu.
Dane wejściowe − informacje dostarczone do programu przez użytkownika, w celu umożliwienia wykonania algorytmu
Dane wyjściowe − są generowane przez program i stanowią wyniki działania programu.
Algorytm − określa sposób przekształcania danych wejściowych w dane wyjściowe zgodnie z celem. Algorytm składa się z opisu:
obiektów na których wykonywane są działania,
działań realizujących cel algorytmu,
kolejności działań.
Programowanie − polega na zapisywaniu algorytmów w formie programów zrozumiałych dla komputera.
Kod źródłowy − program napisany w języku takim jak Pascal lub C++,
czyli w języku algorytmicznym − czytelny dla programisty,
Kod wynikowy − program zapisany jako ciąg rozkazów i danych w kodzie maszynowym procesora (w postaci czytelnej dla komputera), najczęściej w postaci liczb kodu dwójkowego.
Proces tworzenia ( kodowania? ) programu:
↓ edytor → ( *.cpp ) kod źródłowy
↓ kompilator → ( *.obj ) kod wynikowy
↓ linker → ( *.exe ) kod wynikowy połączony z bibliotekami
debugger → (step/watch) śledzenie działania, usuwanie błędów
Język C++ jest rozszerzeniem języka C :
typy i zmienne referencyjne, unie anonimowe,
operatory new i delete,
funkcje przeciążone, funkcje z atrybutem inline,
domyślne wartości parametrów funkcji,
przekazywanie parametrów funkcji przez referencję,
klasy i obiekty (programowanie obiektowe)
wzorce
obsługa wyjątków
ZAPIS PROGRAMU ZA POMOCĄ SCHEMATÓW BLOKOWYCH
void main( ) // najprostszy program w języku C / C++
{ }
#include < iostream.h > // wypisanie tekstu na ekranie (C++)
void main( void )
{
cout << ”Czesc ! To ja, twój komputer” ;
}
#include < iostream.h > // proste obliczenia - iloczyn liczb (C++)
void main( )
{
int liczba_1, liczba_2 ;
float wynik ;
cout << endl << ”To jest program obliczajacy iloczyn dwóch liczb ” << endl ;
cout << ”Podaj pierwsza liczbe X = ” ;
cin >> liczba_1 ;
cout << ”Podaj druga liczbe Y = ” ;
cin >> liczba_2 ;
wynik = liczba_1 liczba_2 ;
cout << endl << ”Wynik obliczenia X Y = ” << wynik << endl ;
}
#include < iostream.h > // cykliczne wykonywanie programu
void main( )
{
char znak;
do
{ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
instrukcje programu
cout << endl << ”Czy chcesz zakonczyc program ( T/N ) ? ” ;
cin >> znak ;
} ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅
while( znak != ' t ' ) ;
cout << endl << ”Koniec programu ” ;
}
Proceduralna i obiektowa komunikacja z użytkownikiem
/* proceduralnie: C / C++ */ #include <stdio.h> void main(void) { printf(”Dzien ”); printf(”dobry!\n”); } |
// obiektowo: C++ #include <iostream.h> void main(void) { cout << ”Dzien ” ; cout << ”dobry” << endl ; } |
#include ← dyrektywa dołączenia tekstu zawartego w pliku
stdio.h ← (StandardInputOutput) plik definicji funkcji Wej/Wyj
iostream.h ← (InputOutputStream) plik definicji strumieni obiektowych
main ← zastrzeżona nazwa głównej funkcji programu
void ← typ danej “pustej”
\n ← przejscie do nowego wiersza
\t ← znak tabulacji
\” ← znak cudzysłowu
\\ ← jeden znak \
endl ← manipulator przejścia do nowej linii
// 2 przyklad → proceduralnie #include <stdio.h> #include <conio.h> int x,y,s; void main(void) { clrscr(); printf (”Podaj x = ”); scanf ( ”%d” , &x ); printf (”Podaj y = ”); scanf ( ”%d” , &y ); s = x+y; printf(”Suma x+y = %d\n”, s ); getch(); } |
// 2 przyklad → obiektowo #include <iostream.h> #include <conio.h> int x,y,s; void main(void) { clrscr(); cout << ”Podaj x = ” ; cin >> x ; cout <<”Podaj y = ” ; cin >> y ; s = x+y; cout << ”Suma x+y=” << s << '\n' ; getch(); } |
Definiowanie zmiennych → ustalenie nazwy, typu, rezerwacja pamięci
nazwa_typu nazwa_zmiennej ;
nazwa_typu zmienna_1, zmienna_2, zmienna_3 ;
Podstawowe typy:
Nazwa typu |
Zawartość |
Przedział wartości |
Zajęt. pamięć |
char |
znak |
-128 ÷ 127 |
1 bajt |
int |
liczba całkowita |
-32768 ÷ 32767 |
2 bajty |
long |
liczba całkowita |
-2147mln ÷ 2147mln |
4 bajty |
float |
liczba rzeczyw. |
10-38 ÷ 1038 (7cyfr) |
4 bajty |
double |
liczba rzeczyw. |
10-308 ÷ 10308 (15 cyfr) |
8 bajtów |
Modyfikatory typu:
signed → ze znakiem (±), int char −
unsigned → bez znaku, int char −
short → krótka (mniejsza), int − −
long → długa (większa) int − double
np. unsigned long int dluga_liczba_bez_znaku ;
Wartości domyślne: long = long int
int = signed int
char = signed char
Type Length Range
unsigned char 8 bits 0 ÷ 255
char 8 bits -128 ÷ 127
enum 16 bits -32,768 ÷ 32,767
unsigned int 16 bits 0 ÷ 65,535
short int 16 bits -32,768 ÷ 32,767
int 16 bits -32,768 ÷ 32,767
unsigned long 32 bits 0 ÷ 4,294,967,295
long 32 bits -2,147,483,648 ÷ 2,147,483,647
float 32 bits 3.4 * (10**-38) ÷ 3.4 * (10**+38)
double 64 bits 1.7 * (10**-308) ÷ 1.7 * (10**+308)
long double 80 bits 3.4 * (10**-4932) ÷ 1.1 * (10**+4932)
OPERATORY
operatory arytmetyczne: + dodawanie
− odejmowanie
∗ mnożenie
/ dzielenie
% reszta z dzielenia
operatory przypisania: = zwykłe przypisanie x = 2;
+= przypisanie sumy x+=2; → x = x + 2;
−= przypisanie różnicy x−=2; → x = x − 2;
∗= przypisanie iloczynu x∗=2; → x = x ∗ 2;
/= przypisanie ilorazu x /=2; → x = x / 2;
%= przypisanie reszty x%=2; → x = x % 2;
operatory inkrementacji i dekrementacji:
zmienna++ − inkrementacja zmiennej po wyliczeniu wyrażenia
++zmienna − inkrementacja zmiennej przed wyliczeniem wyrażenia
zmienna−− − dekrementacja zmiennej po wyliczeniu wyrażenia
−−zmienna − dekrementacja zmiennej przed wyliczeniem wyrażenia
np. int x, y = 1;
x = ++ y ; /∗ rezultat: x=2, y=2∗/ x = y ++ ; /∗ rezultat: x=1, y=2∗/
operatory relacyjne: == równe
!= różne
< mniejsze
> większe
<= mniejsze lub równe
>= większe lub równe
operatory logiczne: && koniunkcja (AND)
|| alternatywa (OR)
! negacja (NOT)
bitowe operatory logiczne: & bitowa koniunkcja (AND)
| bitowa alternatywa (OR)
^ bitowa różnica symetryczna (XOR)
<< przesunięcie bitów w lewo
>> przesunięcie bitów w prawo
Priorytety operatorów w języku C:
Operator Opis Przykład
( ) wywołanie funkcji sin()
[ ] element tablicy tab[10]
. element struktury osoba.nazwisko
−> wskazanie elemenu struktury wsk_osoby−>nazwisko
! negacja logiczna if( ! (x >max) ) kontynuuj;
~ negacja bitowa ~(001101) ≡ (110010)
− zmiana znaku (negacja) x = 10 ∗ (− y)
++ inkrementacja (zwiększenie o 1) x + + + y ≡ (x+ +) + y
− − dekrementacja (zmiejszenie o 1) − − y ≠ − − y ≡ − (− y)
& operator referencji (adres elementu) wsk_x = &x
∗ operator dereferencji ∗wsk_x = 10
(type) zmiana typu (typecast) (double) 10 ≡ 10.0
sizeof rozmiar zmiennej lub typu (w bajtach) sizeof( int ) ≡ 2
∗ mnożenie
/ dzielenie
% operacja modulo (reszta z dzielenia) if( x%2 == 0 ) parzyste;
+ dodawanie
− odejmowanie
<< przesunięcie bitowe w lewo 1 << 2 ≡ (0001) << 2 ≡ (0100)
>> przesuniecie bitowe w prawo x = 4 >>1 ≡ x = 2
< mniejszy niż if( liczba < max ) max = liczba;
<= mniejszy lub równy
> wiekszy niż
>= wiekszy lub równy
== równy
!= nierówny (różny od)
& iloczyn bitowy
^ suma bitowa modulo (różnica symetryczna)
| suma bitowa
&& iloczyn logiczny
|| suma logiczna
? : wyrażenie warunkowe
= przypisanie
∗= /= %= += przypisania arytmetyczne
−= <<= >>=
&= ^= |=
, operator przecinka
Funkcja: printf() biblioteka: <stdio.h>
wysyła sformatowane dane do standardowego strumienia wyjściowego (stdout)
int printf ( tekst_sterujący , argument_1 , argument_2 , . . . ) ;
tekst sterujący
jest to stała łańcuchowa (w podwójnych cudzysłowach) zawierająca:
− zwykłe znaki (które są po prostu kopiowane na ekran)
− kody formatujące kolejnych argumentów:
%c − pojedynczy znak
%s − łańcuch znaków
%d − liczba dziesiętna ze znakiem
%f − liczba zmiennoprzecinkowa (notacja dziesiętna)
%e − liczba zmiennoprzecinkowa (notacja wykładnicza)
%g − liczba zmiennoprzecinkowa (krótszy z formatów %f %e)
%u − liczba dziesiętna bez znaku
%x − liczba w kodzie szesnastkowym (bez znaku)
%o − liczba w kodzie ósemkowym (bez znaku)
l − przedrostek (long) stosowany przed: d u x o
np. #include <stdio.h>
void main(void)
{
int x = 10;
long y = 20;
double s;
s = x + y;
printf ( ”%s obliczen %d + %ld = %f” , ”Wynik” , x , y , s );
}
efekt na ekranie → Wynik obliczen 10 + 20 = 30.000000
Aby określić ilość drukowanych cyfr do kodu formatującego można dodać kody długości: %Xd %X.Xf
np. %4d − liczba dziesiętna na czterech pozycjach
%10f − liczba rzeczywista na 10 pozycjach
%10.2f − liczba rzeczywista na 10 pozycjach, 2 cyfry po przecinku
%.3f − liczba rzeczywista z dokladnoscią do 3 cyfr po przecinku
Funkcja: scanf() <stdio.h>
odczytuje dane ze standardowego strumienia wejściowego (stdin)
w/g zadanego formatu i zapamiętuje je pod zadanymi adresami pamięci
int scanf ( tekst_sterujący , adres_1 , adres_2 , . . . ) ;
tekst sterujący → jest to stała łańcuchowa (w podwójnych cudzysłowach) zawierająca instrukcję jak traktować kolejne dane wczytywane ze strumienia (jakie typy zmiennych są pod adresami adres_1, adres_2, ... )
Kody formatujące (podobne jak dla printf() ):
%c − pojedynczy znak
%s − łańcuch znaków
%d − liczba dziesiętna ze znakiem
%f lub %e − liczba zmiennoprzecinkowa
%u − liczba dziesiętna bez znaku
%x − liczba w kodzie szesnastkowym (bez znaku)
%o − liczba w kodzie ósemkowym (bez znaku)
l − przedrostek stosowany przed: d u x o (long int)
l − przedrostek stosowany przed: f e (double)
L − przedrostek stosowany przed: f e (long double)
& − operator adresowania (zwraca adres zmiennej podanej po operatorze)
np. #include <stdio.h>
void main(void)
{
int x;
double y;
char znak;
printf( ”Podaj jedna liczbe calkowita: ” );
scanf ( ”%d” , &x );
printf( ”Podaj jedna liczbe rzeczywista i jeden znak: ”);
scanf ( ”%lf %c” , &y , &znak );
}
Wydruk → Podaj jedna liczbe calkowita:
Odczyt ← 123 ↵
Wydruk → Podaj jedna liczbe rzeczywista i jeden znak:
Odczyt ← 456.789 a ↵
Wynik wczytywania: x == 123, y == 456.789, znak == 'a'
PODSTAWOWE INSTRUKCJE JĘZYKA C++
Nawiasy klamrowe { } są używane do grupowania wielu deklaracji i instrukcji w jedną instrukcję złożoną (jeden blok).
przykład:
#include <stdio.h>
void main( void )
{
int a = 10, b = 20 ;
{
int a = 30 ; // 'przesłonięcie' poprzedniej definicji a
printf( ”A = %d, B = %d \n” , a , b ); // wydruk: A=30, B=20
}
printf( ”A = %d, B = %d \n” , a , b ); // wydruk: A=10, B=20
. . .
if( a > 0 )
{
printf( ”Podaj nową wartość A =” );
scanf( ”%d” , &a );
}
}
Instrukcja warunkowa:
if ( wyrażenie )
instrukcja_1 ;
else
instrukcja_2 ;
− część od słowa else można pominąć,
− instrukcja sprawdza czy wyrażenie jest różne od zera
tzn. if ( wyrażenie ) jest równoważne if ( wyrażenie != 0 )
#include <stdio.h>
void main( void )
{ int a;
printf( *Podaj wartość dodatnią A = ” ); scanf( ”%d” , &a );
if( a < 0 ) a = −a ;
if( a==0 )
{ printf( *A jest zerowe, podaj nową wartość A = ” ); scanf( ”%d” , &a ); }
}
Konstrukcja else-if:
if ( wyrażenie_1 )
instrukcja_1;
else
if ( wyrażenie_2 )
instrukcja_2;
else
if ( wyrażenie_3 )
instrukcja_3;
else
instrukcja_4;
Instrukcja wyboru:
switch ( wyrażenie_całkowite )
{
case wartość_1 : instrukcja_1;
break;
case wartość_2 :
case wartość_3 :
case wartość_4 : instrukcja_234;
break;
default : instrukcja_domyslna;
break;
}
#include <stdio.h>
void main( void )
{
int liczba;
printf( "Podaj wartość liczby całkowitej A =" );
scanf( "%d" , &liczba );
switch( liczba ) {
case 0 : printf( ”Podałeś liczbę zerową” ); break;
case -5 : printf( ”Podałeś liczbę minus pięć” ); break;
case 7 : printf( ”Podałeś liczbę siedem” );
break;
case 9 : printf( ”Podałeś liczbę dziewięć” ); break;
default: printf( "Podałeś inną liczbę niż: 0, -5, 7, 9 " );
break;
}
}
Dalsze przykłady dla instrukcji warunkowej:
#include <stdio.h> // Wartość maksymalna z trzech wczytanych liczb
void main(void)
{
int A, B, C, max;
printf( *Podaj pierwsza liczbe: ” );
scanf( ”%d” , &A );
printf( *Podaj druga liczbe: ” );
scanf( ”%d” , &B );
printf( *Podaj trzecia liczbe: ” );
scanf( ”%d” , &C );
max = A;
if( max < B ) max = B;
if( max < C ) max = C;
printf( ”\n Maksymalna wartosc = %d” , max );
getchar();
}
#include <stdio.h> // Pierwiastki trójmianu kwadratowego Ax2+Bx+C=0
#include <conio.h>
#include <math.h>
void main( void )
{
double a, b, c, delta, x1, x2;
clrscr();
printf( "Podaj pierwsza liczbe A= " );
scanf( "%lf" , &a ); // Uwaga !!! %lf a nie %f
printf( "Podaj druga liczbe B= " );
scanf( "%lf" , &b );
printf( "Podaj trzecia liczbe C= " );
scanf( "%lf" , &c );
delta = b∗b − 4∗a∗c;
if( delta < 0 )
printf( "\n Brak rozwiazan" );
else
if( delta == 0 )
{
x1 = x2 = −b/(2∗a);
printf( "Jest jedno rozwiazanie x1=x2= %f", x1 );
}
else
{
x1 = (−b − sqrt(delta)) / (2∗a); x2 = (−b + sqrt(delta)) / (2∗a);
printf( "Sa dwa rozwiazania x1= %.2f, x2= %.2f", x1, x2 );
}
}
Przykład dla instrukcji wyboru:
#include <stdio.h> // Program „kalkulator” zawierający proste „menu”
void main( void )
{
char znak;
double a, b, wynik;
printf( "Podaj pierwsza liczbe A =" ); // wczytanie dwóchliczb z klawiatury
scanf( "%lf" , &a );
printf( "Podaj druga liczbe B =" );
scanf( "%lf" , &b );
printf( "\n\nMozliwe operacje:" ); // wyswietlenie „menu”
printf( "\n (+) wynik = A + B" );
printf( "\n (−) wynik = A − B" );
printf( "\n (∗) wynik = A ∗ B" );
printf( "\n ( / ) wynik = A / B" );
printf( "\n\nPodaj znak operacji: " );
flushall( ); // wyczyszczenie wszystkich buforów (tutaj->klawiatury)
znak = getchar( ); // wczytanie znaku wybranej operacji
switch( znak ) // instrukcja wyboru jednej z operacji arytmetycznych
{
case '+' : wynik = a + b; break;
case '−' : wynik = a − b; break;
case '∗' : wynik = a ∗ b;
break;
case '/' : wynik = a / b; break;
default: wynik = 0;
printf( "\nBład operatora: podano zły znak operacji" );
break;
}
// wydruk liczb i wyniku z zadana dokladnoscia miejsc po przecinku
printf( "\nWynik obliczen: %.1f %c %.1f = %.2f " , a , znak , b , wynik );
printf( "\n\nKoniec programu. Nacisnij dowolny klawisz" );
fflush( stdin ); // wyczyszczenie bufora strumienia <stdin> tzn. klawiatury
getchar( );
}INSTRUKCJE REPETYCYJNE − PĘTLE
Pętla while( ):
|
while ( wyrażenie )
while ( wyrażenie )
|
Pętla wykonywana jest tak długo jak wartość wyrażenie jest różna od zera
Przykłady:
int i ; // pętla wyświetlająca liczby 1,2,3 ... i = 1; while( i <=10 ) { printf ( ”%2d\n” , i ); i = i + 1; } |
int i = 1; // 1, 2, 3, . . . w innym zapisie while( i<11 ) printf ( ”%2d\n” , i++ );
|
int i ; // pętla wyświetlająca liczby 10,9,8 i = 10; while( i != 0 ) { printf ( ”%2d\n” , i ); i = i − 1; } |
int i = 10; //10, 9, 8, . . . w innym zapisie while( i ) printf ( ”%2d\n” , i −− );
|
Pętla do while( ):
|
do
do
|
Pętla wykonywana jest tak długo jak wartość wyrażenie jest różna od zera
Przykłady:
int i ; // pętla wyświetlająca liczby 1,2,3 ... i = 1; do { printf ( ”%2d\n” , i ); i = i + 1; } while( i<=10);
|
int i = 1; // 1, 2, 3, . . . w innym zapisie do printf ( ”%2d\n” , i ); while( ++i <11 );
|
int i ; // pętla wyświetlająca liczby 10,9,8 i = 10; do { printf ( ”%2d\n” , i ); i = i − 1; } while( i != 0 ); |
int i = 10; // 10, 9, 8, . . . w innym zapisie do printf ( ”%2d\n” , i ); while( − −i );
|
Inne przykłady:
#include <stdio.h>
#include <conio.h>
void przyklad_1( ) //odczytywanie klawiszy do momentu nacisniecia 'k'
{
clrscr(); printf( "Przyklad_1: ilustracja petli <do while>, koniec po nacisnieciu 'k' " );
char znak;
do
{ // za pomoca petli "do while"
printf( " \npodaj znak: " );
znak = getche( );
}
while( znak != 'k' );
printf( " \n\nTeraz przyklad wykorzystania petli <while> " );
znak = 0;
while( znak != 'k' )
{
printf( " \npodaj znak: " );
znak = getche( );
}
printf( " \nWykryto nacisniecie klawisza 'k' "); getch( );
} //przyklad_1
#include <stdlib.h> //dolaczenie biblioteki zawierajacej funkcje "random"
void przyklad_2( ) //odgadywanie wartosci wylosowanej liczby z przedzialu 1-6
{
int liczba_losowa, liczba_wczytana;
liczba_losowa = 1 + random(6); // losowanie liczby
clrscr(); printf( " Przyklad_2: losowanie i odgadywanie liczby z przedziału <1,6> " );
do // rozwiązanie z wykorzystaniem petli "do while"
{
printf("\nOdgadnij wylosowana liczbe: ");
scanf("%d",&liczba_wczytana);
}
while( liczba_wczytana != liczba_losowa );
printf( " \nOdgadles ! Wylosowana liczba to: %d " , liczba_losowa );
printf( " \n\nJeszcze raz to samo z wykorzystaniem petli <while> " );
liczba_wczytana = 0;
while( liczba_wczytana != liczba_losowa )
{
printf( " \nOdgadnij wylosowana liczbe: " );
scanf( "%d" , &liczba_wczytana);
}
printf( " \nOdgadles ! Wylosowana liczba to: %d " , liczba_losowa); getch();
} // przyklad_2
Pętla for( ):
for( wyrażenie_inicjujace ; wyrażenie_testujace ; wyrażenie_modyfikujace )
wykonywana_instrukcja ;
jest równoważna konstrukcji:
wyrazenie_inicjujace ;
while( wyrazenie_testujace )
{
wykonywana_instrukcja ;
wyrazenie_modyfikujace ;
}
int i ; i = 10; while( i != 0 ) { printf ( ”%2d\n” , i ); i = i − 1; } |
int i ;
for( i = 10; i != 0 ; i = i − 1 ) lub int i ; for( i = 10; i ; printf(”%2d\n” , i −−) ) ; |
//przykładowy program wypisujacy tabele kodów ASCII
#include <stdio.h>
void main(void)
{
int n;
printf( ”\n” );
for( n=32; n<256; n++ )
printf( ”%3d = %c\n” , n , n );
}
//prymitywny kalkulator biurowy
#include <stdio.h>
void main(void)
{
double suma=0, liczba;
while( scanf( ”%lf” , &liczba ) > 0 )
printf( ”\t%.2f\n” , suma+=liczba );
}
#include <stdio.h> // program zliczający naciskane klawisze
#include <conio.h>
void main(void)
{
int licznik = 0, klawisz;
printf( *Program zliczający naciskane klawisze. Koniec = ESC” );
do
{
klawisz = getch();
licznik++;
}
while( klawisz != 27 ) // 27 = kod klawisza Escape
printf( ”\n Ilość naciśnietych klawiszy = %d” , licznik );
}
• • • // ten sam program z użyciem pętli for
int licznik;
for( licznik = 0 ; getch() != 27 ; licznik++ )
• • •
#include <stdio.h> //program klasyfikujący naciskane klawisze
#include <conio.h>
#define ESC 27 //definicja kodu klawisza «Escape»
void main(void)
{
int klawisz=0;
clrscr();
while( klawisz != ESC )
{
printf( ”\n\nNaciśnij jakiś klawisz (ESC->Koniec): ” );
klawisz = getch();
if( `a'<=klawisz && klawisz<='z' )
printf( ”-> To jest mala litera.” );
else if( `A'<=klawisz && klawisz<='Z' )
printf( ”-> To jest duza litera.” );
else if( `0'<=klawisz && klawisz<='9' )
printf( ”-> To jest cyfra.” );
else if( klawisz == 13 )
printf( ”-> To jest klawisz ENTER.” );
else if( klawisz == ` ` )
printf(“-> To jest klawisz spacji”);
else
printf(“-> To jest inny klawisz.”);
}
}
#include <stdio.h> //program rozpoznajacy klawisze funkcyjne
#include <conio.h>
#include "def_klaw.h" //dołaczenie pliku zawierajacego definicje klawiszy
void main( void )
{
int klawisz;
clrscr();
do
{
printf( "\n\n Nacisnij jakis klawisz: " );
klawisz = getch( );
switch( klawisz )
{
case ENTER : printf( "To jest ENTER" ); break;
case ESC : printf( "To jest ESCAPE" ); break;
case ZERO : // pierwszy odczytany znak mial kod równy 0
klawisz = getch( );
switch( klawisz )
{
case DELETE : printf( "Delete" ); break;
case UP_ARROW : printf( "Up arrow" ); break;
case DOWN_ARROW : printf( "Down arrow" ); break;
}
break;
case BACKSPACE : printf( "To jest BACKSPACE" ); break;
default : printf( "Nieznany pojedynczy klawisz" ); break;
}
}
while( klawisz != ESC );
}
// Zbior *def_klaw.h* zawierajacy definicje kodow klawiszy
#ifndef DEF_KLAW
#define DEF_KLAW
#define ZERO 0
// klawisze "zwykle" - kodowane za pomoca jednego znaku
#define ESC 27
#define ENTER 13
#define BACKSPACE 8
//klawisze "funkcyjne" - kodowane za pomoca dwoch znakow
#define DELETE 83 // 0, 83
#define UP_ARROW 72 // 0, 72
#define DOWN_ARROW 80 // 0, 80
#define LEFT_ARROW 75 // 0, 75
#define RIGHT_ARROW 77 // 0, 77
#define HOME 71 // 0, 71
#define END 79 // 0, 79
#endif
Tablice w języku C/C++
Ogólna postać definicji tablicy:
typ_elementu nazwa_tablicy [wymiar_1][wymiar_2] . . . [wymiar_N] ;
np.
int tablica [ 10 ]; // 10-cio elementowa tablica liczb całkowitych
char tekst [ 255 ]; // 255-cio elementowa tablica znaków
float macierz [ 5 ] [ 2 ]; // dwuwymiarowa tablica: 5 wierszy po 2 kolumny,
UWAGA:
w języku C tablice są zawsze indeksowane od zera
np. pierwszym elementem tablicy *macierz* jest: macierz[ 0 ][ 0 ]
a ostatnim elementem jest: macierz[ wymiar_1 − 1 ][wymiar_2 − 1]
tzn. macierz[ 4 ][ 1 ]
w języku C nie jest sprawdzana zgodność indeksu z wymiarami tablicy !!!
często jest to przyczyną trudnych do wykrycia błędów.
np. odwołanie: macierz[ 1 ][ 2 ] zwróci w rzeczywistości wartość pierwszego elementu z trzeciego wiersza tzn. macierz[ 2 ][ 0 ]
0 , 0 |
0 , 1 |
|
1 , 0 |
1 , 1 |
1 , 2 |
2 , 0 |
2 , 1 |
|
3 , 0 |
3 , 1 |
|
4 , 0 |
4 , 1 |
|
reprezentacja tej macierzy ↓ w pamięci komputera
0 , 0 |
0 , 1 |
1 , 0 |
1 , 1 |
2 , 0 |
2 , 1 |
3 , 0 |
3 , 1 |
4 , 0 |
4 , 1 |
↑
macierz[ 1 ][ 2 ]
Obszar pamięci zajmowany przez tablicę musi być mniejszy od 64 kB
W implementacji C++ firmy Borland to ograniczenie można obejść używając przy definicji tablicy słowo kluczowe huge .
np. definicja: long double huge tab[ 20000 ];
jest poprawna, chociaż tablica *tab* zajmuje 200 000 bajtów ≈ 196 kB
Definicję tablicy można połączyć z inicjacją jej zawartości:
int tab[ 10 ]; // ← sama definicja bez inicjacji
int tab_inicjowana[ 10 ] = { 20, -3, 12, 1, 0, 7, -5, 100, 2, 5 };
char tab_znakow[ 5 ] = { `a', `B', `\n', `1', `\0' };
float macierz_A[ 3 ][ 2 ] = { {1,1}, {3.5,7.0}, {-15,100} };
float macierz_B[ 3 ][ 2 ] = { 1, 1, 3.5, 7.0, -15, 100 };
Kolejne „inicjatory” zawsze wstawiane są do kolejnych „komórek” tablicy
(w związku z tym można pominąć wewnętrzne nawiasy klamrowe).
Jeżeli lista inicjatorów jest krótsza niż ilość elementów tablicy to pozostałe elementy są uzupełniane zerami lub wskaźnikami NULL
np. definicja:
int tab[ 10 ] = { 20, -3, 12, 1 };
jest równoważna:
int tab[ 10 ] = { 20, -3, 12, 1, 0, 0, 0, 0, 0, 0 };
a definicja:
float macierz[ 3 ][ 2 ] = { {1}, {3.5,7.0} };
jest równoważna:
float macierz[ 3 ][ 2 ] = { {1,0}, {3.5,7.0}, {0,0} };
lub:
float macierz[ 3 ][ 2 ] = { 1, 0, 3.5, 7.0, 0, 0 };
W języku C inicjatorami muszą być stałe, natomiast w języku C++ inicjatorami mogą być zarówno stałe jak i zmienne.
Wykorzystanie stałych do definiowania ilości elementów tablicy:
int tablica [ 100 ] ; // rozmiar zadany bezpośrednio
#define ROZMIAR 100 // definicja stałej w stylu języka C
int tablica [ ROZMIAR ] ;
const ROZMIAR_2 = 100 ; // definicja stałej w stylu języka C++
int tablica_2 [ ROZMIAR_2 ] ;
for( int i=0 ; i < ROZMIAR ; i++ ) // przykład dalszego wykorzystania stałej
printf ( ”%d” , tablica[ i ] );
Przypisywanie / odczytywanie wartości elementów tablicy
void main( )
{
const ROZM = 4 ;
int Tab [ ROZM ] ;
// bezpośrednie przypisanie wartości
Tab[ 0 ] = 0 ;
Tab[ 1 ] = 10 ;
Tab[ 2 ] = - 20 ;
Tab[ 3 ] = 3 ;
// wczytanie zawartości z klawiatury
scanf( ”%d” , &Tab[ 0 ] ) ;
scanf( ”%d %d” , &Tab[ 1 ] , &Tab[ 2 ] ) ;
printf( ” Podaj 4 element tablicy = ” );
scanf( ”%d” , &Tab[ 3 ] ) ;
// wykorzystywanie i wyświetlanie zawartości elementów tablicy
long suma = Tab[0] + Tab[1] + Tab[2] + Tab[3] ;
printf( ” Tab[1] = %5d ” , Tab[0] );
printf( ” Tab[2] = %5d ” , Tab[1] );
printf( ” Tab[3] = %5d ” , Tab[2] );
printf( ” Tab[4] = %5d ” , Tab[3] );
// pośrednie zadawanie wartości indeksu za pomocą zmiennej pomocniczej
int i = 2 ;
Tab[ i ] = 10; // równoważne poleceniu: Tab[ 2 ] = 10;
// zadawanie indeksu elementu z klawiatury
printf( ” Podaj indeks elementu którego wartość chcesz wczytać ” );
scanf( ”%d” , &i );
printf( ” Podaj nową wartość Tab[ %d ] = ” , i );
scanf( ”%d” , &Tab[ i ] );
printf( ” Nowa wartość Tab[ %d ] wynosi %d ” , i , Tab[ i ] );
}
Zastosowanie instrukcji repetycyjnej ”for” do operacji na tablicach
#include <stdio.h>
void main( void )
{
#define ROZMIAR 10
float tablica[ ROZMIAR ]; // definicja tablicy liczb rzeczywistych
int i ;
// inicjowanie zawartości tablicy liczbami parzystymi: 0, 2, 4, 6, ...
for( i = 0 ; i < ROZMIAR ; i++ )
tablica[ i ] = 2∗i ;
// wczytanie zawartości elementów tablicy z klawiatury
for( i = 0 ; i < ROZMIAR ; i++ )
{
printf( ” Podaj Tab[%2d] = ”, i+1 );
scanf( ” %f ” , &tablica[ i ] );
}
// wyświetlenie zawartości elementów tablicy
for( i = 0 ; i < ROZMIAR ; i++ )
printf( ” Tab[%2d] = %10.3f ”, i+1 , tablica[ i ] );
// zsumowanie wartości elementów tablicy
float suma = 0 ;
for( i = 0 ; i < ROZMIAR ; i++ )
suma = suma + tablica[ i ]; // suma += tablica[ i ];
printf( ”Suma wartości elementów tablicy wynosi: %.2f ” , suma );
// zliczenie ilości elementów o dodatnich wartościach
int ilosc = 0 ;
for( i = 0 ; i < ROZMIAR ; i++ )
if( tablica[ i ] > 0 )
ilosc = ilosc + 1 ; // ilość += 1; lub ilość++;
if( ilość>0 )
printf( ”Ilość dodatnich elementów = %d ” , ilosc );
else
printf( ”W tablicy nie ma ani jednego dodatniego elementu ” );
}
Przykłady prostych algorytmów „tablicowych”
#include <stdio.h>
void main( void )
{
#define ROZMIAR 10
int tab[ ROZMIAR ];
int i, ilosc, max, poz;
long suma;
double srednia;
for( i = 0 ; i < ROZMIAR ; i++ ) //--------- wczytanie liczb z klawiatury
{
printf( ”Tab[%2d] = ”, i+1 );
scanf( ”%d” , &tablica[ i ] );
}
i=0; //--------------------------- zliczenie elementów niezerowych
ilosc=0;
while( ROZMIAR − i )
if( tab[i++] )
ilosc++;
suma=0; //----------- wyznaczenie średniej z elementów niezerowych
ilosc=0;
i=0;
do
if( tab[ i ] != 0 )
{
suma += tab[ i ];
ilosc++;
}
while( ++i < ROZMIAR );
if( ilosc )
{
srednia = (double)suma / ilosc;
printf( "\nSrednia niezerowych = %.2f" , srednia );
}
else
printf( "\nNie ma elementow niezerowych" );
max=tab[0]; //----------- wyznaczenie wartości i pozycji maksimum
poz=0;
for( i=1; i<ROZMIAR ; i++ )
if( max<tab[ i ] )
{
max = tab[ i ];
poz = i ;
}
printf( "\nNajwieksza wartosc jest rowna %d" , max );
printf( "i wystapila na pozycji %d" , poz+1 );
Przykłady funkcji operujących na tablicach
#include <stdio.h>
#include <conio.h>
#define ROZMIAR 10
void WczytajTablice( double tablica[ ] )
{
clrscr();
printf( ” Podaj wartości elementów tablicy \n” );
for( int i = 0 ; i < ROZMIAR ; i++ )
{
printf( ”Tab[%2d] = ”, i+1 );
scanf( ”%lf” , &tablica[ i ] );
}
} //------------------------------------------------------------ funkcja WczytajTablicę
void WyswietlTablice( double tablica[ ] )
{
clrscr();
printf( ” Wartości elementów tablicy są równe: \n” );
for( int i = 0 ; i < ROZMIAR ; i++ )
printf( ”Tab[%2d] = %f”, i+1 , tablica[ i ] );
printf( ” Nacisnij dowolny klawisz” );
getch();
} //------------------------------------------------------------ funkcja WyswietlTablicę
void DodajTablice( double wejscie_1[ ] , double wejscie_1[ ], double wynik [ ] )
{
for( int i = 0 ; i < ROZMIAR ; i++ )
wynik[ i ] = wejscie_1[ i ] + wejscie_2[ i ] ;
} //------------------------------------------------------------ funkcja DodajTablice
void main( void )
{
double A[ ROZMIAR ] ;
double B[ ROZMIAR ], C[ ROZMIAR ] ;
WczytajTablice( A );
WyswietlTablice( A );
WczytajTablice( B );
DodajTablice( A, B, C );
WyswietlTablice( C );
}WSKAŹNIKI / ADRESY
Wskaźnik → jest zmienną, która zawiera adres (wskazanie) początku dowolnego obszaru w pamięci komputera,
(np. może być to adres obszaru danych lub adres kodu programu)
Ogólna postać definicji wskaźnika:
typ_danych ∗ identyfikator wskaźnika ;
Najczęściej używane są wskaźniki „zdefiniowane” zawierające adres innej zmiennej. Taki wskaźnik zawiera informację o:
adresie zmiennej w pamięci komputera
typie danych przechowywanych w tej zmiennej
Przykłady definicji:
int ∗ wskaznik; // wskaźnik na zmienną całkowitą
double ∗ wsk_liczby; // wskaźnik na zmienną rzeczywistą
char ∗ wsk_znaku; // wskaźnik na pojedynczy znak
char ∗ tekst; // wskaźnik na początek łańcucha znaków
(na pierwszy znak tego łańcucha)
Można również korzystać ze wskaźników „niezdefiniowanych” (anonimowych).
Taki wskaźnik zawiera tylko informację o adresie obszaru pamięci (bez określenia typu wskazywanych danych). Definicja takiego wskaźnika ma postać:
void ∗ identyfikator wskaźnika ;
jest to wskaźnik na „dowolny” ciąg bajtów danych.
Ze wskaźnikami i adresami związane są dwa operatory:
operator referencji & zwracający adres zmiennej podanej po prawej stronie tego operatora.
operator dereferencji ∗ identyfikujący obszar wskazywany przez wskaźnik podany po prawej stronie tego operatora.
int liczba ;
int ∗wskaźnik ;
wskaznik = &liczba; // przypisanie zmiennej wskaźnik
// adresu zmiennej liczba
∗wskaźnik = 10; // przypisanie 10 zawartości zmiennej
// wskazywanej przez wskaźnik
// tutaj równoważne liczba = 10
Arytmetyka wskaźników
Na wskaźnikach mogą być wykonywane następujące operacje:
przypisania ( = )
wsk = wskaznik_zmiennej_lub_obszaru_pamięci ;
(w przypadku niezgodności typów konieczne jest dokonanie konwersji typu)
operacje porównania ( ==, !=, <, >, <=, >= ):
wsk_1 == wsk_2 // sprawdzenie czy zmienne zawierają te same adresy
wsk_1 < wsk_2 // czy zmienna wsk_1 zawiera adres mniejszy
// od adresu zawartego w zmiennej wsk_2
operacje powiększania lub pomniejszania wskaźnika ( +, −, ++, −−, +=, −= ) o liczbę całkowitą (tylko dla wskaźników zdefiniowanych)
→ powiększenie (pomniejszenie) wskaźnika o wartość N powoduje wyznaczenie adresu przesuniętego o:
N ∗ sizeof( typ_zmiennej_wskazywanej )
bajtów w kierunku rosnących (malejących) adresów.
np. int ∗x;
|
|
|
|
x |
|
|
|
|
|
x+3 |
|
|
|
|
|
|
|
|
↓ |
|
|
|
|
|
↓ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
adresy → |
. . . |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
. . . |
operacje odejmowania wskaźników tego samego typu → wyznaczenie „odległości” pomiędzy dwoma adresami w pamięci.
( odległości w sensie N ∗ sizeof ( typ_elementu_wskazywanego ) )
Przykłady zmiennych wskaźnikowych:
int ∗ wsk_liczby; // wskaźnik na liczbę typu int
int tab_A[10]; // 10-cio elementowa tablica liczb int
( identyfikator tab_A jest stałą równą adresowi
pierwszego elementu tablicy o tej samej nazwie
tzn. tab_A == &( tab_A[0] )
int ∗ tab_B[10]; // 10-cio elementowa tablica wskaźników na liczby int
int ∗ ( tab_C[10] ); // jak wyżej
( int ∗) tab_D[10]; // jak wyżej
int (∗tab_E)[10]; // wskaźnik na 10-cio elementową tablicę liczb int
PRZYKŁADY: Dostęp do zmiennych za pomocą wskaźników
#include <stdio.h>
int a ; int b ; int c ; float x ; int∗ wsk_int ;
// organizacja zajętości pamięci komputera przy w/w definicjach
zmienne → |
a |
b |
c |
x |
wsk_int |
|
|||||||
|
|
|
|
|
|
|
|||||||
bajty pamięci |
|
|
|
|
|
|
|
|
|
|
|
|
|
adresy → . . . |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
. . . |
void main( ) // Różne sposoby zapisu nowej wartości do zmiennej b { // za pomocą wskaźnika na b i sąsiadujące zmienne
// Wyświeltlenie adresów zmiennych printf(" \n Adres zmiennej A = %u " , (unsigned) &a ) ;
printf(" \n Adres zmiennej B = %u " , (unsigned) &b ) ;
printf(" \n Adres zmiennej C = %u " , (unsigned) &c ) ;
printf(" \n Adres zmiennej X = %u " , (unsigned) &x ) ;
printf(" \n Adres zmiennej WSK_INT = %u " , (unsigned) &wsk_int ) ;
a = b = c = 0; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
b=10; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
wsk_int = &b;
∗wsk_int = 20; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
wsk_int = &a; ∗(wsk_int+1) = 30; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
∗(&a + 1) = 40; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
∗(&c − 1) = 50; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
∗( (int∗)&x − 2) = 60; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
∗(int∗)( &x − 1) = 70; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
∗((int∗)&wsk_int − 4) = 80; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
∗(int∗) (&wsk_int − 4) = 90; printf( " \n A=%d, B=%d, C=%d " , a, b, c ) ;
PRZYKŁADY: Dostęp do tablic za pomocą indeksów i/lub wskaźników
#include <stdio.h> // Część wspólna przykładów na tej stronie
#define ROZMIAR 10
void main(void)
{
int tab[ ROZMIAR ];
// wczytanie liczby do tablicy
• • • // przemnożenie elementu tablicy przez 2
// wyświetlenie elementu tablicy
}
a) int i; // dostęp za pomocą indeksu
for( i = 0; i < ROZMIAR; i++ )
{
scanf( ”%d”, &tab[ i ] );
tab[ i ] = 2 ∗ tab[ i ]; // tab[ i ] ∗= 2;
printf( ”Tab[ %d ] = %d \n”, i+1 , tab[ i ] );
}
b) int i; // dostęp za pomocą adresu i indeksu
for( i = 0; i < ROZMIAR; i++ )
{
scanf( ”%d”, tab + i ); // &∗(tab+i) == tab+i
∗(tab+i) = 2 ∗ ∗(tab+i); // ∗(tab+i) ∗= 2;
printf( ”Tab[ %d ] = %d \n”, i+1 , ∗(tab+i) );
}
c) int licznik, ∗wsk; // dostęp za pomocą wskaźnika i licznika
for( licznik=0, wsk=tab; licznik < ROZMIAR; licznik++, wsk++ )
{
scanf( ”%d”, wsk );
∗wsk = 2∗∗wsk; // ∗wsk ∗= 2;
printf( ”Tab[ %d ] = %d \n”, licznik+1 , ∗wsk );
}
d) int ∗wsk; // dostęp za pomocą wskaźnika
for( wsk=tab; wsk < tab + ROZMIAR; wsk++ )
{ // wsk < &tab[ROZMIAR] ← adres ”końca tablicy”
scanf( ”%d”, wsk );
∗wsk ∗= 2;
printf( ”Tab[ %d ] = %d \n”, wsk−tab+1 , ∗wsk );
}
Funkcja → wysoce niezależny blok definicji i instrukcji programu (podprogram)
Każdy program napisany w języku C/C++ zawiera przynajmniej jedną funkcję o predefiniowanej nazwie: main( ). Najczęściej wykorzystuje się również wiele innych predefiniowanych funkcji np. printf(...), scanf(...), abs(...), sin(...), itp.
Można również definiować nowe−własne funkcje.
Składnia definicji funkcji:
zwracany_typ NAZWA_FUNKCJI ( lista parametrów )
{
instrukcja lub sekwencja instrukcji ;
}
przykład:
int MAX ( int liczba_1 , int liczba_2 )
{
if( liczba_1 > liczba_2 )
return liczba_1 ;
else
return liczba_2 ;
}
lista parametrów może być pusta lub zawierać opisy kolejnych parametrów (pooddzielane przecinkami):
main( ) main( void ) main( int argc , char∗ argv[ ] )
parametry definiowane są tak jak zmienne. Uwaga: nie można grupować sekwencji parametrów tego samego typu:
int MAX ( int liczba_1, liczba_2 ) ← źle !
„ciało” funkcji jest zawarte pomiędzy nawiasami: { ... } (bez średnika na końcu)
działanie funkcji kończy się po napotkaniu polecenia return lub po wykonaniu sekwencji wszystkich instrukcji zawartych w ciele funkcji,
jeżeli funkcja jest typu void, to używamy samego słowa return, bez żadnego wyrażenia po nim,
jeżeli funkcja jest typu innego niż void to po poleceniu return musi się pojawić wyrażenie odpowiedniego typu (może być w nawiasach), np.:
return liczba_1; lub return( liczba_1 ) ;
Prototyp funkcji → deklaracja „uprzedzająca”, określa tylko nazwę funkcji oraz typy zwracanej wartości i parametrów (sam nagłówek funkcji zakończony średnikiem)
Deklaracja funkcji jest konieczna w przypadkach, gdy wywołanie funkcji występuje wcześniej niż jej definicja. Np.
// program wyznaczający maksimum 3 liczb poprzez wywołanie funkcji MAX
#include <stdio.h>
int MAX ( int , int ) ; // Prototyp - deklaracja funkcji MAX
void main( void )
{
int a , b , c , m. ;
printf( " Podaj liczbe A = " );
scanf( " %d " , &a );
printf( " Podaj liczbe B = " );
scanf( " %d " , &b );
printf( " Podaj liczbe C = " );
scanf( " %d " , &c );
m = MAX( a , b ); // Wywolanie funkcji MAX
printf( " \n\nMaksimum z liczb A i B rowna sie = %d " , m ) ;
printf( " \n\nMaksimum z liczb B i C rowna sie = %d " , MAX( b,c ) ) ;
printf( " \n\nMaksimum z A,B,C rowna sie = %d " , MAX( a, MAX(b,c) ) ) ;
flushall();
getchar();
}
int MAX ( int liczba_1, int liczba_2 ) // Definicja funkcji MAX
{
if( liczba_1 > liczba_2 )
return liczba_1 ;
else
return liczba_2 ;
}
FUNKCJE / PRZEKAZYWANIE PARAMETRÓW
1. Funkcja bezparametrowa nie zwracająca żadnej wartości
void nazwa_funkcji(void)
{
• • •
return; // powoduje natychmiastowe zakończenie wykonywania funkcji
} // na końcu funkcji można pominąć
przykład
void odwrotność(void)
{ // obliczenie odwrotności liczby wczytanej z klawiatury
double liczba;
scanf( ”%lf” , &liczba );
if( liczba == 0 )
return;
printf( ”%f” , 1/liczba );
return; // to *return* można pominąć
}
2. Funkcja pobierająca parametr i zwracająca wartość
UWAGA ! w języku C parametry przekazywane są tylko przez wartość
tzn. po wywołaniu funkcji tworzone są nowe zmienne (lokalne),
których zawartość inicjowana jest wartościami parametrów
(zmiennych, stałych lub wyrażeń) podanych przy wywołaniu.
przykład a)
double odwrotność( double liczba ) // definicja funkcji *odwrotność*
{
if( liczba == 0 )
return( 0 );
else
return( 1/liczba );
}
void main( void )
{
double x=10, y;
y = odwrotnosc( 20 ); // przykłady wywoływania funkcji *odwrotnosc*
y = odwrotnosc( x );
odwrotnosc( 3∗(15-x) );
}
przykład b)
// przykład funkcji zwracającej wartość większego z argumentów
double maksimum( double a, double b )
{
if( a > b)
return( a );
return( b );
}
przykład c)
void posortuj_1 ( double a, double b )
{ // UWAGA !!!
double buf; // błędny sposób przekazywania
if( a > b) // paramerów (przez wartość).
{ // Sortowane są zmienne lokalne a i b
buf = a; // (kopie parametrów x i y).
a = b; // Zawartość x i y nie ulegnie zmianie !
b = buf;
}
}
void main( void )
{
double x=7, y=5;
posortuj_1( x, y ); // do funkcji przekazywane są wartości zmiennych
}
przykład d)
void posortuj_2 ( double ∗a, double ∗b )
{ // przekazywanie parametrów „przez adres”
double buf;
if( ∗a > ∗b) // porównywane są zawartości miejsc
{ // wskazywanych przez wskazniki a i b
buf = ∗a;
∗a = ∗b;
∗b = buf;
}
}
void main( void )
{
double x=7, y=5;
posortuj_2( &x, &y ); //do funkcji przekazywane są adresy zmiennych
}
W języku C++ parametry mogą być przekazywane przez wartość lub przez referencję
(przekazywanie przez referencję jest odpowiednikiem przekazywania przez zmienną)
Typ referencyjny → zmienne tego typu nie zajmują nowego miejsca w pamięci, służą do reprezentacji innych zmiennych w programie.
nazwa_typu nazwa_zmiennej; ← utworzenie zwykłej zmiennej
nazwa_typu & nazwa_zmiennej_referencyjnej = nazwa_zmiennej;
(jest to zdefiniowanie aliasu − innej nazwy dla tej samej zmiennej)
przykład
int wzrost;
int sredni_wzrost = wzrost;
int& wysokosc = wzrost; // utworzenie zmiennej referencyjnej
// związanej z tym samym obszarem
// pamięci co wzrost
wysokosc = wysokosc + 1; // równoważne: wzrost = wzrost + 1
przykład e)
void posortuj_3 ( double & a, double & b )
{
double buf; // przekazywanie parametrów
if( a > b) // przez referencję
{
buf = a; // a i b są referencyjnymi nazwami x i y
a = b;
b = buf;
}
}
void main( void )
{
double x=7, y=5;
posortuj_3( x, y ); // parametry x i y inicjują zmienne referencyjne
}
FUNKCJE OPERUJĄCE NA PAMIĘCI <mem.h>
void ∗memset ( void ∗wsk_pocz, int wartosc, size_t dlugosc )
(obszar wskazywany przez wsk_pocz o długości dlugosc jest wypełniany wartością wartosc)
np. int i, tab[1000];
memset( &i , 0, sizeof( i ) ); // równoważne: i = 0
memset( &i , 1, sizeof( i ) ); // równoważne: i = 257 = 1∗256 + 1
memset( tab , 0, sizeof( tab ) ); // wypełnienie tablicy zerami
void ∗memcpy ( void ∗wsk_dokąd, void ∗wsk_skąd, size_t dlugosc )
( „memory copy” → skopiowanie dlugosc bajtów spod adresu wsk skąd pod adres wsk dokąd)
np. int i, j=10, tab1[1000], tab2[1000];
memcpy( &i , &j, sizeof( i ) ); // równoważne: i = j ;
memcpy( tab1 , tab2, sizeof( tab1 ) ); // skopiowanie zawartości
// tablicy tab2 do tab1
int memcmp ( void ∗obszar_1, void ∗obszar_2, size_t dlugosc )
( „memory compare” → porównanie dlugosc bajtów spod adresu obszar 1 oraz adresu obszar 2 )
funkcja zwraca wartość: < 0 gdy zawartość obszar_1 < obszar_2
= 0 gdy zawartość obszar_1 == obszar_2
> 0 gdy zawartość obszar_1 > obszar_2
np. int i, j, tab1[1000], tab2[1000];
if( memcmp( &i , &j, sizeof( int ) ) ) // równoważne porównaniu i != j
printf( ”te zmienne mają rozna wartosc” );
memcmp( tab1 , tab2, sizeof( tab1 ) ); // porównanie zawartości
// tablicy tab1 oraz tab2
void ∗memmove ( void ∗wsk_dokąd, void ∗wsk_skąd, size_t dlugosc )
( „memory move” → kopiowanie ze sprawdzaniem „zachodzenia się” obszarów )
void ∗memccpy (void ∗dokąd, void ∗skąd, int znak, size_t dlugosc )
( „memory char copy” → kopiowanie ograniczone ilością bajtów lub skopiowaniem znaku )
void ∗memchr ( void ∗wsk_pocz, int znak, size_t dlugosc )
( „memory char search” → szukanie pozycji wystąpienia bajtu o zadanej wartości )
Przykłady wykorzystania funkcji „mem”
// załóżmy następującą definicję tablicy:
long tab[10] = { −5, −4, −3, −2, −1, 0, 1, 2, 3, 4, 5 } ;
// po operacji:
memcpy( &tab[ 0 ], &tab[ 5 ], sizeof(long) );
// lub:
memcpy( tab + 0, tab + 5, sizeof(long) ) ;
// zawartość tablicy jest równa: { 0 , −4, −3, −2, −1, 0, 1, 2, 3, 4, 5 }
// po operacji:
memcpy( tab + 0, tab + 6 , 5 ∗ sizeof(long) ) ;
// zawartość tablicy jest równa: { 1, 2, 3, 4, 5 , 0, 1, 2, 3, 4, 5 }
// po operacji: ( ← ← ← )
memcpy( tab + 0, tab + 1 , 10 ∗ sizeof(long) ) ;
// zawartość tablicy jest równa: { −4, −3, −2, −1, 0, 1, 2, 3, 4, 5 , 5 }
// po operacji: ( → → → )
memcpy( tab + 1, tab + 0 , 10 ∗ sizeof(long) ) ;
// zawartość tablicy jest równa: { −5, −5, −5, −5, −5, −5, −5, −5, −5, −5, −5 }
// po operacji: ( → → → )
memmove( tab + 1, tab + 0 , 10 ∗ sizeof(long) ) ;
// zawartość tablicy jest równa: { −5, −5, −4, −3, −2, −1, 0, 1, 2, 3, 4 }
// skopiowanie zawartości tablicy A do tablicy B :
long A[ 100 ], B[ 100 ] ;
// poprzez operację:
memcpy( B, A , 100 ∗ sizeof(long) ) ;
// lub:
memcpy( B, A, sizeof( B ) ) ; // lub: memcpy( B, A, sizeof( A ) ) ;
// UWAGA !!! przy kopiowaniu zawartości tablicy, która jest parametrem funkcji :
void funkcja( long A[ 100 ] )
{
long B[ 100 ] ;
memcpy( B, A , sizeof( A ) ) ; // ŹLE !!! bo A jest zmienną zawierającą adres,
// sizeof( A ) jest równe 2 (zamiast 400)
memcpy( B, A , sizeof( B ) ) ; // ← dobrze
}
ŁAŃCUCHY W JĘZYKU C/C++
Stała tekstowa / łańcuchowa jest tablicą znaków zakończoną znakiem o kodzie: 0
np. stała łańcuchowa: ”Jestem tekstem”
. . . |
74 |
101 |
115 |
116 |
101 |
109 |
32 |
116 |
101 |
107 |
115 |
116 |
101 |
109 |
0 |
. . . |
. . . |
'J' |
'e' |
's' |
't' |
'e' |
'm' |
' ' |
't' |
'e' |
'k' |
's' |
't' |
'e' |
'm' |
'\0' |
. . . |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
char ∗ tekst; // wskaźnik na znak == wskaźnik na początek łańcucha znaków
tekst = ”Jestem tekstem” ; // przypisanie zmiennej tekst adresu
// początku łańcucha znaków
char tekst2[ 100]; // 100-elementowa tablica znakow
tekst2 = ”Jestem tekstem” ; // błędne przypisanie !!!
memcpy( tekst2, ”Jestem tekstem”, 15 ); // poprawne przypisanie
Funkcje operujące na łańcuchach <string.h>
// kopiowanie jednego łańcucha do drugiego → wersja tablicowa
char ∗ strcpy( char tekst_wyj[ ], char tekst_wej[ ] )
{
int i = 0;
while( ( tekst_wyj[ i ] = tekst_wej[ i ] ) != `\0' )
i++;
return( tekst_wyj );
}
// kopiowanie jednego łańcucha do drugiego → wersja wskaźnikowa 1
char ∗ strcpy( char ∗tekst_wyj, char ∗tekst_wej )
{
char ∗pocz=tekst_wyj;
while( ( ∗tekst_wyj = ∗tekst_wej ) != `\0' )
{
tekst_wyj++;
tekst_wej++;
}
return( pocz );
}
// funkcja kopiująca łańcuchy − wersja wskaźnikowa 2
char ∗ strcpy( char ∗tekst_wyj, char *tekst_wej )
{
char ∗pocz=tekst_wyj;
while( ∗tekst_wyj++ = ∗tekst_wej++ ) ;
return( pocz );
}
Funkcja porównująca teksty: int strcmp ( char ∗tekst_1, char ∗tekst_2 )
( ang. „string compare” )
funkcja zwraca wartość: < 0 gdy tekst_1 < tekst_2
= 0 gdy tekst_1 == tekst_2
> 0 gdy tekst_1 > tekst_2
int strcmp( char tekst_1[ ], char tekst_2[ ] ) // wersja tablicowa
{
int i = 0;
while( tekst_1[ i ] == tekst_2[ i ] )
if( tekst_1[ i++ ] == `\0' )
return( 0 );
return( tekst_1[ i ] − tekst_2[ i ] );
}
int strcmp( char ∗tekst_1, char ∗tekst_2 ) // wersja wskaźnikowa 1
{
while( ∗tekst_1 == ∗tekst_2 )
{
if( ∗tekst_1 == `\0' )
return( 0 );
tekst_1 = tekst_1 + 1;
tekst_2 = tekst_2 + 1 ;
}
return( ∗tekst_1 − ∗tekst_2 );
}
int strcmp( char ∗tekst_1, char ∗tekst_2 ) // wersja wskaźnikowa 2
{
for( ; ∗tekst_1 == ∗tekst_2 ; tekst_2++ )
if( ! ∗tekst_1++ )
return( 0 );
return( ∗tekst_1 − ∗tekst_2 );
}
Inne wybrane funkcje biblioteki string → <string.h>
size_t strlen( const char ∗s )
od ang. „ string length ”
Funkcja wyznacza i zwraca długość (ilość znaków) łańcucha s (bez znaku `\0')
char ∗strcat( char ∗dest, const char ∗src )
od ang. „ string concatenate ”
Funkcja dodaje łańcuch src (ang. source) do łańcucha dest (ang. destination)
Zwraca wskaźnik na połączony łańcuch (dest)
char ∗strchr( const char ∗s, int c )
od ang. „ string char ”
Funkcja szuka pierwszego wystąpienia znaku c w podanym łańcuchu s
Zwraca wskaźnik na znalezioną pozycję wystąpienia lub adres NULL.
char ∗strrchr( char ∗s, int c )
od ang. „ string right char ”
Funkcja szuka ostatniego wystąpienia znaku c w podanym łańcuchu s
Zwraca wskaźnik na znalezioną pozycję wystąpienia lub adres NULL.
char ∗strstr( char ∗s, const char ∗sub )
od ang. „ scans string for substring ”
Funkcja szuka pierwszego wystąpienia łańcucha sub w podanym łańcuchu s
Zwraca wskaźnik na znalezioną pozycję wystąpienia lub adres NULL.
char∗ strupr( char ∗s )
od ang. „ string upper ”
Funkcja zamienia zawartość łąńcucha s na duże litery
char∗ strlwr( char ∗s )
od ang. „ string lower ”
Funkcja zamienia zawartość łąńcucha s na małe litery
Przykłady operacji na łańcuchach znaków
1) #include <stdio.h> // przykład zamiany wszystkich liter na duże
#include <ctype.h>
// standardowe funkcje zamiany łańcuchów na małe lub duże litery
// #include <string.h> → char *strlwr(char *s); char *strupr(char *s);
char ∗Zamien_Na_Duze( char∗ tekst )
{
char ∗wsk = tekst;
do
∗wsk = toupper(∗wsk ); // zamiana pojedynczej litery na dużą
while(∗wsk++ );
return( tekst );
} //------------------------------------------------------------------------ Zamien_Na_Duze
void main( void )
{
char ∗lancuch_testowy = "abcdefghijklmnopqrstuvwxyz";
printf( "%s\n" , Zamien_Na_Duze ( lancuch_testowy ) );
}
2) #include <stdio.h> // przykład zamiany pierwszych liter wyrazów
#include <ctype.h>
char ∗Slowa_Na_Duze( char∗ tekst )
{
char ∗wsk = tekst;
if( !∗wsk ) // jeżeli tekst pusty to zakończ działanie
return(tekst);
∗wsk = toupper( ∗wsk ); // zamiana pierwszej litery
while( ∗++wsk )
if( ∗(wsk-1) == ' ' ) // jeżeli poprzedzający znak jest spacją
∗wsk = toupper( ∗wsk ); // zamiana znaku na dużą literę
return( tekst );
} //------------------------------------------------------------------------ Slowa_Na_Duze
void main( void )
{
char ∗lancuch = "to jest probka tekstu ";
printf( "%s\n" , Slowa_Na_Duze( lancuch ) );
}
3) #include <stdio.h> // funkcja zamieniająca zadane fragmenty tekstu
#include <string.h>
void Zamien_Fragmenty( char∗ tekst,
char∗ stary_wzorzec,
char∗ nowy_wzorzec )
{
char∗ wsk = tekst;
int dlugosc_starego = strlen( stary_wzorzec );
int dlugosc_nowego = strlen( nowy_wzorzec );
do {
wsk = strstr( tekst, stary_wzorzec );
if( wsk ) // if( wsk != null )
{
// ewentualne zsunięcie lub rozsunięcie tekstu
memmove( wsk + dlugosc_nowego ,
wsk + dlugosc_starego ,
strlen( wsk + dlugosc_starego ) +1 );
// wpisanie nowego wzorca w przygotowane miejsce
memcpy( wsk, nowy_wzorzec, dlugosc_nowego);
}
} while( wsk );
} //---------------------------------------------------------------------- Zamien_Fragmenty
void main( void )
{
char tekst[200] = "Ala ma kota a Ola ma Asa";
printf( "Stary tekst: %s\n" , tekst );
Zamien_Fragmenty( tekst, "ma", "miala" );
printf( " Nowy tekst: %s\n" , tekst ); // "Ala miala kota a Ola miala Asa"
}
UWAGA !
Zastosowanie w powyższym przykładzie funkcji strcpy zamiast memmove będzie generować błędy (gdy nowy_wzorzec będzie dłuższy od stary_wzorzec)
np. strcpy( wsk+dlugosc_nowego, wsk+dlugosc_starego );
utworzy tekst: ” Ala ma ko ko ko ko ko ko ko k”
Definicja: char∗ tekst = "Ala ma kota a Ola ma Asa";
jest równoważna: char tekst[24+1] = "Ala ma kota a Ola ma Asa";
Ponieważ podczas zamiany tekst może się wydłużyć (poprzez wstawienie dłuższych fragmentów), więc zmienna tekst powinna być tablicą większą niż długość inicjującego tekstu.
TYP STRUKTURALNY
Struktury → najbardziej elastyczny sposób reprezentowania danych w języku C (odpowiednik rekordów w języku Pascal),
→ obiekt złożony z jednej lub kilku zmiennych, które mogą być różnego typu (w przeciwieństwie do tablic),
→ budowa - układ i typy pól składowych - typu strukturalnego
są definiowane przez programistę.
Przykład:
nazwisko |
imię |
rok_urodz |
płeć |
wzrost |
stypendium |
char [30] |
char [15] |
int |
char |
unsigned char |
double |
Deklarowanie typu strukturalnego
struct nazwa_typu ← nazwa tworzonego typu strukturalnego
{
typ_pola_1 nazwa_pola_1;
typ_pola_2 nazwa_pola_2; ← typy i nazwy pól składowych
• • •
typ_pola_n nazwa_pola_n;
} ;
Przykłady:
struct dane_osobowe
{
char nazwisko[31];
char imie[16];
int rok_urodz;
char plec;
unsigned char wzrost;
double stypendium;
} ;
struct punkt { double x, y; } ;
struct okrag
{
struct punkt srodek_okregu; // struktura zagnieżdżona
double promien;
} ;
Definiowanie (tworzenie) zmiennych strukturalnych
struct nazwa_typu nazwa_tworzonej_zmiennej;
Przykład:
struct dane_osobowe student;
struct okrag figura_1;
Można połączyć definicję zmiennej z inicjacją jej wartości. Np.
// { Nazwisko, Imie, Rok_urodz, Plec,Wzrost, Stypendium }
struct dane_osobowe student = { ”Kowalski”, ”Jan”, 1970,`M', 175, 320.0 };
// { { x , y } , promień }
struct okrag figura_1 = { { 10.0, 15.75 } , 50.5 };
Można połączyć deklarację typu strukturalnego z definicją zmiennych
struct nazwa_typu ← nazwę typu można pominąć
{
typ_pola_1 nazwa_pola_1;
typ_pola_2 nazwa_pola_2; ← typy i nazwy pól składowych
• • •
typ_pola_n nazwa_pola_n;
} nazwa_ zmiennej ; ← nazwa definiowanej zmiennej
Np.
struct // pominięto nazwę typu
{
char nazwisko[31];
char imie[16];
int rok_urodz;
char plec;
unsigned char wzrost;
double stypendium;
} student_1, student_2; // definicja dwóch zmiennych strukturalnych
struct okrag // jednoczesna deklaracja typów i definicja zmiennych
{
struct punkt // bezpośrednia definicja struktury zagnieżdżonej
{
double x, y;
} srodek_okregu;
double promien;
} figura_1;
Odwoływanie się do elementów struktury → za pomocą kropki
Przykłady:
student . wzrost = 180 ; // przypisanie wartości
student_1 . wzrost = student_2 . wzrost;
figura_1 . promien = 50;
figura_1 . srodek_okregu . x = 15;
scanf( ”%lf” , &student . stypendium ); // wczytanie z klawiatury
scanf( ”%s” , student . nazwisko ); // wczytanie łańcucha znaków
strcpy( student . imie, ”Tomasz” );
W pierwotnej wersji języka C (Kernigham, Ritchie) jedynymi dozwolonymi operacjami na strukturze były pobranie adresu (&) oraz działania na składowych.
W wersji ANSI-C (Turbo C++) możliwe jest bezpośrednie przypisanie struktur, struktura może być również argumentem i wynikiem zwracanym przez funkcję.
student_1 = student_2; // bezpośrednie przypisanie struktur
memcpy( &student_1, &student_2, sizeof( student_1 ) );
student_1 . nazwisko = student_2 . nazwisko
strcpy( student_1 . nazwisko, student_2 . nazwisko );
// funkcja zwracająca daną strukturalną
struct dane_osobowe Wczytaj_Dane_Osobowe( void )
{
struct dane_osobowe nowe_dane;
printf( ”Podaj nazwisko: ” );
scanf( ”%s” , nowe_dane . nazwisko );
• • •
return( nowe_dane );
}
// funkcja której argumentem jest zmienna strukturalna
void Wyswietl_Dane_Osobowe( struct dane_osobowe osoba )
{
printf( ”Nazwisko: %s\n” , osoba . nazwisko );
printf( ” Imie: %s\n” , osoba . imie );
• • •
}
Struktura jako element tablicy
struct dane_osobowe baza[ 100 ]; // definicja tablicy struktur
struct dane_osobowe mala_baza[ 2 ] = {
{ ”Kowalski”, ”Jan”, 1970,`M', 175, 320.0 } ,
{ ”Nowak”, ”Tomasz”, 1965,`M', 180, 50.0 }
};
struct okrag figury[ 3 ] = { {{15, 10}, 50} , {{0, 0}, 10} , {{30, -70}, 8} };
baza[ 2 ] . wzrost = 150; // przykładowe operacje na tablicach struktur
figury[ 0 ] . srodek_okregu . y = 50;
for( int i = 0; i < 100; i++ )
{
printf( ”Podaj nazwisko: ” );
scanf( ”%s” , baza[ i ] . nazwisko );
• • •
printf( ”Podaj stypendium: ” );
scanf( ”%lf” , &(baza[ i ] . stypendium) );
}
Wskaźniki do struktur - dostęp do struktury za pośrednictwem adresu
struct dane_osobowe student;
struct dane_osobowe ∗wsk_os;
student . wzrost = 180; // bezposrednie przypisanie do pola struktury
wsk_os = &student;
(∗wsk_os) . wzrost = 180; // pośrednie przypisanie poprzez wskaźnik
wsk_os −> wzrost = 180; // j.w. z nowym operatorem strzałki
// funkcja porządkująca roznąco zawartości dwu zmiennych
void Uporzadkuj( struct dane_osobowe ∗wsk_os_1,
struct dane_osobowe ∗wsk_os_2 )
{ struct dane_osobowe bufor;
if( wsk_os_1−>wiek > wsk_os_2−>wiek )
{
bufor = ∗wsk_os_1;
∗wsk_os_1 = ∗wsk_os_2;
wsk_os_2 = bufor
}
} //--------------------------------------------------------------------------------Uporzadkuj
PLIKOWE OPERACJE WEJŚCIA - WYJŚCIA
Język C/C++ nie ma wbudowanych żadnych instrukcji umożliwiających wykonywanie operacji wejścia-wyjścia ! Służą do tego funkcje biblioteczne.
Funkcje zawarte w bibliotece < io.h >
Dostęp do pliku za pomocą uchwytu (ang. Handle) - operacje niskiego poziomu
Funkcje otwierania (zwraca uchwyt pliku) oraz zamknięcia pliku
int open ( const char ∗nazwa_pliku, int tryb_dostepu )
int close ( int handle )
Funkcje zapisu i odczytu z pliku
int write ( int handle, void ∗adres_bufora, unsigned ilosc_bajtow )
int read ( int handle, void ∗adres_bufora, unsigned ilosc_bajtow );
Funkcje pomocnicze
int eof ( int handle ) // zwraca 1 gdy „END OF FiILE”
long tell ( int handle ) // zwraca pozycję wskaźnika pliku
long filelength ( int handle ) // zwraca długosć pliku w bajtach
long lseek ( int handle, long przesuniecie, int względem_czego )
// przesuwa wskaźnik pliku o zadaną ilość bajtów
względem zadanego miejsca:
SEEK_SET - względem początku pliku
SEEK_CUR - względem aktualnej pozycji
SEEK_END - względem końca pliku
Przykład
int plik;
char tekst[ ] = "To jest tekst zapisywany i odczytywany z pliku";
char znak;
plik = open( "test.dat", O_CREAT | O_RDWR );
write( plik, tekst, strlen( tekst ) ); // zapis zawartosci tekstu do pliku
lseek( plik, 0L, SEEK_SET ); // przesuniecie wskaźnika na poczatek
do
{ // odczyt po jednym znaku aż do napotkania eof
read( plik, &znak, 1);
printf( "%c", znak ); // wydruk odczytanego znaku na ekranie
} while ( !eof( plik ) );
close( plik );
Funkcje zawarte w bibliotece < stdio.h >
Operacje we/wy realizowane za pomocą strumieni (ang. Stream)
Strumienie reprezentowane są przez zmienne typu FILE. Struktura taka tworzona jest automatycznie podczas otwierania strumienia (zawiera informacje o nazwie pliku, trybie otwarcia, itp.). Wszystkie dalsze operacje na strumieniu wymagają podania wskaźnika na tą strukturę.
Przykład
FILE ∗plik_wej, ∗wyniki ; // definicja zmiennych „strumieniowych”
Standardowe strumienie wejścia i wyjscia (otwierane automatycznie)
stdin − strumień wejściowy (konsola - klawiatura)
stdout − strumień wyjściowy (konsola - monitor)
stderr − strumień komunikatów błędów (konsola)
stdprn − strumień drukarki
Funkcje otwarcia (zwraca wskaźnik na FILE) oraz zamknięcia pliku
FILE ∗ fopen ( char ∗nazwa_pliku, char ∗rodzaj_operacji )
rodzaj operacji:
r − tylko do odczytu
w − tylko do zapisu (utworzenie nowego)
a − dopisywanie na końcu
+ − z mozliwością aktualizacji
b − otwarcie jako plik binarny
t − otwarcie jako plik tekstowy
Przykład
FILE ∗plik; // utworzenie pliku binarnego z możliwoscia aktualizacji
plik = fopen( ” a:\wyniki.dat ”, ” w+b ” );
if( plik == NULL ) // kontrola błędów we/wy
{
printf( ”Blad otwarcia pliku wyników” );
exit( 1 );
}
int fclose ( FILE ∗strumien ) // zamknięcie wskazanego strumienia
int fcloseall (void ) // zamknięcie wszystkich strumieni
Zapis danych do strumienia
int fputc ( int znak, FILE ∗strumien ) // wysłanie pojedynczego znaku
int fputs ( char ∗tekst, FILE ∗strumien ) // wysłanie łańcucha znaków
int fprintf ( FILE ∗strumien, char ∗format, . . . )
// funkcja sformatowanego wyjscia analogiczna do printf( )
int fwrite ( void∗ adres_w_pamieci,
size_t rozmiar_bloku, size_t ilosc_blokow,
FILE ∗ strumien)
// funkcja kopiująca (ilosc_blokow∗rozmiar_bloku) bajtów
spod wskazanego obszaru pamięci do strumienia (pliku)
Przykład
#include <stdio.h>
struct student
{
char nazwisko[31];
char imie[16];
int wiek;
};
void main( void )
{
FILE *strumien;
struct sudent baza_danych[10];
if ( (strumien = fopen( "test.bin" , " wb " ) ) != NULL )
{ // zapis zawartości calej bazy ( tablicy struktur) do pliku binarnego
fwrite( baza_danych, sizeof(struct student), 10 , strumien);
fclose( strumien );
}
if ( (strumien = fopen( "test.txt" , " wt " ) ) != NULL )
{ // zapis zawartości calej bazy ( tablicy struktur) do pliku tekstowego
for( int i = 0; i < 10; i++ )
fprintf ( strumien, ”%s %s %d \n”,
baza[ i ].nazwisko, baza[ i ].imie, baza[ i ].wiek ); baza[ i ].nazwisko,
fclose( strumien );
}
}
Jeżeli jako strumień wyjsciowy podamy stdout (standardowy strumien wyjsciowy) to wtedy wydruk bedzie dokonywany na ekran.
np. fprintf( stdout, ” format” , ........ ) ≡ printf( ”format” , ........ )
Odczyt danych ze strumienia
int fgetc ( FILE ∗strumien ) // wczytanie pojedynczego znaku
char∗ fgets ( char ∗tekst, int dlugosc, FILE ∗strumien )
// wczytanie łańcucha składającego się z co najwyżej (dlugosc−1) znaków
int fscanf ( FILE ∗strumien, char ∗format, . . . )
// funkcja sformatowanego wejścia analogiczna do scanf( )
int fread ( void∗ adres_w_pamieci,
size_t rozmiar_bloku, size_t ilosc_blokow,
FILE ∗ strumien)
// funkcja odczytująca (ilosc_blokow∗rozmiar_bloku) bajtów
ze strumienia do wskazanego obszaru pamięci
Przykład
#include <stdio.h>
struct student
{
char nazwisko[31];
char imie[16];
int wiek;
};
void main( void )
{
FILE *strumien;
struct sudent baza_danych[10];
int ilosc_osob;
if ( (strumien = fopen( "test.bin" , " rb " ) ) != NULL )
{ // wczytanie zawartości bazy ( tablicy struktur) z pliku binarnego
ilosc = 0;
while( fread( &baza_danych[ilosc], sizeof(student), 1, strumien) == 1)
ilosc++;
fclose( strumien );
}
if ( (strumien = fopen( "test.txt" , " rt " ) ) != NULL )
{ // wczytaniet zawartości bazy ( tablicy struktur) z pliku tekstowego
for( ilosc = 0; ( !feof(strumien) ) && (ilosc <= 10); i++ )
fscanf( strumien, ” %s %s %d” ,
baza[ i ].nazwisko, baza[ i ].imie, &(baza[ i ].wiek) ); baza[ i ].nazwisko,
fclose( strumien );
}
}
Funkcje pomocnicze
int feof ( FILE ∗strumien ) // testowanie osiągnięcia końca pliku
int fseek ( FILE ∗strumien, long przesuniecie, int wzgledem)
// przesuwa wskaźnik pliku o zadaną ilość bajtów
względem zadanego miejsca:
SEEK_SET - względem początku pliku
SEEK_CUR - względem aktualnej pozycji
SEEK_END - względem końca pliku
long ftell ( FILE ∗strumien )
// zwraca aktualną pozycję wskaźnika pliku
int fflush ( FILE ∗strumien ) // „wymiata” bufor wskazanego strumienia
int fflush ( void ) // j.w.dla wszystkich buforowanych strumieni
Przykład zadania zaliczeniowego
// funkcja wyznaczająca pozycję maksymalnej liczby double w pliku binarnym
#include <stdio.h>
long Maksimum( char ∗nazwa_pliku )
{
FILE ∗plik_danych;
long pozycja=0, poz_max = −1;
double liczba, maksimum;
if ( (plik_danych = fopen( nazwa_pliku , "rb" ) ) != NULL )
{
while( fread( &liczba, sizeof(double), 1, plik_danych) == 1)
{
if( pozycja == 0 )
{
maksimum = liczba;
poz_max = 0;
}
else
if( liczba > maksimum )
{
maksimum = liczba;
poz_max = pozycja;
}
pozycja++;
}
fclose( strumien );
}
return( poz_max );
}
OPERACJE WEJŚCIA − WYJŚCIA
Operacje wejścia / wyjścia → odczyt i zapis danych do różnych
zewnętrznych urządzeń lub nośników pamięciowych komputera:
np. klawiatury, ekranu monitora, dyskietki, czytnika taśmy, drukarki, itp.
Język C/C++ nie ma wbudowanych żadnych instrukcji umożliwiających wykonywanie operacji wejścia-wyjścia ! Służą do tego funkcje biblioteczne.
Operacje na plikach (niskiego poziomu) → < IO.H >
int open ( char ∗nazwa_pliku, int tryb_dostepu )
int close ( int handle )
int write ( int handle, void ∗adres_bufora, unsigned ilosc_bajtow ) bin.
int read ( int handle, void ∗adres_bufora, unsigned ilosc_bajtow ) bin.
int eof ( int handle )
long tell ( int handle )
long filelength ( int handle )
long lseek ( int handle, long przesuniecie, int względem_czego )
Proceduralnie za pomocą strumieniu → < STDIO.H >
FILE ∗ fopen ( char ∗nazwa_pliku, char ∗rodzaj_operacji )
int fclose ( FILE ∗strumien )
int fcloseall (void )
int fputc ( int znak, FILE ∗strumien ) txt
int fputs ( char ∗tekst, FILE ∗strumien ) txt
int fprintf ( FILE ∗strumien, char ∗format, . . . ) txt
int fwrite ( void∗ adres, size_t rozm_bl, size_t il_blokow, FILE∗ strumien ) bin
int fgetc ( FILE ∗strumien ) txt
char∗ fgets ( char ∗tekst, int dlugosc, FILE ∗strumien ) txt
int fscanf ( FILE ∗strumien, char ∗format, . . . ) txt
int fread ( void∗ adres, size_t rozm_bl, size_t il_blokow, FILE∗ strumien ) bin
int feof ( FILE ∗strumien )
int fseek ( FILE ∗strumien, long przesuniecie, int wzgledem)
long ftell ( FILE ∗strumien )
int fflush ( FILE ∗strumien )
int flushall ( void )
OBIEKTOWA REALIZACJA OPERACJI WEJŚCIA − WYJŚCIA
W języku C++ możliwa jest obiektowa realizacja operacji we/wy.
Podejście obiektowe zakłada, że różne „urządzenia” będą reprezentowane w programie za pomocą różnych obiektów modelujących strumienie danych wpływające lub wypływające z tych „urządzeń”.
W obiektowych bibliotekach we/wy zdefiniowano różne klasy obiektów −strumieni (w zależności od specyficznych cech danego „urządzenia”). Cechy strumienia można odczytać z poszczególne liter nazw klas :
i.... − (in) − strumienie wejściowe (np. istream, ifstream, istrstream),
o.... − (out) − strumienie wyjściowe (np. ostream, ofstream, ostrstream),
f.... − (file) − strumienie plikowe (np. ifstream, ofstream, fstream),
str.. − (string) − strumienie pamięciowe (np. istrstream, strstream),
Aby uniknąć wielokrotnego definiowania tych samych operacji
(np. dla każdego strumienia musi być funkcja informująca czy wystąpił błąd) klasy strumieni tworzą wielopoziomową hierarchię:
Podstawową klasą jest klasa ios
Modeluje ona właściwości (tzn. funkcje, zmienne i stałe) wspólne dla wszystkich strumieni. Definicja klasy ios jest zawarta w pliku <iostream.h>.
Najważniejsze metody tej klasy:
int ios::bad( ) - zwraca wartość różną od zera, jeżeli wystąpił błąd,
int ios::good( ) - zwraca wartość różną od zera, jeżeli nie było błędu,
int ios::eof( ) - zwraca wartość różną od zera, gdy koniec danych,
int ios::width( int ) - steruje szerokością pola wyjściowego (np.ilość cyfr)
int ios::precision( int ) - steruje ilością cyfr po przecinku
Stałe trybów otwarcia strumienia:
ios::in - otwórz strumień do odczytu,
ios::out - otwórz strumień do zapisu,
ios::app - otwórz strumień w trybie dopisywania na końcu,
ios::trunc - wyzeruj rozmiar pliku, jeżeli istnieje,
ios::binary - otwórz jako strum. binarny (domyślnie → strum. tekstowy),
Stałe określające pozycję odniesienia (podczas przesuwania pozycji):
ios::beg - względem początku pliku,
ios::cur - względem pozycji aktualnej,
ios::end - względem końca pliku,
PodstawowE operacje odczytu → klasa istream
Modeluje ona metody wspólne dla wszystkich strumieni wejściowych z których odczytujemy dane (tekstowe lub binarne). Definicja klasy istream jest zawarta również w pliku <iostream.h>.
Najważniejsze metody tej klasy:
get( char& znak) - wczytuje jeden znak ze strumienia,
getline(char∗ bufor, int max_dlug, char znak_konca) - wczytuje linię znaków,
read( char∗ bufor, int ilość_bajtów ) - wczytuje ciąg bajtów do bufora,
>> - operator pobrania/odczytu danych ze strumienia tekstowego.
Podstawowe operacje ZAPISU → klasa ostream
Modeluje ona metody wspólne dla wszystkich strumieni wyjściowych do których zapisujemy dane (tekstowe lub binarne). Definicja klasy ostream jest zawarta również w pliku <iostream.h>.
Najważniejsze metody tej klasy:
put( char& znak) - wysyła jeden znak do strumienia,
write(char∗ bufor, int ilość_bajtów) - wysyła ciąg bajtów z bufora do strum.
<< - operator wysłania/zapisu danych do strumienia tekstowego.
strumienie standardowe
W programach napisanych w języku C++ można korzystać z czterech predefiniowanych, zawsze otwartych strumieni standardowych:
cin - standardowy strumień wejściowy - klawiatura - (istream),
cout - standardowy strumień wyjściowy - ekran - (ostream),
cerr - strumień komunikatów błędów - zazwyczaj ekran - (ostream),
clog - w pełni buforowany strumień komunikatów błędów,
PORÓWNANIE WE/WY *proceduralnego* i *obiektowego*
Wczytywanie danych z klawiatury i wydruk na ekranie
// podejście proceduralne # include <stdio.h> void main( void ) { char znak; int x; long y; double z; char tekst[ 20 ]; scanf( "%c", &znak ); scanf( "%d", &x ); scanf( "%ld", &y ); scanf( "%lf", &z ); scanf( "%19s", tekst );
printf( "znak = %c \n" , znak ); printf( "int = %d \n" , x ); printf( "long = %d \n" , y ); printf( "double = %f \n" , z ); printf( "tekst = %s \n" , tekst ); } |
// podejście obiektowe # include <iostream.h> void main( void ) { char znak; int x; long y; double z; char tekst[ 20 ]; cin >> znak; // cin.get(znak); cin >> x; cin >> y; cin >> z; cin >> tekst; //cin.getline(tekst,19)
cout << "znak =" << znak << "\n"; cout << "int =" << x << "\n"; cout << "long =" << y << "\n"; cout << "double = " << z << "\n"; cout << "tekst = " << tekst << "\n"; } |
strumienie plikowe → klasa fstream
Klasa fstream jest klasą pochodną od klas iostream (istream + ostream) oraz fstreambase. Jej definicja zawarta jest w pliku <fstream.h>.
Najważniejsze metody tej klasy:
void open( char ∗nazwa_pliku, int tryb_otwarcia ) - otwarcie pliku,
void close( void ) - zamknięcie pliku skojarzonego ze strumieniem
Oraz wszystkie metody klas pierwotnych (względem fstream):
z klasy ios → fail, good, eof, width, precision
z klasy istream → get, getline, read, <<
z klasy ostream → put, write, >>
Kopiowanie plików tekstowych z jednoczesną zamianą liter na duże
// podejście proceduralne # include <stdio.h> # include <ctype.h> void main( void ) { char znak; FILE ∗wej, ∗wyj; wej = fopen( "dane.dat", "rt" ); wyj = fopen( "wyniki.dat", "wt" ); if( (wej!=NULL) && (wyj!=NULL) ) { while( !feof(wej) ) { znak = fgetc(wej); znak = toupper(znak); fputc( znak,wyj ); } } fclose( wej ); fclose( wyj ); } |
// podejście obiektowe # include <fstream.h> # include <ctype.h> void main( void ) { char znak; fstream wej,wyj; wej.open( "dane.dat", ios::in ); wyj.open( "wyniki.dat", ios::out ); if( wej.good( ) && wyj.good( ) ) { while( ! wej.eof( ) ) { wej.get( znak ); znak = toupper( znak ); wyj.put( znak ); } } wej.close( ); wyj.close( ); } |
// funkcja wyznaczająca pozycję maksymalnej liczby double w pliku binarnym
# include <fstream.h>
# include <values.h>
double POZYCJA_MAKSIMUM( char ∗nazwa_pliku )
{
long licznik=0, pozycja=0; double liczba, max = -MAXDOUBLE;
fstream plik( nazwa_pliku , ios::in | ios::binary );
while( plik.good( ) && !plik.eof( ) )
{
plik.read( (char*)&liczba, sizeof(double) );
licznik++;
if( liczba>max )
{
max=liczba; pozycja=licznik;
}
}
plik.close( );
return( pozycja );
}
tablice wielowymiarowe
Operacje na tablicach wielowymiarowych w zapisie indeksowym
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 ); // cout << ” TAB[ ” << i << ”,” << j << ”] =”
scanf( ”%d” , &tab[ i ] [ j ] ); // cin >> tab[ i ] [ j ]
}
Reprezentacja tablicy int tab[3][5] w pamięci komputera:
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 );
}
int tab[ 3 ][ 5 ] ;
int 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 na zmienną:
int ∗∗tab2 → Wskaźnik na tablicę wskaźników na tablice:
// 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 ; // wsk_pom = ∗(tab2 + 0) ;
∗tab2 = ∗(tab2 + 2) ; // ∗(tab2 + 0) = ∗(tab2 + 2) ;
∗(tab2 + 2) = wsk_pom ;
Dostęp do dowolnego obszaru pamięci
Dostęp do zmiennej poprzez nazwę lub wskaźnik:
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 ; // wysokość = (double∗) malloc( sizeof(double) ) ;
∗wysokość = 170 ;
Dostęp do dowolnych bajtów pamięci:
double far ∗wysokość ;
wysokość = (double far∗) adres_początku_8_bajtów_w_pamięci ;
// na komputerach typu IBM PC:
wysokość = (double far∗) MK_FP( segment_adresu , offset_adresu ) ;
∗wysokość = 170 ;
Np. bezpośredni zapis znaku do pamięci obrazu:
char far ∗pierwszy_znak ;
pierwszy_znak = (char far∗) MK_FP( 0xB800 , 0x0000 ) ;
∗pierwszy_znak = `A' ;
// równoważne:
gotoxy( 1, 1 );
cprintf( ”A” );
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++ ;
Obszar pamięci widziany jako tablice o różnej strukturze:
int i ;
double A[ 2 ];
for( i=0 ; i < 2; i++ )
A[ i ] = 0 ;
long∗ B = (long∗) A ;
for( i=0 ; i < 4; i++ )
B[ i ] = 0 ;
int∗ C = (int∗) A ;
for( i=0 ; i < 8; i++ )
C[ i ] = 0 ;
char∗ D = (char∗) A ;
for( i=0 ; i < 16; i++ )
D[ i ] = 0 ;
Bezpośredni zapis do pamięci ekranu (wypełnianie spacjami, biały znak, czarne tło):
// 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; }
// 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 ;
// 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 ;
WSKAZANIA NA FUNKCJE
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() ; // nazwa funkcji z nawiasami () jest traktowana jako „wywolanie
// funkcji” tzn. polecenie wykonania fragmentu kodu umieszczo-
// nego pod podanym adresem
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 );
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 ) // 1, 1/2, 1/3, 1/4, 1/5, ...
{ return( 1.0/(n+1) ); }
. . .
printf( ”Suma elementów = %lf”, Suma_Ciagu( Nty_Element , 100 );
. . .
funkcja qsort <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:
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
);
Przykład sortowania tablicy liczb całkowitych
#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] );
}
Przykład sortowania tablicy tekstów
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int teksty_rosnaco( const void ∗wsk_1, const void ∗wsk_2)
{
return( strcmp( (char ∗) wsk_1, (char ∗) wsk_2) );
}
void main( void )
{
char tab_tekstow[5][10] = { "Opel", "Audi", "Ford", "Trabant", "Fiat" };
qsort( tab_tekstow, 5, sizeof( tab_tekstow[0] ) , teksty_rosnaco );
}
Przykład sortowania bazy danych (tablicy struktur)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student
{
char nazwisko[31];
char imie[16];
int wiek;
char plec;
float stypendium;
} ;
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 );
}
Przykłady różnych kombinacji wskaźników
Przykładowe elementy:
float LICZBA; // liczba rzeczywista float
int TAB_INT [ 5 ]; // 5-cio elementowa tablica liczb int
double FUNKCJA ( int x ) // funkcja z parametrem int zwracająca
{ // wartość double
return x+0.1;
}
Wskaźniki na w/w elementy:
float∗ wsk_liczby ; // wskaźnik na liczbę float
wsk_liczby = & LICZBA ;
int ( ∗wsk_tab ) [ 5 ] ; // wskaźnik na 5-cio elementowa tablicę
wsk_tab = & TAB_INT ;
double ( ∗wsk_fun ) ( int ) ; // wskaźnik na funkcję
wsk_fun = FUNKCJA ;
Tablice elementów:
float tab_liczb [ 10 ] ; // tablica liczb float
int tab_tab [ 10 ] [ 5 ] ; // tablica tablic
// − − − − − − − − − − // nie ma tablicy funkcji
Tablice wskaźników na elementy:
float∗ tab_wsk_liczb [ 10 ] ; // tablica wskaźników na liczby
tab_wsk_liczb [ 2 ] = & LICZBA ;
int (∗ tab_wsk_tab [ 10 ] ) [ 5 ] ; // tablica wskaźników na tablice
tab_wsk_tab [ 2 ] = & TAB_INT ;
double (∗ tab_wsk_fun [ 10 ] ) ( int ) ; // tablica wskaźników na funkcje
tab_wsk_fun [ 2 ] = FUNKCJA ;
Funkcje zwracające elementy:
float FUNKCJA_E1 ( void ) // funkcja zwracająca liczbę float
{ return 0.1; }
// − − − − − − − − − − // nie ma funkcji zwracającej tablice
// − − − − − − − − − − // nie ma funkcji zwracającej funkcje
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 ] // funkcja (void) zwracająca
{ // wskaźnik na tablicę
int (∗wsk_tab)[ 5 ] ; // pięciu liczb int
wsk_tab = &TAB_INT ;
return wsk_tab ;
}
double (∗ FUNKCJA_W3( void ) ) ( int ) // funkcja (void) zwracająca
{ // wskaźnik na funkcję
double (∗wsk_fun)( int ); // double (int)
wsk_fun = FUNKCJA ;
return wsk_fun ;
}
Tylko dla koneserów
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 ;
}
DynamicznE Przyd. pamieci
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) → tzw. STOS (ang. stack)
dane dynamiczne (zmienne, które można tworzyć i usuwać w dowolnym momencie pracy programu) → w pamięci wolnej komputera → tzw. STERTA (ang. heap)
Zmienne dynamiczne → są to zmienne tworzone przez programistę w pamięci wolnej komputera (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); // przydział tablicy
void free( void ∗wskaznik); // zwolnienie wskazywanego obszaru
unsigned coreleft( void ); // 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;
∗wsk = 10; // przykładowe operacje na dynamicznej liczbie int
∗wsk ∗= 2;
printf( ”%d”, ∗wsk );
scanf( ”%d”, wsk );
• • •
free( wsk ); // zwolnienie pamięci przed zakończeniem programu
Przykład operacji na dynamicznej tablicy o dowolnej ilości elementów:
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 );
}
W języku „C++” do dynamicznego przydzielania pamięci wygodniej jest wykorzystywać operatory new i delete :
<wskaźnik_na_obiekt> = new <typ_obiektu> [parametry_inicjacyjne] ;
delete <wskaźnik_na_obiekt> ;
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 ; // usunięcie zmiennej dynam. (zwolnienie pamięci)
}
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 ;
double ∗tablica_dynamiczna;
tablica_dynamiczna = new double[ rozmiar_tablicy ];
• • •
delete [ ] tablica_dynamiczna;
Przykład operacji na jednej strukturze utworzonej dynamicznie:
// ( 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 );
}
Operacje na dynamicznej tablicy struktur o dowolnej ilości elementów:
int rozmiar_tablicy;
struct dane_osobowe ∗baza;
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;
}
• • •
// 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) );
• • •
}
• • •
if( baza != NULL )
free( baza ); // zwolnienie pamięci przed zakończeniem programu
M.Piasecki: INFORMATYKA 1 − 1 − (W1) Wstęp, podstawy języka C++
INE 1007 − Informatyka 1
Dr Marek Piasecki Język programowania C++ (W01)
M.Piasecki: INFORMATYKA 1 − 3 − (W1) Wstęp, podstawowe pojęcia
M.P. PASCAL (P01) − 4 − Wstęp, pierwsze programy
M.Piasecki: INFORMATYKA 1 − 4 − (W1) Wstęp, pierwsze programy
M.Piasecki: INFORMATYKA 1 − 5 − (W1) Wstęp, podstawowe operacje wej/wyj
M.Piasecki: INFORMATYKA 1 − 6 − (W1) Wstęp, podstawowe operacje wej/wyj
M.Piasecki: INFORMATYKA 1 − 6 − (W1) Wstęp, typy predefiniowane
M.Piasecki: INFORMATYKA 1 − 10 − (W2) Operacje wejścia/wyjścia
OPERACJE WEJŚCIA / WYJŚCIA
M.Piasecki: INFORMATYKA 1 − 7 − (W2) Operacje wejścia/wyjścia
M.Piasecki: INFORMATYKA 1 24 (W4) Tablice w języku C/C++
M.Piasecki: INFORMATYKA 1 − 32 − (W6) Definiowanie własnych funkcji
M.Piasecki: INFORMATYKA 1 − 35 − (W6) Przekazywanie parametrów funkcji
M.Piasecki: INFORMATYKA 1 − 58 − (W10) Wskaźniki a tablice wielowymiarowe
M.Piasecki: INFORMATYKA 1 − 60 − (W10) Dostęp do dowolnego obszaru pamieci
M.Piasecki: INFORMATYKA 1 − 63 − (W10) Wskazania na funkcje
M.Piasecki: INFORMATYKA 1 − 65 − (W10) Różne kombinacje wskaźników
M.Piasecki: INFORMATYKA 1 − 68 − (W1) Wstęp, pierwsze programy