LEKCJA 23 - Co nowego w C++?
________________________________________________________________
Z tej lekcji dowiesz się, jakie mechanizmy C++ pozwalają na
stosowanie nowoczesnego obiektowego i zdarzeniowego stylu
programowania i co programy robią z pamięcią.
________________________________________________________________
W porównaniu z klasycznym C - C++ posiada:
* rozszerzony zestaw słów kluczowych (ang. keywords):
** nowe słowa kluczowe C++:
class - klasa,
delete - skasuj (dynamicznie utworzony obiekt),
friend - "zaprzyjaźnione" funkcje z dostępem do danych,
inline - wpleciony (funkcje przeniesione w formie rozwiniętej
do programu wynikowego),
new - utwórz nowy obiekt,
operator - przyporządkuj operatorowi nowe działanie,
private - dane i funkcje prywatne klasy (obiektu), do których
zewnętrzne funkcje nie mają prawa dostępu,
protected - dane i funkcje "chronione", dostępne z
ograniczeniami,
public - dane i funklcje publiczne, dostępne bez ograniczeń,
template - szablon,
this - ten, pointer wskazujący bieżący obiekt,
virtual - funkcja wirtualna, abstrakcyjna, o zmiennym
działaniu.
* nowe operatory (kilka przykładów już widzieliśmy), np.:
<< - wyślij do strumienia wyjściowego,
>> - pobierz ze strumienia wejściowego.
* nowe typy danych:
klasy,
obiekty,
abstrakcyjne typy danych (ang. ADT).
* nowe zasady posługiwania się funkcjami:
funkcje o zmiennej liczbie argumentów,
funkcje "rozwijane" inline,
funkcje wirtualne, itp.;
Przede wszystkim (i od tego właśnie rozpoczniemy) zobaczymy
funkcje o nowych możliwościach.
ROZSZERZENIE C - FUNKCJE.
Funkcje uzyskują w C++ znacznie więcej możliwości. Przegląd
rozpoczniemy od sytuacji często występującej w praktyce
programowania - wykorzystywania domyślnych (ang. default)
parametrów.
FUNKCJE Z DOMYŚLNYMI ARGUMENTAMI.
Prototyp funkcji w C++ pozwala na podanie deklaracji domyślnych
wartości argumentów funkcji. Jeśli w momencie wywołania funkcji
w programie jeden (lub więcej) argument (ów) zostanie pominięte,
kompilator wstawi w puste miejsce domyślną wartość argumentu.
Aby uzyskać taki efekt, prototyp funkcji powinien zostać
zadeklarowany w programie np. tak:
void Funkcja(int = 7, float = 1.234);
Efekt takiego działania będzie następujący:
Wywołanie w programie: Efekt:
________________________________________________________________
Funkcja(99, 5.127); Normalnie: Funkcja(99, 5.127);
Funkcja(99); Funkcja(99, 1.234);
Funkcja(); Funkcja(7, 1.234);
________________________________________________________________
[!!!] Argumentów może ubywać wyłącznie kolejno. Sytuacja:
Funkcja(5.127); //ŹLE
Funkcja(99); //DOBRZE
jest w C++ niedopuszczalna. Kompilator potraktuje liczbę 5.127
jako pierwszy argument typu int i wystąpi konflikt.
[P079.CPP]
#include
void fun_show(int = 1234, float = 222.00, long = 333L);
main()
{
fun_show(); // Trzy arg. domyslne
fun_show(1); // Pierwszy parametr
fun_show(11, 2.2); // Dwa parametry
fun_show(111, 2.22, 3L); // Trzy parametry
return 0;
}
void fun_show(int X, float Y, long Z)
{
cout << "\nX = " << X;
cout << ", Y = " << Y;
cout << ", Z = " << Z;
}
Uruchom program i przekonaj się, czy wstawianie argumentów
domyślnych przebiega poprawnie.
W KTÓRYM MIEJSCU UMIESZCZAĆ DEKLARACJE ZMIENNYCH.
C++ pozwala deklarować zmienne w dowolnym miejscu, z
zastrzeżeniem, że deklaracja zmiennej musi nastąpić przed jej
użyciem. Umieszczanie deklaracji zmiennych możliwie blisko
miejsca ich użycia znacznie poprawia czytelność (szczególnie
dużych "wieloekranowych") programów. Klasyczny sposób deklaracji
zmiennych:
int x, y, z;
...
main()
{
...
z = x + y + 1;
...
}
może zostać zastąpiony deklaracją w miejscu zastosowania (w tym
np. wewnątrz pętli):
main()
{
...
for ( int i = 1; i <= 10; i++)
cout << "Biezace i wynosi: " << i;
...
}
Należy jednak pamiętać o pewnym ograniczeniu. Zmienne
deklarowane poza funkcją main() są traktowane jako zmienne
globalne i są widoczne (dostępne) dla wszystkich innych
elementów programu. Zmienne deklarowane wewnątrz bloku/funkcji
są zmiennymi lokalnymi i mogą "przesłaniać" zmienne globalne.
Jeśli wielu zmiennym nadamy te same nazwy-identyfikatory, możemy
prześledzić mechanim przesłaniania zmiennych w C++. W
przykładzie poniżej zastosowano trzy zmienne o tej samej nazwie
"x":
[P080.CPP]
//Program demonstruje przesłanianie zmiennych
#include
int x = 1; //Zmienna globalna
void daj_x(void); //Prototyp funkcji
main()
{
int x = 22; //Zmienna lokalna funkcji main
cout << ::x << " <-- To jest globalny ::x \n";
cout << x << " <-- A to lokalny x \n";
daj_x();
return 0;
}
void daj_x(void)
{
cout << "To ja funkcja daj_x(): \n";
cout << ::x << " <-- To jest globalny ::x \n";
cout << x << " <-- A to lokalny x \n";
int x = 333;
cout << "A to moja zmienna lokalna - automatyczna ! \n";
cout << x << " <-- tez x ";
}
Program wydrukuje tekst:
1 <-- To jest globalny ::x
22 <-- A to lokalny x
To ja funkcja daj_x():
1 <-- To jest globalny ::x
1 <-- A to lokalny x
A to moja zmienna lokalna - automatyczna !
333 <-- tez x
Zwróć uwagę, że zmienne deklarowane wewnątrz funkcji (tu:
main()) nie są widoczne dla innych funkcji (tu: daj_x()).
Operator :: (ang. scope) pozwala nam wybierać pomiędzy zmiennymi
globalnymi a lokalnymi.
TYP WYLICZENIOWY enum JAKO ODRĘBNY TYP ZMIENNYCH.
W C++ od momentu zdefiniowania typu wyliczeniowego enum staje
się on równoprawnym ze wszystkimi innymi typem danych. Program
poniżej demonstruje przykład wykorzystania typu enum w C++.
[P081.CPP]
# include
enum ciuchy
{
niewymowne = 1, skarpetka, trampek, koszula, marynarka,
czapa, peruka, koniec
};
main()
{
ciuchy n;
do
{
cout << "\nNumer ciucha ? --> (1-7, 8 = quit): ";
cin >> (int) n;
switch (n)
{
case niewymowne: cout << "niewymowne";
break;
case skarpetka: cout << "skarpetka";
break;
case trampek: cout << "trampek";
break;
case koszula: cout << "koszula";
break;
case marynarka: cout << "marynarka";
break;
case czapa: cout << "czapa";
break;
case peruka: cout << "peruka";
break;
case koniec: break;
default:
cout << "??? Tego chyba nie nosze...";
}
} while (n != koniec);
return 0;
}
Zwróć uwagę w programie na forsowanie typu (int) przy pobraniu
odpowiedzi-wyboru z klawiatury. Ponieważ w C++ "ciuchy" stanowią
nowy (zdefiniowany przed chwilą) typ danych, do utożsamienia ich
z typem int niezbędne jest wydanie takiego polecenia przy
pobieraniu danych ze strumienia cin >> . W opcjach pracy
kompilatora możesz włączyć/wyłączyć opcję "Treat enums as int"
(traktuj typ enum jak int) i wtedy pominąć forsowanie typu w
programie.
JEDNOCZESNE ZASTOSOWANIE DWU KOMPILATORÓW.
Jak już wspomnieliśmy wcześniej kompilator C++ składa się w
istocie z dwu różnych kompilatorów:
* kompilatora C wywoływanego standardowo dla plików *.C,
* kompilatora C++ wywoływanego standardowo dla plików *.CPP.
Oba kompilatory stosują RÓŻNE metody tworzenia nazw zewnętrznych
(ang. external names). Jeśli zatem program zawiera moduł, w
którym funkcje zostały przekompilowane w trybie
charakterystycznym dla klasycznego C - C++ powinien zostać o tym
poinformowany. Dla przykładu, C++
* kategorycznie kontroluje zgodność typów argumentów,
* na swój własny użytek dodaje do nazw funkcji przyrostki (ang.
suffix) pozwalające na określenie typu parametrów,
* pozwala na tworzenie tzw. funkcji polimorficznych (kilka
różnych funkcji o tej samej nazwie), itp.
Zwykły C tego nie potrafi i nie robi. Dlatego też do
wprowadzenia takiego podziału kompetencji należy czasem
zastosować deklarację extern "C". Funkcja rand() w programie
poniżej generuje liczbę losową.
[P081.CPP]
#include
extern "C"
{
# include //Prototyp rand() w STDLIB.H
}
main()
{
cout << rand();
return 0;
}
GENERACJA LICZB LOSOWYCH.
Kompilatory C++ umożliwoają generację liczb pseudolosowych
użytecznych często w obliczeniach statystycznych (np. metoda
Monte Carlo) i emulacji "rozmytaj" arytmetyki i logiki
(ang.fuzzy math).
[!!!] UWAGA - Liczby PSEUDO-Losowe.
________________________________________________________________
Funkcja rand() powoduje uruchomienie generatora liczb
pseudolosowych. Jeśli chcesz uzyskać liczbę pseudolosową z
zadanego przedziału wartości, najlepiej zastosuj dzielenie
modulo:
int n = rand % 10;
powoduje tzw. normalizację. Reszta z dzielenia przez 10 może być
wyłącznie liczbą z przedziału 0...9.
Aby przy każdym urichomieniu aplikacji ciąg liczb pseudolosowych
rozpoczynał się od innej wartości należy uruchomić generator
liczb wcześniej - przed użyciem funkcji rand() - np.:
randomize();
...
int n = rand() % 100;
...
________________________________________________________________
W programie przykładowym funkcje z STDLIB.H zostaną skompilowane
przez kompilator C. Określenie trybu kompilacji deklaracją
extern "C" jest umieszczane zwykle nie wewnątrz programu
głównego a w dołączanych plikach nagłówkowych *.H. Jest to
możliwość szczególnie przydatne, jeśli dysponujesz bibliotekami
funkcji dla C a nie masz chęci, czasu, bądź możliwości
przerabiania ich na wersję przystosowaną do wymagań C++. Drugi
przykład poniżej zajmuje się sortowaniem krewnych przy pomocy
funkcji C qsort().
[P082.CPP]
# include
# include
# include
extern "C" int comp(const void*, const void*);
main()
{
int max;
for(;;)
{
cout << "\n Ilu krewnych chcesz posortowac? (1...6): ";
cin >> max;
if( max > 0 && max < 7) break;
cout << "\n Nic z tego...";
}
static char* krewni[] =
{
"Balbina - ciotka",
"Zenobiusz - kuzyn",
"Kleofas - stryjek",
"Ola - kuzynka (ach)",
"Waleria - tez niby ciotka",
"Halina - stryjenka"
};
qsort(krewni, 6, sizeof(char*), comp);
for (int n = 0; n < max; n++)
cout << "\n" << krewni[n];
return 0;
}
extern "C"
{
int comp(const void *x, const void *y)
{
return strcmp(*(char **)x, *(char **)y);
}
}
Program wykonuje następujące czynności:
* deklaruje prototyp funkcji typu C,
* deklaruje statyczną tablicę wskaźników do łańcuchów znakowych,
* sortuje wskaźniki,
* wyświetla posortowane łańcuchy znakowe,
* definiuje funkcję comp() - porównaj,
* wykorzystuje funkcję biblioteczną C - strcmp() - String
Compare do porównania łańcuchów znaków.
O PAMIĘCI.
Program w C++ dzieli dostępną pamięć na kilka obszarów o
określonym z góry przeznaczeniu. Dla zaawansowanego programisty
zrozumienie i efektywne wykorzystanie mechanizmów zarządzania
pamięcią w C++ może okazać się wiedzą wielce przydatną.
Zaczniemy, jak zwykle od "elementarza".
CO PROGRAM ROBI Z PAMIĘCIĄ.
W klasycznym C najczęściej stosowanymi do zarządzania pamięcią
funkcjami są:
* malloc() - przyporządkuj pamięć,
* farmalloc() - przyporządkuj odległą pamięć,
* realloc() - przyporządkuj powtórnie (zmienioną) ilość pamięci,
* calloc() - przydziel pamięć i wyzeruj,
* free() - zwolnij pamięć.
Pamięć dzielona jest w obszarze programu na następujące bloki:
___________________
niskie adresy --> Ngłówek programu I.
Adres startowy
KOD: Kod programu
___________________
Zmienne statyczne II.
DANE: 1. Zainicjowane Zmienne globalne
___________________
Zmienne statyczne III.
DANE: 2. Niezainicjowane Zmienne globalne
___________________
STERTA: (heap) W miarę potrzeby IV.
rośnie w dół.
Tu operują funkcje
malloc(), free().
___________________
POLE NICZYJE: V.
___________________
W miarę potrzeby VI.
STOS: (stack) rośnie w górę.
wysokie adresy --> ___________________
W obszarze kodu (I.) znajdują się instrukcje. Na stosie
przechowywane są:
* zmienne lokalne,
* argumenty przekazywane funkcji w momencie jej wywołania,
* adresy powrotne dla funkcji (RET == CS:IP).
Na stercie natomiast przy pomocy funkcji (a jak przekonamy się
za chwilę - także operatorów C++) możemy przydzielać pamięć dla
różnych obiektów tworzonych w czasie pracy programu (ang.
run-time memory allocation) - np. tworzyć bufory dla łańcuchów,
tablic, struktur itp.. Zwróć uwagę, że obszar V. - POLE NICZYJE
może być w czasie pracy programu stopniowo udostępniany dla
stosu (który rozrasta się "w górę"), albo dla sterty (która
rozrasta się "w dół"). W przykładowym programie poniżej podano,
w którym obszarze pamięci zostanie umieszczony dany element
programu.
# include
int a; // III.
int b = 6; // II.
main()
{
char *Dane;
...
float lokalna; // VI.
...
Dane = malloc(16); // IV.
...
}
OPERATORY new I delete.
Operatory new i delete działają podobnie do pary funkcji
malloc() - free(). Pierwszy przyporządkowuje - drugi zwalnia
pamięć. Dokładniej rzecz biorąc
- operator new może zostać zastosowany wraz ze wskaźnikiem do
bloku danych określonego typu:
* struktury danych,
* tablicy, itp. (wkrótce zastosujemy go także w stosunku do
klas i obiektów);
- przyporządkowuje pamięć blokowi danych;
- przypisuje począkowy adres bloku pamięci wskaźnikowi.
- operator delete zwalnia pamięć przyporządkowaną poprzednio
blokowi danych,
Operatory new i delete mogą współdziałać z danymi wieloma typami
danych (wcale nie tylko ze strukturami), jednakże rozpoczniemy
do struktury Data zawierającej datę urodzenia mojej córki.
[P083.CPP]
# include "iostream.h"
struct Data
{
int dzien;
int miesiac;
int rok;
};
void main()
{
Data *pointer = new Data;
/* Dekl. wskaznik do struct typu Data */
/* Przydziel pamiec dla struktury */
pointer -> miesiac = 11; // pole "miesiac" = 11
pointer -> dzien = 3;
pointer -> rok = 1979;
cout << "\n URODZINY CORKI: ";
cout << pointer -> dzien << '.';
cout << pointer -> miesiac << ". ";
cout << "co rok ! od " << pointer -> rok << " r.";
delete pointer; //Skasuj wskaznik - zwolnij pamiec.
}
Program tworzy w pamięci (dokł. na stercie) strukturę typu Data
bez nazwy. O którą strukturę chodzi i gdzie jej szukać w pamięci
wiemy dzięki wskaźnikowi do struktury *pointer. Zapis
Data *pointer = new Data;
oznacza jednoczesną deklarację i zainicjowanie wskaźnika.
TWORZENIE DYNAMICZNYCH TABLIC O ZMIENNEJ WIELKOŚCI.
Jeśli mamy dane wyłącznie jednego typu (tu: int), zastosowanie
struktury jest właściwie przysłowiowym "strzelaniem z armaty do
wróbli". Trójelementowa tablica typu
int TAB[3];
zupełnie nam wystarczy. Utworzymy ją jednak nie jako tablicę
globalną (bądź statyczną) w obszarze pamięci danych, lecz
dynamicznie - na stercie.
[P084.CPP]
# include "iostream.h"
main()
{
int *pointer = new int[3]; // Przydziel pamiec
pointer[0] = 3; // Tabl_bez_nazwy[0] - dzien
pointer[1] = 11; // Tabl_bez_nazwy[1] - miesiac
pointer[2] = 1979;
cout << "Data urodzenia: ";
for(int i = 0; i < 3; i++)
cout << pointer[i] << '.';
delete pointer;
}
Uważny Czytelnik doszedł zapewne do wniosku, że skoro tablica
tworzona jest dynamicznie w ruchu programu (run-time), to
kompilator nie musi znać na etapie kompilacji programu
(compile-time) wielkości tablicy! Idąc dalej, program powinien
taką techniką tworzyć tablice o takiej wielkośći, jakiej w ruchu
zażyczy sobie użytkownik. Spróbujmy zrealizować to praktycznie.
[P085.CPP]
# include
# include
# include
void main()
{
for(;;)
{
cout << "\nPodaj wielkosc tablicy (1...100) --> ";
int i, size;
cin >> size;
/* Na stercie tworzymy dynamiczna tablica: */
int *pointer = new int[size];
/* Wypelniamy tablice liczbami naturalnymi: */
for (i = 0; i < size; i++)
pointer[i] = i;
cout << "\n TABLICA: \n";
/* Sprawdzamy zawartosc tablicy: */
for (i = 0; i < size; i++)
cout << " " << pointer[i];
char k = getch();
if(k == 'a') break;
delete pointer;
}
}
Twój dialog z programem powinien wyglądać następująco:
Podaj wielkosc tablicy (1...100) --> 20
TABLICA:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19
Podaj wielkosc tablicy (1...100) --> 100
TABLICA:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
Skoro dynamiczne tablice o zmiennej wielkości "chodzą", możemy
wykorzystać to w bardziej interesujący sposób.
[P086.CPP]
# include
# include
# include
extern "C"
{
int Fporownaj(const void* x, const void* y)
{
return (strcmp(*(char **)x, *(char **)y));
}
}
main()
{
cout << "Wpisz maksymalna ilosc imion --> ";
int ilosc, i;
cin >> ilosc;
char **pointer = new char *[ilosc];
for (i = 0; i < ilosc; i++)
{
cout << "Podaj imie Nr: " << i + 1 << "--> ";
char *imie = new char[80];
cin >> imie;
if (strcmp(imie, "stop") == 0) break;
else
pointer[i] = new char[strlen(imie)+1];
strcpy(pointer[i], imie);
delete imie;
}
qsort(pointer, i, sizeof(char *), Fporownaj);
for (i = 0; i < ilosc; i++)
cout << pointer[i] << '\n';
for (i = 0; i < ilosc; i++)
delete pointer[i];
delete pointer;
return 0;
}
Tworzymy dynamicznie przy pomocy operatora new bezimienną
tablicę składającą się z tablic niższego rzędu (łańcuch znaków
to też tablica tyle, że jednowymiarowa - ma tylko długość).
Zwróć uwagę, że w C++ wskaźnik do wskaźnika (**pointer)
odpowiada konstrukcji "tablica składająca się z tablic". Aby
program uczynić bardziej poglądowym spolszczymy nazwy funkcji
przy pomocy preprocesora.
[P087.CPP]
# define Fporown_string strcmp
# define Fkopiuj_string strcpy
# define Fsortuj qsort
# include
# include
# include
extern "C"
{
int Fporownaj(const void* x, const void* y)
{
return (Fporown_string(*(char **)x, *(char **)y));
}
}
main()
{
cout << "Wpisz maksymalna ilosc imion --> ";
int ilosc, i;
cin >> ilosc;
char **pointer = new char *[ilosc];
for (i = 0; i < ilosc; i++)
{
cout << "Podaj imie Nr: " << i + 1 << "--> ";
char *imie = new char[80];
cin >> imie;
if (Fporown_string(imie, "stop") == 0) break;
else
pointer[i] = new char[strlen(imie)+1];
Fkopiuj_string(pointer[i], imie);
delete imie;
}
/* w tym momencie i == ilosc */
Fsortuj(pointer, i, sizeof(char *), Fporownaj);
for (i = 0; i < ilosc; i++)
cout << pointer[i] << '\n';
for (i = 0; i < ilosc; i++)
delete pointer[i];
delete pointer;
return 0;
}
Wskaźnik może wskazywać dane o różnym stopniu złożoności:
zmienną, tablicę, strukturę, obiekt (o czym za chwilę), ale może
wskazywać także funkcję.
JEŚLI ZABRAKNIE PAMIĘCI - _new_handler.
Aby obsługiwać błędną sytuację - brakło pamięci na stercie -
potrzebna nam będzie funkcja - tzw. HANDLER. Aby jedna było
wiadomo, gdzie szukać handlera, powinniśmy operatorowi new
przekazać informację jaka funkcja obsługuje brak pamięci i gdzie
jej szukać.
Możemy podstawiać na miejsce funkcji stosowanej w programie tę
funkcję, która w danym momencie jest nam potrzebna. Jest to
praktyka często stosowana w programach obiekktowych, więc
przypomnijmy raz jeszcze przykładowy program - tym razem w
trochę innym kontekście. Aby wskazać funkcję zastosujemy
wskaźnik. . Przypomnijmy deklarację
double ( *Funkcja ) (double);
[P088.CPP]
#include
#include
#include
double Nasza_F(double); //Deklaracja zwyklej funkcji
double (*Funkcja)(double); //pointer do funkcji
double liczba; //zwyczajna zmienna
int wybor;
int main(void)
{
clrscr();
cout << "\nPodaj Liczbe \n";
cin >> Liczba;
cout << "CO OBLICZYC ?\n________________\n";
cout<<"1 - Sin \n2 - Cos \n3 - Odwrotnosc 1/X\n";
switch(cin >> wybor)
{
case 1: Funkcja = sin; break;
case 2: Funkcja = cos; break;
case 3: Funkcja = Nasza_F; break;
}
cout << "\n\nWYNIK = " << Funkcja(liczba);
return (0);
}
double Nasza_F(double x)
{
if (x != 0)
x = 1/x;
else
cout << "???\n";
return x;
}
Komputer nie jest "z gumy" i nie posiada dowolnie dużej
"rozciągliwej" pamięci. Funkcja malloc(), jeśli pamięci
zabraknie, zwraca pusty wskaźnik (ang. NULL pointer), co można
łatwo przetestować w programie. Jeśli natomiast stosujemy
operator new - konsekwentnie - operator new powinien zwracać
NULL (i próbować dokonać przypisania pointerowi zero). To też
można sprawdzić w programie.
W C++ istnieje jednak również inny, przydatny do tych celów
mechanizm. C++ dysponuje globalnym wskaźnikiem _new_handler
(wskaźnik do funkcji obsługującej operator new, jeśli zabraknie
pamięci). Dzięki istnieniu tego (predefiniowanego) wskaźnika
możemy przyporządkować "handler" - funkcję obsługującą wyjście
przez operator new poza dostępną pamięć.
Dopóki nie zażyczymy sobie inaczej, wskaźnik
_new_handler == NULL // NULL == 0
i operator new w przypadku niepowodzenia próby przyporządkowania
pamięci zwróci wartość NULL inicjując pusty wskaźnik (innymi
słowy "wskaźnik do nikąd"). Jeśli jednak
_new_handler != NULL
to zawartość wskaźnika zostanie przez operator new uznana za
adres startowy funkcji obsługi błędnej sytuacji (ang. addres to
call).
[P089.CPP]
# include
# include
static void Funkcja()
{
cout << "\nTo ja ... Funkcja - handler \n";
cout << '\a' << " ! BRAK PAMIECI ! ";
exit (1);
}
extern void (*_new_handler)();
long suma; //Automatycznie suma = 0;
void main()
{
_new_handler = Funkcja; //Inicjujemy wskaznik
for(;;)
{
char *pointer = new char[8192];
suma += 8192;
cout << "\nMam juz " << suma << " znakow w RAM\n";
if (pointer != 0)
cout << "Pointer != NULL";
}
}
[!!!] SPRAWDŹ - KONIECZNIE!
________________________________________________________________
W programach użytkowych, a szczególnie w tych oferowanych
klientom jako produkty o charakterze komercyjnym należy ZAWSZE
sprawdzać poprawność wykonania newralgicznych operacji - a
szczególnie poprawność zarządzania pamięcią i poprawność
operacji dyskowych. Utrata danych, lub nie zauważone i nie
wykryte przez program przekłamanie może spowodować przykre
skutki. Raz utracone dane mogą okazać sie nie do odzyskania.
________________________________________________________________
Wyszukiwarka
Podobne podstrony:
www livemocha com angielski lekcja audio
jezyk ukrainski lekcja 03
Lekcja sortowanie
lekcja12
Kris Jamsa Wygraj Z C lekcja32
lekcja1 (2)
Lekcja7
ćw oswajające z piłką lekcja dla dzieci
Logo na lekcjach matematyki w szkole podstawowej
C LEKCJA18
lekcja
Kris Jamsa Wygraj Z C lekcja 5
Lekcja algorytmy w geometrii
LEKCJA 1 Uwierz w siebie, możesz wszystko!
Lekcja 7 Trening pamieci to nie wszystko Zadbaj o swoja koncentracje
lekcja6
więcej podobnych podstron