Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЛЕКЦИЯ№3_С++(массивы&указатели).doc
Скачиваний:
64
Добавлен:
25.11.2019
Размер:
740 Кб
Скачать
☆

1.1.2. Указатель функции

С функцией можно проделать только две вещи: вызвать её и получить её адрес. Указатель, полученный взятием адреса функции, можно затем использовать для вызова функции.

Указатель функции содержит адрес в сегменте кода, по которому располагается исполняемый код функции.

Указатель функции имеет тип «указатель функции, возвращающей значение заданного типа и имеющей аргументы заданного типа».

Синтаксис объявления указателя функции:

<тип> (*<имя>) (<список_типов_аргументов>);

Пример 1.

int (*fun) (double, double);

задает указатель с именем fun на функцию, возвращающую значение типа int и имеющую два аргумента типа double.

Пример 2.

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;

// Объявляем сам указатель на функцию, используя предварительно определённый тип

void f1(int x) { ... }

int f2(int x) { ... }

void f3(char x) { ... }

void f (){

pf = &f1;

// Правильно

pf = &f2;

// Ошибка - не тот возвращаемый тип

pf = &f3;

// Ошибка - не тот тип параметра

}

Правила передачи параметров при вызове функций через указатель те же самые, что и при непосредственном вызове функций.

    1. Операции с указателями

С указателями можно выполнять следующие операции: разадресация (*), присваивание, сложение с константой, вычитание, инкремент (++), декремент (––), сравнение, приведение типов. При работе с указателями часто используется операция получения адреса (&).

Существуют две операции, которые имеют отношение к работе с указателями. Этими операциями являются:

  • операция взятия адреса (адресация) &;

  • операция взятия значения по адресу (косвенная адресация или разыменование) *.

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; //переменная типа char

char * 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)++, напротив, инкрементирует значение, на которое ссылается указатель.

Унарная операция получения адреса & применима к величинам, имеющим имя и размещенным в оперативной памяти. Нельзя получить адрес скалярного выражения, неименованной константы или регистровой переменной.

Адресная арифметика

Над указателями можно производить следующие действия:

  • складывать указатель и целое число;

  • вычитать из указателя целое число;

  • вычитать из указателя указатель – получается целое число.

При этом прибавляемое/вычитаемое или полученное целое число обозначает не количество байт, а количество элементов того типа, на который указывает указатель, т.е. это число умножается или делится на размер типа.

Вычитание указателей друг из друга определено только в том случае, если оба указателя указывают на элементы одного и того же массива (хотя язык не позволяет быстро проверить, так ли это). Результатом вычитания одного указателя из другого будет количество элементов массива (целое число) между этими указателями. В результате прибавления целого числа к указателю и вычитания целого числа из указателя получается новый указатель. Если полученный таким образом указатель не указывает на элемент того же массива (или на элемент, следующий за последним), что и исходный указатель, то результат его использования не определён.

Указатели можно использовать для обработки массивов.

int a[100], n, *end, *p;

end = a + n;

// n - количество элементов массива a. Имя массива является адресом его начала. // Таким образом, end - адрес элемента, находящегося после последнего элемента массива.

for (p = a; p < end; p++)

printf("%4d", *p);

Из того, что можно вычитать из указателя целое число, следует, что возможно использование отрицательных чисел в операции [ ].

int a[N]; int *endA = a + N - 1, i; for (i = 0; i < N; i++) printf("%4d", endA[-i]);

Ссылки

Ссылка представляет собой синоним имени, указанного при инициализации ссылки. Ссылку можно рассматривать как указатель, который всегда разыменовывается. Формат объявления ссылки:

тип &имя;

где тип — это тип величины, на которую указывает ссылка, & — оператор ссылки, означающий, что следующее за ним имя является именем переменной ссылочного типа, например:

int kol;

int& pal = kol; // ссылка pal — альтернативное имя для kol

const char& CR = '\n'; // ссылка на константу

  • Переменная-ссылка должна явно инициализироваться при ее описании, кроме случаев, когда она является параметром функции, описана как extern или ссылается на поле данных класса.

  • После инициализации ссылке не может быть присвоена другая переменная.

  • Тип ссылки должен совпадать с типом величины, на которую она ссылается.

  • Не разрешается определять указатели на ссылки, создавать массивы ссылок и ссылки на ссылки.

Ссылки применяются чаще всего в качестве параметров функций и типов возвращаемых функциями значений. Ссылки позволяют использовать в функциях переменные, передаваемые по адресу, без операции разадресации, что улучшает читаемость программы.

Операция над ссылкой приводит к изменению величины, на которую она ссылается.