Instrukcja continue
Instrukcja continue stosowana jest w pętlach
for, while, do..while
Powoduje zaniechanie wykonania instrukcji w bieżącym wywołaniu
instrukcji pętli i przejście do kolejnego obiegu pętli.
Przyklad
#include <iostream.h>
main()
{
int i;
for(i=0;i<10;i++)
{
cout<<"a";
if(i>2) continue;
cout<<"b\n";
}
}
Ekran po wykonaniu programu:
ab
ab
ab
aaaaaaa
Stale
Stałe – liczby całkowite
w systemie dziesiętnym:
17 –5 20
w systemie ósemkowym:
010 dziesiątkowo 8
014 dziesiątkowo 8+4=12
w systemie szesnastkowym:
0x10 dziesiątkowo 1*16+0=16
0xa1 dziesiątkowo 10*16+1=161
0xff dziesiątkowo 15*16+15=255
Stałe całkowite są traktowane jako int.
Wymuszenia:
do typu long: 20L (20l)
do typu unsigned: 20u
do typu unsigned long: 20uL
Stałe – liczby zmiennoprzecinkowe
zapis z kropką dziesiętną:
12.3 –234.87
zapis w notacji naukowej:
8e2 oznacza 8×102=800
Wskazniki
Definiowanie wskaźników:
typ_obiektu *nazwa_wskaźnika;
nazwa_wskaźnika wskazuje na obiektu typu typ_obiektu.
Przykłady
Można definiować wskaźniki do obiektów różnych typów:
Uwaga:
Z definicji wskaźnika wynika, że wskaźnik pokazuje na obiekt. Referencja nie jest obiektem, dlatego nie można definiować wskaźników do referencji.
Wskaźnik, który pokazuje na obiekt jednego typu, nie może być wykorzystany do pokazywania na obiekt innego typu.
Sama definicja wskaźnika nie powoduje, że wskaźnik wskazuje na konkretnyobiekt.
Nadanie wartości obiektowi (wskaźnik wskazuje na istniejący obiekt):
Jeśli wskaźnik wskazuje na konkretny obiekt, można odnosić się do tego obiektu za pomocą wskaźnika. Do operacji tej służy jednoargumentowy operator odniesienia (wyłuskania) *:
Przyklad1
Przyklad2
Definicja wskaźnika bez podania typu obiektu, na jaki wskazuje. Może być użyty do wskazywania na obiekty dowolnego typu.
Przykład
Wskaźnik dowolnego (niestałego) typu można przypisać wskaźnikowi typu void. działanie odwrotne wymaga operatora rzutowania.
int *wsk; //definicja wskaźnika
int tab[10]; //definicja tablicy
wsk=&tab[indeks]; //ustawienie wskaźnika na elemencie tablicy
//o indeksie indeks
wsk=&tab[0];
wsk=tab;
wsk=&tab[indeks];
wsk=wsk+ind;
wsk += ind;
Dodanie do wskaźnika liczby całkowitej ind powoduje, że wskaźnik pokazuje o ind elementów dalej w tablicy; niezależnie od tego, jakiego typu są elementy tablicy.
#include <iostream.h>
void main(){
float *wf;
float tab[10];
wf=tab; //lub wf=&tab[0];
for(int i=0; i<10; i++){
*(wf++)=i/10.0;
}
for(i=0,wf=tab;i<10;i++,wf++)
cout<<*wf<<endl;
Nazwa tablicy jest jednocześnie adresem jej zerowego elementu.
To stwierdzenie nie jest równoważne stwierdzeniu:
Nazwa tablicy jest jednocześnie wskaźnikiem do jej zerowego elementu.
float *wsk;
float tab[10];
wsk=tab; //wsk=&tab[0];
//możliwe jest przypisanie
wsk++;
//niemożliwa jest instrukcja
tab++;
Nazwa tablicy jest stałym wskaźnikiem do jej zerowego elementu.
Wskaźnik jest pewnym obiektem w pamięci (to znaczy posiada swój adres).
Nazwa (również tablicy) nie jest obiektem (nie ma więc adresu).
float *wsk;
&wsk; //adres wskaźnika – odwołanie poprawne
Przesylanie tablic do funkcji
#include <iostream.h>
void f1(int *pi, int rozm);
void f2(int *pi, int rozm);
void f3(int t[], int rozm);
void main(){
int tab[5]={1,2,3,4,5};
f1(tab,5);
f2(tab,5);
f3(tab,5);
}
void f1(int *pi, int rozm){
cout<<"\nfunkcja f1\t";
for(int i=0;i<rozm;i++)
cout<<*(pi++)<<'\t';
}
void f2(int *pi, int rozm){
cout<<"\nfunkcja f2\t";
for(int i=0;i<rozm;i++)
cout<<pi[i]<<'\t';
}
void f3(int t[], int rozm){
cout<<"\nfunkcja f3\t";
for(int i=0;i<rozm;i++)
cout<<t[i]<<'\t';
}
Odebranie tablicy jako tablicy – czytelność funkcji
Odebranie tablicy jako adresu inicjującego wskaźnik – funkcja działa szybciej
Odebranie tablicy jako adresu – łatwiejsze przekazywanie tablic
wielowymiarowych (rozmiary tablicy nie muszą być znane w momencie wywołania funkcji)
Odczytanie wartości segmentu i offsetu wskaźnika umożliwiają makra:
unsigned FP_SEG(void far *wsk); // segment wskaźnika
unsigned FP_OFF(void far *wsk); // offset wskaźnika
Utworzenie wskaźnika o określonym segmencie i offsecie umożliwia makro:
void far *MK_FP(unsigned segment, unsigned offset).
Np.
void far *d = MK_FP(0xB800, 0x0000);
unsigned s = FP_SEG(d); // s = 0xB800;
unsigned o = FP_OFF(d); // o = 0x0000;
Uwaga! Aby użyć powyższych makr trzeba dołączyć plik nagłówkowy dos.h.
Wskaźniki do stałych mogą zawierać adresy dowolnych zmiennych i mogą być modyfikowane w programie, ale nie można za ich pomocą modyfikować zmiennych wskazywanych.
const int stala = 10; // definicja stałej typu int
const int *wsk_st; // wskaźnik do stałej typu int
void main() {
int i = 5;
const int *w = &i; // wskaźnik zainicjowany
wsk_st = &i; // inicjacja adresem zmiennej automatycznej i
cout << *wsk_st << endl; // 5
cout << *w << endl; // 5
// *w = *w + 5; // błąd kompilatora !
// *wsk_st+= 7; // nie można modyfikować stałej
wsk_st = &stala; // wskaźnik inicjowany adresem stałej
w = &stala; // wskaźnik inicjowany adresem stałej
cout << *wsk_st << endl; // 10
cout << *w << endl; // 10
}
Stały wskaźnik to wskaźnik, który zawsze pokazuje na to samo. Wskaźnik tego typu musi być zainicjowany w miejscu definicji - tak jak każda stała.
W programie nie można już modyfikować jego wartości.
Za pomocą wskaźnika stałego można jednak modyfikować zawartość zmiennej wskazywanej, ale tylko tej, której adresem wskaźnik został zainicjowany.
const int st = 4; // stała
int zm = 10; // definicja zmiennej
int * const w = &zm; // stały wskaźnik do zmiennej zm
// int * const x = &st; // błąd – wskaźnik musi wskazywać na stałą
const * const x = &st; // dobrze - stały wskaźnik do stałej
int i = 7;
int * const wi = &i; // stały wskaźnik do zmiennej lokalnej i
int * const pz = &zm; // stały wskaźnik do zmiennej zm
cout << *wi << endl; // i=7 = i
cout << *pz << *w << endl; // 10 i 10; zm=10
*wi +=8; // i = 7 + 8 = 15
cout << i << endl; // i =15
// wi = &zm; // błąd – nie wolno zmieniać stałej wi
*pz += 2; // zm = 10 + 2
cout << zm << endl; // zm = 12
typ_wskazywany *tablica[rozmiar];
//typ_wskazywany *(tablica[rozmiar]); zapis równoważny
double *tablica[10];
Funkcja otrzymuje kopie wskaźnika, który może określać adres zmiennej należącej do innej funkcji. Zmiana zawartości obszaru wskazywanego przez parametry wskaźnikowe prowadzi do zmiany wartości zmiennych utworzonych w innych funkcjach programu.
Wskaźnik – zmienna przeznaczona do przechowywania adresu innej zmiennej
Przekazywanie parametrów przez wskaźniki jest stosowane:
do przekazywania struktur danych o dużych rozmiarach (np. tablic);
w celu umożliwienia zwrócenia więcej niż jednej wartości.
typ_wartości nazwa_funkcji (typ_1 *par1, typ_2 *par2, ... )
Podczas wywołania funkcji należy przekazać adresy argumentów:
zmienna = nazwa_funkcji(&arg1, &arg2, ....);
np. scanf(”%d”, &x);
Przykład
#include <iostream.h>
#include <conio.h>
void sum(int *x, int y);
void main(void)
{int a, b;
clrscr();
a = 5; b = 10;
cout << "\nWartosc przed wywolaniem funkcji a = " << a; // a = 5
sum(&a,b); // funkcja nie zwraca wyniku a = 15
cout << "\nWartosc po wywolaniu funkcji a = " << a; // a = 15
getch();
}
void sum(int *x, int y)
{
*x = *x + y;
cout << "\nLiczba wewnatrz funkcji x = " << *x;
}
Tablice
Przekazywanie tablicy do funkcji
Przekazywanie tablicy do funkcji – przesyłany jest tylko adres początku tablicy.
void funkcja(int tab[]);
.....
//fragment programu
int t[]={1,4,-2};
funkcja(t); //wywołanie funkcji
Uwaga!!!
Nazwa tablicy jest jednocześnie adresem zerowego elementu tej tablicy.
t+2 jest w pamięci adresem elementu o indeksie 2, czyli jest równoważne zapisowi &t[2]
Znak & (ampersand) – to jednoargumentowy operator do uzyskania adresu obiektu.
Wartość elementu o adresie &t[2] to t[2]
Tablice znakowe
char tekst[20]={"przyklad"};
Tablice ciągów znaków – na końcu zawsze dodawany jest znak o kodzie 0,znak NULL.
Znak NULL jest automatycznie dodawany do końca ciągu, ponieważ ciąg "przyklad" został ujęty w cudzysłów.
Znak NULL nie został na końcu dodany bo nie ma cudzysłowu.
Jednak ponieważ jest to inicjalizacja tablicy 20 elementowej, to reszta tablicy zostanie wypełniona zerami. Znak NULL ma też wartość 0 → cały ciąg zostanie poprawnie zakończony.
Znaki nie tworzą ciągu znaków. Na końcu nie będzie znaku NULL.
Dynamiczna alokacja pamieci
Rezerwowanie i zwalnianie pamięci w trakcie działania programu
Operator new – przydziela pamięć dla obiektów.
Operator delete – zwalnia pamięć przydzieloną za pomocą operatora new, (wynik działania jest typu void).
Własności zmiennych utworzonych za pomocą operatora new:
Czas życia obiektów – od momentu utworzenia operatorem new do momentu
skasowania operatorem delete.
Tak utworzony obiekt nie ma nazwy. Można się do niego odwoływać tylko
za pomocą wskaźników (operatorem wyłuskania *).
Nie podlegają regułom obowiązującym dla zwykłych zmiennych, a dotyczących zasięgu i czasu trwania
Obiekty te nie są inicjalizowane wartościami początkowymi (przydzielony blok pamięci zawiera wartość przypadkową).
Po zwolnieniu pamięci wskaźnik zmiennej nadal zawiera ten sam adres, ale zwolniona pamięć może być udostępniona dla innych zmiennych (należy uważać, aby nie wstawiać danych do pamięci wskazywanej przez „zwolnione” wskaźniki)
Jeśli alokacja pamięci powiodła się (na stercie była wystarczająca ilość wolnego miejsca), to wskaźnik jest różny od NULL.
Uwagi!
Nie należy stosować operatora delete dla obiektów, które nie zostały utworzone za pomocą new.
Nie wolno również kasować dwa razy tego samego obiektu. Aby się ustrzec przed taką możliwością wystarczy po skasowaniu obiektu ustawić jego wskaźnik na NULL. Zwalnianie wskaźnika pustego nie powoduje błędu.
Należy również uważać, aby nie utracić zawartości wskaźnika, który zawiera adres zmiennej utworzonej za pomocą operatora new. W takim przypadku nie będzie można zwolnić przydzielonej pamięci co może doprowadzić do wyczerpania sterty.
Zmienna dynamiczna może być utworzona wewnątrz funkcji. Przydzielony obszar pamięci nie znika po zakończeniu funkcji. Należy jednak zadbać o to, aby funkcja przekazała wskaźnik do utworzonego obszaru na zewnątrz.
int * Tworz1()
{return new int;} // funkcja zwraca wskaźnik do int
void Tworz2(int* *x) // przekazywanie przez wskaźnik
{*x = new int;} // funkcja ustawia wskaźnik do int
void Tworz3(int* &x) // przekazywanie przez referencję
{x = new int; } // funkcja ustawia wskaźnik do int
void main()
{ int *pw = NULL; // wskaźnik do int
pw = Tworz1(); // funkcja zwraca wskaźnik do int
if (pw) {*pw = 1; cout << *pw << endl; // 1
delete pw; pw = NULL; }
Tworz2(&pw); // przekazywanie pw przez wskaźnik
if (pw) {*pw = 2; cout << *pw << endl; // 2
delete pw; pw = NULL; }
Tworz3(pw); // przekazywanie pw przez referencję
if (pw) {*pw = 3; cout << *pw << endl; // 3
delete pw; pw = NULL; }
}
W języku C/C++ istnieją standardowe funkcje umożliwiające dynamiczną alokację pamięci (malloc i calloc) oraz funkcja zwalniająca przydzieloną pamięć (free). Prototypy funkcji:
void *malloc(size_t K); // alokacja K bajtów
void *calloc(size_t N, size_t K); // alokacja N razy po K bajtów
void free(void *x); // zwolnienie obszaru
Funkcje malloc i calloc przydzielają spójne obszary pamięci, których rozmiary nie przekraczają 64 KB (size_t jest zdefiniowany jako unsigned) i zwracają wskazanie do przydzielonego obszaru.
Jeżeli alokacja nie jest możliwa, to zwracany jest wskaźnik NULL. Funkcja calloc dodatkowo zeruje przydzieloną pamięć. Funkcja free zwraca do systemu przydzieloną pamięć.
Funkcje malloc i calloc zwracają wskaźniki do typu void dlatego niezbędne są konwersje typu przy podstawieniach do wskaźników innych typów.
Należy uważać, aby za pomocą funkcji free nie zwalniać pamięci, która nie została przydzielona.
Przyklad
void main()
{
int *pw = NULL; // wskaźnik do int
pw = (int *) malloc( sizeof(int) ); // alokacja 2 bajtów
if (pw) { *pw = 1;
cout << *pw << endl; // 1
free(pw);
pw = NULL; }
pw = (int *) calloc(1, sizeof(int) ); // 1 blok 2 bajtowy
if (pw) {
cout << *pw << endl; // wartość 0 – zawarta
// w komórce o adresie pw
*pw = 2;
cout << *pw << endl; // 2
free(pw);
pw = NULL; }
}