Будь умным!


У вас вопросы?
У нас ответы:) SamZan.net

Лабораторная работа 4 Включаемые файлы и области видимости директивы препроцессора языка C Цель- Научит

Работа добавлена на сайт samzan.net:


Программирование на языке C

Лабораторная работа 4

Включаемые файлы и области видимости, директивы препроцессора языка C

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

Основные теоретические сведения

Область видимости программных объектов

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

1. Переменная, объявленная глобально (т.е. вне всех блоков), существует на протяжении всего времени выполнения программы.

2. Локальные переменные (т.е. объявленные внутри блока) с классом памяти register или auto, имеют время жизни только на период выполнения того блока, в котором они объявлены. Если локальная переменная объявлена с классом памяти static или extern, то она имеет время жизни на период выполнения всей программы.

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

1. Переменная, объявленная или определенная глобально, видима от точки объявления или определения до конца исходного файла. Можно сделать переменную видимой и в других исходных файлах, для чего в этих файлах следует ее объявить с классом памяти extern.

2. Переменная, объявленная или определенная локально, видима от точки объявления или определения до конца текущего блока. Такая переменная называется локальной.

3. Переменные из объемлющих блоков, включая переменные объявленные на глобальном уровне, видимы во внутренних блоках. Эту видимость называют вложенной. Если переменная, объявленная внутри блока, имеет то же имя, что и переменная, объявленная в объемлющем блоке, то это разные переменные, и переменная из объемлющего блока во внутреннем блоке будет невидимой.

4. Функции с классом памяти static видимы только в исходном файле, в котором они определены. Всякие другие функции видимы во всей программе.

Метки в функциях видимы на протяжении всей функции.

Имена формальных параметров, объявленные в списке параметров прототипа функции, видимы только от точки объявления параметра до конца объявления функции.

При инициализации необходимо придерживаться следующих правил:

1. Объявления содержащие спецификатор класса памяти extern не могут содержать инициаторов.

2. Глобальные переменные всегда инициализируются, и если это не сделано явно, то они инициализируются нулевым значением.

3. Переменная с классом памяти static может быть инициализирована константным выражением. Инициализация для них выполняется один раз перед началом программы. Если явная инициализация отсутствует, то переменная инициализируется нулевым значением.

4. Инициализация переменных с классом памяти auto или register выполняется всякий раз при входе в блок, в котором они объявлены. Если инициализация переменных в объявлении отсутствует, то их начальное значение не определено.

5. Начальными значениями для глобальных переменных и для переменных с классом памяти static должны быть константные выражения. Адреса таких переменных являются константами и эти константы можно использовать для инициализации объявленных глобально указателей. Адреса переменных с классом памяти auto или register не являются константами и их нельзя использовать в инициаторах. 

Пример:

int global_var;

int func(void)

{ int local_var; /* по умолчанию auto  */

 static int *local_ptr=&local_var; /*  так неправильно   */

 static int *global_ptr=&global_var; /*  а так правильно   */

 register int *reg_ptr=&local_var;  /*  и так правильно   */

}

В приведенном примере глобальная переменная global_var имеет глобальное время жизни и постоянный адрес в памяти, и этот адрес можно использовать для инициализации статического указателя global_ptr. Локальная переменная local_var, имеющая класс памяти auto размещается в памяти только на время работы функции func, адрес этой переменной не является константой и не может быть использован для инициализации статической переменной local_ptr. Для инициализации локальной регистровой переменной reg_ptr можно использовать неконстантные выражения, и, в частности, адрес переменной local_ptr

Заголовочные (включаемые) файлы

Типы во всех описаниях одного и того же объекта должны быть  согласованными. Один из способов это достичь состоит в обеспечении того, что исходный текст,  как он передается на рассмотрение компилятору, или согласован, или содержит информацию, которая позволяет компилятору обнаружить несогласованности.  Один несовершенный,  но простой способ достичь согласованности состоит во включении  заголовочных  файлов, содержащих интерфейсную информацию, в исходные файлы, в которых содержится исполняемый код и/или определения данных.

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

#include "to_be_included"

замещает строку,  в которой встретилось #include,  содержимым файла "to_be_included". Его содержимым должен быть исходный текст на С++, поскольку дальше его будет читать компилятор. Часто включение обрабатывается отдельной программой, называемой C препроцессором, которую команда C вызывает для преобразования исходного файла, который дал программист,  в файл без директив включения перед тем,  как начать собственно компиляцию. В другом варианте эти директивы обрабатывает  интерфейсная  система  компилятора  по  мере того,  как они встречаются в исходном тексте. Для включения файлов  из стандартной  директории включения вместо кавычек используются угловые скобки < и >.

Например:

#include <stream.h>      // из стандартной директории включения

#define "myheader.h"     // из текущей директории

Использование <>  имеет  то преимущество,  что в программу фактическое имя директории включения не встраивается (как правило,  сначала просматривается include). К сожалению, пробелы в директиве include существенны:

#include < stream.h > // не найдет <stream.h>

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

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

В заголовочном файле могут содержаться:

 Определения типов struct point { int x,  y;  }

 Описания функций extern  int  strlen(const  char*);

 Определения  inline-функций (для С++) inline  char get() { return *p++;  }

 Описания данных extern int a;

 Определения констант const float pi = 3.141593

 Перечисления enum   bool   {  false,  true  };

 Директивы  include  #include <signal.h>

 Определения макросов #define case break;case

 Комментарии /* проверка на конец файла */

но никогда

 Определения обычных функций char get() { return *p++; }

 Определения данных int a;

 Определения сложных  константных  объектов const tbl[] = { /*... */ }

Принято,  что  заголовочные  файлы  имеют  суффикс (расширение) .h.  Файлы, содержащие определение данных или функций, должны иметь суффикс .c.  Такие файлы часто называют, соответственно,  ".h файлы" и ".c файлы". Следует заметить,  что в С++  макросы  гораздо  менее  полезны,  чем  в  C, поскольку С++ имеет такие языковые конструкции, как const для определения констант и inline для исключения расходов на вызов функции.

Причина того,  почему в заголовочных файлах допускается определение   простых  констант,  но  не  допускается  определение  сложных константных объектов,  прагматическая.  В принципе,  сложность  тут только в том, чтобы сделать допустимым дублирование определений переменных (даже определения функций можно было бы дублировать).  Однако  для  компоновщиков  старого  образца слишком трудно проверять тождественность нетривиальных констант и убирать ненужные  повторы. Кроме  того,  простые  случаи гораздо более обиходны и потому более важны для генерации хорошего кода.

Директивы препроцессора

Директивы препроцессора представляют собой инструкции, записанные в тексте программы на СИ, и выполняемые до трансляции программы. Директивы препроцессора позволяют изменить текст программы, например, заменить некоторые лексемы в тексте, вставить текст из другого файла, запретить трансляцию части текста и т.п. Все директивы препроцессора начинаются со знака #. После директив препроцессора точка с запятой не ставятся. 

Директива #include

Директива #include включает в текст программы содержимое указанного файла. Эта директива имеет две формы: 

#include "имя файла"

#include <имя файла>

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

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

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

Директива #define

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

Директива #define имеет две синтаксические формы: 

#define  идентификатор [текст]

#define  идентификатор(список параметров) текст

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

Пример: 

#define WIDTH 80

#define LENGTH (WIDTH+10)

Эти директивы изменят в тексте программы каждое слово WIDTH на число 80, а каждое слово LENGTH на выражение (80+10) вместе с окружающими его скобками.

Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, и в результате получится 780, а не 630.

В первой форме записи текст может и отсутствовать. Такой формат применяется специально совместно с директивами условной компиляции #ifdef #ifndef #else #endif для определения флагов, изменяющих процесс компиляции. Этому случаю также соответствует определение через настройки проекта.

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

При макровызове вслед за идентификатором записывается список фактических аргументов, количество которых должно совпадать с количеством формальных параметров.

Пример:

#define  MAX(x,y) ((x)>(y))?(x):(y)

Эта директива заменит фрагмент

t=MAX(i,s[i]);

на фрагмент

t=((i)>(s[i])?(i):(s[i]);

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

Например, при наличии скобок фрагмент

t=MAX(i&j,s[i]||j);

будет заменен на фрагмент

t=((i&j)>(s[i]||j)?(i&j):(s[i]||j);

а при отсутствии скобок - на фрагмент

t=(i&j>s[i]||j)?i&j:s[i]||j;

в котором условное выражение вычисляется в совершенно другом порядке.

Директива #undef

Директива #undef используется для отмены действия директивы #define. Синтаксис этой директивы следующий #undef идентификатор 

Директива отменяет действие текущего определения #define для указанного идентификатора. Не является ошибкой использование директивы #undef для идентификатора, который не был определен директивой #define. 

Пример: 

#undef WIDTH

#undef MAX

Эти директивы отменяют определение именованной константы WIDTH и макроопределения MAX.

Условная компиляция.

Borland C поддерживает  условную  компиляцию, заменяя соответствующие строки исходного кода пустой строкой.  Строки, которые  начинаются  с  #,  игнорируются  (кроме директив #if, #ifdef, #ifndef, #else, #elif и #endif), также как все строки, которые не должны компилироваться как результат директив. Все директивы условной компиляции должны  полностью  находиться  в исходном или во включаемом файле, в котором они начинаются.

Директивы условной компиляции #if, #elif, #else, #endif.

Директивы условной компиляции #if,  #elif,  #else, #endif работают как обычные условные операторы С. Они используются:

#if константное-выражение1

секция1

[#elif константное-выражение2 новая-строка секция2]

...

[#elif константное-выражениеn новая-строка секцияn]

...

[#else последняя-секция]

#endif

Если константное-выражение1  вычисляется  не  в 0 (true), строки кода (возможно пустые),  представленные секцией1,  либо это строки с командами препроцессора, либо это строки исходного кода обрабатываются препроцессором и передаются в  компилятор Borland C.  Иначе,  если константное-выражение1 вычисляется в 0 (false),  секция1 игнорируется (не макрорасширяется и не компилируется).

В случае true после препроцессорной обработки секции1 управление  передается на соответствующую #endif (которая заканчивает условный эпизод),  и продолжается со следующей секцией. В  случае false управление передается на следующую #elif (если она есть) и вычисляется  константное-выражение2  и  т.д.  Если true, то секция2 обрабатывается, после чего управление переходит  на  соответствующий  #endif.  В  противном  случае,  если константное-выражение2 false,  то управление переходит на следующую #elif,  и т.д.,  до тех пор,  пока не  исчерпаются  все #else  или #endif.  Необязательная #else используется как альтернативный вариант в случае,  если все предыдущие  тесты  вычислялись в false.  #endif заканчивает условную последовательность.

Секции могут  содержать  директиву  условной  компиляции, вложенную на любую глубину; каждая #if должна быть сбалансирована закрывающей #endif.

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

Константные выражения    должны   вычисляться   в   целое константное значение.

Оператор defined.

Оператор defined обеспечивает альтернативный,  более гибкий способ проверки, была ли определена комбинация идентификаторов или нет. Он допустим только в выражениях #if и #elif. 

defined(идентификатор)

Это выражение вычисляется  в  1  (true), если символ идентификатор был предварительно определен (используя #define) и не был отключен (используя #undef);  в противном случае он вычисляется в 0 (false). Так директива

#if defined (mysym)

аналогична

#ifdef mysym

Преимущество defined  в  том,  что Вы можете использовать его в сложных выражениях в #if, таких как

#if defined(mysym) && !defined(yoursym)

Директивы условной компиляции #ifdef и #ifndef.

Директивы условной  компиляции #ifdef и #ifndef позволяют Вам проверять определен идентификатор или нет, то есть, была ли обработана предыдущая команда #define для этого идентификатора и находится ли она еще в силе. Строка

#ifdef идентификатор

действует как

#if 1

если идентификатор определен, и действует как

#if 0

если идентификатор неопределен.

#ifndef

возвращает true для "неопределенного" условия, и строка

#ifndef идентификатор

действует как

#if 0

если идентификатор определен, и действует как

#if 1

если идентификатор неопределен.

Синтаксис использования  такой  же,  как для #if,  #elif, #else, #endif.

Идентификатор, определенный как NULL считается определенным.

Управляющая директива #line.

Вы можете использовать команду #line для передачи номеров строк  в  программу  для  перекрестных ссылок и выдачи ошибок. Если программа состоит из частей, включенных из другого файла, бывает  полезно пометить такие части номерами строк оригинального файла, а не номерами строк результирующей программы. Синтаксис

#line целая-константа <"имя-файла">

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

Например

#include <stdio.h>

#line 4 "junk.c"

 

void main()

{

 printf("in line %d of %s", __LINE__, __FILE__); #line 12 "temp.c"

 printf("\n");

 printf(" in line %d of %s", __LINE__, __FILE__);

 #line 8

 printf("\n");

 printf("in line %d of %s", __LINE__, __FILE__);

}

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

Директива #error.

Директива #error имеет синтаксис:

#error сообщение

Она выдает сообщение

Error: имя-файла строка# : error directive: сообщение

Эта директива  обычно используется с условными директивами,  чтобы отловить некоторые  нежелательные  условия  времени компиляции. Обычно это условие будет false. Если условие true, Вы хотите,  чтобы компилятор вывел сообщение об ошибке и остановил  компиляцию.  Вы делаете это,  поместив директиву #error внутри условия, которое будет true для нежелательного случая. 

Например, предположим Вы определили #define MYVAL,  которое должно быть 0 или 1.  Вы можете включить следующее выражение  в  исходный  код  для  проверки  на некорректное значение MYVAL:

#if (MYVAL != 0 && MYVAL != 1)

#error MYVAL должен быть определен от 0 до 1

#endif

Директива #pragma.

Директива #pragma применяется для директив,  зависимых от реализации компилятора:

#pragma имя-директивы

С помощью #pragma Borland C может определить директивы, которые не конфликтуют с другими компиляторами, поддерживающими #pragma.  Если компилятор не распознает имя  директивы,  он игнорирует  директиву #pragma без выдачи ошибок или предупреждений. За полным списком опций директивы (имя-директивы) и их значением необходимо обращаться к справочной документации компилятора. Обычно с помощью директивы можно переопределить или назначить параметры компиляции программы, при этом параметры, заданные при помощи этой директивы имеют приоритет над параметрами, заданными в настройках проекта либо в командной строке.

Задание

Организовать включаемые файлы для программы, разработанной в результате лабораторной работы3, разбив описания функций и необходимых переменных по различным файлам (мин. два файла *.c и соответствующих им *.h). Организовать компиляцию функции обработки символа в двух различных вариантах, определяемых по наличию определения (#define). В качестве первого варианта компиляции взять разработанную на предыдущей лабораторной работе программу. В качестве второго варианта взять вариант следующего по списку студента из предыдущей лабораторной работы.

Задание на предыдущую лабораторную работу (№3): Составить программу обработки текста, считываемого из файла. Для чего разработать функцию для обработки текста с переменным числом параметров, в качестве параметров она должна принимать значения текстовых предложений (разделитель - .), строк (разделитель - \n) или слов (разделитель – пробел или . , ! ? \n) (по варианту задания) для обработки и возвращать указатель на обработанный текст. В качестве первого параметра – имя функции (указатель), используемой для перевода символов из одного формата в другой, которую определить ниже по тексту программы. Данная функция должна вызываться через переданный указатель и принимать обрабатываемый(-ые) символ(ы), возвращая результирующий. Обработанный текст вывести в результирующий файл. В отчете привести исходный и обработанный текст.

Варианты задания предыдущей Л.Р. (№3)

Вариант задания рассчитывается по номеру студента в журнале преподавателя.

Вариант

Функция с переменным числом параметров получает

Функция обработки символа

Вариант

Функция с переменным числом параметров получает

Функция обработки символа

1

Строки

Изменение регистра на противоположный (рус)

15

Слова

Изменение регистра на противоположный (англ)

2

Слова

Исправление неверной раскладки (с рус на англ)

16

Строки

Исправление неверной раскладки (с англ на рус)

3

Предложения

Все буквы – прописные (рус)

17

Строки

Все буквы – прописные (англ)

4

Слова

Все буквы строчные (англ)

18

Предложения

Все буквы строчные (рус)

5

Строки

Все строки – с загл. Буквы

19

Строки

Все строки – с мал. Буквы

6

Предложения

Замена всех гласных (рус) на *

20

Предложения

Замена всех согласных (рус) на #

7

Строки

Замена всех загл. (рус) на ~

21

Строки

Замена всех загл. (англ) на $

8

Слова

Замена всех гласных (англ) на $

22

Слова

Замена всех согласных (англ) на $

9

Предложения

Замена более двух подряд повторов символов на ^

23

Предложения

Замена двух и более загл. Символов (рус) на *

10

Слова

Слова – с загл. буквы

24

Слова

Слова – с мал. буквы

11

Строки

Замена двух и более загл. Символов (англ) на $

25

Строки

Строки нач. с мал. буквы, все остальные – большие (рус)

12

Предложения

Замена всех цифр на буквы: 0 – а, 1 – б

26

Предложения

Замена всех загл. (рус) на ~

13

Строки

Исправление ошибочного нажатия Shift при введении цифр

27

Строки

Строки нач. с мал. буквы, все остальные – большие (англ)

14

Слова

Слова нач. с мал. буквы, все остальные – большие (укр)

28

Слова

Слова нач. с мал. буквы, все остальные – большие (англ)

Содержание отчета и литература. Содержание отчета и список рекомендуемой литературы совпадает с указанными в указаниям к лабораторной работе 1.




1. Учебное пособие- Депривационные феномены как причина и следствие нарушенного развития
2. Авторская позиция в романе Обрыв
3. Тема 1 Основы бухгалтерского финансового учета Бухгалтерский учет как сплошное непрерывное взаимосвязан
4. Лекция 11. элементы квантовой механики [1] гл
5. Відкриття періодичного закону Менделєєва
6. Совладание с социальными стереотипами
7. Тема- Звук К 1
8. 20 року слідчий посада найменування органу ініціали
9. реферат дисертації на здобуття наукового ступеня кандидата економічних наук Київ
10. Лабораторна робота 67 Потрібно- 1
11. то легко определить тем более что среди защитников единосущности были такие например богословы как св
12. Затверджую керівник відділу з навчальної роботи Нестеренко Л
13. Право и правовые нормы
14. Реферат- Озоновый слой - проблема XXI века.html
15. WORLDTEM на летний сезон 2014 года
16. Асоціальна поведінка підлітків.html
17. 2014 навчальний рік вул
18. темам- устойчивое развитие оценка ценности природы 1
19. Пережитки язичництва в культурі Київської Русі Х-ХІІІ ст
20. Альпы