Б. Страуструп - Язык программирования С++ (1119446), страница 32
Текст из файла (страница 32)
Теперь этотфакт можно выразить явно:// table.h: описание таблицы именstruct name {char* string;name* next;double value;};extern name* look(const char* p, int ins = 0);inline name* insert(const char* s) { return look(s,1); }// table.h: определение таблицы имен#include "error.h"#include <string.h>#include "table.h"const int TBLSZ = 23;name* table[TBLSZ];name* look(const char* p, int ins) { /* ... */ }Заметьте, что теперь описания строковых функций берутся из включаемого файла <string.h>. Темсамым удален еще один источник ошибок.// lex.h: описания для ввода и лексического анализаenum token_value {NAME,NUMBER,END,PLUS='+',MINUS='-',MUL='*',PRINT=';', ASSIGN='=',LP='(',RP= ')'};extern token_value curr_tok;extern double number_value;extern char name_string[256];extern token_value get_token();Интерфейс с лексическим анализатором достаточно запутанный.
Поскольку недостаточносоответствующих типов для лексем, пользователю функции get_token() предоставляются те же буферыnumber_value и name_string, с которыми работает сам лексический анализатор.// lex.c: определения для ввода и лексического анализа#include <iostream.h>#include <ctype.h>#include "error.h"#include "lex.h"token_value curr_tok;double number_value;char name_string[256];token_value get_token() { /* ... */ }Интерфейс с синтаксическим анализатором определен четко:// syn.h: описания для синтаксического анализа и вычисленийextern double expr();extern double term();extern double prim();// syn.c: определения для синтаксического анализа и вычислений#include "error.h"#include "lex.h"103Бьерн Страуструп.Язык программирования С++#include "syn.h"double prim() { /* ...
*/ }double term() { /* ... */ }double expr() { /* ... */ }Как обычно, определение основной программы тривиально:// main.c: основная программа#include <iostream.h>#include "error.h"#include "lex.h"#include "syn.h"#include "table.h"int main(int argc, char* argv[]) { /* ... */ }Какое число заголовочных файлов следует использовать для данной программы зависит от многихфакторов. Большинство их определяется способом обработки файлов именно в вашей системе, а несобственно в С++. Например, если ваш редактор не может работать одновременно с несколькимифайлами, диалоговая обработка нескольких заголовочных файлов затрудняется. Другой пример: можетоказаться, что открытие и чтение 10 файлов по 50 строк каждый занимает существенно большевремени, чем открытие и чтение одного файла из 500 строк. В результате придется хорошенькоподумать, прежде чем разбивать небольшую программу, используя множественные заголовочныефайлы.
Предостережение: обычно можно управиться с множеством, состоящим примерно из 10заголовочных файлов (плюс стандартные заголовочные файлы). Если же вы будете разбиватьпрограмму на минимальные логические единицы с заголовочными файлами (например, создавая длякаждой структуры свой заголовочный файл), то можете очень легко получить неуправляемое множествоиз сотен заголовочных файлов.4.4 Связывание с программами на других языкахПрограммы на С++ часто содержат части, написанные на других языках, и наоборот, часто фрагмент наС++ используется в программах, написанных на других языках. Собрать в одну программу фрагменты,написанные на разных языках, или, написанные на одном языке, но в системах программирования сразными соглашениями о связывании, достаточно трудно. Например, разные языки или разныереализации одного языка могут различаться использованием регистров при передаче параметров,порядком размещения параметров в стеке, упаковкой таких встроенных типов, как целые или строки,форматом имен функций, которые транслятор передает редактору связей, объемом контроля типов,который требуется от редактора связей.
Чтобы упростить задачу, можно в описании внешних указатьусловие связывания. Например, следующее описание объявляет strcpy внешней функцией и указывает,что она должна связываться согласно порядку связывания в С:extern "C" char* strcpy(char*, const char*);Результат этого описания отличается от результата обычного описанияextern char* strcpy(char*, const char*);только порядком связывания для вызывающих strcpy() функций. Сама семантика вызова и, в частности,контроль фактических параметров будут одинаковы в обоих случаях. Описание extern "C" имеет смыслиспользовать еще и потому, что языки С и С++, как и их реализации, близки друг другу.
Отметим, что вописании extern "C" упоминание С относится к порядку связывания, а не к языку, и часто такое описаниеиспользуют для связи с Фортраном или ассемблером. Эти языки в определенной степени подчиняютсяпорядку связывания для С.Утомительно добавлять "C" ко многим описаниям внешних, и есть возможность указать такуюспецификацию сразу для группы описаний. Например:extern "C" {char* strcpy(char*, const char);int strcmp(const char*, const char*)int strlen(const char*)104Бьерн Страуструп.Язык программирования С++// ...}В такую конструкцию можно включить весь заголовочный файл С, чтобы указать, что он подчиняетсясвязыванию для С++, например:extern "C" {#include <string.h>}Обычно с помощью такого приема из стандартного заголовочного файла для С получают такой файлдля С++.
Возможно иное решение с помощью условной трансляции:#ifdef __cplusplusextern "C" {#endifchar* 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" { // стандартные математические функции105Бьерн Страуструп.doubledoubledoubledoubledouble// ...Язык программирования С++// как правило написаны на Сsqrt(double); // подмножество <math.h>sin(double);cos(double);exp(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 Описания функцийОписание функции содержит ее имя, тип возвращаемого значения (если оно есть) и число и типыпараметров, которые должны задаваться при вызове функции.
Например:externexternexternexterndouble sqrt(double);elem* next_elem();char* strcpy(char* to, const char* from);void exit(int);Семантика передачи параметров тождественна семантике инициализации: проверяются типы106Бьерн Страуструп.Язык программирования С++фактических параметров и, если нужно, происходят неявные преобразования типов. Так, если учестьприведенные описания, то в следующем определении:double sr2 = sqrt(2);содержится правильный вызов функции sqrt() со значением с плавающей точкой 2.0.