Указатели на функции
С функцией можно выполнить только две операции: вызывать её и получить её адрес. Адрес функции может быть использован для вызова функции. Например:
void error(int i); //прототип функции error
void (*p)(int); //прототип функции p
p=&error; //получение адреса функции
(*p)(1); //вызов функции
Разыменование указателя при вызове не обязательно. Также не обязательно пользоваться & для получения адреса функции.
Предыдущий пример может быть записан так:
void error(int i);
void (*p)(int);
p=error; //получение адреса функции
p(1); //вызов функции
Указатели и структуры
В случае, если указатель используется для хранения адреса объекта типа структуры, до доступ к полям структуры может быть осуществлён двумя способами (строки 4 и 5):
struct vec2
{ double x, y; }; //1
vec2 dir; //2
vec2 *p_dir; //3
(*p_dir).x=0; //4
p_dir->x=0; //5
В строке 4 используется полная форма записи: разыменование указателя и обращение к полю x структуры dir. В строке используется сокращённая форма записи с использованием оператора ->, который обеспечивает прямое обращение к полю структуры указываемого объекта. Таким образом, строки 4 и 5 дают один и тот же результат.
Использование указателей
Указатели позволяют максимально эффективно решить проблему возврата нескольких значений из функции, а также возврата больших объектов, копирование которых нежелательно.
В случае, если требуется вернуть несколько значений, можно воспользоваться следующим синтаксисом:
void GetMouseState(int *p_keys, int *p_x, int *p_y) {
*p_keys = …;
*p_x = …;
*p_y = …;
}
Поскольку функция GetMouseState получает копии адресов объектов, то при изменении их через указатели меняются сами объекты, а не их копии. Обратите внимание – копируются указатели, а не сами объекты! Формально здесь происходит то же самое, что и в случае со ссылками.
Вызов этой функции будет выглядеть следующим образом:
int keys,x,y;
GetMouseState(&keys,&x,&y);
В данном случае, в отличие от передачи аргумента по ссылке, клиентский код явно указывает, что он передаёт адрес объекта. Передавая адрес клиент подразумевает, что, скорее всего, адрес будет использован для изменения значения. Исходя из этого, рекомендуется для обеспечения работы с самим объектом, а не с его копией, использовать константные ссылки, а для ситуации, когда вызываемая функция планирует изменять значение – использовать указатели. Во втором случае в клиентском коде явно видно, что берётся адрес объекта и следует ожидать его изменения.
Альтернативный вариант с использованием структуры в качестве возвращаемого значения:
struct MouseState
{
int keys;
int x,y;
};
MouseState GetMouseState()
{
MouseState mouse_state;
mouse_state.keys = …;
mouse_state.x = …;
mouse_state.y = …;
return mouse_state;
}
не всегда удобен, и в общем случае требует копирования объекта структуры при возврате значения, что может быть неэффективно, а в ряде случаев и невозможно.
Но даже в этом случае можно использовать комбинированный приём:
struct MouseState
{
int keys;
int x,y;
};
void GetMouseState(MouseState *p_mouse_state)
{
p_mouse_state->keys = …;
p_mouse_state->x = …;
p_mouse_state->y = …;
}
В этом случае в функцию передаётся указатель на структуру, которая может быть заполнена в функции:
void DisplayCursorInfo()
{
MouseState mouse_state;
GetMouseState(&mouse_state);
cout << “Mouse cursor x=” << cursor.x << endl;
cout << “Mouse cursor y=” << cursor.y << endl;
}
В данном случае объект структуры создаётся в клиентском коде и не требуется его копирование при возврате функции в противоположность возврату значения структуры.
Принципиальное отличие указателя от ссылки заключается в том, что указатель может иметь нулевое значение, то есть не указывать ни на какой объект. В то время как ссылка (при корректном использовании) всегда будет ссылаться на реальный объект.
Хорошим стилем является инициализация указателя, не указывающего ни на какой объект нулевым значением. Даже если это значение не планируется использовать реально – т.е. указатель планируется установить на требуемый объект, а затем использовать. В случае, если программистом будет допущена ошибка и указатель будет использован до того, как в него будет помещен адрес реального объекта, значительно проще будет обнаружить ошибку при обращении по нулевому адресу, чем по случайному адресу в памяти.
