Учебники

D Программирование — Указатели

Указатели на программирование D легко и весело выучить. Некоторые задачи D-программирования выполняются с помощью указателей, а другие задачи D-программирования, такие как динамическое распределение памяти, не могут быть выполнены без них. Простой указатель показан ниже.

Указатель в D

Вместо прямого указания на переменную указатель указывает на адрес переменной. Как вы знаете, каждая переменная является ячейкой памяти, и каждая ячейка памяти имеет свой адрес, к которому можно обратиться, используя оператор амперсанда (&), который обозначает адрес в памяти. Рассмотрим следующее, которое печатает адрес определенных переменных:

Live Demo

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

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —

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

Что такое указатели?

Указатель — это переменная, значением которой является адрес другой переменной. Как и любая переменная или константа, вы должны объявить указатель, прежде чем вы сможете работать с ним. Общая форма объявления переменной указателя —

type *var-name;

Здесь тип — это базовый тип указателя; это должен быть допустимый тип программирования, а var-name — это имя переменной-указателя. Звездочка, которую вы использовали для объявления указателя, та же, что и для умножения. Тем не мение; в этом утверждении звездочка используется для обозначения переменной в качестве указателя. Следующее является действительным объявлением указателя —

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

Фактический тип данных значения всех указателей, будь то целое число, число с плавающей запятой, символ или другое, является одним и тем же, длинное шестнадцатеричное число, представляющее адрес памяти. Единственное различие между указателями разных типов данных — это тип данных переменной или константы, на которую указывает указатель.

Использование указателей в D-программировании

Есть несколько важных операций, когда мы очень часто используем указатели.

  • мы определяем переменные указателя

  • назначить адрес переменной указателю

  • наконец, получить доступ к значению по адресу, доступному в переменной указателя.

мы определяем переменные указателя

назначить адрес переменной указателю

наконец, получить доступ к значению по адресу, доступному в переменной указателя.

Это делается с помощью унарного оператора *, который возвращает значение переменной, расположенной по адресу, указанному ее операндом. В следующем примере используются эти операции —

Live Demo

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

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —

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

Нулевые указатели

Рекомендуется всегда присваивать указатель NULL переменной-указателю, если у вас нет точного адреса для назначения. Это делается во время объявления переменной. Указатель, которому присваивается значение NULL, называется указателем NULL .

Пустой указатель — это константа со значением ноль, определенная в нескольких стандартных библиотеках, включая iostream. Рассмотрим следующую программу —

Live Demo

import std.stdio;

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

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —

The value of ptr is null

В большинстве операционных систем программам не разрешен доступ к памяти по адресу 0, поскольку эта память зарезервирована операционной системой. Тем не мение; адрес памяти 0 имеет особое значение; он сигнализирует о том, что указатель не предназначен для указания на доступную ячейку памяти.

По соглашению, если указатель содержит нулевое (нулевое) значение, предполагается, что он ничего не указывает. Чтобы проверить нулевой указатель, вы можете использовать оператор if следующим образом:

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

Таким образом, если все неиспользуемые указатели имеют нулевое значение и вы избегаете использования нулевого указателя, вы можете избежать случайного неправильного использования неинициализированного указателя. Часто неинициализированные переменные содержат некоторые ненужные значения, и становится трудно отлаживать программу.

Указатель Арифметика

Существует четыре арифметических оператора, которые можно использовать в указателях: ++, -, + и —

Чтобы понять арифметику указателей, рассмотрим целочисленный указатель с именем ptr , который указывает на адрес 1000. Предполагая 32-разрядные целые числа, выполним следующую арифметическую операцию с указателем:

ptr++ 

тогда ptr будет указывать на местоположение 1004, потому что каждый раз, когда ptr увеличивается, он указывает на следующее целое число. Эта операция переместит указатель на следующую ячейку памяти, не влияя на фактическое значение в ячейке памяти.

Если ptr указывает на символ, адрес которого равен 1000, то вышеуказанная операция указывает на местоположение 1001, потому что следующий символ будет доступен на 1001.

Увеличение указателя

Мы предпочитаем использовать указатель в нашей программе вместо массива, потому что указатель переменной можно увеличивать, в отличие от имени массива, которое нельзя увеличивать, потому что это постоянный указатель. Следующая программа увеличивает указатель переменной для доступа к каждому последующему элементу массива:

Live Demo

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

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —

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

Указатели против массива

Указатели и массивы тесно связаны. Однако указатели и массивы не являются полностью взаимозаменяемыми. Например, рассмотрим следующую программу —

Live Demo

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

В приведенной выше программе вы можете увидеть var.ptr [2] для установки второго элемента и ptr [0], который используется для установки нулевого элемента. Оператор инкремента может использоваться с ptr, но не с var.

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —

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

Указатель на указатель

Указатель на указатель — это форма множественного косвенного обращения или цепочка указателей. Обычно указатель содержит адрес переменной. Когда мы определяем указатель на указатель, первый указатель содержит адрес второго указателя, который указывает на местоположение, которое содержит фактическое значение, как показано ниже.

C ++ указатель на указатель

Переменная, которая является указателем на указатель, должна быть объявлена ​​как таковая. Это делается путем размещения дополнительной звездочки перед ее именем. Например, следующий синтаксис для объявления указателя на указатель типа int —

int **var; 

Когда на целевое значение косвенно указывает указатель на указатель, то для доступа к этому значению требуется дважды применить оператор звездочки, как показано ниже в примере:

Live Demo

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

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —

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

Передача указателя на функции

D позволяет передавать указатель на функцию. Для этого он просто объявляет параметр функции как тип указателя.

Следующий простой пример передает указатель на функцию.

Live Demo

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

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —

Average is :214.4 

Возврат указателя из функций

Рассмотрим следующую функцию, которая возвращает 10 чисел, используя указатель, означает адрес первого элемента массива.

Live Demo

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

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —

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

Указатель на массив

Имя массива — это постоянный указатель на первый элемент массива. Поэтому в декларации —

double balance[50];

balance — это указатель на & balance [0], который является адресом первого элемента массива balance. Таким образом, следующий фрагмент программы назначает p адрес первого элемента баланса

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

Допустимо использовать имена массивов в качестве указателей на константы, и наоборот. Следовательно, * (баланс + 4) является законным способом доступа к данным на балансе [4].

Как только вы сохраните адрес первого элемента в p, вы можете получить доступ к элементам массива, используя * p, * (p + 1), * (p + 2) и так далее. В следующем примере показаны все концепции, рассмотренные выше.

Live Demo

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

Когда приведенный выше код компилируется и выполняется, он дает следующий результат —