|
Программирование >> Поддержка объектно-ориентированного программирования
... В такую конструкцию можно включить весь заголовочный файл С, чтобы указать, что он подчиняется связыванию для С++, например: extern C { #include <string.h> Обычно с помощью такого приема из стандартного заголовочного файла для С получают такой файл для С++. Возможно иное решение с помощью условной трансляции: #ifdef cplusplus extern C { #endif char* strcpy(char*, const char*); int strcmp(const char*, const char*); int strlen(const char*); ... #ifdef cplusplus #endif Предопределенное макроопределение cplusplus нужно, чтобы обойти конструкцию extern C { ...}, если заголовочный файл используется для С. Поскольку конструкция extern C { ... } влияет только на порядок связывания, в ней может содержаться любое описание, например: extern C { произвольные описания например: static int st; int glob; Никак не меняется класс памяти и область видимости описываемых объектов, поэтому по-прежнему st подчиняется внутреннему связыванию, а glob остается глобальной переменной. Укажем еще раз, что описание extern C влияет только на порядок связывания и не влияет на порядок вызова функции. В частности, функция, описанная как extern C , все равно подчиняется правилам контроля типов и преобразования фактических параметров, которые в C++ строже, чем в С. Например: extern C int f(); int g() return f(1); ошибка: параметров быть не должно 4.5 Как создать библиотеку Распространены такие обороты (и в этой книге тоже): поместить в библиотеку , поискать в такой-то библиотеке . Что они означают для программ на С++? К сожалению, ответ зависит от используемой системы. В этом разделе говорится о том, как создать и использовать библиотеку для десятой версии системы ЮНИКС. Другие системы должны предоставлять похожие возможности. Библиотека состоит из файлов .o, которые получаются в результате трансляции файлов .c. Обычно существует один или несколько файлов .h, в которых содержатся необходимые для вызова файлов .o описания. Рассмотрим в качестве примера, как для четко не оговоренного множества пользователей можно достаточно удобно определить некоторое множество стандартных математических функций. Заголовочный файл может иметь такой вид: extern C { стандартные математические функции как правило написаны на С double sqrt(double); подмножество <math.h> double sin(double); double cos(double); double exp(double); double log(double); ... Определения этих функций будут находиться в файлах sqrt.c, sin.c, cos.c, exp.c и log.c, соответственно. Библиотеку с именем math.a можно создать с помощью таких команд: $ CC -c sqrt.c sin.c cos.c exp.c log.c $ ar cr math.a sqrt.o sin.o cos.o exp.o log.o $ ranlib math.a Здесь символ $ является приглашением системы. Вначале транслируются исходные тексты, и получаются модули с теми же именами. Команда ar (архиватор) создает архив под именем math.a. Наконец, для быстрого доступа к функциям архив индексируется. Если в вашей системе нет команды ranlib (возможно она и не нужна), то, по крайней мере, можно найти в справочном руководстве ссылку на имя ar. Чтобы использовать библиотеку в своей программе, надо задать режим трансляции следующим образом: $ CC myprog.c math.a Встает вопрос: что дает нам библиотека math.a? Ведь можно было бы непосредственно использовать файлы .o, например так: $ CC myprog.c sqrt.o sin.o cos.o exp.o log.o Дело в том, что во многих случаях трудно правильно указать, какие файлы .o действительно нужны. В приведенной выше команде использовались все из них. Если же в myprog вызываются только sqrt() и cos(), тогда, видимо, достаточно задать такую команду: $ CC myprog.c sqrt.o cos.o Но это будет неверно, т.к. функция cos() вызывает sin(). Редактор связей, который вызывается командой CC для обработки файлов .a (в нашем случае для файла math.a), умеет из множества файлов, образующих библиотеку, извлекать только нужные файлы .o. Иными словами, связывание с библиотекой позволяет включать в программы много определений одного имени (в том числе определения функций и переменных, используемых только внутренними функциями, о которых пользователь никогда не узнает). В то же время в результирующую программу войдет только минимально необходимое число определений. 4.6 Функции Самый распространенный способ задания в С++ каких-то действий - это вызов функции, которая выполняет такие действия. Определение функции есть описание того, как их выполнить. Неописанные функции вызывать нельзя. 4.6.1 Описания функций Описание функции содержит ее имя, тип возвращаемого значения (если оно есть) и число и типы параметров, которые должны задаваться при вызове функции. Например: extern double sqrt(double); extern elem* next elem(); extern char* strcpy(char* to, const char* from); extern void exit(int); Семантика передачи параметров тождественна семантике инициализации: проверяются типы фактических параметров и, если нужно, происходят неявные преобразования типов. Так, если учесть приведенные описания, то в следующем определении: double sr2 = sqrt(2); содержится правильный вызов функции sqrt() со значением с плавающей точкой 2.0. Контроль и преобразование типа фактического параметра имеет в С++ огромное значение. В описании функции можно указывать имена параметров. Это облегчает чтение программы, но транслятор эти имена просто игнорирует. 4.6.2 Определения функций Каждая вызываемая в программе функция должна быть где-то в ней определена, причем только один раз. Определение функции - это ее описание, в котором содержится тело функции. Например: extern void swap(int*, int*); описание void swap(int* p, int* q) определение int t = *p; *p = *q; *q = *t; Не так редки случаи, когда в определении функции не используются некоторые параметры: void search(table* t, const char* key, const char*) третий параметр не используется ... Как видно из этого примера, параметр не используется, если не задано его имя. Подобные функции появляются при упрощении программы или если рассчитывают на ее дальнейшее расширение. В обоих случаях резервирование места в определении функции для неиспользуемого параметра гарантирует, что другие функции, содержащие вызов данной, не придется менять. Уже говорилось, что функцию можно определить как подстановку (inline). Например: inline fac(int i) { return i<2 ? 1 : n*fac(n-1); } Спецификация inline служит подсказкой транслятору, что вызов функции fac можно реализовать подстановкой ее тела, а не с помощью обычного механизма вызова функций ($$R.7.1.2). Хороший оптимизирующий транслятор вместо генерации вызова fac(6) может просто использовать константу 720. Из-за наличия взаиморекурсивных вызовов функций-подстановок, а также функций-подстановок, рекурсивность которых зависит от входных данных, нельзя утверждать, что каждый вызов функции-подстановки действительно реализуется подстановкой ее тела. Степень оптимизации, проводимой транслятором, нельзя формализовать, поэтому одни трансляторы создадут команды 6*5*4*3*2*1, другие - 6*fac(5), а некоторые ограничатся неоптимизированным вызовом fac(6). Чтобы реализация вызова подстановкой стала возможна даже для не слишком развитых систем программирования, нужно, чтобы не только определение, но и описание функции-подстановки находилось в текущей области видимости. В остальном спецификация inline не влияет на семантику вызова. 4.6.3 Передача параметров При вызове функции выделяется память для ее формальных параметров, и каждый формальный параметр инициализируется значением соответствующего фактического параметра. Семантика передачи параметров тождественна семантике инициализации. В частности, сверяются типы формального и соответствующего ему фактического параметра, и выполняются все стандартные и пользовательские преобразования типа. Существуют специальные правила передачи массивов ($$4.6.5). Есть возможность передать параметр, минуя контроль типа ($$4.6.8), и возможность задать
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |