задает указатель с именем fun на функцию, возвращающую значение типа int и имеющую два аргумента типа double.
void f (int x) { ... }
void (*pf)(int);
// Указатель на функцию. Скобки обязательны!
void g(){
pf = &f;
// pf указывает на функцию f
pf(0);
// Вызов функции f через указатель pf
}
Компилятор распознаёт, что pf является указателем и вызывает функцию, на которую он указывает. То есть, разыменование указателя на функцию при помощи операции * необязательно. Аналогично, необязательно пользоваться операцией & для получения адреса функции.
Параметры указателей на функцию объявляются точно так же, как и параметры самих функций. При присваивании типы функций должны в точности совпадать.
typedef void (*PF)(int);
// Для объявления типа «указатель на функцию» можно использовать объявление typedef
PF pf;
// Объявляем сам указатель на функцию, используя предварительно определённый тип
Правила передачи параметров при вызове функций через указатель те же самые, что и при непосредственном вызове функций.
С указателями можно выполнять следующие операции: разадресация (*), присваивание, сложение с константой, вычитание, инкремент (++), декремент (––), сравнение, приведение типов. При работе с указателями часто используется операция получения адреса (&).
Существуют две операции, которые имеют отношение к работе с указателями. Этими операциями являются:
· операция взятия адреса (адресация) &;
· операция взятия значения по адресу (косвенная адресация или разыменование) *.
int a, *p;
p = &a;
// Переменной p присваивается адрес переменной a
*p = 0;
// Значение по адресу, находящемуся в переменной p (т.е. значение переменной а), становится равным 0
Ссылка является альтернативным именем объекта. Ссылка – это объект, который синтаксически выглядит как переменная, а по семантике является адресом. Объявление ссылки, кроме случаев, когда ссылка является параметром функции, возвращаемым функцией значением или членом класса, должно содержать инициализатор. Далее все операции производятся не над самой ссылкой, а над тем объектом, на который она указывает.
int a = 10;
int &r = a;
// Объявляем и инициализируем ссылку
r++;
// Значение переменной а становится 11
void f(double &a) { a += 3.14; }
double d = 0;
f(d);
// d = 3.14
int v[20];
int& f(int i) { return v[i]; }
f(3) = 7;
// Элементу массива v[3] присваивается 7
На первый взгляд, ссылка является удобной заменой указателю, но она затрудняет понимание программы из-за несовпадения синтаксиса и семантики ссылки. Однако ссылки могут быть полезны для того, чтобы не передавать по значению (и не копировать) параметр функции, который имеет большой размер. В том случае, если мы не собираемся менять этот параметр внутри функции, можно объявить ссылку с модификатором const. В этом случае мы будем гарантированы, что параметр не изменится, вместо большого объекта будет передаваться его адрес, а для пользователя всё будет выглядеть как передача параметра по значению.
class X { ... };
// Описание большого класса
int f (const X& x) { ... }
Операция разадресации, или разыменования, предназначена для доступа к величине, адрес которой хранится в указателе. Эту операцию можно использовать как для получения, так и для изменения значения величины (если она не объявлена как константа):
char a; //переменная типа charchar * p = new char; //выделение памяти под указатель и под динамическую переменную типа char*p = 'Ю'; a = *p; //присваивание значения обеим переменным
На одну и ту же область памяти может ссылаться несколько указателей различного типа. Примененная к ним операция разадресации даст разные результаты. Например, программа
#include <stdio.h> int main() { unsigned long int A = 0Xсс77ffaa; unsigned int* pint = (unsigned int *) &A; unsigned char* pchar = (unsigned char *) &A; printf(" | %x | %x |", *pint, *pchar); return 0;}
на IBM PC выведет на экран строку:
| ffaa | aa |
В примере при инициализации указателей были использованы операции приведения типов. Синтаксис операции явного приведения типа прост: перед именем переменной в скобках указывается тип, к которому ее требуется преобразовать.
При смешивании в выражении указателей разных типов явное преобразование типов требуется для всех указателей, кроме void*. Указатель может неявно преобразовываться в значение типа bool.
Присваивание без явного приведения типов допускается только указателям типа void* или если тип указателей справа и слева от операции присваивания один и тот же.
Присваивание указателей данных указателям функций (и наоборот) недопустимо.
Арифметические операции с указателями (сложение, вычитание, инкремент и декремент) автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, последовательно размещенными в памяти, например, с массивами.
Инкремент перемещает указатель к следующему элементу массива, декремент — к предыдущему.
Фактически значение указателя изменяется на величину sizeof(тип).
Разность двух указателей — это разность их значений, деленная на размер типа в байтах. Суммирование двух указателей не допускается.
При записи выражений с указателями следует обращать внимание на приоритеты операций. В качестве примера рассмотрим последовательность действий, заданную в операторе
*p++ = 10;
То же самое можно записать подробнее:
*p = 10; p++;
Выражение (*p)++, напротив, инкрементирует значение, на которое ссылается указатель.
Унарная операция получения адреса & применима к величинам, имеющим имя и размещенным в оперативной памяти. Нельзя получить адрес скалярного выражения, неименованной константы или регистровой переменной.