С помощью этой директивы можно вывести сообщение об ошибке при компиляции.
#error сообщение
Сообщение представляет собой любую строку (не заключенную в кавычки!), которая может содержать макросы, расширяемые препроцессором. Обычно директива применяется, чтобы обеспечить определение некоторого идентификатора:
#ifndef WIN_VERSION
terror He определена версия Windows!
#endif
Об этой директиве мы уже достаточно говорили в прошлой главе. Напомним вкратце, что препроцессор заменяет директиву содержимым указанного в ней файла. Есть две ее формы:
#include <filename> #include "filename"
В первом случае поиск нужного файла производится только в стандартных каталогах включаемых файлов; во втором случае этому предшествует поиск в текущем каталоге.
Директива позволяет установить внутренний счетчик строк компилятора, возвращаемый макросом _LINE_. Она имеет следующий вид:
#line номер строки ["имя файла"]
Номер_строки должен быть целой константой. Если указано необязательное имя_файла, то модифицируется также значение макроса_FILE_.
Эта директива служит для установки параметров, специфичных для компилятора. Часто выполняемые с ее помощью установки можно выполнить и другим способом, например, в диалоге Project Options или в командной строке компилятора. Директива имеет вид
#pragma директива
Что такое директива, описывает следующая таблица.
Таблица 4.3. Директивы “pragma компилятора C++Builder
| Директива |
Описание |
||
| alignment | Выдает сообщение о текущем выравнивании данных и размере enum-типов. | ||
| anon struct | Синтаксис:
#pragma anon struct on ^pragma anon struct off Разрешает или запрещает использование вложенных анонимных структур в классах. | ||
| argsused | Подавляет выдачу сообщения о неиспользуемых параметрах для функции, непосредственно следующей за директивой. | ||
| checkoption | Синтаксис:
#pragma checkoption строкаопций Проверяет, установлены ли опции, указанные в директиве. Если нет, выдается сообщение об ошибке. | ||
| codeseg | Синтаксис:
#pragma codeseg [имясегмента] ["класс"] [группа] Позволяет указать имя или класс сегмента либо группу, где будут размещаться функции. Если все опции директивы опущены, используется сегмент кода по умолчанию. | ||
| comment | Записывает строку-комментарий в объектный или исполняемый файл. Синтаксис:
ftpragma comment (тип, "строка") | ||
| exit | Позволяет указать функцию (функции), которая будет вызываться непосредственно перед завершением программы. Синтаксис директивы:
#pragma exit имя функции [приоритет] Необязательный приоритет в диапазоне 64-255 определяет порядок вызова подобных функций (чем он выше, т. е. меньше, тем позже вызывается функция). | ||
| hdrfile | Специфицирует имя файла прекомпилируемых заголовков. | ||
| hdrstop | Запрещает включать дальнейшую информацию в файл прекомпилируемых заголовков. | ||
| inline | Говорит компилятору, что файл должен компилироваться через ассемблер (компилятор генерирует код ассемблера, затем запускает TASM, который выдает конечный obj-файл). | ||
| intrinsic | Синтаксис:
#pragma intrinsic [-]имяфункции Управляет inline-расширением внутренних (intrinsic) функций (к ним относятся в основном функции исполнительной библиотеки для работы со строками, такие, как strncpy, memset и другие). | ||
| link | Синтаксис:
#pragma link "имяфайла" Заставляет компоновщик подключить к исполняемому модулю указанный объектный файл. | ||
| message | Синтаксис:
#pragma message ("текст"...) ttpragma message текст Выдает сообщение при компиляции. | ||
| nopushoptwarn | Подавляет предупреждение о том, что опции компилятора, имевшиеся в начале обработки текущего "файла, не были восстановлены к концу его компиляции (см. ниже о #pragma option). | ||
| obsolete | Синтаксис:
#pragma obsolete имяфункции Выдает предупреждение о том, что данная функция является устаревшей (если имеются обращения к ней). Директивой можно информировать других программистов, что вы усовершенствовали свой код и предусмотрели новую функцию для данной задачи. | ||
| option | Синтаксис:
#pragma option опции #pragma option push опции #pragma option pop Директива позволяет указать необходимые опции командной строки прямо в коде программы. Форма option push сначала сохраняет текущие установки в стеке компилятора; option pop, соответственно, выталкивает из стека последний набор опций. | ||
| pack | Синтаксис:
#pragma pack(n) #pragma pack (push, n) #pragma pack(pop) Задает выравнивание данных в памяти и эквивалентна ftpragma option -an. | ||
| package | Синтаксис:
#pragma package(smart init) #pragma package(smart init, weak) Управляет порядком инициализации модулей в пакетах C++Builder; по умолчанию включается в начало каждого автоматически создаваемого модуля. | ||
| resource | Синтаксис:
tpragma resource "*.dfm" Текущий файл помечается как модуль формы; в текущем каталоге должны присутствовать соответствующий dfrn-файл и заголовок. Всеми этими файлами IDE управляет автоматически. | ||
| startup | Аналогична pragma exit; позволяет специфицировать функции, исполняющиеся при запуске программы (перед вызовом main). Функции с более высоким приоритетом вызываются раньше. | ||
| warn | Позволяет управлять выдачей предупреждений. Синтаксис:
#pragma warn +|-\.www Www может быть трехбуквенным или четырехзначным цифровым идентификатором конкретного сообщения. Предшествующий ему плюс разрешает выдачу предупреждения, минус запрещает, точка — восстанавливает исходное состояние. |
О различных ключах командной строки (и эквивалентных установках диалога Project Options) мы расскажем в разделе об особенностях компилятора.
Препроцессорная обработка представляет собой первую фазу того процесса, что именуется компиляцией программы на C/C++. Компилятор C++Builder не генерирует промежуточного файла после препроцессорной обработки. Однако, если хотите, можно посмотреть на результат работы препроцессора, запустив отдельную программу срр32.ехе из командной строки:
срр32 myfile.c
Макроопределения, называемые в просторечии макросами, определяются директивой препроцессора #define. Можно выделить три формы макросов #define: простое определение символа, определение символической константы и определение макроса с параметрами.
Простое определение выглядит так:
#define NDEBUG
После такой директивы символ NDEBUG считается определенным. Не предполагается, что он что-то означает; он просто — определен (как пустой). Можно было бы написать:
#define NDEBUG 1
Тогда NDEBUG можно было бы использовать и в качестве символической константы, о которых говорилось в предыдущей главе. Всякое вхождение в текст лексемы NDEBUG препроцессор заменил бы на “I”. Зачем нужны макроопределения, которые ничего не определяют, выяснится при обсуждении условных конструкций препроцессора.
Как вы могли бы догадаться, #define может определять не только константы. Поскольку препроцессор выполняет просто текстовую подстановку, можно сопоставить символу и любую последовательность операторов, как показано ниже:
#define SHUTDOWN \
printf("Error!"); \ return -1
…
if (ErrorCondition()) SHUTDOWN; // "Вызов" макроса.
Обратная дробная черта (\) означает, что макрос продолжается на следующей строчке. В отличие от операторов С директивы препроцессора должны располагаться в одной строке, и поскольку это технически не всегда возможно, приходится явно вводить некоторый признак продолжения.
Определенный ранее макрос можно аннулировать директивой #undef:
#undef NDEBUG
После этого макрос становится неопределенным, и последующие ссылки на него будут приводить к ошибке при компиляции.
Предопределенные макросы
Компилятор C++Builder автоматически определяет некоторые макросы. Их можно разбить на две категории: макросы ANSI и макросы, специфические для C++Builder. Сводки предопределенных макросов даны соответственно в таблицах 4.1 и 4.2.
Таблица 4.1. Предопределенные макросы ANSI
| Макрос |
Описание |
||
| DATE | Литеральная строка в формате “mmm dd yyyy”, представляющая дату обработки данного файла препроцессором. | ||
| FILE | Строка имени текущего файла (в кавычках). | ||
| LIME | Целое, представляющее номер строки текущего файла. | ||
| STDC | Равно 1, если установлена совместимость компилятора со стандартом ANSI (ключ -А командной строки). В противном случае макрос не определен. | ||
| TIME | Строка в формате “hh:mm:ss”, представляющее время препроцессорной обработки файла. |

|
Макрос |
Значение |
Описание |
| ВСОРТ | 1 | Определен в любом оптимизирующем компиляторе. |
| BCPLUSPLUS | 0х0540 | Определен, если компиляция производится в режиме C++. В последующих версиях будет увеличиваться. |
| BORLANDC | 0х0540 | Номер версии. |
| CDECL | 1 | Определен, если установлено соглашение о вызове cdecl; в противном случае не определен. |
| CHARUNSIGNED | 1 | Определен по умолчанию (показывает, что char по умолчанию есть unsigned char). Можно аннулировать ключом -К. |
| CONSOLE | Определен при компиляции консольных приложений. | |
| CPPUNWIND | 1 | Разрешение разматывания стека; определен по умолчанию. Для аннулирования можно применить ключ -xd-. |
| cplusplus | 1 | Определен при компиляции в режиме C++. |
| DLL | 1 | Определен, если компилируется динамическая библиотека. |
| FLAT | 1 | Определен при компиляции в 32-битной модели памяти. |
| MIХ86 | Определен всегда. Значение по умолчанию — 300. (Можно изменить значение на 400 или 500, применив соответственно ключи /4 или /5 в командной строке.) | |
| MSDOS | 1 | Целая константа. |
| MT | 1 | Определен, если установлена опция -WM. Она означает, что будет присоединяться мультили-нейная (multithread) библиотека. |
| PASCAL | 1 | Определен, если установлено соглашение о вызове Pascal. |
| TCPLUSPLUS | 0х0540 | Определен, если компиляция производится в режиме C++ (аналогично bcplusplus ). |
| TEMPLATES | 1 | Определен для файлов C++ (показывает, что поддерживаются шаблоны). |
| TLS | 1 | Thread Local Storage. В C++Builder определен всегда. |
| TURBOC | 0х0540 | Номер версии (аналогичен BORLANDC ). |
| WCHAR T | 1 | Определен только в программах C++ (показывает, что wear t — внутренне определенный тип. |
| WCAR T DEFINED | 1 | То же, что и WCHAR Т. |
| Windows | Определен для кода, используемого только в Windows. | |
| WIN32 | 1 | Определен для консольных и GUI-приложений. |


В этом разделе мы обсудим два вопроса: особенности реализации языка в C++Builder и управление компилятором в IDE (диалог Project Options).
C++Builder поддерживает использование ряда ключевых слов, отсутствующих в стандартных ANSI C/C++. В таблице 4.4 перечислены все такие ключевые слова, которые могут применяться в программах на С. Многие из них могут записываться с одним или двумя начальными символами подчеркивания либо без них. Это сделано для того, чтобы можно было переопределить в препроцессоре какое-либо ключевое слово (например, форму без подчеркивания), сохранив возможность использования исходного слова (в форме с подчеркиванием). Рекомендую вам всегда пользоваться формой с двумя подчеркиваниями.
Таблица 4.4. Расширения набора ключевых слов языка С
| Ключевые слова |
Описание |
||
| asm
_asm __asm | Позволяет вводить код ассемблера непосредственно в текст программы на C/C++. Синтаксис:
__asm операция операнды ;_ или перевод_ строки Можно сгруппировать сразу несколько инструкций ассемблера в одном блоке asm: __asm { группа_ инструкций } | ||
| cdecl
_cdecl __cdecl | Специфицирует функцию как вызываемую в соответствии с соглашениями языка С. Перекрывает установки по умолчанию, сделанные в IDE или препроцессорных директивах. | ||
| _Except | Служит для управления исключениями в программах на С. | ||
| _Export
__export | Служит для экспорта из DLL классов, функций или данных. (См. главу 2, где приведен пример построения DLL.) | ||
| _fastcall
__fastcall | Специфицирует функцию как вызываемую в соответствии с соглашением fascall (передача параметров в регистрах). | ||
| _Finally | Служит для управления исключениями в программах на С. | ||
| _Import
__import | Импортирует классы, функции или данные, находящиеся в DLL. | ||
| _Inline | Применяется для объявления в программах на С расширяемых функций (inline). Соответствует ключевому слову inline, которое имеется только в C++. | ||
| _Pascal
__pascal ___pascal | Специфицирует функцию как вызываемую в соответствии с соглашениями языка Pascal. | ||
| _stdcall
__stdcall | Специфицирует функцию как вызываемую в соответствии со стандартными соглашениями о вызове. | ||
| _Thread | Позволяет определять глобальные переменные, имеющие тем не менее отдельные копии для каждой из параллельно выполняющихся линий кода (threads). | ||
| _Try | Служит для управления исключениями в программах на С. |


Эта страница (рис. 4.2) позволяет управлять деталями генерации объектного кода.

Рис. 4.2 Страница Advanced Compiler диалога Project Options
Группа радиокнопок Instruction set задает тип процессора целевой системы. Установки этой группы эквивалентны ключам командной строки -3, -4, -5 и -6.
Группа Data alignment управляет выравниванием данных в памяти. Выравнивание может отсутствовать (Byte), либо данные могут располагаться по адресам, кратным 2, 4 или 8 байтам. В структурах, если необходимо, вводятся байты заполнения. Установки группы эквивалентны ключам командной строки -an, где п — 1, 2, 4 или 8.
Группа Calling convention задает соглашение о вызове, применяемое по умолчанию. (Register обозначает соглашение _fastcall.) Эквивалентные ключи командной строки — -рс (или -р-), -р, -рг и -ps.
Группа Register variables управляет созданием регистровых переменных. None запрещает их использование. Automatic разрешает компилятору размещать переменные в регистрах там, где это целесообразно, и Register keyword разрешает размещать в регистрах только переменные, объявленные как register. Радиокнопки соответствуют ключам -г-, -г и -rd.
Раздел Output имеет два флажка: Autodepedency information и Generate
underscores. Первый из них определяет, будет ли включаться в объектный файл информация о зависимостях между исходными файлами (необходимо для работы команды Make). Второй флажок указывает, что имена функций _cdecl должны снабжаться начальным символом подчеркивания. Установленные флажки соответствуют ключам -Х- и -и.
Раздел Floating point управляет генерацией кода арифметики с плавающей точкой. None показывает, что ваш код не использует чисел с плавающей точкой. Fast исключает промежуточные преобразования типов при арифметических вычислениях, как явные, так и неявные;
при сброшенном флажке все преобразования выполняются в строгом соответствии с правилами ANSI С. Correct Pentium FDIV генерирует код, исключающий возможность ошибки из-за дефекта в ранних версиях процессора Pentium. Соответствующие ключи командной строки — -f-, -ff и -fp.
Эта страница диалога показана на рис. 4.1.
В нижней части страницы вы видите две кнопки: Full debug и Release. Первая из них выполняет все установки параметров, позволяющие в полной мере использовать возможности отладчика C++Builder; вторая запрещает генерацию какой-либо отладочной информации и оптимизирует код для получения максимальной скорости выполнения. При изучении языка вам лучше всего воспользоваться кнопкой Full debug и не задумываться больше об установках, влияющих на отладку и эффективность кода.

Рис. 4.1 Страница Compiler диалога Project Options
Коротко о разделах страницы Compiler.
Группа радиокнопок Code optimization позволяет полностью отключить оптимизацию, задать оптимизацию по скорости или выбрать отдельные опции оптимизации, включив радиокнопку Selected и нажав Optimizations. При этом откроется окно диалога со списком опций, в котором, кстати, показаны эквивалентные ключи командной строки, управляющие оптимизацией.
Группа Warnings управляет выдачей предупреждений. Включив соответствующую радиокнопку, можно запретить все, разрешить все предупреждения или управлять выдачей отдельных предупреждений. Диалог Compiler Warnings также показывает ключи командной строки. Я бы советовал вам разрешить выдачу всех предупреждений. Грамотно написанный код должен транслироваться не только без ошибок, но и без всяких замечаний со стороны компилятора.
Раздел Pre-compiled headers управляет прекомпиляцией заголовочных файлов.

Объем кода заголовочных файлов, включаемых в модуль исходный модуль, может достигать тысяч, если не десятков и сотен тысяч строк. К тому же часто эти заголовочные файлы включаются в каждый модуль проекта. Поскольку при разработке программы заголовочные файлы приходится изменять сравнительно редко (а стандартные заголовки вообще не меняются), имеет смысл компилировать все необходимые заголовки один раз и создать файл специального вида, который будет содержать всю необходимую “заголовочную” информацию в форме, обеспечивающей максимально быстрый доступ к ней. Компилятор C++Builder может генерировать такие файлы (с расширением .csm), •во много раз ускоряющие повторное построение проектов. Недостатком их можно считать разве что весьма большой размер — типичный файл прекомпилируемых заголовков может занимать от пяти до десяти мегабайт.

На этой странице диалога Project Options (рис. 4.3) расположены несколько полей редактирования, позволяющих задавать стандартные каталоги по умолчанию — библиотек, заголовочных файлов и т. д. Нас на этой странице интересует сейчас только раздел Conditionals.
В поле Conditional defines можно определять символы C/C++, языка Object Pascal и компилятора ресурсов, которые будут, например, управлять директивами условной компиляции в исходных файлах. Для присвоения символам значений используется знак равенства. Можно ввести в это поле сразу несколько определений, отделяя их друг от друга точкой с запятой, например:
NDEBUG;ххх=1;yyy-YES
Для ввода определений можно также воспользоваться редактором строк, отрывающимся при нажатии кнопки с многоточием.

Рис. 4.3 Страница Directories/Conditronals
В командной строке символы определяются с помощью ключа -D:
bcc32 -DNDEBUG -Dxxx=l -Dyyy=YES ...

Мы немного рассказали о ключах командной строки компилятора не столько для того, чтобы вы умели запускать bcc32.ехе вручную, а чтобы дать вам некоторые г начальные сведения, которые помогут вам разбираться в Ьрг-файлах проектов C++Builder. Полное руководство по запуску компилятора из командной строки вы можете найти в оперативной справке в разделах command-line compiler и command-line options.
Заключение
Эта глава завершает предварительный курс по С, включая имеющиеся в C++Builder различные расширения языка. Прежде чем заняться вплотную объектно-ориентированным программированием на C++, мы считаем целесообразным познакомить вас с принципами отладки программ в IDE, чем мы и займемся в следующей главе.
В этом коротком разделе мы покажем только некоторые из наиболее распространенных случаев, когда препроцессорные директивы могут оказаться полезны.
Предотвращение включения файлов
Иногда при использовании заголовков может происходить дублирование кода из-за повторного включения некоторого файла. (Допустим, у вас имеется исходный файл myprog.c, который подключает директивой # include два заголовка headerl.h и header2.h. Если, в свою очередь, оба этих файла подключают некоторый headerO.h, то последний будет дважды включен в исходный файл myprog.c. Это ни к чему, хотя обычно и не приводит к ошибке.)
Чтобы предотвратить повторное включение кода заголовочного файла, можно организовать контроль следующим образом (как говорят, “поставить часового”):
/*
** header0.h: Заголовочный файл, который может оказаться
** многократно включенным...
*/
#ifndef _HEADERO_H
#define _HEADERO_H
/* /
** Здесь идут макросы, определения типов
** и т.д. вашего заголовочного файла...
*/
*endif
Переключение разделов кода
Директивы условной компиляции могут использоваться для простого переключения между двумя различными вариантами кода — старым и экспериментальным алгоритмом, например. Это можно сделать так:
/*
** Измените определение на 0, чтобы вернуться к старому варианту.
*/
*define NEW_VER I
#if NEW_VER /*
** Экспериментальный код.
*/
#else /*
** Старый код.
*/
*endif
Или, если не вводить дополнительный идентификатор:
/*
** Измените на 1, когда новый код будет отлажен.
*/
*if 0 /*
** Экспериментальный код.
*/
#else /*
* * Старый код.
*/
*endif
Отладочные диагностические сообщения
При отладке программ можно с большой пользой применять макросы, генерирующие операторы вывода различных сообщений с указанием файла и номера строки, например:
#define INFO(msg)
printf(#msg "\n")
#define DIAG(msg)
printf("File " _FILE_ " Line %d: " \ #msg "\n", _LINE_)
void SomeFunc(void)
{
INFO(Entering SomeFunc.);
/* Выводит информационное сообщение. */
if (someError)
DIAG(Error encountered!);
/* Выводит сообщение об ошибке. */
INFO(Exiting SomeFunc...) ;
}
Макрос assert()
В заголовочном файле assert.h определен макрос assert (), выполняющий примерно то же самое, что и показанный выше пример. Его “прототип” можно записать как
void assert(int test);
Макрос расширяется в оператор if, проверяющий условие test. Если его значение равно 0, печатается сообщение Assertion failed: с указанием имени файла и номера строки. Вот пример:
#include <assert.h>
…
assert(!someError) ;
Если перед включением 41айла assert.h определить символ ndebug, операторы assert () будут “закомментированы”.
В этом разделе мы рассмотрим установки диалога Project Options, имеющие отношение к программам на С. В основном это будет касаться страниц Compiler и Advanced Compiler этого диалога. Он открывается выбором Project | Options в главном меню.
Можно производить выборочную компиляцию различных участков кода в зависимости от оценки некоторого константного выражения или определения идентификатора. Для этого служат директивы #if, #elif, #else и #endif. Общая форма применения директив условной компиляции следующая:
# выражение_1
группа_операторов 1
[# elif выражение_2
группа_опера торов_2
# elif выражение_3
группа_ операторов_ 3...]
[# else группа операторов else]
#endif
Первая группа операторов компилируется, если выражение_1 истинно; в противном случае операторы ее опускаются. Вторая группа компилируется, если выражение_1 ложно и выражение_2 истинно и т. д. Группа #else компилируется только в том случае, если все условные выражения ложны. Конструкция условной компиляции должна заканчиваться директивой #endif.
Разделы #elifH#else могут отсутствовать. Необходимыми элементами условной конструкции являются только директивы #if и #endif.
Операции в условиях #if
Выражения в директивах могут содержать обычные операции отношения <, >, <=, >= и ==. С их помощью можно проверять, например, значения предопределенных макросов или идентификаторов, определяемых директивой #define. В директивах препроцессора имеется также одна специальная операция defined. Она позволяет проверить, определен ли некоторый символ, например:
#define TEST
#if defined(TEST)
testFile = open("TEST.$$$", 0_CREAT | 0_TEXT) ;
#else testFile = -1;
#endif
Операция defined может комбинироваться с логическим отрицанием (!). ! defined (Sym) будет истинным, если Sym не определен.
Директивы #ifdef и ftifndef
Эти две директивы эквивалентны соответственно #if defined и #if !defined.
В IDE имеются и другие инструменты отладки помимо описанных выше. Мы расскажем о них очень коротко, поскольку применять их приходится не слишком часто.
Диалог Evaluate/Modify
Этот диалог (рис. 5.14) служит для оценки выражений и изменения значений переменных. Его можно открыть командой Run | Evaluate/Modify или из контекстного меню редактора, установив курсор на нужной переменной или выделенном выражении.

Рис. 5.14 Диалог Evaluate/Modify
В поле Expression вводится выражение, которое требуется оценить. При нажатии кнопки Evaluate результат оценки отображается в поле Result. Если вы хотите изменить значение переменной, введите новое значение в поле New value и нажмите кнопку Modify.

Диалог Evaluate/Modify можно использовать в качестве простого калькулятора, позволяющего вычислять арифметические выражения и оценивать логические условия. В выражениях можно смешивать десятичные, восьмеричные и шестнадцатеричные значения. Результат вычисления выводится всегда в десятичном виде, поэтому очень просто, например, перевести шестнадцатеричное число в десятичное. Введите численное выражение в поле Expression и нажмите Evaluate. Поле Result покажет результат вычисления.
Окно CPU
Это окно, показанное на рис. 5.15, открывается командой View | Debug Windows | CPU главного или View CPU контекстного меню редактора.
Окно имеет пять отдельных панелей. Слева вверху находится панель дизассемблера. Она показывает строки исходного кода (если в контекстном меню панели помечен флажок Mixed) и генерированные для них машинные инструкции. В окне CPU можно устанавливать контрольные точки, как в редакторе, и выполнять отдельные инструкции командами Step Over

Рис. 5.15 Окно CPU
и Trace Into. На рисунке вы видите фрагмент программы, приведенной в начале главы — заголовок цикла for и начало блока ассемблерных инструкций.
Справа вверху находятся две панели, отображающие состояние регистров и флагов процессора. Содержимое регистров, модифицированных в результате последней инструкции, выделяется красным цветом.

В этой главе вы познакомитесь с основными приемами отладки кода с помощью встроенного отладчика IDE. Это мощный инструмент, обладающий широкими возможностями вплоть до отладки на уровне машинного кода.
Программисты часто пренебрегают имеющимися в их распоряжении отладчиками и не используют их в полной мере, полагаясь на собственную сообразительность и пользуясь кустарными приемами отладки. Не берите с них пример, тем более что работать со встроенным отладчиком C++Builder очень просто, как вы сами скоро убедитесь. Следует также подчеркнуть, что отладчик является еще и прекрасным инструментом изучения языка, который позволяет производить наглядные эксперименты с различными языковыми конструкциями.
Наиболее общими приемами отладки являются установка контрольных точек, наблюдение за переменными и пошаговое исполнение кода.
Программа, запущенная под управлением отладчика IDE, исполняется как обычно, т. е. с полной скоростью, пока не будет встречена контрольная точка (breakpoint). Тогда отладчик приостанавливает программу, и вы можете исследовать и изменять содержимое переменных, исполнять операторы в пошаговом режиме и т. д.
Контрольные точки в C++Builder 4 могут быть четырех видов: в исходном коде, на адресе, на данных и точки загрузки модуля.
Контрольные точки в исходном коде
Это самый распространенный вид контрольных точек. Точка представляет собой маркер, установленный на некоторой строке исходного кода. Когда управление достигает этой строки, программа приостанавливается.
Проще всего установить контрольную точку такого типа прямо в редакторе кода, щелкнув кнопкой мыши на пробельном поле редактора (слева от текста) рядом со строкой, на которой требуется приостановить программу. В пробельном поле появится красный маркер, и сама строка будет выделена красным цветом фона (рис. 5.2). Повторное нажатие кнопки мыши удаляет контрольную точку.

Рис. 5.2 Установка контрольных точек
Если теперь запустить программу кнопкой Run, она будет остановлена на контрольной точке (рис. 5.3).

Рис. 5.3 Остановка программы на контрольной точке
Зеленая пометка на маркере контрольной точки означает, что точка проверена и признана действительной. Могут быть и недействительные контрольные точки — такие, что установлены на строках, не генерирующих исполняемого кода. Это могут быть комментарии, объявления, пустые строки или операторы, исключенные при оптимизации программы.
Текущая точка исполнения показана в пробельном поле зеленой стрелкой. Она указывает строку, которая должна исполняться следующей. Программу можно продолжить кнопкой Run или выполнять ее операторы в пошаговом режиме, о чем будет сказано ниже.
То, что мы сейчас показали — это простые контрольные точки в исходном коде; контрольные точки могут быть также условными, со счетчиком проходов или комбинированного типа.
Если вы в данный момент экспериментируете с отладчиком, откройте окно списка контрольных точек (View Debug Windows Breakpoints). Оно отображает все имеющиеся контрольные точки. Контекстное меню окна позволяет запретить остановку программы на контрольной точки, не удаляя ее (пункт Enable). Кроме того, выбрав пункт Properties..., вы получите доступ к свойствам выбранной точки (рис. 5.4 и 5.5).








Итак, вы остановили программу в контрольной точке. Обычно затем смотрят, каковы значения тех или иных переменных. Это называется наблюдением переменных (watching variables).
В IDE имеется специальное окно списка наблюдаемых переменных (рис. 5.8). Его можно открыть командой View | Debug Windows | Watches и ввести в него любое число переменных.

Рис. 5.8 Окно Watch List
Проще всего добавить переменную в список наблюдения можно, поместив курсор редактора кода на ее имя и выбрать в контекстном меню редактора Add Watch at Cursor. В окне наблюдений будет показано имя переменной и ее текущее значение либо сообщение, показывающее, что переменная в данный момент недоступна или наблюдение отключено (<disabled>). Можно ввести в список и целое выражение, если выделить его в редакторе и вызвать контекстное меню. Альтернативным методом добавления переменных или выражений является выбор в контекстном меню окна наблюдений пункта Add Watch... (пункт Edit Watch... служит для редактирования свойств уже имеющегося в списке наблюдения). Будет открыт диалог Watch Properties (рис. 5.9).

Рис. 5.9 Диалог Watch Properties
Помимо выражения, которое будет наблюдаться, диалог позволяет задать формат представления его значения. Поле Repeat count определяет число отображаемых элементов, если наблюдаемый объект — массив.
Быстрый просмотр данных
Редактор кода имеет встроенный инструмент, позволяющий чрезвычайно быстро узнать текущее значение переменной или выражения. Он называется подсказкой оценки выражения. Достаточно на секунду задержать курсор мыши над именем переменной или выделенном выражением, и под курсором появится окошко инструментальной подсказки с именем переменной и ее текущим значением (рис. 5.10). Причем — в отличие от окна наблюдений — таким способом можно просматривать и переменные, находящиеся за пределами текущей области действия (поскольку здесь не может возникнуть неоднозначности с именами).

Рис. 5.10 Подсказка с оценкой элементов массива

Выдачей подсказок управляет диалог Tools | Editor Options..., страница Code Insight (рис. 5.11). Чтобы разрешить отображение подсказок с оценками, следует убедиться, что флажок Tooltip expression evaluation помечен. Ползунок Delay задает задержку появления подсказок.



| Пункт меню |
Описание |
| Range... | Позволяет указать диапазон элементов массива, которые будут показаны. |
| Change... | Позволяет присвоить новое значение элементу данных. Если элемент можно изменять, в поле его значения имеется кнопка с многоточием. Ее нажатие эквивалентно команде Change. |
| Show Inherited | Помечаемый пункт, влияющий на объем отображаемой инспектором информации. Если помечен, то инспектор показывает не только элементы, объявленные в классе, но и унаследованные от базовых классов. |
| Inspect | Открывает новое окно инспекции для выбранного элемента данных. Это полезно при исследовании деталей структур, классов и массивов. |
| Descend | То же, что и Inspect, но выбранный элемент отображается в том же самом окне; нового окна инспекции не создается. Чтобы вернуться к инспекции прежнего объекта, выберите его в выпадающем списке в верхней части окна. |
| Type Cast... | Позволяет указать новый тип данных для инспектируемого объекта. Полезен в случаях, когда вы исследуете, например, указатель типа void*. |
| New Expression... | Позволяет ввести новое выражение, задающее инспектируемый объект. |

При отладке вам понадобится обращаться в основном к трем меню; это каскадное меню View | Debug Windows, меню Run и контекстное меню редактора кода. Пункты этих меню для управления отладкой приведены ниже в таблицах 5.1 - 5.3.
Таблица 5.1. Пункты меню Viev | Debug Windows
| Пункт |
Клавиша |
Описание |
|||
| Breakpoints | Ctrl+Alt+B | Открывает окно списка контрольных точек, показывающее активные контрольные точки и их свойства. | |||
| Call Stack | Ctrl+Alt+S | Открывает окно стека вызовов. Стек показывает, какие и в каком порядке вызывались функции, прежде чем управление достигло текущей точки программы. | |||
| Watches | Ctrl+Alt+W | Открывает окно наблюдения за переменными. Окно отображает список наблюдаемых переменных с их текущими значениями. | |||
| Local Variables | Ctrl+Alt+L | Открывает окно локальных переменных. В нем отображаются значения всех локальных переменных текущей функции. | |||
| Threads | Ctrl+Alt+T | Окно активных процессов и линий потока управления (threads). | |||
| Modules | Ctrl+Alt+M | Окно загруженных модулей — исполняемых файлов, динамических библиотек и пакетов запущенного проекта. | |||
| Event Log | Ctrl+Alt+E | Отображает протокол событий, происходящих при запуске проекта; какие события будут регистрироваться, можно задать на странице Event Log диалога Debugger Options. | |||
| CPU | Ctrl+Alt+C | Открывает окно состояния процессора. Отображает, в частности, компилированный код программы и содержимое регистров. | |||
| FPU | Ctrl+Alt+F | Открывает окно состояния FPU, отражающее содержимое его регистров и флагов. |
Таблица 5.2. Пункты меню Run
| Пункт |
Клавиша |
Описание |
|||
| Run | F9 | Запускает программу, при необходимости производя перед этим её сборку (Make). | |||
| Attach to Process... | Прикрепляет отладчик к уже выполняющемуся в данный момент процессу. | ||||
| Parameters... | Позволяет ввести аргументы командной строки или указать приложение, которое является “хозяином” отлаживаемой DLL. | ||||
| Step Over | F8 | Исполняет текущую строку исходного кода и переходит к следующей строке. | |||
| Trace Into | F7 | Исполняет текущую строку исходного кода; если строка содержит вызов функции, переходит к трассировке последней. | |||
| Trace to Next Source Line | Shift+F7 | Исполняет программу до следующей строки исходного кода. Например, если программа вызывает функцию API, требующую возвратно-вызываемой процедуры, отладчик остановит выполнение на входе в эту процедуру. | |||
| Run to Cursor | F4 | Исполняет программу до строки исходного кода, в которой установлен курсор редактора. | |||
| Run Until Return | Shift+F8 | Исполняет программу до возврата из текущей функции | |||
| Show Execution Point | Устанавливает курсор редактора кода на строку, в которой приостановлена программа. | ||||
| Program Pause | Приостанавливает выполнение программы, как только управление попадает в наличный исходный код. | ||||
| Program Reset | Ctrl+F2 | Закрывает программу. | |||
| Inspect... | Открывает диалог Inspect, в котором можно ввести имя инспектируемого объекта. | ||||
| Evaluate/Modify... | Ctrl+F7 | Открывает диалог Evaluate/Modify | |||
| Add Watch... | Ctrl+F5 | Открывает диалог Watch Properties | |||
| Add Breakpoint | Каскадное меню, позволяющее устанавливать контрольные точки различного вида (в исходном коде, на адресе, на данных, точки загрузки модуля). |

| Пункт |
Клавиша |
Описание |
| Toggle Breakpoint | F5 | Переключает (устанавливает или сбрасывает) контрольную точку в строке, где находится курсор редактора. |
| Run to Cursor | F4 | То же, что и в меню Run. |
| Goto Address... | Позволяет указать адрес области памяти, которая будет отображаться в панели дизассемблера окна CPU. | |
| Inspect... | Alt+F5 | Открывает окно инспекции объекта, на имени которого находится курсор. |
| Evaluate/Modify... | То же, что и в меню Run. | |
| Add Watch at Cursor | Ctrl+F5 | Вносит в список наблюдения переменную, на имени которой находится курсор. |
| View CPU | То же, что Viev меню.| Debug Windows| CPU в главном |
Одной из важнейших и самых очевидных операций при отладке является пошаговое исполнение кода. Когда программа приостановлена в контрольной точке, вы можете наблюдать значения различных переменных. Но чтобы найти конкретный оператор, ответственный за неправильную работу программы, нужно видеть, как эти значения меняются при исполнении отдельных операторов. Таким образом, выполняя операторы программы по одному, можно определить момент, когда значения каких-то переменных оказываются совсем не теми, что ожидались. После этого уже можно подумать, почему это происходит и как нужно изменить исходный код, чтобы устранить обнаруженную ошибку.
Эти команды могут выполняться из главного меню Run или с помощью кнопок инструментальной панели.
Step Over
Команда Step Over выполняет оператор, на котором приостановлено выполнение, и останавливается на следующем по порядку операторе. Текущий оператор выделяется в редакторе кода синим фоном и помечается зеленой стрелкой в пробельном поле. Если текущий оператор содержит вызов функции, она выполняется без остановок, и текущим становится первый оператор, следующий за возвратом из функции. Step Over “перешагивает” через вызов функции.
Trace Into
Команда Trace Into эквивалентна Step Over в случае, когда текущий оператор не содержит вызовов функций. Если же оператор вызывает некоторую функцию, то отладчик по команде Trace Into переходит на строку ее заголовка (заголовок при желании тоже можно рассматривать как исполняемый оператор, ответственный за инициализацию локальных переменных-параметров). При следующей команде (все равно — Step Over или Trace Into) текущим станет первый исполняемый оператор тела функции. Trace Into “входит внутрь” функции.

При выполнении команд Step Over и Trace Into в окне CPU отладчик исполняет не операторы, а отдельные машинные инструкции.
Заключение
Как видите, мы уделили отладчику IDE довольно много внимания. Но одно изучение, так сказать, “теории” отладки не научит вас отыскивать причины ошибок в неправильно работающей программе. Даже при наличии такого мощного инструмента, как отладчик C++Builder, отладка все равно остается чем-то вроде искусства. А любое искусство требует, во-первых, овладения техникой, т. е. знания имеющихся в вашем распоряжении средств, а во-вторых, опыта, приобретаемой в результате практической работы с отладчиком. Поэтому я советую вам не жалеть времени на изучение отладчика и побольше экспериментировать.
Прежде всего, для исследования отладчика нам понадобится программа. В листинге 5.1 показана тестовая программа, вызывающая функции DoSomeMath () и DoSort () . Первая из них довольно бессмысленна и включена в программу только для того, чтобы продемонстрировать плавающую арифметику и соглашение _pascal. Вторая представляет собой вариант пузырьковой сортировки, частично реализованный на языке ассемблера.

При создании нового модуля debugC.C автоматически создается заготовка включаемого файла debugC.h, причем C++Builder сразу вводит в него директивы защиты от повторных включений, о которых мы говорили в прошлой главе. Кстати, главный исходный файл консольного проекта называется в 5-й версии Debug, bpf, а не Debug.срр
Листинг 5.1. Тексты программы Debug
/**********************************************************
* * Debug.срр: Главный файл проекта.
*/
#pragma hdrstop
#include <condefs.h>
USEUNIT("debugC.с") ;
#define main
/**********************************************************
* * debugC.h: Заголовок для модуля debugC.с.
*/
#ifndef debugCH
#define debugCH
double _pascal DoSomeMath(double r, double h);
void DoSort(int array[], int n) ;
*endif
/*******************************************
* * debugC.с: Программа для демонстрации отладчика.
*/
#pragma inline
#pragma hdrstop
#include <stdio.h>
#include "debugC.h"
const double Pi = 3.14159265;
#pragma argsused
int main(int argc, char *argv[])
{
double rad, vol;
int i, n = 8;
int iArr[8] = {-1, 23, 7, -16, 0, 11, 24, 3};
rad = 2.0;
vol = DoSomeMath(rad, 3.0);
printf("Volume = %10.6f\n", vol);
DoSort(iArr, n) ;
printf("Sorted array:");
for (i=0; i<n; i++)
printf("%6d", iArr[i]);
printf("\n") ;
return 0;
} /* main */
/************************************************
** Просто чтобы продемонстрировать вызов pascal.
*/
double _pascal DoSomeMath(double r, double h)
{
double s;
s = Pi * r*r;
return s * h;


C++ допускает объявление объединений специального вида, называемых анонимными. Они не имеют этикеток и не являются именованными объектами. Последнее означает, что при обращении к разделам такого объединения не нужна операция-точка.
Глобальные анонимные объединения должны объявляться как static.
В прототипе функции язык C++ позволяет задать значения по умолчанию для некоторых параметров. Тогда при вызове функции соответствующие аргументы можно будет опустить, и компилятор подставит на их место указанные в прототипе значения. Правда, здесь существуют определенные ограничения.
Если вы задаете для какого-то параметра значение по умолчанию, то должны предусмотреть значения по умолчанию для всех последующих параметров функции. Таким образом, параметры со значениями по умолчанию должны стоять после всех остальных параметров в списке. Соответственно, если вы опускаете в вызове функции некоторый аргумент, то нужно опустить и все последующие аргументы. Короче говоря, в списке аргументов не должно быть пропусков.
Вот небольшая иллюстрация:
//////////////////////////////////////////////////
// Defaults.срр: Аргументы по умолчанию.
//
#include <stdio.h>
int Sum(int a, int b, int с = 0, int d = 0);
//Корректный прототип.
//
// Следующий прототип был бы некорректным:
// int Sum(int а, int b = 0, int с, int d = 0) ;
// int Sum(int a, int b, int с, int d)
// Определение функции.
{
return а + b + с + d;
}
int main(void) (
printf("l + 2 + 3 + 4 = %d\n", Sum(l, 2, 3, 4));
printf("1 + 2 + 3 = %d\n", Sum(l, 2, 3) ) ;
printf("1 + 2 = %d\n", Sum(1, 2));
//
// Недопустимый вызов:
// printf(" 1 + 2 + 4 = %d\n", Sum(l, 2,, 4)) ;
//
return 0;
}

В примерах программ этой главы мы не будем приводить строки, автоматически генерируемые при создании консольных приложении. Надеюсь, это не вызовет у вас никаких недоразумений.
Главная функция определяется в примерах как int main (void), но это не существенно, и если вы захотите посмотреть, как они работают, можно оставить заголовок, который генерирует C++Builder.
Функция Sum, как видите, вычисляет сумму четырех переданных ей чисел. Благодаря значениям по умолчанию последние два аргумента в вызове Sum можно не указывать, если требуется посчитать сумму всего двух или трех чисел.

Значения по умолчанию в прототипе не обязаны быть обязательно константами. Это могут быть, например, глобальные переменные или даже значения, возвращаемые функцией.
Модифицированный С
Объектно-ориентированное программирование
Классы C++
Потоки ввода-вывода
Шаблоны
Библиотека стандартных шаблонов
Управление исключениями
RTTI и приведение типов
В этой части мы будем изучать объектно-ориентированное программирование на C++. Конечно, C++ не единственный язык, работающий с классами и объектами. Есть Object Pascal, есть Java, есть и более старые языки вроде Smalltalk. Поэтому, наверное, имеет смысл поговорить и об общих принципах организации подобных языков.
Помимо общих принципов объектной архитектуры и классов языка C++ мы поговорим о таких вещах, как стандартные потоки, исключения, шаблоны и RTTI. К концу этой части книги вы узнаете практически все о стандартном ANSI C++, и в части III мы перейдем к изучению средств визуального программирования C++Builder.
Для ряда операций, в основном логических, в ANSI C++ введены альтернативные обозначения (ключевые слова) в стиле языка Pascal. Это было сделано потому, что на некоторых национальных клавиатурах трудно вводить знаки вроде “^” или “~”. Ниже перечислены все такие обозначения.
| Ключевое слово | Эквивалентный знак | Операция | |||
| and | && | Логическое AND | |||
| and eq | &= | Присвоение поразрядного AND | |||
| bitand | & | Поразрядное AND | |||
| bitor | | | Поразрядное OR | |||
| coiripl | ~ | Поразрядное NOT (дополнение до 1) | |||
| not | ! | Логическое NOT | |||
| not eq | ! = | Отношение “не равно” | |||
| or | || | Логическое OR | |||
| or eq | |= | Присвоение поразрядного OR | |||
| xor | ^ | Поразрядное исключающее OR | |||
| xor eq | ^= | Присвоение исключающего OR |
К сожалению, в C++Builder, даже в 5-й версии, эти ключевые слова пока не реализованы, но мы все равно их здесь перечислили. О них следует знать, особенно если вам придется переносить в C++Builder код, написанный для других компиляторов, например, Borland C++ 5.
В некоторых своих аспектах язык C++ является, так сказать, улучшенным С. В этой главе мы не будем касаться совершенно новых возможностей C++, таких, как объектно-ориентированное программирование и шаблоны, а остановимся на тех моментах, которые можно рассматривать в качестве модификаций старого языка С.
В С глобальная константа, т. е. инициализированная глобальная переменная с модификатором const, имеет своей областью действия всю программу (доступна для внешней компоновки), как и любая переменная без const. Другими словами, ее имя заносится в список глобальных символов объектного модуля и поэтому к ней можно обращаться из любого другого исходного файла программы.
В C++ областью действия глобальных констант является текущий файл, аналогично глобальным переменным static. Для того, чтобы к глобальной константе можно было обращаться из других компилируемых модулей, она должна быть определена как extern const, например:
extern const double eConst = 2.718281828;
В модулях, которые будут обращаться к такой константе, она, как и в С, должна быть объявлена внешней:
extern const double eConst;
Такое поведение глобальных констант C++ упрощает их объявление. Можно поместить определение константы (без extern) в заголовочный файл и включать его во все исходные модули, где она используется. Тем самым будет генерированы отдельные константы для каждого модуля, с одним именем и одинаковыми значениями. В С такие объявления вызывали бы ошибку дублирования определения.
В языке C++ этикетки структур, объединений и перечислений являются именами типов в отличие от С, где новые имена типов могут порождаться только оператором typedef. Тем самым определение новых типов упрощается. Вместо старого определения
struct Person {
struct Person *link;
char firstName[32];
char lastName[32];
};
struct Person aPerson;
или введения нового имени с помощью typedef достаточно будет написать
struct Person {
Person *link;
char firstName[32];
char lastName[32] ;
};
Person aPerson;
Person, таким образом, будет настоящим именем типа.
В языке С модификатор const означает, что значение переменной после инициализации не может быть изменено. В C++ переменные с const рассматриваются как истинные константные выражения. Поэтому в отличие от С в C++ допускается их использование в объявлении массивов:
const int aSize = 20 * sizeof(int);
char byteArray[aSize];
Локальные переменные в С должны объявляться в начале блока, т. е. до всех исполняемых операторов. В C++ переменные можно объявлять где угодно. Это предоставляет программисту определенные удобства и уменьшает возможность ошибок, позволяя размещать объявления переменных ближе к тому месту, где они используются. Вот один пример:
#include <stdio.h>
int main(void) {
int n = 10;
printf("Hello! i =") ;
int i;
for (i=0; i<n; i++)
{
printf("%4d", i);
}
printf("\nAfter loop i = %d\n", i);
return 0;
Счетчик i объявляется непосредственно перед заголовком цикла for, а не в начале блока.

Можно объявлять переменную счетчика прямо в заголовке цикла, как это часто и делается:
for (int i=0; i<n; i++) {...}
Ранее считалось, что при таком объявлении i остается доступной и после завершения цикла. Но стандарт ANSI постулирует, что область действия объявленной в заголовке цикла переменной ограничивается телом цикла. То же самое относится к циклам while.
В языке C++ для управления динамической памятью введены операции new и delete (для массивов new [ ] и delete [ ]). В С для этого применялись в C++, однако новые операции имеют некоторые преимущества.
Переопределение операций new и delete
Стандартные (глобальные) версии операций new и delete можно переопределить или перегрузить, чтобы придать им некоторые дополнительные свойства или обеспечить возможность передачи им дополнительных аргументов. Это бывает полезно при отладке, когда требуется проследить все выделения и освобождения динамической памяти:
#include <stdlib.h>
#include <stdio.h>
////////////////////////////////////////////////////
/ / Переопределение операций new и delete.
//
void* operator new(size_t size)
{
printf("%u bytes requested.\n", size);
return malloc(size);
void operator delete(void *ptr)
{
printf("Delete performed.\n") ;
free(ptr) ;
}
/////////////////////////////////////////////////////////////
// Перегрузка new для выдачи дополнительной информации.
//
void* operator new (size t size, char *file, int line)
printf("%u bytes requested in file %s line %d.\n", size, file, line) ;
return malloc(size);
}
int main(void) {
double *dptr = new double; // Вызывает новую new.
*dptr = 3.14159265;
delete dptr; // Вызывает новую delete.
// Вызов перегруженной new.
dptr = new(_FILE_, _LINE_) double;
*dptr = 1.0;
delete dptr;
return 0;
}

Здесь используется механизм определения функций-операций C++. В этом языке можно переопределить или перегрузить практически любое обозначение операции, чтобы, например, можно было применять стандартные знаки арифметических операций к объектам созданных пользователем типов. Об этом мы будем говорить в следующих главах.
Обратите внимание на синтаксис определения и вызова функций-операций new и delete. Операция new должна возвращать пустой указатель, а ее первым параметром всегда является число затребованных байтов. Компилятор автоматически вычисляет это число в соответствии с типом создаваемого объекта. Возвращаемый указатель приводится к этому типу.

В языке С локальная переменная скрывает глобальную с тем же именем. Другими словами, если функция объявляет переменную, одноименную глобальной переменной, то, хотя мы и остаемся в области действия последней, она оказывается недоступна для функции. Чтобы разрешить конфликт имен, компилятор считает, что все ссылки на данное имя относятся к локальной переменой.
В C++ имеется операция разрешения области действия, позволяющая в такой ситуации обращаться к глобальной переменной, предваряя ее имя символами “ : : ”. Вот пример, поясняющий разрешение области действия:
#include <stdio.h> int aVar = 111; // Глобальная переменная.
int main(void)
{
int aVar == 222; // Локальная переменная.
printf("Local aVar is %d.\n", aVar);
printf("Global aVar is, %d.\n", ::aVar);
return 0;
}
Есть несколько случаев, когда написанный на С код оказывается не корректным, если его компилировать в режиме C++.
В программировании то и дело случается писать функции для схожих действий, выполняемых над различными типами и наборами данных. Возьмите, например, функцию, которая должна возвращать квадрат своего аргумента. В C/C++ возведение в квадрат целого и числа с плавающей точкой — существенно разные операции. Вообще говоря, придется написать две функции — одну, принимающую целый аргумент и возвращающую целое, и вторую, принимающую тип double и возвращающую также double. В С функции должны иметь уникальные имена. Таким образом, перед программистом встает задача придумывать массу имен для различных функций, выполняющих аналогичные действия. Например, Square-Int() и SquareDbl() .
В C++ допускаются перегруженные имена функций (термин взят из лингвистики), когда функции с одним именем можно тем не менее идентифицировать по их списку параметров, контексту, так сказать, в котором имя употребляется.
Рассмотрите следующий пример с вышеупомянутыми квадратами. Мы предусмотрели еще “возведение в квадрат” строки, когда результатом функции должна быть строка, в которой любые символы, кроме пробелов, удваиваются.
#include <stdio.h>
int Square(int arg)
{
return arg*arg;
}
double Square(double arg)
{
return arg*arg;
char *Square(const char *arg, int n)
{
static char res[256];
int j = 0;
while (*arg && j < n) { if (*arg != ' ') res[j++] = *arg;
res[j++] = *arg++;
}
res[j] = 0;
return res;
}
int main(void)
{
int x = 11;
double у = 3.1416;
char msg[] = "Output from overloaded Function!";
printf("Output: %d, %f, %s\n", Square (x) , Square (y) , Square (msg, 32) ) ;
return 0 ;
}
}
Результат работы программы показан на рис. 6.1.
Довольно понятно, что компилятор, когда ему встречается вызов перегруженной функции, видит только список фактических параметров, но

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

Рано или поздно практически каждый программист сталкивается с проблемой конфликта идентификаторов, особенно если проект разрабатывают несколько программистов либо привлекаются библиотеки классов, поставляемые кем-то другим. Приходится искать какой-то способ избежать употребления глобальных имен, используемых имеющимися библиотеками, другими программистами группы и т. п.
Язык C++ решает эту проблему, позволяя разбить проект на сегменты с различными пространствами имен (namespaces). Заключив свои символы в отдельное пространство имен, вы, по существу, снабжаете имя всякого типа, переменной и т. д. некоторым скрытым префиксом. Например, если определить пространство имен
namespace MYSPACE { int х;
}
то переменная х будет существовать в пространстве имен MYSPACE. Любые другие переменные х, принадлежащие глобальной области действия или другому пространству имен, не будут с ней конфликтовать. Чтобы явно обратиться к переменной, применяется операция разрешения области действия:
MYSPACE::х = 11;
Пространства имен могут быть вложенными:
namespace OUTER { int x;
namespace INNER { int x;
}
Соответственно, чтобы ссылаться на эти переменные, нужно будет написать:
OUTER::х = 11;
OUTER::INNER::x = 22;

Пространства имен можно создавать и не только в глобальной области действия, но и, например, в функциях. Но это редко имеет практический смысл.
Создание пространства имен
К созданию именных пространств можно отнести три момента:
Первоначальное объявление пространства.
Расширение пространства имен.
Определение анонимного пространства.
Первоначальное объявление пространства имен — это определение пространства с именем, еще не встречавшимся в программе. Синтаксис его следующий:
namespace новое_имя {тело пространства имен}
Тем самым в программе объявляется пространство имен с именем новое_имя.
Объявленное пространство имен можно расширять, в том же исходном файле или любом другом, применяя ту же конструкцию, но с уже имеющимся именем:
namespace существующее_имя [тело_пространства__имен]
Определение функции транслируется компилятором в отдельный и относительно автономный блок объектного кода. При вызове функции в вызывающей процедуре (программе) генерируется инструкция CALL с предшествующим ей кодом, ответственным за передачу параметров, и последующим кодом очистки стека. Инструкция CALL передает управление коду функции, являющемуся внешним по отношению к вызывающей процедуре. Все это вы можете увидеть воочию в окне CPU отладчика.
По своем завершении функция возвращает управление инструкцией RET. Выполнение вызывающей процедуры возобновляется с инструкции, следующей за call.
Однако, если определить функцию с модификатором inline, компилятор, по крайней мере в принципе, генерирует встроенное расширение функции на месте вызова. Это напоминает расширение макроса. Тем самым из кода исключаются инструкции вызова, возврата, передачи параметров и т. д., что, естественно, уменьшает общее время выполнения. Но следует иметь в виду, что код функции будет воспроизводиться в исполняемом файле столько раз, сколько имеется операторов вызова в исходной программе. Если функция велика и вызывается из многих мест, объем исполняемого файла может заметно возрасти.
Вот простейший пример расширяемой функции:
#include <stdio.h>
inline int. Max(int a, int b)
{
return a > b? a : b;
}
int main(void)
int x = 11, у = 22;
printf("Max(%d, %d) = %d.\n", x, у, Мах(х, у)) ;
return 0;
}

Если зы хотите, чтобы компилятор генерировал расширения iniine-функций, нужно сбросить флажок Disable inline expansions на странице Compiler диалога Project Options.
Тут же следует сделать целый ряд оговорок относительно того, будет ли компилятор действительно генерировать встроенный код для функций, которые вы определили как inline.
Прежде всего, в самом стандарте ANSI сказано, что модификатор inline является лишь рекомендацией компилятору генерировать встроенное расширение функции. Наподобие модификатора register, который рекомендует разместить переменную в регистре процессора. Это не всегда возможно, поэтому компилятор может безо всяких предупреждений игнорировать модификатор inline.


Типом символьной константы (символ, заключенный в апострофы) в С является int, В языке C++ символьная константа имеет тип char. Конечно, это различие практически несущественно, но можно придумать случай, когда из-за него код С окажется несовместимым с C++.
В языке С тип char эквивалентен либо signed char, либо unsigned char; какому именно, определяется реализацией или установками компилятора. Обычно считается, что char — это signed char. В C++ char.

Различие трех символьных типов становится ясным в свете перегрузки функций/ о которой будет говориться в следующем разделе этой главы. Можно определить, например, такие перегруженные функции:
void OutC(char с)
{
printf("Unspec: %c\n", c);
}
void OutC(signed char c)
{
printf("Signed: %c\n",'c);
}
void OutC(unsigned char c)
{
printf("Unsigned: %c\n", c);
}
Для сравнения отметим, что перегрузить подобным образом функции для типа int невозможно:
void OutI(int i)
{
printf("Unspec: %d\n", i);
}
void OutI(signed int i)
{
printf("Signed: %d\n", i);
} void OutI(unsigned int i)
{
printf("Unsigned: %d\n", i);
}
Такие определения вызовут сообщение об ошибке компиляции, поскольку типы int и signed int эквивалентны.
В C++ имеется модифицированная форма указателей, называемая ссылкой (reference). Ссылка — это указатель, который автоматически разыменовывается при любом обращении к нему. Поэтому ссылке, как таковой, нельзя присвоить значение, как это можно сделать с обычным указателем. Ссылку можно только инициализировать.
Ссылки как псевдонимы переменных
Переменная-ссылка объявляется со знаком “&”, предшествующим ее имени; инициализирующим выражением должно быть имя переменной. Рассмотрите такой пример:
///////////////////////////////////////////////////////////////////
// Alias.cpp: Ссылка как псевдоним.
//
#include <stdio.h>
int main(void)
{
int iVar = 7777;
// Инициализация целой переменной.
int *iPtr = siVar;
// Инициализация указателя адресом
// iVar.
int &iRef = iVar;
// Инициализация ссылки как
// псевдонима iVar.
int *irPtr = &iRef;
// Инициализация указателя "адресом"
// ссылки.
printf("iVar = %d\n", iVar) ;
printf("*iPtr = %d\n", *iPtr);
printff'iRef = %d\n", iRef);
printf("*irPtr = %d\n", *irPtr) ;
printff'iPtr = %p, irPtr = %p\n", iPtr, irPtr);
return 0;
}
Первые четыре оператора printf выводят одно и то же число 7777. последний оператор печатает значения двух указателей, первый из которых инициализирован адресом переменной, а второй адресом ссылки. Эти адреса также оказываются одинаковыми. Как видите, после инициализации ссылки ее имя используется в точности как имя ассоциированной с ней переменной, т. е. как псевдоним.
Еще раз подчеркнем, что ссылку после инициализации нельзя изменить; все обращения к ссылке будут относиться на самом деле к переменной, именем которой она была инициализирована.
Ссылки в качестве параметров функции
Все сказанное в предыдущем параграфе не имеет существенного практического значения; к чему, в самом деле, вводить ссылку на переменную, если с тем же успехом можно обращаться к ней самой? По-иному дело обстоит в случае, когда ссылкой объявляется параметр функции.

В языке С и первых версиях C++ не было специального булева (логического) типа данных. В качестве логических использовались переменные целочисленных типов. В качестве истинного и ложного логических значений часто вводили символические константы, например:
#define FALSE0
#define TRUE 1
int done = FALSE;
while (!done) { // И т.д...
}
Теперь в ANSI C++ есть тип bool, позволяющий объявлять переменные специально булева типа. Кроме того, для представления булевых значений имеются предопределенные константы true и false. Внутренним представлением true является 1, представлением fal5e .— 0. С их помощью можно присваивать значения булевым переменным:
bool done;
const bool forever = true;
done = false;
Можно определять булевы функции, булевы параметры и т. п. Вот, например, прототип булевой функции с булевым параметром:
bool Continue(bool showPrompt);
В условиях циклов и выражениях условных операторов булевы переменные ведут себя точно так же, как прежние “логические” типа int.
Это еще один новый встроенный тип C++. Он предназначен для хранения “широких” символов (в противоположность “узким” символам типа char). Тип wchar_t имеет достаточный диапазон, чтобы отображать очень большие наборы символов — вплоть до китайских иероглифов. В С++Вuilder он эквивалентен типу short int.
В языке С значение типа void* можно непосредственно присваивать переменной-указателю любого типа, без каких-либо приведений. В C++ это не допускается. Другими словами, код вроде
SomeStruct *sPtr;
void AllocBuffer(int n)
{
sPtr = malloc(n * sizeof(SomeStruct));
}
вызовет ошибку в C++. Необходимо явное приведение типа указателя:
sPtr = (SomeStruct *)malloc(n * sizeof(SomeStruct));
В этом разделе мы рассмотрим те нововведения C++, которые можно считать усовершенствованиями С, поскольку они не выходят за рамки классической структурной модели программирования.