Programação D - Ponteiros

Os ponteiros de programação em D são fáceis e divertidos de aprender. Algumas tarefas de programação em D são realizadas mais facilmente com ponteiros, e outras tarefas de programação em D, como alocação de memória dinâmica, não podem ser realizadas sem eles. Um ponteiro simples é mostrado abaixo.

Em vez de apontar diretamente para a variável, o ponteiro aponta para o endereço da variável. Como você sabe, cada variável é um local da memória e cada local da memória tem seu endereço definido, que pode ser acessado usando o operador E comercial (&) que denota um endereço na memória. Considere o seguinte, que imprime o endereço das variáveis ​​definidas -

import std.stdio;
 
void main () { 
   int var1; 
   writeln("Address of var1 variable: ",&var1);  
   
   char var2[10]; 
   writeln("Address of var2 variable: ",&var2); 
}

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

Address of var1 variable: 7FFF52691928 
Address of var2 variable: 7FFF52691930

O que são ponteiros?

UMA pointeré uma variável cujo valor é o endereço de outra variável. Como qualquer variável ou constante, você deve declarar um ponteiro antes de trabalhar com ele. A forma geral de uma declaração de variável de ponteiro é -

type *var-name;

Aqui, typeé o tipo base do ponteiro; deve ser um tipo de programação válido evar-nameé o nome da variável de ponteiro. O asterisco que você usou para declarar um ponteiro é o mesmo asterisco que você usa para a multiplicação. Contudo; nesta declaração, o asterisco está sendo usado para designar uma variável como um ponteiro. A seguir estão as declarações de ponteiro válidas -

int    *ip;    // pointer to an integer 
double *dp;    // pointer to a double 
float  *fp;    // pointer to a float 
char   *ch     // pointer to character

O tipo de dados real do valor de todos os ponteiros, seja inteiro, flutuante, caractere ou outro, é o mesmo, um número hexadecimal longo que representa um endereço de memória. A única diferença entre ponteiros de diferentes tipos de dados é o tipo de dados da variável ou constante para a qual o ponteiro aponta.

Usando ponteiros na programação D

Existem algumas operações importantes, quando usamos os ponteiros com muita freqüência.

  • nós definimos variáveis ​​de ponteiro

  • atribuir o endereço de uma variável a um ponteiro

  • finalmente acesse o valor no endereço disponível na variável de ponteiro.

Isso é feito usando o operador unário *que retorna o valor da variável localizada no endereço especificado por seu operando. O exemplo a seguir faz uso dessas operações -

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); 
}

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

Value of var variable: 20 
Address stored in ip variable: 7FFF5FB7E930 
Value of *ip variable: 20

Ponteiros nulos

É sempre uma boa prática atribuir o ponteiro NULL a uma variável de ponteiro, caso você não tenha um endereço exato a ser atribuído. Isso é feito no momento da declaração da variável. Um ponteiro atribuído a nulo é chamado denull ponteiro.

O ponteiro nulo é uma constante com valor zero definido em várias bibliotecas padrão, incluindo iostream. Considere o seguinte programa -

import std.stdio;

void main () { 
   int  *ptr = null; 
   writeln("The value of ptr is " , ptr) ;  
}

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

The value of ptr is null

Na maioria dos sistemas operacionais, os programas não têm permissão para acessar a memória no endereço 0 porque essa memória é reservada pelo sistema operacional. Contudo; o endereço de memória 0 tem um significado especial; ele sinaliza que o ponteiro não se destina a apontar para um local de memória acessível.

Por convenção, se um ponteiro contém o valor nulo (zero), ele não aponta para nada. Para verificar se há um ponteiro nulo, você pode usar uma instrução if da seguinte maneira -

if(ptr)     // succeeds if p is not null 
if(!ptr)    // succeeds if p is null

Portanto, se todos os ponteiros não usados ​​receberem o valor nulo e você evitar o uso de um ponteiro nulo, poderá evitar o uso incorreto acidental de um ponteiro não inicializado. Muitas vezes, as variáveis ​​não inicializadas contêm alguns valores inúteis e torna-se difícil depurar o programa.

Pointer Arithmetic

Existem quatro operadores aritméticos que podem ser usados ​​em ponteiros: ++, -, + e -

Para entender a aritmética de ponteiros, vamos considerar um ponteiro inteiro chamado ptr, que aponta para o endereço 1000. Assumindo números inteiros de 32 bits, vamos realizar a seguinte operação aritmática no ponteiro -

ptr++

então o ptrirá apontar para a localização 1004 porque cada vez que ptr é incrementado, ele aponta para o próximo inteiro. Esta operação moverá o ponteiro para o próximo local da memória sem afetar o valor real no local da memória.

E se ptr aponta para um personagem cujo endereço é 1000, então a operação acima aponta para o local 1001 porque o próximo caractere estará disponível em 1001.

Incrementando um Ponteiro

Preferimos usar um ponteiro em nosso programa em vez de um array porque o ponteiro variável pode ser incrementado, ao contrário do nome do array que não pode ser incrementado porque é um ponteiro constante. O programa a seguir incrementa o ponteiro variável para acessar cada elemento sucessivo da matriz -

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); 
   } 
}

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

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

Ponteiros vs Array

Ponteiros e arrays estão fortemente relacionados. No entanto, ponteiros e matrizes não são completamente intercambiáveis. Por exemplo, considere o seguinte programa -

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); 
   } 
}

No programa acima, você pode ver var.ptr [2] para definir o segundo elemento e ptr [0] que é usado para definir o elemento zero. O operador de incremento pode ser usado com ptr, mas não com var.

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

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

Ponteiro para Ponteiro

Um ponteiro para um ponteiro é uma forma de múltiplas vias indiretas ou uma cadeia de ponteiros. Normalmente, um ponteiro contém o endereço de uma variável. Quando definimos um ponteiro para um ponteiro, o primeiro ponteiro contém o endereço do segundo ponteiro, que aponta para a localização que contém o valor real, conforme mostrado abaixo.

Uma variável que é um ponteiro para um ponteiro deve ser declarada como tal. Isso é feito colocando um asterisco adicional na frente de seu nome. Por exemplo, a seguir está a sintaxe para declarar um ponteiro para um ponteiro do tipo int -

int **var;

Quando um valor de destino é indiretamente apontado por um ponteiro para um ponteiro, o acesso a esse valor requer que o operador asterisco seja aplicado duas vezes, como é mostrado abaixo no exemplo -

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); 
}

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

Value of var :3000 
Value available at *ptr :3000 
Value available at **pptr :3000

Passando Ponteiro para Funções

D permite que você passe um ponteiro para uma função. Para fazer isso, ele simplesmente declara o parâmetro da função como um tipo de ponteiro.

O exemplo simples a seguir passa um ponteiro para uma função.

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; 
}

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

Average is :214.4

Return Pointer from Functions

Considere a seguinte função, que retorna 10 números usando um ponteiro, significa o endereço do primeiro elemento da matriz.

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]; 
}

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

*(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

Ponteiro para uma matriz

Um nome de array é um ponteiro constante para o primeiro elemento do array. Portanto, na declaração -

double balance[50];

balanceé um ponteiro para & balance [0], que é o endereço do primeiro elemento da matriz balance. Assim, o seguinte fragmento de programa atribuip o endereço do primeiro elemento de balance -

double *p; 
double balance[10]; 
 
p = balance;

É legal usar nomes de array como ponteiros constantes e vice-versa. Portanto, * (saldo + 4) é uma forma legítima de acessar os dados em saldo [4].

Depois de armazenar o endereço do primeiro elemento em p, você pode acessar os elementos do array usando * p, * (p + 1), * (p + 2) e assim por diante. O exemplo a seguir mostra todos os conceitos discutidos acima -

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)); 
   } 
}

Quando o código acima é compilado e executado, ele produz o seguinte resultado -

Array values using pointer  
*(p + 0) : 1000 
*(p + 1) : 2 
*(p + 2) : 3.4 
*(p + 3) : 17
*(p + 4) : 50