Указатель — одна из самых мощных функций языка C++. Указатель помогает манипулировать переменными через его адрес.
В этой статье мы подробно поговорим об указателях и их использовании в C++.
Что такое указатель?
Указатель — это переменная, которая содержит адрес ячейки памяти. Вы уже знаете, что все переменные, которые мы объявляем, имеют определенный адрес в памяти. Мы объявляем переменную-указатель, чтобы указать на эти адреса в памяти.
Общий синтаксис для объявления переменной-указателя:
1 |
datatype * variable_name; |
Например, объявление int* ptr;
Это означает, что ptr — это указатель, указывающий на переменную типа int. Следовательно, переменная-указатель всегда содержит ячейку памяти или адрес. Давайте посмотрим на работу переменных-указателей ниже.
Предположим, у нас есть следующие объявления:
1 2 3 |
Int p, *ptr; //объявление переменной p и переменной-указателя ptr p = 4; //присваивание значения 4 переменной p ptr = &p; //назначение адреса p переменной-указателю ptr |
В памяти эти объявления будут представлены следующим образом:
Это внутреннее представление указателя в памяти. Когда мы присваиваем адресную переменную переменной-указателю, она указывает на переменную, как показано в представлении выше.
Поскольку ptr имеет адрес переменной p, *ptr даст значение переменной p (переменная, на которую указывает переменная-указатель ptr).
Примечание . Оператор *, который мы используем с указателем, используется для обозначения того, что это переменная указателя.
Давайте рассмотрим некоторые концепции указателей, которые используются в C++.
Арифметика указателя
Мы знаем, что переменная-указатель всегда указывает на адрес в памяти. Среди операций, которые мы можем выполнять, у нас есть следующие арифметические операции, которые выполняются над указателями.
- Оператор приращения (++)
- Оператор декремента (–)
- Дополнение (+)
- Вычитание (-)
Давайте посмотрим на использование этих операций в примере программы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <iostream> #include <string> using namespace std; int main() { int myarray[5] = {2, 4,6, 8,10}; int* myptr; myptr = myarray; cout<<"First element in the array :"<<*myptr<<endl; myptr ++; cout<<"next element in the array :"<<*myptr<<endl; myptr +=1; cout<<"next element in the array :"<<*myptr<<endl; myptr--; cout<<"next element in the array :"<<*myptr<<endl; myptr -= 1; cout<<"next element in the array :"<<*myptr<<endl; return 0; } |
Вывод данных:
1 2 3 4 5 |
First element in the array :2 next element in the array :4 next element in the array :6 next element in the array :4 next element in the array :2 |
Обратите внимание, что оператор приращения ++ увеличивает указатель и указывает на следующий элемент в массиве. Точно так же оператор декремента уменьшает переменную-указатель на 1, чтобы она указывала на предыдущий элемент в массиве.
Мы также используем операторы + и –. Во-первых, мы добавили 1 к переменной указателя. Результат показывает, что он указывает на следующий элемент в массиве. Точно так же оператор – заставляет переменную-указатель указывать на предыдущий элемент в массиве.
Помимо этих арифметических операторов, мы также можем использовать операторы сравнения, такие как ==, < и >.
Нулевые и пустые указатели
Если в случае переменной указателя не назначен адрес переменной, то рекомендуется присвоить значение NULL переменной указателя. Переменная-указатель со значением NULL называется указателем NULL.
Нулевой указатель — это постоянный указатель с нулевым значением, определенным в заголовке iostream. Память по адресу 0 зарезервирована операционной системой, и мы не можем получить доступ к этому месту.
Используя нулевой указатель, мы можем избежать неправильного использования неиспользуемых указателей и предотвратить присвоение переменным указателя каких-либо мусорных значений.
Пустые указатели — это специальные указатели, которые указывают на значения без типа. Пустые указатели более гибкие, поскольку они могут указывать на любой тип. Но они не могут быть напрямую разыменованы. Для разыменования указатель void необходимо преобразовать в указатель, указывающий на значение с конкретным типом данных.
Мы показали работу указателя NULL и указателя void в следующем примере кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> #include <string> using namespace std; int main() { int intvar = 10; char c = 'A'; void* vptr; int* myptr = NULL; cout<<"NULL pointer value :"<<myptr<<endl; vptr = &c; char* charptr; charptr = (char*)vptr; cout<<"Void pointer vptr points to:"<<*charptr<<endl; int* intptr; vptr = &intvar; intptr = (int*)vptr; cout<<"Void pointer vptr points to:"<<*intptr; return 0; } |
Вывод данных:
1 2 3 |
NULL pointer value :0 Void pointer vptr points to:A Void pointer vptr points to:10 |
В приведенной выше программе сначала мы объявляем целочисленный указатель, которому присваивается значение NULL. После ввода этого указателя, мы видим, что значение равно 0, как и обсуждали ранее.
Далее мы объявляем указатель void. Во-первых, мы присваиваем этому указателю void адрес символьной переменной. Затем мы присваиваем указатель void указателю символа и приводим его к типу с помощью char*. Затем мы печатаем значение charptr, которое указывает на char A, которая была символьной переменной, которую мы объявили ранее, и на которую указывает указатель void.
Далее мы присваиваем целочисленную переменную указателю void, а затем выполняем те же шаги по разыменованию этого указателя void с помощью целочисленного указателя.
Массивы и указатели
Массивы и указатели тесно связаны друг с другом. Мы знаем, что имя массива указывает на первый элемент массива, и это постоянный указатель.
Мы можем присвоить этот указатель переменной указателя, а затем получить доступ к массиву либо путем уменьшения указателя, либо с помощью оператора нижнего индекса.
Мы увидим эту связь между переменной-указателем и массивом в следующем примере кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> #include <string> using namespace std; int main() { int myarray[5] = {1, 1, 2, 3, 5}; int* ptrvar; ptrvar = myarray; for(int i=0;i<5;i++) { cout<<*ptrvar<<"\t"; ptrvar++; } return 0; } |
Вывод данных:
1 |
1 1 2 3 5 |
В приведенной выше программе мы присваиваем имя массива переменной-указателю. Поскольку имя массива указывает на первый элемент в массиве, мы можем напечатать содержимое всего массива с помощью переменной-указателя и увеличить его с помощью оператора ++. Это показано на выходе.
Массив указателей
Иногда нам нужно более одной переменной-указателя в программе. Вместо объявления каждой отдельной переменной указателя мы можем объявить массив указателей.
Давайте сразу возьмем пример, чтобы продемонстрировать работу массива указателей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> #include <string> using namespace std; int main() { int myarray[5] = {2,4,6,8,10}; int *ptr[5]; //array of pointers for(int i=0;i<5;i++){ ptr[i] = &myarray[i]; } for (int i = 0; i < 5; i++) { cout << "Value of myarray[" << i << "] = "; cout << *ptr[i] << endl; } return 0; } |
Вывод данных:
1 2 3 4 5 6 7 |
Value of myarray[0] = 2 Value of myarray[1] = 4 Value of myarray[2] = 6 Value of myarray[3] = 8 Value of myarray[4] = 10 In the declaration in the above, int *ptr[5]; |
Мы это можем интерпретировать как; ptr представляет собой массив из 5 целых указателей. Следовательно, каждый элемент ptr будет указывать на переменную типа integer.
Мы используем массив целых чисел и присваиваем адрес каждого элемента массива каждому из элементов ptr. Затем мы отображаем содержимое массива ptr, выводя «*ptr[i]».
Указатель на указатели
Указатель на указатели — это не что иное, как множественные косвенные указания. Это своего рода цепочка указателей. Когда мы определяем указатель на указатели, первый указатель имеет адрес второго указателя, который, в свою очередь, имеет адрес переменной, на которую он указывает.
В памяти это будет представлено так:
Указатель на указатели объявляется следующим образом:
1 |
int** intptr; |
Вот пример кода, для лучшего понимания выше изложенного:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> #include <string> using namespace std; int main() { int *vptr; int ** intptr; int var = 10; vptr = &var; intptr = &vptr; cout<<"Variable var: "<<var<<endl; cout<<"Pointer to Variable: "<<*vptr<<endl; cout<<"Pointer to Pointer to a variable: "<<**intptr; return 0; } |
Вывод данных:
1 2 3 |
Variable var: 10 Pointer to Variable: 10 Pointer to Pointer to a variable: 10 |
В приведенной выше программе мы объявляем целочисленную переменную, целочисленный указатель и указатель на указатели на целое число. Как показано в программе, переменной указателя присваивается значение переменной. Указатель переменной указателя получает адрес переменной указателя.
В конце мы печатаем три переменные, которые отображают одно и то же значение 10, равное целочисленной переменной.
Передача указателей на функции
Передача указателей на функцию аналогична другим методам передачи параметров.
Мы вернемся к нашему обмену двумя значениями и изменим его, чтобы передать переменные-указатели в качестве параметров:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> #include <string> using namespace std; void swap(int* a, int* b) { int temp; temp = *a; *a = *b; *b = temp; } int main() { int a, b; cout<<"Enter the values to be swapped: "; cin>>a>>b; cout<<"a = "<<a<<"\t"<<"b = "<<b; swap(&a,&b); cout<<endl; cout<<"Swapped values"<<endl; cout<<"a = "<<a<<"\t"<<"b = "<<b; return 0; } |
Вывод данных:
1 2 3 4 |
Enter the values to be swapped: 3 2 a = 3 b = 2 Swapped values a = 2 b = 3 |
Как показано в программе, мы передаем значения для замены как целочисленные переменные. Формальные параметры определяются как переменные-указатели. В результате изменения, внесенные в переменные внутри функций, также отражаются в вызывающей функции.
Указатели функций
Точно так же, как у нас есть указатели на переменные, массивы и т. д., у нас также могут быть указатели на функции. Но разница в том, что указатель на функцию указывает на исполняемый код, а не на данные, такие как переменные или массивы.
Вот пример для демонстрации указателей на функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> #include <string> using namespace std; void displayVal(int a) { printf("Value of a is %d\n", a); } int main() { void (*func_ptr)(int) = &displayVal; (*func_ptr)(100); return 0; } |
Вывод данных:
1 |
Value of a is 100 |
В приведенной выше программе у нас есть функция displayVal, которая просто печатает переданное ей целочисленное значение. В основной функции мы определили указатель функции func_ptr, который принимает целое число в качестве аргумента и возвращает тип void.
void (*func_ptr)(int)
Нам нужно заключить указатель на функцию внутри (). Если мы его опустим, он станет прототипом функции.
Мы присвоили этому указателю на функцию адрес функции displayVal. Затем, используя этот указатель функции func_ptr, мы передаем значение аргумента 100, что эквивалентно вызову displayVal с аргументом 100.
Теперь, если у нас есть другая функция с тем же прототипом, то мы можем использовать тот же указатель на функцию, присвоив ему адрес функции. Это основное использование указателей на функции.
Вывод
Это все, что вам необходимо знать об указателях, их определениях и использовании в C++.
В нашей следующей статье вы узнаете о ссылках в C++. Ссылки имеют специальное применение в C++ и часто используются в качестве псевдонимов для переменных.
С Уважением, МониторБанк