Тема 2 — Модульное программирование

Тема 2. Модульное программирование.

Модульное программирование.

Функции.

Директивы препроцессора.

Области действия идентификаторов.

Модульное программирование.

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

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

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

Модуль содержит данные и функции их обработки. Другим модулям нежелательно иметь собственные средства обработки этих данных, они должны пользоваться для этого функциями первого модуля. Для того чтобы использовать модуль, нужно знать только его интерфейс, а не все детали его реализации. Чем более независимы модули, тем легче отлаживать программу. Это уменьшает общий объем информации, которую необходимо одновременно помнить при отладке. Разделение программы на максимально обособленные части является сложной задачей, которая должна решаться на этапе проектирования программы.

Скрытие деталей реализации называется инкапсуляцией. Инкапсуляция является ключевой идеей как структурного, так и объектно-ориентированного программирования. Пример инкапсуляции – помещение фрагмента кода в функцию и передача всех необходимых ей данных в качестве параметров. Чтобы использовать такую функцию, требуется знать только ее интерфейс, определяемый заголовком (имя, тип возвращаемого значения и типы параметров). Интерфейсом модуля являются заголовки всех функций и описания доступных извне типов, переменных и констант. Описания глобальных программных объектов во всех модулях программы должны быть согласованы.

Модульность в языке C++ поддерживается с помощью директив препроцессора, пространств имен, классов памяти, исключений и раздельной компиляции.

Функции.

Функция – это именованная последовательность описаний и операторов, выполняющая какое-либо законченное действие. Функция может принимать параметры и возвращать значение.

Любая программа на C++ состоит из функций, одна из которых должна иметь имя main (с нее начинается выполнение программы). Функция начинает выполняться в момент вызова. Любая функция должна быть объявлена и определена. Как и для других величин, объявлений может быть несколько, а определение только одно. Объявление функции должно находиться в тексте раньше ее вызова для того, чтобы компилятор мог осуществить проверку правильности вызова.

Объявление функции (прототип, заголовок, сигнатура) задает ее имя, тип возвращаемого значения и список передаваемых параметров. Определение функции содержит, кроме объявления, тело функции, представляющее собой последовательность операторов и описаний в фигурных скобках:

[класс] тип имя ([список_параметров])[throw (исключения)]

{тело функции}

Рассмотрим составные части определения.

С помощью необязательного модификатора класс можно явно задать область видимости функции, используя ключевые слова extern и static: extern – глобальная видимость во всех модулях программы (по умолчанию); static – видимость только в пределах модуля, в котором определена функция.

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

Список параметров определяет величины, которые требуется передать в функцию при ее вызове. Элементы списка параметров разделяются запятыми. Для каждого параметра, передаваемого в функцию, указывается его тип и имя (в объявлении имена можно опускать).

В определении, в объявлении и при вызове одной и той же функции типы и порядок следования параметров должны совпадать. На имена параметров ограничений по соответствию не накладывается, поскольку функцию можно вызывать с различными аргументами, а в прототипах имена компилятором игнорируются (они служат только для улучшения читаемости программы).

Функцию можно определить как встроенную с помощью модификатора inline, который рекомендует компилятору вместо обращения к функции помещать ее код непосредственно в каждую точку вызова. Модификатор inline ставится перед типом функции. Он применяется для коротких функций, чтобы снизить накладные расходы на вызов (сохранение и восстановление регистров, передача управления). Директива inline носит рекомендательный характер и выполняется компилятором по мере возможности. Использование inline-функций может увеличить объем исполняемой программы. Определение функции должно предшествовать ее вызовам, иначе вместо inline-расширения компилятор сгенерирует обычный вызов.

Тип возвращаемого значения и типы параметров совместно определяют тип функции.

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

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

При выходе из функции соответствующий участок стека освобождается, поэтому значения локальных переменных между вызовами одной и той же функции не сохраняются. Если этого требуется избежать, при объявлении локальных переменных используется модификатор static.

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

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

Механизм возврата из функции в вызвавшую ее функцию реализуется оператором

return [выражение];

Функция может содержать несколько операторов return. Если функция описана как void, выражение не указывается. Оператор return можно опускать для функции типа void, если возврат из нее происходит перед закрывающей фигурной скобкой, и для функции main.

Выражение, указанное после return, неявно преобразуется к типу возвращаемого функцией значения и передается в точку вызова функции.

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

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

Существует два способа передачи параметров в функцию: по значению и по адресу.

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

При передаче по адресу в стек заносятся копии адресов аргументов, а функция осуществляет доступ к ячейкам памяти по этим адресам и может изменить исходные значения аргументов:

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

Если требуется запретить изменение параметра внутри функции, используется модификатор const.

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

По умолчанию параметры любого типа, кроме массива и функции (например, вещественного, структурного, перечисление, объединение, указатель), передаются в функцию по значению.

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

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

Функцию можно вызвать через указатель на нее. Для этого объявляется указатель соответствующего типа и ему с помощью операции взятия адреса присваивается адрес функции:

void f (int а) { / * . . . * / } // определение функции

void (*pf)(int); // указатель на функцию

pf = &f; // указателю присваивается адрес функции

// (можно написать pf = f ;)

pf(10); // функция f вызывается через указатель pf

// (можно написать (*pf)(10) )

Для того чтобы сделать программу легко читаемой, при описании указателей на функции используют переименование типов (typedef). Можно объявлять массивы указателей на функции (например, при реализации меню):

// Описание типа PF как указателя

// на функцию с одним параметром типа int;

typedef void (*PF)(int);

// Описание и инициализация массива указателей:

PF menu[] = {&new, &open, &save};

menu[l](10); // Вызов функции open

Здесь new, open и save – имена функций, которые должны быть объявлены ранее.

Указатели на функции передаются в подпрограмму таким же образом, как и параметры других типов. Тип указателя и тип функции, которая вызывается посредством этого указателя, должны совпадать в точности.

Чтобы упростить вызов функции, в ее заголовке можно указать значения параметров по умолчанию. Эти параметры должны быть последними в списке и могут опускаться при вызове функции. Если при вызове параметр опущен, должны быть опущены и все параметры, стоящие за ним. В качестве значений параметров по умолчанию могут использоваться константы, глобальные переменные и выражения.

Если список формальных параметров функции заканчивается многоточием, это означает, что при ее вызове на этом месте можно указать еще несколько параметров. Проверка соответствия типов для этих параметров не выполняется, char и short передаются как int, а float – как double. Для доступа к необязательным параметрам внутри функции используются макросы библиотеки va_start. va_arg и va_end, находящиеся в заголовочном файле .

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

Рекурсивные функции чаще всего применяют для компактной реализации рекурсивных алгоритмов, а также для работы со структурами данных, описанными рекурсивно, например, с двоичными деревьями. Любую рекурсивную функцию можно реализовать без применения рекурсии, для этого программист должен обеспечить хранение всех необходимых данных самостоятельно. Достоинством рекурсии является компактная запись, а недостатками – расход времени и памяти на повторные вызовы функции и передачу ей копий параметров, и, главное, опасность переполнения стека.

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

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

Если точного соответствия не найдено, выполняются продвижения порядковых типов в соответствии с общими правилами, например, bool и char в int, float в double и т.д. Далее выполняются стандартные преобразования типов, например, int в double или указателей в void*. Следующим шагом является выполнение преобразований типа, заданных пользователем, а также поиск соответствий за счет переменного числа аргументов функций. Если соответствие на одном и том же этапе может быть получено более чем одним способом, вызов считается неоднозначным и выдается сообщение об ошибке.

Неоднозначность может появиться при:

преобразовании типа;

использовании параметров-ссылок;

использовании аргументов по умолчанию.

Правила описания перегруженных функций.

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

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

Функции не могут быть перегружены, если описание их параметров отличается только модификатором const или использованием ссылки.

Многие алгоритмы не зависят от типов данных, с которыми они работают. Естественно желание параметризовать алгоритм таким образом, чтобы его можно было использовать для различных типов данных. Первое, что может прийти в голову – передать информацию о типе в качестве параметра. Использование дополнительного параметра означает генерацию дополнительного кода, что снижает эффективность программы, особенно при рекурсивных вызовах и вызовах во внутренних циклах; кроме того, отсутствует возможность контроля типов. Другим решением будет написание для работы с различными типами данных нескольких перегруженных функций, но в таком случае в программе будет несколько одинаковых по логике функций, и для каждого нового типа придется вводить новую.

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

Формат простейшей функции-шаблона:

template заголовок{

/* тело функции */

}

Вместо слова Туре может использоваться произвольное имя.

В общем случае шаблон функции может содержать несколько параметров, каждый из которых может быть не только типом, но и просто переменной, например:

template void f(){ … }

Первый же вызов функции, который использует конкретный тип данных, приводит к созданию компилятором кода для соответствующей версии функции. Этот процесс называется инстанцированием шаблона (instantiation). Конкретный тип для инстанцирования либо определяется компилятором автоматически, исходя из типов параметров при вызове функции, либо задается явным образом. При повторном вызове с тем же типом данных код заново не генерируется. На месте параметра шаблона, являющегося не типом, а переменной, должно указываться константное выражение.

Чтобы применить функцию-шаблон к типу данных, определенному пользователем (структуре или классу), требуется перегрузить операции для этого типа данных, используемые в функции.

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

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

// без параметров:

тип main(){ /* … */ }

// с двумя параметрами:

тип main(int argc, char* argv[]){ /* … */ }

При запуске программы параметры разделяются пробелами. Имена параметров в программе могут быть любыми, но принято использовать argc и argv. Первый параметр (argc) определяет количество параметров, передаваемых функции, включая имя самой программы, второй параметр (argv) является указателем на массив указателей типа char*. Каждый элемент массива содержит указатель на отдельный параметр командной строки, хранящийся в виде С-строки, оканчивающейся нуль-символом. Первый элемент массива (argv[0]) ссылается на полное имя запускаемого на выполнение файла, следующий (argv[l]) указывает на первый параметр, argv[2] — на второй параметр, и так далее. Параметр argv[argc] должен быть равен 0.

Если функция main() ничего не возвращает, вызвавшая система получит значение, означающее успешное завершение. Ненулевое значение означает аварийное завершение. Оператор возврата из main() можно опускать.

Функции стандартной библиотеки

Любая программа на C++ содержит обращения к стандартной библиотеке, в которой находятся определения типов, констант, макросов, функций и классов. Чтобы использовать их в программе, требуется с помощью директивы #inc1ude включить в исходный текст программы заголовочные файлы, в которых находятся соответствующие объявления. Сами библиотечные функции хранятся в скомпилированном виде и подключаются к программе на этапе компоновки. В программах на C++ могут использоваться функции, унаследованные от библиотеки С.

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

Функции ввода/вывода



Ввод/вывод в C++ реализуется либо с помощью функций, унаследованных от библиотеки С, либо с помощью потоков C++. Смешивать эти два способа в одной программе можно только синхронизировав ввод с помощью функции sync_with_stdio(). Каждый способ имеет свои преимущества. Преимущество использования потоков в том, что они легче в использовании в простых случаях ввода/вывода, не требующих форматирования, а, главное, потоковые операции можно переопределить для собственных классов. Ввод/вывод в стиле С удобнее использовать при форматированном выводе в программах, не использующих объектно-ориентированную технику.

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

Работа с потоком начинается с его открытия. Поток можно открыть для чтения и/или записи в двоичном или текстовом режиме. Функция открытия потока имеет формат:

FILE* fopen(const char* filename, const char* mode);

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

«r» – файл открывается для чтения;

«w» – открывается пустой файл для записи (если файл существует, он стирается);

«а» – файл открывается для добавления информации в его конец;

«r+» – файл открывается для чтения и записи (файл должен существовать);

«w+» – открывается пустой файл для чтения и записи (если файл существует, он стирается);

«а+» – файл открывается для чтения и добавления информации в его конец.

Режим открытия может также содержать символы t (текстовый режим) или b (двоичный режим), отличающиеся обработкой символов перехода на новую строку. По умолчанию файл открывается в текстовом режиме, при котором комбинация символов «возврат каретки» и «перевод строки» (0×13 0×10) при вводе преобразуются в одиночный символ перевода строки (при выводе выполняется обратное преобразование). В двоичном режиме эти преобразования не выполняются.

Пример.

FILE * f = fopen(«d:\\cpp\\clata», «rb+»);

Указатель f используется в дальнейших операциях с потоком. Его передают функциям ввода/вывода в качестве параметра.

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

Существует пять предопределенных потоков, которые открываются в начале работы программы: стандартный ввод stdin, стандартный вывод stdout, стандартный вывод сообщений об ошибках stderr, стандартный дополнительный поток stdaux и стандартная печать stdprn. Первые три потока по умолчанию относятся к консоли. Эти указатели можно использовать в любой функции ввода/вывода там, где требуется указатель потока.

Ввод/вывод в поток можно осуществлять различными способами: в виде последовательности байтов, в виде символов и строк или с использованием форматных преобразований. Для каждого вида операций определен свой набор функций.

Операции ввода/вывода выполняются, начиная с текущей позиции потока, определяемой положением указателя потока. Указатель устанавливается при открытии на начало или конец файла (в соответствии с режимом открытия) и изменяется автоматически после каждой операции, ввода/вывода. Текущее положение указателя можно получить с помощью функций ftell и fgetpos и задать явным образом с помощью функций fseek и fsetpos. Эти функции нельзя использовать для стандартных потоков. Основными функциями ввода/вывода потока являются:

Чтение и запись потока байтов выполняют функции fread и fwrite.

Чтение символа из потока – getc, fgetc, из стандартного потока stdin – getchar.

Запись символа в поток – putс, fputc, в стандартный поток stdout – putchar.

Чтение строки из потока – fgets, из стандартного потока stdin – gets.

Запись строки в поток – fputs, в стандартный поток stdout – puts.

Форматированный ввод из потока – fscant, из стандартного потока stdin – scant, из строки – sscanf.

Форматированный вывод в поток – fprintf, в стандартный поток stdout – printf, в строку – sprintf.

Поток закрывается либо при завершении программы, либо явным образом с помощью функции fclose:

int fclose(FILE*);

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

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

int feof (FILE*) возвращает не равное нулю значение, если достигнут конец файла, в противном случае 0;

int ferror (FILE*) возвращает не равное нулю значение, если обнаружена ошибка ввода/вывода, в противном случае 0.

Функции работы со строками и символами

Строка представляет собой массив символов, заканчивающийся нуль-символом. В С++ есть две возможности работы со строками: функции, унаследованные из библиотеки С (заголовочный файл или ), и библиотечный класс C++ string, предоставляющий более широкие возможности представления, обработки и контроля строк.

Библиотека С содержит функции копирования строк (strcpy, strncpy), сравнения (strcmp, strncmp), объединения строк (strcat. strncat), поиска подстроки (strstr), поиска вхождения символа (strchr, strrchr. strpbrk), определения длины строки (strlen) и другие.

В заголовочных файлах и содержатся функции преобразования строк в числа (обратные преобразования можно сделать с помощью функции sprintf):

double atof(const char* p) преобразует переданную строку в double;

int atoi (const char* p) преобразует переданную строку в int;

long atol (const char* p) преобразует переданную строку в long.

Пробелы и табуляции в начале строки пропускаются. Преобразование прекращается при встрече недопустимого символа или конца строки. Если строку нельзя преобразовать в число, возвращается 0. Если число выходит за пределы диапазона данного типа, переменной errno (заголовочный файл ) присваивается значение ERANGE и возвращается допустимое число.

Для работы с символами в стандартной библиотеке (заголовочные файлы и ) есть следующие функции:

Имя

Проверка на принадлежность символа множеству

isalnum

букв и цифр (A-Z, a-z, 0-9)

isalfa

букв (A-Z, a-z)

iscntrl

управляющих символов (с кодами 0..31 и 127)

isdigit

цифр (0-9)

isgraph

печатаемых символов, кроме пробела (isalfa | isdigit | ispunct)

islower

букв нижнего регистра (a-z)

isprint

печатаемых символов

ispunct

знаков пунктуации

isspace

символов-разделителей

isupper

букв верхнего регистра (A-Z)

isxdigit

шестнадцатеричных цифр (A-F, a-f, 0-9)

Функции принимают величину типа int и возвращают значение true, если условие выполняется. Рекомендуется пользоваться стандартными функциями, а не писать собственные циклы проверки, так как это снижает количество ошибок в программе.

В библиотеке есть также функции tolower и toupper, переводящие символ латинского алфавита соответственно в нижний и верхний регистр.

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

Математические функции



С++ унаследовал из С стандартные математические функции, описание которых находится в заголовочных файлах
(). Они позволяют получить абсолютное значение (abs, fabs), округленное число (ceil, floor), квадратный корень (sqrt), степень (pow), значения тригонометрических функций (sin, cos, tan, sinh, cosh, tanh, asin, acos, atan, atan2), экспоненту (exp), логарифм (log, log10), дробную и целую части числа (modf), остаток от деления (fmod) и другие.

Ошибки индицируются установкой errno из () в значение EDOM для ошибок, связанных с выходом из области определения, и ERANGE для ошибок выхода за пределы диапазона.

Директивы препроцессора.

Препроцессором называется первая фаза компилятора. Инструкции препроцессора называются директивами. Они должны начинаться с символа #, перед которым в строке могут находиться только пробельные символы.

Директива #include

Директива #include имя_файла> вставляет содержимое указанного файла в ту точку исходного файла, где она записана. Включаемый файл также может содержать директивы #include. Поиск файла, если не указан полный путь, ведется в стандартных каталогах включаемых файлов. Вместо угловых скобок могут использоваться кавычки (» «) – в этом случае поиск файла ведется в каталоге, содержащем исходный файл, а затем уже в стандартных каталогах.



Директива #include включает информацию об интерфейсе из заголовочных файлов.

Заголовочные файлы обычно имеют расширение .h и могут содержать:

определения типов, констант, встроенных функций, шаблонов, перечислений;

объявления функций, данных, имен, шаблонов;

пространства имен;

директивы препроцессора;

комментарии.

В заголовочном файле не должно быть определений функций и данных. При указании заголовочных файлов стандартной библиотеки расширение .h можно опускать. Это сделано для того, чтобы не ограничивать способы их хранения. Для каждого файла библиотеки С с именем имеется соответствующий файл библиотеки C++ , в котором те же средства описываются в пространстве имен std. Например, директива #1nclucle обеспечивает те же возможности, что и #1nclude , но при обращении к стандартным функциям требуется указывать имя пространства имен std.

Директива #define

Директива #define определяет подстановку в тексте программы. Она используется для определения:

символических констант:

#define имя текст_подстановки

макросов, которые выглядят как функции, но реализуются подстановкой их текста в текст программы:

#define имя( параметры ) текст_подстановки

символов, управляющих условной компиляцией. Они используются вместе с директивами #ifdef и #ifndef. Формат: #define имя

Имена рекомендуется записывать прописными буквами, чтобы зрительно отличать их от имен переменных и функций. Параметры макроса используются при макроподстановке, например, если в тексте программы используется вызов макроса у = MAX(sum1, sum2);. он будет заменен на у = ((sum1)>(sum2)?(sum1):(sum2));

Отсутствие круглых скобок может привести к неправильному порядку вычисления, поскольку препроцессор не оценивает вставляемый текст с точки зрения синтаксиса. Например, если к макросу #define sqr(x) (х*х) обратиться как sqr(y+l), в результате подстановки получится выражение (у+1*у+1).

Макросы и символические константы унаследованы из языка С, при написании программ на C++ их следует избегать. Вместо символических констант предпочтительнее использовать const или enum, а вместо макросов – встроенные функции или шаблоны.

Директивы условной компиляции

Директивы условной компиляции #if, #ifdef и #ifndef применяются для того, чтобы исключить компиляцию отдельных частей программы. Это бывает полезно при отладке или, например, при поддержке нескольких версий программы для различных платформ.

Формат директивы #if:

#if константное_выражение

[ #elif константное_выражение

]

[ #e1if константное_выражение



Страницы: 1 | 2 | Весь текст