Указатели на программирование D легко и весело выучить. Некоторые задачи D-программирования выполняются с помощью указателей, а другие задачи D-программирования, такие как динамическое распределение памяти, не могут быть выполнены без них. Простой указатель показан ниже.
Вместо прямого указания на переменную указатель указывает на адрес переменной. Как вы знаете, каждая переменная является ячейкой памяти, и каждая ячейка памяти имеет свой адрес, к которому можно обратиться, используя оператор амперсанда (&), который обозначает адрес в памяти. Рассмотрим следующее, которое печатает адрес определенных переменных:
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-программировании
Есть несколько важных операций, когда мы очень часто используем указатели.
-
мы определяем переменные указателя
-
назначить адрес переменной указателю
-
наконец, получить доступ к значению по адресу, доступному в переменной указателя.
мы определяем переменные указателя
назначить адрес переменной указателю
наконец, получить доступ к значению по адресу, доступному в переменной указателя.
Это делается с помощью унарного оператора *, который возвращает значение переменной, расположенной по адресу, указанному ее операндом. В следующем примере используются эти операции —
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. Рассмотрим следующую программу —
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.
Увеличение указателя
Мы предпочитаем использовать указатель в нашей программе вместо массива, потому что указатель переменной можно увеличивать, в отличие от имени массива, которое нельзя увеличивать, потому что это постоянный указатель. Следующая программа увеличивает указатель переменной для доступа к каждому последующему элементу массива:
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
Указатели против массива
Указатели и массивы тесно связаны. Однако указатели и массивы не являются полностью взаимозаменяемыми. Например, рассмотрим следующую программу —
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
Указатель на указатель
Указатель на указатель — это форма множественного косвенного обращения или цепочка указателей. Обычно указатель содержит адрес переменной. Когда мы определяем указатель на указатель, первый указатель содержит адрес второго указателя, который указывает на местоположение, которое содержит фактическое значение, как показано ниже.
Переменная, которая является указателем на указатель, должна быть объявлена как таковая. Это делается путем размещения дополнительной звездочки перед ее именем. Например, следующий синтаксис для объявления указателя на указатель типа int —
int **var;
Когда на целевое значение косвенно указывает указатель на указатель, то для доступа к этому значению требуется дважды применить оператор звездочки, как показано ниже в примере:
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 позволяет передавать указатель на функцию. Для этого он просто объявляет параметр функции как тип указателя.
Следующий простой пример передает указатель на функцию.
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 чисел, используя указатель, означает адрес первого элемента массива.
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) и так далее. В следующем примере показаны все концепции, рассмотренные выше.
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)); } }
Когда приведенный выше код компилируется и выполняется, он дает следующий результат —