Чтение онлайн

ЖАНРЫ

Программирование для карманных компьютеров

Волков Владимир

Шрифт:

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

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

3. Работа линковщика. Файлы объектного кода связываются между собой в единый исполняемый файл с расширением. exe.

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

в исполняемый файл, отвечает специальная программа управления проектами. В большинстве версий языка С++ эта программа носит название make. На вход программы make подается специальный файл (makefile), в котором описывается состав проекта, опции препроцессора, компилятора и линковщика для каждой единицы компиляции, список доступных каталогов и прочие необходимые сведения. В случае eVC все стадии создания файла программы от работы препроцессора до линковки обычно не видны разработчику. Он лишь выполняет команду Build или Execute из пункта меню Build, и весь процесс выполняется за один шаг. Тем не мене в реальности «за ширмой» среды выполняются все этапы, и в качестве файла конфигурации проекта выступает файл с расширением. vcp.

Препроцессор и заголовочные файлы

Файлы заголовков

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

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

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

2. Описания классов.

3. Описания внешних переменных.

4. Определения макросов.

5. Определения типов, доступных для всего проекта.

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

Упражнение 4.7

1. Создать простое приложение и сохранить его с именем AdvancedCPP.

2. На вкладке FileView отыскать файл newres.h и двойным щелчком открыть его в редакторе кода. Поскольку этот файл содержит в себе множество директив препроцессора, он послужит хорошей иллюстрацией к их описанию.

Директива #include задает включение в текст данного файла текста другого файла, имя которого указано после директивы. В файле newres.h есть несколько директив #include.

#include <commctrl.h>

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

Директива #define используется для создания символических констант, для определения макрофункций и для определения управляющего идентификатора.

3. Найти в редакторе кода следующую строку:

#define AFXCE_IDR_SCRATCH_SHMENU 28700

Эта строка создает символическую константу AFXCE_IDR_SCRATCH_SHMENU со значением 28700. Теперь компилятор, обнаружив в тексте программы имя AFXCE_ IDR_SCRATCH_SHMENU, будет вместо него подставлять значение 28700. 4. Открыть в окне FileView файл aygshell.h. В этом файле нужно найти следующую строку кода:

#define CEM_UPCASEALLWORDS (WM_USER + 1)

Данное объявление говорит о том, что препроцессор, встретив вызов макрофункции CEM_UPCASEALLWORDS, вместо имени подставит выражение (WM_USER + 1). Макрофункция, как и любая другая функция, может принимать параметры. К примеру, объявление #define MF(a, b, c) (a*b*c/(a+b+c)) далее в тексте может быть использовано как MF(x, y, z). Вместо имени фунции с заданными аргументами препроцессор вставит тело функции, то есть (x*y*z/(x+y+z)). Файл newres.h начинается со строк:

#ifndef __NEWRES_H__ #define __NEWRES_H__

а завершается строкой:

#endif //__NEWRES_H__

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

Эти строки дают возможность перейти к директивам условной компиляции (условного расширения). К этим директивам относятся #if, #ifdef, #ifndef, #endif, #else и #elif.

Директива условной компиляции #if позволяет управлять процессом компиляции проекта. Если выражение const_exp, стоящее после директивы #if в конструкции #if const_exp, имеет ненулевое значение, то текст, следующий за директивой #if до

соответстующей ей директивы #endif, будет включен в текст, подаваемый на вход компилятора (а значит, и скомпилирован). В противном случае этот текст не попадет на вход компилятора и не войдет в программу.

Если имя ident, стоящее после директивы #ifdef в конструкции #if ident, определено в тексте программы, то текст, следующий за директивой #ifdef до соответстующей ей директивы #endif, будет включен в текст, подаваемый на вход компилятора.

Если имя ident, стоящее после директивы #ifndef в конструкции #ifndef ident, не определено в тексте программы, то текст, следующий за директивой #ifndef до соответствующей ей директивы #endif, будет включен в текст, подаваемый на вход компилятора.

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

Листинг 4.30

#if cnst_ex1//Если выражение const_exp1 имеет значение true,

[text1]//тогда расширяется text1

[#elif cnst_ex2//иначе если cnst_ex2 имеет значение true,

text2]//тогда расширяется text2

[#elif cnst_ex3//иначе если cnst_ex3 имеет значение true,

text3]//тогда расширяется text3 … и так далее.

[#elif cnst_exN//если не был расширен ни один из предыдущих блоков,

textN]//расширяем текст textN

#endif//и завершаем блок условной компиляции

Теперь нужно проанализировать реальный код, который приведен в листинге 4.31. Листинг 4.31

/* Если имя __NEWRES_H__ не определено, */

#ifndef __NEWRES_H__

/* Определяем это имя и расширяем текст модуля */

#define __NEWRES_H__

[текст модуля]

#endif //__NEWRES_H__

Если имя __NEWRES_H__ уже было определено в какой-то части программы, то повторной вставки модуля в текст не последует. Такой прием используется для предотвращения многократного расширения в тексте программы одного и того же модуля, на который оказалось несколько ссылок директивы #include. Также этот механизм предотвращает ситуацию, когда два модуля оказались взаимно включены друг в друга. Это вполне возможно в сложных программах, где включаемые модули в свою очередь тоже содержат директивы #include.

Директива #undef удаляет объявление имени, сделанное при помощи директивы #define.

Редко используемая директива #line позволяет изменить нумерацию строк и имя файлов, выводимых макросами____ LINE__ и___ FILE__.

Директива #import предназначена для вставки в текущий файл импортированной из соответствующей библиотеки типов информации. Например, директива

#import..\office\office.olb

вставит описание интерфейсов из файла office.olb в текущий файл.

Директива #pragma позволяет вставлять в текст модулей директивы, свойственные только данной платформе или операционной системе. У каждого компилятора для каждой операционной системы свой набор директив #pragma.

Функции

Функции main и WinMain

Функции в C++ являются краеугольным камнем всей концепции программирования. Собственно говоря, функции как раз и выполняют всю работу, которую запланировал для своего приложения разработчик. Само выполнение программы, написанной на С++, начинается с вызова специальной функции. Для консольных приложений это будет функция main О, для приложений Windwos – WinMainO. Модуль, в котором определена эта функция, и является основным модулем программы.

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

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

Синтаксис объявления функции WinMain приведен ниже.

int WINAPI WinMain(

HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPWSTR lpCmdLine,

int nShowCmd);

Параметры функции рассматриваются в следующем списке.

? hlnstance – уникальный идентификатор запускаемого экземляра приложения.

? hPrevInstance – уникальный идентификатор предыдущего запущенного экземпляра приложения. Для Pocket PC этот параметр всегда имеет значение NULL.

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

? nShowCmd – целочисленная константа, определяющая, как именно основное окно приложения будет показано после запуска. Ее возможные значения перечислены в табл. 4.5.

Таблица 4.5. Параметры запуска приложения

В качестве возвращаемого значения функция WinMainO возвращает целочисленную константу, переданную ей как wParam сообщения WM_QUIT.

Объявление и реализация функций

В стандарте C++ для того, чтобы объявить и реализовать функцию, необходимо разместить как объявление функции, так и реализацию. Реализация и прототип отличаются друг от друга только наличием тела функции, другими словами, реализация – это прототип с телом функции.

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

Поделиться с друзьями: