Нижегородский государственный университет им. Н.И.Лобачевского
Опубликован: 25.11.2008 | Доступ: свободный | Студентов: 9601 / 1296 | Оценка: 4.06 / 3.66 | Длительность: 21:16:00
Лекция 13:

Дополнительные сведения о системе программирования Borland C++ 3.1

< Лекция 12 || Лекция 13: 12 || Лекция 14 >
Аннотация: Материалы данной лекции посвящены препроцессору и компилятору системы программирования Borland C++ 3.1. Приводятся основные понятия и определения, а также практические примеры

12.1. Препроцессор и условная компиляция

В состав системы программирования BC 3.1 входит препроцессор cpp.exe, который выполняет следующую подготовительную работу перед компиляцией программы:

  • включает в программу тексты указанных файлов;
  • исключает из программы фрагменты, не удовлетворяющие заданным условиям;
  • осуществляет предусмотренную замену (макроподстановка);
  • заменяет Esc-последовательности их числовыми кодами;
  • объединяет смежные символьные строки и устраняет символы переноса строк.

Все действия препроцессора диктуются директивами, которые программист включает в текст своей программы. Первым символом директивы является символ #.

Для включения в текст программы указанных файлов используется директива #include (от англ. – включить), допускающая два следующие формата:

#include <file_name>
#include "file_name"

Угловые скобки являются указанием препроцессору, что поиск файла с заданным именем надо начинать с системного каталога (например, с каталога c:\bc\include ). Если в указанном каталоге файл file_name не обнаружен, то поиск продолжается сначала с текущего каталога, а затем по всем каталогам, перечисленным в директиве PATH операционной системы. Если имя файла заключено в двойные кавычки, то поиск начинается с текущего каталога.

Обычно, с помощью директивы #include к программе подключаются системные и пользовательские заголовочные файлы с расширением .h (от header – заголовок). Однако точно так же к программе можно подключить ранее заготовленный фрагмент исходного кода, оформленный в виде текстового файла с расширением .inc.

Замена одной цепочки символов в тексте программы на другую цепочку символов реализуется с помощью макроподстановки #define (от англ. – определить):

#define s1s2s3...sn  q1q2...qm

При этом цепочка символов s1s2s3...sn в тексте исходной программы будет заменена на цепочку q1q2...qm. Пробелы перед замещающей цепочкой и в ее конце игнорируются. Замене не подвергаются значения строк и комментарии. Заменяющий фрагмент может оказаться и многостроковым. В этом случае в конце каждой строки помещается символ переноса – " \ ". В приведенной ниже программе содержится несколько наиболее характерных примеров использования директивы #define:

#include <stdio.h>
#include <conio.h>
#define Nmax 100
#define max(a,b)  ((a)>(b))?(a):(b)
#define print(a) printf("\n%s=%d\n",#a,a);\
                 getch()
void main()
{ int x=5,y=8;
  int z=Nmax;
  int w=max(x*y,z);
  print(x);
  print(y);
  print(z);
  print(w);   
}
//=== Результат работы ===
x=5
y=8
z=100
w=100

Обратите внимание на некоторые тонкости в приведенных подстановках.

Во-первых, аргументы макроопределения-функции max в замещающем выражении заключены в круглые скобки. Это позволяет использовать в конкретных обращениях нормальные арифметические выражения. Представим себе, что в программе достаточно часто приходится использовать операцию возведения в квадрат. Если для этой цели макроподстановку Square(x) определить без использования скобок (#define Square(x) x*x), то для арифметического выражения Square(1+z) результат такой подстановки даст 1+z*1+z=2*z+1, т.е. заведомо неправильное значение.

Наличие круглых скобок тоже не является стопроцентной гарантией правильности результата подстановки. Например, попытка обратиться к макросу max необычным образом сообщений об ошибках не вызовет, но и результат может оказаться далеким от истины:

w=max(x+=2,y+=3);	//результат подстановки w=14
w=max(x+2,y+3);		//результат правильный w=11

В бесскобочном макроопределении первое обращение привело бы к синтаксической ошибке ( Lvalue required – требуется левое значение).

Во-вторых, макроопределение max не зависит от типа используемых данных.

В-третьих, в макроопределении print использована довольно редко описываемая возможность вывода имени переменной ( #a – интерпретируется препроцессором как имя переменной a ). Если бы мы включили эту переменную в форматную строку ( printf("\na=%d",a); ), то ничего хорошего из этого бы не вышло. Так как на содержимое строк макроподстановка не распространяется, то при каждом обращении вместо имени очередной переменной выводился бы символ 'a'.

В операторе макроподстановки иногда используется операция склейки лексем (аналог того, что при работе со строками называют конкатенацией ):

#define Paste(a,b)  a##b

В результате такой подстановки строка Paste(x,4) будет заменена на x4.

Две группы следующих директив используются для организации "условной компиляции":

#define...#ifdef...#ifndef...#undef
#if...#elif...#elif...#else...#endif

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

Если заглянуть в любой заголовочный файл из каталога ...BC\INCLUDE, например, в файл math.h, то в самом его начале (исключая комментарий по поводу авторских прав) находятся следующие строки:

#ifndef  __MATH_H
#define  __MATH_H
#if !defined(___DEFS_H)
#include <_defs.h>
#endif

В чем смысл двух первых строк? Сначала проверяется, была ли ранее объявлена подстановка для идентификатора __MATH_H (этот идентификатор является уникальным, т.к. он повторяет имя заголовочного файла). Если такого указания еще не было, то следующая строка объявляет о необходимости такой подстановки в строках программы, следующих ниже по тексту (не важно, что замещающее выражение пусто), и все остальное содержимое файла, который мы присоединяем по директиве #include <math.h>, будет включено в текст наше программы. Но если такая подстановка ранее была заявлена, то повторное подключение файла math.h не произойдет. Это позволяет избежать дублирования констант и других макроопределений, которые могли бы появиться из-за повторения заголовочного файла.

Примерно такую же функцию выполняют три следующие строки. Первая из них проверяет, не состоялось ли ранее присвоение значения идентификатору ___DEFS_H. Если такого действия еще не было, то файл defs.h будет загружен в оперативную память. В противном случае фрагмент программы до строки #endif не будет передан компилятору. Кстати, подобная тройка строк присутствует во многих заголовочных файлах, и такая проверка предупреждает повторную загрузку файла defs.h.

Более детально, первая группа директив препроцессора выполняет следующие действия:

Таблица 12.1.
Директива Пояснение
#define name value Объявляет о необходимости замены выражения name на значение value в строках программы, расположенных ниже
#ifdef name Проверяет, была ли объявлена замена выражения name ( true, если была)
#ifndef name Проверяет, была ли объявлена замена выражения name ( true, если не была)
#undef name Отменяет указание о замене выражения name в строках программы, расположенных ниже

Вторая группа директив предназначена для включения или отключения фрагментов программы пользователя средствами, напоминающими действие условного оператора if – then – else:

#if (условие_1)     //если выполнено условие_1
  фрагмент_1
#elif (условие_2)   //если выполнено условие_2
  фрагмент_2
#elif (условие_3)   //если выполнено условие_3
  фрагмент_3
.............
#else               //если не выполнено ни одно из предшествующих условий
  фрагмент_k
#endif              //конец проверок

Если результат проверки дал положительный результат, то следующий за ним фрагмент программы будет передан компилятору. В противном случае это фрагмент исключается из текста исходной программы (не затирается, а просто не поступает на вход компилятора). Условия, которые задаются в директивах проверки, могут выполняться только над константными выражениями:

#define name 1
.............
#if (name==1)

Одним из достаточно частых применений группы #if...#endif является подключение или отключение отладочных выдач.

< Лекция 12 || Лекция 13: 12 || Лекция 14 >
Alexey Ku
Alexey Ku

Попробуйте часть кода до слова main заменить на 

#include "stdafx.h" //1

#include <iostream> //2
#include <conio.h>

using namespace std; //3

Александр Талеев
Александр Талеев

#include <iostream.h>
#include <conio.h>
int main(void)
{
int a,b,max;
cout << "a=5";
cin >> a;
cout <<"b=3";
cin >> b;
if(a>b) max=a;
else max=b;
cout <<" max="<<max;
getch();
return 0;
}

при запуске в visual express выдает ошибки 

Ошибка    1    error C1083: Не удается открыть файл включение: iostream.h: No such file or directory    c:\users\саня\documents\visual studio 2012\projects\проект3\проект3\исходный код.cpp    1    1    Проект3

    2    IntelliSense: не удается открыть источник файл "iostream.h"    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    1    1    Проект3

    3    IntelliSense: идентификатор "cout" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    6    1    Проект3

    4    IntelliSense: идентификатор "cin" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    7    1    Проект3

при создании файла я выбрал пустой проект. Может нужно было выбрать консольное приложение?