Параметры функции в 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 перед его использованием.