Programowanie D - wskaźniki
Wskaźniki programowania D są łatwe i przyjemne do nauczenia. Niektóre zadania programowania D są łatwiejsze do wykonania za pomocą wskaźników, a inne zadania programowania D, takie jak dynamiczna alokacja pamięci, nie mogą być wykonywane bez nich. Poniżej przedstawiono prosty wskaźnik.
Zamiast bezpośrednio wskazywać na zmienną, wskaźnik wskazuje adres zmiennej. Jak wiadomo, każda zmienna jest miejscem w pamięci, a każda lokalizacja pamięci ma zdefiniowany adres, do którego można uzyskać dostęp za pomocą operatora ampersand (&), który oznacza adres w pamięci. Rozważmy następujący, który wypisuje adres zdefiniowanych zmiennych -
import std.stdio;
void main () {
int var1;
writeln("Address of var1 variable: ",&var1);
char var2[10];
writeln("Address of var2 variable: ",&var2);
}
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Address of var1 variable: 7FFF52691928
Address of var2 variable: 7FFF52691930
Co to są wskaźniki?
ZA pointerjest zmienną, której wartością jest adres innej zmiennej. Jak w przypadku każdej zmiennej lub stałej, przed rozpoczęciem pracy należy zadeklarować wskaźnik. Ogólną postacią deklaracji zmiennej wskaźnikowej jest -
type *var-name;
Tutaj, typejest typem bazowym wskaźnika; musi to być prawidłowy typ programowania ivar-namejest nazwą zmiennej wskaźnika. Gwiazdka użyta do zadeklarowania wskaźnika to ta sama gwiazdka, której używasz do mnożenia. Jednak; w tej instrukcji gwiazdka jest używana do oznaczenia zmiennej jako wskaźnika. Poniżej znajduje się prawidłowa deklaracja wskaźnika -
int *ip; // pointer to an integer
double *dp; // pointer to a double
float *fp; // pointer to a float
char *ch // pointer to character
Rzeczywisty typ danych wartości wszystkich wskaźników, niezależnie od tego, czy są to liczby całkowite, zmiennoprzecinkowe, znakowe, czy inne, jest taki sam, długa liczba szesnastkowa, która reprezentuje adres pamięci. Jedyną różnicą między wskaźnikami różnych typów danych jest typ danych zmiennej lub stałej, na którą wskazuje wskaźnik.
Używanie wskaźników w programowaniu D.
Jest kilka ważnych operacji, gdy używamy wskaźników bardzo często.
definiujemy zmienne wskaźnikowe
przypisać adres zmiennej do wskaźnika
na koniec uzyskaj dostęp do wartości pod adresem dostępnym w zmiennej wskaźnika.
Odbywa się to za pomocą jednoargumentowego operatora *która zwraca wartość zmiennej znajdującej się pod adresem określonym przez jej operand. Poniższy przykład wykorzystuje te operacje -
import std.stdio;
void main () {
int var = 20; // actual variable declaration.
int *ip; // pointer variable
ip = &var; // store address of var in pointer variable
writeln("Value of var variable: ",var);
writeln("Address stored in ip variable: ",ip);
writeln("Value of *ip variable: ",*ip);
}
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Value of var variable: 20
Address stored in ip variable: 7FFF5FB7E930
Value of *ip variable: 20
Puste wskaźniki
Zawsze dobrze jest przypisać wskaźnik NULL do zmiennej wskaźnikowej, na wypadek gdybyś nie posiadał dokładnego adresu do przypisania. Odbywa się to w momencie deklaracji zmiennej. Wskaźnik, któremu przypisano wartość null, nazywa się anull wskaźnik.
Wskaźnik zerowy jest stałą o wartości zero zdefiniowaną w kilku standardowych bibliotekach, w tym w iostream. Rozważ następujący program -
import std.stdio;
void main () {
int *ptr = null;
writeln("The value of ptr is " , ptr) ;
}
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
The value of ptr is null
W większości systemów operacyjnych programy nie mają dostępu do pamięci pod adresem 0, ponieważ pamięć ta jest zarezerwowana przez system operacyjny. Jednak; adres pamięci 0 ma szczególne znaczenie; sygnalizuje, że wskaźnik nie ma wskazywać dostępnego miejsca w pamięci.
Zgodnie z konwencją, jeśli wskaźnik zawiera wartość null (zero), zakłada się, że nie wskazuje niczego. Aby sprawdzić pusty wskaźnik, możesz użyć instrukcji if w następujący sposób -
if(ptr) // succeeds if p is not null
if(!ptr) // succeeds if p is null
Tak więc, jeśli wszystkie nieużywane wskaźniki mają wartość null i unikasz używania wskaźnika pustego, możesz uniknąć przypadkowego niewłaściwego użycia niezainicjowanego wskaźnika. Często niezainicjalizowane zmienne przechowują pewne niepotrzebne wartości i debugowanie programu staje się trudne.
Arytmetyka wskaźników
Istnieją cztery operatory arytmetyczne, których można używać na wskaźnikach: ++, -, + i -
Aby zrozumieć arytmetykę wskaźników, rozważmy wskaźnik o nazwie całkowitej ptr, co wskazuje na adres 1000. Zakładając 32-bitowe liczby całkowite, wykonajmy na wskaźniku następującą operację arytmatyczną -
ptr++
a później ptrwskaże lokalizację 1004, ponieważ za każdym razem, gdy ptr jest zwiększane, wskazuje na następną liczbę całkowitą. Ta operacja przeniesie wskaźnik do następnej lokalizacji pamięci bez wpływu na rzeczywistą wartość w tej lokalizacji.
Gdyby ptr wskazuje na znak o adresie 1000, to powyższa operacja wskazuje na lokalizację 1001, ponieważ następny znak będzie dostępny pod 1001.
Zwiększanie wskaźnika
Preferujemy użycie wskaźnika w naszym programie zamiast tablicy, ponieważ wskaźnik zmiennej może być zwiększany, w przeciwieństwie do nazwy tablicy, której nie można zwiększać, ponieważ jest wskaźnikiem stałym. Poniższy program zwiększa wskaźnik zmiennej, aby uzyskać dostęp do każdego kolejnego elementu tablicy -
import std.stdio;
const int MAX = 3;
void main () {
int var[MAX] = [10, 100, 200];
int *ptr = &var[0];
for (int i = 0; i < MAX; i++, ptr++) {
writeln("Address of var[" , i , "] = ",ptr);
writeln("Value of var[" , i , "] = ",*ptr);
}
}
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Address of var[0] = 18FDBC
Value of var[0] = 10
Address of var[1] = 18FDC0
Value of var[1] = 100
Address of var[2] = 18FDC4
Value of var[2] = 200
Wskaźniki a tablica
Wskaźniki i tablice są silnie powiązane. Jednak wskaźniki i tablice nie są całkowicie zamienne. Na przykład rozważmy następujący program -
import std.stdio;
const int MAX = 3;
void main () {
int var[MAX] = [10, 100, 200];
int *ptr = &var[0];
var.ptr[2] = 290;
ptr[0] = 220;
for (int i = 0; i < MAX; i++, ptr++) {
writeln("Address of var[" , i , "] = ",ptr);
writeln("Value of var[" , i , "] = ",*ptr);
}
}
W powyższym programie możesz zobaczyć var.ptr [2], aby ustawić drugi element i ptr [0], który jest używany do ustawienia elementu zerowego. Operator inkrementacji może być używany z ptr, ale nie z var.
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Address of var[0] = 18FDBC
Value of var[0] = 220
Address of var[1] = 18FDC0
Value of var[1] = 100
Address of var[2] = 18FDC4
Value of var[2] = 290
Wskaźnik do wskaźnika
Wskaźnik do wskaźnika jest formą wielokrotnego pośrednictwa lub łańcuchem wskaźników. Zwykle wskaźnik zawiera adres zmiennej. Kiedy definiujemy wskaźnik do wskaźnika, pierwszy wskaźnik zawiera adres drugiego wskaźnika, który wskazuje lokalizację zawierającą rzeczywistą wartość, jak pokazano poniżej.
Zmienna będąca wskaźnikiem do wskaźnika musi być zadeklarowana jako taka. Odbywa się to poprzez umieszczenie dodatkowej gwiazdki przed jego nazwą. Na przykład, poniżej przedstawiono składnię deklarowania wskaźnika do wskaźnika typu int -
int **var;
Gdy wartość docelowa jest pośrednio wskazywana przez wskaźnik do wskaźnika, dostęp do tej wartości wymaga dwukrotnego zastosowania operatora gwiazdki, jak pokazano poniżej w przykładzie -
import std.stdio;
const int MAX = 3;
void main () {
int var = 3000;
writeln("Value of var :" , var);
int *ptr = &var;
writeln("Value available at *ptr :" ,*ptr);
int **pptr = &ptr;
writeln("Value available at **pptr :",**pptr);
}
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Value of var :3000
Value available at *ptr :3000
Value available at **pptr :3000
Przekazywanie wskaźnika do funkcji
D umożliwia przekazanie wskaźnika do funkcji. Aby to zrobić, po prostu deklaruje parametr funkcji jako typ wskaźnika.
Poniższy prosty przykład przekazuje wskaźnik do funkcji.
import std.stdio;
void main () {
// an int array with 5 elements.
int balance[5] = [1000, 2, 3, 17, 50];
double avg;
avg = getAverage( &balance[0], 5 ) ;
writeln("Average is :" , avg);
}
double getAverage(int *arr, int size) {
int i;
double avg, sum = 0;
for (i = 0; i < size; ++i) {
sum += arr[i];
}
avg = sum/size;
return avg;
}
Kiedy powyższy kod jest kompilowany i wykonywany razem, daje następujący wynik -
Average is :214.4
Zwróć wskaźnik z funkcji
Rozważmy następującą funkcję, która zwraca 10 liczb za pomocą wskaźnika, czyli adres pierwszego elementu tablicy.
import std.stdio;
void main () {
int *p = getNumber();
for ( int i = 0; i < 10; i++ ) {
writeln("*(p + " , i , ") : ",*(p + i));
}
}
int * getNumber( ) {
static int r [10];
for (int i = 0; i < 10; ++i) {
r[i] = i;
}
return &r[0];
}
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
*(p + 0) : 0
*(p + 1) : 1
*(p + 2) : 2
*(p + 3) : 3
*(p + 4) : 4
*(p + 5) : 5
*(p + 6) : 6
*(p + 7) : 7
*(p + 8) : 8
*(p + 9) : 9
Wskaźnik do tablicy
Nazwa tablicy jest stałym wskaźnikiem do pierwszego elementu tablicy. Dlatego w deklaracji -
double balance[50];
balancejest wskaźnikiem do & balance [0], który jest adresem pierwszego elementu tablicy balance. W ten sposób przypisuje się następujący fragment programup adres pierwszego elementu balance -
double *p;
double balance[10];
p = balance;
Dozwolone jest używanie nazw tablic jako stałych wskaźników i na odwrót. Dlatego * (balance + 4) jest legalnym sposobem dostępu do danych w bilansie [4].
Po zapisaniu adresu pierwszego elementu w p, możesz uzyskać dostęp do elementów tablicy za pomocą * p, * (p + 1), * (p + 2) i tak dalej. Poniższy przykład pokazuje wszystkie koncepcje omówione powyżej -
import std.stdio;
void main () {
// an array with 5 elements.
double balance[5] = [1000.0, 2.0, 3.4, 17.0, 50.0];
double *p;
p = &balance[0];
// output each array element's value
writeln("Array values using pointer " );
for ( int i = 0; i < 5; i++ ) {
writeln( "*(p + ", i, ") : ", *(p + i));
}
}
Kiedy powyższy kod jest kompilowany i wykonywany, daje następujący wynik -
Array values using pointer
*(p + 0) : 1000
*(p + 1) : 2
*(p + 2) : 3.4
*(p + 3) : 17
*(p + 4) : 50