Обзор
Указатели. Массивы. Строки
Механизм вызова функций, подставляемые функции
Рекурсия
Языки «С» и «С++» разрешают использование рекурсии, то есть вызова функцией самой себя.
Пример (функция вычисления факториала)
long fact(int k)
{
if (k<0) return 0;
if (k<=1) return 1;
return k*fact(k-1);
}
Каждая объявленная функция компилируется в свою область памяти, переход к выполнению соответствующего кода выполнятся по адресу размещения функции. Адрес функции доступен через её имя. Для вызова функции должна быть выполнена следующая комбинация действий:
— сохранения контекста выполнения функции, из которой происходит вызов: заключается, прежде всего, в сохранении некоторых регистров процессора;
— сохранение фактических параметров функции: как правило, производится в стек, но при небольшом их количестве может осуществляться передача через регистры; есть специальные директивы, управляющие режимом передачи аргументов;
— вызов функции по её адресу.
|
|
После выполнения тела функции необходимо провести обратный набор операций: сохранение результата, возврат в точку вызова, восстановление контекста.
Таким образом, вызов функции является достаточно ресурсозатратным процессом. Это в особенности справедливо, если тело функции не велико, а количество вызовов достаточно большое (например, вызов происходит в цикле). Казалось бы, в этом случае эффективнее не выделять в программе отдельные функции, но такое решение может не соответствовать принципам структурного программирования. Для такой ситуации введены подставляемые (inline) функции. Их тело компилируется непосредственно в точку их вызова, а не в отдельную область памяти. На такие функции накладывается ряд достаточно очевидных ограничений: в них недопустима рекурсия, нельзя объявить указатель на такую функцию. Большинство современных компиляторов автоматически выбирает режим (стандартный / подстановка) для функций.
inline int f(); //пример объявления подставляемой функции
Переменные связаны с адресом их размещения, но единственной доступной операцией управления этим адресом является получение его значения (операция «&»).
Существует набор задач, в которых необходимо иметь возможность манипулировать не только самими данными, но и адресом их размещения:
— реализация динамических (меняющих свою структуру во время выполнения программы) структур данных — такие структуры рассматриваются в третьей главе;
— передача в функцию не непосредственно объекта, а его адреса — используется, если объект должен модифицироваться в результате выполнения функции или если он достаточно большой, и создание его копии для передачи в функцию является ресурсозатратным процессом;
|
|
— доступ к сервисам операционной системы или оборудования, доступным по заданному адресу.
В этом случае используют указатель. Указатель на переменную хранит адрес её размещения. Обратиться к переменной можно через операцию разыменовывания указателя (*). Объявление указателя осуществляется символом «*» перед именем переменной. Пример:
int x;
int *t; // объявили указатель t на переменную типа int
t = &x; // присваиваем указателю адрес переменной x
*t = 4; // используя указатель, присваиваем x значение 4
Указатели и объекты одного типа могут быть объявлены вместе. Пример (другая реализация предыдущего примера):
int x,*t=&x;
Константа NULL предназначена для обозначения неинициализированных указателей.
Указатели на объект любого типа за исключением пустого называют типизированными. Возможно объявить указатель на пустой тип (void*). Такие указатели называют нетипизированными и используют, если важен адрес размещения, но не тип размещаемого данного.
Для обращения к полям структур, передаваемых по указателю, используется символ «->».
Пример:
struct S {int a; float b;};
int Func(S*s)
{
…
(*s).a = 3; // можно обратиться к полю так
s->b = 5.6 // а можно и так
…
return 0;
}
В языке «C» простые типы данных автоматически приводятся к требуемому. Вещественные преобразуются к целым путём отсечения дробной части. Данные большего размера приводятся к данным меньшего размера отбрасыванием старших байт. Такое приведение называется неявным.
Для производных и структурированных типов данных, в том числе для указателей, требуется явное преобразование типов. Операция преобразования типа имеет вид:
(новый_тип)переменная
Такое приведение называют явным.
При этом для объектов (экземпляров классов и структур) операция приведения должна быть определена. Для указателей допустимо приведение к другому указателю. Приведение указателей никак не преобразует сами данные. Указатель любого типа может быть приведен к нетипизированному указателю неявно.
Примеры приведены далее.