Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual_Studio_2010

.pdf
Скачиваний:
109
Добавлен:
03.03.2016
Размер:
5.94 Mб
Скачать

В алгоритме сортировки слиянием можно выделить три этапа.

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

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

3.Два упорядоченных массива половинного размера соединяются в один. Рекурсивное разбиение заданного массива происходит до тех пор, пока

размер массива не достигнет единицы. Далее решается нетривиальная задача соединения двух упорядоченных массивов в один.

Алгоритм сортировки слиянием был предложен Джоном фон Нейманом в 1945 г. Такая сортировка считается предпочтительной, когда требуется застраховаться от наихудшего случая, возможного при использовании быстрой сортировки [12].

Задание 7

1.Все вспомогательные функции программы разместите во внешние файлы с именами compX_1.c, compX_2.c и т. д., где Х – номер компьютера, на котором выполняется лабораторная работа. Протестируйте программу на примере и подсчитайте количество рекурсивных вызовов.

2.Подготовьте два неотсортированных массива и произведите их слияние в один отсортированный массив. Данные массивов задаются случайно по равномерному закону из интервалов [–5Х; 5Х] и [0; 10Х], где Х – номер компьютера, на котором выполняется лабораторная работа. Размерности массивов должны задаваться с клавиатуры.

3.Напишите программу по сортировке слиянием двух массивов, расположенных в текстовых файлах (внешняя сортировка слиянием). Результат сортировки разместите также в текстовом файле с именем compX.txt, где Х – номер компьютера, на котором выполняется лабораторная работа.

Контрольные вопросы

1.Когда следует применять рекурсивные алгоритмы?

2.Что такое «концевая» рекурсия?

3.Какие методы и приемы устранения «хвостовой» рекурсии Вам известны?

4.Какие проблемы могут возникать при реализации рекурсивных алгоритмов на электронных вычислительных машинах?

5.В чем отличие глубины рекурсии от рекурсивного вызова?

6.В каких задачах в программировании применение рекурсии оправданно?

7.Какая альтернатива есть у рекурсивных алгоритмов?

8.Можно ли обойтись без рекурсии?

341

 

БИБЛИОГРАФИЧЕСКИЙ СПИСОК

 

1.

Картавов С. А. Математические

термины :

справ.-библиогр.

 

слов. / С. А. Картавов. – Киев : Выща шк., 1988. – 295 с.

 

2.

Сэджвик Р. Фундаментальные алгоритмы на С++. Анализ / Структуры

 

данных / Сортировка / Поиск : пер. с

англ. / Роберт

Сэджвик. – Ки-

 

ев : ДиаСофт, 2001. – 688 с.

 

 

3.Новицкая Ю. В. Основы логического и функционального программирования : учеб. пособие / Ю. В. Новицкая. – Новосибирск : Изд-во НГТУ,

2006. – 60 с.

4.Дейтел Х. М. Как программировать на С : пер. с англ. / Х. М. Дейтел, П. Дж. Дейтел. – 4-е изд. – М. : Бином-Пресс, 2006. – 912 с.

5.Хэзфилд Р. Искусство программирования на С. Фундаментальные алгоритмы, структуры данных и примеры приложений. Энциклопедия программиста : пер. с англ. / Ричард Хэзвилд, Лоуренс Кирби. – Киев : Диа-

Софт, 2001. – 736 с.

6. Подбельский В. В. Программирование на языке Си : учеб. пособие / В. В. Подбельский, С. С. Фомин. – 2-е изд., доп. – М. : Финансы и статистика, 2007. – 600 с.

7.Прата С. Язык программирования С. Лекции и упражнения : пер. с англ. / С. Прата. – 5-е изд. – М. : Вильямс, 2006. – 960 с.

8.Шилдт Г. Полный справочник по С : пер. с англ. / Г. Шилдт. – 4-е изд. –

М. : Вильямс, 2007. – 704 с.

9.Порублев И. Н. Алгоритмы и программы. Решение олимпиадных задач / И. Н. Порублев, А. Б. Ставровский. – М. : Вильямс, 2007. – 480 с.

10.Мейер Б. Методы программирования : в 2-х т. : пер. с франц. / Б. Мейер,

К. Бодуэн. – М. : Мир, 1982. – Т. 2. – 368 с.

11.

Вирт Н. Алгоритмы и структуры

данных : пер. с англ. / Н. Вирт. –

 

М. : Мир, 1989. – 360 с.

 

12.

Хусаинов Б. С. Структуры и алгоритмы данных. Примеры на языке Си :

 

Учеб. Пособие / Б. С. Хусаинов. – М. : Финансы и статистика, 2004. –

 

464 с.

 

13.

С/С++. Архив программ / А. Фридман, Л. Кландер, М. Михаэлис,

 

Х. Шильдт. – М. : БИНОМ, 2001. – 640 с.

14.

Ахо А. В. Структуры данных и алгоритмы : учеб. пособие : пер. с

 

англ. / А. В. Ахо, Д. Хопкрофт, Д. Д.

Ульман. – М. : Вильямс, 2000. –

 

384 с.

 

342

Тема 19

ПРЕПРОЦЕССОР ЯЗЫКА С

Рассматриваются практически важные свойства препроцессора языка С и примеры типовых препроцессорных директив и конструкций.

ТЕОРЕТИЧЕСКАЯ ЧАСТЬ

Препроцессор (англ. preprocessor) – программа, выполняющая предварительную обработку входных данных для другой программы [1]. Препроцессор языка программирования С просматривает программу до компилятора и заменяет в ней определенные сочетания символов (символические аббревиатуры) на соответствующие директивы. Он отыскивает и подключает к программе необходимые файлы и может изменить условия компиляции [1]. Препроцессор имеет тот же смысл, что и буферный процессор.

Препроцессор языка С выполняет макроподстановку, условную компиляцию и включение именованных файлов. Строки, начинающиеся со знака # (перед которыми разрешены символы пустого пространства), задают препроцессору инструкции-директивы. Их синтаксис не зависит от остальной части языка, они могут фигурировать где угодно и оказывать влияние (независимо от области действия) вплоть до конца единицы трансляции. Границы строк принимаются во внимание: каждая строка анализируется отдельно (но есть возможность и сцеплять их). Лексемами для препроцессора являются все лексемы языка и последовательность символов, задающие имена файлов. Кроме того, любой символ, не определенный каким-либо другим способом, также воспринимается как лексема [2]. Влияние символов пустого пространства, отличающихся от пробелов и горизонтальных табуляций, внутри строк препроцессора не определено.

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

Имеются следующие директивы препроцессора:

#define

#endif

#ifdef

#line

#elif

#error

#ifndef

#pragma

#else

#if

 

#include

#undef

Каждая директива препроцессора должна занимать отдельную строку.

Например, строка

 

 

 

 

 

 

 

 

 

#include <stdio.h>

#include <stdlib.h>

 

 

 

 

 

 

 

 

рассматривается как недопустимая [3].

343

Директива #define

Директива #define определяет идентификатор и последовательность символов, которые будут подставляться вместо идентификатора каждый раз, когда он встретится в исходном файле. Идентификатор называется именем макроса, а сам процесс замены – макрозаменой (макрорасширением, макрогенерацией, макроподстановкой) [3]. В общем виде директива #define выглядит следующим образом (должны использоваться буквы латинского алфавита):

#define имя_макроса последовательность_символов

Вопределении, как правило, в конце последовательности символов точка

сзапятой не ставится. Между идентификатором и последовательностью символов может быть любое количество пробелов, но признаком конца последовательности может быть только разделитель строк [3]. Имена макросов обычно

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

ределениями с параметрами и макросами, напоминающими функции) [3].

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

Программный код решения примера

#include <stdio.h>

#include <conio.h>

#include <stdlib.h> #include <time.h>

#define MAX(a,b,c) ((1+rand()%(b)) > ((a)+(b))/2 ) ? (c):(b)

int main (void) { int a, b, c;

srand((unsigned) time(NULL));

printf("\n Enter a, b, c: "); scanf_s("%d%d%d", &a, &b, &c);

printf("\n MAX(a,b,c): %d\n", MAX(a,b,c));

printf("\n\n ... Press any key: ");

_getch(); return 0;

}

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

344

Использование вместо настоящих функций макросов с формальными параметрами (т. е. a, b, с) дает преимущество: увеличивается скорость выполнения кода, потому что в таких случаях не надо тратить ресурсы на вызов функций. Кроме того, макрос не чувствителен к типу данных, т. е. в нем отсутствует проверка типов аргументов. Однако если у макроса с формальными параметрами очень большие размеры, то тогда из-за дублирования кода повышение скорости достигается за счет увеличения размеров программы [3]. Кроме того, в конструировании макроса следует быть очень внимательным. Как правило, макросы используются для небольших пользовательских функций [4].

Директива #error

Директива #error заставляет компилятор прекратить компиляцию [3], используется в основном для отладки. В общем виде выглядит следующим образом:

#error сообщение – об – ошибке

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

Директива #include

Директива #include дает указание компилятору читать еще один исходный файл в дополнение к тому, в котором находится сама директива [3]. Имя исходного (подключаемого) файла должно быть заключено в двойные кавычки или угловые скобки. Обычно имена стандартных заголовочных файлов берут в угловые скобки, а кавычки приберегают для имен специальных файлов, относящихся к конкретной программе. Способ поиска файла зависит от того, в кавычках или угловых скобках находится его имя. Если имя заключено в угловые скобки, то поиск файла проводится тем способом, который определен в компиляторе. Часто это означает поиск определенного каталога, специально предназначенного для хранения таких файлов. Если имя заключено в кавычки, то поиск файла проводится другим способом. Во многих компиляторах это означает поиск файла в текущем рабочем каталоге. Если же файл не найден, то поиск повторяется уже так, как будто имя файла заключено в угловые скобки

[3].

Многие заголовочные файлы могут быть расположены в теле функции, например в main().

Файлы, имена которых находятся в директивах #include, могут в свою очередь содержать другие директивы #include. Они называются вложенными. Количество допустимых уровней вложенности у разных компиляторов может быть разным. Однако в стандарте С89 предусмотрено, что компиляторы должны допускать не менее 8 таких уровней [3].

345

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

Директивы условной компиляции дают возможность выборочно компилировать части исходного кода программы. Этот процесс называется условной компиляцией [3].

Директива #if выглядит следующим образом:

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

последовательность операторов программного кода

#endif

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

Директива условной компиляции #else используется практически так же, как в обычном условном операторе языка С: if – else. Дополнительная директива условной компиляции #else в общем случае имеет вид

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

последовательность операторов программного кода

#else

альтернативная последовательность операторов программного кода

#endif

Аналогично используются директивы #elif (else if), которые в общем случае имеют следующий вид:

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

последовательность операторов программного кода

#elif 2_ константное_выражение

2_ я_последовательность операторов программного кода

#elif 3_ константное_выражение

3_ я_последовательность операторов программного кода

.

.

.

#elif N_ константное_выражение

N_ я_последовательность операторов программного кода

#else

альтернативная последовательность операторов программного кода

#endif

Если константное выражение в директиве #elif истинно (не нулевое, например), то будет компилироваться соответствующая последовательность операторов программного кода. При этом другие выражения в директивах #elif проверяться уже не будут, в том числе директива #else.

346

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

В соответствии со стандартом С89 у директив #if и #elif может быть не менее 8 уровней вложенности. При вложенности каждая директива #endif, #else или #elif относится к ближайшей директиве #if или #elif [3]. Каждая директива #if сопровождается директивой #endif.

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

#ifdef имя_макроса

последовательность операторов

#endif

Директива условной компиляции #ifdef означает «if defined» (если определено) [3]. Последовательность операторов будет компилироваться, если имя макроса было определено ранее с помощью директивы #define.

Директива условной компиляции #ifndef означает «if not defined» (если не определено), в общем виде выглядит следующим образом:

#ifndef имя_макроса последовательность операторов

#endif

Последовательность операторов будет компилироваться, если имя макроса еще не определено директивой #define. В директивах #ifdef и #ifndef можно использовать #else или #elif. Согласно стандарту С89 допускается не менее 8 уровней #ifdef и #ifndef.

Директива #undef удаляет заданное определение имени макроса, т. е. «аннулирует» его определение; само имя макроса должно находиться после директивы [3]. Используется в основном для того, чтобы локализовать имена макросов в тех участках кода, где они нужны. В общем случае эта директива выглядит следующим образом:

#undef имя_макроса

Для того чтобы узнать, определено ли имя макроса, можно использовать директиву #if в сочетании с оператором времени компиляции defined [3], который выглядит следующим образом:

defined имя_макроса

Если имя_макроса определено, то выражение считается истинным, в противном случае – ложным.

Единственная причина, по которой используется оператор defined, состоит в том, что с его помощью в #elif можно узнать, определено ли имя макроса [3].

347

Директива #line

Директива #line изменяет содержимое __LINE__ и __FILE__, которые являются зарезервированными идентификаторами (макросами) в компиляторе. В первом из них находится номер компилируемой в данный момент строки программного кода программы [3], а второй идентификатор – это строка, содержащая имя компилируемого исходного файла.

Директива #line выглядит следующим образом:

#line номер "имя_файла"

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

Директива #line в основном используется для отладки и специальных применений [3].

Предопределенные символические константы

В языке С определены пять встроенных, предопределенных имен макрокоманд (символических констант) [2–5] (табл. 19.1). Они начинаются и заканчиваются двумя символами подчеркивания.

 

 

Таблица 19.1

 

Предопределенные символические константы

 

 

 

Символическая кон-

 

Объяснение

станта

 

 

__LINE__

 

Возвращает целую константу для номера текущей обрабатывае-

 

 

мой строки исходного кода программы

 

 

 

__FILE__

 

По умолчанию возвращает в виде строки символов имя компили-

 

 

руемого исходного файла

 

 

 

__DATE__

 

Возвращает в виде строки символов дату (мм дд гг) начала ком-

 

 

пиляции текущего исходного файла

 

 

 

__TIME__

 

Возвращает в виде строки символов время (чч:мм:сс) начала

 

 

компиляции текущего исходного файла

 

 

 

__STDC__

 

Возвращает целую константу 1, которая указывает на то, что

 

 

данная реализация совместима со стандартом ANSI

 

 

 

Операторы препроцессора # и ##

Операторы # и ## применяются в сочетании с директивой #define [3], предусмотрены для работы препроцессора в некоторых особых случаях [3; 5].

348

Оператор #, который обычно называют оператором превращения в строку (stringize), трансформирует аргумент, перед которым стоит, в строку, заключенную в кавычки. Должен использоваться в макросах с аргументами, поскольку операнд после # ссылается на аргумент макроса [5].

Оператор ##, называемый оператором склеивания (pasting), или конкатенации, конкатенирует две лексемы. Операция ## должна иметь два операн-

да [5].

Директива #pragma

Директива #pragma – это определяемая реализацией директива, которая позволяет передавать компилятору различные инструкции [3; 4]. Возможности этой директивы следует изучать по документации по компилятору.

Макрос подтверждения assert

Макрос assert, определенный в заголовочном файле assert.h, проверяет значение выражения [5]. Если оно равно 0 (ложное), то assert распечатывает (например, выводит на консоль) сообщение об ошибке и вызывает функцию abort() (из библиотеки stdlib.h), завершающую работу программы. Например, в программе переменная х должна принимать значения, не превышающие 10. Для проверки и подтверждения такого условия в программу можно включить строку

assert( x <= 10 );

Если при выполнении данного макроса переменная х окажется больше 10, то выдается сообщение, содержащее номер строки и имя файла (например, main.c), в котором нарушено условие, а выполнение программы при этом прерывается. Формат выводимого сообщения зависит от конкретной реализации системы программирования [3]. С помощью assert можно производить отладку программы во многих ее местах. Когда программа будет отлажена, то действие данного макроса можно устранить с помощью символической константы NDEBUG (not debugging – без отладки). Для этого перед заголовочным файлом assert.h следует вставить строку

#define NDEBUG

349

ПРАКТИЧЕСКАЯ ЧАСТЬ

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

Программный код решения примера

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h>

#include <conio.h>

// Макрос с формальными параметрами

#define SIMPLE(x, d, b) for(d = 2; d < x; d++) \ if (!(x%d)) b = 0; \

if (b) puts("\n It is the simple number"); \ else puts("\n It is not the simple number");

int main (void) {

int b = 1, d = 0, x = 13;

printf("\n Enter the natural number: "); scanf("%d", &x);

// Макрос с действительными параметрами

SIMPLE(x, d, b);

printf("\n %5sTime: %s\n Version C: %d \n", "", \ __TIME__, __STDC__);

printf("\n ... Press any key: "); _getch();

return 0;

}

В первой строке программы включена символическая константа для исключения вывода предупреждения относительно функции scanf() в среде MS Visual Studio 2010. В программе показано применение макроса функции с тремя формальными параметрами и несколькими строками программного кода.

Результат выполнения программы приведен на рис. 19.1.

Рис. 19.1. Проверка введенного числа на простоту

350

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]