Многофайловые программы. Межфайловое взаимодействие переменных
Межфайловое взаимодействие
В многофайловых программах элементы, хранящиеся в разных файлах, должны каким-то образом сообщаться друг с другом. В этом разеделе мы выясним, как это делается. Вначале мы обсудим, как сообщаются откомпилированные по от- дельности, но скомпонованные вместе исходные файлы (.cpp), а затем — как об- ращаться с заголовочными (.H) файлами.
Взаимодействие исходных файлов
Давайте рассмотрим, как сообщаются между собой отдельные исходные файлы. Мы обратим внимание на три основных элемента: переменные, функции и клас сы. Для каждого из них есть свои правила межфайлового взаимодействия. В этом месте полезно будет вспомнить о понятии зоны действия, для этого придется вернуться ненадолго к главе 5. Зона действия (или зона видимости) — это часть программы, внутри которой к данной переменной или другому элементу про- граммы имеется доступ. Так, элементы, объявленные внутри функции, имеют локальную зону видимости, то есть доступ к ним может осуществляться только из данной функции. Соответственно, компоненты класса видны только внутри класса (кроме случаев использования оператора явного задания).
Элементы программы, объявленные вне всех функций и классов, имеют гло- бальную зону видимости, то есть доступ к ним осуществляется из любого места файла исходного текста программы. Как мы сейчас увидим, из других файлов того же проекта глобальные переменные тоже видны.
Межфайловые переменные
Начнем с простых переменных. Для этого вспомним разницу между объявлени- ем и определением. Мы объявляем, декларируем какую-то простую переменную, задавая ее тип и имя. Это не означает, что для нее сразу же резервируется место в памяти. Объявление переменной просто сообщает компилятору о том, что где- либо там, в программе, может встретиться переменная с таким-то именем и тако- го-то типа. Переменная определяется, когда под нее в памяти резервируется мес- то, которое способно содержать любое ее значение. Определение, так сказать, создает реальную переменную.
Большинство объявлений являются также и определениями. Точнее, единст- венным объявлением простой переменной, не являющимся определением, явля- ется объявление с использованием зарезервированного слова extern (без ини- циализации):
int SomeVar; //объявление и определение в одном флаконе
extern int someVar; //только объявление
Можно догадаться, что глобальная переменная может быть определена толь- ко один раз во всей программе, независимо от того, из скольких файлов она со- стоит.
//файл A
int globalvar; //определение в файле A //файл В
int globalVar; //как НЕЛЬЗЯ делать: то же определение в //файле B
Конечно, такие строгости касаются только глобальных переменных. Если переменные являются локальными по отношению к каким-либо классам или функциям, то вы можете сколько душе угодно определять одни и те же пере- менные с одинаковыми именами и типами данных. Не забудьте только разнести их по разным зонам видимости. Но лучше, конечно, вообще так не делайте — переменные с одинаковыми именами ничего, кроме сумятицы, в программу не вносят.
Как же обеспечить доступ к глобальным переменным, находящимся в одном файле, из другого? Тот факт, что компоновщик будет воротить нос при попытке
определения одной и той же глобальной переменной в разных файлах, еще не означает, что она будет отовсюду видна. Все-таки переменную нужно объявлять во всех файлах, в которых она используется. Например, такой вариант не подходит:
//файл A
int globalVar; //Определение в файле A //файл В
globalVar=3; //НЕЛЬЗЯ! globalVar тут никто не знает
Компилятор справедливо заметит, что globalVar — неидентифицированный идентификатор.
Чтобы то, что мы так рекламировали в начале этого пункта, было действи- тельно правдой, то есть чтобы переменная, определенная в одном файле, была видна в другом, нужно объявлять ее во всех остальных файлах с помощью заре- зервированного слова extern.
//файл A
int globalVar; //Определение в файле A //файл В
extern int globalVar; //Объявление в файле B
globalVar = 3; //Вот теперь все хорошо
Как вы уже, наверное, догадались, объявление глобальной переменной в фай- ле A сделало ее видимой в файле B, Зарезервированное слово extern означает, что объявление в данном случае — это только объявление, ничего более. Оно просит компилятор, который в каждый момент времени видит только один файл, не обращать внимание на то, что переменная globalVar не определена в файле B. Компоновщик, который с высоты своего особого статуса обозревает все файлы проекта, позаботится об установке ссылки на переменную, не определенную в данном файле.
Необходимо помнить об одном, возможно, неожиданном, ограничении: пере- менную нельзя инициализировать в объявлении с extern. Выражение
extern int globalVar = 27; //не то, что вы имеете в виду
заставит компилятор думать, что вы хотите определить globalVar, а не просто объ- явить ее. То есть он просто проигнорирует слово extern и сделает из объявления определение. Если эта же переменная где-то в другом файле уже определена, то компоновщик выдаст ошибку повторного определения.
Но что, если вам действительно никак не прожить без глобальных перемен- ных с одинаковыми именами в разных файлах? В этом случае необходимо опре- делять их с помощью зарезервированного слова static. Тем самым область види- мости глобальной переменной сужается до файла, в котором она определена. Другие переменные с тем же именем могут в других файлах использоваться без ограничений,
//файл A
static int globalVar; //определение: переменная видна //только в A
//файл В
static int globalVar; //определение: переменная видна
//только в В
Хотя определены две переменные с одинаковыми именами, конфликта не воз- никает. Код, в который входит обращение к globalVar, будет обращаться только к переменной, определенной в данном файле. В этом случае говорят, что статиче- ская переменная имеет внутреннее связывание. Нестатические глобальные пере- менные имеют внешнее связывание. (Как мы увидим чуть позднее, для сужения области видимости до данного файла может использоваться пространство имен.)
В многофайловых программах мы рекомендуем делать глобальные перемен- ные статическими, если по логике работы к ним не требуется доступ более чем из одного файла. Это предотвратит возникновение ошибки, связанной со слу- чайным заданием того же имени переменной в другом файле. К тому же лис- тинг в этом случае становится более прозрачным — не нужно заботиться о про- верке разных файлов на предмет совпадения имен.
Обратите внимание, что зарезервированное слово static имеет несколько зна- чений в зависимости от контекста. В главе 5 «Функции» мы обсуждали, что ког- да static изменяет локальную переменную (то есть определенную внутри функ- ции), изменяется ее продолжительность жизни от функции до всей программы, но видимость сохраняется неизменной, ограниченной функцией. Как уже говори- лось в главе 6 «Объекты и классы», компонентные данные статических классов имеют одинаковое значение для всех объектов, а не для каждого — свое. Однако в контексте глобальных переменных слово static просто сужает область видимо- сти до одного файла.
Переменная, определенная с помощью const, в общем случае не видна за пре- делами одного файла. В этом смысле она такая же, как static. Но ее можно сде- лать видимой из любого файла программы и для определения, и для объявле- ния с помощью слова extern:
//файл A
extern const int conVar2 = 99; //определение
//файл В
extern const int conVar2; //объявление
Здесь файл В будет иметь доступ к переменной conVar2, определенной в фай- ле A. Компилятор различает объявление и определение константы по наличию или отсутствию инициализации. 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | Поиск по сайту:
|