ДОПОЛНЕНИЕ К ИЗУЧЕННОМУ МАТЕРИАЛУ
Препроцессор и его функции
Препроцессор – программа, используемая для обработки исходного текста программы на языке С до компиляции и выполняющая следующие действия: поиск и включение в программу нужных внешних файлов; изменение условий компиляции; определение значений констант и т. д.
Препроцессор “общается” с программой при помощи директив. Директивы препроцессора представляют собой инструкции, записанные в тексте программы и выполняемые до ее трансляции. Директивы препроцессора позволяют изменить текст программы, например, заменить некоторые лексемы в тексте, вставить текст из другого файла, запретить трансляцию части текста и т. п. Директивы препроцессора отмечаются специальным маркером#. Знак # должен быть первым символом в строке, содержащей директиву. Между знаком # и первой буквой директивы могут находиться пробелы. После директив препроцессора точка с запятой не ставится.
Директивы препроцессора могут встречаться в любом месте программы, но обычно их стараются помещать в начале для удобства восприятия текста программы. Директивы, появляющиеся в любом месте исходного файла, применимы только к тексту, идущему после этой директивы. Написание директивы препроцессора завершается нажатием клавиши Enter. Для написания директивы в две строки в месте переноса следует нажать комбинацию клавиш [ \ ] + [ Enter ], и директива продолжается в следующей строке.
Обработанный препроцессором текст программы уже не содержит директив препроцессора, имеет гораздо больший объем по сравнению с исходным текстом. В таком виде программа передается для дальнейшей обработки компилятору.
Немного о прототипах функции
В современных программах на языке C каждую функцию перед использованием необходимо объявлять. Объявление функции называется прототипом функции. Например, для функцииMyFunction(), приведенной ранее, прототип будет иметь следующий вид:
void MyFunction(void);
или
void MyFunction();
Хотя прототипы в языке C формально не требуются, их применение очень желательно. Прототипы дают компилятору возможность тщательно выполнить проверку типов аргументов, правильность их преобразования, обнаружить несоответствия в количестве аргументов, использованных при вызове функции, и в количестве параметров функции. В языке C нет требования, чтобы определение функции обязательно предшествовало вызову функции. Определение может находиться где угодно, например, за определением функции main() или в другом файле. Обязательным условием является объявление функции до ее вызова. Это необходимо для проверки соответствия типов фактических аргументов типам формальным параметров, а также типа возвращаемого функцией значения. Объявление функции называется прототипом функции.
В прототипе функции (в C++ прототип обязателен) должны присутствовать:
- имя функции;
- типы и число формальных параметров;
- тип возвращаемого значения.
Формат прототипа тот же, что и у определения функции, но прототип не имеет:
- тела функции;
- заголовок оканчивается точкой с запятой.
Формат прототипа:
<класс памяти> <тип>имя (список формальных параметров);
О функциях
Функция не может возвращать массив, но может возвращать указатель на любой тип, в том числе и на массив. Тип возвращаемого значения, задаваемый в определении функции, должен соответствовать типу в объявлении этой функции.
Имя функции строится по стандартным правилам записи идентификаторов.
Список формальных параметров - это заключенная в круглые скобки последовательность объявлений формальных параметров, разделенных запятыми.
Формальные параметры - это переменные, используемые внутри тела функции и получающие значения при вызове функции путем копирования в них значений соответствующих фактических параметров.
Список формальных параметров может заканчиваться многоточием (…) – это означает, что число аргументов функции переменное. Однако предполагается, что функция имеет, по крайней мере, столько обязательных аргументов, сколько формальных параметров задано перед последней запятой в списке параметров.
Такой функции может быть передано большее число аргументов, но над дополнительными аргументами не проводится контроль типов, например,
void fn(unsigned c,float b,int a,…); т. е. число аргументов функции fn³3.
Если функция не имеет параметров, то наличие круглых скобок обязательно, а вместо списка параметров рекомендуется указать слово void.
Порядок и типы формальных параметров должны быть одинаковыми в определении функции и во всех ее объявлениях. Тип формального параметра может быть любым основным типом, структурой, объединением, перечислением, указателем или массивом. Типы фактических аргументов при вызове функции должны быть совместимы с типами соответствующих формальных параметров. Фактические аргументы функции при ее вызове помещаются в специальную область памяти программы, которая называется стек. Память в стеке выделяется в соответствии с типами формальных параметров. Несоответствие типов фактических аргументов и формальных параметров может быть причиной ошибки.
Параметры функции бывают двух видов: параметры-значения и параметры-ссылки. Параметры-значения функции могут рассматриваться как локальные переменные, для которых выделяется память в стеке при вызове функции и производится копирование значений фактических аргументов. При завершении работы функции происходит очистка стека от данных, принадлежащих этой функции, при этом значения переменных теряются. Такой способ передачи параметров в функцию не изменяет значения переменных в вызывающей функции, являющихся фактическими параметрами.
Пример передачи в функцию параметров-значений b и c:
int fn1(int b,float c);
Второй способ передачи параметров представляет собой передачу в функцию адреса фактического аргумента. Обращение к фактическому аргументу по адресу позволяет вызванной функции изменить его значение в вызвавшей эту функцию подпрограмме.
В качестве такого параметра можно использовать указатель на некоторую переменную:
int fn2(int* pb);
Используя операцию косвенной адресации в теле функции, можно изменить значение этой переменной: *pb=6.
Другой способ передачи в функцию адреса переменной – передача параметра по ссылке, при этом в вызванной функции создается псевдоним исходной переменной, форма обращения к такому параметру-ссылке такая же, как и к обычной переменной, а сам параметр передается с помощью адреса, например, int fn3(int &ab);при этом обращение к переменной ab в теле функции fn3() такое же, как к обычной переменной типа int.
В функцию не могут быть переданы массив и имя функции, но можно использовать указатели на эти объекты.
О функции main()
Иногда при запуске программы бывает полезно передать ей какую-либо информацию из внешнего окружения (операционной системы). Обычно такая информация передается функции main() с помощью аргументов (параметров) командной строки, содержащей команду запуска программы. Аргумент командной строки – это информация, которая вводится в командной строке операционной системы после имени программы.
Во внешнем окружении действуют свои правила представления данных, а точнее, все данные представляются в виде строк символов. Для передачи этих строк в функцию main() используются два специальных встроенных аргумента (параметра). Первый параметр содержит количество передаваемых строк, второй - сами строки. Общепринятые (но не обязательные) имена этих параметров argc и argv.
Параметр argc имеет тип int, его значение формируется из анализа командной строки и равно количеству слов в командной строке, включая и имя вызываемой программы (под словом понимается любой текст, не содержащий символа пробел). Численно этот параметр всегда больше или равен 1, потому что первым аргументом считается имя программы.
Параметр argv – это массив указателей на строки, каждая из которых содержит одно слово из командной строки. Если слово должно содержать символ пробел, то при записи его в командную строку оно должно быть заключено в кавычки. Обычно argc и argvиспользуют для того, чтобы передать программе начальные команды, которые понадобятся ей при запуске, например, имя файла, параметр и т. д.
Функция main() может иметь и третий параметр, который принято называть envp. Этот параметр служит для передачи в функцию main() параметров операционной системы (среды), в которой выполняется С-программа.
В итоге заголовок функции main() имеет вид:
int main(int argc, char *argv[], char *envp[]).
Операционная система поддерживает передачу значений для параметров argc, argv, envp, а на пользователе данного исполняемого файла лежит ответственность за передачу фактических аргументов функции main().
Оператор выбора switch
Оператор выбора switch (или переключатель) предназначен для выбора ветви вычислительного процесса исходя из значения управляющего выражения. Использование данного оператора целесообразно при сложном условном ветвлении.
Структура оператора следующая:
switch (выражение)
{
case константное выражение: оператор или группа операторов
break;
case константное выражение: оператор или группа операторов
break;
……………
default: оператор или группа операторов
}
Значение выражения оператора switch должно быть целочисленным. Это означает, что в выражении можно использовать переменные только целого или символьного типа. Результат вычисления выражения по очереди сравнивается с каждым из константных выражений. Если в какой-либо строке находится совпадение, управление передается на соответствующую метку case, и выполняется связанная с ней группа операторов. Выполнение продолжается до конца тела оператора switch или пока не встретится оператор break, который передает управление из тела switch оператору, следующему за закрывающей данную конструкцию фигурной скобкой.
Применение оператора break в контексте блока switch является обычным. Без него после выполнения варианта case, который соответствует значению управляющего выражения, оператор switchпродолжит свою работу, при этом будут выполнены все последующие варианты caseи ветка default.
Оператор, связанный с default, выполняется, если выражение не совпало ни с одним из константных выражений в case. Оператор defaultне обязательно располагается в конце конструкции. Кроме того, он и сам не обязателен. В этом случае при отсутствии совпадений не выполняется ни один оператор. Не допускается совпадение константных выражений.
Пример 1. По номеру дня недели, введенному с клавиатуры, вывести на экран название дня недели:
#include <stdio.h>
int main(void)
{
char day;
printf("Введите номер дня недели\n");
scanf("%c",&day);
switch (day)
{
case'1': printf("Понедельник\n");break;
case'2': printf("Вторник\n");break;
case'3': printf("Среда\n");break;
case'4': printf("Четверг\n");break;
case'5': printf("Пятница\n");break;
case'6': printf("Суббота\n");break;
case'7': printf("Воскресенье\n");break;
default: printf("Ошибка ввода\n");
}
return 0;}
В этом примере переменная day, управляющая оператором switch, имеет символьный тип. При вводе с клавиатуры символов 1, 2, … 7 на экран выводится название соответствующего дня недели. При вводе любого другого символа на экран выводится сообщение об ошибке.
КЛАССЫ ПАМЯТИ
В языке C все переменные и функции должны быть объявлены до их использования. Объявление переменной устанавливает соответствие имени и атрибутов переменной, вызывает выделение памяти для хранения ее значения. Место размещения переменной в памяти программы задается с помощью класса памяти (класса хранения) переменной. В зависимости от места размещения переменной в памяти определяется время жизни и область видимости переменной. Класс памяти задается спецификатором класса памяти.
В языке C имеется 4 класса памяти:
- extern (внешние переменные);
- auto (автоматические переменные);
- register (регистровые переменные);
- static (статические переменные);
Время жизни – это интервал времени выполнения программы, в течение которого программный объект (переменная или функция) существует, что определяется фактом выделения памяти. Время жизни переменной может быть локальным или глобальным. Переменная с глобальным временем жизни имеет выделенную память и определенное значение на протяжении всего времени выполнения программы, начиная с момента объявления этой переменной. Переменная с локальным временем жизни имеет выделенную память и определенное значение только во время выполнения блока, в котором эта переменная объявлена. При каждом входе в блок локальной переменной выделяется новая память, которая освобождается при выходе из блока.
Область видимости – это часть текста программы, в которой может быть использован данный объект. Объект считается видимым в блоке или в исходном файле, если в этом блоке или файле известны имя и тип объекта. Объект может быть видимым в пределах блока, текущего файла или во всех файлах, образующих программу.
Время жизни и область видимости объекта зависят от того, в каком месте объявлен объект. Если объект объявлен на внутреннем уровне, т. е. внутри некоторого блока, то по умолчанию он имеет локальное время жизни и локальную область видимости, т. е. он существует, пока выполняется блок, и видим в этом блоке и во всех внутренних блоках (локальная переменная). Если объект объявлен на внешнем уровне, т. е. вне всех блоков, он имеет глобальное время жизни и глобальную видимость. Такой объект существует до завершения программы и видим от точки его объявления до конца данного исходного файла (глобальная переменная).
Изменить время жизни переменной и область ее видимости можно, если указать для нее класс памяти. Все переменные, которые использовались до сих пор, были известны только содержащим их функциям. В C имеются возможности сделать ряд переменных известными сразу нескольким функциям и быстро обработать переменные путем помещения их значений в регистры и т. д. Эти и другие возможности реализуются при помощи присваивания переменным класса памяти.
Служебное слово, определяющее класс памяти, ставится в описаниях переменных перед служебным словом, задающим тип переменных:
static int i,j;
extern char ch1;
register int k;
Логическая структура памяти программы
Область памяти, в которой размещается программа, делится на разделы по назначению и способам управления данными:
· сегмент кода используется для хранения кода программы – функций. Функции помещаются в сегмент кода на этапе компиляции программы и находятся там до завершения работы программы, поэтому все функции в C имеют глобальное время жизни и существуют в течение всего времени выполнения программы;
· статическая память (сегмент данных) предназначена для хранения переменных в течение всего времени выполнения программы. Если для переменной в какой-либо момент работы программы выделена память в сегменте данных, она там будет находиться до завершения работы программы, даже если эта переменная больше не нужна. По умолчанию в сегменте данных хранятся глобальные переменные;
· стек – это область памяти, в которой хранятся локальные переменные и параметры функций. При вызове функции ее параметры и локальные переменные помещаются в стек. Стек функционирует по принципу стакана – значение, помещенное в стек первым, оказывается на дне стека, в то время как последнее значение – на вершине стека. По завершении работы функции все данные, принадлежащие этой функции, удаляются из стека. Очистка стека начинается с вершины, т. е. со значений, помещенных в стек последними;
· динамическая память (куча) позволяет программисту управлять процессом выделения памяти под переменные и освобождением памяти. Переменные, размещаемые в динамической памяти, называются динамическими переменными. О них речь пойдет позже, в динамическом распределении памяти.