Параметры функции в C++ могут представлять указатели. Указатели передаются в функцию по значению, то есть функция получает копию указателя. В то же время копия указателя будет в качестве значения иметь тот же адрес, что оригинальный указатель. Поэтому используя в качестве параметров указатели, мы можем получить доступ к значению аргумента и изменить его.
Например, пусть у нас будет простейшая функция, которая увеличивает число на единицу:
#include <iostream> void increment(int); int main() { int n {10}; increment(n); std::cout << "main function: " << n << std::endl; } void increment(int x) { x++; std::cout << "increment function: " << x << std::endl; }
Здесь переменная n передается в качестве аргумента для параметра x. Передача происходит по значению, поэтому любое изменение параметра x в функции increment никак не скажется на значении переменной n. Что мы можем увидеть, запустим программу:
increment function: 11 main function: 10
Теперь изменим функцию increment, использовав в качестве параметра указатель:
#include <iostream> void increment(int*); int main() { int n {10}; increment(&n); std::cout << "main function: " << n << std::endl; } void increment(int *x) { (*x)++; // получаем значение по адресу в x и увеличиваем его на 1 std::cout << "increment function: " << *x << std::endl; }
Для изменения значения параметра применяется операция разыменования с последующим инкрементом: (*x)++
. Это изменяет значение, которое находится по адресу, хранимому в указателе x.
Поскольку теперь функция в качестве параметра принимает указатель, то при ее вызове необходимо передать адрес переменной: increment(&n);
.
В итоге изменение параметра x также повлияет на переменную n, потому что оба они хранят адрес на один и тот же участок памяти:
increment function: 11 main function: 11
В то же время поскольку аргумент передается в функцию по значению, то есть функция получает копию адреса, то если внутри функции будет изменен адрес указателя, то это не затронет внешний указатель, который передается в качестве аргумента:
#include <iostream> void increment(int*); int main() { int n {10}; int *ptr {&n}; increment(ptr); std::cout << "main function: " << *ptr << std::endl; } void increment(int *x) { int z {6}; x = &z; // переустанавливаем адрес указателя x std::cout << "increment function: " << *x << std::endl; }
В функцию increment передается указатель ptr, который хранит адрес переменной n. При вызове функция increment получает копию этого указателя через параметр x. В функции изменяется адрес указателя x на адрес переменной z. Но это никак не затронет указатель ptr, так как он предствляет другую копию. В итоге поле переустановки адреса указатели x и ptr будут хранить разные адреса.
Результат работы программы:
increment function: 6 main function: 10
Параметры, которые преставляют указатели, могут быть константными.
#include <iostream> void print(const int*); // константный параметр int main() { int n {10}; print(&n); } void print(const int *x) { std::cout << *x << std::endl; }
По константному параметру мы не можем изменить значение. То есть фактически такие параметры представляют указатели на константу. Поэтому константные параметры полезны, когда необходимо передать в функцию адрес константы - в этом случае параметр обязательно должен быть константным:
#include <iostream> void print(const int*); // константный параметр int main() { const int n {10}; print(&n); // передаем адрес константы } void print(const int *x) { std::cout << *x << std::endl; }
При этом константность параметра не означает, что мы не можем изменить адрес, хранимый в указателе, например, следующим образом:
void print(const int *x) { int z{2}; x = &z; // меняем адрес в указателе на адрес переменной z std::cout << *x << std::endl; // 2 }
Чтобы гарантировать, что не только значение по указателю не будет меняться, но и само значение указателя (хранимый в нем адрес) не будет меняться, надо определить указатель как константный:
#include <iostream> void print(const int*); // константный параметр int main() { const int n {10}; print(&n); } void print(const int* const x) // константный указатель на константу { int z{2}; //x = &z; // значение указателя нельзя изменить std::cout << "z = " << z << std::endl; // z = 2 std::cout << "*x = " << *x << std::endl; // *x = 10 }
Параметры, передаваемые по ссылке, и параметры-указатели похожи в том плане, что оба эти вида параметров позволяют менять значения передаваемых в них переменных.
Единственной отличительной особенностью указателя является то, что он может иметь значение nullptr
, в то время как ссылка всегда должна ссылаться на что-то.
Поэтому, если необходимо, что параметр не имел никакого значения, то можно использовать указатели. Единственное, что в этом случае необходимо проверять указатель на
значение nullptr
перед его использованием.